[Checkins] SVN: z3c.layout/ Initial import.

Malthe Borch mborch at gmail.com
Wed Jul 30 11:54:26 EDT 2008


Log message for revision 89023:
  Initial import.

Changed:
  A   z3c.layout/
  A   z3c.layout/trunk/
  A   z3c.layout/trunk/.installed.cfg
  A   z3c.layout/trunk/README.txt
  A   z3c.layout/trunk/bootstrap.py
  A   z3c.layout/trunk/buildout.cfg
  A   z3c.layout/trunk/setup.py
  A   z3c.layout/trunk/src/
  A   z3c.layout/trunk/src/z3c/
  A   z3c.layout/trunk/src/z3c/__init__.py
  A   z3c.layout/trunk/src/z3c/layout/
  A   z3c.layout/trunk/src/z3c/layout/README.txt
  A   z3c.layout/trunk/src/z3c/layout/__init__.py
  A   z3c.layout/trunk/src/z3c/layout/browser/
  A   z3c.layout/trunk/src/z3c/layout/browser/__init__.py
  A   z3c.layout/trunk/src/z3c/layout/browser/configure.zcml
  A   z3c.layout/trunk/src/z3c/layout/browser/interfaces.py
  A   z3c.layout/trunk/src/z3c/layout/browser/layout.py
  A   z3c.layout/trunk/src/z3c/layout/browser/providers.py
  A   z3c.layout/trunk/src/z3c/layout/configure.zcml
  A   z3c.layout/trunk/src/z3c/layout/interfaces.py
  A   z3c.layout/trunk/src/z3c/layout/layout.py
  A   z3c.layout/trunk/src/z3c/layout/meta.zcml
  A   z3c.layout/trunk/src/z3c/layout/regions.py
  A   z3c.layout/trunk/src/z3c/layout/tests/
  A   z3c.layout/trunk/src/z3c/layout/tests/__init__.py
  A   z3c.layout/trunk/src/z3c/layout/tests/templates/
  A   z3c.layout/trunk/src/z3c/layout/tests/templates/default/
  A   z3c.layout/trunk/src/z3c/layout/tests/templates/default/index.html
  A   z3c.layout/trunk/src/z3c/layout/tests/templates/default/index.jpg
  A   z3c.layout/trunk/src/z3c/layout/tests/templates/default/main.css
  A   z3c.layout/trunk/src/z3c/layout/tests/test_doctests.py
  A   z3c.layout/trunk/src/z3c/layout/zcml.py
  A   z3c.layout/trunk/src/z3c/layout/zcml.txt

-=-
Added: z3c.layout/trunk/.installed.cfg
===================================================================
--- z3c.layout/trunk/.installed.cfg	                        (rev 0)
+++ z3c.layout/trunk/.installed.cfg	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,19 @@
+[buildout]
+installed_develop_eggs = /Users/mborch/co/z3c.layout/develop-eggs/z3c.layout.egg-link
+parts = test
+
+[test]
+__buildout_installed__ = /Users/mborch/co/z3c.layout/parts/test
+	/Users/mborch/co/z3c.layout/bin/test
+__buildout_signature__ = zc.recipe.testrunner-1.0.0-py2.5.egg zc.recipe.egg-1.1.0-py2.5.egg setuptools-0.6c8-py2.5.egg zope.testing-3.6.0-py2.5.egg zc.buildout-1.1.0-py2.5.egg zc.buildout-1.1.0-py2.5.egg zope.interface-3.4.1-py2.5-macosx-10.3-i386.egg
+_b = /Users/mborch/co/z3c.layout/bin
+_d = /Users/mborch/co/z3c.layout/develop-eggs
+_e = /Users/mborch/Development/sources/buildout
+bin-directory = /Users/mborch/co/z3c.layout/bin
+develop-eggs-directory = /Users/mborch/co/z3c.layout/develop-eggs
+eggs = %(__buildout_space_n__)sz3c.layout
+eggs-directory = /Users/mborch/Development/sources/buildout
+executable = /opt/local/bin/python2.5
+location = /Users/mborch/co/z3c.layout/parts/test
+recipe = zc.recipe.testrunner
+script = /Users/mborch/co/z3c.layout/bin/test

Added: z3c.layout/trunk/README.txt
===================================================================
--- z3c.layout/trunk/README.txt	                        (rev 0)
+++ z3c.layout/trunk/README.txt	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,24 @@
+HTML layout engine
+==================
+
+This package implements a page rendering model where a layout is based
+on an existing HTML document and definitions of dynamic regions that
+point to elements in the document tree.
+
+A layout is rendered in the context of some object and it's left to
+content providers to fill in dynamic data to the regions (see
+``zope.contentprovider``).
+
+Images, CSS and JS-resources that are referenced by the HTML document
+are included automatically by declaring them as Zope browser
+resources.
+
+Benefits:
+
+* No template language required
+* Integrates directly with creative workflow
+* Provides flexible extension points
+
+Additionally, a set of viewlet managers are included to easily insert
+viewlets into common HTML document slots.
+

