Package lxml :: Module builder

[hide private]
[frames] | no frames]

# Source Code for Module lxml.builder

      1  # cython: language_level=2
      2
      3  #
      4  # Element generator factory by Fredrik Lundh.
      5  #
      6  # Source:
      7  #    http://online.effbot.org/2006_11_01_archive.htm#et-builder
      8  #    http://effbot.python-hosting.com/file/stuff/sandbox/elementlib/builder.py
      9  #
     10  # --------------------------------------------------------------------
     11  # The ElementTree toolkit is
     12  #
     13  # Copyright (c) 1999-2004 by Fredrik Lundh
     14  #
     15  # By obtaining, using, and/or copying this software and/or its
     16  # associated documentation, you agree that you have read, understood,
     17  # and will comply with the following terms and conditions:
     18  #
     19  # Permission to use, copy, modify, and distribute this software and
     20  # its associated documentation for any purpose and without fee is
     21  # hereby granted, provided that the above copyright notice appears in
     22  # all copies, and that both that copyright notice and this permission
     23  # notice appear in supporting documentation, and that the name of
     24  # Secret Labs AB or the author not be used in advertising or publicity
     25  # pertaining to distribution of the software without specific, written
     26  # prior permission.
     27  #
     28  # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
     29  # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
     30  # ABILITY AND FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
     31  # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
     32  # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
     33  # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
     34  # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
     35  # OF THIS SOFTWARE.
     36  # --------------------------------------------------------------------
     37
     38  """
     39  The ``E`` Element factory for generating XML documents.
     40  """
     41
     42  from __future__ import absolute_import
     43
     44  import lxml.etree as ET
     45
     46  from functools import partial
     47
     48  try:
     49      basestring
     50  except NameError:
     51      basestring = str
     52
     53  try:
     54      unicode
     55  except NameError:
     56      unicode = str
     57
     58
     59 -class ElementMaker(object):
     60      """Element generator factory.
     61
     62      Unlike the ordinary Element factory, the E factory allows you to pass in
     63      more than just a tag and some optional attributes; you can also pass in
     64      text and other elements.  The text is added as either text or tail
     65      attributes, and elements are inserted at the right spot.  Some small
     66      examples::
     67
     68          >>> from lxml import etree as ET
     69          >>> from lxml.builder import E
     70
     71          >>> ET.tostring(E("tag"))
     72          '<tag/>'
     73          >>> ET.tostring(E("tag", "text"))
     74          '<tag>text</tag>'
     75          >>> ET.tostring(E("tag", "text", key="value"))
     76          '<tag key="value">text</tag>'
     77          >>> ET.tostring(E("tag", E("subtag", "text"), "tail"))
     78          '<tag><subtag>text</subtag>tail</tag>'
     79
     80      For simple tags, the factory also allows you to write ``E.tag(...)`` instead
     81      of ``E('tag', ...)``::
     82
     83          >>> ET.tostring(E.tag())
     84          '<tag/>'
     85          >>> ET.tostring(E.tag("text"))
     86          '<tag>text</tag>'
     87          >>> ET.tostring(E.tag(E.subtag("text"), "tail"))
     88          '<tag><subtag>text</subtag>tail</tag>'
     89
     90      Here's a somewhat larger example; this shows how to generate HTML
     91      documents, using a mix of prepared factory functions for inline elements,
     92      nested ``E.tag`` calls, and embedded XHTML fragments::
     93
     94          # some common inline elements
     95          A = E.a
     96          I = E.i
     97          B = E.b
     98
     99          def CLASS(v):
    100              # helper function, 'class' is a reserved word
    101              return {'class': v}
    102
    103          page = (
    104              E.html(
    105                  E.head(
    106                      E.title("This is a sample document")
    107                  ),
    108                  E.body(
    109                      E.h1("Hello!", CLASS("title")),
    110                      E.p("This is a paragraph with ", B("bold"), " text in it!"),
    111                      E.p("This is another paragraph, with a ",
    112                          A("link", href="http://www.python.org"), "."),
    113                      E.p("Here are some reserved characters: <spam&egg>."),
    114                      ET.XML("<p>And finally, here is an embedded XHTML fragment.</p>"),
    115                  )
    116              )
    117          )
    118
    119          print ET.tostring(page)
    120
    121      Here's a prettyprinted version of the output from the above script::
    122
    123          <html>
    124            <head>
    125              <title>This is a sample document</title>
    126            </head>
    127            <body>
    128              <h1 class="title">Hello!</h1>
    129              <p>This is a paragraph with <b>bold</b> text in it!</p>
    130              <p>This is another paragraph, with <a href="http://www.python.org">link</a>.</p>
    131              <p>Here are some reserved characters: &lt;spam&amp;egg&gt;.</p>
    132              <p>And finally, here is an embedded XHTML fragment.</p>
    133            </body>
    134          </html>
    135
    136      For namespace support, you can pass a namespace map (``nsmap``)
    137      and/or a specific target ``namespace`` to the ElementMaker class::
    138
    139          >>> E = ElementMaker(namespace="http://my.ns/")
    140          >>> print(ET.tostring( E.test ))
    141          <test xmlns="http://my.ns/"/>
    142
    143          >>> E = ElementMaker(namespace="http://my.ns/", nsmap={'p':'http://my.ns/'})
    144          >>> print(ET.tostring( E.test ))
    145          <p:test xmlns:p="http://my.ns/"/>
    146      """
    147
    148 -    def __init__(self, typemap=None,
    149                   namespace=None, nsmap=None, makeelement=None):
    150          if namespace is not None:
    151              self._namespace = '{' + namespace + '}'
    152          else:
    153              self._namespace = None
    154
    155          if nsmap:
    156              self._nsmap = dict(nsmap)
    157          else:
    158              self._nsmap = None
    159
    160          if makeelement is not None:
    161              assert callable(makeelement)
    162              self._makeelement = makeelement
    163          else:
    164              self._makeelement = ET.Element
    165
    166          # initialize type map for this element factory
    167
    168          if typemap:
    169              typemap = dict(typemap)
    170          else:
    171              typemap = {}
    172
    173          def add_text(elem, item):
    174              try:
    175                  elem[-1].tail = (elem[-1].tail or "") + item
    176              except IndexError:
    177                  elem.text = (elem.text or "") + item
    178
    179          def add_cdata(elem, cdata):
    180              if elem.text:
    181                  raise ValueError("Can't add a CDATA section. Element already has some text: %r" % elem.text)
    182              elem.text = cdata
    183
    184          if str not in typemap:
    185              typemap[str] = add_text
    186          if unicode not in typemap:
    187              typemap[unicode] = add_text
    188          if ET.CDATA not in typemap:
    189              typemap[ET.CDATA] = add_cdata
    190
    191          def add_dict(elem, item):
    192              attrib = elem.attrib
    193              for k, v in item.items():
    194                  if isinstance(v, basestring):
    195                      attrib[k] = v
    196                  else:
    197                      attrib[k] = typemap[type(v)](None, v)
    198          if dict not in typemap:
    199              typemap[dict] = add_dict
    200
    201          self._typemap = typemap
    202
    203 -    def __call__(self, tag, *children, **attrib):
    204          typemap = self._typemap
    205
    206          if self._namespace is not None and tag[0] != '{':
    207              tag = self._namespace + tag
    208          elem = self._makeelement(tag, nsmap=self._nsmap)
    209          if attrib:
    210              typemap[dict](elem, attrib)
    211
    212          for item in children:
    213              if callable(item):
    214                  item = item()
    215              t = typemap.get(type(item))
    216              if t is None:
    217                  if ET.iselement(item):
    218                      elem.append(item)
    219                      continue
    220                  for basetype in type(item).__mro__:
    221                      # See if the typemap knows of any of this type's bases.
    222                      t = typemap.get(basetype)
    223                      if t is not None:
    224                          break
    225                  else:
    226                      raise TypeError("bad argument type: %s(%r)" %
    227                                      (type(item).__name__, item))
    228              v = t(elem, item)
    229              if v:
    230                  typemap.get(type(v))(elem, v)
    231
    232          return elem
    233
    234 -    def __getattr__(self, tag):
    235          return partial(self, tag)
    236
    237
    238  # create factory object
    239  E = ElementMaker()
    240