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 11.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.
# - - - 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.
#-- 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.
#-- 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})”.
#-- 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
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 <tcc-doc@nmt.edu>.
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.
#-- 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 addDict ( elt, item ):
elt.append ( item )
sample = et.Element ( 'sample' )
self.__typeMap[type(sample)] = addDict