lxml.builder

Package lxml :: Module builder |
[hide private]
[frames] | no frames]

# Source Code for Module lxml.builder

      1  #
      2  # Element generator factory by Fredrik Lundh.
      3  #
      4  # Source:
      5  #    http://online.effbot.org/2006_11_01_archive.htm#et-builder
      6  #    http://effbot.python-hosting.com/file/stuff/sandbox/elementlib/builder.py
      7  #
      8  # --------------------------------------------------------------------
      9  # The ElementTree toolkit is
     10  #
     11  # Copyright (c) 1999-2004 by Fredrik Lundh
     12  #
     13  # By obtaining, using, and/or copying this software and/or its
     14  # associated documentation, you agree that you have read, understood,
     15  # and will comply with the following terms and conditions:
     16  #
     17  # Permission to use, copy, modify, and distribute this software and
     18  # its associated documentation for any purpose and without fee is
     19  # hereby granted, provided that the above copyright notice appears in
     20  # all copies, and that both that copyright notice and this permission
     21  # notice appear in supporting documentation, and that the name of
     22  # Secret Labs AB or the author not be used in advertising or publicity
     23  # pertaining to distribution of the software without specific, written
     24  # prior permission.
     25  #
     26  # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
     27  # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
     28  # ABILITY AND FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
     29  # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
     30  # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
     31  # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
     32  # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
     33  # OF THIS SOFTWARE.
     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
     55 -class ElementMaker(object):
     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: &lt;spam&amp;egg&gt;.</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):
    146          if namespace is not None:
    147              self._namespace = '{' + namespace + '}'
    148          else:
    149              self._namespace = None
    150
    151          if nsmap:
    152              self._nsmap = dict(nsmap)
    153          else:
    154              self._nsmap = None
    155
    156          if makeelement is not None:
    157              assert callable(makeelement)
    158              self._makeelement = makeelement
    159          else:
    160              self._makeelement = ET.Element
    161
    162          # initialize type map for this element factory
    163
    164          if typemap:
    165              typemap = dict(typemap)
    166          else:
    167              typemap = {}
    168
    169          def add_text(elem, item):
    170              try:
    171                  elem[-1].tail = (elem[-1].tail or "") + item
    172              except IndexError:
    173                  elem.text = (elem.text or "") + item
    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                      # See if the typemap knows of any of this type's bases.
    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
    230 -    def __getattr__(self, tag):
    231          return partial(self, tag)
    232
    233
    234  # create factory object
    235  E = ElementMaker()
    236