Added: z3c.layout/trunk/bootstrap.py
===================================================================
--- z3c.layout/trunk/bootstrap.py	                        (rev 0)
+++ z3c.layout/trunk/bootstrap.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 77225 2007-06-29 09:20:13Z dobe $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+try:
+    import pkg_resources
+except ImportError:
+    ez = {}
+    exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                         ).read() in ez
+    ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+    import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)
+

Added: z3c.layout/trunk/buildout.cfg
===================================================================
--- z3c.layout/trunk/buildout.cfg	                        (rev 0)
+++ z3c.layout/trunk/buildout.cfg	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,8 @@
+[buildout]
+develop = .
+parts = test
+
+[test]
+recipe = zc.recipe.testrunner
+eggs =
+   z3c.layout [test]
\ No newline at end of file

Added: z3c.layout/trunk/setup.py
===================================================================
--- z3c.layout/trunk/setup.py	                        (rev 0)
+++ z3c.layout/trunk/setup.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+import os
+from setuptools import setup, find_packages, Extension
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+setup(name='z3c.layout',
+      version = '0.1',
+      description='HTML layout engine',
+      long_description=read('README.txt')+read('src/z3c/layout/README.txt'),
+      keywords = "zope3 layout HTML",
+      classifiers = [
+          'Development Status :: 4 - Beta',
+          'Environment :: Web Environment',
+          'Intended Audience :: Developers',
+          'License :: OSI Approved :: Zope Public License',
+          'Programming Language :: Python',
+          'Natural Language :: English',
+          'Operating System :: OS Independent',
+          'Topic :: Internet :: WWW/HTTP',
+          'Framework :: Zope3'],
+      license='ZPL 2.1',
+      packages=find_packages('src'),
+      package_dir = {'': 'src'},
+      namespace_packages=['z3c', ],
+      extras_require = dict(
+        test = [
+            'zope.app.testing',
+            ],
+        ),
+      install_requires=['setuptools',
+                        'zope.interface',
+                        'zope.schema',
+                        'zope.component',
+                        'zope.app.publisher',
+                        'zope.contentprovider',
+                        'zope.viewlet',
+                        'lxml>=2.0',
+                        ],
+      include_package_data = True,
+      zip_safe = False,
+      )

Added: z3c.layout/trunk/src/z3c/__init__.py
===================================================================
--- z3c.layout/trunk/src/z3c/__init__.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/__init__.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,6 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)

