diff --git a/xml/README.txt b/xml/README.txt
new file mode 100644
index 0000000..0eab647
--- /dev/null
+++ b/xml/README.txt
@@ -0,0 +1,94 @@
+XML (and XHTML) Generation
+==========================
+
+The elements generator lets you easily create snippets of XML or XHTML:
+
+ >>> from cybertools.xml.element import elements as e
+ >>> doc = e.html(
+ ... e.head(e.title(u'Page Title')),
+ ... e.body(
+ ... e.div(u'The top bar', class_=u'top'),
+ ... e.div(u'The body stuff', class_=u'body'),
+ ... ))
+
+ >>> print doc.render()
+
+
+
+ Page Title
+
+
+
+
+ The top bar
+
+
+ The body stuff
+
+
+
+
+An XML element thus created may be converted to an ElementTree:
+
+ >>> doc.renderTree()
+ 'Page TitleThe top bar
The body stuff
'
+
+ >>> tree = doc.makeTree()
+ >>> tree.findtext('head/title')
+ 'Page Title'
+
+ >>> xml = ('Page Title'
+ ... 'The top bar
'
+ ... 'The body stuff
')
+ >>> from cybertools.xml.element import fromXML
+ >>> doc = fromXML(xml)
+ >>> print doc.render()
+
+
+
+ Page Title
+
+
+
+
+ The top bar
+
+
+ The body stuff
+
+
+
+
+Alternative Notation
+--------------------
+
+We can also create such a structure by successively adding elements
+just by accessing an element's attributes:
+
+ >>> doc = e.html
+ >>> dummy = doc.head.title(u'Page Title')
+ >>> body = doc.body
+ >>> div1 = body.div(u'The top bar', class_=u'top')
+ >>> div2 = body.div(u'The body stuff', class_=u'body')
+ >>> print doc.render()
+
+
+
+ Page Title
+
+
+
+
+ The top bar
+
+
+ The body stuff
+
+
+
+
+ >>> for text in (u'Welcome', u'home'):
+ ... p = div2.p(text, style='font-size: 80%;')
+ >>> print doc.render()
+ .........
+
diff --git a/xml/__init__.py b/xml/__init__.py
new file mode 100644
index 0000000..a374c65
--- /dev/null
+++ b/xml/__init__.py
@@ -0,0 +1,4 @@
+# makes this directory a package
+#
+# $Id$
+#
diff --git a/xml/element.py b/xml/element.py
new file mode 100644
index 0000000..a6165d5
--- /dev/null
+++ b/xml/element.py
@@ -0,0 +1,109 @@
+"""
+Generation and manipulation of XML trees.
+
+$Id$
+"""
+
+from cStringIO import StringIO
+from lxml import etree
+
+
+class Generator(object):
+
+ def __getitem__(self, name):
+ return Element(etree.Element(name))
+
+ def __getattr__(self, name):
+ return self[name]
+
+elements = Generator()
+
+
+class Element(object):
+
+ def __init__(self, baseElement):
+ self.baseElement = baseElement
+
+ @property
+ def __name__(self):
+ return self.baseElement.tag
+
+ @property
+ def children(self):
+ base = self.baseElement
+ result = []
+ if base.text:
+ result.append(base.text)
+ for c in base.getchildren():
+ result.append(Element(c))
+ if base.tail:
+ result.append(base.tail)
+ return result
+
+ @property
+ def attributes(self):
+ return self.baseElement.attrib
+
+ def __getattr__(self, name):
+ if name.endswith('_'):
+ name = name[:-1]
+ return self[name]
+
+ def __getitem__(self, name):
+ elem = etree.Element(name)
+ self.baseElement.append(elem)
+ return Element(elem)
+
+ def __call__(self, *children, **attributes):
+ base = self.baseElement
+ for c in children:
+ if isinstance(c, Element):
+ base.append(c.baseElement)
+ elif not base:
+ base.text = base.text and '\n'.join(base.text, c) or c
+ else:
+ base.tail = base.tail and '\n'.join(base.tail, c) or c
+ for a in attributes:
+ if a.endswith('_'):
+ attr = a[:-1]
+ attributes[attr] = attributes[a]
+ del attributes[a]
+ for a in attributes:
+ base.attrib[a] = attributes[a]
+ return self
+
+ def render(self, level=0):
+ out = StringIO()
+ out.write(' ' * level)
+ out.write('<' + self.__name__)
+ for a in self.attributes:
+ attr = a
+ if attr.endswith('_'):
+ attr = attr[:-1]
+ out.write(' %s="%s"' % (attr, self.attributes[a]))
+ out.write('>\n')
+ for e in self.children:
+ if isinstance(e, Element):
+ out.write(e.render(level+1))
+ else:
+ out.write(' ' * (level+1))
+ out.write(e)
+ out.write('\n')
+ out.write(' ' * level)
+ out.write('%s>' % self.__name__)
+ out.write('\n')
+ return out.getvalue()
+
+ def makeTree(self):
+ return etree.ElementTree(self.baseElement)
+
+ def renderTree(self):
+ out = StringIO()
+ tree = self.makeTree()
+ tree.write(out)
+ return out.getvalue()
+
+
+def fromXML(xml):
+ elem = etree.XML(xml)
+ return Element(elem)
diff --git a/xml/element.sav.py b/xml/element.sav.py
new file mode 100644
index 0000000..bd783d0
--- /dev/null
+++ b/xml/element.sav.py
@@ -0,0 +1,112 @@
+"""
+Generation and manipulation of XML trees.
+
+$Id$
+"""
+
+from cStringIO import StringIO
+from lxml import etree
+
+
+class Generator(object):
+
+ def __getitem__(self, name):
+ return Element(name)
+
+ def __getattr__(self, name):
+ return self[name]
+
+elements = Generator()
+
+
+class Element(object):
+
+ def __init__(self, name):
+ self.__name__ = name
+ self.children = []
+ self.attributes = {}
+
+ def __getattr__(self, name):
+ if name.endswith('_'):
+ name = name[:-1]
+ return self[name]
+
+ def __getitem__(self, name):
+ el = Element(name)
+ self.children.append(el)
+ return el
+
+ def __call__(self, *children, **attributes):
+ self.children.extend(list(children))
+ for a in attributes:
+ if a.endswith('_'):
+ attr = a[:-1]
+ attributes[attr] = attributes[a]
+ del attributes[a]
+ self.attributes.update(attributes)
+ return self
+
+ def render(self, level=0):
+ out = StringIO()
+ out.write(' ' * level)
+ out.write('<' + self.__name__)
+ for a in self.attributes:
+ attr = a
+ if attr.endswith('_'):
+ attr = attr[:-1]
+ out.write(' %s="%s"' % (attr, self.attributes[a]))
+ out.write('>\n')
+ for e in self.children:
+ if isinstance(e, Element):
+ out.write(e.render(level+1))
+ else:
+ out.write(' ' * (level+1))
+ out.write(e)
+ out.write('\n')
+ out.write(' ' * level)
+ out.write('%s>' % self.__name__)
+ out.write('\n')
+ return out.getvalue()
+
+ def makeTree(self):
+ elem = etree.Element(self.__name__)
+ makeSubTree(elem, self)
+ return etree.ElementTree(elem)
+
+ def renderTree(self):
+ out = StringIO()
+ tree = self.makeTree()
+ tree.write(out)
+ return out.getvalue()
+
+
+def makeSubTree(elem, content):
+ for a in content.attributes:
+ elem.set(a, content.attributes[a])
+ subElem = None
+ for c in content.children:
+ if isinstance(c, Element):
+ subElem = etree.SubElement(elem, c.__name__)
+ makeSubTree(subElem, c)
+ elif subElem is None:
+ elem.text = elem.text and '\n'.join(elem.text, c) or c
+ else:
+ subElem.tail = subElem.tail and '\n'.join(subElem.tail, c) or c
+
+
+def getElementsFromTree(elem):
+ content = Element(elem.tag)
+ for key, value in elem.items():
+ content.attributes[key] = value
+ if elem.text:
+ content.children.append(elem.text)
+ for child in elem.getchildren():
+ content.children.append(getElementsFromTree(child))
+ if child.tail:
+ content.children.append(child.tail)
+ return content
+
+
+def fromXML(xml):
+ elem = etree.XML(xml)
+ return getElementsFromTree(elem)
diff --git a/xml/tests.py b/xml/tests.py
new file mode 100755
index 0000000..4ad2c74
--- /dev/null
+++ b/xml/tests.py
@@ -0,0 +1,49 @@
+#! /usr/bin/python
+
+"""
+Tests for the 'cyberdev.xml' package.
+
+$Id$
+"""
+
+import unittest, doctest
+from zope.testing.doctestunit import DocFileSuite
+from cStringIO import StringIO
+
+from cybertools.xml.element import elements as e, fromXML
+
+
+class TestXml(unittest.TestCase):
+ "Basic tests for the xml package."
+
+ baseHtml = e.html(
+ e.head(e.title(u'Page Title')),
+ e.body(
+ e.div(u'The top bar', class_='top'),
+ e.div(u'The body stuff', class_='body'),
+ ))
+
+ def testBasicStuff(self):
+ doc = self.baseHtml
+ tree = doc.makeTree()
+ out = StringIO()
+ tree.write(out)
+ text = out.getvalue()
+
+ def testParsing(self):
+ xml = ('Page Title'
+ 'The top bar
'
+ 'The body stuff
')
+ doc = fromXML(xml)
+ text = doc.render()
+
+
+def test_suite():
+ flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
+ return unittest.TestSuite((
+ DocFileSuite('README.txt', optionflags=flags),
+ unittest.makeSuite(TestXml),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')