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  try: 
 43      from functools import partial 
 44  except ImportError: 
 45       
 47          return lambda *args, **kwargs: func(tag, *args, **kwargs) 
  48   
 49  try: 
 50      callable 
 51  except NameError: 
 52       
 55   
 56  try: 
 57      basestring 
 58  except NameError: 
 59      basestring = str 
 60   
 61  try: 
 62      unicode 
 63  except NameError: 
 64      unicode = str 
 65   
 66   
 68      """Element generator factory. 
 69   
 70      Unlike the ordinary Element factory, the E factory allows you to pass in 
 71      more than just a tag and some optional attributes; you can also pass in 
 72      text and other elements.  The text is added as either text or tail 
 73      attributes, and elements are inserted at the right spot.  Some small 
 74      examples:: 
 75   
 76          >>> from lxml import etree as ET 
 77          >>> from lxml.builder import E 
 78   
 79          >>> ET.tostring(E("tag")) 
 80          '<tag/>' 
 81          >>> ET.tostring(E("tag", "text")) 
 82          '<tag>text</tag>' 
 83          >>> ET.tostring(E("tag", "text", key="value")) 
 84          '<tag key="value">text</tag>' 
 85          >>> ET.tostring(E("tag", E("subtag", "text"), "tail")) 
 86          '<tag><subtag>text</subtag>tail</tag>' 
 87   
 88      For simple tags, the factory also allows you to write ``E.tag(...)`` instead 
 89      of ``E('tag', ...)``:: 
 90   
 91          >>> ET.tostring(E.tag()) 
 92          '<tag/>' 
 93          >>> ET.tostring(E.tag("text")) 
 94          '<tag>text</tag>' 
 95          >>> ET.tostring(E.tag(E.subtag("text"), "tail")) 
 96          '<tag><subtag>text</subtag>tail</tag>' 
 97   
 98      Here's a somewhat larger example; this shows how to generate HTML 
 99      documents, using a mix of prepared factory functions for inline elements, 
100      nested ``E.tag`` calls, and embedded XHTML fragments:: 
101   
102          # some common inline elements 
103          A = E.a 
104          I = E.i 
105          B = E.b 
106   
107          def CLASS(v): 
108              # helper function, 'class' is a reserved word 
109              return {'class': v} 
110   
111          page = ( 
112              E.html( 
113                  E.head( 
114                      E.title("This is a sample document") 
115                  ), 
116                  E.body( 
117                      E.h1("Hello!", CLASS("title")), 
118                      E.p("This is a paragraph with ", B("bold"), " text in it!"), 
119                      E.p("This is another paragraph, with a ", 
120                          A("link", href="http://www.python.org"), "."), 
121                      E.p("Here are some reservered characters: <spam&egg>."), 
122                      ET.XML("<p>And finally, here is an embedded XHTML fragment.</p>"), 
123                  ) 
124              ) 
125          ) 
126   
127          print ET.tostring(page) 
128   
129      Here's a prettyprinted version of the output from the above script:: 
130   
131          <html> 
132            <head> 
133              <title>This is a sample document</title> 
134            </head> 
135            <body> 
136              <h1 class="title">Hello!</h1> 
137              <p>This is a paragraph with <b>bold</b> text in it!</p> 
138              <p>This is another paragraph, with <a href="http://www.python.org">link</a>.</p> 
139              <p>Here are some reservered characters: <spam&egg>.</p> 
140              <p>And finally, here is an embedded XHTML fragment.</p> 
141            </body> 
142          </html> 
143   
144      For namespace support, you can pass a namespace map (``nsmap``) 
145      and/or a specific target ``namespace`` to the ElementMaker class:: 
146   
147          >>> E = ElementMaker(namespace="http://my.ns/") 
148          >>> print(ET.tostring( E.test )) 
149          <test xmlns="http://my.ns/"/> 
150   
151          >>> E = ElementMaker(namespace="http://my.ns/", nsmap={'p':'http://my.ns/'}) 
152          >>> print(ET.tostring( E.test )) 
153          <p:test xmlns:p="http://my.ns/"/> 
154      """ 
155   
156 -    def __init__(self, typemap=None, 
157                   namespace=None, nsmap=None, makeelement=None): 
 158          if namespace is not None: 
159              self._namespace = '{' + namespace + '}' 
160          else: 
161              self._namespace = None 
162   
163          if nsmap: 
164              self._nsmap = dict(nsmap) 
165          else: 
166              self._nsmap = None 
167   
168          if makeelement is not None: 
169              assert callable(makeelement) 
170              self._makeelement = makeelement 
171          else: 
172              self._makeelement = ET.Element 
173   
174           
175   
176          if typemap: 
177              typemap = typemap.copy() 
178          else: 
179              typemap = {} 
180   
181          def add_text(elem, item): 
182              try: 
183                  elem[-1].tail = (elem[-1].tail or "") + item 
184              except IndexError: 
185                  elem.text = (elem.text or "") + item 
 186   
187          def add_cdata(elem, cdata): 
188              if elem.text: 
189                  raise ValueError("Can't add a CDATA section. Element already has some text: %r" % elem.text) 
190              elem.text = cdata 
 191   
192          if str not in typemap: 
193              typemap[str] = add_text 
194          if unicode not in typemap: 
195              typemap[unicode] = add_text 
196          if ET.CDATA not in typemap: 
197              typemap[ET.CDATA] = add_cdata 
198   
199          def add_dict(elem, item): 
200              attrib = elem.attrib 
201              for k, v in item.items(): 
202                  if isinstance(v, basestring): 
203                      attrib[k] = v 
204                  else: 
205                      attrib[k] = typemap[type(v)](None, v) 
206          if dict not in typemap: 
207              typemap[dict] = add_dict 
208   
209          self._typemap = typemap 
210   
211 -    def __call__(self, tag, *children, **attrib): 
 212          get = self._typemap.get 
213   
214          if self._namespace is not None and tag[0] != '{': 
215              tag = self._namespace + tag 
216          elem = self._makeelement(tag, nsmap=self._nsmap) 
217          if attrib: 
218              get(dict)(elem, attrib) 
219   
220          for item in children: 
221              if callable(item): 
222                  item = item() 
223              t = get(type(item)) 
224              if t is None: 
225                  if ET.iselement(item): 
226                      elem.append(item) 
227                      continue 
228                  for basetype in type(item).__mro__: 
229                       
230                      t = get(basetype) 
231                      if t is not None: 
232                          break 
233                  else: 
234                      raise TypeError("bad argument type: %s(%r)" % 
235                                      (type(item).__name__, item)) 
236              v = t(elem, item) 
237              if v: 
238                  get(type(v))(elem, v) 
239   
240          return elem 
 241   
243          return partial(self, tag) 
 244   
245   
246  E = ElementMaker() 
247