[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