1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19   
 20   
 21   
 22   
 23   
 24   
 25   
 26   
 27   
 28   
 29   
 30   
 31   
 32   
 33   
 34   
 35   
 36  """ 
 37  The ``E`` Element factory for generating XML documents. 
 38  """ 
 39   
 40  import lxml.etree as ET 
 41   
 42  from functools import partial 
 43   
 44  try: 
 45      basestring 
 46  except NameError: 
 47      basestring = str 
 48   
 49  try: 
 50      unicode 
 51  except NameError: 
 52      unicode = str 
 53   
 54   
 56      """Element generator factory. 
 57   
 58      Unlike the ordinary Element factory, the E factory allows you to pass in 
 59      more than just a tag and some optional attributes; you can also pass in 
 60      text and other elements.  The text is added as either text or tail 
 61      attributes, and elements are inserted at the right spot.  Some small 
 62      examples:: 
 63   
 64          >>> from lxml import etree as ET 
 65          >>> from lxml.builder import E 
 66   
 67          >>> ET.tostring(E("tag")) 
 68          '<tag/>' 
 69          >>> ET.tostring(E("tag", "text")) 
 70          '<tag>text</tag>' 
 71          >>> ET.tostring(E("tag", "text", key="value")) 
 72          '<tag key="value">text</tag>' 
 73          >>> ET.tostring(E("tag", E("subtag", "text"), "tail")) 
 74          '<tag><subtag>text</subtag>tail</tag>' 
 75   
 76      For simple tags, the factory also allows you to write ``E.tag(...)`` instead 
 77      of ``E('tag', ...)``:: 
 78   
 79          >>> ET.tostring(E.tag()) 
 80          '<tag/>' 
 81          >>> ET.tostring(E.tag("text")) 
 82          '<tag>text</tag>' 
 83          >>> ET.tostring(E.tag(E.subtag("text"), "tail")) 
 84          '<tag><subtag>text</subtag>tail</tag>' 
 85   
 86      Here's a somewhat larger example; this shows how to generate HTML 
 87      documents, using a mix of prepared factory functions for inline elements, 
 88      nested ``E.tag`` calls, and embedded XHTML fragments:: 
 89   
 90          # some common inline elements 
 91          A = E.a 
 92          I = E.i 
 93          B = E.b 
 94   
 95          def CLASS(v): 
 96              # helper function, 'class' is a reserved word 
 97              return {'class': v} 
 98   
 99          page = ( 
100              E.html( 
101                  E.head( 
102                      E.title("This is a sample document") 
103                  ), 
104                  E.body( 
105                      E.h1("Hello!", CLASS("title")), 
106                      E.p("This is a paragraph with ", B("bold"), " text in it!"), 
107                      E.p("This is another paragraph, with a ", 
108                          A("link", href="http://www.python.org"), "."), 
109                      E.p("Here are some reserved characters: <spam&egg>."), 
110                      ET.XML("<p>And finally, here is an embedded XHTML fragment.</p>"), 
111                  ) 
112              ) 
113          ) 
114   
115          print ET.tostring(page) 
116   
117      Here's a prettyprinted version of the output from the above script:: 
118   
119          <html> 
120            <head> 
121              <title>This is a sample document</title> 
122            </head> 
123            <body> 
124              <h1 class="title">Hello!</h1> 
125              <p>This is a paragraph with <b>bold</b> text in it!</p> 
126              <p>This is another paragraph, with <a href="http://www.python.org">link</a>.</p> 
127              <p>Here are some reserved characters: <spam&egg>.</p> 
128              <p>And finally, here is an embedded XHTML fragment.</p> 
129            </body> 
130          </html> 
131   
132      For namespace support, you can pass a namespace map (``nsmap``) 
133      and/or a specific target ``namespace`` to the ElementMaker class:: 
134   
135          >>> E = ElementMaker(namespace="http://my.ns/") 
136          >>> print(ET.tostring( E.test )) 
137          <test xmlns="http://my.ns/"/> 
138   
139          >>> E = ElementMaker(namespace="http://my.ns/", nsmap={'p':'http://my.ns/'}) 
140          >>> print(ET.tostring( E.test )) 
141          <p:test xmlns:p="http://my.ns/"/> 
142      """ 
143   
144 -    def __init__(self, typemap=None, 
145                   namespace=None, nsmap=None, makeelement=None): 
 174   
175          def add_cdata(elem, cdata): 
176              if elem.text: 
177                  raise ValueError("Can't add a CDATA section. Element already has some text: %r" % elem.text) 
178              elem.text = cdata 
 179   
180          if str not in typemap: 
181              typemap[str] = add_text 
182          if unicode not in typemap: 
183              typemap[unicode] = add_text 
184          if ET.CDATA not in typemap: 
185              typemap[ET.CDATA] = add_cdata 
186   
187          def add_dict(elem, item): 
188              attrib = elem.attrib 
189              for k, v in item.items(): 
190                  if isinstance(v, basestring): 
191                      attrib[k] = v 
192                  else: 
193                      attrib[k] = typemap[type(v)](None, v) 
194          if dict not in typemap: 
195              typemap[dict] = add_dict 
196   
197          self._typemap = typemap 
198   
199 -    def __call__(self, tag, *children, **attrib): 
 200          typemap = self._typemap 
201   
202          if self._namespace is not None and tag[0] != '{': 
203              tag = self._namespace + tag 
204          elem = self._makeelement(tag, nsmap=self._nsmap) 
205          if attrib: 
206              typemap[dict](elem, attrib) 
207   
208          for item in children: 
209              if callable(item): 
210                  item = item() 
211              t = typemap.get(type(item)) 
212              if t is None: 
213                  if ET.iselement(item): 
214                      elem.append(item) 
215                      continue 
216                  for basetype in type(item).__mro__: 
217                       
218                      t = typemap.get(basetype) 
219                      if t is not None: 
220                          break 
221                  else: 
222                      raise TypeError("bad argument type: %s(%r)" % 
223                                      (type(item).__name__, item)) 
224              v = t(elem, item) 
225              if v: 
226                  typemap.get(type(v))(elem, v) 
227   
228          return elem 
 229   
231          return partial(self, tag) 
 232   
233   
234   
235  E = ElementMaker() 
236