Next / Previous / Contents / TCC Help System / NM Tech homepage

14.8. ElementMaker.__init__(): Constructor

The factory instance returned by the ElementMaker constructor must look at the type of each of its positional arguments in order to know what to do with it. Python's dictionary type makes this easy to do: we use a dictionary whose keys are Python type objects. Each of the corresponding values in this dictionary is a function that can be called to process arguments of that type.

The dictionary is a private attribute .__typeMap, and all the constructor does is set this dictionary up.

The functions that process arguments all have this generic calling sequence:

f(elt, item)

where elt is the et.Element being built, and item is the argument to be processed.

The first step is to initialize the .__typeMap dictionary. In most cases, the user will be satisfied with the type set described in Section 13.1, “Using the etbuilder module”. However, as a convenience, Lundh's original builder.py design allows the caller to supply a dictionary of additional type-function pairs as an optional argument; in that case, we will copy the supplied dictionary as the initial value of self.__typeMap.

etbuilder.py
# - - -   E l e m e n t M a k e r . _ _ i n i t _ _

    def __init__(self, typeMap=None):
        '''Constructor for the ElementMaker factory class.
        '''
        #-- 1 --
        # [ if typeMap is None ->
        #     self.__typeMap  :=  a new, empty dictionary
        #   else ->
        #     self.__typeMap  :=  a copy of typeMap ]
        if typeMap is None:
            self.__typeMap  =  {}
        else:
            self.__typeMap  =  typeMap.copy()

The first types we'll need to handle are the str and unicode types. These types will use a function we define locally named addText(). Adding text to an element in the ElementTree world has two cases. If the element has no children, the text is added to the element's .text attribute. If the element has any children, the new text is added to the last child's .tail attribute. See Section 2, “How ElementTree represents XML” for a review of text handling.

etbuilder.py
        #-- 2 --
        # [ self.__typeMap[str], self.__typeMap[unicode]  :=
        #     a function with calling sequence
        #       addText(elt, item)
        #     and intended function
        #       [ (elt is an et.Element) and
        #         (item is a str or unicode instance) ->
        #           if elt has no children and elt.text is None ->
        #             elt.text  :=  item
        #           else if elt has no children ->
        #             elt.text  +:=  item
        #           else if elt's last child has .text==None ->
        #             that child's .text  :=  item
        #           else ->
        #             that child's .text  +:=  item ]
        def addText(elt, item):
            if len(elt):
                elt[-1].tail  =  (elt[-1].tail or "") + item
            else:
                elt.text  =  (elt.text or "") + item
        self.__typeMap[str]  =  self.__typeMap[unicode]  =  addText

Lundh's original module did not handle arguments of type int, but this ability is handy for many common tags, such as “<table border='8'>”, which becomes “E.table(border=8)”.

A little deviousness is required here. The addInt() function can't call the addText() function above directly, because the name addText is bound to that function only inside the constructor. The instance does not know that name. However, we can assume that self.__typeMap[str] is bound to that function, so we call it from there.

etbuilder.py
        #-- 3 --
        # [ self.__typeMap[str], self.__typeMap[unicode]  :=
        #     a function with calling sequence
        #       addInt(elt, item)
        #     and intended function
        #       [ (elt is an et.Element) and
        #         (item is an int instance) ->
        #           if elt has no children and elt.text is None ->
        #             elt.text  :=  str(item)
        #           else if elt has no children ->
        #             elt.text  +:=  str(item)
        #           else if elt's last child has .text==None ->
        #             that child's .text  :=  str(item)
        #           else ->
        #             that child's .text  +:=  str(item) ]
        def addInt(elt, item):
            self.__typeMap[str](elt, str(item))
        self.__typeMap[int]  =  addInt

The next type we need to handle is dict. Each key-value pair from the dictionary becomes an XML attribute. For user convenience, if the value is not a string, we'll use the str() function on it, allowing constructs like “E({border: 1})”.

etbuilder.py
        #-- 4 --
        # [ self.__typeMap[dict]  :=  a function with calling
        #       sequence 
        #         addDict(elt, item)
        #       and intended function
        #         [ (elt is an et.Element) and
        #           (item is a dictionary) ->
        #             elt  :=  elt with an attribute made from
        #                      each key-value pair from item ]
        def addDict(elt, item):
            for key, value in item.items():
                if isinstance(value, basestring):
                    elt.attrib[key]  =  value
                else:
                    elt.attrib[key]  =  str(value)
        self.__typeMap[dict]  =  addDict

Note

In Lundh's original, the last line of the previous block was the equivalent of this:

                    elt.attrib[key]  =  \
                         self.__typeMap[type(value)](None, value)

I'm not entirely sure what he had in mind here. If you have any good theories, please forward them to .

Next up is the handler for arguments that are instances of et.Element. We'll actually create an et.Element to be sure that self.__typeMap uses the correct key.

etbuilder.py
        #-- 5 --
        # [ self.__typeMap[type(et.Element instances)]  :=  a
        #       function with calling sequence 
        #         addElt(elt, item)
        #       and intended function
        #         [ (elt and item are et.Element instances) ->
        #             elt  :=  elt with item added as its next
        #                      child element ]
        def addElement(elt, item):
            elt.append(item)
        sample  =  et.Element('sample')
        self.__typeMap[type(sample)]  =  addElement