[Repoze-checkins] r1476 - in repoze.bfg/trunk/docs/tutorials/lxmlgraph: . step04/myapp

Paul Everitt paul at agendaless.com
Fri Jul 25 11:24:07 EDT 2008


Author: Paul Everitt <paul at agendaless.com>
Date: Fri Jul 25 11:24:07 2008
New Revision: 1476

Log:
Step 4 written up.  I also made the small changes to the models.py that Chris had in Step 03.

Modified:
   repoze.bfg/trunk/docs/tutorials/lxmlgraph/index.rst
   repoze.bfg/trunk/docs/tutorials/lxmlgraph/step04.rst
   repoze.bfg/trunk/docs/tutorials/lxmlgraph/step04/myapp/models.py

Modified: repoze.bfg/trunk/docs/tutorials/lxmlgraph/index.rst
==============================================================================
--- repoze.bfg/trunk/docs/tutorials/lxmlgraph/index.rst	(original)
+++ repoze.bfg/trunk/docs/tutorials/lxmlgraph/index.rst	Fri Jul 25 11:24:07 2008
@@ -14,3 +14,4 @@
    step01
    step02
    step03
+   step04

Modified: repoze.bfg/trunk/docs/tutorials/lxmlgraph/step04.rst
==============================================================================
--- repoze.bfg/trunk/docs/tutorials/lxmlgraph/step04.rst	(original)
+++ repoze.bfg/trunk/docs/tutorials/lxmlgraph/step04.rst	Fri Jul 25 11:24:07 2008
@@ -76,8 +76,8 @@
 detail below.
 
 
-File ``samplemodel.xml``
-===================================
+``samplemodel.xml``
+=====================
 
 The XML document with the information for our website has quite a
 number of changes:
@@ -95,4 +95,157 @@
    ``<title>``, plus some HTML-namespaced markup content inside a
    ``<body>``.
 