Added: z3c.layout/trunk/src/z3c/layout/README.txt
===================================================================
--- z3c.layout/trunk/src/z3c/layout/README.txt	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/README.txt	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,189 @@
+Walk-through
+============
+
+A layout is essentially an HTML-document with zero or more region
+definitions.
+
+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.
+
+    >>> from z3c.layout.layout import Layout
+    >>> layout = Layout(
+    ...     "Testlayout",
+    ...     "test",
+    ...     "%s/templates/default/index.html" % test_path,
+    ...     "index.jpg")
+
+We need register this layout as a utility to make it available for the
+rendering machinery.
+
+    >>> from z3c.layout.interfaces import ILayout
+    >>> component.provideUtility(layout, ILayout, name="testlayout")
+
+Regions
+-------
+
+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.
+
+    >>> from z3c.layout.regions import Region
+
+We'll define two regions.
+
+    >>> logo = Region("logo", ".//p", "The Logo Region")
+    >>> content = Region("content", ".//div", "The Content Region")
+
+To add them to the layout we simply append them to the list of
+regions.
+
+    >>> layout.regions.append(logo)
+    >>> layout.regions.append(content)
+
+Region content providers
+------------------------
+
+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.
+    
+    >>> assignment = LayoutAssignment('testlayout', {
+    ...     'logo': 'logo_provider',
+    ...     'content': 'content_provider'})
+
+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(
+    ...     lambda page: assignment, (MockPage,), ILayoutAssignment)
+    
+We proceed by setting up content providers that render the
+regions. This follows the standard content provider interface.
+
+    >>> from zope.contentprovider.interfaces import IContentProvider
+    
+    >>> class MockContentProvider(object):
+    ...     interface.implements(IContentProvider)
+    ...     
+    ...     __name__ = u""
+    ...
+    ...     def __init__(self, *args):
+    ...         pass
+    ...
+    ...     def update(self):
+    ...         pass
+    ...
+    ...     def render(self):
+    ...         return self.__name__
+    ...
+    ...     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")
+
+    >>> component.provideAdapter(
+    ...     MockContentProvider, (IRegion, IBrowserRequest, IBrowserView),
+    ...     name="content_provider")
+
+Rendering the layout
+--------------------
+    
+The layout is rendered by a specialized view.
+    
+    >>> from zope.publisher.browser import TestRequest
+    >>> page = MockPage()
+    >>> request = TestRequest()
+
+    >>> from z3c.layout.browser.layout import LayoutView
+    >>> view = LayoutView(page, 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'>)]
+
+    >>> assignment.provider_map['logo'] = 'non_existing_logo_provider'
+
+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>
+    <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>

Added: z3c.layout/trunk/src/z3c/layout/__init__.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/__init__.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/__init__.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1 @@
+#

Added: z3c.layout/trunk/src/z3c/layout/browser/__init__.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/browser/__init__.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/browser/__init__.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1 @@
+#

Added: z3c.layout/trunk/src/z3c/layout/browser/configure.zcml
===================================================================
--- z3c.layout/trunk/src/z3c/layout/browser/configure.zcml	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/browser/configure.zcml	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,24 @@
+<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/interfaces.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/browser/interfaces.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/browser/interfaces.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,6 @@
+from zope.contentprovider.interfaces import IContentProvider
+
+class ITreeContentProvider(IContentProvider):
+    def insert(tree):
+        """Render and insert into the element tree."""
+

Added: z3c.layout/trunk/src/z3c/layout/browser/layout.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/browser/layout.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/browser/layout.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,146 @@
+import lxml.etree
+import lxml.html
+
+from zope import interface
+from zope import component
+
+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 urlparse import urlparse
+
+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)
+        
+    def __call__(self):
+        tree = lxml.html.parse(self.layout.template)        
+        rebase(tree, '++resource++%s' % self.layout.resource_directory)
+
+        # lookup content provider components
+        region_content_providers = self._get_region_content_providers()
+        tree_content_providers = self._get_tree_content_providers()
+        
+        # update content providers
+        for region, provider in region_content_providers:
+            provider.update()
+
+        for provider in tree_content_providers:
+            provider.update()
+
+        # render and insert region content providers
+        for region, provider in region_content_providers:
+            html = provider.render()
+            subtree = tree.xpath(region.xpath)
+
+            if len(subtree) != 1:
+                continue
+
+            subtree = subtree[0]
+
+            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."""
+        
+        results = []
+        provider_map = self.assignment.provider_map
+        
+        for region in self.layout.regions:
+            name = provider_map.get(region.name, None)
+            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))
+
+        return results
+
+    def _get_tree_content_providers(self):
+        """Lookup tree content providers.
+
+        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))

Added: z3c.layout/trunk/src/z3c/layout/browser/providers.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/browser/providers.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/browser/providers.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,66 @@
+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))

Added: z3c.layout/trunk/src/z3c/layout/configure.zcml
===================================================================
--- z3c.layout/trunk/src/z3c/layout/configure.zcml	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/configure.zcml	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,5 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <include package=".browser" />
+  
+</configure>

