[Checkins] SVN: z3c.layout/trunk/src/z3c/layout/ Major changes to tighten key functionality.

Malthe Borch mborch at gmail.com
Sun Aug 3 06:33:16 EDT 2008


Log message for revision 89249:
  Major changes to tighten key functionality.

Changed:
  U   z3c.layout/trunk/src/z3c/layout/README.txt
  D   z3c.layout/trunk/src/z3c/layout/browser/configure.zcml
  A   z3c.layout/trunk/src/z3c/layout/browser/insertion.py
  D   z3c.layout/trunk/src/z3c/layout/browser/interfaces.py
  U   z3c.layout/trunk/src/z3c/layout/browser/layout.py
  D   z3c.layout/trunk/src/z3c/layout/browser/providers.py
  D   z3c.layout/trunk/src/z3c/layout/configure.zcml
  U   z3c.layout/trunk/src/z3c/layout/interfaces.py
  D   z3c.layout/trunk/src/z3c/layout/layout.py
  U   z3c.layout/trunk/src/z3c/layout/meta.zcml
  A   z3c.layout/trunk/src/z3c/layout/model.py
  D   z3c.layout/trunk/src/z3c/layout/regions.py
  U   z3c.layout/trunk/src/z3c/layout/tests/templates/default/index.html
  U   z3c.layout/trunk/src/z3c/layout/tests/test_doctests.py
  A   z3c.layout/trunk/src/z3c/layout/utils.py
  U   z3c.layout/trunk/src/z3c/layout/zcml.py
  U   z3c.layout/trunk/src/z3c/layout/zcml.txt

-=-
Modified: z3c.layout/trunk/src/z3c/layout/README.txt
===================================================================
--- z3c.layout/trunk/src/z3c/layout/README.txt	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/README.txt	2008-08-03 10:33:16 UTC (rev 89249)
@@ -1,81 +1,79 @@
 Walk-through
 ============
 
-A layout is essentially an HTML-document with zero or more region
-definitions.
+Layouts and regions
+-------------------
 
 Let's begin by instantiating a layout. We'll do this manually for the
-sake of this demonstration; usually this is done using the
-ZCML-directive <browser:layout>, which is available with this package.
+sake of this demonstration; usually this is done using the included
+ZCML-directive <browser:layout>.
 
-    >>> from z3c.layout.layout import Layout
+    >>> from z3c.layout.model import Layout
+    
     >>> layout = Layout(
-    ...     "Testlayout",
-    ...     "test",
-    ...     "%s/templates/default/index.html" % test_path,
-    ...     "index.jpg")
+    ...     "test", "%s/templates/default/index.html" % test_path, "test")
 
-We need register this layout as a utility to make it available for the
-rendering machinery.
+Register resource directory.
+    
+    >>> import zope.configuration.config as config
+    >>> context = config.ConfigurationMachine()
+    
+    >>> from zope.app.publisher.browser import resourcemeta
+    >>> resourcemeta.resourceDirectory(
+    ...     context, "test", "%s/templates/default" % test_path)
+    >>> context.execute_actions()
+    
+Layouts are made dynamic by defining one or more regions. They are
+mapped to HTML locations using an xpath-expression and an insertion
+mode, which is one of "replace", "append", "prepend", "before" or
+"after".
 
-    >>> from z3c.layout.interfaces import ILayout
-    >>> component.provideUtility(layout, ILayout, name="testlayout")
+Regions can specify the name of a content provider directly or it may
+rely on adaptation to yield a content provider component. We'll
+investigate both of these approaches:
 
-Regions
--------
+    >>> from z3c.layout.model import Region
 
-A region is the interior of an element in the template as located by
-an xpath-expression. Regions are named and may optionally be given a
-title.
+First we define a title region where we directly specify the name of a
+content provider.
 
-    >>> from z3c.layout.regions import Region
+    >>> title = Region("title", ".//title", title=u"Title", provider="title")
 
-We'll define two regions.
+Then a content region where we leave it the content provider to
+component adaptation.
+    
+    >>> content = Region("content", ".//div", "Content")
 
-    >>> logo = Region("logo", ".//p", "The Logo Region")
-    >>> content = Region("content", ".//div", "The Content Region")
+To register them with the layout we simply add them.
 
-To add them to the layout we simply append them to the list of
-regions.
+    >>> layout.regions.add(title)
+    >>> layout.regions.add(content)
 
-    >>> layout.regions.append(logo)
-    >>> layout.regions.append(content)
+We need to provide a general adapter that can provide content
+providers for regions that do not specify them directly.
 
-Region content providers
-------------------------
+    >>> from z3c.layout.interfaces import IRegion
+    >>> from zope.contentprovider.interfaces import IContentProvider
+    >>> from zope.publisher.interfaces.browser import IBrowserRequest
+    >>> from zope.publisher.interfaces.browser import IBrowserView
 