-#. 
+#. Lines 13-32 show a nested XML hierarchy.  A ``<folder>`` gets an
+   ``@name``, making the folder itself published as a possible URL.
+   It also gets an ``@xml:id``, allowing it to be uniquely addressed
+   within the entire document.  The ``<folder>`` gets a ``<title>``
+   child node, but then gets two more ``__getitem__``-enabled child
+   nodes of type ``<document>``.  ("Enabled", as in, has a ``@name``
+   attribute.)
+
+Thus the major changes:
+
+- The root contains ``<folder>`` nodes which contain ``<document>``
+  nodes.
+
+- The root could contain documents too, and the folder could contain
+  sub-folders.  There's nothing special about the arrangement.
+
+- The only thing special is the presence of ``@name`` attributes,
+  which allow ``__getitem__`` (via XPath) to traverse to that child.
+
+- As we'll see in a second, the ``@xml:id`` allows jumping through the
+  hierarchy directly to a node.
+
+- Finally, for UI-framework-ish purposes, having a child ``<title>``
+  allows us to show in a browser what we're looking at.
+
+The ``models.py`` hasn't changed, so let's move to the small changes
+in the ``views.py``.
+
+``views.py``
+============
+
+As noted above, we removed the ZPT views, and thus ``views.py`` is
+shorter, and focused only on a function that provides an XSLT
+template.  Although there aren't many lines in that function, there
+are some concepts to explain:
+
+.. literalinclude:: step04/myapp/views.py
+   :linenos:
+
+#. We are going to be using a feature from XML called ``xml:id``,
+   which we explained above in the ``samplemodel.xml`` section.  In
+   lines 4 and 5, we make constants that point to the namespace
+   needed.
+
+#. As the comment says, line 9 grabs the "root" node of the site.
+   Inside this function, we are traversing, node-by-node, through a
+   hierarchy.  Thus our context node is an lxml Element object, which
+   supports a method to grab the tree (and thus root) in which the
+   context sits.
+
+#. Next, to support the XSLT approach we show next, we want to let the
+   XSLT know the ``xml:id`` that we are sitting on.  Since we are
+   passing it in as an XSLT paramter, we need some special handling:
+
+  - The *value* of the parameter is the ``@xml:id`` of the node we are
+    sitting on.
+
+  - Per lxml's needs, that value needs to be quoted.  Thus, the value
+    of ``contextid`` will be something like ``"'n1'"``
+
+#. Finally, on line 12, we call the XSLT processor, passing in keyword
+   arguments that become XSLT parameters.  Unlike before, the node we
+   pass in is the top of the tree, rather than the current (context)
+   node.
+
+In summary, we render the XSLT by handing it the root node of the
+tree, plus a flag that says which node we are currently sitting on.
+
+``xsltview.xsl``
+=================
+
+The XSLT template gets the most substantive changes, as we both have
+to support this root-and-contextid idea, as well as some features that
+put this to use:
+
+- Having a common look-and-feel across all pages, along with "rules"
+  that handle each content type
+
+- Show the context's ``<title>`` in the same place
+
+- Show some general information about the node
+
+The following XSLT accomplishes these features:
+
+.. literalinclude:: step04/myapp/xsltview.xsl
+   :linenos:
+   :language: xml
+
+#. Line 3 accepts the ``@xml:id`` passed in as a parameter of the XSLT
+   transformation.  This can differ between requests, as different
+   nodes are traversed.
+
+#. Line 4 jumps directly to the tree node that has that ``@xml:id`` by
+   using XPath's ``id()`` function.  This is a high-speed lookup,
+   similar to ``document.getElementById()`` in JavaScript.  We then
+   assign the node to a global XSLT variable, to avoid paying that
+   price again.
+
+#. Line 5 gets into XSLT's rule-oriented mumbo-jumbo.  This template
+   rule says: "Match on the root of the tree that was passed in, then
+   do some work."  Think of this as a CSS rule that matches on ``body
+   {}``.
+
+#. Lines 7-11 output HTML for the document and ``<head>``.
+
+#. Line 9 (and line 14) inserts the value of the context node's
+   ``<title>`` using ``<xsl:value-of>``.
+
+#. Line 16 does some XSLT mumbo jumbo.  It says "Find a rule that
+   handles the context node, which might be a ``<folder>`` or might be
+   a ``<document>``."  Control is then passed to an ``<xsl:template>``
+   that meets the conditions.  Once that rule is finished, control
+   returns to line 17.
+
+#. Lines 17-42 then format some basic information about the context
+   node.  The HTML generated for this, however, appears *after* the
+   type-specific handler in the resulting HTML.
+
+#. Line 46 is an ``<xsl:template>`` rule that handles ``<folder>``
+   nodes.  It only gets control when something else hands control to it.
+
+   In this case, the rule makes a paragraph then lists the contents of
+   the folder.
+
+#. Line 50 checks to see if the folder contains any "publishable"
+   content.  We wouldn't want a heading to appear saying "Folder
+   Contents" with empty space under it.
+
+#. Line 53 then iterates over all the child nodes which have an
+   ``@xml:id``.
+
+#. Lines 55-57 make an ``<li>`` with an ``<a>`` for each item in the
+   folder.  Inside the ``<xsl:for-each``, the "current" node is the
+   current item in the iteration.  The ``@href`` uses what XSLT calls
+   "attribute value template" (curly braces) to let the XSLT processor
+   operate inside an attribute.
+
+#. Line 63 handles ``<document>`` nodes when handed control.
+
+#. Line 67 recursively copies the nodes in the ``<document>`` content.
+
+To recap, this XSLT handles any node passed in, and generates a UI
+that can handle the global styling, the navigational elements, and the
+content for the current traversal hop.
+
+Conclusion
+=====================
+
+Though not very much code, this is the basis for a useful amount of
+features.  A hierarchical website with templating that can handle
+global styling and navigation, as well as type-driven templating, all
+at reasonable (albeit in-memory) performance.
+
+

Modified: repoze.bfg/trunk/docs/tutorials/lxmlgraph/step04/myapp/models.py
==============================================================================
--- repoze.bfg/trunk/docs/tutorials/lxmlgraph/step04/myapp/models.py	(original)
+++ repoze.bfg/trunk/docs/tutorials/lxmlgraph/step04/myapp/models.py	Fri Jul 25 11:24:07 2008
@@ -1,3 +1,5 @@
+import os
+
 from zope.interface import implements
 from zope.interface import Attribute
 from zope.interface import Interface
@@ -32,7 +34,9 @@
     parser.set_element_class_lookup(parser_lookup)
 
     # Now load the XML file
-    xmlstring = open("myapp/samplemodel.xml").read()
+    here = os.path.join(os.path.dirname(__file__))
+    samplemodel = os.path.join(here, 'samplemodel.xml')
+    xmlstring = open(samplemodel).read()
     root = etree.XML(xmlstring, parser)
 
     return root


More information about the Repoze-checkins mailing list