Added: z3c.layout/trunk/src/z3c/layout/interfaces.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/interfaces.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/interfaces.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,38 @@
+from zope.interface import Interface, Attribute
+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."""
+
+    name = schema.TextLine(
+        title=u"Name of region.",
+        required=True)
+    
+    title = schema.TextLine(
+        title=u"Title",
+        required=False)
+
+    xpath = schema.TextLine(
+        title=u"Xpath expression for this region",
+        required=True)

Added: z3c.layout/trunk/src/z3c/layout/layout.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/layout.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/layout.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,18 @@
+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 = []

Added: z3c.layout/trunk/src/z3c/layout/meta.zcml
===================================================================
--- z3c.layout/trunk/src/z3c/layout/meta.zcml	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/meta.zcml	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,13 @@
+<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>
+  </directives>
+</configure>
+

Added: z3c.layout/trunk/src/z3c/layout/regions.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/regions.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/regions.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,14 @@
+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))

Added: z3c.layout/trunk/src/z3c/layout/tests/__init__.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/tests/__init__.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/tests/__init__.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1 @@
+#

Added: z3c.layout/trunk/src/z3c/layout/tests/templates/default/index.html
===================================================================
--- z3c.layout/trunk/src/z3c/layout/tests/templates/default/index.html	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/tests/templates/default/index.html	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,13 @@
+<html>
+  <head>
+    <link rel="stylesheet" href="main.css" type="text/css" media="screen" />
+  </head>
+  <body>
+    <p>
+      <img id="logo" src="images/logo.png" />
+    </p>
+    <div id="content">
+      A content region.
+    </div>
+  </body>
+</html>

Added: z3c.layout/trunk/src/z3c/layout/tests/templates/default/index.jpg
===================================================================

Added: z3c.layout/trunk/src/z3c/layout/tests/templates/default/main.css
===================================================================
--- z3c.layout/trunk/src/z3c/layout/tests/templates/default/main.css	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/tests/templates/default/main.css	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,3 @@
+body {
+    background-image: url(images/background.jpg);
+}
\ No newline at end of file

Added: z3c.layout/trunk/src/z3c/layout/tests/test_doctests.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/tests/test_doctests.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/tests/test_doctests.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,34 @@
+from zope import interface
+from zope import component
+
+import zope.testing
+import unittest
+
+OPTIONFLAGS = (zope.testing.doctest.REPORT_ONLY_FIRST_FAILURE |
+               zope.testing.doctest.ELLIPSIS |
+               zope.testing.doctest.NORMALIZE_WHITESPACE)
+
+import zope.component.testing
+
+def test_suite():
+    doctests = ['README.txt', 'zcml.txt', 'browser/layout.py']
+
+    import z3c.layout.tests
+    path = z3c.layout.tests.__path__[0]
+
+    globs = dict(
+        interface=interface,
+        component=component,
+        test_path=path)
+    
+    return unittest.TestSuite((
+        zope.testing.doctest.DocFileSuite(doctest,
+                                          optionflags=OPTIONFLAGS,
+                                          setUp=zope.component.testing.setUp,
+                                          tearDown=zope.component.testing.tearDown,
+                                          globs=globs,
+                                          package="z3c.layout") for doctest in doctests
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: z3c.layout/trunk/src/z3c/layout/zcml.py
===================================================================
--- z3c.layout/trunk/src/z3c/layout/zcml.py	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/zcml.py	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+#
+# File: metadirective.py
+#
+
+__author__    = """Stefan Eletzhofer <stefan.eletzhofer at inquant.de>"""
+__docformat__ = 'plaintext'
+__revision__  = "$Revision: $"
+__version__   = '$Revision: $'[11:-2]
+
+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):
+        self._context = _context
+        self.name = name
+        self.resource_directory = resource_directory
+        self.template = template
+        self.thumbnail = thumbnail
+        self.regions = []
+
+    def region(self, _context, name, xpath, title):
+        self.regions.append(Region(name=name, xpath=xpath, title=title))
+
+    def __call__(self):
+        layout = Layout(
+            name = self.name,
+            resource_directory = self.resource_directory,
+            template = self.template,
+            thumbnail = self.thumbnail)
+
+        layout.regions = self.regions
+
+        component.provideUtility(layout, ILayout, name=self.name)
+
+# vim: set ft=python ts=4 sw=4 expandtab :

Added: z3c.layout/trunk/src/z3c/layout/zcml.txt
===================================================================
--- z3c.layout/trunk/src/z3c/layout/zcml.txt	                        (rev 0)
+++ z3c.layout/trunk/src/z3c/layout/zcml.txt	2008-07-30 15:54:26 UTC (rev 89023)
@@ -0,0 +1,69 @@
+ZCML Directives
+===============
+
+Layout directive
+----------------
+
+We can declare layouts using a ZCML directive.
+
+    >>> test_layout = "%s/templates/default" % test_path
+    
+    >>> zcml="""
+    ... <configure xmlns="http://namespaces.zope.org/meta"
+    ...            xmlns:browser="http://namespaces.zope.org/browser">
+    ...
+    ...    <include package="zope.app.publisher.browser" file="meta.zcml" />
+    ...
+    ...    <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"
+    ...           xpath=".//html/body/div"
+    ...           title="The Content area"
+    ...         />
+    ...
+    ...      <browser:region
+    ...           name="sidebar"
+    ...           xpath=".//html/body/span"
+    ...           title="A funky sidebar"
+    ...         />
+    ...
+    ...    </browser:layout>
+    ... </configure>
+    ... """ % locals()
+
+Load meta configuration.
+    
+    >>> from zope.configuration.xmlconfig import xmlconfig, XMLConfig
+
+    >>> meta = "%s/../meta.zcml" % test_path
+    >>> import z3c.layout
+    >>> XMLConfig(meta, z3c.layout)()
+
+Load layout configuration.    
+
+    >>> from StringIO import StringIO
+    >>> xmlconfig(StringIO(zcml))
+
+Verify that the layout has been registered as a component and that
+it's been correctly populated with region definitions.
+
+    >>> from z3c.layout.interfaces import ILayout
+    >>> component.queryUtility(ILayout, name='testlayout') is not None
+    True
+
+    >>> layout = component.queryUtility(ILayout, name='testlayout')
+    
+    >>> 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')
+    >>> region_two.name, region_two.xpath, region_two.title
+    (u'sidebar', u'.//html/body/span', u'A funky sidebar')



More information about the Checkins mailing list