X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/09f3d4e679f5742c54fb062ede846b77606a9c5b..7e4501ee3cc88ee8dd2b75e2964118e2897ec4a2:/wxPython/tools/XRCed/xxx.py diff --git a/wxPython/tools/XRCed/xxx.py b/wxPython/tools/XRCed/xxx.py index 0459af1a2f..47d8b0a03f 100644 --- a/wxPython/tools/XRCed/xxx.py +++ b/wxPython/tools/XRCed/xxx.py @@ -1,7 +1,8 @@ -# Name: xxx.py (xxx is easy to distinguish from 'wx' :) ) +# Name: xxx.py ('xxx' is easy to distinguish from 'wx' :) ) # Purpose: XML interface classes # Author: Roman Rolinsky # Created: 22.08.2001 +# RCS-ID: $Id$ from wxPython.wx import * from wxPython.xrc import * @@ -10,25 +11,93 @@ import wxPython.lib.wxpTag from params import * +# Parameter value class +class xxxParam: + # Standard use: for text nodes + def __init__(self, node): + self.node = node + if not node.hasChildNodes(): + # If does not have child nodes, create empty text node + text = tree.dom.createTextNode('') + node.appendChild(text) + else: + text = node.childNodes[0] # first child must be text node + assert text.nodeType == minidom.Node.TEXT_NODE + self.textNode = text + # Value returns string + def value(self): + return str(self.textNode.data) + def update(self, value): + self.textNode.data = value + def remove(self): + self.node.parentNode.removeChild(self.node) + self.node.unlink() + +# Content parameter +class xxxParamContent: + def __init__(self, node): + self.node = node + data, l = [], [] # data is needed to quicker value retrieval + nodes = node.childNodes[:] # make a copy of the child list + for n in nodes: + if n.nodeType == minidom.Node.ELEMENT_NODE: + assert n.tagName == 'item', 'bad content content' + if not n.hasChildNodes(): + # If does not have child nodes, create empty text node + text = tree.dom.createTextNode('') + node.appendChild(text) + else: + # !!! normalize? + text = n.childNodes[0] # first child must be text node + assert text.nodeType == minidom.Node.TEXT_NODE + l.append(text) + data.append(text.data) + else: # remove other + node.removeChild(n) + n.unlink() + self.l, self.data = l, data + def value(self): + return self.data + def update(self, value): + # If number if items is not the same, recreate children + if len(value) != len(self.l): # remove first if number of items has changed + for n in self.node.childNodes: + self.node.removeChild(n) + l = [] + for str in value: + itemElem = tree.dom.createElement('item') + itemText = tree.dom.createTextNode(str) + itemElem.appendChild(itemText) + self.node.appendChild(itemElem) + l.append(itemText) + else: + for i in range(len(value)): + self.l[i].data = value[i] + self.data = value + +################################################################################ + # Classes to interface DOM objects class xxxObject: - # Param ids for controls - ID_CHECK_PARAMS = wxNewId() - ID_TEXT_PARAMS = wxNewId() # Default behavior hasChildren = false # has children elements? + hasStyle = true # almost everyone hasName = true # has name attribute? isSizer = hasChild = false + # Style parameters (all optional) + styles = ['fg', 'bg', 'font', 'enabled', 'focused', 'hidden', 'tooltip'] + # Special parameters + specials = [] # Required paremeters: none by default required = [] # Default parameters with default values default = {} # Parameter types paramDict = {} - # Additional styles - styles = [] + # Window styles and extended styles + winStyles = [] # Tree icon index - image = -1 + #image = -1 # Construct a new xxx object from DOM element # parent is parent xxx object (or None if none), element is DOM element object def __init__(self, parent, element): @@ -43,86 +112,51 @@ class xxxObject: nodes = element.childNodes[:] for node in nodes: if node.nodeType == minidom.Node.ELEMENT_NODE: - if node.tagName == 'object': + tag = node.tagName + if tag == 'object': continue # do nothing for object children here - if not node.tagName in self.allParams: + if not tag in self.allParams and not tag in self.styles: print 'WARNING: unknown parameter for %s: %s' % \ - (self.className, node.tagName) - if node.tagName == 'content': # has items - # Param value is a list of text nodes - l = [] - nodes = node.childNodes[:] - for n in nodes: - if n.nodeType == minidom.Node.ELEMENT_NODE: - assert n.tagName == 'item', 'bad content content' - if not n.hasChildNodes(): - # If does not have child nodes, create empty text node - text = tree.dom.createTextNode('') - node.appendChild(text) - else: - # !!! normalize? - text = n.childNodes[0] # first child must be text node - assert text.nodeType == minidom.Node.TEXT_NODE - l.append(text) - else: - node.removeChild(n) - n.unlink() - self.params[node.tagName] = l + (self.className, tag) + elif tag in self.specials: + self.special(tag, node) + elif tag == 'content': + self.params[tag] = xxxParamContent(node) + elif tag == 'font': # has children + self.params[tag] = xxxParamFont(self, node) else: # simple parameter - if not node.hasChildNodes(): - # If does not have child nodes, create empty text node - text = tree.dom.createTextNode('') - node.appendChild(text) - else: - text = node.childNodes[0] # first child must be text node - assert text.nodeType == minidom.Node.TEXT_NODE - self.params[node.tagName] = text + self.params[tag] = xxxParam(node) else: # Remove all other nodes element.removeChild(node) node.unlink() - # Generate HTML def generateHtml(self, prefix=''): SetCurrentXXX(self) - html = '
\ + html = '' % self.className # Has id (name) attribute if self.hasName: html += """\ """ % (self.ID_TEXT_PARAMS, self.name) + +""" html += '
\ %s - - - - -