-Regions are rendered by content providers. When rendering a page, a
-layout assignment dictates which providers are to be used for
-rendering which regions.
-
-    >>> from z3c.layout.interfaces import ILayoutAssignment
-
-A layout assignment defines the active layout and has information on
-how to render each region. This is defined in the provider map:
-
-    >>> class LayoutAssignment(object):
-    ...     def __init__(self, name, provider_map):
-    ...         self.name = name
-    ...         self.provider_map = provider_map
-
-Let's set up an assignment for our two regions.
+As an example, we'll define an adapter that simply tries to lookup a
+content provider with the same name as the region.
     
-    >>> assignment = LayoutAssignment('testlayout', {
-    ...     'logo': 'logo_provider',
-    ...     'content': 'content_provider'})
+    >>> @interface.implementer(IContentProvider)
+    ... @component.adapter(IRegion, IBrowserRequest, IBrowserView)
+    ... def getEponymousContentProvider(region, request, view):
+    ...     return component.getMultiAdapter(
+    ...        (region, request, view), IContentProvider, region.name)
 
-A layout is rendered in some context, typically a page. We'll provide
-this layout assignment for all such pages.
-
-    >>> class MockPage(object):
-    ...     interface.implements(interface.Interface)
+    >>> component.provideAdapter(getEponymousContentProvider)
     
-    >>> component.provideAdapter(
-    ...     lambda page: assignment, (MockPage,), ILayoutAssignment)
-    
-We proceed by setting up content providers that render the
-regions. This follows the standard content provider interface.
+Rendering
+---------
 
+Before we can render the layout, we need to register content providers
+for the two regions. We'll use a mock class for demonstration.
+
     >>> from zope.contentprovider.interfaces import IContentProvider
     
     >>> class MockContentProvider(object):
@@ -95,95 +93,51 @@
     ...     def __repr__(self):
     ...         return "<MockContentProvider '%s'>" % self.__name__
 
-    >>> from zope.publisher.interfaces.browser import IBrowserRequest
-    >>> from zope.publisher.interfaces.browser import IBrowserView
-
-    >>> from z3c.layout.interfaces import IRegion
-
     >>> component.provideAdapter(
     ...     MockContentProvider, (IRegion, IBrowserRequest, IBrowserView),
-    ...     name="logo_provider")
+    ...     name="title")
 
     >>> component.provideAdapter(
     ...     MockContentProvider, (IRegion, IBrowserRequest, IBrowserView),
-    ...     name="content_provider")
+    ...     name="content")
 
-Rendering the layout
---------------------
-    
-The layout is rendered by a specialized view.
-    
+Let's instantiate the layout browser-view. We must define a context
+and set up a request.
+
+    >>> class MockContext(object):
+    ...     interface.implements(interface.Interface)
+
     >>> from zope.publisher.browser import TestRequest
-    >>> page = MockPage()
+    
+    >>> context = MockContext()
     >>> request = TestRequest()
 
+The view expects the context to adapt to ``ILayout``.
+    
+    >>> from z3c.layout.interfaces import ILayout
+    >>> component.provideAdapter(
+    ...     lambda context: layout, (MockContext,), ILayout)
+
     >>> from z3c.layout.browser.layout import LayoutView
-    >>> view = LayoutView(page, request)
+    >>> view = LayoutView(context, request)
 
 Verify that the layout view is able to get to these providers.
     
