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 Title
The 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('' % 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('' % 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')