[Checkins] SVN: Sandbox/malthe/chameleon.html/ Added resource rebase functionality.
Malthe Borch
mborch at gmail.com
Thu Sep 25 21:24:27 EDT 2008
Log message for revision 91494:
Added resource rebase functionality.
Changed:
U Sandbox/malthe/chameleon.html/CHANGES.txt
U Sandbox/malthe/chameleon.html/README.txt
U Sandbox/malthe/chameleon.html/src/chameleon/html/language.py
U Sandbox/malthe/chameleon.html/src/chameleon/html/template.py
U Sandbox/malthe/chameleon.html/src/chameleon/html/template.txt
U Sandbox/malthe/chameleon.html/src/chameleon/html/tests/layouts/example.html
U Sandbox/malthe/chameleon.html/src/chameleon/html/tests/test_doctests.py
-=-
Modified: Sandbox/malthe/chameleon.html/CHANGES.txt
===================================================================
--- Sandbox/malthe/chameleon.html/CHANGES.txt 2008-09-25 21:48:15 UTC (rev 91493)
+++ Sandbox/malthe/chameleon.html/CHANGES.txt 2008-09-26 01:24:25 UTC (rev 91494)
@@ -4,5 +4,6 @@
Head
~~~~
+- Added resource rebase support. [malthe]
Modified: Sandbox/malthe/chameleon.html/README.txt
===================================================================
--- Sandbox/malthe/chameleon.html/README.txt 2008-09-25 21:48:15 UTC (rev 91493)
+++ Sandbox/malthe/chameleon.html/README.txt 2008-09-26 01:24:25 UTC (rev 91494)
@@ -2,22 +2,41 @@
========
This package implements a template compiler for dynamic HTML
-documents. In particular, it supports the dynamic element language XSS
-which is used to set up dynamic content.
+documents. In particular, it supports the XSS rule language which is
+used to associate elements with dynamic content.
-XSS language
-------------
+XSS rule language
+-----------------
-The XSS language uses a CSS-compliant syntax to let you match HTML
+The XSS rule language uses a CSS-compliant syntax to let you match HTML
elements using CSS selectors and set up dynamic content
definitions.
-For example:
+To associate a template with a rule file, use the <link> tag:
+ <link rel="xss" type="text/xss" src="rules.xss" />
+
+XSS files contain rules like the following:
+
html > head > title {
name: document-heading;
structure: true;
attributes: document-attributes;
}
+This rule will associate the <title> tag with the dynamic content
+identifier "document-heading", escape the inserted content and apply
+the dynamic attributes bound to the "document-attributes" identifier.
+See the file ``template.txt`` within the package for documentation on
+how to render templates and provide dynamic content and attributes.
+
+
+Resource rebase functionality
+-----------------------------
+
+If a resource location adapter is available (see
+``chameleon.html.interfaces.IResourceLocation``), references resources
+(e.g. images, stylesheets, javascripts) will be "rebased" to the URL
+returned by the component.
+
Modified: Sandbox/malthe/chameleon.html/src/chameleon/html/language.py
===================================================================
--- Sandbox/malthe/chameleon.html/src/chameleon/html/language.py 2008-09-25 21:48:15 UTC (rev 91493)
+++ Sandbox/malthe/chameleon.html/src/chameleon/html/language.py 2008-09-26 01:24:25 UTC (rev 91494)
@@ -1,18 +1,24 @@
import cgi
import os
+import re
+import lxml.cssselect
+from zope import component
+
from chameleon.core import translation
from chameleon.core import config
from chameleon.core import etree
from chameleon.core import types
from chameleon.core import utils
+from xss import parse_xss
+from interfaces import IResourceLocation
+
true_values = 'true', '1', 'yes'
-from xss import parse_xss
+re_stylesheet_import = re.compile(
+ r'^(\s*@import\surl*\()([^\)]+)(\);?\s*)$')
-import lxml.cssselect
-
def merge_dicts(dict1, dict2):
if dict2 is None:
return dict1
@@ -38,12 +44,17 @@
def composite_attr_dict(attrib, *dicts):
return reduce(merge_dicts, (dict(attrib),) + dicts)
+def rebase(context, request, path):
+ return component.getMultiAdapter(
+ (context, request, path), IResourceLocation)
+
class Element(translation.Element):
"""The XSS template language base element."""
class node(translation.Node):
define_symbol = '_define'
composite_attr_symbol = '_composite_attr'
+ rebase_symbol = '_rebase'
@property
def omit(self):
@@ -65,6 +76,17 @@
(self.composite_attr_symbol, attrib, attributes))
value.symbol_mapping[self.composite_attr_symbol] = composite_attr_dict
return value
+
+ @property
+ def dynamic_attributes(self):
+ for scope in self.stream.scope:
+ if 'context' in scope and 'request' in scope:
+ if self.element.xss_rebase is not None:
+ name = self.element.attrib[self.element.xss_rebase]
+ value = types.value("%s(context, request, %s)" % (
+ self.rebase_symbol, repr(name)))
+ value.symbol_mapping[self.rebase_symbol] = rebase
+ return [(types.declaration((self.element.xss_rebase,)), value)]
@property
def define(self):
@@ -88,6 +110,11 @@
return expression
@property
+ def static_attributes(self):
+ return utils.get_attributes_from_namespace(
+ self.element, config.XHTML_NS)
+
+ @property
def skip(self):
if self.element.xss_content is not None:
return types.value(self.define_symbol)
@@ -106,21 +133,60 @@
xss_attributes = utils.attribute(
'{http://namespaces.repoze.org/xss}attributes')
+ xss_rebase = utils.attribute(
+ '{http://namespaces.repoze.org/xss}rebase')
+
+class MetaElement(translation.MetaElement):
+ class node(translation.Node):
+ rebase_symbol = '_rebase'
+
+ @property
+ def omit(self):
+ if self.element.meta_omit is not None:
+ return self.element.meta_omit or True
+ if self.element.meta_replace:
+ return True
+
+ @property
+ def content(self):
+ if self.element.xss_rebase and self.element.text:
+ for scope in self.stream.scope:
+ if 'context' in scope and 'request' in scope:
+ m = re_stylesheet_import.match(self.element.text)
+ assert m is not None
+ before = m.group(1)
+ path = m.group(2)
+ after = m.group(3)
+ value = types.value(
+ "'<!-- %s' + %s(context, request, %s) + '%s -->'" % (
+ before, self.rebase_symbol, repr(path), after))
+ value.symbol_mapping[self.rebase_symbol] = rebase
+ break
+ else:
+ value = types.value("'<!-- %s -->'" % self.element.text)
+ return value
+ return self.element.meta_replace
+
+ node = property(node)
+
+ xss_rebase = utils.attribute(
+ '{http://namespaces.repoze.org/xss}rebase')
+
class XSSTemplateParser(etree.Parser):
"""XSS template parser."""
element_mapping = {
config.XHTML_NS: {None: Element},
- config.META_NS: {None: translation.MetaElement}}
+ config.META_NS: {None: MetaElement}}
-class DynamicHTMLParser(XSSTemplateParser):
+class DynamicHTMLParser(XSSTemplateParser):
def __init__(self, filename):
self.path = os.path.dirname(filename)
def parse(self, body):
root, doctype = super(DynamicHTMLParser, self).parse(body)
- # locate XSS links
+ # process dynamic rules
links = root.xpath(
'.//xmlns:link[@rel="xss"]', namespaces={'xmlns': config.XHTML_NS})
for link in links:
@@ -135,8 +201,8 @@
raise ValueError(
"File not found: %s" % repr(href))
+ # parse and apply rules
rules = parse_xss(filename)
-
for rule in rules:
selector = lxml.cssselect.CSSSelector(rule.selector)
for element in root.xpath(
@@ -155,5 +221,31 @@
rule.attributes
link.getparent().remove(link)
-
+
+ # prepare reference rebase logic
+ elements = root.xpath(
+ './/xmlns:link[@href] | .//xmlns:img[@src] | .//xmlns:script[@src]',
+ namespaces={'xmlns': config.XHTML_NS})
+ for element in elements:
+ href = element.attrib.get('href')
+ if href is not None:
+ element.attrib['{http://namespaces.repoze.org/xss}rebase'] = 'href'
+ src = element.attrib.get('src')
+ if src is not None:
+ element.attrib['{http://namespaces.repoze.org/xss}rebase'] = 'src'
+ elements = root.xpath(
+ './/xmlns:style', namespaces={'xmlns': config.XHTML_NS})
+ for element in elements:
+ for comment in element:
+ text = comment.text
+ m = re_stylesheet_import.match(text)
+ if m is not None:
+ index = element.index(comment)
+ element.remove(comment)
+ comment = element.makeelement(
+ utils.meta_attr('comment'))
+ element.insert(index, comment)
+ comment.attrib['{http://namespaces.repoze.org/xss}rebase'] = 'true'
+ comment.text = text
+
return root, doctype
Modified: Sandbox/malthe/chameleon.html/src/chameleon/html/template.py
===================================================================
--- Sandbox/malthe/chameleon.html/src/chameleon/html/template.py 2008-09-25 21:48:15 UTC (rev 91493)
+++ Sandbox/malthe/chameleon.html/src/chameleon/html/template.py 2008-09-26 01:24:25 UTC (rev 91494)
@@ -7,3 +7,7 @@
parser = language.DynamicHTMLParser(filename)
super(DynamicHTMLFile, self).__init__(
filename, parser, **kwargs)
+
+ def render(self, content={}, attributes={}, **kwargs):
+ return template.TemplateFile.render(
+ self, content=content, attributes=attributes, **kwargs)
Modified: Sandbox/malthe/chameleon.html/src/chameleon/html/template.txt
===================================================================
--- Sandbox/malthe/chameleon.html/src/chameleon/html/template.txt 2008-09-25 21:48:15 UTC (rev 91493)
+++ Sandbox/malthe/chameleon.html/src/chameleon/html/template.txt 2008-09-26 01:24:25 UTC (rev 91494)
@@ -1,23 +1,88 @@
Dynamic HTML template
=====================
+For this demonstration, an example template has been prepared.
+
+ >>> filename = os.path.join(path, 'layouts', 'example.html')
+
+We instantiate the dynamic HTML template like a normal Chameleon
+template, passing the filename as an argument.
+
>>> from chameleon.html.template import DynamicHTMLFile
+ >>> template = DynamicHTMLFile(filename)
- >>> template = DynamicHTMLFile(os.path.join(path, 'layouts', 'example.html'))
- >>> print template(
- ... content={'title': u"Document title",
- ... 'content': u"Document content"},
- ... attributes={'meta-generator': {'content': u"Chameleon"},
- ... 'meta-description': {'content': u"Document description"},
- ... 'meta-keywords': {'content': u"Keywords"}})
+Dynamic content and attributes are passed in as keyword arguments to
+the template.
+
+The ``content`` parameter should be passed a dict with keys
+corresponding to dynamic content names and values being unicode
+strings.
+
+ >>> content = {'title': u"Document title",
+ ... 'content': u"Document content"}
+
+The ``attributes`` parameters is a double dictionary where the first
+level of values are element attribute dictionaries, e.g. the second
+dictionary is a mapping of element attribute names and values.
+
+ >>> attributes={'meta-generator': {'content': u"Document generator"},
+ ... 'meta-description': {'content': u"Document description"},
+ ... 'meta-keywords': {'content': u"Document keywords"}}
+
+Passing these arguments by keyword, we can verify the rendered output.
+
+ >>> print template(content=content, attributes=attributes)
<html>
<head>
<title>Document title</title>
- <meta content="Chameleon" name="generator" />
+ <!-- meta -->
+ <meta content="Document generator" name="generator" />
<meta content="Document description" name="description" />
- <meta content="Keywords" name="keywords" />
+ <meta content="Document keywords" name="keywords" />
+ <style type="text/css"><!-- @import url(example.css); --></style>
+ <link href="example.kss" rel="kukit" type="text/kss" />
+ <script src="example.js" type="text/javascript">
+ </script>
</head>
<body>
- <div>Document content</div>
+ <div id="content">Document content</div>
+ <img src="example.png" />
</body>
</html>
+
+If we pass in ``context`` and ``request`` arguments, resources
+referenced in the template are rebased if a resource location
+component is available.
+
+ >>> def resource_location(context, request, path):
+ ... return 'http://host/%s' % path
+
+ >>> from chameleon.html.interfaces import IResourceLocation
+ >>> component.provideAdapter(
+ ... resource_location,
+ ... (interface.Interface, interface.Interface, interface.Interface),
+ ... IResourceLocation)
+
+Notice in the following that URLs of resource is rebased as defined in
+the adapter.
+
+ >>> print template(context=object(), request=object())
+ <html>
+ <head>
+ <title>Example layout</title>
+ <!-- meta -->
+ <meta content="Emacs" name="generator" />
+ <meta content="This is an example layout." name="description" />
+ <meta content="example, layout, xss." name="keywords" />
+ <style type="text/css"><!-- @import url(http://host/example.css); --></style>
+ <link href="http://host/example.kss" rel="kukit" type="text/kss" />
+ <script src="http://host/example.js" type="text/javascript">
+ </script>
+ </head>
+ <body>
+ <div id="content">
+ This is the content area.
+ </div>
+ <img src="http://host/example.png" />
+ </body>
+ </html>
Modified: Sandbox/malthe/chameleon.html/src/chameleon/html/tests/layouts/example.html
===================================================================
--- Sandbox/malthe/chameleon.html/src/chameleon/html/tests/layouts/example.html 2008-09-25 21:48:15 UTC (rev 91493)
+++ Sandbox/malthe/chameleon.html/src/chameleon/html/tests/layouts/example.html 2008-09-26 01:24:25 UTC (rev 91494)
@@ -1,14 +1,23 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Example layout</title>
- <link rel="xss" type="text/xss" href="example.xss" />
+
+ <!-- meta -->
<meta name="generator" content="Emacs" />
<meta name="description" content="This is an example layout." />
<meta name="keywords" content="example, layout, xss." />
+
+ <link rel="xss" type="text/xss" href="example.xss" />
+
+ <style type="text/css"><!-- @import url(example.css); --></style>
+ <link rel="kukit" type="text/kss" href="example.kss" />
+ <script type="text/javascript" src="example.js">
+ </script>
</head>
<body>
<div id="content">
This is the content area.
</div>
+ <img src="example.png" />
</body>
</html>
Modified: Sandbox/malthe/chameleon.html/src/chameleon/html/tests/test_doctests.py
===================================================================
--- Sandbox/malthe/chameleon.html/src/chameleon/html/tests/test_doctests.py 2008-09-25 21:48:15 UTC (rev 91493)
+++ Sandbox/malthe/chameleon.html/src/chameleon/html/tests/test_doctests.py 2008-09-26 01:24:25 UTC (rev 91494)
@@ -1,6 +1,8 @@
import unittest
import os
+import zope.interface
+import zope.component
import zope.testing
import zope.component.testing
import zope.configuration.xmlconfig
@@ -26,7 +28,12 @@
filesuites = 'language.txt', 'template.txt'
testsuites = ()
- globs = dict(render_xss=render_xss, os=os, path=chameleon.html.tests.__path__[0])
+ globs = dict(
+ render_xss=render_xss,
+ interface=zope.interface,
+ component=zope.component,
+ os=os,
+ path=chameleon.html.tests.__path__[0])
chameleon.core.config.DISK_CACHE = False
More information about the Checkins
mailing list