[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