' html += '\n' # Add required parameters for param in self.allParams: # Add checkbox or just text if param in self.required: - html += '' % param + html += '' % param else: # optional parameter html += """\ -""" % (self.ID_CHECK_PARAMS, param, param + ' ') - # Add value part - if self.params.has_key(param): - if param == 'content': - l = [] - for text in self.params[param]: - l.append(str(text.data)) # convert from unicode - value = str(l) - else: - value = "('" + self.params[param].data + "')" - else: - value = "('')" + +""" % (paramIDs[param], prefix + 'check_' + param, param) # Get parameter type try: # Local or overriden type @@ -137,12 +171,9 @@ class xxxObject: html += """\ -""" % (typeClass, self.ID_TEXT_PARAMS, - prefix + param, value, prefix + param) +""" % (typeClass, -1, prefix + 'data_' + param) html += '
%s:
%s:
- + -%s: %s: - - - +
\n' return html # Returns real tree object @@ -161,6 +192,40 @@ class xxxObject: ################################################################################ +class xxxParamFont(xxxParam): + allParams = ['size', 'style', 'weight', 'family', 'underlined', + 'face', 'encoding'] + def __init__(self, parent, element): + xxxObject.__init__(self, parent, element) + self.parentNode = element # required to behave similar to DOM node + v = [] + for p in self.allParams: + try: + v.append(str(self.params[p].data)) + except KeyError: + v.append('') + self.data = v + def update(self, value): + # `value' is a list of strings corresponding to all parameters + elem = self.element + for node in elem.childNodes: + elem.removeChild(node) + i = 0 + self.params.clear() + v = [] + for param in self.allParams: + if value[i]: + fontElem = tree.dom.createElement(param) + textNode = tree.dom.createTextNode(value[i]) + self.params[param] = textNode + fontElem.appendChild(textNode) + elem.appendChild(fontElem) + v.append(value[i]) + i += 1 + self.data = v + +################################################################################ + class xxxContainer(xxxObject): hasChildren = true @@ -169,21 +234,61 @@ class xxxContainer(xxxObject): class xxxPanel(xxxContainer): allParams = ['pos', 'size', 'style'] + styles = ['fg', 'bg', 'font', 'enabled', 'focused', 'hidden', 'exstyle', + 'tooltip'] + winStyles = ['wxNO_3D', 'wxTAB_TRAVERSAL', 'wxCLIP_CHILDREN'] + exStyles = ['wxWS_EX_VALIDATE_RECURSIVELY'] class xxxDialog(xxxContainer): allParams = ['title', 'pos', 'size', 'style'] required = ['title'] - styles = ['wxDIALOG_MODAL', 'wxCAPTION', 'wxDEFAULT_DIALOG_STYLE', - 'wxRESIZE_BORDER', 'wxSYSTEM_MENU', 'wxTHICK_FRAME', 'wxSTAY_ON_TOP'] + winStyles = ['wxDEFAULT_DIALOG_STYLE', 'wxSTAY_ON_TOP', + 'wxDIALOG_MODAL', 'wxDIALOG_MODELESS', + 'wxCAPTION', 'wxSYSTEM_MENU', 'wxRESIZE_BORDER', 'wxRESIZE_BOX', + 'wxTHICK_FRAME', + 'wxNO_3D', 'wxTAB_TRAVERSAL', 'wxCLIP_CHILDREN'] + styles = ['fg', 'bg', 'font', 'enabled', 'focused', 'hidden', 'exstyle', + 'tooltip'] + exStyles = ['wxWS_EX_VALIDATE_RECURSIVELY'] class xxxFrame(xxxContainer): allParams = ['title', 'centered', 'pos', 'size', 'style'] paramDict = {'centered': ParamBool} required = ['title'] - styles = ['wxDEFAULT_FRAME_STYLE', 'wxICONIZE', 'wxCAPTION', 'wxMINIMIZE', - 'wxICONIZE', 'wxMINIMIZE_BOX', 'wxMAXIMIZE', 'wxMAXIMIZE_BOX', - 'wxSTAY_ON_TOP', 'wxSYSTEM_MENU', 'wxRESIZE_BORDER', - 'wxFRAME_FLOAT_ON_PARENT', 'wxFRAME_TOOL_WINDOW'] + winStyles = ['wxDEFAULT_FRAME_STYLE', 'wxDEFAULT_DIALOG_STYLE', + 'wxSTAY_ON_TOP', + 'wxCAPTION', 'wxSYSTEM_MENU', 'wxRESIZE_BORDER', + 'wxRESIZE_BOX', 'wxMINIMIZE_BOX', 'wxMAXIMIZE_BOX', + 'wxFRAME_FLOAT_ON_PARENT', 'wxFRAME_TOOL_WINDOW', + 'wxNO_3D', 'wxTAB_TRAVERSAL', 'wxCLIP_CHILDREN'] + styles = ['fg', 'bg', 'font', 'enabled', 'focused', 'hidden', 'exstyle', + 'tooltip'] + exStyles = ['wxWS_EX_VALIDATE_RECURSIVELY'] + +class xxxTool(xxxObject): + allParams = ['bitmap', 'bitmap2', 'toggle', 'tooltip', 'longhelp'] + paramDict = {'bitmap2': ParamFile} + hasStyle = false + +class xxxToolBar(xxxContainer): + allParams = ['bitmapsize', 'margins', 'packing', 'separation', + 'pos', 'size', 'style'] + hasStyle = false + paramDict = {'bitmapsize': ParamPosSize, 'margins': ParamPosSize, + 'packing': ParamInt, 'separation': ParamInt, + 'style': ParamNonGenericStyle} + winStyles = ['wxTB_FLAT', 'wxTB_DOCKABLE', 'wxTB_VERTICAL', 'wxTB_HORIZONTAL'] + +################################################################################ +# Bitmap, Icon + +class xxxBitmap(xxxObject): + allParams = ['bitmap'] + required = ['bitmap'] + +class xxxIcon(xxxObject): + allParams = ['icon'] + required = ['icon'] ################################################################################ # Controls @@ -191,20 +296,25 @@ class xxxFrame(xxxContainer): class xxxStaticText(xxxObject): allParams = ['label', 'pos', 'size', 'style'] required = ['label'] - styles = ['wxALIGN_LEFT', 'wxALIGN_RIGHT', 'wxALIGN_CENTRE', 'wxST_NO_AUTORESIZE'] + winStyles = ['wxALIGN_LEFT', 'wxALIGN_RIGHT', 'wxALIGN_CENTRE', 'wxST_NO_AUTORESIZE'] class xxxStaticLine(xxxObject): allParams = ['pos', 'size', 'style'] - styles = ['wxLI_HORIZONTAL', 'wxLI_VERTICAL'] + winStyles = ['wxLI_HORIZONTAL', 'wxLI_VERTICAL'] + +class xxxStaticBitmap(xxxObject): + allParams = ['bitmap', 'pos', 'size', 'style'] + required = ['bitmap'] class xxxTextCtrl(xxxObject): allParams = ['value', 'pos', 'size', 'style'] - styles = ['wxTE_PROCESS_ENTER', 'wxTE_PROCESS_TAB', 'wxTE_MULTILINE', - 'wxTE_PASSWORD', 'wxTE_READONLY'] + winStyles = ['wxTE_PROCESS_ENTER', 'wxTE_PROCESS_TAB', 'wxTE_MULTILINE', + 'wxTE_PASSWORD', 'wxTE_READONLY', 'wxHSCROLL'] class xxxChoice(xxxObject): allParams = ['content', 'selection', 'pos', 'size', 'style'] required = ['content'] + winStyles = ['wxCB_SORT'] class xxxSlider(xxxObject): allParams = ['value', 'min', 'max', 'pos', 'size', 'style', @@ -214,24 +324,25 @@ class xxxSlider(xxxObject): 'linesize': ParamInt, 'thumb': ParamInt, 'thumb': ParamInt, 'tick': ParamInt, 'selmin': ParamInt, 'selmax': ParamInt} required = ['value', 'min', 'max'] - styles = ['wxSL_HORIZONTAL', 'wxSL_VERTICAL', 'wxSL_AUTOTICKS', 'wxSL_LABELS', - 'wxSL_LEFT', 'wxSL_RIGHT', 'wxSL_TOP', 'wxSL_SELRANGE'] + winStyles = ['wxSL_HORIZONTAL', 'wxSL_VERTICAL', 'wxSL_AUTOTICKS', 'wxSL_LABELS', + 'wxSL_LEFT', 'wxSL_RIGHT', 'wxSL_TOP', 'wxSL_BOTTOM', + 'wxSL_BOTH', 'wxSL_SELRANGE'] class xxxGauge(xxxObject): allParams = ['range', 'pos', 'size', 'style', 'value', 'shadow', 'bezel'] paramDict = {'range': ParamInt, 'value': ParamInt, 'shadow': ParamInt, 'bezel': ParamInt} - styles = ['wxGA_HORIZONTAL', 'wxGA_VERTICAL', 'wxGA_PROGRESSBAR', 'wxGA_SMOOTH'] + winStyles = ['wxGA_HORIZONTAL', 'wxGA_VERTICAL', 'wxGA_PROGRESSBAR', 'wxGA_SMOOTH'] class xxxScrollBar(xxxObject): allParams = ['pos', 'size', 'style', 'value', 'thumbsize', 'range', 'pagesize'] paramDict = {'value': ParamInt, 'range': ParamInt, 'thumbsize': ParamInt, 'pagesize': ParamInt} - styles = ['wxSB_HORIZONTAL', 'wxSB_VERTICAL'] + winStyles = ['wxSB_HORIZONTAL', 'wxSB_VERTICAL'] class xxxListCtrl(xxxObject): allParams = ['pos', 'size', 'style'] - styles = ['wxLC_LIST', 'wxLC_REPORT', 'wxLC_ICON', 'wxLC_SMALL_ICON', + winStyles = ['wxLC_LIST', 'wxLC_REPORT', 'wxLC_ICON', 'wxLC_SMALL_ICON', 'wxLC_ALIGN_TOP', 'wxLC_ALIGN_LEFT', 'wxLC_AUTOARRANGE', 'wxLC_USER_TEXT', 'wxLC_EDIT_LABELS', 'wxLC_NO_HEADER', 'wxLC_SINGLE_SEL', 'wxLC_SORT_ASCENDING', 'wxLC_SORT_DESCENDING'] @@ -241,13 +352,13 @@ xxxCheckList = xxxListCtrl class xxxTreeCtrl(xxxObject): allParams = ['pos', 'size', 'style'] - styles = ['wxTR_HAS_BUTTONS', 'wxTR_NO_LINES', 'wxTR_LINES_AT_ROOT', + winStyles = ['wxTR_HAS_BUTTONS', 'wxTR_NO_LINES', 'wxTR_LINES_AT_ROOT', 'wxTR_EDIT_LABELS', 'wxTR_MULTIPLE'] class xxxHtmlWindow(xxxObject): allParams = ['pos', 'size', 'style', 'borders', 'url', 'htmlcode'] paramDict = {'borders': ParamInt} - styles = ['wxHW_SCROLLBAR_NEVER', 'wxHW_SCROLLBAR_AUTO'] + winStyles = ['wxHW_SCROLLBAR_NEVER', 'wxHW_SCROLLBAR_AUTO'] class xxxCalendar(xxxObject): allParams = ['pos', 'size', 'style'] @@ -255,7 +366,7 @@ class xxxCalendar(xxxObject): class xxxNotebook(xxxContainer): allParams = ['usenotebooksizer', 'pos', 'size', 'style'] paramDict = {'usenotebooksizer': ParamBool} - styles = ['wxNB_FIXEDWIDTH', 'wxNB_LEFT', 'wxNB_RIGHT', 'wxNB_BOTTOM'] + winStyles = ['wxNB_FIXEDWIDTH', 'wxNB_LEFT', 'wxNB_RIGHT', 'wxNB_BOTTOM'] ################################################################################ # Buttons @@ -264,23 +375,24 @@ class xxxButton(xxxObject): allParams = ['label', 'default', 'pos', 'size', 'style'] paramDict = {'default': ParamBool} required = ['label'] - styles = ['wxBU_LEFT', 'wxBU_TOP', 'wxBU_RIGHT', 'wxBU_BOTTOM'] + winStyles = ['wxBU_LEFT', 'wxBU_TOP', 'wxBU_RIGHT', 'wxBU_BOTTOM'] class xxxBitmapButton(xxxObject): allParams = ['bitmap', 'selected', 'focus', 'disabled', 'default', 'pos', 'size', 'style'] required = ['bitmap'] - styles = ['wxBU_LEFT', 'wxBU_TOP', 'wxBU_RIGHT', 'wxBU_BOTTOM'] + winStyles = ['wxBU_AUTODRAW', 'wxBU_LEFT', 'wxBU_TOP', + 'wxBU_RIGHT', 'wxBU_BOTTOM'] class xxxRadioButton(xxxObject): allParams = ['label', 'value', 'pos', 'size', 'style'] paramDict = {'value': ParamBool} required = ['label'] - styles = ['wxRB_GROUP'] + winStyles = ['wxRB_GROUP'] class xxxSpinButton(xxxObject): allParams = ['pos', 'size', 'style'] - styles = ['wxSP_HORIZONTAL', 'wxSP_VERTICAL', 'wxSP_ARROW_KEYS', 'wxSP_WRAP'] + winStyles = ['wxSP_HORIZONTAL', 'wxSP_VERTICAL', 'wxSP_ARROW_KEYS', 'wxSP_WRAP'] ################################################################################ # Boxes @@ -293,7 +405,7 @@ class xxxRadioBox(xxxObject): allParams = ['label', 'content', 'selection', 'dimension', 'pos', 'size', 'style'] paramDict = {'dimension': ParamInt} required = ['label', 'content'] - styles = ['wxRA_SPECIFY_ROWS', 'wxRA_SPECIFY_COLS'] + winStyles = ['wxRA_SPECIFY_ROWS', 'wxRA_SPECIFY_COLS'] class xxxCheckBox(xxxObject): allParams = ['label', 'pos', 'size', 'style'] @@ -302,20 +414,19 @@ class xxxCheckBox(xxxObject): class xxxComboBox(xxxObject): allParams = ['content', 'selection', 'value', 'pos', 'size', 'style'] required = ['content'] - styles = ['wxCB_SIMPLE', 'wxCB_DROPDOWN', 'wxCB_READONLY', 'wxCB_DROPDOWN', - 'wxCB_SORT'] + winStyles = ['wxCB_SIMPLE', 'wxCB_SORT', 'wxCB_READONLY', 'wxCB_DROPDOWN'] class xxxListBox(xxxObject): allParams = ['content', 'selection', 'pos', 'size', 'style'] required = ['content'] - styles = ['wxLB_SINGLE', 'wxLB_MULTIPLE', 'wxLB_EXTENDED', 'wxLB_HSCROLL', + winStyles = ['wxLB_SINGLE', 'wxLB_MULTIPLE', 'wxLB_EXTENDED', 'wxLB_HSCROLL', 'wxLB_ALWAYS_SB', 'wxLB_NEEDED_SB', 'wxLB_SORT'] ################################################################################ # Sizers class xxxSizer(xxxContainer): - hasName = false + hasName = hasStyle = false paramDict = {'orient': ParamOrient} isSizer = true @@ -325,7 +436,7 @@ class xxxBoxSizer(xxxSizer): default = {'orient': 'wxVERTICAL'} # Tree icon depends on orientation def treeImage(self): - if self.params['orient'].data == 'wxHORIZONTAL': return self.imageH + if self.params['orient'].value() == 'wxHORIZONTAL': return self.imageH else: return self.imageV class xxxStaticBoxSizer(xxxBoxSizer): @@ -338,16 +449,45 @@ class xxxGridSizer(xxxSizer): required = ['cols'] default = {'cols': '2', 'rows': '2'} +# For repeated parameters +class xxxParamMulti: + def __init__(self): + self.l, self.data = [], [] + def append(self, param): + self.l.append(param) + self.data.append(param.value()) + def value(self): + return self.data + def remove(self): + for param in self.l: + param.remove() + self.l, self.data = [], [] + class xxxFlexGridSizer(xxxGridSizer): - pass + specials = ['growablecols', 'growablerows'] + allParams = ['cols', 'rows', 'vgap', 'hgap'] + specials + paramDict = {'growablecols':ParamContent, 'growablerows':ParamContent} + # Special processing for growable* parameters + # (they are represented by several nodes) + def special(self, tag, node): + if tag not in self.params.keys(): + self.params[tag] = xxxParamMulti() + self.params[tag].append(xxxParam(node)) + def setSpecial(self, param, value): + # Straightforward implementation: remove, add again + self.params[param].remove() + del self.params[param] + for str in value: + node = tree.dom.createElement(param) + text = tree.dom.createTextNode(str) + node.appendChild(text) + self.element.appendChild(node) + self.special(param, node) # Container with only one child. # Not shown in tree. class xxxChildContainer(xxxObject): - # Special param ids - ID_CHECK_PARAMS = wxNewId() - ID_TEXT_PARAMS = wxNewId() - hasName = false + hasName = hasStyle = false hasChild = true def __init__(self, parent, element): xxxObject.__init__(self, parent, element) @@ -396,7 +536,7 @@ class xxxNotebookPage(xxxChildContainer): self.child.allParams.remove('size') class xxxSpacer(xxxObject): - hasName = false + hasName = hasStyle = false allParams = ['size', 'option', 'flag', 'border'] paramDict = {'option': ParamInt} default = {'size': '0,0'} @@ -407,26 +547,36 @@ class xxxMenuBar(xxxContainer): class xxxMenu(xxxContainer): allParams = ['label'] default = {'label': ''} + paramDict = {'style': ParamNonGenericStyle} # no generic styles + winStyles = ['wxMENU_TEAROFF'] class xxxMenuItem(xxxObject): allParams = ['checkable', 'label', 'accel', 'help'] default = {'label': ''} class xxxSeparator(xxxObject): - hasName = false + hasName = hasStyle = false allParams = [] +################################################################################ + xxxDict = { 'wxPanel': xxxPanel, 'wxDialog': xxxDialog, 'wxFrame': xxxFrame, + 'tool': xxxTool, + 'wxToolBar': xxxToolBar, + 'wxBitmap': xxxBitmap, + 'wxIcon': xxxIcon, + 'wxButton': xxxButton, 'wxBitmapButton': xxxBitmapButton, 'wxRadioButton': xxxRadioButton, 'wxSpinButton': xxxSpinButton, 'wxStaticBox': xxxStaticBox, + 'wxStaticBitmap': xxxStaticBitmap, 'wxRadioBox': xxxRadioBox, 'wxComboBox': xxxComboBox, 'wxCheckBox': xxxCheckBox, @@ -460,6 +610,17 @@ xxxDict = { 'separator': xxxSeparator, } +# Create IDs for all parameters of all classes +paramIDs = {'fg': wxNewId(), 'bg': wxNewId(), 'exstyle': wxNewId(), 'font': wxNewId(), + 'enabled': wxNewId(), 'focused': wxNewId(), 'hidden': wxNewId(), + 'tooltip': wxNewId() + } +for cl in xxxDict.values(): + for param in cl.allParams + cl.paramDict.keys(): + if not paramIDs.has_key(param): + paramIDs[param] = wxNewId() + +################################################################################ # Helper functions # Test for object elements @@ -468,7 +629,13 @@ def IsObject(node): # Make XXX object from some DOM object, selecting correct class def MakeXXXFromDOM(parent, element): - return xxxDict[element.getAttribute('class')](parent, element) + try: + return xxxDict[element.getAttribute('class')](parent, element) + except KeyError: + # Verify that it's not recursive exception + if element.getAttribute('class') not in xxxDict.keys(): + print 'ERROR: unknown class:', element.getAttribute('class') + raise # Make empty DOM element def MakeEmptyDOM(className):