-    >>> list(view._get_region_content_providers())
-    [(<Region 'logo'>, <MockContentProvider 'logo'>),
-     (<Region 'content'>, <MockContentProvider 'content'>)]
+    >>> tuple(view._get_content_providers())
+    ((<Region 'title' .//title (replace) 'title'>, <MockContentProvider 'title'>),
+     (<Region 'content' .//div (replace) None>, <MockContentProvider 'content'>))
 
-    >>> assignment.provider_map['logo'] = 'non_existing_logo_provider'
+Now for the actual output.
 
-Even if a provider map is registered, we might not be able to look
-them up. Missing components will be silently ignored.
-
-    >>> list(view._get_region_content_providers())
-    [(<Region 'content'>, <MockContentProvider 'content'>)]
-
-Let's restore the assignment to the correct logo provider before
-proceeding.
-
-    >>> assignment.provider_map['logo'] = 'logo_provider'
-    
-Let's try rendering the page. We expect the interior of the two
-regions we've defined to be replaced by the output of the (dummy)
-region content providers.
-     
     >>> print view()
     <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
     <html>
-    <head><link rel="stylesheet" href="++resource++test/main.css" type="text/css" media="screen"></head>
+    <head>
+    <link rel="stylesheet" href="test/main.css" type="text/css" media="screen">
+    <title>title</title>
+    </head>
     <body>
-        <p>logo</p>
         <div id="content">content</div>
       </body>
     </html>
 
-Tree content providers
-----------------------
-
-Layouts may be augmented with tree content providers that insert their
-content into the element tree before it's serialized and returned.
-
-    >>> from z3c.layout.browser.interfaces import ITreeContentProvider
-    >>> import lxml.html
-    
-    >>> class MockTreeContentProvider(MockContentProvider):
-    ...     interface.implements(ITreeContentProvider)
-    ...
-    ...     def insert(self, tree):
-    ...         body = tree.find('.//body')
-    ...         body.append(lxml.html.fromstring(self.render()))
-    ...
-    ...     def render(self):
-    ...         return u"<span>Hello World!</span>"
-    
-    >>> component.provideAdapter(
-    ...     MockTreeContentProvider,
-    ...     (MockPage, IBrowserRequest, IBrowserView),
-    ...     IContentProvider)
-
-    >>> print view()
-    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
-    <html>
-    <head><link rel="stylesheet" href="++resource++test/main.css" type="text/css" media="screen"></head>
-    <body>
-        <p>logo</p>
-        <div id="content">content</div>
-      <span>Hello World!</span>
-    </body>
-    </html>

Deleted: z3c.layout/trunk/src/z3c/layout/browser/configure.zcml
===================================================================
--- z3c.layout/trunk/src/z3c/layout/browser/configure.zcml	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/browser/configure.zcml	2008-08-03 10:33:16 UTC (rev 89249)
@@ -1,24 +0,0 @@
-<configure xmlns="http://namespaces.zope.org/zope"
-           xmlns:browser="http://namespaces.zope.org/browser">
-
-  <include package="zope.viewlet" file="meta.zcml" />
-  
-  <browser:viewletManager
-     name="z3c.layout.htmlhead"
-     class=".providers.HtmlHead"
-     permission="zope.View"
-     />
-
-  <browser:viewletManager
-     name="z3c.layout.htmltop"
-     class=".providers.HtmlTop"
-     permission="zope.View"
-     />
-
-  <browser:viewletManager
-     name="z3c.layout.htmlbottom"
-     class=".providers.HtmlBottom"
-     permission="zope.View"
-     />
-
-</configure>    

Added: z3c.layout/trunk/src/z3c/layout/browser/insertion.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/browser/insertion.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/browser/insertion.py	2008-08-03 10:33:16 UTC (rev 89249)
@@ -0,0 +1,92 @@
+def replace(node, provided):
+    """Replace node with contents.
+
+    >>> from lxml import html
+    >>> tree = html.fromstring('<div>abc</div>')
+    >>> from z3c.layout.browser.insertion import replace
+    >>> replace(tree, html.fromstring('<div>def</div>'))
+    >>> html.tostring(tree)
+    '<div>def</div>'
+    
+    """
+    
+    del node[:]
+
+    if provided.text:
+        node.text = provided.text
+        
+    node.extend(tuple(provided))
+
+def prepend(node, provided):
+    """Prepend contents to node.
+
+    >>> from lxml import html
+    >>> tree = html.fromstring('<div>abc<span>def</span></div>')
+    >>> from z3c.layout.browser.insertion import prepend
+    >>> prepend(tree, html.fromstring('<div>ghi<p>jkl</p></div>'))
+    >>> html.tostring(tree)
+    '<div>ghiabc<p>jkl</p><span>def</span></div>'
+    
+    """
+
+    if provided.text:
+        node.text = provided.text + node.text or ""
+
+    for element in reversed(provided):
+        node.insert(0, element)
+
+def append(node, provided):
+    """Append contents to node.
+
+    >>> from lxml import html
+    >>> tree = html.fromstring('<div><span>abc</span>def</div>')
+    >>> from z3c.layout.browser.insertion import append
+    >>> append(tree, html.fromstring('<div>ghi<p>jkl</p></div>'))
+    >>> html.tostring(tree)
+    '<div><span>abc</span>defghi<p>jkl</p></div>'
+    
+    """
+
+    if provided.text and len(node) > 0:
+        last = node[-1]
+        last.tail = (last.tail or "") + provided.text
+    
+    for element in reversed(provided):
+        node.append(element)
+
+def before(node, provided):
+    """Add contents before node.
+
+    >>> from lxml import html
+    >>> tree = html.fromstring('<div><span>abc</span>def<span>ghi</span></div>')
+    >>> from z3c.layout.browser.insertion import before
+    >>> before(tree.xpath('.//span')[1], html.fromstring('<div>jkl<p>mno</p>pqr</div>'))
+    >>> html.tostring(tree)
+    '<div><span>abc</span>defjkl<p>mno</p>pqr<span>ghi</span></div>'
+    
+    """
+
+    prev = node.getprevious()
+    if prev is not None and provided.text:
+        prev.tail = (prev.tail or "") + provided.text
+
+    for element in reversed(provided):
+        node.addprevious(element)
+
+def after(node, provided):
+    """Add contents after node.
+
+    >>> from lxml import html
+    >>> tree = html.fromstring('<div><span>abc</span>def<span>ghi</span></div>')
+    >>> from z3c.layout.browser.insertion import after
+    >>> after(tree.xpath('.//span')[0], html.fromstring('<div>jkl<p>mno</p>pqr</div>'))
+    >>> html.tostring(tree)
+    '<div><span>abc</span>jkl<p>mno</p>pqrdef<span>ghi</span></div>'
+    
+    """
+
+    for element in provided:
+        node.addnext(element)
+
+    if provided.text:
+        node.tail = node.tail or "" + provided.text

Deleted: z3c.layout/trunk/src/z3c/layout/browser/interfaces.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/browser/interfaces.py	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/browser/interfaces.py	2008-08-03 10:33:16 UTC (rev 89249)
@@ -1,6 +0,0 @@
-from zope.contentprovider.interfaces import IContentProvider
-
-class ITreeContentProvider(IContentProvider):
-    def insert(tree):
-        """Render and insert into the element tree."""
-

Modified: z3c.layout/trunk/src/z3c/layout/browser/layout.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/browser/layout.py	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/browser/layout.py	2008-08-03 10:33:16 UTC (rev 89249)
@@ -7,140 +7,69 @@
 from zope.publisher.browser import BrowserView
 from zope.contentprovider.interfaces import IContentProvider
 
-from z3c.layout.interfaces import ILayout
-from z3c.layout.interfaces import ILayoutAssignment
-from z3c.layout.browser.interfaces import ITreeContentProvider
+from z3c.layout import interfaces
 
-from urlparse import urlparse
+import insertion
 
 class LayoutView(BrowserView):
     def __init__(self, context, request):
         BrowserView.__init__(self, context, request)
-        
-        self.assignment = assignment = ILayoutAssignment(context)
-        self.layout = component.getUtility(
-            ILayout, name=assignment.name)
-        
+        self.layout = interfaces.ILayout(context)
+                
     def __call__(self):
-        tree = lxml.html.parse(self.layout.template)        
-        rebase(tree, '++resource++%s' % self.layout.resource_directory)
+        tree = self.layout.parse()
 
         # lookup content provider components
-        region_content_providers = self._get_region_content_providers()
-        tree_content_providers = self._get_tree_content_providers()
+        content_providers = self._get_content_providers()
         
         # update content providers
-        for region, provider in region_content_providers:
+        for region, provider in content_providers:
             provider.update()
 
-        for provider in tree_content_providers:
-            provider.update()
+        # render and insert content providers
+        for region, provider in content_providers:
+            self._insert_provider(tree, region, provider)
 
-        # render and insert region content providers
-        for region, provider in region_content_providers:
-            html = provider.render()
-            subtree = tree.xpath(region.xpath)
+        return lxml.html.tostring(tree, pretty_print=True).rstrip('\n')
+    
+    def _insert_provider(self, tree, region, provider):
+        # render and wrap provided content
+        html = provider.render() 
+        provided = lxml.html.fromstring(
+            u"<div>%s</div>" % html)
 
-            if len(subtree) != 1:
-                continue
+        # look up insertion method
+        try:
+            insert = getattr(insertion, region.mode)
+        except AttributeError:
+            raise ValueError("Invalid mode: %s" % repr(region.mode))
 
-            subtree = subtree[0]
+        # insert provided content into nodes
+        nodes = tree.xpath(region.xpath)
+        for node in nodes:
+            insert(node, provided)
 
-            del subtree[:]
-
-            provided = lxml.html.fromstring(
-                u"<div>%s</div>" % html)
-
-            subtree.text = provided.text
-            subtree.extend(tuple(provided))
-
-        # render and insert tree content providers
-        for provider in tree_content_providers:
-            provider.insert(tree)
-            
-        return lxml.html.tostring(tree, pretty_print=True).rstrip('\n')
-
-    def _get_region_content_providers(self):
-        """Lookup region content providers."""
+    def _get_content_providers(self):
+        """Lookup content providers for regions."""
         
         results = []
-        provider_map = self.assignment.provider_map
         
         for region in self.layout.regions:
-            name = provider_map.get(region.name, None)
+            name = region.provider
+
             if name is not None:
                 provider = component.queryMultiAdapter(
                     (region, self.request, self), IContentProvider, name=name)
-                
-                if provider is not None:
-                    provider.__name__ = region.name
-                    results.append((region, provider))
+            else:
+                provider = component.queryMultiAdapter(
+                    (region, self.request, self), IContentProvider)
 
-        return results
+            if provider is None:
+                raise ValueError(
+                    "Unable to determine content "
+                    "provider for region '%s'." % region.name)
 
-    def _get_tree_content_providers(self):
-        """Lookup tree content providers.
+            provider.__name__ = region.name
+            results.append((region, provider))
 
-        To play nice with viewlet managers and content provider
-        adapters alike, we first lookup factories providing the
-        ``IContentProvider`` interface, then return all providers that
-        implement the ``ITreeContentProvider`` interface.
-        """
-        
-        sm = component.getSiteManager()
-        required = self.context, self.request, self
-
-        factories = sm.adapters.lookupAll(
-            map(interface.providedBy, required), IContentProvider)
-
-        return [factory(*required) for name, factory in factories if \
-                ITreeContentProvider.implementedBy(factory)]
-            
-def rebase(tree, prefix):
-    """Rebase resources.
-
-    Parse through an lxml tree, adding the ``prefix`` to all tag
-    attributes that point to URL-relative layout resources:
-
-    Tag        Attribute
-    -----------------------------
-    img        src
-    
-    >>> from z3c.layout.browser.layout import rebase
-    >>> import lxml
-
-    >>> html = '''\
-    ... <html>
-    ...   <head>
-    ...      <link href="some/url" />
-    ...   </head>
-    ...   <body>
-    ...      <img src="some/url" />
-    ...      <img src="http://host/some/url" />
-    ...   </body>
-    ... </html>'''
-
-    >>> tree = lxml.etree.fromstring(html)
-    >>> rebase(tree, '++foo++')
-    >>> print lxml.etree.tostring(tree, pretty_print=True)
-    <html>
-      <head>
-         <link href="++foo++/some/url"/>
-      </head>
-      <body>
-         <img src="++foo++/some/url"/>
-         <img src="http://host/some/url"/>
-      </body>
-    </html>
-    
-    """
-
-    for xpath, attribute in (('.//img', 'src'),
-                             ('.//head/link', 'href')):
-        for element in tree.findall('%s[@%s]' % (xpath, attribute)):
-            value = element.get(attribute)
-            protocol, host = urlparse(value)[:2]
-
-            # rebase if host is trivial
-            if not host:
-                element.set(attribute, '%s/%s' % (prefix, value))
+        return results            

Deleted: z3c.layout/trunk/src/z3c/layout/browser/providers.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/browser/providers.py	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/browser/providers.py	2008-08-03 10:33:16 UTC (rev 89249)
@@ -1,66 +0,0 @@
-from zope import interface
-from lxml import html
-
-import interfaces
-
-class HtmlHead(object):
-    """Tree content provider mixin.
-
-    Appends rendered content into the HTML <head>.
-    """
-
-    interface.implements(interfaces.ITreeContentProvider)
-        
-    def insert(self, tree):
-        head = tree.find('.//head')
-        if head is not None:
-            provided = html.fromstring(
-                u"<div>%s</div>" % self.render())
-
-            if len(head) > 0:
-                head[-1].tail = provided.text
-            else:
-                head.text = provided.text
-
-            head.extend(tuple(provided))
-
-class HtmlTop(object):
-    """Tree content provider mixin.
-
-    Prepends rendered content to HTML <body>.
-    """
-
-    interface.implements(interfaces.ITreeContentProvider)
-    
-    def insert(self, tree):
-        body = tree.find('.//body')
-        if body is not None:
-            provided = html.fromstring(
-                u"<div>%s</div>" % self.render())
-
-            provided.tail = body.text
-            body.text = provided.text
-
-            for element in reversed(provided):
-                body.insert(0, element)
-
-class HtmlBottom(object):
-    """Tree content provider mixin.
-
-    Appends rendered content to HTML <body>.
-    """
-
-    interface.implements(interfaces.ITreeContentProvider)
-    
-    def insert(self, tree):
-        body = tree.find('.//body')
-        if body is not None:
-            provided = html.fromstring(
-                u"<div>%s</div>" % self.render())
-
-            if len(body) > 0:
-                body[-1].tail = provided.text
-            else:
-                body.text = provided.text
-            
-            body.extend(tuple(provided))

Deleted: z3c.layout/trunk/src/z3c/layout/configure.zcml
===================================================================
--- z3c.layout/trunk/src/z3c/layout/configure.zcml	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/configure.zcml	2008-08-03 10:33:16 UTC (rev 89249)
@@ -1,5 +0,0 @@
-<configure xmlns="http://namespaces.zope.org/zope">
-
-  <include package=".browser" />
-  
-</configure>

Modified: z3c.layout/trunk/src/z3c/layout/interfaces.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/interfaces.py	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/interfaces.py	2008-08-03 10:33:16 UTC (rev 89249)
@@ -1,27 +1,7 @@
 from zope.interface import Interface, Attribute
+from zope.configuration import fields
 from zope import schema
 
-class ILayoutAssignment(Interface):
-    """A layout assignment."""
-    
-    name = Attribute(
-        """Layout name.""")
-    
-    provider_map = Attribute(
-        """Mapping from region names to content providers.""")
-
-class ILayout(Interface):
-    """A layout is a template with region definitions."""
-    
-    regions = Attribute(
-        """Regions that are defined in this layout.""")
-
-    template = Attribute(
-        """Template path.""")
-    
-    resource_directory = Attribute(
-        """Browser resource directory name.""")
-
 class IRegion(Interface):
     """Represents a region definition for a template."""
 
@@ -29,10 +9,35 @@
         title=u"Name of region.",
         required=True)
     
+    xpath = schema.TextLine(
+        title=u"X-path expression for this region",
+        required=True)
+
     title = schema.TextLine(
         title=u"Title",
         required=False)
 
-    xpath = schema.TextLine(
-        title=u"Xpath expression for this region",
-        required=True)
+    mode = schema.Choice(
+        (u"replace", u"append", u"prepend", u"before", u"after"),
+        default=u"replace",
+        required=False)
+    
+    provider = schema.TextLine(
+        title=u"Content provider",
+        description=u"Name of the content provider component to render this region.",
+        required=False)
+
+class ILayout(Interface):
+    """A layout is a template with region definitions."""
+
+    name = schema.TextLine(
+        title=u"Title")
+    
+    template = fields.Path(
+        title=u"Template")
+
+    regions = schema.Set(
+        title=u"Regions that are defined in this layout.",
+        value_type=schema.Object(schema=IRegion),
+        required=False)
+

Deleted: z3c.layout/trunk/src/z3c/layout/layout.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/layout.py	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/layout.py	2008-08-03 10:33:16 UTC (rev 89249)
@@ -1,18 +0,0 @@
-import os
-
-from zope import interface
-from zope import component
-
-from z3c.layout.interfaces import ILayout
-from z3c.layout.interfaces import IRegion
-
-
-class Layout(object):
-    interface.implements(ILayout)
-
-    def __init__(self, name, resource_directory, template, thumbnail):
-        self.name = name
-        self.resource_directory = resource_directory
-        self.template = template
-        self.thumbnail = thumbnail
-        self.regions = []

Modified: z3c.layout/trunk/src/z3c/layout/meta.zcml
===================================================================
--- z3c.layout/trunk/src/z3c/layout/meta.zcml	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/meta.zcml	2008-08-03 10:33:16 UTC (rev 89249)
@@ -1,13 +1,17 @@
 <configure xmlns="http://namespaces.zope.org/meta">
     <directives namespace="http://namespaces.zope.org/browser">
-    <complexDirective
-       name="layout"
-       schema=".zcml.ILayoutDirective"
-       handler=".zcml.LayoutDirective">
-       <subdirective
-               name="region"
-               schema=".zcml.IRegionDirective" />
+      
+      <complexDirective
+         name="layout"
+         schema=".interfaces.ILayout"
+         handler=".zcml.LayoutDirective">
+        
+        <subdirective
+           name="region"
+           schema=".interfaces.IRegion" />
+        
     </complexDirective>
+      
   </directives>
 </configure>
 

Added: z3c.layout/trunk/src/z3c/layout/model.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/model.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/model.py	2008-08-03 10:33:16 UTC (rev 89249)
@@ -0,0 +1,41 @@
+from zope import interface
+from zope import component
+
+from zope.app.publisher.browser.directoryresource import Directory
+from zope.security.checker import CheckerPublic
+
+import lxml.html
+import interfaces
+import utils
+
+class Layout(object):
+    interface.implements(interfaces.ILayout)
+
+    def __init__(self, name, template, resource_path, regions=None):
+        self.name = name
+        self.template = template
+        self.regions = regions or set()
+        self.resource_path = resource_path
+        
+    def parse(self):
+        tree = lxml.html.parse(self.template)
+        utils.rebase(tree, self.resource_path)
+        return tree
+    
+class Region(object):
+    interface.implements(interfaces.IRegion)
+
+    def __init__(self, name, xpath, title=u"", mode="replace", provider=None):
+        self.name = name
+        self.xpath = xpath
+        self.title =  title
+        self.mode = mode
+        self.provider = provider
+        
+    def __repr__(self):
+        return "<%s %s %s (%s) %s>" % (
+            self.__class__.__name__,
+            repr(self.name),
+            self.xpath,
+            self.mode,
+            repr(self.provider))

Deleted: z3c.layout/trunk/src/z3c/layout/regions.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/regions.py	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/regions.py	2008-08-03 10:33:16 UTC (rev 89249)
@@ -1,14 +0,0 @@
-from zope import interface
-
-import interfaces
-
-class Region(object):
-    interface.implements(interfaces.IRegion)
-
-    def __init__(self, name, xpath, title=None):
-        self.name = name
-        self.xpath = xpath
-        self.title =  title
-
-    def __repr__(self):
-        return "<%s %s>" % (self.__class__.__name__, repr(self.name))

Modified: z3c.layout/trunk/src/z3c/layout/tests/templates/default/index.html
===================================================================
--- z3c.layout/trunk/src/z3c/layout/tests/templates/default/index.html	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/tests/templates/default/index.html	2008-08-03 10:33:16 UTC (rev 89249)
@@ -1,11 +1,9 @@
 <html>
   <head>
     <link rel="stylesheet" href="main.css" type="text/css" media="screen" />
+    <title>Page title</title>
   </head>
   <body>
-    <p>
-      <img id="logo" src="images/logo.png" />
-    </p>
     <div id="content">
       A content region.
     </div>

Modified: z3c.layout/trunk/src/z3c/layout/tests/test_doctests.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/tests/test_doctests.py	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/tests/test_doctests.py	2008-08-03 10:33:16 UTC (rev 89249)
@@ -11,7 +11,7 @@
 import zope.component.testing
 
 def test_suite():
-    doctests = ['README.txt', 'zcml.txt', 'browser/layout.py']
+    doctests = ('README.txt', 'zcml.txt', 'utils.py', 'browser/insertion.py')
 
     import z3c.layout.tests
     path = z3c.layout.tests.__path__[0]

Added: z3c.layout/trunk/src/z3c/layout/utils.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/utils.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/utils.py	2008-08-03 10:33:16 UTC (rev 89249)
@@ -0,0 +1,76 @@
+import sys
+import os
+
+from urlparse import urlparse
+
+def dotted_name(path):
+    """Determine dotted name to path from the set of Python packages."""
+
+    syspaths = sorted(
+        sys.path, key=lambda syspath: root_length(syspath, path), reverse=True)
+
+    syspath = syspaths[0]
+    
+    path = os.path.normpath(path)
+    if not path.startswith(syspath):
+        return None
+    
+    path = path[len(syspath):]
+    
+    # convert path to dotted filename
+    if path.startswith(os.path.sep):
+        path = path[1:]
+        
+    return path
+
+def root_length(a, b):
+    return b.startswith(a) and len(a) or 0
+
+def rebase(tree, prefix):
+    """Rebase resources.
+
+    Parse through an lxml tree, adding the ``prefix`` to all tag
+    attributes that point to URL-relative layout resources:
+
+    Tag        Attribute
+    -----------------------------
+    img        src
+    
+    >>> from z3c.layout.utils import rebase
+    >>> import lxml
+
+    >>> html = '''\
+    ... <html>
+    ...   <head>
+    ...      <link href="some/url" />
+    ...   </head>
+    ...   <body>
+    ...      <img src="some/url" />
+    ...      <img src="http://host/some/url" />
+    ...   </body>
+    ... </html>'''
+
+    >>> tree = lxml.etree.fromstring(html)
+    >>> rebase(tree, '++foo++')
+    >>> print lxml.etree.tostring(tree, pretty_print=True)
+    <html>
+      <head>
+         <link href="++foo++/some/url"/>
+      </head>
+      <body>
+         <img src="++foo++/some/url"/>
+         <img src="http://host/some/url"/>
+      </body>
+    </html>
+    
+    """
+
+    for xpath, attribute in (('.//img', 'src'),
+                             ('.//head/link', 'href')):
+        for element in tree.findall('%s[@%s]' % (xpath, attribute)):
+            value = element.get(attribute)
+            protocol, host = urlparse(value)[:2]
+
+            # rebase if host is trivial
+            if not host:
+                element.set(attribute, '%s/%s' % (prefix, value))

Modified: z3c.layout/trunk/src/z3c/layout/zcml.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/zcml.py	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/zcml.py	2008-08-03 10:33:16 UTC (rev 89249)
@@ -1,77 +1,43 @@
-# -*- coding: utf-8 -*-
-#
-# File: metadirective.py
-#
+from zope.component import zcml
+from zope.app.publisher.browser import resourcemeta
 
-__author__    = """Stefan Eletzhofer <stefan.eletzhofer at inquant.de>"""
-__docformat__ = 'plaintext'
-__revision__  = "$Revision: $"
-__version__   = '$Revision: $'[11:-2]
+import interfaces
+import model
+import utils
+import md5
+import os
 
-from zope import component
-from zope import interface
-from zope import schema
-
-from zope.app.publisher.browser.resourcemeta import resourceDirectory
-from zope.configuration import fields
-from zope.publisher.interfaces.browser import IDefaultBrowserLayer
-
-from z3c.layout.regions import Region
-from z3c.layout.layout import Layout
-from z3c.layout.interfaces import ILayout
-
-class ILayoutDirective(interface.Interface):
-    """Layout."""
-
-    name = schema.TextLine(
-        title=u"Title")
-    
-    template = fields.Path(
-        title=u"Template")
-    
-    thumbnail = fields.Path(
-        title=u"Thumbnail")
-    
-    resource_directory = schema.TextLine(
-        title=u"Resource directory")
-
-class IRegionDirective(interface.Interface):
-    """Layout region."""
-
-    name = schema.TextLine(
-        title=u"title")
-    
-    xpath = schema.TextLine(
-        title=u"xpath")
-    
-    title = schema.TextLine(
-        title=u"title")
-
 class LayoutDirective(object):
-    """
-    Call order will be __init__, region, __call__
-    """
-
-    def __init__(self, _context, name, template, thumbnail, resource_directory):
+    def __init__(self, _context, name, template, regions=()):
         self._context = _context
         self.name = name
-        self.resource_directory = resource_directory
         self.template = template
-        self.thumbnail = thumbnail
-        self.regions = []
+        self.regions = set()
 
-    def region(self, _context, name, xpath, title):
-        self.regions.append(Region(name=name, xpath=xpath, title=title))
+    def region(self, _context, *args, **kwargs):
+        self.regions.add(
+            model.Region(*args, **kwargs))
 
     def __call__(self):
-        layout = Layout(
-            name = self.name,
-            resource_directory = self.resource_directory,
-            template = self.template,
-            thumbnail = self.thumbnail)
+        path, filename = os.path.split(self.template)
 
-        layout.regions = self.regions
+        # compute resource directory name
+        dotted_name = utils.dotted_name(path)
+        resource_name = md5.new(dotted_name).hexdigest()
+        resource_path = '++resource++%s' % resource_name
+        
+        layout = model.Layout(
+            self.name, self.template, resource_path, self.regions)
 
-        component.provideUtility(layout, ILayout, name=self.name)
+        # register resource directory
+        resourcemeta.resourceDirectory(
+            self._context,
+            resource_name,
+            path)
 
-# vim: set ft=python ts=4 sw=4 expandtab :
+        # register layout
+        zcml.utility(
+            self._context,
+            provides=interfaces.ILayout,
+            component=layout,
+            name=self.name)

Modified: z3c.layout/trunk/src/z3c/layout/zcml.txt
===================================================================
--- z3c.layout/trunk/src/z3c/layout/zcml.txt	2008-08-03 10:15:29 UTC (rev 89248)
+++ z3c.layout/trunk/src/z3c/layout/zcml.txt	2008-08-03 10:33:16 UTC (rev 89249)
@@ -12,28 +12,20 @@
     ... <configure xmlns="http://namespaces.zope.org/meta"
     ...            xmlns:browser="http://namespaces.zope.org/browser">
     ...
-    ...    <include package="zope.app.publisher.browser" file="meta.zcml" />
+    ...    <browser:layout
+    ...         name="testlayout"
+    ...         template="%(test_layout)s/foo.html">
     ...
-    ...    <browser:resourceDirectory
-    ...         name="test"
-    ...         directory="%(test_layout)s"
-    ...      />
-    ...
-    ...    <browser:layout name="testlayout"
-    ...         template="%(test_layout)s/foo.html"
-    ...         thumbnail="%(test_layout)s/foo.jpg"
-    ...         resource_directory="test">
-    ...
     ...      <browser:region
     ...           name="content"
+    ...           title="Content area"
     ...           xpath=".//html/body/div"
-    ...           title="The Content area"
     ...         />
     ...
     ...      <browser:region
     ...           name="sidebar"
+    ...           title="Sidebar"
     ...           xpath=".//html/body/span"
-    ...           title="A funky sidebar"
     ...         />
     ...
     ...    </browser:layout>
@@ -64,6 +56,6 @@
     
     >>> region_one, region_two = layout.regions
     >>> region_one.name, region_one.xpath, region_one.title
-    (u'content', u'.//html/body/div', u'The Content area')
+    (u'content', u'.//html/body/div', u'Content area')
     >>> region_two.name, region_two.xpath, region_two.title
-    (u'sidebar', u'.//html/body/span', u'A funky sidebar')
+    (u'sidebar', u'.//html/body/span', u'Sidebar')



More information about the Checkins mailing list