[Checkins] SVN: classix/ A library that helps to glue classes to
XML elements (lxml).
Martijn Faassen
faassen at infrae.com
Fri Jun 6 12:42:30 EDT 2008
Log message for revision 87209:
A library that helps to glue classes to XML elements (lxml).
Changed:
A classix/
A classix/trunk/
A classix/trunk/buildout.cfg
A classix/trunk/setup.py
A classix/trunk/src/
A classix/trunk/src/classix/
A classix/trunk/src/classix/README.txt
A classix/trunk/src/classix/__init__.py
A classix/trunk/src/classix/components.py
A classix/trunk/src/classix/directive.py
A classix/trunk/src/classix/meta.py
A classix/trunk/src/classix/tests.py
-=-
Added: classix/trunk/buildout.cfg
===================================================================
--- classix/trunk/buildout.cfg (rev 0)
+++ classix/trunk/buildout.cfg 2008-06-06 16:42:29 UTC (rev 87209)
@@ -0,0 +1,12 @@
+[buildout]
+develop = .
+parts = devpython test
+
+[devpython]
+recipe = zc.recipe.egg
+interpreter = devpython
+eggs = classix
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = classix
Added: classix/trunk/setup.py
===================================================================
--- classix/trunk/setup.py (rev 0)
+++ classix/trunk/setup.py 2008-06-06 16:42:29 UTC (rev 87209)
@@ -0,0 +1,20 @@
+from setuptools import setup, find_packages
+
+setup(
+ name='classix',
+ version='0.1',
+ author='Martijn Faassen',
+ author_email='faassen at startifact.com',
+ description="""\
+Declarative way to associate classes with lxml XML elements.
+""",
+ packages=find_packages('src'),
+ package_dir = {'': 'src'},
+ include_package_data = True,
+ zip_safe=False,
+ install_requires=[
+ 'setuptools',
+ 'lxml == 2.0.6',
+ 'martian >= 0.10',
+ ],
+)
Added: classix/trunk/src/classix/README.txt
===================================================================
--- classix/trunk/src/classix/README.txt (rev 0)
+++ classix/trunk/src/classix/README.txt 2008-06-06 16:42:29 UTC (rev 87209)
@@ -0,0 +1,168 @@
+Classix
+=======
+
+Classix is a thin layer over lxml's `custom element classes`_
+functionality that lets you configure them declaratively. It uses
+Martian_ to accomplish this effect, but to you this should be an
+implementation detail.
+
+.. _`custom element classes`: http://codespeak.net/lxml/element_classes.html
+
+.. _`Martian`: http://pypi.python.org/pypi/martian
+
+To parse an XML document you need a parser. An `XMLParser`` can be
+configured with custom element classes that will be associated to the
+right elements in the XML document after parsing. This way you can
+enrich the content model of your XML document.
+
+First we need to do the configuration for this package. This only needs to
+happen once for this package. First, we need to set up a ``GrokkerRegistry``.
+
+ >>> import martian
+ >>> reg = martian.GrokkerRegistry()
+
+Now we can grok the grokkers in this package::
+
+ >>> from classix import meta
+ >>> reg.grok('meta', meta)
+ True
+
+Now we can start using classix. You need to create an ``XMLParser``
+class that you will associate your custom element classes with::
+
+ >>> import classix
+ >>> class MyParser(classix.XMLParser):
+ ... pass
+
+Let's grok the parser::
+
+ >>> reg.grok('MyParser', MyParser)
+ True
+
+Now you can set up classes to associate with particular elements in particular
+namespaces, for that parser::
+
+ >>> XMLNS = 'http://ns.example.com'
+ >>> class Test(classix.Element):
+ ... classix.namespace(XMLNS)
+ ... classix.parser(MyParser)
+ ...
+ ... def custom_method(self):
+ ... return "The custom method"
+
+We also need to grok this::
+
+ >>> reg.grok('Test', Test)
+ True
+
+Now that we have it all set up, we can initialize the parser::
+
+ >>> parser = MyParser()
+
+Let's parse a bit of XML::
+
+ >>> xml = '''\
+ ... <test xmlns="http://ns.example.com" />
+ ... '''
+ >>> from lxml import etree
+ >>> root = etree.XML(xml, parser)
+ >>> root.custom_method()
+ 'The custom method'
+
+No namespace
+------------
+
+Sometimes you want to associate a class with an element in no
+namespace at all. Do do this, you can set the namepace to None
+explicitly::
+
+ >>> reg = martian.GrokkerRegistry()
+ >>> reg.grok('meta', meta)
+ True
+
+ >>> class MyParser(classix.XMLParser):
+ ... pass
+ >>> reg.grok('MyParser', MyParser)
+ True
+ >>> class Test(classix.Element):
+ ... classix.namespace(None)
+ ... classix.parser(MyParser)
+ ... def custom_method(self):
+ ... return 'The custom method for no namespace'
+ >>> reg.grok('Test', Test)
+ True
+ >>> parser = MyParser()
+ >>> no_ns_xml = '''\
+ ... <test />
+ ... '''
+ >>> root = etree.XML(no_ns_xml, parser)
+ >>> root.custom_method()
+ 'The custom method for no namespace'
+
+When supplied with an element with a namespace, the ``Test`` class will
+not be associated with that element::
+
+ >>> root = etree.XML(xml, parser)
+ >>> root.custom_method()
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'lxml.etree._Element' object has no attribute 'custom_method'
+
+Namespaces are assumed to be ``None`` if the ``classix.namespace``
+directive isn't used at all::
+
+ >>> class MyParser(classix.XMLParser):
+ ... pass
+ >>> reg.grok('MyParser', MyParser)
+ True
+ >>> class Test(classix.Element):
+ ... classix.parser(MyParser)
+ ... def custom_method(self):
+ ... return 'The custom method for no namespace 2'
+ >>> reg.grok('Test', Test)
+ True
+ >>> parser = MyParser()
+ >>> no_ns_xml = '''\
+ ... <test />
+ ... '''
+ >>> root = etree.XML(no_ns_xml, parser)
+ >>> root.custom_method()
+ 'The custom method for no namespace 2'
+
+Namespaces in parser
+--------------------
+
+As a convenience, you can also configure the default namespace in the
+parser, as a fall-back so you don't have to specify it in all the
+element classes::
+
+ >>> reg = martian.GrokkerRegistry()
+ >>> reg.grok('meta', meta)
+ True
+
+ >>> class MyParserWithNamespace(classix.XMLParser):
+ ... classix.namespace(XMLNS)
+ >>> reg.grok('MyParserWithNamespace', MyParserWithNamespace)
+ True
+
+ >>> class Test2(classix.Element):
+ ... classix.parser(MyParserWithNamespace)
+ ... classix.name('test') # also override name
+ ... def custom_method(self):
+ ... return "Another custom method"
+ >>> reg.grok('Test2', Test2)
+ True
+
+ >>> parser_ns = MyParserWithNamespace()
+ >>> root = etree.XML(xml, parser_ns)
+ >>> root.custom_method()
+ 'Another custom method'
+
+Absent classix.parser directive
+-------------------------------
+
+If the ``classix.parser` directive is absent, ``classix.Element``
+subclasses will be automatically associated with the one
+``classix.Parser`` available in the module.
+
+
Added: classix/trunk/src/classix/__init__.py
===================================================================
--- classix/trunk/src/classix/__init__.py (rev 0)
+++ classix/trunk/src/classix/__init__.py 2008-06-06 16:42:29 UTC (rev 87209)
@@ -0,0 +1,2 @@
+from classix.directive import namespace, name, parser
+from classix.components import XMLParser, Element
Added: classix/trunk/src/classix/components.py
===================================================================
--- classix/trunk/src/classix/components.py (rev 0)
+++ classix/trunk/src/classix/components.py 2008-06-06 16:42:29 UTC (rev 87209)
@@ -0,0 +1,9 @@
+from lxml import etree
+
+class Element(etree.ElementBase):
+ pass
+
+class XMLParser(etree.XMLParser):
+ def __init__(self, *args, **kw):
+ super(XMLParser, self).__init__(*args, **kw)
+ self.set_element_class_lookup(self._lookup)
Added: classix/trunk/src/classix/directive.py
===================================================================
--- classix/trunk/src/classix/directive.py (rev 0)
+++ classix/trunk/src/classix/directive.py 2008-06-06 16:42:29 UTC (rev 87209)
@@ -0,0 +1,21 @@
+import martian
+
+class parser(martian.Directive):
+ scope = martian.CLASS
+ store = martian.ONCE
+ validate = martian.validateClass
+
+def validateTextOrNone(self, value):
+ if value is None:
+ return value
+ return martian.validateText(self, value)
+
+class namespace(martian.Directive):
+ scope = martian.CLASS
+ store = martian.ONCE
+ validate = validateTextOrNone
+
+class name(martian.Directive):
+ scope = martian.CLASS
+ store = martian.ONCE
+ validate = martian.validateText
Added: classix/trunk/src/classix/meta.py
===================================================================
--- classix/trunk/src/classix/meta.py (rev 0)
+++ classix/trunk/src/classix/meta.py 2008-06-06 16:42:29 UTC (rev 87209)
@@ -0,0 +1,42 @@
+from lxml import etree
+import martian
+from classix.components import Element, XMLParser
+from classix.directive import namespace, name, parser
+
+def default_namespace(class_, module, parser, **data):
+ return namespace.bind().get(parser)
+
+def default_name(class_, module, parser, **data):
+ return class_.__name__.lower()
+
+class ElementGrokker(martian.ClassGrokker):
+ martian.component(Element)
+
+ martian.directive(parser)
+ martian.directive(namespace, get_default=default_namespace)
+ martian.directive(name, get_default=default_name)
+
+ def execute(self, class_, parser, namespace, name, **kw):
+ parser._lookup.add_element_class(namespace, name, class_)
+ return True
+
+class Lookup(etree.CustomElementClassLookup):
+ def __init__(self):
+ super(Lookup, self).__init__()
+ self._classes = {}
+
+ def add_element_class(self, namespace, name, class_):
+ self._classes[(namespace, name)] = class_
+
+ def lookup(self, node_type, document, namespace, name):
+ return self._classes.get((namespace, name))
+
+class XMLParserGrokker(martian.ClassGrokker):
+ martian.component(XMLParser)
+ martian.priority(martian.priority.bind().get(ElementGrokker) + 1)
+
+ martian.directive(namespace)
+
+ def execute(self, class_, **kw):
+ class_._lookup = Lookup()
+ return True
Added: classix/trunk/src/classix/tests.py
===================================================================
--- classix/trunk/src/classix/tests.py (rev 0)
+++ classix/trunk/src/classix/tests.py 2008-06-06 16:42:29 UTC (rev 87209)
@@ -0,0 +1,13 @@
+import unittest, doctest
+
+globs = {}
+optionflags = doctest.NORMALIZE_WHITESPACE + doctest.ELLIPSIS
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTests([
+ doctest.DocFileSuite('README.txt',
+ globs=globs,
+ optionflags=optionflags),
+ ])
+ return suite
More information about the Checkins
mailing list