================================================ Step 2: Hello World as XML ================================================ We now have a 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 :mod:`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: - Use :term:`lxml` Element classes to inject :mod:`repoze.bfg` behavior into ``lxml`` nodes - That model class needs to implement the :mod:`repoze.bfg` publishing contract All of the below filenames are relative to the ``lxmlgraph`` *package* rather than the *project*. ``samplemodel.xml`` ----------------------------------- We're going to add an XML document that will serve as a source for model data named ``samplemodel.xml``. Put the content of this file in your package: .. 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 ````. It has a name of ``site``. #. In lines 3-4, the ```` contains 2 top-level children: a and b. These are provided as an element name ````. This, also, is meaningless as far as :mod:`repoze.bfg` is concerned. However, this is where you compose the information model you are publishing. The only special constraint is that an XML node that wants to be "found" by :mod:`repoze.bfg` in during traversal *must* have a ``name`` attribute. (The use of ``@name`` corresponds to ``__name__`` in the :mod:`repoze.bfg` sense of :term:`location` ). 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. Replace the contents of the autogenerated ``models.py`` with the content we show below. .. literalinclude:: step02/myapp/models.py :linenos: #. Line 4 imports :term:`lxml`. #. Line 9 creates the custom class we are going to use to extend etree.ElementBase. The ``_ 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. #. :mod:`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 :mod:`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+ we do the :term:`lxml` magic to get the custom Python class registered. We then load some XML and return the top of the tree. Module ``views.py`` ----------------------------- Replace the autogenerated ``views.py`` code in the ``lxmlgraph`` package with the following: .. literalinclude:: step02/myapp/views.py :linenos: Here's what that file does: #. 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. We don't need to change the ``configure.zcml`` because the autogenerated one is still correct for this configuration. It includes: .. literalinclude:: step02/myapp/configure.zcml :linenos: :language: xml Browsing the Model ------------------------ We're done changing code. Start the application by executing ``paster serve lxmlgraph.ini`` (the ``.ini`` file is in the project directory). It will listen on port 5432. We can use these URLs to browse the model graph and see results:: http://localhost:5432/a (Hello to document from a @ /a) http://localhost:5432/b (Hello to document from b @ /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.