================================================ Step 2: Hello World as XML ================================================ We now have a default project named ``lxmlgraph``. It contains a *package* (also) named ``lxmlgraph``. In this step we will add an XML document to the *package* as our model data. We will leverage the following ``repoze.bfg`` machinery: - Model data with interfaces that define "types" - ZCML configuration to provide type-specific views Our application will need to do these things: - Our model class needs to use lxml to inject itelf into the XML nodes - That model class needs to implement the "handshake" All of the below filenames are relative to the ``lxmlgraph`` *package* rather than the project. File ``samplemodel.xml`` ----------------------------------- We're going to add an XML document that will serve as a source for model data named ``samplemodel.xml``. .. literalinclude:: step02/myapp/samplemodel.xml :linenos: :language: xml #. Line 2 provides the root of the model as an XML ```` node. The element name doesn't have to be ````. #. In lines 3-4, the ```` contains 2 top-level children: a and b. These are provided as an element name ````. This, also, is meaningfless as far as ``repoze.bfg`` is concerned. However, this is where you compose th information model you are publishing. The only special constraint is that a node that wants to be "found" by ``repoze.bfg`` in during traversal *must* have an ``name`` attribute. (The use of ``@name`` corresponds to ``__name__`` in the ``repoze.bfg`` handshake.) Each hop in the URL tries to grab a child with an attribute matching the next hop. Also, the value of the ``@name`` should be unique in its containing node. Module ``models.py`` ------------------------------ At a high level, we make write a class that "extends" lxml Element nodes, create an lxml parser, and register the custom class with the parser. .. literalinclude:: step02/myapp/models.py :linenos: #. Line 4 imports lxml. #. Line 9 creates the custom class we are going to use to extend etree.ElementBase. The lxml website has great documentation on the various ways to inject custom Python behavior into XML. #. Just as before, line 12 says that instances of this class support a certain content type (interface.) In our case, instances will be XML nodes. #. ``repoze.bfg`` has a "protocol" where model data should have an ``__name__`` attribute. Lines 14-16 implement this by grabbing the ``@name`` attribute of the current node. #. URL traversal in ``repoze.bfg`` works via the ``__getitem__`` protocol. Thus, we need a method that implements this. Lines 18-26 use XPath to look for a direct child that has an ``@name`` matching the item name that's being traversed to. If it finds it, return it. If not, or if more than one is found, raise an error. #. As before, ``get_root`` is the function that is expected to return the top of the model. In lines 30-32 we do the lxml magic to get the custom Python class registered. We then load some XML and return the top of the tree. Module ``views.py`` ----------------------------- Our ``views.py`` module does the following: .. literalinclude:: step02/myapp/views.py :linenos: #. Line 5 grabs the element name (tag name) of the ``context``, which is the current XML node that we're traversing through. #. Line 6 uses the special property we defined in our custom Python class to get the ``__name__`` of the context. Browsing the Model ------------------------ We can use these URLs to browse the model graph and see results:: http://localhost:5432/a http://localhost:5432/b http://localhost:5432/c (Not Found) In this case, each request grabs a node in the XML and uses it as the data for the view.