[Checkins] SVN: z3c.rml/trunk/src/z3c/rml/ Change strategy of parsing the XML and processing it to create the PDF. It

Stephan Richter srichter at cosmos.phy.tufts.edu
Tue Mar 27 23:02:23 EDT 2007


Log message for revision 73805:
  Change strategy of parsing the XML and processing it to create the PDF. It 
  is all interface and field based, which means that soon I will be able to 
  generate the reference documentation and DTD from the interfaces.
  
  

Changed:
  D   z3c.rml/trunk/src/z3c/rml/attr.py
  A   z3c.rml/trunk/src/z3c/rml/attrng.py
  U   z3c.rml/trunk/src/z3c/rml/canvas.py
  U   z3c.rml/trunk/src/z3c/rml/chart.py
  A   z3c.rml/trunk/src/z3c/rml/directive.py
  U   z3c.rml/trunk/src/z3c/rml/document.py
  D   z3c.rml/trunk/src/z3c/rml/element.py
  U   z3c.rml/trunk/src/z3c/rml/flowable.py
  U   z3c.rml/trunk/src/z3c/rml/form.py
  U   z3c.rml/trunk/src/z3c/rml/interfaces.py
  A   z3c.rml/trunk/src/z3c/rml/occurence.py
  U   z3c.rml/trunk/src/z3c/rml/page.py
  U   z3c.rml/trunk/src/z3c/rml/platypus.py
  U   z3c.rml/trunk/src/z3c/rml/special.py
  U   z3c.rml/trunk/src/z3c/rml/stylesheet.py
  U   z3c.rml/trunk/src/z3c/rml/template.py
  A   z3c.rml/trunk/src/z3c/rml/tests/input/tag-color.rml
  U   z3c.rml/trunk/src/z3c/rml/tests/input/tag-image-1.rml
  U   z3c.rml/trunk/src/z3c/rml/tests/input/tag-image.rml
  A   z3c.rml/trunk/src/z3c/rml/tests/input/tag-path.rml
  A   z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerCidFont.rml
  A   z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerTTFont.rml
  A   z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerType1Face.rml
  A   z3c.rml/trunk/src/z3c/rml/tests/input/tag-textAnnotation.rml
  U   z3c.rml/trunk/src/z3c/rml/tests/test_rml.py

-=-
Deleted: z3c.rml/trunk/src/z3c/rml/attr.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/attr.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/attr.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -1,400 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2007 Zope Foundation 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.
-#
-##############################################################################
-"""RML-specific XML tools
-
-$Id$
-"""
-__docformat__ = "reStructuredText"
-import cStringIO
-import os
-import re
-import reportlab
-import reportlab.lib.colors
-import reportlab.lib.styles
-import reportlab.lib.units
-import reportlab.lib.utils
-import reportlab.lib.pagesizes
-import reportlab.graphics.widgets.markers
-import urllib
-from lxml import etree
-from xml.sax import saxutils
-
-from z3c.rml import interfaces
-
-DEFAULT = object()
-
-
-def getManager(context, interface):
-    while (not interface.providedBy(context) and context is not None):
-        context = context.parent
-    if context is None:
-        raise ValueError(
-            'Manager for %s could not be found.' %interface.getName())
-    return context
-
-
-class Attribute(object):
-
-    def __init__(self, name=None, default=DEFAULT):
-        self.name = name
-        self.default = default
-
-    def convert(self, value, context=None):
-        return value
-
-    def get(self, element, default=DEFAULT, context=None):
-        value = element.get(self.name, DEFAULT)
-        if value is DEFAULT or value is default:
-            if default is DEFAULT:
-                return self.default
-            return default
-        return self.convert(value, context)
-
-class Combination(Attribute):
-
-    def __init__(self, name=None, valueTypes=(), default=DEFAULT):
-        super(Combination, self).__init__(name, default)
-        self.valueTypes = valueTypes
-
-    def convert(self, value, context=None):
-        for valueType in self.valueTypes:
-            try:
-                return valueType.convert(value, context)
-            except ValueError:
-                pass
-        raise ValueError(value)
-
-class Text(Attribute):
-
-    def convert(self, value, context=None):
-        return unicode(value)
-
-
-class Int(Attribute):
-
-    def convert(self, value, context=None):
-        return int(value)
-
-
-class Float(Attribute):
-
-    def convert(self, value, context=None):
-        return float(value)
-
-
-class StringOrInt(Attribute):
-
-    def convert(self, value, context=None):
-        try:
-            return int(value)
-        except ValueError:
-            return str(value)
-
-
-class Sequence(Attribute):
-
-    splitre = re.compile('[ \t\n,;]*')
-    minLength = None
-    maxLength = None
-
-    def __init__(self, name=None, valueType=None, default=DEFAULT,
-                 splitre=None, minLength=None, maxLength=None, length=None):
-        super(Sequence, self).__init__(name, default)
-        self.valueType = valueType
-        if minLength is not None:
-            self.minLength = minLength
-        if maxLength is not None:
-            self.maxLength = maxLength
-        if length is not None:
-            self.minLength = self.maxLength = length
-        if splitre is not None:
-            self.splitre = splitre
-
-    def convert(self, value, context=None):
-        if value.startswith('(') and value.endswith(')'):
-            value = value[1:-1]
-        value = value.strip()
-        values = self.splitre.split(value)
-        result = [self.valueType.convert(value.strip(), context)
-                  for value in values]
-        if ((self.minLength is not None and len(result) < self.minLength) and
-            (self.maxLength is not None and len(result) > self.maxLength)):
-            raise ValueError(
-                'Length of sequence must be at least %s and at most %i' % (
-                self.minLength, self.maxLength))
-        return result
-
-
-class BaseChoice(Attribute):
-    choices = {}
-
-    def convert(self, value, context=None):
-        value = value.lower()
-        if value in self.choices:
-            return self.choices[value]
-        raise ValueError(
-            '%r not a valid value for attribute "%s"' % (value, self.name))
-
-
-class Choice(BaseChoice):
-
-    def __init__(self, name=None, choices=None, default=DEFAULT):
-        super(Choice, self).__init__(name, default)
-        if isinstance(choices, (tuple, list)):
-            choices = dict([(val.lower(), val) for val in choices])
-        self.choices = choices
-
-
-class Bool(BaseChoice):
-    choices = {'true': True, 'false': False,
-               'yes': True, 'no': False,
-               '1': True, '0': False,
-               }
-
-
-class DefaultBool(Bool):
-    choices = Bool.choices.copy()
-    choices.update({'default': None})
-
-
-class Measurement(Attribute):
-
-    def __init__(self, name=None, default=DEFAULT,
-                 allowPercentage=False, allowStar=False):
-        super(Measurement, self).__init__(name, default)
-        self.allowPercentage = allowPercentage
-        self.allowStar = allowStar
-
-    units = [
-	(re.compile('^(-?[0-9\.]+)\s*in$'), reportlab.lib.units.inch),
-	(re.compile('^(-?[0-9\.]+)\s*cm$'), reportlab.lib.units.cm),
-	(re.compile('^(-?[0-9\.]+)\s*mm$'), reportlab.lib.units.mm),
-	(re.compile('^(-?[0-9\.]+)\s*$'), 1)
-        ]
-
-    allowPercentage = False
-    allowStar = False
-
-    def convert(self, value, context=None):
-        if value == 'None':
-            return None
-        if value == '*' and self.allowStar:
-            return value
-        if value.endswith('%') and self.allowPercentage:
-            return value
-	for unit in self.units:
-            res = unit[0].search(value, 0)
-            if res:
-                return unit[1]*float(res.group(1))
-        raise ValueError('The value %r is not a valid measurement.' %value)
-
-class File(Text):
-
-    open = staticmethod(urllib.urlopen)
-    packageExtract = re.compile('^\[([0-9A-z_.]*)\]/(.*)$')
-
-    doNotOpen = False
-
-    def __init__(self, name=None, default=DEFAULT, doNotOpen=False):
-        super(File, self).__init__(name, default)
-        self.doNotOpen = doNotOpen
-
-
-    def convert(self, value, context=None):
-        # Check whether the value is of the form:
-        #    [<module.path>]/rel/path/image.gif"
-        if value.startswith('['):
-            result = self.packageExtract.match(value)
-            if result is None:
-                raise ValueError(
-                    'The package-path-pair you specified was incorrect')
-            modulepath, path = result.groups()
-            module = __import__(modulepath, {}, {}, (modulepath))
-            value = os.path.join(os.path.dirname(module.__file__), path)
-        if self.doNotOpen:
-            return value
-        # Open/Download the file
-        fileObj = self.open(value)
-        sio = cStringIO.StringIO(fileObj.read())
-        fileObj.close()
-        sio.seek(0)
-        return sio
-
-
-class Image(File):
-
-    def __init__(self, name=None, default=DEFAULT, onlyOpen=False):
-        super(Image, self).__init__(name, default)
-        self.onlyOpen = onlyOpen
-
-    def convert(self, value, context=None):
-        fileObj = super(Image, self).convert(value, context)
-        if self.onlyOpen:
-            return fileObj
-        return reportlab.lib.utils.ImageReader(fileObj)
-
-
-class Color(Text):
-
-    def convert(self, value, context=None):
-        if value == 'None':
-            return None
-        manager = getManager(context, interfaces.IColorsManager)
-        if value in manager.colors:
-            return manager.colors[value]
-        return reportlab.lib.colors.toColor(value)
-
-
-class Style(Text):
-
-    def __init__(self, name=None, default='Normal'):
-        super(Style, self).__init__(name, default)
-
-    def convert(self, value, context=None):
-        manager = getManager(context, interfaces.IStylesManager)
-        for styles in (manager.styles,
-                       reportlab.lib.styles.getSampleStyleSheet().byName):
-            if value in styles:
-                return styles[value]
-            elif 'style.' + value in styles:
-                return styles['style.' + value]
-            elif value.startswith('style.') and value[6:] in styles:
-                return styles[value[6:]]
-        raise ValueError('Style %r could not be found.' %value)
-
-    def get(self, element, default=DEFAULT, context=None):
-        value = element.get(self.name, DEFAULT)
-        if value is DEFAULT:
-            if default is DEFAULT:
-                return self.convert(self.default, context)
-            elif default is None:
-                return None
-            return self.convert(default, context)
-        return self.convert(value, context)
-
-
-class Symbol(Text):
-
-    def convert(self, value, context=None):
-        return reportlab.graphics.widgets.markers.makeMarker(value)
-
-class PageSize(Attribute):
-
-    sizePair = Sequence(valueType=Measurement())
-    words = Sequence(valueType=Attribute())
-
-    def convert(self, value, context=None):
-        # First try to get a pair
-        try:
-            return self.sizePair.convert(value, context)
-        except ValueError:
-            pass
-        # Now we try to lookup a name. The following type of combinations must
-        # work: "Letter" "LETTER" "A4 landscape" "letter portrait"
-        words = self.words.convert(value, context)
-        words = [word.lower() for word in words]
-        # First look for the orientation
-        orienter = None
-        for orientation in ('landscape', 'portrait'):
-            if orientation in words:
-                orienter = getattr(reportlab.lib.pagesizes, orientation)
-                words.remove(orientation)
-        # We must have exactely one value left that matches a paper size
-        pagesize = getattr(reportlab.lib.pagesizes, words[0].upper())
-        # Now do the final touches
-        if orienter:
-            pagesize = orienter(pagesize)
-        return pagesize
-
-
-class TextNode(Attribute):
-    """Text ndoes are not really attributes, but behave mostly like it."""
-
-    def __init__(self):
-        super(TextNode, self).__init__('TEXT')
-
-    def get(self, element, default=DEFAULT, context=None):
-        if element.text is None:
-            return u''
-        return unicode(element.text).strip()
-
-class FirstLevelTextNode(TextNode):
-    """Text ndoes are not really attributes, but behave mostly like it."""
-
-    def __init__(self):
-        super(TextNode, self).__init__('TEXT')
-
-    def get(self, element, default=DEFAULT, context=None):
-        text = element.text or u''
-        for child in element.getchildren():
-            text += child.tail or u''
-        return text
-
-
-class TextNodeSequence(Sequence):
-
-    def __init__(self, *args, **kw):
-        super(TextNodeSequence, self).__init__('TEXT', *args, **kw)
-
-    def get(self, element, default=DEFAULT, context=None):
-        return self.convert(element.text, context)
-
-
-class TextNodeGrid(TextNodeSequence):
-
-    def __init__(self, valueType=None, cols=None, default=DEFAULT):
-        super(TextNodeSequence, self).__init__(
-            'TEXT', valueType, default, length=cols)
-        self.cols = cols
-
-    def convert(self, value, context=None):
-        result = super(TextNodeGrid, self).convert(value, context)
-        if len(result) % self.cols != 0:
-            raise ValueError(
-                'Number of elements must be divisible by %i.' %self.cols)
-        return [result[i*self.cols:(i+1)*self.cols]
-                for i in range(len(result)/self.cols)]
-
-
-class RawXMLContent(Attribute):
-
-    def __init__(self, default=DEFAULT):
-        super(RawXMLContent, self).__init__('XML', default)
-        # Do it in here, since we hace a recursive problem otherwise
-        from z3c.rml import special
-        self.handleElements = {'getName': special.GetName}
-
-    def get(self, element, default=DEFAULT, context=None):
-        # Replace what we can replace
-        for subElement in element.iterdescendants():
-            if subElement.tag in self.handleElements:
-                substitute = self.handleElements[subElement.tag](
-                    subElement, context, None)
-                substitute.process()
-        # Now create the text
-        text = saxutils.escape(element.text or u'')
-        for child in element.getchildren():
-            text += etree.tounicode(child)
-        if text is None:
-            if default is DEFAULT:
-                return self.default
-            return default
-        return text
-
-
-class XMLContent(RawXMLContent):
-
-    def get(self, element, default=DEFAULT, context=None):
-        result = super(XMLContent, self).get(element, default, context)
-        return result.strip().replace('\t', ' ')

Added: z3c.rml/trunk/src/z3c/rml/attrng.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/attrng.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/attrng.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -0,0 +1,383 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation 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.
+#
+##############################################################################
+"""RML Attribute Implementation
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import cStringIO
+import os
+import re
+import reportlab.graphics.widgets.markers
+import reportlab.lib.colors
+import reportlab.lib.pagesizes
+import reportlab.lib.styles
+import reportlab.lib.units
+import reportlab.lib.utils
+import urllib
+import zope.schema
+from lxml import etree
+from xml.sax import saxutils
+
+MISSING = object()
+
+def getManager(context, interface=None):
+    if interface is None:
+        # Avoid circular imports
+        from z3c.rml import interfaces
+        interface = interfaces.IManager
+    # Walk up the path until the manager is found
+    while (not interface.providedBy(context) and context is not None):
+        context = context.parent
+    # If no manager was found, raise an error
+    if context is None:
+        raise ValueError('The manager could not be found.')
+    return context
+
+
+class RMLAttribute(zope.schema.Field):
+    """An attribute of the RML directive."""
+
+    missing_value = MISSING
+    default = MISSING
+
+    def fromUnicode(self, ustr):
+        """See zope.schema.interfaces.IField"""
+        if self.context is None:
+            raise ValueError('Attribute not bound to a context.')
+        return super(RMLAttribute, self).fromUnicode(unicode(ustr))
+
+    def get(self):
+        """See zope.schema.interfaces.IField"""
+        value = self.context.element.get(self.__name__, self.missing_value)
+        if value is self.missing_value:
+            if self.default is not None:
+                return self.default
+            return self.missing_value
+        return self.fromUnicode(value)
+
+
+class BaseChoice(RMLAttribute):
+    choices = {}
+
+    def fromUnicode(self, value):
+        value = value.lower()
+        if value in self.choices:
+            return self.choices[value]
+        raise ValueError(
+            '%r not a valid value for attribute "%s"' % (value, self.__name__))
+
+
+class Combination(RMLAttribute):
+
+    def __init__(self, value_types=(), *args, **kw):
+        super(Combination, self).__init__(*args, **kw)
+        self.value_types = value_types
+
+    def fromUnicode(self, value):
+        for value_type in self.value_types:
+            bound = value_type.bind(self)
+            try:
+                return bound.fromUnicode(value)
+            except ValueError:
+                pass
+        raise ValueError(value)
+
+
+class String(RMLAttribute, zope.schema.Bytes):
+    """A simple Bytes string."""
+
+
+class Text(RMLAttribute, zope.schema.Text):
+    """A simple unicode string."""
+
+
+class Integer(RMLAttribute, zope.schema.Int):
+    # By making min and max simple attributes, we avoid some validation
+    # problems.
+    min = None
+    max = None
+
+
+class Float(RMLAttribute, zope.schema.Float):
+    # By making min and max simple attributes, we avoid some validation
+    # problems.
+    min = None
+    max = None
+
+
+class StringOrInt(RMLAttribute):
+
+    def fromUnicode(self, value):
+        try:
+            return int(value)
+        except ValueError:
+            return str(value)
+
+
+class Sequence(RMLAttribute, zope.schema._field.AbstractCollection):
+
+    splitre = re.compile('[ \t\n,;]*')
+
+    def __init__(self, splitre=None, *args, **kw):
+        super(Sequence, self).__init__(*args, **kw)
+        if splitre is not None:
+            self.splitre = splitre
+
+    def fromUnicode(self, ustr):
+        if ustr.startswith('(') and ustr.endswith(')'):
+            ustr = ustr[1:-1]
+        ustr = ustr.strip()
+        raw_values = self.splitre.split(ustr)
+        result = [self.value_type.bind(self.context).fromUnicode(raw.strip())
+                  for raw in raw_values]
+        if ((self.min_length is not None and len(result) < self.min_length) and
+            (self.max_length is not None and len(result) > self.max_length)):
+            raise ValueError(
+                'Length of sequence must be at least %s and at most %i' % (
+                self.min_length, self.max_length))
+        return result
+
+
+class Choice(BaseChoice):
+
+    def __init__(self, choices=None, *args, **kw):
+        super(Choice, self).__init__(*args, **kw)
+        if isinstance(choices, (tuple, list)):
+            choices = dict([(val.lower(), val) for val in choices])
+        self.choices = choices
+
+
+class Boolean(BaseChoice):
+    choices = {'true': True, 'false': False,
+               'yes': True, 'no': False,
+               '1': True, '0': False,
+               }
+
+
+class BooleanWithDefault(Boolean):
+    choices = Boolean.choices.copy()
+    choices.update({'default': None})
+
+
+class Measurement(RMLAttribute):
+
+    def __init__(self, allowPercentage=False, allowStar=False, *args, **kw):
+        super(Measurement, self).__init__(*args, **kw)
+        self.allowPercentage = allowPercentage
+        self.allowStar = allowStar
+
+    units = [
+	(re.compile('^(-?[0-9\.]+)\s*in$'), reportlab.lib.units.inch),
+	(re.compile('^(-?[0-9\.]+)\s*cm$'), reportlab.lib.units.cm),
+	(re.compile('^(-?[0-9\.]+)\s*mm$'), reportlab.lib.units.mm),
+	(re.compile('^(-?[0-9\.]+)\s*$'), 1)
+        ]
+
+    allowPercentage = False
+    allowStar = False
+
+    def fromUnicode(self, value):
+        if value == 'None':
+            return None
+        if value == '*' and self.allowStar:
+            return value
+        if value.endswith('%') and self.allowPercentage:
+            return value
+	for unit in self.units:
+            res = unit[0].search(value, 0)
+            if res:
+                return unit[1]*float(res.group(1))
+        raise ValueError('The value %r is not a valid measurement.' %value)
+
+
+class File(Text):
+
+    open = staticmethod(urllib.urlopen)
+    packageExtract = re.compile('^\[([0-9A-z_.]*)\]/(.*)$')
+
+    doNotOpen = False
+
+    def __init__(self, doNotOpen=False, *args, **kw):
+        super(File, self).__init__(*args, **kw)
+        self.doNotOpen = doNotOpen
+
+    def fromUnicode(self, value):
+        # Check whether the value is of the form:
+        #    [<module.path>]/rel/path/image.gif"
+        if value.startswith('['):
+            result = self.packageExtract.match(value)
+            if result is None:
+                raise ValueError(
+                    'The package-path-pair you specified was incorrect')
+            modulepath, path = result.groups()
+            module = __import__(modulepath, {}, {}, (modulepath))
+            value = os.path.join(os.path.dirname(module.__file__), path)
+        if self.doNotOpen:
+            return value
+        # Open/Download the file
+        fileObj = self.open(value)
+        sio = cStringIO.StringIO(fileObj.read())
+        fileObj.close()
+        sio.seek(0)
+        return sio
+
+
+class Image(File):
+
+    def __init__(self, onlyOpen=False, *args, **kw):
+        super(Image, self).__init__(*args, **kw)
+        self.onlyOpen = onlyOpen
+
+    def fromUnicode(self, value):
+        fileObj = super(Image, self).fromUnicode(value)
+        if self.onlyOpen:
+            return fileObj
+        return reportlab.lib.utils.ImageReader(fileObj)
+
+
+class Color(RMLAttribute):
+
+    def __init__(self, acceptNone=False, *args, **kw):
+        super(Color, self).__init__(*args, **kw)
+        self.acceptNone = acceptNone
+
+    def fromUnicode(self, value):
+        if self.acceptNone and value == 'None':
+            return None
+        manager = getManager(self.context)
+        if value in manager.colors:
+            return manager.colors[value]
+        return reportlab.lib.colors.toColor(value)
+
+
+class Style(String):
+
+    default = reportlab.lib.styles.getSampleStyleSheet().byName['Normal']
+
+    def fromUnicode(self, value):
+        manager = getManager(self.context)
+        for styles in (manager.styles,
+                       reportlab.lib.styles.getSampleStyleSheet().byName):
+            if value in styles:
+                return styles[value]
+            elif 'style.' + value in styles:
+                return styles['style.' + value]
+            elif value.startswith('style.') and value[6:] in styles:
+                return styles[value[6:]]
+        raise ValueError('Style %r could not be found.' %value)
+
+
+class Symbol(Text):
+
+    def fromUnicode(self, value):
+        return reportlab.graphics.widgets.markers.makeMarker(value)
+
+
+class PageSize(RMLAttribute):
+
+    sizePair = Sequence(value_type=Measurement())
+    words = Sequence(value_type=String())
+
+    def fromUnicode(self, value):
+        # First try to get a pair
+        try:
+            return self.sizePair.fromUnicode(value)
+        except ValueError:
+            pass
+        # Now we try to lookup a name. The following type of combinations must
+        # work: "Letter" "LETTER" "A4 landscape" "letter portrait"
+        words = self.words.fromUnicode(value)
+        words = [word.lower() for word in words]
+        # First look for the orientation
+        orienter = None
+        for orientation in ('landscape', 'portrait'):
+            if orientation in words:
+                orienter = getattr(reportlab.lib.pagesizes, orientation)
+                words.remove(orientation)
+        # We must have exactely one value left that matches a paper size
+        pagesize = getattr(reportlab.lib.pagesizes, words[0].upper())
+        # Now do the final touches
+        if orienter:
+            pagesize = orienter(pagesize)
+        return pagesize
+
+
+class TextNode(RMLAttribute):
+    """Text nodes are not really attributes, but behave mostly like it."""
+
+    def get(self):
+        if self.context.element.text is None:
+            return u''
+        return unicode(self.context.element.text).strip()
+
+
+class FirstLevelTextNode(TextNode):
+    """Text ndoes are not really attributes, but behave mostly like it."""
+
+    def get(self):
+        text = self.context.element.text or u''
+        for child in self.context.element.getchildren():
+            text += child.tail or u''
+        return text.strip()
+
+
+class TextNodeSequence(Sequence):
+
+    def get(self):
+        return self.fromUnicode(self.context.element.text)
+
+
+class TextNodeGrid(TextNodeSequence):
+
+    def __init__(self, columns=None, *args, **kw):
+        super(TextNodeGrid, self).__init__(*args, **kw)
+        self.columns = columns
+
+    def fromUnicode(self, ustr):
+        result = super(TextNodeGrid, self).fromUnicode(ustr)
+        if len(result) % self.columns != 0:
+            raise ValueError(
+                'Number of elements must be divisible by %i.' %self.columns)
+        return [result[i*self.columns:(i+1)*self.columns]
+                for i in range(len(result)/self.columns)]
+
+
+class RawXMLContent(RMLAttribute):
+
+    def __init__(self, *args, **kw):
+        super(RawXMLContent, self).__init__(*args, **kw)
+        # Do it in here, since we hace a recursive problem otherwise
+        from z3c.rml import special
+        self.handleElements = {'getName': special.GetName}
+
+    def get(self):
+        # Replace what we can replace
+        for subElement in self.context.element.iterdescendants():
+            if subElement.tag in self.handleElements:
+                substitute = self.handleElements[subElement.tag](
+                    subElement, self.context)
+                substitute.process()
+        # Now create the text
+        text = saxutils.escape(self.context.element.text or u'')
+        for child in self.context.element.getchildren():
+            text += etree.tounicode(child)
+        return text
+
+
+class XMLContent(RawXMLContent):
+
+    def get(self):
+        result = super(XMLContent, self).get()
+        return result.strip().replace('\t', ' ')


Property changes on: z3c.rml/trunk/src/z3c/rml/attrng.py
___________________________________________________________________
Name: svn:keywords
   + Id

Modified: z3c.rml/trunk/src/z3c/rml/canvas.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/canvas.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/canvas.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -16,218 +16,474 @@
 $Id$
 """
 __docformat__ = "reStructuredText"
-import cStringIO
 import zope.interface
 import reportlab.pdfgen.canvas
-from z3c.rml import attr, element, flowable, interfaces, stylesheet
-from z3c.rml import chart, form, page
+from z3c.rml import attrng, directive, interfaces, occurence, stylesheet
+from z3c.rml import chart, flowable, form, page
 
 
-class DrawString(element.FunctionElement):
-    functionName = 'drawString'
-    args = (
-        attr.Measurement('x'),
-        attr.Measurement('y'),
-        attr.TextNode())
+class IShape(interfaces.IRMLDirectiveSignature):
+    """A shape to be drawn on the canvas."""
 
+    x = attrng.Measurement(
+        title=u'X-Coordinate',
+        description=(u'The X-coordinate of the lower-left position of the '
+                     u'shape.'),
+        required=True)
 
+    y = attrng.Measurement(
+        title=u'Y-Coordinate',
+        description=(u'The Y-coordinate of the lower-left position of the '
+                     u'shape.'),
+        required=True)
+
+    fill = attrng.Boolean(
+        title=u'Fill',
+        description=u'A flag to specify whether the shape should be filled.',
+        required=False)
+
+    stroke = attrng.Boolean(
+        title=u'Stroke',
+        description=(u"A flag to specify whether the shape's outline should "
+                     u"be drawn."),
+        required=False)
+
+
+class CanvasRMLDirective(directive.RMLDirective):
+    callable = None
+    attrMapping = None
+
+    def process(self):
+        kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
+        canvas = attrng.getManager(self, interfaces.ICanvasManager).canvas
+        getattr(canvas, self.callable)(**kwargs)
+
+
+class IDrawString(interfaces.IRMLDirectiveSignature):
+    """Draws a simple string (left aligned) onto the canvas at the specified
+    location."""
+
+    x = attrng.Measurement(
+        title=u'X-Coordinate',
+        description=(u'The X-coordinate of the lower-left position of the '
+                     u'string.'),
+        required=True)
+
+    y = attrng.Measurement(
+        title=u'Y-Coordinate',
+        description=(u'The Y-coordinate of the lower-left position of the '
+                     u'string.'),
+        required=True)
+
+    text = attrng.TextNode(
+        title=u'Text',
+        description=(u'The string/text that is put onto the canvas.'),
+        required=True)
+
+class DrawString(CanvasRMLDirective):
+    signature = IDrawString
+    callable = 'drawString'
+
+class IDrawRightString(IDrawString):
+    """Draws a simple string (right aligned) onto the canvas at the specified
+    location."""
+
 class DrawRightString(DrawString):
-    functionName = 'drawRightString'
+    signature = IDrawRightString
+    callable = 'drawRightString'
 
 
+class IDrawCenteredString(IDrawString):
+    """Draws a simple string (centered aligned) onto the canvas at the specified
+    location."""
+
 class DrawCenteredString(DrawString):
-    functionName = 'drawCentredString'
+    signature = IDrawCenteredString
+    callable = 'drawCentredString'
 
 
+class IDrawAlignedString(IDrawString):
+    """Draws a simple string (aligned to the pivot character) onto the canvas
+    at the specified location."""
+
+    pivotChar = attrng.Text(
+        title=u'Text',
+        description=(u'The string/text that is put onto the canvas.'),
+        min_length=1,
+        max_length=1,
+        default=u'.',
+        required=True)
+
 class DrawAlignedString(DrawString):
-    functionName = 'drawAlignedString'
-    args = DrawString.args + (
-        attr.Text('pivotChar', '.'), )
+    signature = IDrawAlignedString
+    callable = 'drawAlignedString'
 
 
-class Shape(element.FunctionElement):
-    args = (
-        attr.Measurement('x'),
-        attr.Measurement('y') )
-    kw = (
-        ('fill', attr.Bool('fill')),
-        ('stroke', attr.Bool('stroke')) )
+class IEllipse(IShape):
+    """Draws an ellipse on the canvas."""
 
+    width = attrng.Measurement(
+        title=u'Width',
+        description=u'The width of the ellipse.',
+        required=True)
 
-class Ellipse(Shape):
-    functionName = 'ellipse'
-    args = Shape.args + (
-        attr.Measurement('width'),
-        attr.Measurement('height') )
+    height = attrng.Measurement(
+        title=u'Height',
+        description=u'The height of the ellipse.',
+        required=True)
 
+class Ellipse(CanvasRMLDirective):
+    signature = IEllipse
+    callable = 'ellipse'
+    attrMapping = {'x': 'x1', 'y': 'y1'}
+
     def process(self):
-        args = self.getPositionalArguments()
-        kw = self.getKeywordArguments()
+        kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
+        canvas = attrng.getManager(self, interfaces.ICanvasManager).canvas
         # Convert width and height to end locations
-        args[2] += args[0]
-        args[3] += args[1]
-        getattr(self.context, self.functionName)(*args, **kw)
+        kwargs['x2'] = kwargs['x1'] + kwargs['width']
+        del kwargs['width']
+        kwargs['y2'] = kwargs['y1'] + kwargs['height']
+        del kwargs['height']
+        getattr(canvas, self.callable)(**kwargs)
 
-class Circle(Shape):
-    functionName = 'circle'
-    args = Shape.args + (
-        attr.Measurement('radius'), )
 
+class ICircle(IShape):
+    """Draws a circle on the canvas."""
 
-class Rectangle(Shape):
-    functionName = 'rect'
-    args = Shape.args + (
-        attr.Measurement('width'),
-        attr.Measurement('height') )
-    kw = Shape.kw + (
-        ('radius', attr.Measurement('round')),
-        )
+    radius = attrng.Measurement(
+        title=u'Radius',
+        description=u'The radius of the circle.',
+        required=True)
 
+class Circle(CanvasRMLDirective):
+    signature = ICircle
+    callable = 'circle'
+    attrMapping = {'x': 'x_cen', 'y': 'y_cen', 'radius': 'r'}
+
+
+class IRectangle(IShape):
+    """Draws an ellipse on the canvas."""
+
+    width = attrng.Measurement(
+        title=u'Width',
+        description=u'The width of the rectangle.',
+        required=True)
+
+    height = attrng.Measurement(
+        title=u'Height',
+        description=u'The height of the rectangle.',
+        required=True)
+
+    round = attrng.Measurement(
+        title=u'Corner Radius',
+        description=u'The radius of the rounded corners.',
+        required=False)
+
+class Rectangle(CanvasRMLDirective):
+    signature = IRectangle
+    callable = 'rect'
+    attrMapping = {'round': 'radius'}
+
     def process(self):
         if 'round' in self.element.keys():
-            self.functionName = 'roundRect'
+            self.callable = 'roundRect'
         super(Rectangle, self).process()
 
 
-class Grid(element.FunctionElement):
-    functionName = 'grid'
-    args = (
-        attr.Sequence('xs', attr.Measurement()),
-        attr.Sequence('ys', attr.Measurement()) )
+class IGrid(interfaces.IRMLDirectiveSignature):
+    """A shape to be drawn on the canvas."""
 
+    xs = attrng.Sequence(
+        title=u'X-Coordinates',
+        description=(u'A sequence x-coordinates that represent the vertical '
+                     u'line positions.'),
+        value_type=attrng.Measurement(),
+        required=True)
 
-class Lines(element.FunctionElement):
-    functionName = 'lines'
-    args = (
-        attr.TextNodeGrid(attr.Measurement(), 4),
-        )
+    ys = attrng.Sequence(
+        title=u'Y-Coordinates',
+        description=(u'A sequence y-coordinates that represent the horizontal '
+                     u'line positions.'),
+        value_type=attrng.Measurement(),
+        required=True)
 
 
-class Curves(element.FunctionElement):
-    functionName = 'bezier'
-    args = (
-        attr.TextNodeGrid(attr.Measurement(), 8),
-        )
+class Grid(CanvasRMLDirective):
+    signature = IGrid
+    callable = 'grid'
+    attrMapping = {'xs': 'xlist', 'ys': 'ylist'}
 
+
+class ILines(interfaces.IRMLDirectiveSignature):
+    """A path of connected lines drawn on the canvas."""
+
+    linelist = attrng.TextNodeGrid(
+        title=u'Line List',
+        description=(u'A list of lines coordinates to draw.'),
+        value_type=attrng.Measurement(),
+        columns=4,
+        required=True)
+
+class Lines(CanvasRMLDirective):
+    signature = ILines
+    callable = 'lines'
+
+
+class ICurves(interfaces.IRMLDirectiveSignature):
+    """A path of connected bezier curves drawn on the canvas."""
+
+    curvelist = attrng.TextNodeGrid(
+        title=u'Curve List',
+        description=(u'A list of curve coordinates to draw.'),
+        value_type=attrng.Measurement(),
+        columns=8,
+        required=True)
+
+class Curves(CanvasRMLDirective):
+    signature = ICurves
+    callable = 'bezier'
+
     def process(self):
-        argset = self.getPositionalArguments()
-        for args in argset[0]:
-            getattr(self.context, self.functionName)(*args)
+        argset = self.getAttributeValues(valuesOnly=True)[0]
+        canvas = attrng.getManager(self, interfaces.ICanvasManager).canvas
+        for args in argset:
+            getattr(canvas, self.callable)(*args)
 
 
-class Image(element.FunctionElement):
-    functionName = 'drawImage'
-    args = (
-        attr.Image('file'),
-        attr.Measurement('x'),
-        attr.Measurement('y') )
-    kw = (
-        ('width', attr.Measurement('width')),
-        ('height', attr.Measurement('height')) )
+class IImage(interfaces.IRMLDirectiveSignature):
+    """Draws an external image on the canvas."""
 
+    file = attrng.Image(
+        title=u'File',
+        description=(u'Reference to the external file of the iamge.'),
+        required=True)
+
+    x = attrng.Measurement(
+        title=u'X-Coordinate',
+        description=(u'The X-coordinate of the lower-left position of the '
+                     u'shape.'),
+        required=True)
+
+    y = attrng.Measurement(
+        title=u'Y-Coordinate',
+        description=(u'The Y-coordinate of the lower-left position of the '
+                     u'shape.'),
+        required=True)
+
+    width = attrng.Measurement(
+        title=u'Width',
+        description=u'The width of the image.',
+        required=False)
+
+    height = attrng.Measurement(
+        title=u'Height',
+        description=u'The height of the image.',
+        required=False)
+
+    showBoundary = attrng.Boolean(
+        title=u'Show Boundary',
+        description=(u'A flag determining whether a border should be drawn '
+                     u'around the image.'),
+        default=False,
+        required=False)
+
+    preserveAspectRatio = attrng.Boolean(
+        title=u'Preserve Aspect Ratio',
+        description=(u"A flag determining whether the image's aspect ration "
+                     u"should be conserved under any circumstances."),
+        default=False,
+        required=False)
+
+class Image(CanvasRMLDirective):
+    signature = IImage
+    callable = 'drawImage'
+    attrMapping = {'file': 'image'}
+
     def process(self):
-        args = self.getPositionalArguments()
-        kw = self.getKeywordArguments()
+        kwargs = dict(self.getAttributeValues(attrMapping=self.attrMapping))
+        preserve = kwargs.pop('preserveAspectRatio')
+        show = kwargs.pop('showBoundary')
 
-        preserve = attr.Bool('preserveAspectRatio').get(self.element, False)
-
         if preserve:
-            imgX, imgY = args[0].getSize()
+            imgX, imgY = kwargs['image'].getSize()
 
             # Scale image correctly, if width and/or height were specified
-            if 'width' in kw and 'height' not in kw:
-                kw['height'] = imgY * kw['width'] / imgX
-            elif 'height' in kw and 'width' not in kw:
-                kw['width'] = imgX * kw['height'] / imgY
-            elif 'width' in kw and 'height' in kw:
-                if float(kw['width']) / kw['height'] > float(imgX) / imgY:
-                    kw['width'] = imgX * kw['height'] / imgY
+            if 'width' in kwargs and 'height' not in kwargs:
+                kwargs['height'] = imgY * kwargs['width'] / imgX
+            elif 'height' in kwargs and 'width' not in kwargs:
+                kwargs['width'] = imgX * kwargs['height'] / imgY
+            elif 'width' in kwargs and 'height' in kwargs:
+                if float(kwargs['width'])/kwargs['height'] > float(imgX)/imgY:
+                    kwargs['width'] = imgX * kwargs['height'] / imgY
                 else:
-                    kw['height'] = imgY * kw['width'] / imgX
+                    kwargs['height'] = imgY * kwargs['width'] / imgX
 
-        getattr(self.context, self.functionName)(*args, **kw)
+        canvas = attrng.getManager(self, interfaces.ICanvasManager).canvas
+        getattr(canvas, self.callable)(**kwargs)
 
-        show = attr.Bool('showBoundary').get(self.element, False)
         if show:
-            width = kw.get('width', args[0].getSize()[0])
-            height = kw.get('height', args[0].getSize()[1])
-            self.context.rect(args[1], args[2], width, height)
+            width = kwargs.get('width', kwargs['image'].getSize()[0])
+            height = kwargs.get('height', kwargs['image'].getSize()[1])
+            canvas.rect(kwargs['x'], kwargs['y'], width, height)
 
 
-class Place(element.FunctionElement):
-    args = (
-        attr.Measurement('x'), attr.Measurement('y'),
-        attr.Measurement('width'), attr.Measurement('height') )
+class IPlace(interfaces.IRMLDirectiveSignature):
+    """Draws a set of flowables on the canvas within a given region."""
 
+    x = attrng.Measurement(
+        title=u'X-Coordinate',
+        description=(u'The X-coordinate of the lower-left position of the '
+                     u'place.'),
+        required=True)
+
+    y = attrng.Measurement(
+        title=u'Y-Coordinate',
+        description=(u'The Y-coordinate of the lower-left position of the '
+                     u'place.'),
+        required=True)
+
+    width = attrng.Measurement(
+        title=u'Width',
+        description=u'The width of the place.',
+        required=False)
+
+    height = attrng.Measurement(
+        title=u'Height',
+        description=u'The height of the place.',
+        required=False)
+
+class Place(CanvasRMLDirective):
+    signature = IPlace
+
     def process(self):
-        x, y, width, height = self.getPositionalArguments()
+        x, y, width, height = self.getAttributeValues(
+            select=('x', 'y', 'width', 'height'), valuesOnly=True)
         y += height
 
-        flows = flowable.Flow(self.element, self.parent, self.context)
+        flows = flowable.Flow(self.element, self.parent)
         flows.process()
+
+        canvas = attrng.getManager(self, interfaces.ICanvasManager).canvas
         for flow in flows.flow:
             flowWidth, flowHeight = flow.wrap(width, height)
             if flowWidth <= width and flowHeight <= height:
                 y -= flowHeight
-                flow.drawOn(self.context, x, y)
+                flow.drawOn(canvas, x, y)
                 height -= flowHeight
             else:
                 raise ValueError("Not enough space")
 
 
-class Param(element.FunctionElement):
-    args = (attr.Attribute('name'), attr.TextNode() )
+class IParam(interfaces.IRMLDirectiveSignature):
+    """Sets one paramter for the text annotation."""
 
+    name = attrng.String(
+        title=u'Name',
+        description=u'The name of the paramter.',
+        required=True)
+
+    value = attrng.TextNode(
+        title=u'Value',
+        description=(u'The parameter value.'),
+        required=True)
+
+class Param(directive.RMLDirective):
+    signature = IParam
+
     def process(self):
-        name, value = self.getPositionalArguments()
-        self.context[name] = value
+        args = dict(self.getAttributeValues())
+        self.parent.params[args['name']] = args['value']
 
-class TextAnnotation(element.ContainerElement, element.FunctionElement):
-    args = (attr.FirstLevelTextNode(), )
 
-    paramTypes = {
-        'escape': attr.Int(),
-        }
+class ITextAnnotation(interfaces.IRMLDirectiveSignature):
+    """Writes a low-level text annotation into the PDF."""
+    occurence.containing(
+        occurence.ZeroOrMore('param', IParam))
 
-    subElements = {'param': Param}
+    contents = attrng.FirstLevelTextNode(
+        title=u'Contents',
+        description=u'The PDF commands that are inserted as annotation.',
+        required=True)
 
+class TextAnnotation(CanvasRMLDirective):
+    signature = ITextAnnotation
+    factories = {'param': Param}
+
+    paramTypes = {'escape': attrng.Integer()}
+
     def process(self):
-        contents = self.getPositionalArguments()[0]
-        params = {}
-        self.processSubElements(params)
+        contents = self.getAttributeValues(valuesOnly=True)[0]
+        self.params = {}
+        self.processSubDirectives()
         for name, type in self.paramTypes.items():
-            if name in params:
-                params[name] = type.convert(params[name])
-        self.context.textAnnotation(contents, **params)
+            if name in self.params:
+                bound = type.bind(self)
+                self.params[name] = bound.fromUnicode(self.params[name])
+        canvas = attrng.getManager(self, interfaces.ICanvasManager).canvas
+        canvas.textAnnotation(contents, **self.params)
 
 
-class MoveTo(element.FunctionElement):
-    args = (
-        attr.TextNodeSequence(attr.Measurement(), length=2),
-        )
+class IMoveTo(interfaces.IRMLDirectiveSignature):
+    """Move the path cursor to the specified location."""
 
+    position = attrng.TextNodeSequence(
+        title=u'Position',
+        description=u'Position to which the path pointer is moved to.',
+        value_type=attrng.Measurement(),
+        min_length=2,
+        max_length=2,
+        required=True)
+
+class MoveTo(directive.RMLDirective):
+    signature = IMoveTo
+
     def process(self):
-        args = self.getPositionalArguments()
-        self.context.moveTo(*args[0])
+        args = self.getAttributeValues(valuesOnly=True)
+        self.parent.path.moveTo(*args[0])
 
-class CurvesTo(Curves):
-    functionName = 'curveTo'
-    args = (
-        attr.TextNodeGrid(attr.Measurement(), 6),
+
+class ICurvesTo(interfaces.IRMLDirectiveSignature):
+    """Create a bezier curve from the current location to the specified one."""
+
+    curvelist = attrng.TextNodeGrid(
+        title=u'Curve Specification',
+        description=u'Describes the end position and the curve properties.',
+        value_type=attrng.Measurement(),
+        columns=6,
+        required=True)
+
+class CurvesTo(directive.RMLDirective):
+    signature = ICurvesTo
+
+    def process(self):
+        argset = self.getAttributeValues(valuesOnly=True)[0]
+        for args in argset:
+            self.parent.path.curveTo(*args)
+
+class IPath(IShape):
+    """Create a line path."""
+    occurence.containing(
+        occurence.ZeroOrMore('moveTo', IMoveTo),
+        occurence.ZeroOrMore('curvesTo', ICurvesTo),
         )
 
-class Path(element.FunctionElement):
-    args = (
-        attr.Measurement('x'),
-        attr.Measurement('y') )
-    kw = (
-        ('close', attr.Bool('close')),
-        ('fill', attr.Bool('fill')),
-        ('stroke', attr.Bool('stroke')) )
+    points = attrng.TextNodeGrid(
+        title=u'Points',
+        description=(u'A list of coordinate points that define th path.'),
+        value_type=attrng.Measurement(),
+        columns=2,
+        required=True)
 
-    points = attr.TextNodeGrid(attr.Measurement(), 2)
+    close = attrng.Boolean(
+        title=u'Close Path',
+        description=(u"A flag specifying whether the path should be closed."),
+        default=False,
+        required=False)
 
-    subElements = {
+class Path(CanvasRMLDirective):
+    signature = IPath
+    factories = {
         'moveto': MoveTo,
         'curvesto': CurvesTo
         }
@@ -235,107 +491,270 @@
     def processPoints(self, text):
         if text.strip() == '':
             return
-        for coords in self.points.convert(text):
+        bound = self.signature['points'].bind(self)
+        for coords in bound.fromUnicode(text):
             self.path.lineTo(*coords)
 
     def process(self):
-        args = self.getPositionalArguments()
-        kw = self.getKeywordArguments()
+        kwargs = dict(self.getAttributeValues(ignore=('points',)))
 
-        self.path = self.context.beginPath()
-        self.path.moveTo(*args)
+        # Start the path and set the cursor to the start location.
+        canvas = attrng.getManager(self, interfaces.ICanvasManager).canvas
+        self.path = canvas.beginPath()
+        self.path.moveTo(kwargs.pop('x'), kwargs.pop('y'))
 
+        # Process the text before the first sub-directive.
         if self.element.text is not None:
             self.processPoints(self.element.text)
-        for subElement in self.element.getchildren():
-            if subElement.tag in self.subElements:
-                self.subElements[subElement.tag](
-                    subElement, self, self.path).process()
-            if subElement.tail is not None:
-                self.processPoints(subElement.tail)
+        # Handle each sub-directive.
+        for directive in self.element.getchildren():
+            if directive.tag in self.factories:
+                self.factories[directive.tag](directive, self).process()
+            # If there is more text after sub-directive, process it.
+            if directive.tail is not None:
+                self.processPoints(directive.tail)
 
-        if kw.pop('close', False):
+        if kwargs.pop('close', False):
             self.path.close()
 
-        self.context.drawPath(self.path, **kw)
+        canvas.drawPath(self.path, **kwargs)
 
 
-class Fill(element.FunctionElement):
-    functionName = 'setFillColor'
-    args = (
-        attr.Color('color'), )
+class IFill(interfaces.IRMLDirectiveSignature):
+    """Set the fill color."""
 
+    color = attrng.Color(
+        title=u'Color',
+        description=(u'The color value to be set.'),
+        required=True)
 
-class Stroke(element.FunctionElement):
-    functionName = 'setStrokeColor'
-    args = (
-        attr.Color('color'), )
+class Fill(CanvasRMLDirective):
+    signature = IFill
+    callable = 'setFillColor'
+    attrMapping = {'color': 'aColor'}
 
 
-class SetFont(element.FunctionElement):
-    functionName = 'setFont'
-    args = (
-        attr.Text('name'),
-        attr.Measurement('size'), )
+class IStroke(interfaces.IRMLDirectiveSignature):
+    """Set the fill color."""
 
+    color = attrng.Color(
+        title=u'Color',
+        description=(u'The color value to be set.'),
+        required=True)
 
-class Scale(element.FunctionElement):
-    functionName = 'scale'
-    args = (attr.Float('sx'), attr.Float('sy'), )
+class Stroke(CanvasRMLDirective):
+    signature = IStroke
+    callable = 'setStrokeColor'
+    attrMapping = {'color': 'aColor'}
 
 
-class Translate(element.FunctionElement):
-    functionName = 'translate'
-    args = (attr.Measurement('dx', 0), attr.Measurement('dy', 0), )
+class ISetFont(interfaces.IRMLDirectiveSignature):
+    """Set the font name and/or size."""
 
+    name = attrng.String(
+        title=u'Font Name',
+        description=(u'The name of the font as it was registered.'),
+        required=True)
 
-class Rotate(element.FunctionElement):
-    functionName = 'rotate'
-    args = (attr.Float('degrees'), )
+    size = attrng.Measurement(
+        title=u'Size',
+        description=(u'The font size.'),
+        required=True)
 
+    leading = attrng.Measurement(
+        title=u'Leading',
+        description=(u'The font leading.'),
+        required=False)
 
-class Skew(element.FunctionElement):
-    functionName = 'skew'
-    args = (attr.Measurement('alpha'), attr.Measurement('beta'), )
+class SetFont(CanvasRMLDirective):
+    signature = ISetFont
+    callable = 'setFont'
+    attrMapping = {'name': 'psfontname'}
 
 
-class Transform(element.FunctionElement):
-    functionName = 'transform'
-    args = (attr.TextNodeSequence(attr.Float()), )
+class IScale(interfaces.IRMLDirectiveSignature):
+    """Scale the drawing using x and y sclaing factors."""
 
+    sx = attrng.Float(
+        title=u'X-Scaling-Factor',
+        description=(u'The scaling factor applied on x-coordinates.'),
+        required=True)
+
+    sy = attrng.Float(
+        title=u'Y-Scaling-Factor',
+        description=(u'The scaling factor applied on y-coordinates.'),
+        required=True)
+
+class Scale(CanvasRMLDirective):
+    signature = IScale
+    callable = 'scale'
+    attrMapping = {'sx': 'x', 'sy': 'y'}
+
+
+class ITranslate(interfaces.IRMLDirectiveSignature):
+    """Translate the drawing coordinates by the specified x and y offset."""
+
+    dx = attrng.Measurement(
+        title=u'X-Offset',
+        description=(u'The amount to move the drawing to the right.'),
+        required=True)
+
+    dy = attrng.Measurement(
+        title=u'Y-Offset',
+        description=(u'The amount to move the drawing upward.'),
+        required=True)
+
+class Translate(CanvasRMLDirective):
+    signature = ITranslate
+    callable = 'translate'
+
+
+class IRotate(interfaces.IRMLDirectiveSignature):
+    """Rotate the drawing counterclockwise."""
+
+    degrees = attrng.Measurement(
+        title=u'Angle',
+        description=(u'The angle in degrees.'),
+        required=True)
+
+class Rotate(CanvasRMLDirective):
+    signature = IRotate
+    callable = 'rotate'
+    attrMapping = {'degrees': 'theta'}
+
+
+class ISkew(interfaces.IRMLDirectiveSignature):
+    """Skew the drawing."""
+
+    alpha = attrng.Measurement(
+        title=u'Alpha',
+        description=(u'The amount to skew the drawing in the horizontal.'),
+        required=True)
+
+    beta = attrng.Measurement(
+        title=u'Beta',
+        description=(u'The amount to skew the drawing in the vertical.'),
+        required=True)
+
+class Skew(CanvasRMLDirective):
+    signature = ISkew
+    callable = 'skew'
+
+
+class ITransform(interfaces.IRMLDirectiveSignature):
+    """A full 2-D matrix transformation"""
+
+    matrix = attrng.TextNodeSequence(
+        title=u'Matrix',
+        description=u'The transformation matrix.',
+        value_type=attrng.Float(),
+        min_length=6,
+        max_length=6,
+        required=True)
+
+class Transform(CanvasRMLDirective):
+    signature = ITransform
+
     def process(self):
-        args = self.getPositionalArguments()
-        getattr(self.context, self.functionName)(*args[0])
+        args = self.getAttributeValues(valuesOnly=True)
+        canvas = attrng.getManager(self, interfaces.ICanvasManager).canvas
+        canvas.transform(*args[0])
 
 
-class LineMode(element.FunctionElement):
-    kw = (
-        ('width', attr.Measurement('width')),
-        ('dash', attr.Sequence('dash', attr.Measurement())),
-        ('miterLimit', attr.Measurement('miterLimit')),
-        ('join', attr.Choice(
-             'join', {'round': 1, 'mitered': 0, 'bevelled': 2})),
-        ('cap', attr.Choice(
-             'cap', {'default': 0, 'round': 1, 'square': 2})),
-        )
+class ILineMode(interfaces.IRMLDirectiveSignature):
+    """Set the line mode for the following graphics elements."""
 
+    width = attrng.Measurement(
+        title=u'Width',
+        description=(u'The line width.'),
+        required=False)
+
+    dash = attrng.Sequence(
+        title=u'Dash-Pattern',
+        description=(u'The dash-pattern of a line.'),
+        value_type=attrng.Measurement(),
+        required=False)
+
+    miterLimit = attrng.Measurement(
+        title=u'Miter Limit',
+        description=(u'The ???.'),
+        required=False)
+
+    join = attrng.Choice(
+        title=u'Join',
+        description=u'The way lines are joined together.',
+        choices=interfaces.JOIN_CHOICES,
+        required=False)
+
+    cap = attrng.Choice(
+        title=u'Cap',
+        description=u'The cap is the desciption of how the line-endings look.',
+        choices=interfaces.CAP_CHOICES,
+        required=False)
+
+class LineMode(CanvasRMLDirective):
+    signature = ILineMode
+
     def process(self):
-        kw = self.getKeywordArguments()
+        kw = dict(self.getAttributeValues())
+        canvas = attrng.getManager(self, interfaces.ICanvasManager).canvas
         if 'width' in kw:
-            self.context.setLineWidth(kw['width'])
+            canvas.setLineWidth(kw['width'])
         if 'join' in kw:
-            self.context.setLineJoin(kw['join'])
+            canvas.setLineJoin(kw['join'])
         if 'cap' in kw:
-            self.context.setLineCap(kw['cap'])
+            canvas.setLineCap(kw['cap'])
         if 'miterLimit' in kw:
-            self.context.setMiterLimit(kw['miterLimit'])
+            canvas.setMiterLimit(kw['miterLimit'])
         if 'dash' in kw:
-            self.context.setDash(kw['dash'])
+            canvas.setDash(kw['dash'])
 
 
-class Drawing(element.ContainerElement):
+class IDrawing(interfaces.IRMLDirectiveSignature):
+    """A container directive for all directives that draw directly on the
+    cnavas."""
+    occurence.containing(
+        occurence.ZeroOrMore('drawString', IDrawString),
+        occurence.ZeroOrMore('drawRightString', IDrawRightString),
+        occurence.ZeroOrMore('drawCenteredString', IDrawCenteredString),
+        occurence.ZeroOrMore('drawCentredString', IDrawCenteredString),
+        occurence.ZeroOrMore('drawAlignedString', IDrawAlignedString),
+        # Drawing Operations
+        occurence.ZeroOrMore('ellipse', IEllipse),
+        occurence.ZeroOrMore('circle', ICircle),
+        occurence.ZeroOrMore('rect', IRectangle),
+        occurence.ZeroOrMore('grid', IGrid),
+        occurence.ZeroOrMore('lines', ILines),
+        occurence.ZeroOrMore('curves', ICurves),
+        occurence.ZeroOrMore('image', IImage),
+        occurence.ZeroOrMore('place', IPlace),
+        occurence.ZeroOrMore('textAnnotation', ITextAnnotation),
+        occurence.ZeroOrMore('path', IPath),
+        # State Change Operations
+        occurence.ZeroOrMore('fill', IFill),
+        occurence.ZeroOrMore('stroke', IStroke),
+        occurence.ZeroOrMore('setFont', ISetFont),
+        occurence.ZeroOrMore('scale', IScale),
+        occurence.ZeroOrMore('translate', ITranslate),
+        occurence.ZeroOrMore('rotate', IRotate),
+        occurence.ZeroOrMore('skew', ISkew),
+        occurence.ZeroOrMore('transform', ITransform),
+        occurence.ZeroOrMore('lineMode', ILineMode),
+        # Form Field Elements
+        occurence.ZeroOrMore('barCode', form.IBarCode),
+        # Charts
+        #ZeroOrMore('barChart', IBarChart),
+        #ZeroOrMore('barChart3D', IBarChart3D),
+        #ZeroOrMore('linePlot', ILinePlot),
+        #ZeroOrMore('pieChart', IPieChart),
+        #ZeroOrMore('pieChart3D', IPieChart3D),
+        #ZeroOrMore('spiderChart', ISpiderChart),
+        )
 
-    subElements = {
+class Drawing(directive.RMLDirective):
+    signature = IDrawing
+
+    factories = {
         'drawString': DrawString,
         'drawRightString': DrawRightString,
         'drawCenteredString': DrawCenteredString,
@@ -374,58 +793,37 @@
         }
 
 
+class IPageDrawing(IDrawing):
+    """Draws directly on the content of one page's canvas. Every call of this
+    directive creates a new page."""
+
+    occurence.containing(
+        #'mergePage': IMergePage,
+        *IDrawing.getTaggedValue('directives'))
+
 class PageDrawing(Drawing):
+    signature = IDrawing
 
-    subElements = Drawing.subElements.copy()
-    subElements.update({
+    factories = Drawing.factories.copy()
+    factories.update({
         'mergePage': page.MergePage
         })
 
     def process(self):
         super(Drawing, self).process()
-        self.context.showPage()
+        canvas = attrng.getManager(self, interfaces.ICanvasManager).canvas
+        canvas.showPage()
 
 
-class PageInfo(element.Element):
+class IPageInfo(interfaces.IRMLDirectiveSignature):
+    """Set's up page-global settings."""
 
-    def process(self):
-        pageSize = attr.PageSize('pageSize').get(self.element)
-        self.context.setPageSize(pageSize)
+    pageSize = attrng.PageSize(
+        title=u'Page Size',
+        description=(u'The page size of all pages within this document.'),
+        required=True)
 
-
-class Canvas(element.ContainerElement):
-    zope.interface.implements(interfaces.IPostProcessorManager)
-
-    subElements = {
-        'stylesheet': stylesheet.Stylesheet,
-        'pageDrawing': PageDrawing,
-        'pageInfo': PageInfo,
-        }
-
-    def __init__(self, element, parent, context):
-        super(Canvas, self).__init__(element, parent, context)
-        self.postProcessors = []
-
-    def process(self, outputFile):
-        verbosity = attr.Bool('verbosity').get(self.element, 0)
-        compression = attr.DefaultBool('compression').get(self.element, 0)
-
-        # Create a temporary output file, so that post-processors can massage
-        # the output
-        tempOutput = cStringIO.StringIO()
-
-        canvas = reportlab.pdfgen.canvas.Canvas(
-            tempOutput,
-            pageCompression=compression,
-            verbosity=verbosity)
-        self.processSubElements(canvas)
-        canvas.save()
-
-        # Process all post processors
-        for name, processor in self.postProcessors:
-            tempOutput.seek(0)
-            tempOutput = processor.process(tempOutput)
-
-        # Save the result into our real output file
-        tempOutput.seek(0)
-        outputFile.write(tempOutput.getvalue())
+class PageInfo(CanvasRMLDirective):
+    signature=IPageInfo
+    callable = 'setPageSize'
+    attrMapping = {'pageSize': 'size'}

Modified: z3c.rml/trunk/src/z3c/rml/chart.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/chart.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/chart.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -20,62 +20,94 @@
 from reportlab.graphics import shapes
 from reportlab.graphics.charts import barcharts, lineplots, piecharts
 from reportlab.graphics.charts import spider, doughnut
-from z3c.rml import attr, element
+from z3c.rml import attrng, directive, interfaces, occurence
 
 # Patches against Reportlab 2.0
 lineplots.Formatter = reportlab.lib.formatters.Formatter
 
 
-class PropertyItem(element.Element):
-    attrs = None
+class PropertyItem(directive.RMLDirective):
 
     def process(self):
-        attrs = element.extractAttributes(self.attrs, self.element, self)
-        self.context.append(attrs)
+        attrs = dict(self.getAttributeValues())
+        self.parent.dataList.append(attrs)
 
 
-class PropertyCollection(element.ContainerElement):
+class PropertyCollection(directive.RMLDirective):
     propertyName = None
-    attrs = None
-    subElements = None
 
     def processAttributes(self):
-        prop = getattr(self.context, self.propertyName)
+        prop = getattr(self.parent.context, self.propertyName)
         # Get global properties
-        attrs = element.extractAttributes(self.attrs, self.element, self)
-        for name, value in attrs.items():
+        for name, value in self.getAttributeValues():
             setattr(prop, name, value)
 
     def process(self):
         self.processAttributes()
         # Get item specific properties
-        prop = getattr(self.context, self.propertyName)
-        dataList = []
-        self.processSubElements(dataList)
-        for index, data in enumerate(dataList):
+        prop = getattr(self.parent.context, self.propertyName)
+        self.dataList = []
+        self.processSubDirectives()
+        for index, data in enumerate(self.dataList):
             for name, value in data.items():
                 setattr(prop[index], name, value)
 
 
-class Text(element.Element):
-    attrs = (
-        attr.Measurement('x'),
-        attr.Measurement('y'),
-        attr.Float('angle', 0),
-        attr.TextNode(),
-        attr.Measurement('fontSize'),
-        attr.Color('fillColor'),
-        attr.Text('fontName'),
-        attr.Choice(
-            'textAnchor',
-            ('start','middle','end','boxauto')),
-        )
+class IText(interfaces.IRMLDirectiveSignature):
+    """Draw a text on the chart."""
 
+    x = attrng.Measurement(
+        title=u'X-Coordinate',
+        description=(u'The X-coordinate of the lower-left position of the '
+                     u'text.'),
+        required=True)
+
+    y = attrng.Measurement(
+        title=u'Y-Coordinate',
+        description=(u'The Y-coordinate of the lower-left position of the '
+                     u'text.'),
+        required=True)
+
+    angle = attrng.Float(
+        title=u'Rotation Angle',
+        description=(u'The angle about which the text will be rotated.'),
+        required=False)
+
+    text = attrng.TextNode(
+        title=u'Text',
+        description=u'The text to be printed.',
+        required=True)
+
+    fontName = attrng.String(
+        title=u'Font Name',
+        description=u'The name of the font.',
+        required=False)
+
+    fontSize = attrng.Measurement(
+        title=u'Font Size',
+        description=u'The font size for the text.',
+        required=False)
+
+    fillColor = attrng.Color(
+        title=u'Fill Color',
+        description=u'The color in which the text will appear.',
+        required=False)
+
+    textAnchor = attrng.Choice(
+        title=u'Text Anchor',
+        description=u'The position in the text to which the coordinates refer.',
+        choices=('start', 'middle', 'end', 'boxauto'),
+        required=False)
+
+
+class Text(directive.RMLDirective):
+    signature = IText
+
     def process(self):
-        attrs = element.extractAttributes(self.attrs, self.element, self)
+        attrs = dict(self.getAttributeValues())
         string = shapes.String(
-            attrs.pop('x'), attrs.pop('y'), attrs.pop('TEXT'))
-        angle = attrs.pop('angle')
+            attrs.pop('x'), attrs.pop('y'), attrs.pop('text'))
+        angle = attrs.pop('angle', 0)
         for name, value in attrs.items():
             setattr(string, name, value)
         group = shapes.Group(string)
@@ -84,29 +116,45 @@
         self.parent.parent.drawing.add(group)
 
 
-class Texts(element.ContainerElement):
-    subElements = {'text': Text}
+class ITexts(interfaces.IRMLDirectiveSignature):
+    """A set of texts drawn on the chart."""
+    occurence.containing(
+        occurence.ZeroOrMore('text', IText)
+        )
 
+class Texts(directive.RMLDirective):
+    signature = ITexts
+    factories = {'text': Text}
 
-class Series(element.Element):
-    attrList = None
 
+class Series(directive.RMLDirective):
+
     def process(self):
-        attrs = element.extractPositionalArguments(
-            self.attrList, self.element, self)
-        self.context.append(attrs[0])
+        attrs = self.getAttributeValues(valuesOnly=True)
+        self.parent.data.append(attrs[0])
 
-class Data(element.ContainerElement):
+
+class Data(directive.RMLDirective):
     series = None
 
     def process(self):
-        data = []
-        self.subElements = {'series': self.series}
-        self.processSubElements(data)
-        self.context.data = data
+        self.data = []
+        self.factories = {'series': self.series}
+        self.processSubDirectives()
+        self.parent.context.data = self.data
 
+
+class ISeries1D(interfaces.IRMLDirectiveSignature):
+    """A one-dimensional series."""
+
+    values = attrng.TextNodeSequence(
+        title=u'Values',
+        description=u"Numerical values representing the series' data.",
+        value_type=attrng.Float(),
+        required=True)
+
 class Series1D(Series):
-    attrList = (attr.TextNodeSequence(attr.Float()),)
+    signature = ISeries1D
 
 class Data1D(Data):
     series = Series1D
@@ -114,414 +162,852 @@
 class SingleData1D(Data1D):
 
     def process(self):
-        data = []
-        self.subElements = {'series': self.series}
-        self.processSubElements(data)
-        self.context.data = data[0]
+        self.data = []
+        self.factories = {'series': self.series}
+        self.processSubDirectives()
+        self.parent.context.data = self.data[0]
 
+
+class ISeries2D(interfaces.IRMLDirectiveSignature):
+    """A two-dimensional series."""
+
+    values = attrng.TextNodeGrid(
+        title=u'Values',
+        description=u"Numerical values representing the series' data.",
+        value_type=attrng.Float(),
+        columns=2,
+        required=True)
+
 class Series2D(Series):
-    attrList = (attr.TextNodeGrid(attr.Float(), 2),)
+    signature = ISeries2D
 
 class Data2D(Data):
     series = Series2D
 
 
+class IBar(interfaces.IRMLDirectiveSignature):
+    """Define the look of a bar."""
+
+    strokeColor = attrng.Color(
+        title=u'Stroke Color',
+        description=u'The color in which the bar border is drawn.',
+        required=False)
+
+    strokeWidth = attrng.Measurement(
+        title=u'Stroke Width',
+        description=u'The width of the bar border line.',
+        required=False)
+
+    fillColor = attrng.Color(
+        title=u'Fill Color',
+        description=u'The color with which the bar is filled.',
+        required=False)
+
 class Bar(PropertyItem):
-    attrs = (
-        attr.Color('strokeColor'),
-        attr.Measurement('strokeWidth'),
-        attr.Color('fillColor') )
+    signature = IBar
 
+class IBars(IBar):
+    """Collection of bar subscriptions."""
+    occurence.containing(
+        occurence.ZeroOrMore('bar', IBar)
+        )
 
 class Bars(PropertyCollection):
+    signature = IBars
     propertyName = 'bars'
-    attrs = Bar.attrs
-    subElements = {'bar': Bar}
+    factories = {'bar': Bar}
 
 
+class ILabelBase(interfaces.IRMLDirectiveSignature):
+
+    dx = attrng.Measurement(
+        title=u'Horizontal Extension',
+        description=(u'The width of the label.'),
+        required=False)
+
+    dy = attrng.Measurement(
+        title=u'Vertical Extension',
+        description=(u'The height of the label.'),
+        required=False)
+
+    angle = attrng.Float(
+        title=u'Angle',
+        description=(u'The angle to rotate the label.'),
+        required=False)
+
+    boxAnchor = attrng.Choice(
+        title=u'Box Anchor',
+        description=(u'The position relative to the label.'),
+        choices=('nw','n','ne','w','c','e','sw','s','se', 'autox', 'autoy'),
+        required=False)
+
+    boxStrokeColor = attrng.Color(
+        title=u'Box Stroke Color',
+        description=(u'The color of the box border line.'),
+        required=False)
+
+    boxStrokeWidth = attrng.Measurement(
+        title=u'Box Stroke Width',
+        description=u'The width of the box border line.',
+        required=False)
+
+    boxFillColor = attrng.Color(
+        title=u'Box Fill Color',
+        description=(u'The color in which the box is filled.'),
+        required=False)
+
+    boxTarget = attrng.Text(
+        title=u'Box Target',
+        description=u'The box target.',
+        required=False)
+
+    fillColor = attrng.Color(
+        title=u'Fill Color',
+        description=(u'The color in which the label is filled.'),
+        required=False)
+
+    strokeColor = attrng.Color(
+        title=u'Stroke Color',
+        description=(u'The color of the label.'),
+        required=False)
+
+    strokeWidth = attrng.Measurement(
+        title=u'Stroke Width',
+        description=u'The width of the label line.',
+        required=False)
+
+    frontName = attrng.String(
+        title=u'Font Name',
+        description=u'The font used to print the value.',
+        required=False)
+
+    frontSize = attrng.Measurement(
+        title=u'Font Size',
+        description=u'The size of the value text.',
+        required=False)
+
+    leading = attrng.Measurement(
+        title=u'Leading',
+        description=(u'The height of a single text line. It includes '
+                     u'character height.'),
+        required=False)
+
+    width = attrng.Measurement(
+        title=u'Width',
+        description=u'The width the label.',
+        required=False)
+
+    maxWidth = attrng.Measurement(
+        title=u'Maximum Width',
+        description=u'The maximum width the label.',
+        required=False)
+
+    height = attrng.Measurement(
+        title=u'Height',
+        description=u'The height the label.',
+        required=False)
+
+    textAnchor = attrng.Choice(
+        title=u'Text Anchor',
+        description=u'The position in the text to which the coordinates refer.',
+        choices=('start', 'middle', 'end', 'boxauto'),
+        required=False)
+
+    visible = attrng.Boolean(
+        title=u'Visible',
+        description=u'A flag making the label text visible.',
+        required=False)
+
+    leftPadding = attrng.Measurement(
+        title=u'Left Padding',
+        description=u'The size of the padding on the left side.',
+        required=False)
+
+    rightPadding = attrng.Measurement(
+        title=u'Right Padding',
+        description=u'The size of the padding on the right side.',
+        required=False)
+
+    topPadding = attrng.Measurement(
+        title=u'Top Padding',
+        description=u'The size of the padding on the top.',
+        required=False)
+
+    bottomPadding = attrng.Measurement(
+        title=u'Bottom Padding',
+        description=u'The size of the padding on the bottom.',
+        required=False)
+
+
+class IPositionLabelBase(ILabelBase):
+
+    x = attrng.Measurement(
+        title=u'X-Coordinate',
+        description=(u'The X-coordinate of the lower-left position of the '
+                     u'label.'),
+        required=False)
+
+    y = attrng.Measurement(
+        title=u'Y-Coordinate',
+        description=(u'The Y-coordinate of the lower-left position of the '
+                     u'label.'),
+        required=False)
+
+
+class ILabel(IPositionLabelBase):
+    """A label for the chart."""
+
+    text = attrng.TextNode(
+        title=u'Text',
+        description=u'The label text to be displayed.',
+        required=True)
+
 class Label(PropertyItem):
-    attrs = (
-        attr.Measurement('x'),
-        attr.Measurement('y'),
-        attr.Measurement('dx'),
-        attr.Measurement('dy'),
-        attr.Float('angle'),
-        attr.Choice(
-            'boxAnchor',
-            ('nw','n','ne','w','c','e','sw','s','se', 'autox', 'autoy')),
-        attr.Color('boxStrokeColor'),
-        attr.Measurement('boxStrokeWidth'),
-        attr.Color('boxFillColor'),
-        attr.Text('boxTarget'),
-        attr.Color('fillColor'),
-        attr.Color('strokeColor'),
-        attr.Measurement('strokeWidth'),
-        attr.Text('fontName'),
-        attr.Measurement('fontSize'),
-        attr.Measurement('leading'),
-        attr.Measurement('width'),
-        attr.Measurement('maxWidth'),
-        attr.Measurement('height'),
-        attr.Choice('textAnchor', ('start','middle','end','boxauto')),
-        attr.Bool('visible'),
-        attr.Measurement('topPadding'),
-        attr.Measurement('leftPadding'),
-        attr.Measurement('rightPadding'),
-        attr.Measurement('bottomPadding'),
-        attr.TextNode()
+    signature = ILabel
+
+
+class ILabels(IPositionLabelBase):
+    """A set of labels."""
+    occurence.containing(
+        occurence.ZeroOrMore('label', ILabel)
         )
-    attrs[-1].name = 'text'
 
-
 class Labels(PropertyCollection):
+    signature = ILabels
     propertyName = 'labels'
-    attrs = Label.attrs[:-1]
-    subElements = {'label': Label}
+    factories = {'label': Label}
 
 
-class Axis(element.ContainerElement):
-    name = ''
-    attrs = (
-        attr.Bool('visible'),
-        attr.Bool('visibleAxis'),
-        attr.Bool('visibleTicks'),
-        attr.Bool('visibleLabels'),
-        attr.Bool('visibleGrid'),
-        attr.Measurement('strokeWidth'),
-        attr.Color('strokeColor'),
-        attr.Sequence('strokeDashArray', attr.Float()),
-        attr.Measurement('gridStrokeWidth'),
-        attr.Color('gridStrokeColor'),
-        attr.Sequence('gridStrokeDashArray', attr.Float()),
-        attr.Measurement('gridStart'),
-        attr.Measurement('gridEnd'),
-        attr.Choice('style', ('parallel', 'stacked', 'parallel_3d')),
+class IAxis(interfaces.IRMLDirectiveSignature):
+    occurence.containing(
+        occurence.ZeroOrMore('labels', ILabels)
         )
 
-    subElements = {'labels': Labels}
+    visible = attrng.Boolean(
+        required=False)
 
+    visibleAxis = attrng.Boolean(
+        required=False)
+
+    visibleTicks = attrng.Boolean(
+        required=False)
+
+    visibleLabels = attrng.Boolean(
+        required=False)
+
+    visibleGrid = attrng.Boolean(
+        required=False)
+
+    strokeWidth = attrng.Measurement(
+        required=False)
+
+    strokeColor = attrng.Color(
+        required=False)
+
+    strokeDashArray = attrng.Sequence(
+        value_type=attrng.Float(),
+        required=False)
+
+    gridStrokeWidth = attrng.Measurement(
+        required=False)
+
+    gridStrokeColor = attrng.Color(
+        required=False)
+
+    gridStrokeDashArray = attrng.Sequence(
+        value_type=attrng.Float(),
+        required=False)
+
+    gridStart = attrng.Measurement(
+        required=False)
+
+    gridEnd = attrng.Measurement(
+        required=False)
+
+    style = attrng.Choice(
+        choices=('parallel', 'stacked', 'parallel_3d'),
+        required=False)
+
+
+class Axis(directive.RMLDirective):
+    signature = IAxis
+    name = ''
+    factories = {'labels': Labels}
+
     def process(self):
-        attrs = element.extractAttributes(self.attrs, self.element, self)
-        axis = getattr(self.context, self.__name__)
-        for name, value in attrs.items():
+        self.context = axis = getattr(self.parent.context, self.name)
+        for name, value in self.getAttributeValues():
             setattr(axis, name, value)
-        self.processSubElements(axis)
+        self.processSubDirectives()
 
 
-class Name(element.Element):
-    attrs = (attr.TextNode(),)
+class IName(interfaces.IRMLDirectiveSignature):
 
+    text = attrng.TextNode(
+        title=u'Text',
+        required=True)
+
+class Name(directive.RMLDirective):
+    signature = IName
+
     def process(self):
-        attrs = element.extractAttributes(self.attrs, self.element, self)
-        self.context.append(attrs['TEXT'])
+        text = self.getAttributeValues(valuesOnly=True)[0]
+        self.parent.names.append(text)
 
 
-class CategoryNames(element.ContainerElement):
-    subElements = {'name': Name}
+class CategoryNames(directive.RMLDirective):
+    factories = {'name': Name}
 
     def process(self):
-        self.context.categoryNames = []
-        self.processSubElements(self.context.categoryNames)
+        self.names = []
+        self.processSubDirectives()
+        self.parent.context.categoryNames = self.names
 
+
+class ICategoryAxis(IAxis):
+
+    categoryNames = attrng.Sequence(
+        value_type=attrng.Text(),
+        required=False)
+
+    joinAxis = attrng.Boolean(
+        required=False)
+
+    joinAxisPos = attrng.Measurement(
+        required=False)
+
+    reverseDirection = attrng.Boolean(
+        required=False)
+
+    labelAxisMode = attrng.Choice(
+        choices=('high', 'low', 'axis'),
+        required=False)
+
+    tickShift = attrng.Boolean(
+        required=False)
+
 class CategoryAxis(Axis):
+    signature = ICategoryAxis
     name = 'categoryAxis'
-    attrs = Axis.attrs + (
-        attr.Sequence('categoryNames', attr.Text()),
-        attr.Bool('joinAxis'),
-        attr.Measurement('joinAxisPos'),
-        attr.Bool('reverseDirection'),
-        attr.Choice('labelAxisMode', ('high', 'low', 'axis')),
-        attr.Bool('tickShift'),
-        )
-    subElements = Axis.subElements.copy()
-    subElements.update({
+    factories = Axis.factories.copy()
+    factories.update({
         'categoryNames': CategoryNames,
         })
 
+
+class IXCategoryAxis(ICategoryAxis):
+
+    tickUp = attrng.Measurement(
+        required=False)
+
+    tickDown = attrng.Measurement(
+        required=False)
+
+    joinAxisMode = attrng.Choice(
+        choices=('bottom', 'top', 'value', 'points', 'None'),
+        required=False)
+
 class XCategoryAxis(CategoryAxis):
-    attrs = CategoryAxis.attrs + (
-        attr.Measurement('tickUp'),
-        attr.Measurement('tickDown'),
-        attr.Choice('joinAxisMode',
-                    ('bottom', 'top', 'value', 'points', 'None')) )
+    signature = IXCategoryAxis
 
 
+class IYCategoryAxis(ICategoryAxis):
+
+    tickLeft = attrng.Measurement(
+        required=False)
+
+    tickRight = attrng.Measurement(
+        required=False)
+
+    joinAxisMode = attrng.Choice(
+        choices=('bottom', 'top', 'value', 'points', 'None'),
+        required=False)
+
 class YCategoryAxis(CategoryAxis):
-    attrs = CategoryAxis.attrs + (
-        attr.Measurement('tickLeft'),
-        attr.Measurement('tickRight'),
-        attr.Choice('joinAxisMode',
-                    ('bottom', 'top', 'value', 'points', 'None')) )
+    signature = IYCategoryAxis
 
 
+class IValueAxis(IAxis):
+
+    forceZero = attrng.Boolean(
+        required=False)
+
+    minimumTickSpacing = attrng.Measurement(
+        required=False)
+
+    maximumTicks = attrng.Integer(
+        required=False)
+
+    labelTextFormat = attrng.String(
+        required=False)
+
+    labelTextPostFormat = attrng.Text(
+        required=False)
+
+    labelTextScale = attrng.Float(
+        required=False)
+
+    valueMin = attrng.Float(
+        required=False)
+
+    valueMax = attrng.Float(
+        required=False)
+
+    valueStep = attrng.Float(
+        required=False)
+
+    valueSteps = attrng.Measurement(
+        required=False)
+
+    rangeRound = attrng.Text(
+        required=False)
+
+    zrangePref = attrng.Float(
+        required=False)
+
 class ValueAxis(Axis):
+    signature = IValueAxis
     name = 'valueAxis'
-    attrs = Axis.attrs + (
-        attr.Bool('forceZero'), # TODO: Support 'near'
-        attr.Measurement('minimumTickSpacing'),
-        attr.Int('maximumTicks'),
-        attr.Attribute('labelTextFormat'),
-        attr.Text('labelTextPostFormat'),
-        attr.Float('labelTextScale'),
-        attr.Float('valueMin'),
-        attr.Float('valueMax'),
-        attr.Float('valueStep'),
-        attr.Measurement('valueSteps'),
-        attr.Text('rangeRound'),
-        attr.Float('zrangePref'),
-        )
 
 
+class IXValueAxis(IValueAxis):
+
+    tickUp = attrng.Measurement(
+        required=False)
+
+    tickDown = attrng.Measurement(
+        required=False)
+
+    joinAxis = attrng.Boolean(
+        required=False)
+
+    joinAxisMode = attrng.Choice(
+        choices=('bottom', 'top', 'value', 'points', 'None'),
+        required=False)
+
+    joinAxisPos = attrng.Measurement(
+        required=False)
+
 class XValueAxis(ValueAxis):
-    attrs = ValueAxis.attrs + (
-        attr.Measurement('tickUp'),
-        attr.Measurement('tickDown'),
-        attr.Bool('joinAxis'),
-        attr.Choice('joinAxisMode',
-                    ('bottom', 'top', 'value', 'points', 'None')),
-        attr.Float('joinAxisPos'),
-        )
+    signature = IXValueAxis
 
+class LineXValueAxis(XValueAxis):
+    name = 'xValueAxis'
+
+class IYValueAxis(IValueAxis):
+
+    tickLeft = attrng.Measurement(
+        required=False)
+
+    tickRight = attrng.Measurement(
+        required=False)
+
+    joinAxis = attrng.Boolean(
+        required=False)
+
+    joinAxisMode = attrng.Choice(
+        choices=('bottom', 'top', 'value', 'points', 'None'),
+        required=False)
+
+    joinAxisPos = attrng.Measurement(
+        required=False)
+
 class YValueAxis(ValueAxis):
-    attrs = ValueAxis.attrs + (
-        attr.Measurement('tickLeft'),
-        attr.Measurement('tickRight'),
-        attr.Bool('joinAxis'),
-        attr.Choice('joinAxisMode',
-                    ('bottom', 'top', 'value', 'points', 'None')),
-        attr.Float('joinAxisPos'),
-        )
+    signature = IYValueAxis
 
+class LineYValueAxis(YValueAxis):
+    name = 'yValueAxis'
 
+
+class ILineBase(interfaces.IRMLDirectiveSignature):
+
+    strokeWidth = attrng.Measurement(
+        required=False)
+
+    strokeColor = attrng.Color(
+        required=False)
+
+    strokeDashArray = attrng.Sequence(
+        value_type = attrng.Float(),
+        required=False)
+
+    symbol = attrng.Symbol(
+        required=False)
+
+class ILine(ILineBase):
+
+    name = attrng.Text(
+        required=False)
+
 class Line(PropertyItem):
-    attrs = (
-        attr.Measurement('strokeWidth'),
-        attr.Color('strokeColor'),
-        attr.Sequence('strokeDashArray', attr.Float()),
-        attr.Symbol('symbol'),
-        attr.Text('name'),
-        )
+    signature = ILine
 
+class ILines(ILineBase):
+    pass
 
 class Lines(PropertyCollection):
+    signature = ILines
     propertyName = 'lines'
-    attrs = Line.attrs[:-1]
-    subElements = {'line': Line}
+    factories = {'line': Line}
 
 
+class ISliceLabel(ILabelBase):
+
+    text = attrng.TextNode(
+        title=u'Text',
+        description=u'The label text to be displayed.',
+        required=True)
+
 class SliceLabel(Label):
-    attrs = Label.attrs[2:]
+    signature = ISliceLabel
 
     def process(self):
-        attrs = element.extractAttributes(self.attrs, self.element, self)
-        for name, value in attrs.items():
-            self.context['label_'+name] = value
+        for name, value in self.getAttributeValues():
+            self.parent.context['label_'+name] = value
         # Now we do not have simple labels anymore
-        self.parent.parent.context.simpleLabels = False
+        self.parent.parent.parent.context.simpleLabels = False
 
-class SlicePointer(element.Element):
-    attrs = (
-        attr.Color('strokeColor'),
-        attr.Measurement('strokeWidth'),
-        attr.Measurement('elbowLength'),
-        attr.Measurement('edgePad'),
-        attr.Measurement('piePad'),
-        )
 
+class ISlicePointer(interfaces.IRMLDirectiveSignature):
+
+    strokeColor = attrng.Color(
+        required=False)
+
+    strokeWidth = attrng.Measurement(
+        required=False)
+
+    elbowLength = attrng.Measurement(
+        required=False)
+
+    edgePad = attrng.Measurement(
+        required=False)
+
+    piePad = attrng.Measurement(
+        required=False)
+
+class SlicePointer(directive.RMLDirective):
+    signature = ISlicePointer
+
     def process(self):
-        attrs = element.extractAttributes(self.attrs, self.element, self)
-        for name, value in attrs.items():
-            self.context['label_pointer_'+name] = value
+        for name, value in self.getAttributeValues():
+            self.parent.context['label_pointer_'+name] = value
 
-class Slice(element.ContainerElement):
-    attrs = (
-        attr.Measurement('strokeWidth'),
-        attr.Color('fillColor'),
-        attr.Color('strokeColor'),
-        attr.Sequence('strokeDashArray', attr.Float()),
-        attr.Measurement('popout'),
-        attr.Text('fontName'),
-        attr.Measurement('fontSize'),
-        attr.Measurement('labelRadius'),
-        attr.Symbol('swatchMarker'),
-        )
 
-    subElements = {
+class ISliceBase(interfaces.IRMLDirectiveSignature):
+
+    strokeWidth = attrng.Measurement(
+        required=False)
+
+    fillColor = attrng.Color(
+        required=False)
+
+    strokeColor = attrng.Color(
+        required=False)
+
+    strokeDashArray = attrng.Sequence(
+        value_type=attrng.Float(),
+        required=False)
+
+    popout = attrng.Measurement(
+        required=False)
+
+    fontName = attrng.String(
+        required=False)
+
+    fontSize = attrng.Measurement(
+        required=False)
+
+    labelRadius = attrng.Measurement(
+        required=False)
+
+class ISlice(ISliceBase):
+
+    swatchMarker = attrng.Symbol(
+        required=False)
+
+
+class Slice(directive.RMLDirective):
+    signature = ISlice
+    factories = {
         'label': SliceLabel,
         'pointer': SlicePointer}
 
     def process(self):
-        attrs = element.extractAttributes(self.attrs, self.element, self)
-        self.processSubElements(attrs)
-        self.context.append(attrs)
+        self.context = attrs = dict(self.getAttributeValues())
+        self.processSubDirectives()
+        self.parent.context.append(attrs)
 
+
+class ISlice3D(ISlice):
+
+    fillColorShaded = attrng.Color(
+        required=False)
+
 class Slice3D(Slice):
-    attrs = Slice.attrs + (
-        attr.Color('fillColorShaded'),
-        )
-
+    signature = ISlice3D
     subElements = {}
     # Sigh, the 3-D Pie does not support advanced slice labels. :-(
     #     'label': SliceLabel}
 
-class Slices(element.ContainerElement):
-    attrs = Slice.attrs[:-1]
-    subElements = {'slice': Slice}
 
+class ISlices(ISliceBase):
+    pass
+
+class Slices(directive.RMLDirective):
+    signature = ISlices
+    factories = {'slice': Slice}
+
     def process(self):
         # Get global slice properties
-        attrs = element.extractAttributes(self.attrs, self.element, self)
-        for name, value in attrs.items():
-            setattr(self.context.slices, name, value)
+        for name, value in self.getAttributeValues():
+            setattr(self.parent.context.slices, name, value)
         # Get slice specific properties
-        slicesData = []
-        self.processSubElements(slicesData)
+        self.context = slicesData = []
+        self.processSubDirectives()
         for index, sliceData in enumerate(slicesData):
             for name, value in sliceData.items():
-                setattr(self.context.slices[index], name, value)
+                setattr(self.parent.context.slices[index], name, value)
 
+
+class ISlices3D(ISliceBase):
+
+    fillColorShaded = attrng.Color(
+        required=False)
+
 class Slices3D(Slices):
-    attrs = Slice3D.attrs[:-1]
-    subElements = {'slice': Slice3D}
+    signature = ISlices3D
+    factories = {'slice': Slice3D}
 
 
-class SimpleLabels(element.ContainerElement):
-    subElements = {'label': Name}
+class SimpleLabels(directive.RMLDirective):
+    factories = {'label': Name}
 
     def process(self):
-        self.context.labels = []
-        self.processSubElements(self.context.labels)
+        self.names = []
+        self.processSubDirectives()
+        self.parent.context.labels = self.names
 
 
+class IStrandBase(interfaces.IRMLDirectiveSignature):
+
+    strokeWidth = attrng.Measurement(
+        required=False)
+
+    fillColor = attrng.Color(
+        required=False)
+
+    strokeColor= attrng.Color(
+        required=False)
+
+    strokeDashArray = attrng.Sequence(
+        value_type=attrng.Float(),
+        required=False)
+
+    symbol = attrng.Symbol(
+        required=False)
+
+    symbolSize = attrng.Measurement(
+        required=False)
+
+class IStrand(IStrandBase):
+
+     name = attrng.Text(
+        required=False)
+
 class Strand(PropertyItem):
-    attrs = (
-        attr.Measurement('strokeWidth'),
-        attr.Color('fillColor'),
-        attr.Color('strokeColor'),
-        attr.Sequence('strokeDashArray', attr.Float()),
-        attr.Symbol('symbol'),
-        attr.Measurement('symbolSize'),
-        attr.Text('name'),
-        )
+    signature = IStrand
 
-
 class Strands(PropertyCollection):
+    signature = IStrandBase
     propertyName = 'strands'
-    attrs = Strand.attrs[:-1]
-    subElements = {'strand': Strand}
+    attrs = IStrandBase
+    factories = {'strand': Strand}
 
 
+class IStrandLabelBase(ILabelBase):
+
+    _text = attrng.TextNode(
+        required=False)
+
+    row = attrng.Integer(
+        required=False)
+
+    col = attrng.Integer(
+        required=False)
+
+    format = attrng.String(
+        required=False)
+
+class IStrandLabel(IStrandLabelBase):
+
+    dR = attrng.Float(
+        required=False)
+
 class StrandLabel(Label):
-    attrs = Label.attrs[2:-1] + (attr.TextNode(),)
-    attrs[-1].name = '_text'
-    attrs += (
-        attr.Int('row'),
-        attr.Int('col'),
-        attr.Attribute('format'),
-        attr.Float('dR')
-        )
+    signature = IStrandLabel
 
 class StrandLabels(PropertyCollection):
+    signature = IStrandLabelBase
     propertyName = 'strandLabels'
-    attrs = StrandLabel.attrs[:-1]
-    subElements = {'label': StrandLabel}
+    factories = {'label': StrandLabel}
 
     def process(self):
         self.processAttributes()
         # Get item specific properties
-        prop = getattr(self.context, self.propertyName)
-        dataList = []
-        self.processSubElements(dataList)
-        for data in dataList:
+        prop = getattr(self.parent.context, self.propertyName)
+        self.dataList = []
+        self.processSubDirectives()
+        for data in self.dataList:
             row = data.pop('row')
             col = data.pop('col')
             for name, value in data.items():
                 setattr(prop[row, col], name, value)
 
 
+class ISpoke(interfaces.IRMLDirectiveSignature):
+
+    strokeWidth = attrng.Measurement(
+        required=False)
+
+    fillColor = attrng.Color(
+        required=False)
+
+    strokeColor= attrng.Color(
+        required=False)
+
+    strokeDashArray = attrng.Sequence(
+        value_type=attrng.Float(),
+        required=False)
+
+    labelRadius = attrng.Measurement(
+        required=False)
+
+    visible = attrng.Measurement(
+        required=False)
+
 class Spoke(PropertyItem):
-    attrs = (
-        attr.Measurement('strokeWidth'),
-        attr.Color('fillColor'),
-        attr.Color('strokeColor'),
-        attr.Sequence('strokeDashArray', attr.Float()),
-        attr.Measurement('labelRadius'),
-        attr.Bool('visible'),
-        )
+    signature = ISpoke
 
-
 class Spokes(PropertyCollection):
+    signature = ISpoke
     propertyName = 'spokes'
-    attrs = Spoke.attrs[:-1]
-    subElements = {'spoke': Spoke}
+    factories = {'spoke': Spoke}
 
 
+class ISpokeLabelBase(ILabelBase):
+    pass
+
+class ISpokeLabel(ISpokeLabelBase):
+
+    _text = attrng.TextNode(
+        required=False)
+
 class SpokeLabel(Label):
-    attrs = Label.attrs[2:-1] + (attr.TextNode(),)
-    attrs[-1].name = '_text'
+    signature = ISpokeLabel
 
 class SpokeLabels(PropertyCollection):
+    signature = ISpokeLabelBase
     propertyName = 'spokeLabels'
-    attrs = SpokeLabel.attrs[:-1]
-    subElements = {'label': SpokeLabel}
+    factories = {'label': SpokeLabel}
 
 
-class Chart(element.ContainerElement):
-    attrs = (
-        # Drawing Options
-        attr.Measurement('dx'),
-        attr.Measurement('dy'),
-        attr.Measurement('dwidth'),
-        attr.Measurement('dheight'),
-        attr.Float('angle'),
-        # Plot Area Options
-        attr.Measurement('x'),
-        attr.Measurement('y'),
-        attr.Measurement('width'),
-        attr.Measurement('height'),
-        attr.Color('strokeColor'),
-        attr.Measurement('strokeWidth'),
-        attr.Color('fillColor'),
-        attr.Bool('debug'),
-        )
+class IChart(interfaces.IRMLDirectiveSignature):
 
-    subElements = {
+    # Drawing Options
+
+    dx = attrng.Measurement(
+        required=False)
+
+    dy = attrng.Measurement(
+        required=False)
+
+    dwidth = attrng.Measurement(
+        required=False)
+
+    dheight = attrng.Measurement(
+        required=False)
+
+    angle = attrng.Float(
+        required=False)
+
+    # Plot Area Options
+
+    x = attrng.Measurement(
+        required=False)
+
+    y = attrng.Measurement(
+        required=False)
+
+    width = attrng.Measurement(
+        required=False)
+
+    height = attrng.Measurement(
+        required=False)
+
+    strokeColor = attrng.Color(
+        required=False)
+
+    strokeWidth = attrng.Measurement(
+        required=False)
+
+    fillColor = attrng.Color(
+        required=False)
+
+    debug = attrng.Boolean(
+        required=False)
+
+class Chart(directive.RMLDirective):
+    signature = IChart
+    factories = {
         'texts': Texts
         }
 
-    def getAttributes(self):
-        attrs = [(attr.name, attr) for attr in self.attrs]
-        return element.extractKeywordArguments(attrs, self.element, self)
-
     def createChart(self, attributes):
         raise NotImplementedError
 
     def process(self):
-        attrs = self.getAttributes()
+        attrs = dict(self.getAttributeValues())
         angle = attrs.pop('angle', 0)
         x, y = attrs.pop('dx'), attrs.pop('dy')
         self.drawing = shapes.Drawing(attrs.pop('dwidth'), attrs.pop('dheight'))
-        chart = self.createChart(attrs)
-        self.processSubElements(chart)
+        self.context = chart = self.createChart(attrs)
+        self.processSubDirectives()
         group = shapes.Group(chart)
         group.translate(0,0)
         group.rotate(angle)
         self.drawing.add(group)
-        self.drawing.drawOn(self.context, x, y)
+        manager = attrng.getManager(self, interfaces.ICanvasManager)
+        self.drawing.drawOn(manager.canvas, x, y)
 
 
+class IBarChart(IChart):
+
+    direction = attrng.Choice(
+        choices=('horizontal', 'vertical'),
+        default='horizontal',
+        required=False)
+
+    useAbsolute = attrng.Boolean(
+        default=False,
+        required=False)
+
+    barWidth = attrng.Measurement(
+        default=10,
+        required=False)
+
+    groupSpacing = attrng.Measurement(
+        default=5,
+        required=False)
+
+    barSpacing = attrng.Measurement(
+        default=0,
+        required=False)
+
 class BarChart(Chart):
+    signature = IBarChart
     nameBase = 'BarChart'
-    attrs = Chart.attrs + (
-        attr.Choice('direction', ('horizontal', 'vertical'), 'horizontal'),
-        attr.Bool('useAbsolute', False),
-        attr.Measurement('barWidth', 10),
-        attr.Measurement('groupSpacing', 5),
-        attr.Measurement('barSpacing', 0),
-        )
-
-    subElements = Chart.subElements.copy()
-    subElements.update({
+    factories = Chart.factories.copy()
+    factories.update({
         'data': Data1D,
         'bars': Bars,
         })
@@ -530,11 +1016,11 @@
         direction = attrs.pop('direction')
         # Setup sub-elements based on direction
         if direction == 'horizontal':
-            self.subElements['categoryAxis'] = YCategoryAxis
-            self.subElements['valueAxis'] = XValueAxis
+            self.factories['categoryAxis'] = YCategoryAxis
+            self.factories['valueAxis'] = XValueAxis
         else:
-            self.subElements['categoryAxis'] = XCategoryAxis
-            self.subElements['valueAxis'] = YValueAxis
+            self.factories['categoryAxis'] = XCategoryAxis
+            self.factories['valueAxis'] = YValueAxis
         # Generate the chart
         chart = getattr(
             barcharts, direction.capitalize()+self.nameBase)()
@@ -543,30 +1029,48 @@
         return chart
 
 
+class IBarChart3D(IBarChart):
+
+    theta_x = attrng.Float(
+        required=False)
+
+    theta_y = attrng.Float(
+        required=False)
+
+    zDepth = attrng.Measurement(
+        required=False)
+
+    zSpace = attrng.Measurement(
+        required=False)
+
 class BarChart3D(BarChart):
+    signature = IBarChart3D
     nameBase = 'BarChart3D'
-    attrs = BarChart.attrs + (
-        attr.Float('theta_x'),
-        attr.Float('theta_y'),
-        attr.Measurement('zDepth'),
-        attr.Measurement('zSpace')
-        )
 
 
+class ILinePlot(IChart):
+
+    reversePlotOrder = attrng.Boolean(
+        required=False)
+
+    lineLabelNudge = attrng.Measurement(
+        required=False)
+
+    lineLabelFormat = attrng.String(
+        required=False)
+
+    joinedLines = attrng.Boolean(
+        required=False)
+
 class LinePlot(Chart):
-    attrs = Chart.attrs + (
-        attr.Bool('reversePlotOrder'),
-        attr.Measurement('lineLabelNudge'),
-        attr.Attribute('lineLabelFormat'),
-        attr.Bool('joinedLines'),
-        )
+    signature = ILinePlot
 
-    subElements = Chart.subElements.copy()
-    subElements.update({
+    factories = Chart.factories.copy()
+    factories.update({
         'data': Data2D,
         'lines': Lines,
-        'xValueAxis': XValueAxis,
-        'yValueAxis': YValueAxis,
+        'xValueAxis': LineXValueAxis,
+        'yValueAxis': LineYValueAxis,
         'lineLabels': Labels,
         })
 
@@ -577,24 +1081,45 @@
             setattr(chart, name, value)
         return chart
 
+
+class IPieChart(IChart):
+
+    startAngle = attrng.Integer(
+        required=False)
+
+    direction = attrng.Choice(
+        choices=('clockwise', 'anticlockwise'),
+        required=False)
+
+    checkLabelOverlap = attrng.Boolean(
+        required=False)
+
+    pointerLabelMode = attrng.Choice(
+        choices={'none': None,
+                 'leftright': 'LeftRight',
+                 'leftandright': 'LeftAndRight'},
+        required=False)
+
+    sameRadii = attrng.Boolean(
+        required=False)
+
+    orderMode = attrng.Choice(
+        choices=('fixed', 'alternate'),
+        required=False)
+
+    xradius = attrng.Measurement(
+        required=False)
+
+    yradius = attrng.Measurement(
+        required=False)
+
+
 class PieChart(Chart):
+    signature = IPieChart
     chartClass = piecharts.Pie
-    attrs = Chart.attrs + (
-        attr.Int('startAngle'),
-        attr.Choice('direction', ('clockwise', 'anticlockwise')),
-        attr.Bool('checkLabelOverlap'),
-        attr.Choice('pointerLabelMode',
-                    {'none': None,
-                     'leftright': 'LeftRight',
-                     'leftandright': 'LeftAndRight'}),
-        attr.Bool('sameRadii'),
-        attr.Choice('orderMode', ('fixed', 'alternate')),
-        attr.Measurement('xradius'),
-        attr.Measurement('yradius'),
-        )
 
-    subElements = Chart.subElements.copy()
-    subElements.update({
+    factories = Chart.factories.copy()
+    factories.update({
         'data': SingleData1D,
         'slices': Slices,
         'labels': SimpleLabels,
@@ -607,28 +1132,41 @@
             setattr(chart, name, value)
         return chart
 
+
+class IPieChart3D(IPieChart):
+
+    perspective = attrng.Float(
+        required=False)
+
+    depth_3d = attrng.Measurement(
+        required=False)
+
+    angle_3d = attrng.Float(
+        required=False)
+
 class PieChart3D(PieChart):
+    signature = IPieChart3D
     chartClass = piecharts.Pie3d
-    attrs = PieChart.attrs + (
-        attr.Float('perspective'),
-        attr.Measurement('depth_3d'),
-        attr.Float('angle_3d'),
-        )
 
-    subElements = PieChart.subElements.copy()
-    subElements.update({
+    factories = PieChart.factories.copy()
+    factories.update({
         'slices': Slices3D,
         })
 
+
+class ISpiderChart(IChart):
+
+    startAngle = attrng.Integer(
+        required=False)
+
+    direction = attrng.Choice(
+        choices=('clockwise', 'anticlockwise'),
+        required=False)
+
 class SpiderChart(Chart):
-    attrs = Chart.attrs + (
-        attr.Int('startAngle'),
-        attr.Choice('direction', ('clockwise', 'anticlockwise')),
-        attr.Float('startAngle'),
-        )
-
-    subElements = Chart.subElements.copy()
-    subElements.update({
+    signature = ISpiderChart
+    factories = Chart.factories.copy()
+    factories.update({
         'data': Data1D,
         'strands': Strands,
         'strandLabels': StrandLabels,

Added: z3c.rml/trunk/src/z3c/rml/directive.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/directive.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/directive.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -0,0 +1,91 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation 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.
+#
+##############################################################################
+"""RML Directive Implementation
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import logging
+import zope.interface
+import zope.schema
+
+from z3c.rml import interfaces
+
+logger = logging.getLogger("z3c.rml")
+
+
+class RMLDirective(object):
+    zope.interface.implements(interfaces.IRMLDirective)
+    signature = None
+    factories = {}
+
+    def __init__(self, element, parent):
+        self.element = element
+        self.parent = parent
+
+    def getAttributeValues(self, ignore=None, select=None, attrMapping=None,
+                           includeMissing=False, valuesOnly=False):
+        """See interfaces.IRMLDirective"""
+        items = []
+        for name, attr in zope.schema.getFieldsInOrder(self.signature):
+            # Only add the attribute to the list, if it is supposed there
+            if ((ignore is None or name not in ignore) and
+                (select is None or name in select)):
+                # Get the value.
+                value = attr.bind(self).get()
+                # If no value was found for a required field, raise a value
+                # error
+                if attr.required and value is attr.missing_value:
+                    raise ValueError(
+                        'No value for required attribute %s' %name)
+                # Only add the entry if the value is not the missing value or
+                # missing values are requested to be included.
+                if value is not attr.missing_value or includeMissing:
+                    items.append((name, value))
+
+        # Sort the items based on the section
+        if select is not None:
+            select = list(select)
+            items = sorted(items, key=lambda (n, v): select.index(n))
+
+        # If the attribute name does not match the internal API
+        # name, then convert the name to the internal one
+        if attrMapping:
+            items = [(attrMapping.get(name, name), value)
+                     for name, value in items]
+
+        # Sometimes we only want the values without the names
+        if valuesOnly:
+            return [value for name, value in items]
+
+        return items
+
+    def processSubDirectives(self, select=None, ignore=None):
+        # Go through all children of the directive and try to process them.
+        for element in self.element.getchildren():
+            if select is not None and element.tag not in select:
+                continue
+            if ignore is not None and element.tag in ignore:
+                continue
+            # If the element is a directive, process it
+            if element.tag in self.factories:
+                directive = self.factories[element.tag](element, self)
+                directive.process()
+            else:
+                # Record any tags/elements that could not be processed.
+                logger.warn("Directive %r could not be processed and was "
+                            "ignored." %element.tag)
+
+    def process(self):
+        self.processSubDirectives()


Property changes on: z3c.rml/trunk/src/z3c/rml/directive.py
___________________________________________________________________
Name: svn:keywords
   + Id

Modified: z3c.rml/trunk/src/z3c/rml/document.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/document.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/document.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -16,67 +16,143 @@
 $Id$
 """
 __docformat__ = "reStructuredText"
+import cStringIO
 import sys
 import zope.interface
+import reportlab.pdfgen.canvas
 from reportlab.pdfbase import pdfmetrics, ttfonts, cidfonts
-from z3c.rml import attr, element, error, interfaces
+from z3c.rml import attrng, directive, interfaces, occurence
 from z3c.rml import canvas, stylesheet, template
 
 
-class RegisterType1Face(element.Element):
-    args = ( attr.Attribute('afmFile'), attr.Attribute('pfbFile') )
+class IRegisterType1Face(interfaces.IRMLDirectiveSignature):
+    """Register a new Type 1 font face."""
 
+    afmFile = attrng.String(
+        title=u'AFM File',
+        description=u'Path to AFM file used to register the Type 1 face.',
+        required=True)
+
+    pfbFile = attrng.String(
+        title=u'PFB File',
+        description=u'Path to PFB file used to register the Type 1 face.',
+        required=True)
+
+class RegisterType1Face(directive.RMLDirective):
+    signature = IRegisterType1Face
+
     def process(self):
-        args = element.extractPositionalArguments(self.args, self.element, self)
+        args = self.getAttributeValues(valuesOnly=True)
         face = pdfmetrics.EmbeddedType1Face(*args)
         pdfmetrics.registerTypeFace(face)
 
 
-class RegisterFont(element.Element):
-    args = (
-        attr.Attribute('name'),
-        attr.Attribute('faceName'),
-        attr.Attribute('encName') )
+class IRegisterFont(interfaces.IRMLDirectiveSignature):
+    """Register a new font based on a face and encoding."""
 
+    name = attrng.String(
+        title=u'Name',
+        description=(u'The name under which the font can be used in style '
+                     u'declarations or other parameters that lookup a font.'),
+        required=True)
+
+    faceName = attrng.String(
+        title=u'Face Name',
+        description=(u'The name of the face the font uses. The face has to '
+                     u'be previously registered.'),
+        required=True)
+
+    encName = attrng.String(
+        title=u'Encoding Name',
+        description=(u'The name of the encdoing to be used.'),
+        required=True)
+
+class RegisterFont(directive.RMLDirective):
+    signature = IRegisterFont
+
     def process(self):
-        args = element.extractPositionalArguments(self.args, self.element, self)
+        args = self.getAttributeValues(valuesOnly=True)
         font = pdfmetrics.Font(*args)
         pdfmetrics.registerFont(font)
 
 
-class RegisterTTFont(element.Element):
-    args = (
-        attr.Attribute('faceName'),
-        attr.Attribute('fileName') )
+class IRegisterTTFont(interfaces.IRMLDirectiveSignature):
+    """Register a new TrueType font given the TT file and face name."""
 
+    faceName = attrng.String(
+        title=u'Face Name',
+        description=(u'The name of the face the font uses. The face has to '
+                     u'be previously registered.'),
+        required=True)
+
+    fileName = attrng.String(
+        title=u'File Name',
+        description=u'File path of the of the TrueType font.',
+        required=True)
+
+class RegisterTTFont(directive.RMLDirective):
+    signature = IRegisterTTFont
+
     def process(self):
-        args = element.extractPositionalArguments(self.args, self.element, self)
+        args = self.getAttributeValues(valuesOnly=True)
         font = ttfonts.TTFont(*args)
         pdfmetrics.registerFont(font)
 
 
-class RegisterCidFont(element.Element):
-    args = ( attr.Attribute('faceName'), )
+class IRegisterCidFont(interfaces.IRMLDirectiveSignature):
+    """Register a new CID font given the face name."""
 
+    faceName = attrng.String(
+        title=u'Face Name',
+        description=(u'The name of the face the font uses. The face has to '
+                     u'be previously registered.'),
+        required=True)
+
+class RegisterCidFont(directive.RMLDirective):
+    signature = IRegisterCidFont
+
     def process(self):
-        args = element.extractPositionalArguments(self.args, self.element, self)
-        pdfmetrics.registerFont(cidfonts.UnicodeCIDFont(*args))
+        args = self.getAttributeValues(valuesOnly=True)
+        font = cidfonts.UnicodeCIDFont(*args)
+        pdfmetrics.registerFont(font)
 
 
-class ColorDefinition(element.FunctionElement):
-    args = (
-        attr.Text('id'),
-        attr.Color('RGB'), )
+class IColorDefinition(interfaces.IRMLDirectiveSignature):
+    """Define a new color and give it a name to be known under."""
 
+    id = attrng.String(
+        title=u'Id',
+        description=(u'The id/name the color will be available under.'),
+        required=True)
+
+    # XXX: This is really disgusting; need to rename to "color"!
+    #      This is only here for compatibility with the original RML.
+    RGB = attrng.Color(
+        title=u'Color',
+        description=(u'The color value that is represented.'),
+        required=True)
+
+class ColorDefinition(directive.RMLDirective):
+    signature = IColorDefinition
+
     def process(self):
-        id, value = self.getPositionalArguments()
-        manager = attr.getManager(self, interfaces.IColorsManager)
+        id, value = self.getAttributeValues(valuesOnly=True)
+        manager = attrng.getManager(self)
         manager.colors[id] = value
 
 
-class DocInit(element.ContainerElement):
+class IDocInit(interfaces.IRMLDirectiveSignature):
+    occurence.containing(
+        occurence.ZeroOrMore('registerType1Face', IRegisterType1Face),
+        occurence.ZeroOrMore('registerFont', IRegisterFont),
+        occurence.ZeroOrMore('registerTTFont', IRegisterTTFont),
+        occurence.ZeroOrMore('registerCidFont', IRegisterCidFont),
+        occurence.ZeroOrMore('color', IColorDefinition),
+        )
 
-    subElements = {
+class DocInit(directive.RMLDirective):
+    signature = IDocInit
+    factories = {
         'registerType1Face': RegisterType1Face,
         'registerFont': RegisterFont,
         'registerTTFont': RegisterTTFont,
@@ -85,21 +161,56 @@
         }
 
 
-class Document(element.ContainerElement):
-    zope.interface.implements(
-        interfaces.INamesManager,
-        interfaces.IStylesManager,
-        interfaces.IColorsManager)
+class IDocument(interfaces.IRMLDirectiveSignature):
+    occurence.containing(
+        occurence.ZeroOrOne('docinit', IDocInit),
+        )
 
-    subElements = {
-        'docinit': DocInit
+    filename = attrng.String(
+        title=u'File Name',
+        description=(u'The default name of the output file, if no output '
+                     u'file was provided.'),
+        required=True)
+
+    debug = attrng.Boolean(
+        title=u'Debug',
+        description=u'A flag to activate the debug output.',
+        required=False)
+
+    compression = attrng.BooleanWithDefault(
+        title=u'Compression',
+        description=(u'A flag determining whether page compression should '
+                     u'be used.'),
+        required=False)
+
+    invariant = attrng.BooleanWithDefault(
+        title=u'Invariant',
+        description=(u'A flag that determines whether the produced PDF '
+                     u'should be invariant with respect to the date and '
+                     u'the exact contents.'),
+        required=False)
+
+class Document(directive.RMLDirective):
+    signature = IDocument
+    zope.interface.implements(interfaces.IManager,
+                              interfaces.IPostProcessorManager,
+                              interfaces.ICanvasManager)
+
+    factories = {
+        'docinit': DocInit,
+        'stylesheet': stylesheet.Stylesheet,
+        'template': template.Template,
+        'story': template.Story,
+        'pageInfo': canvas.PageInfo,
+        'pageDrawing': canvas.PageDrawing,
         }
 
     def __init__(self, element):
-        self.element = element
+        super(Document, self).__init__(element, None)
         self.names = {}
         self.styles = {}
         self.colors = {}
+        self.postProcessors = []
 
     def process(self, outputFile=None):
         """Process document"""
@@ -107,10 +218,36 @@
             # TODO: This is relative to the input file *not* the CWD!!!
             outputFile = open(self.element.get('filename'), 'w')
 
-        self.processSubElements(None)
+        # Create a temporary output file, so that post-processors can
+        # massage the output
+        self.outputFile = tempOutput = cStringIO.StringIO()
 
+        # Process common sub-directives
+        self.processSubDirectives(select=('docinit', 'stylesheet'))
+
+        # Handle Page Drawing Documents
         if self.element.find('pageDrawing') is not None:
-            canvas.Canvas(self.element, self, None).process(outputFile)
+            kwargs = dict(self.getAttributeValues(
+                select=('compression', 'debug'),
+                attrMapping={'compression': 'pageCompression',
+                             'debug': 'verbosity'}
+                ))
 
-        if self.element.find('template') is not None:
-            template.Template(self.element, self, None).process(outputFile)
+            self.canvas = reportlab.pdfgen.canvas.Canvas(tempOutput, **kwargs)
+            self.processSubDirectives(select=('pageInfo', 'pageDrawing'))
+            self.canvas.save()
+
+        # Handle Flowable-based documents.
+        elif self.element.find('template') is not None:
+            self.processSubDirectives(select=('template', 'story'))
+            self.doc.multiBuild(self.flowables)
+
+        # Process all post processors
+        for name, processor in self.postProcessors:
+            tempOutput.seek(0)
+            tempOutput = processor.process(tempOutput)
+
+        # Save the result into our real output file
+        tempOutput.seek(0)
+        outputFile.write(tempOutput.getvalue())
+

Deleted: z3c.rml/trunk/src/z3c/rml/element.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/element.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/element.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -1,93 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2007 Zope Foundation 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.
-#
-##############################################################################
-"""Generic RML element
-
-$Id$
-"""
-__docformat__ = "reStructuredText"
-from z3c.rml import attr, error
-
-class Element(object):
-
-    def __init__(self, element, parent, context):
-        self.element = element
-        self.parent = parent
-        self.context = context
-
-class ContainerElement(Element):
-
-    subElements = {}
-    order = None
-
-    def processSubElements(self, context):
-        if self.order is not None:
-            for tag in self.order:
-                for element in self.element.findall(tag):
-                    self.subElements[tag](element, self, context).process()
-        else:
-            for subElement in self.element.getchildren():
-                if subElement.tag in self.subElements:
-                    elem = self.subElements[subElement.tag](
-                        subElement, self, context)
-                    elem.__name__ = subElement.tag
-                    elem.process()
-
-
-    def process(self):
-        self.processSubElements(self.context)
-
-
-def extractAttributes(attrs, element, context=None):
-    values = {}
-    for Attr in attrs:
-        value = Attr.get(element, context=context)
-        if value is not attr.DEFAULT:
-            values[Attr.name] = value
-    return values
-
-
-def extractPositionalArguments(argsList, element, context=None):
-    args = []
-    for Attr in argsList:
-        value = Attr.get(element, context=context)
-        if value is attr.DEFAULT:
-            raise error.RequiredAttributeMissing(element, Attr.name)
-        args.append(value)
-    return args
-
-def extractKeywordArguments(kwList, element, context=None):
-        kw = {}
-        for apiName, Attr in kwList:
-            value = Attr.get(element, context=context)
-            if value is not attr.DEFAULT:
-                kw[apiName] = value
-        return kw
-
-
-class FunctionElement(Element):
-
-    functionName = None
-    args = ()
-    kw = ()
-
-    def getPositionalArguments(self):
-        return extractPositionalArguments(self.args, self.element, self)
-
-    def getKeywordArguments(self):
-        return extractKeywordArguments(self.kw, self.element, self)
-
-    def process(self):
-        args = self.getPositionalArguments()
-        kw = self.getKeywordArguments()
-        getattr(self.context, self.functionName)(*args, **kw)

Modified: z3c.rml/trunk/src/z3c/rml/flowable.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/flowable.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/flowable.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -18,12 +18,15 @@
 __docformat__ = "reStructuredText"
 import copy
 import re
+import reportlab.lib.styles
 import reportlab.platypus
 import reportlab.platypus.doctemplate
 import reportlab.platypus.flowables
 import reportlab.platypus.tables
+import zope.schema
 from reportlab.lib import styles
-from z3c.rml import attr, element, form, platypus, special, stylesheet
+from z3c.rml import attrng, directive, interfaces, occurence
+from z3c.rml import form, platypus, special, stylesheet
 
 try:
     import reportlab.graphics.barcode
@@ -34,45 +37,96 @@
     reportlab.graphics.barcode = types.ModuleType('barcode')
     reportlab.graphics.barcode.createBarcodeDrawing = None
 
-class Flowable(element.FunctionElement):
+class Flowable(directive.RMLDirective):
     klass=None
+    attrMapping = None
 
     def process(self):
-        args = self.getPositionalArguments()
-        kw = self.getKeywordArguments()
-        self.parent.flow.append(self.klass(*args, **kw))
+        args = dict(self.getAttributeValues(attrMapping=self.attrMapping))
+        self.parent.flow.append(self.klass(**args))
 
+
+class ISpacer(interfaces.IRMLDirectiveSignature):
+    """Creates a vertical space in the flow."""
+
+    width = attrng.Measurement(
+        title=u'Width',
+        description=u'The width of the spacer. Currently not implemented.',
+        default=100,
+        required=False)
+
+    length = attrng.Measurement(
+        title=u'Length',
+        description=u'The height of the spacer.',
+        required=True)
+
 class Spacer(Flowable):
+    signature = ISpacer
     klass = reportlab.platypus.Spacer
-    args = ( attr.Measurement('width', 100), attr.Measurement('length'), )
+    attrMapping = {'length': 'height'}
 
 
+class IIllustration(interfaces.IRMLDirectiveSignature):
+    """Inserts an illustration with graphics elements."""
+
+    width = attrng.Measurement(
+        title=u'Width',
+        description=u'The width of the illustration.',
+        required=True)
+
+    height = attrng.Measurement(
+        title=u'Height',
+        description=u'The height of the illustration.',
+        default=100,
+        required=True)
+
 class Illustration(Flowable):
+    signature = IIllustration
     klass = platypus.Illustration
-    args = ( attr.Measurement('width'), attr.Measurement('height', 100))
 
     def process(self):
-        args = self.getPositionalArguments()
-        self.parent.flow.append(self.klass(self, *args))
+        args = dict(self.getAttributeValues())
+        self.parent.flow.append(self.klass(self, **args))
 
+
+class IBarCodeFlowable(form.IBarCodeBase):
+    """Creates a bar code as a flowable."""
+
+    value = attrng.String(
+        title=u'Value',
+        description=u'The value represented by the code.',
+        required=True)
+
 class BarCodeFlowable(Flowable):
+    signature = IBarCodeFlowable
     klass = staticmethod(reportlab.graphics.barcode.createBarcodeDrawing)
-    args = form.BarCode.args[:-1]
-    kw = form.BarCode.kw[2:] + ( ('value', attr.Attribute('value')), )
+    attrMapping = {'code': 'codeName'}
 
-class Preformatted(Flowable):
-    klass = reportlab.platypus.Preformatted
-    args = ( attr.RawXMLContent(u''), attr.Style('style', 'Normal') )
+class IPluginFlowable(interfaces.IRMLDirectiveSignature):
+    """Inserts a custom flowable developed in Python."""
 
-class XPreformatted(Flowable):
-    klass = reportlab.platypus.XPreformatted
-    args = ( attr.RawXMLContent(u''), attr.Style('style', 'Normal') )
+    module = attrng.String(
+        title=u'Module',
+        description=u'The Python module in which the flowable is located.',
+        required=True)
 
+    function = attrng.String(
+        title=u'Function',
+        description=(u'The name of the factory function within the module '
+                     u'that returns the custom flowable.'),
+        required=True)
+
+    params = attrng.TextNode(
+        title=u'Parameters',
+        description=(u'A list of parameters encoded as a long string.'),
+        required=False)
+
 class PluginFlowable(Flowable):
-    args = ( attr.Text('module'), attr.Text('function'), attr.TextNode())
+    signature = IPluginFlowable
 
     def process(self):
-        modulePath, functionName, text = self.getPositionalArguments()
+        modulePath, functionName, text = self.getAttributeValues(
+            valuesOnly=True)
         module = __import__(modulePath, {}, {}, [modulePath])
         function = getattr(module, functionName)
         flowables = function(text)
@@ -81,294 +135,737 @@
         self.parent.flow += list(flowables)
 
 
+class IMinimalParagraphBase(interfaces.IRMLDirectiveSignature):
+
+    style = attrng.Style(
+        title=u'Style',
+        description=(u'The paragraph style that is applied to the paragraph. '
+                     u'See the ``paraStyle`` tag for creating a paragraph '
+                     u'style.'),
+        default=reportlab.lib.styles.getSampleStyleSheet()['Normal'],
+        required=True)
+
+    bulletText = attrng.String(
+        title=u'Bullet Character',
+        description=(u'The bullet character is the ASCII representation of '
+                     u'the symbol making up the bullet in a listing.'),
+        required=False)
+
+    dedent = attrng.Integer(
+        title=u'Dedent',
+        description=(u'Number of characters to be removed in front of every '
+                     u'line of the text.'),
+        required=False)
+
+
+class IBold(interfaces.IRMLDirectiveSignature):
+    """Renders the text inside as bold."""
+
+class IItalic(interfaces.IRMLDirectiveSignature):
+    """Renders the text inside as italic."""
+
+class IUnderLine(interfaces.IRMLDirectiveSignature):
+    """Underlines the contained text."""
+
+class IBreak(interfaces.IRMLDirectiveSignature):
+    """Inserts a line break in the paragraph."""
+
+class IPageNumber(interfaces.IRMLDirectiveSignature):
+    """Inserts the current page number into the text."""
+
+class IParagraphBase(IMinimalParagraphBase):
+    occurence.containing(
+        occurence.ZeroOrMore('b', IBold),
+        occurence.ZeroOrMore('i', IItalic),
+        occurence.ZeroOrMore('u', IUnderLine),
+        occurence.ZeroOrMore('br', IBreak,
+                             condition=occurence.laterThanReportlab21),
+        occurence.ZeroOrMore('pageNumber', IPageNumber)
+        )
+
+class IPreformatted(IMinimalParagraphBase):
+    """A preformatted text, similar to the <pre> tag in HTML."""
+
+    text = attrng.RawXMLContent(
+        title=u'Text',
+        description=(u'The text that will be layed out.'),
+        required=True)
+
+class Preformatted(Flowable):
+    signature = IPreformatted
+    klass = reportlab.platypus.Preformatted
+
+
+class IXPreformatted(IParagraphBase):
+    """A preformatted text that allows paragraph markup."""
+
+    text = attrng.RawXMLContent(
+        title=u'Text',
+        description=(u'The text that will be layed out.'),
+        required=True)
+
+class XPreformatted(Flowable):
+    signature = IXPreformatted
+    klass = reportlab.platypus.XPreformatted
+
+
+class IParagraph(IParagraphBase, stylesheet.IBaseParagraphStyle):
+    """Lays out an entire paragraph."""
+
+    text = attrng.XMLContent(
+        title=u'Text',
+        description=(u'The text that will be layed out.'),
+        required=True)
+
 class Paragraph(Flowable):
+    signature = IParagraph
     klass = reportlab.platypus.Paragraph
-    args = ( attr.XMLContent(u''), attr.Style('style', 'Normal') )
-    kw = ( ('bulletText', attr.Attribute('bulletText')), )
 
-    styleAttrs = stylesheet.ParagraphStyle.attrs[3:]
+    styleAttributes = zope.schema.getFieldNames(stylesheet.IBaseParagraphStyle)
 
     def processStyle(self, style):
-        attrs = element.extractAttributes(self.styleAttrs, self.element, self)
+        attrs = self.getAttributeValues(select=self.styleAttributes)
         if attrs:
             style = copy.deepcopy(style)
-            for name, value in attrs.items():
+            for name, value in attrs:
                 setattr(style, name, value)
         return style
 
     def process(self):
-        args = self.getPositionalArguments()
-        kw = self.getKeywordArguments()
-        args[1] = self.processStyle(args[1])
-        self.parent.flow.append(self.klass(*args, **kw))
+        args = dict(self.getAttributeValues(ignore=self.styleAttributes))
+        args['style'] = self.processStyle(args['style'])
+        self.parent.flow.append(self.klass(**args))
 
+
+class ITitle(IParagraph):
+    """The title is a simple paragraph with a special title style."""
+
+    style = attrng.Style(
+        title=u'Style',
+        description=(u'The paragraph style that is applied to the paragraph. '
+                     u'See the ``paraStyle`` tag for creating a paragraph '
+                     u'style.'),
+        default=reportlab.lib.styles.getSampleStyleSheet()['Title'],
+        required=True)
+
 class Title(Paragraph):
-    args = ( attr.XMLContent(u''), attr.Style('style', 'Title'), )
+    signature = ITitle
 
+
+class IHeading1(IParagraph):
+    """Heading 1 is a simple paragraph with a special heading 1 style."""
+
+    style = attrng.Style(
+        title=u'Style',
+        description=(u'The paragraph style that is applied to the paragraph. '
+                     u'See the ``paraStyle`` tag for creating a paragraph '
+                     u'style.'),
+        default=reportlab.lib.styles.getSampleStyleSheet()['Heading1'],
+        required=True)
+
 class Heading1(Paragraph):
-    args = ( attr.XMLContent(u''), attr.Style('style', 'Heading1'), )
+    signature = IHeading1
 
+
+class IHeading2(IParagraph):
+    """Heading 2 is a simple paragraph with a special heading 2 style."""
+
+    style = attrng.Style(
+        title=u'Style',
+        description=(u'The paragraph style that is applied to the paragraph. '
+                     u'See the ``paraStyle`` tag for creating a paragraph '
+                     u'style.'),
+        default=reportlab.lib.styles.getSampleStyleSheet()['Heading2'],
+        required=True)
+
 class Heading2(Paragraph):
-    args = ( attr.XMLContent(u''), attr.Style('style', 'Heading2'), )
+    signature = IHeading2
 
+
+class IHeading3(IParagraph):
+    """Heading 3 is a simple paragraph with a special heading 3 style."""
+
+    style = attrng.Style(
+        title=u'Style',
+        description=(u'The paragraph style that is applied to the paragraph. '
+                     u'See the ``paraStyle`` tag for creating a paragraph '
+                     u'style.'),
+        default=reportlab.lib.styles.getSampleStyleSheet()['Heading3'],
+        required=True)
+
 class Heading3(Paragraph):
-    args = ( attr.XMLContent(u''), attr.Style('style', 'Heading3'), )
+    signature = IHeading3
 
-class TableCell(element.Element):
 
-    styleAttrs = (
-        ('FONTNAME', (attr.Text('fontName'),)),
-        ('FONTSIZE', (attr.Measurement('fontSize'),)),
-        ('TEXTCOLOR', (attr.Color('fontColor'),)),
-        ('LEADING', (attr.Measurement('leading'),)),
-        ('LEFTPADDING', (attr.Measurement('leftPadding'),)),
-        ('RIGHTPADDING', (attr.Measurement('rightPadding'),)),
-        ('TOPPADDING', (attr.Measurement('topPadding'),)),
-        ('BOTTOMPADDING', (attr.Measurement('bottomPadding'),)),
-        ('BACKGROUND', (attr.Color('background'),)),
-        ('ALIGNMENT', (attr.Choice('align',
-                           {'left': 'LEFT', 'right': 'RIGHT',
-                            'center': 'CENTER', 'decimal': 'DECIMAL'}),)),
-        ('VALIGN', (attr.Choice('vAlign',
-                        {'top': 'TOP', 'middle': 'MIDDLE',
-                         'bottom': 'BOTTOM'}), )),
-        ('LINEBELOW', (attr.Measurement('lineBelowThickness'),
-                       attr.Color('lineBelowColor'),
-                       attr.Choice('lineBelowCap',
-                                   {'butt': 0, 'round': 1, 'square': 2}),
-                       attr.Int('lineBelowCount'),
-                       attr.Measurement('lineBelowSpace'))),
-        ('LINEABOVE', (attr.Measurement('lineAboveThickness'),
-                       attr.Color('lineAboveColor'),
-                       attr.Choice('lineAboveCap',
-                                   {'butt': 0, 'round': 1, 'square': 2}),
-                       attr.Int('lineAboveCount'),
-                       attr.Measurement('lineAboveSpace'))),
-        ('LINEBEFORE', (attr.Measurement('lineLeftThickness'),
-                        attr.Color('lineLeftColor'),
-                        attr.Choice('lineLeftCap',
-                                    {'butt': 0, 'round': 1, 'square': 2}),
-                        attr.Int('lineLeftCount'),
-                        attr.Measurement('lineLeftSpace'))),
-        ('LINEAFTER', (attr.Measurement('lineRightThickness'),
-                       attr.Color('lineRightColor'),
-                       attr.Choice('lineRightCap',
-                                   {'butt': 0, 'round': 1, 'square': 2}),
-                       attr.Int('lineRightCount'),
-                       attr.Measurement('lineRightSpace'))),
+class ITableCell(interfaces.IRMLDirectiveSignature):
+    """A table cell within a table."""
+
+    content = attrng.RawXMLContent(
+        title=u'Content',
+        description=(u'The content of the cell; can be text or any flowable.'),
+        required=True)
+
+    fontName = attrng.String(
+        title=u'Font Name',
+        description=u'The name of the font for the cell.',
+        required=False)
+
+    fontSize = attrng.Measurement(
+        title=u'Font Size',
+        description=u'The font size for the text of the cell.',
+        required=False)
+
+    leading = attrng.Measurement(
+        title=u'Leading',
+        description=(u'The height of a single text line. It includes '
+                     u'character height.'),
+        required=False)
+
+    fontColor = attrng.Color(
+        title=u'Font Color',
+        description=u'The color in which the text will appear.',
+        required=False)
+
+    leftPadding = attrng.Measurement(
+        title=u'Left Padding',
+        description=u'The size of the padding on the left side.',
+        required=False)
+
+    rightPadding = attrng.Measurement(
+        title=u'Right Padding',
+        description=u'The size of the padding on the right side.',
+        required=False)
+
+    topPadding = attrng.Measurement(
+        title=u'Top Padding',
+        description=u'The size of the padding on the top.',
+        required=False)
+
+    bottomPadding = attrng.Measurement(
+        title=u'Bottom Padding',
+        description=u'The size of the padding on the bottom.',
+        required=False)
+
+    background = attrng.Color(
+        title=u'Background Color',
+        description=u'The color to use as the background for the cell.',
+        required=False)
+
+    align = attrng.Choice(
+        title=u'Text Alignment',
+        description=u'The text alignment within the cell.',
+        choices=interfaces.ALIGN_TEXT_CHOICES,
+        required=False)
+
+    vAlign = attrng.Choice(
+        title=u'Vertical Alignment',
+        description=u'The vertical alignment of the text within the cell.',
+        choices=interfaces.VALIGN_TEXT_CHOICES,
+        required=False)
+
+    lineBelowThickness = attrng.Measurement(
+        title=u'Line Below Thickness',
+        description=u'The thickness of the line below the cell.',
+        required=False)
+
+    lineBelowColor = attrng.Color(
+        title=u'Line Below Color',
+        description=u'The color of the line below the cell.',
+        required=False)
+
+    lineBelowCap = attrng.Choice(
+        title=u'Line Below Cap',
+        description=u'The cap at the end of the line below the cell.',
+        choices=interfaces.CAP_CHOICES,
+        required=False)
+
+    lineBelowCount = attrng.Integer(
+        title=u'Line Below Count',
+        description=(u'Describes whether the line below is a single (1) or '
+                     u'double (2) line.'),
+        required=False)
+
+    lineBelowSpace = attrng.Measurement(
+        title=u'Line Below Space',
+        description=u'The space of the line below the cell.',
+        required=False)
+
+    lineAboveThickness = attrng.Measurement(
+        title=u'Line Above Thickness',
+        description=u'The thickness of the line above the cell.',
+        required=False)
+
+    lineAboveColor = attrng.Color(
+        title=u'Line Above Color',
+        description=u'The color of the line above the cell.',
+        required=False)
+
+    lineAboveCap = attrng.Choice(
+        title=u'Line Above Cap',
+        description=u'The cap at the end of the line above the cell.',
+        choices=interfaces.CAP_CHOICES,
+        required=False)
+
+    lineAboveCount = attrng.Integer(
+        title=u'Line Above Count',
+        description=(u'Describes whether the line above is a single (1) or '
+                     u'double (2) line.'),
+        required=False)
+
+    lineAboveSpace = attrng.Measurement(
+        title=u'Line Above Space',
+        description=u'The space of the line above the cell.',
+        required=False)
+
+    lineLeftThickness = attrng.Measurement(
+        title=u'Left Line Thickness',
+        description=u'The thickness of the line left of the cell.',
+        required=False)
+
+    lineLeftColor = attrng.Color(
+        title=u'Left Line Color',
+        description=u'The color of the line left of the cell.',
+        required=False)
+
+    lineLeftCap = attrng.Choice(
+        title=u'Line Left Cap',
+        description=u'The cap at the end of the line left of the cell.',
+        choices=interfaces.CAP_CHOICES,
+        required=False)
+
+    lineLeftCount = attrng.Integer(
+        title=u'Line Left Count',
+        description=(u'Describes whether the left line is a single (1) or '
+                     u'double (2) line.'),
+        required=False)
+
+    lineLeftSpace = attrng.Measurement(
+        title=u'Line Left Space',
+        description=u'The space of the line left of the cell.',
+        required=False)
+
+    lineRightThickness = attrng.Measurement(
+        title=u'Right Line Thickness',
+        description=u'The thickness of the line right of the cell.',
+        required=False)
+
+    lineRightColor = attrng.Color(
+        title=u'Right Line Color',
+        description=u'The color of the line right of the cell.',
+        required=False)
+
+    lineRightCap = attrng.Choice(
+        title=u'Line Right Cap',
+        description=u'The cap at the end of the line right of the cell.',
+        choices=interfaces.CAP_CHOICES,
+        required=False)
+
+    lineRightCount = attrng.Integer(
+        title=u'Line Right Count',
+        description=(u'Describes whether the right line is a single (1) or '
+                     u'double (2) line.'),
+        required=False)
+
+    lineRightSpace = attrng.Measurement(
+        title=u'Line Right Space',
+        description=u'The space of the line right of the cell.',
+        required=False)
+
+class TableCell(directive.RMLDirective):
+    signature = ITableCell
+    styleAttributesMapping = (
+        ('FONTNAME', ('fontName',)),
+        ('FONTSIZE', ('fontSize',)),
+        ('TEXTCOLOR', ('fontColor',)),
+        ('LEADING', ('leading',)),
+        ('LEFTPADDING', ('leftPadding',)),
+        ('RIGHTPADDING', ('rightPadding',)),
+        ('TOPPADDING', ('topPadding',)),
+        ('BOTTOMPADDING', ('bottomPadding',)),
+        ('BACKGROUND', ('background',)),
+        ('ALIGNMENT', ('align',)),
+        ('VALIGN', ('vAlign',)),
+        ('LINEBELOW', ('lineBelowThickness', 'lineBelowColor',
+                       'lineBelowCap', 'lineBelowCount', 'lineBelowSpace')),
+        ('LINEABOVE', ('lineAboveThickness', 'lineAboveColor',
+                       'lineAboveCap', 'lineAboveCount', 'lineAboveSpace')),
+        ('LINEBEFORE', ('lineLeftThickness', 'lineLeftColor',
+                        'lineLeftCap', 'lineLeftCount', 'lineLeftSpace')),
+        ('LINEAFTER', ('lineRightThickness', 'lineRightColor',
+                       'lineRightCap', 'lineRightCount', 'lineRightSpace')),
         )
 
     def processStyle(self):
         row = len(self.parent.parent.rows)
         col = len(self.parent.cols)
-        for styleName, attrs in self.styleAttrs:
-            args = []
-            for attribute in attrs:
-                value = attribute.get(self.element, context=self)
-                if value is not attr.DEFAULT:
-                    args.append(value)
-            if args or len(attrs) == 0:
+        for styleAction, attrNames in self.styleAttributesMapping:
+            args = self.getAttributeValues(select=attrNames, valuesOnly=True)
+            if args or len(attrNames) == 0:
                 self.parent.parent.style.add(
-                    styleName, [col, row], [col, row], *args)
+                    styleAction, [col, row], [col, row], *args)
 
     def process(self):
         # Produce style
         self.processStyle()
         # Produce cell data
-        flow = Flow(self.element, self.parent, self.context)
+        flow = Flow(self.element, self.parent)
         flow.process()
         content = flow.flow
         if len(content) == 0:
-            content = attr.TextNode().get(self.element)
+            content = self.getAttributeValues(
+                select=('content',), valuesOnly=True)[0]
         self.parent.cols.append(content)
 
-class TableRow(element.ContainerElement):
 
-    subElements = {'td': TableCell}
+class ITableRow(interfaces.IRMLDirectiveSignature):
+    """A table row in the block table."""
+    occurence.containing(
+        occurence.OneOrMore('td', ITableCell),
+        )
 
+class TableRow(directive.RMLDirective):
+    signature = ITableRow
+    factories = {'td': TableCell}
+
     def process(self):
         self.cols = []
-        self.processSubElements(None)
+        self.processSubDirectives()
         self.parent.rows.append(self.cols)
 
-class TableBulkData(element.Element):
 
+class ITableBulkData(interfaces.IRMLDirectiveSignature):
+    """Bulk Data allows one to wuickly create a table."""
+
+    content = attrng.TextNodeSequence(
+        title=u'Content',
+        description=u'The bulk data.',
+        splitre=re.compile('\n'),
+        value_type=attrng.Sequence(splitre=re.compile(','),
+                                 value_type=attrng.Text())
+        )
+
+class TableBulkData(directive.RMLDirective):
+    signature = ITableBulkData
+
     def process(self):
-        attribute = attr.TextNodeSequence(
-            splitre=re.compile('\n'),
-            valueType=attr.Sequence(
-                splitre=re.compile(','),
-                valueType=attr.Text()
-                ))
-        self.parent.rows = attribute.get(self.element)
+        self.parent.rows = self.getAttributeValues(valuesOnly=True)[0]
 
 
 class BlockTableStyle(stylesheet.BlockTableStyle):
 
     def process(self):
-        self.parent.style = copy.deepcopy(self.parent.style)
-        attrs = element.extractAttributes(self.attrs, self.element, self)
-        for name, value in attrs.items():
-            setattr(self.parent.style, name, value)
-        self.processSubElements(self.parent.style)
+        self.style = copy.deepcopy(self.parent.style)
+        attrs = self.getAttributeValues()
+        for name, value in attrs:
+            setattr(self.style, name, value)
+        self.processSubDirectives()
+        self.parent.style = self.style
 
 
-class BlockTable(element.ContainerElement, Flowable):
-    klass = reportlab.platypus.Table
-    kw = (
-        ('rowHeights', attr.Sequence('rowHeights', attr.Measurement())),
-        ('colWidths', attr.Sequence('colWidths',
-             attr.Measurement(allowPercentage=True, allowStar=True))),
+class IBlockTable(interfaces.IRMLDirectiveSignature):
+    """A typical block table."""
+    occurence.containing(
+        occurence.ZeroOrMore('tr', ITableRow),
+        occurence.ZeroOrOne('bulkData', ITableBulkData),
+        occurence.ZeroOrMore('blockTableStyle', stylesheet.IBlockTableStyle),
         )
 
-    attrs = ( ('repeatRows', attr.Int('repeatRows')), )
+    style = attrng.Style(
+        title=u'Style',
+        description=(u'The table style that is applied to the table. '),
+        required=False)
 
+    rowHeights = attrng.Sequence(
+        title=u'Row Heights',
+        description=u'A list of row heights in the table.',
+        value_type=attrng.Measurement(),
+        required=False)
 
-    subElements = {
+    colWidths = attrng.Sequence(
+        title=u'Column Widths',
+        description=u'A list of column widths in the table.',
+        value_type=attrng.Measurement(allowPercentage=True, allowStar=True),
+        required=False)
+
+    repeatRows = attrng.Integer(
+        title=u'Repeat Rows',
+        description=u'A flag to repeat rows upon table splits.',
+        required=False)
+
+
+class BlockTable(Flowable):
+    signature = IBlockTable
+    klass = reportlab.platypus.Table
+    factories = {
         'tr': TableRow,
         'bulkData': TableBulkData,
         'blockTableStyle': BlockTableStyle}
 
     def process(self):
+        attrs = dict(self.getAttributeValues())
         # Get the table style; create a new one, if none is found
-        self.style = attr.Style('style', 'table').get(self.element, None, self)
+        self.style = attrs.pop('style', None)
         if self.style is None:
             self.style = reportlab.platypus.tables.TableStyle()
         # Extract all table rows and cells
         self.rows = []
-        self.processSubElements(None)
+        self.processSubDirectives(None)
         # Create the table
-        kw = self.getKeywordArguments()
-
-        table = self.klass(self.rows, style=self.style, **kw)
-
-        attrs = element.extractKeywordArguments(self.attrs, self.element)
-        for name, value in attrs.items():
-            setattr(table, name, value)
-
-        # Must set keepWithNExt on table, since the style is not stored corr.
+        repeatRows = attrs.pop('repeatRows', None)
+        table = self.klass(self.rows, style=self.style, **attrs)
+        if repeatRows:
+            table.repeatRows = repeatRows
+        # Must set keepWithNext on table, since the style is not stored corr.
         if hasattr(self.style, 'keepWithNext'):
             table.keepWithNext = self.style.keepWithNext
         self.parent.flow.append(table)
 
 
+class INextFrame(interfaces.IRMLDirectiveSignature):
+    """Switch to the next frame."""
+    name = attrng.StringOrInt(
+        title=u'Name',
+        description=(u'The name or index of the next frame.'),
+        required=False)
+
 class NextFrame(Flowable):
+    signature = INextFrame
     klass = reportlab.platypus.doctemplate.FrameBreak
-    kw = (
-        ('ix', attr.StringOrInt('name')), )
+    attrMapping = {'name': 'ix'}
 
+
+class ISetNextFrame(interfaces.IRMLDirectiveSignature):
+    """Define the next frame to switch to."""
+    name = attrng.StringOrInt(
+        title=u'Name',
+        description=(u'The name or index of the next frame.'),
+        required=True)
+
 class SetNextFrame(Flowable):
+    signature = INextFrame
     klass = reportlab.platypus.doctemplate.NextFrameFlowable
-    kw = (
-        ('ix', attr.StringOrInt('name')), )
+    attrMapping = {'name': 'ix'}
 
+
+class INextPage(interfaces.IRMLDirectiveSignature):
+    """Switch to the next page."""
+
 class NextPage(Flowable):
+    signature = INextPage
     klass = reportlab.platypus.PageBreak
 
+
+class ISetNextTemplate(interfaces.IRMLDirectiveSignature):
+    """Define the next page template to use."""
+    name = attrng.StringOrInt(
+        title=u'Name',
+        description=u'The name or index of the next page template.',
+        required=True)
+
 class SetNextTemplate(Flowable):
+    signature = ISetNextTemplate
     klass = reportlab.platypus.doctemplate.NextPageTemplate
-    args = ( attr.StringOrInt('name'), )
+    attrMapping = {'name': 'pt'}
 
+
+class IConditionalPageBreak(interfaces.IRMLDirectiveSignature):
+    """Switch to the next page if not enough vertical space is available."""
+    height = attrng.Measurement(
+        title=u'height',
+        description=u'The minimal height that must be remaining on the page.',
+        required=True)
+
 class ConditionalPageBreak(Flowable):
+    signature = IConditionalPageBreak
     klass = reportlab.platypus.CondPageBreak
-    args = ( attr.Measurement('height'), )
 
 
+class IKeepInFrame(interfaces.IRMLDirectiveSignature):
+    """Ask a flowable to stay within the frame."""
+
+    maxWidth = attrng.Measurement(
+        title=u'Maximum Width',
+        description=u'The maximum width the flowables are allotted.',
+        default=None,
+        required=False)
+
+    maxHeight = attrng.Measurement(
+        title=u'Maximum Height',
+        description=u'The maximum height the flowables are allotted.',
+        default=None,
+        required=False)
+
+    mergeSpace = attrng.Boolean(
+        title=u'Merge Space',
+        description=u'A flag to set whether the space should be merged.',
+        required=False)
+
+    onOverflow = attrng.Choice(
+        title=u'On Overflow',
+        description=u'Defines what has to be done, if an overflow is detected.',
+        choices=('error', 'overflow', 'shrink', 'truncate'),
+        required=False)
+
+    id = attrng.Text(
+        title=u'Name/Id',
+        description=u'The name/id of the flowable.',
+        required=False)
+
+    frame = attrng.StringOrInt(
+        title=u'Frame',
+        description=u'The frame to which the flowable should be fitted.',
+        required=False)
+
 class KeepInFrame(Flowable):
+    signature = IKeepInFrame
     klass = reportlab.platypus.flowables.KeepInFrame
-    args = (
-        attr.Measurement('maxWidth', None),
-        attr.Measurement('maxHeight', None), )
-    kw = (
-        ('mergeSpace', attr.Bool('mergeSpace')),
-        ('mode', attr.Choice('onOverflow',
-                             ('error', 'overflow', 'shrink', 'truncate'))),
-        ('name', attr.Text('id')),
-        ('frame', attr.StringOrInt('frame')), )
+    attrMapping = {'onOverflow': 'mode', 'id': 'name'}
 
     def process(self):
-        args = self.getPositionalArguments()
-        kw = self.getKeywordArguments()
+        args = dict(self.getAttributeValues(attrMapping=self.attrMapping))
+        # Circumvent broken-ness in zope.schema
+        args['maxWidth'] = args.get('maxWidth', None)
+        args['maxHeight'] = args.get('maxHeight', None)
         # If the frame was specifed, get us there
-        frame = kw.pop('frame', None)
+        frame = args.pop('frame', None)
         if frame:
             self.parent.flow.append(
                 reportlab.platypus.doctemplate.FrameBreak(frame))
         # Create the content of the container
-        flow = Flow(self.element, self.parent, self.context)
+        flow = Flow(self.element, self.parent)
         flow.process()
-        kw['content'] = flow.flow
+        args['content'] = flow.flow
         # Create the keep in frame container
-        frame = self.klass(*args, **kw)
+        frame = self.klass(**args)
         self.parent.flow.append(frame)
 
 
+class IImageAndFlowables(interfaces.IRMLDirectiveSignature):
+    """An image with flowables around it."""
+
+    imageName = attrng.Image(
+        title=u'Image',
+        description=u'The file that is used to extract the image data.',
+        onlyOpen=True,
+        required=True)
+
+    imageWidth = attrng.Measurement(
+        title=u'Image Width',
+        description=u'The width of the image.',
+        required=False)
+
+    imageHeight = attrng.Measurement(
+        title=u'Image Height',
+        description=u'The height the image.',
+        required=False)
+
+    imageMask = attrng.Color(
+        title=u'Mask',
+        description=u'The height the image.',
+        required=False)
+
+    imageLeftPadding = attrng.Measurement(
+        title=u'Image Left Padding',
+        description=u'The padding on the left side of the image.',
+        required=False)
+
+    imageRightPadding = attrng.Measurement(
+        title=u'Image Right Padding',
+        description=u'The padding on the right side of the image.',
+        required=False)
+
+    imageTopPadding = attrng.Measurement(
+        title=u'Image Top Padding',
+        description=u'The padding on the top of the image.',
+        required=False)
+
+    imageBottomPadding = attrng.Measurement(
+        title=u'Image Bottom Padding',
+        description=u'The padding on the bottom of the image.',
+        required=False)
+
+    iamgeSide = attrng.Choice(
+        title=u'Image Side',
+        description=u'The side at which the image will be placed.',
+        choices=('left', 'right'),
+        required=False)
+
 class ImageAndFlowables(Flowable):
+    signature = IImageAndFlowables
     klass = reportlab.platypus.flowables.ImageAndFlowables
-    args = ( attr.Image('imageName', onlyOpen=True), )
-    kw = (
-        ('width', attr.Measurement('imageWidth')),
-        ('height', attr.Measurement('imageHeight')),
-        ('mask', attr.Color('imageMask')),
-        ('imageLeftPadding', attr.Measurement('imageLeftPadding')),
-        ('imageRightPadding', attr.Measurement('imageRightPadding')),
-        ('imageTopPadding', attr.Measurement('imageTopPadding')),
-        ('imageBottomPadding', attr.Measurement('imageBottomPadding')),
-        ('imageSide', attr.Choice('imageSide', ('left', 'right'))) )
+    attrMapping = {'imageWidth': 'width', 'imageHeight': 'height',
+                   'imageMask': 'mask', 'imageName': 'filename'}
 
     def process(self):
-        flow = Flow(self.element, self.parent, self.context)
+        flow = Flow(self.element, self.parent)
         flow.process()
-        args = self.getPositionalArguments()
-        kw = self.getKeywordArguments()
         # Create the image
-        img = reportlab.platypus.flowables.Image(
-            width=kw.get('width'), height=kw.get('height'),
-            mask=kw.get('mask', 'auto'), *args)
-        for option in ('width', 'height', 'mask'):
-            if option in kw:
-                del kw[option]
+        args = dict(self.getAttributeValues(
+            select=('imageName', 'imageWidth', 'imageHeight', 'imageMask'),
+            attrMapping=self.attrMapping))
+        img = reportlab.platypus.flowables.Image(**args)
         # Create the flowable and add it
+        args = dict(self.getAttributeValues(
+            ignore=('imageName', 'imageWidth', 'imageHeight', 'imageMask'),
+            attrMapping=self.attrMapping))
         self.parent.flow.append(
-            self.klass(img, flow.flow, **kw))
+            self.klass(img, flow.flow, **args))
 
 
+class IPTO(interfaces.IRMLDirectiveSignature):
+    '''A container for flowables decorated with trailer & header lists.
+    If the split operation would be called then the trailer and header
+    lists are injected before and after the split. This allows specialist
+    "please turn over" and "continued from previous" like behaviours.'''
+
 class PTO(Flowable):
+    signature = IPTO
     klass = reportlab.platypus.flowables.PTOContainer
 
     def process(self):
         # Get Content
-        flow = Flow(self.element, self.parent, self.context)
+        flow = Flow(self.element, self.parent)
         flow.process()
         # Get the header
         ptoHeader = self.element.find('pto_header')
         header = None
         if ptoHeader:
-            header = Flow(ptoHeader, self.parent, self.context)
+            header = Flow(ptoHeader, self.parent)
             header.process()
             header = header.flow
         # Get the trailer
         ptoTrailer = self.element.find('pto_trailer')
         trailer = None
         if ptoTrailer:
-            trailer = Flow(ptoTrailer, self.parent, self.context)
+            trailer = Flow(ptoTrailer, self.parent)
             trailer.process()
             trailer = trailer.flow
         # Create and add the PTO Container
         self.parent.flow.append(self.klass(flow.flow, trailer, header))
 
 
+class IIndent(interfaces.IRMLDirectiveSignature):
+    """Indent the contained flowables."""
+
+    left = attrng.Measurement(
+        title=u'Left',
+        description=u'The indentation to the left.',
+        required=False)
+
+    right = attrng.Measurement(
+        title=u'Right',
+        description=u'The indentation to the right.',
+        required=False)
+
 class Indent(Flowable):
-    kw = (
-        ('left', attr.Measurement('left')),
-        ('right', attr.Measurement('right')) )
+    signature = IIndent
 
     def process(self):
-        kw = self.getKeywordArguments()
+        kw = dict(self.getAttributeValues())
         # Indent
         self.parent.flow.append(reportlab.platypus.doctemplate.Indenter(**kw))
         # Add Content
-        flow = Flow(self.element, self.parent, self.context)
+        flow = Flow(self.element, self.parent)
         flow.process()
         self.parent.flow += flow.flow
         # Dedent
@@ -377,57 +874,181 @@
         self.parent.flow.append(reportlab.platypus.doctemplate.Indenter(**kw))
 
 
+class IFixedSize(interfaces.IRMLDirectiveSignature):
+    """Create a container flowable of a fixed size."""
+
+    width = attrng.Measurement(
+        title=u'Width',
+        description=u'The width the flowables are allotted.',
+        required=True)
+
+    height = attrng.Measurement(
+        title=u'Height',
+        description=u'The height the flowables are allotted.',
+        required=True)
+
 class FixedSize(Flowable):
+    signature = IFixedSize
     klass = reportlab.platypus.flowables.KeepInFrame
-    args = (
-        attr.Measurement('width'),
-        attr.Measurement('height'), )
+    attrMapping = {'width': 'maxWidth', 'height': 'maxHeight'}
 
     def process(self):
-        flow = Flow(self.element, self.parent, self.context)
+        flow = Flow(self.element, self.parent)
         flow.process()
-        args = self.getPositionalArguments()
-        frame = self.klass(content=flow.flow, mode='shrink', *args)
+        args = dict(self.getAttributeValues(attrMapping=self.attrMapping))
+        frame = self.klass(content=flow.flow, mode='shrink', **args)
         self.parent.flow.append(frame)
 
+
+class IBookmark(interfaces.IRMLDirectiveSignature):
+    """
+    This creates a bookmark to the current page which can be referred to with
+    the given key elsewhere.
+
+    PDF offers very fine grained control over how Acrobat reader is zoomed
+    when people link to this. The default is to keep the user's current zoom
+    settings. the last arguments may or may not be needed depending on the
+    choice of 'fitType'.
+    """
+
+    name = attrng.Text(
+        title=u'Name',
+        description=u'The name of the bookmark.',
+        required=True)
+
+    fitType = attrng.Choice(
+        title=u'Fit Type',
+        description=u'The Fit Type.',
+        choices=('Fit', 'FitH', 'FitV', 'FitR'),
+        required=False)
+
+    left = attrng.Measurement(
+        title=u'Left',
+        description=u'The left position.',
+        required=False)
+
+    right = attrng.Measurement(
+        title=u'Right',
+        description=u'The right position.',
+        required=False)
+
+    top = attrng.Measurement(
+        title=u'Top',
+        description=u'The top position.',
+        required=False)
+
+    right = attrng.Measurement(
+        title=u'Right',
+        description=u'The right position.',
+        required=False)
+
+    zoom = attrng.Float(
+        title=u'Zoom',
+        description=u'The zoom level when clicking on the bookmark.',
+        required=False)
+
 class Bookmark(Flowable):
+    signature = IBookmark
     klass = platypus.BookmarkPage
-    args = ( attr.Text('name'), )
-    kw = (
-        ('fitType', attr.Choice('fitType', ('Fit', 'FitH', 'FitV', 'FitR'))),
-        ('left', attr.Measurement('left')),
-        ('right', attr.Measurement('right')),
-        ('top', attr.Measurement('top')),
-        ('bottom', attr.Measurement('bottom')),
-        ('zoom', attr.Float('zoom')),
-        )
+    attrMapping = {'name': 'key'}
 
+
+class IHorizontalRow(interfaces.IRMLDirectiveSignature):
+    """Create a horizontal line on the page."""
+
+    width = attrng.Measurement(
+        title=u'Width',
+        description=u'The width of the line on the page.',
+        allowPercentage=True,
+        required=False)
+
+    thickness = attrng.Measurement(
+        title=u'Thickness',
+        description=u'Line Thickness',
+        required=False)
+
+    color = attrng.Color(
+        title=u'Color',
+        description=u'The color of the line.',
+        required=False)
+
+    lineCap = attrng.Choice(
+        title=u'Cap',
+        description=u'The cap at the end of the line.',
+        choices=interfaces.CAP_CHOICES.keys(),
+        required=False)
+
+    spaceBefore = attrng.Measurement(
+        title=u'Space Before',
+        description=u'The vertical space before the line.',
+        required=False)
+
+    spaceAfter = attrng.Measurement(
+        title=u'Space After',
+        description=u'The vertical space after the line.',
+        required=False)
+
+    align = attrng.Choice(
+        title=u'Alignment',
+        description=u'The alignment of the line within the frame.',
+        choices=interfaces.ALIGN_TEXT_CHOICES,
+        required=False)
+
+    valign = attrng.Choice(
+        title=u'Vertical Alignment',
+        description=u'The vertical alignment of the line.',
+        choices=interfaces.VALIGN_TEXT_CHOICES,
+        required=False)
+
+    dash = attrng.Sequence(
+        title=u'Dash-Pattern',
+        description=u'The dash-pattern of a line.',
+        value_type=attrng.Measurement(),
+        default=None,
+        required=False)
+
 class HorizontalRow(Flowable):
+    signature = IHorizontalRow
     klass = reportlab.platypus.flowables.HRFlowable
-    kw = (
-        ('width', attr.Measurement('width', allowPercentage=True)),
-        ('thickness', attr.Measurement('thickness')),
-        ('color', attr.Color('color')),
-        ('lineCap', attr.Choice('lineCap', ('butt', 'round', 'square') )),
-        ('spaceBefore', attr.Measurement('spaceBefore')),
-        ('spaceAfter', attr.Measurement('spaceAfter')),
-        ('hAlign', attr.Choice(
-             'align', ('left', 'right', 'center', 'centre', 'decimal') )),
-        ('vAlign', attr.Choice('vAlign', ('top', 'middle', 'bottom') )),
-        ('dash', attr.Sequence('dash', attr.Measurement())),
-        )
+    attrMapping = {'align': 'hAlign'}
 
+
+class IOutlineAdd(interfaces.IRMLDirectiveSignature):
+    """Add a new entry to the outline of the PDF."""
+
+    title = attrng.TextNode(
+        title=u'Title',
+        description=u'The text displayed for this item.',
+        required=True)
+
+    key = attrng.String(
+        title=u'Key',
+        description=u'The unique key of the item.',
+        required=False)
+
+    level = attrng.Integer(
+        title=u'Level',
+        description=u'The level in the outline tree.',
+        required=False)
+
+    closed = attrng.Boolean(
+        title=u'Closed',
+        description=(u'A flag to determine whether the sub-tree is closed '
+                     u'by default.'),
+        required=False)
+
+
 class OutlineAdd(Flowable):
+    signature = IOutlineAdd
     klass = platypus.OutlineAdd
-    args = ( attr.TextNode(), attr.Text('key', None) )
-    kw = (
-        ('level', attr.Int('level')),
-        ('closed', attr.Bool('closed')),
-        )
 
-class Flow(element.ContainerElement):
 
-    subElements = {
+class IFlow(interfaces.IRMLDirectiveSignature):
+    """A list of flowables."""
+
+class Flow(directive.RMLDirective):
+
+    factories = {
         # Generic Flowables
         'spacer': Spacer,
         'illustration': Illustration,
@@ -466,5 +1087,5 @@
         self.flow = []
 
     def process(self):
-        self.processSubElements(None)
+        self.processSubDirectives()
         return self.flow

Modified: z3c.rml/trunk/src/z3c/rml/form.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/form.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/form.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -17,7 +17,7 @@
 """
 __docformat__ = "reStructuredText"
 import types
-from z3c.rml import attr, element
+from z3c.rml import attrng, directive, interfaces
 
 try:
     import reportlab.graphics.barcode
@@ -29,62 +29,199 @@
     reportlab.graphics.barcode.getCodeNames = lambda : ()
 
 
-class BarCode(element.FunctionElement):
-    args = (
-        attr.Choice('code', reportlab.graphics.barcode.getCodeNames()),
-        attr.TextNode(),
-        )
-    kw = (
-        ('x', attr.Measurement('x')),
-        ('y', attr.Measurement('y')),
-        ('width', attr.Measurement('width')),
-        ('height', attr.Measurement('height')),
-        ('strokeColor', attr.Color('strokeColor')),
-        ('strokeWidth', attr.Measurement('strokeWidth')),
-        ('fillColor', attr.Color('fillColor')),
-        ('barStrokeColor', attr.Color('barStrokeColor')),
-        ('barStrokeWidth', attr.Measurement('barStrokeWidth')),
-        ('barFillColor', attr.Color('barFillColor')),
-        ('gap', attr.Measurement('gap')),
-        # Bar code dependent attributes
-        # I2of5, Code128, Standard93, FIM, POSTNET, Ean13B
-        ('barWidth', attr.Measurement('barWidth')),
-        # I2of5, Code128, Standard93, FIM, POSTNET
-        ('barHeight', attr.Measurement('barHeight')),
-        # I2of5
-        ('ratio', attr.Float('ratio')),
-        # I2of5
-        # Should be boolean, but some code want it as int; will still work
-        ('checksum', attr.Int('checksum')),
-        # I2of5
-        ('bearers', attr.Float('bearers')),
-        # I2of5, Code128, Standard93, FIM, Ean13
-        ('quiet', attr.Bool('quiet')),
-        # I2of5, Code128, Standard93, FIM, Ean13
-        ('lquiet', attr.Measurement('lquiet')),
-        # I2of5, Code128, Standard93, FIM, Ean13
-        ('rquiet', attr.Measurement('rquiet')),
-        # I2of5, Code128, Standard93, FIM, POSTNET, Ean13
-        ('fontName', attr.Text('fontName')),
-        # I2of5, Code128, Standard93, FIM, POSTNET, Ean13
-        ('fontSize', attr.Measurement('fontSize')),
-        # I2of5, Code128, Standard93, FIM, POSTNET, Ean13
-        ('humanReadable', attr.Bool('humanReadable')),
-        # I2of5, Standard93
-        ('stop', attr.Bool('atop')),
-        # FIM, POSTNET
-        ('spaceWidth', attr.Measurement('spaceWidth')),
-        # POSTNET
-        ('shortHeight', attr.Measurement('shortHeight')),
-        # Ean13
-        ('textColor', attr.Color('textColor')),
-        )
+class IBarCodeBase(interfaces.IRMLDirectiveSignature):
+    """Create a bar code."""
 
+    code = attrng.Choice(
+        title=u'Code',
+        description=u'The name of the type of code to use.',
+        choices=reportlab.graphics.barcode.getCodeNames(),
+        required=True)
+
+    value = attrng.TextNode(
+        title=u'Value',
+        description=u'The value represented by the code.',
+        required=True)
+
+    width = attrng.Measurement(
+        title=u'Width',
+        description=u'The width of the barcode.',
+        required=False)
+
+    height = attrng.Measurement(
+        title=u'Height',
+        description=u'The height of the barcode.',
+        required=False)
+
+    strokeColor = attrng.Color(
+        title=u'Stroke Color',
+        description=(u'The color of the line strokes in the area.'),
+        required=False)
+
+    strokeWidth = attrng.Measurement(
+        title=u'Stroke Width',
+        description=u'The width of the line strokes in the area.',
+        required=False)
+
+    fillColor = attrng.Color(
+        title=u'Fill Color',
+        description=(u'The color of the filled shapes in the area.'),
+        required=False)
+
+    barStrokeColor = attrng.Color(
+        title=u'Bar Stroke Color',
+        description=(u'The color of the line strokes in the barcode.'),
+        required=False)
+
+    barStrokeWidth = attrng.Measurement(
+        title=u'Bar Stroke Width',
+        description=u'The width of the line strokes in the barcode.',
+        required=False)
+
+    barFillColor = attrng.Color(
+        title=u'Bar Fill Color',
+        description=(u'The color of the filled shapes in the barcode.'),
+        required=False)
+
+    gap = attrng.Measurement(
+        title=u'Gap',
+        description=u'The width of the inter-character gaps.',
+        required=False)
+
+    # Bar code dependent attributes
+    # I2of5, Code128, Standard93, FIM, POSTNET, Ean13B
+    barWidth = attrng.Measurement(
+        title=u'Bar Width',
+        description=u'The width of the smallest bar within the barcode',
+        required=False)
+
+    # I2of5, Code128, Standard93, FIM, POSTNET
+    barHeight = attrng.Measurement(
+        title=u'Bar Height',
+        description=u'The height of the symbol.',
+        required=False)
+
+    # I2of5
+    ratio = attrng.Float(
+        title=u'Ratio',
+        description=(u'The ratio of wide elements to narrow elements. '
+                     u'Must be between 2.0 and 3.0 (or 2.2 and 3.0 if the '
+                     u'barWidth is greater than 20 mils (.02 inch)).'),
+        min=2.0,
+        max=3.0,
+        required=False)
+
+    # I2of5
+    # Should be boolean, but some code want it as int; will still work
+    checksum = attrng.Integer(
+        title=u'Ratio',
+        description=(u'A flag that enables the computation and inclusion of '
+                     u'the check digit.'),
+        required=False)
+
+    # I2of5
+    bearers = attrng.Float(
+        title=u'Bearers',
+        description=(u'Height of bearer bars (horizontal bars along the top '
+                     u'and bottom of the barcode). Default is 3 '
+                     u'x-dimensions. Set to zero for no bearer bars.'
+                     u'(Bearer bars help detect misscans, so it is '
+                     u'suggested to leave them on).'),
+        required=False)
+
+    # I2of5, Code128, Standard93, FIM, Ean13
+    quiet = attrng.Boolean(
+        title=u'Quiet Zone',
+        description=(u'A flag to include quiet zones in the symbol.'),
+        required=False)
+
+    # I2of5, Code128, Standard93, FIM, Ean13
+    lquiet = attrng.Measurement(
+        title=u'Left Quiet Zone',
+        description=(u"Quiet zone size to the left of code, if quiet is "
+                     u"true. Default is the greater of .25 inch or .15 times "
+                     u"the symbol's length."),
+        required=False)
+
+    # I2of5, Code128, Standard93, FIM, Ean13
+    rquiet = attrng.Measurement(
+        title=u'Right Quiet Zone',
+        description=(u"Quiet zone size to the right of code, if quiet is "
+                     u"true. Default is the greater of .25 inch or .15 times "
+                     u"the symbol's length."),
+        required=False)
+
+    # I2of5, Code128, Standard93, FIM, POSTNET, Ean13
+    frontName = attrng.String(
+        title=u'Font Name',
+        description=(u'The font used to print the value.'),
+        required=False)
+
+    # I2of5, Code128, Standard93, FIM, POSTNET, Ean13
+    frontSize = attrng.Measurement(
+        title=u'Font Size',
+        description=(u'The size of the value text.'),
+        required=False)
+
+    # I2of5, Code128, Standard93, FIM, POSTNET, Ean13
+    humanReadable = attrng.Boolean(
+        title=u'Human Readable',
+        description=(u'A flag when set causes the value to be printed below '
+                     u'the bar code.'),
+        required=False)
+
+    # I2of5, Standard93
+    stop = attrng.Boolean(
+        title=u'Show Start/Stop',
+        description=(u'A flag to specify whether the start/stop symbols '
+                     u'are to be shown.'),
+        required=False)
+
+    # FIM, POSTNET
+    spaceWidth = attrng.Measurement(
+        title=u'Space Width',
+        description=u'The space of the inter-character gaps.',
+        required=False)
+
+    # POSTNET
+    shortHeight = attrng.Measurement(
+        title=u'Short Height',
+        description=u'The height of the short bar.',
+        required=False)
+
+    # Ean13
+    textColor = attrng.Color(
+        title=u'Text Color',
+        description=(u'The color of human readable text.'),
+        required=False)
+
+
+class IBarCode(IBarCodeBase):
+    """A barcode graphic."""
+
+    x = attrng.Measurement(
+        title=u'X-Position',
+        description=u'The x-position of the lower-left corner of the barcode.',
+        default=0,
+        required=False)
+
+    y = attrng.Measurement(
+        title=u'Y-Position',
+        description=u'The y-position of the lower-left corner of the barcode.',
+        default=0,
+        required=False)
+
+
+
+class BarCode(directive.RMLDirective):
+    signature = IBarCode
+
     def process(self):
-        kw = self.getKeywordArguments()
-        name, value = self.getPositionalArguments()
-        kw['value'] = str(value)
+        kw = dict(self.getAttributeValues())
+        name = kw.pop('code')
+        kw['value'] = str(kw['value'])
         x = kw.pop('x', 0)
         y = kw.pop('y', 0)
         code = reportlab.graphics.barcode.createBarcodeDrawing(name, **kw)
-        code.drawOn(self.context, x, y)
+        manager = attrng.getManager(self, interfaces.ICanvasManager)
+        code.drawOn(manager.canvas, x, y)

Modified: z3c.rml/trunk/src/z3c/rml/interfaces.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/interfaces.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/interfaces.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -16,11 +16,38 @@
 $Id$
 """
 __docformat__ = "reStructuredText"
+import reportlab.lib.enums
 import zope.interface
+import zope.schema
 
+from z3c.rml import attrng
+from z3c.rml.occurence import ZeroOrMore, ZeroOrOne, OneOrMore
+
+JOIN_CHOICES = {'round': 1, 'mitered': 0, 'bevelled': 2}
+CAP_CHOICES = {'default': 0, 'butt': 0, 'round': 1, 'square': 2}
+ALIGN_CHOICES = {
+    'left': reportlab.lib.enums.TA_LEFT,
+    'right': reportlab.lib.enums.TA_RIGHT,
+    'center': reportlab.lib.enums.TA_CENTER,
+    'centre': reportlab.lib.enums.TA_CENTER,
+    'justify': reportlab.lib.enums.TA_JUSTIFY}
+ALIGN_TEXT_CHOICES = {
+    'left': 'LEFT', 'right': 'RIGHT', 'center': 'CENTER', 'centre': 'CENTER',
+    'decimal': 'DECIMAL'}
+VALIGN_TEXT_CHOICES = {
+    'top': 'TOP', 'middle': 'MIDDLE', 'bottom': 'BOTTOM'}
+SPLIT_CHOICES = ('splitfirst', 'splitlast')
+
+
 class IRML2PDF(zope.interface.Interface):
     """This is the main public API of z3c.rml"""
 
+    def parseString(xml):
+        """Parse an XML string and convert it to PDF.
+
+        The output is a ``StringIO`` object.
+        """
+
     def go(xmlInputName, outputFileName=None, outDir=None, dtdDir=None):
         """Convert RML 2 PDF.
 
@@ -28,25 +55,68 @@
         ``outputFileName``.
         """
 
-class INamesManager(zope.interface.Interface):
-    """Manages custom names"""
-
+class IManager(zope.interface.Interface):
+    """A manager of all document-global variables."""
     names = zope.interface.Attribute("Names dict")
+    styles = zope.interface.Attribute("Styles dict")
+    colors = zope.interface.Attribute("Colors dict")
 
-class IStylesManager(zope.interface.Interface):
-    """Manages custom styles"""
+class IPostProcessorManager(zope.interface.Interface):
+    """Manages all post processors"""
 
-    styles = zope.interface.Attribute("Styles dict")
+    postProcessors = zope.interface.Attribute(
+        "List of tuples of the form: (name, processor)")
 
+class ICanvasManager(zope.interface.Interface):
+    """A manager for the canvas."""
+    canvas = zope.interface.Attribute("Canvas")
 
-class IColorsManager(zope.interface.Interface):
-    """Manages custom colors"""
+class IRMLDirectiveSignature(zope.interface.Interface):
+    """The attribute and sub-directives signature of the current
+    RML directive."""
 
-    colors = zope.interface.Attribute("Colors dict")
 
+class IRMLDirective(zope.interface.Interface):
+    """A directive in RML extracted from an Element Tree element."""
 
-class IPostProcessorManager(zope.interface.Interface):
-    """Manages all post processors"""
+    signature = zope.schema.Field(
+        title=u'Signature',
+        description=(u'The signature of the RML directive.'),
+        required=True)
 
-    postProcessors = zope.interface.Attribute(
-        "List of tuples of the form: (name, processor)")
+    parent = zope.schema.Field(
+        title=u'Parent RML Element',
+        description=u'The parent in the RML element hierarchy',
+        required=True,)
+
+    element = zope.schema.Field(
+        title=u'Element',
+        description=(u'The Element Tree element from which the data '
+                     u'is retrieved.'),
+        required=True)
+
+    def getAttributeValues(ignore=None, select=None, includeMissing=False):
+        """Return a list of name-value-tuples based on the signature.
+
+        If ``ignore`` is specified, all attributes are returned except the
+        ones listed in the argument. The values of the sequence are the
+        attribute names.
+
+        If ``select`` is specified, only attributes listed in the argument are
+        returned. The values of the sequence are the attribute names.
+
+        If ``includeMissing`` is set to true, then even missing attributes are
+        included in the value list.
+        """
+
+    def processSubDirectives(self):
+        """Process all sub-directives."""
+
+    def process(self):
+        """Process the directive.
+
+        The main task for this method is to interpret the available data and
+        to make the corresponding calls in the Reportlab document.
+
+        This call should also process all sub-directives and process them.
+        """

Added: z3c.rml/trunk/src/z3c/rml/occurence.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/occurence.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/occurence.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -0,0 +1,110 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation 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.
+#
+##############################################################################
+"""Condition Implementation
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import reportlab
+import sys
+import zope.interface
+import zope.schema
+from zope.schema import fieldproperty
+
+class ICondition(zope.interface.Interface):
+    """Condition that is checked before a  directive is available."""
+
+    __doc__ = zope.schema.TextLine(
+        title=u'Description',
+        description=u'The description of the condition.',
+        required=True)
+
+    def __call__(directive):
+        """Check whether the condition is fulfilled for the given directive."""
+
+
+class IOccurence(zope.interface.Interface):
+    """Description of the occurence of a sub-directive."""
+
+    __doc__ = zope.schema.TextLine(
+        title=u'Description',
+        description=u'The description of the occurence.',
+        required=True)
+
+    tag = zope.schema.BytesLine(
+        title=u'Tag',
+        description=u'The tag of the sub-directive within the directive',
+        required=True)
+
+    signature = zope.schema.Field(
+        title=u'Signature',
+        description=u'The signature of the sub-directive.',
+        required=True)
+
+    condition = zope.schema.Field(
+        title=u'Condition',
+        description=u'The condition that the directive is available.',
+        required=False)
+
+
+ at zope.interface.implementer(ICondition)
+def laterThanReportlab21(directive):
+    """The directive is only available in Reportlab 2.1 and higher."""
+    return [int(num) for num in reportlab.Version.split('.')] >= (2, 0)
+
+
+def containing(*occurences):
+    frame = sys._getframe(1)
+    f_locals = frame.f_locals
+    f_globals = frame.f_globals
+
+    if not (f_locals is not f_globals
+            and f_locals.get('__module__')
+            and f_locals.get('__module__') == f_globals.get('__name__')
+            ):
+        raise TypeError("contains not called from signature interface")
+
+    f_locals['__interface_tagged_values__'] = {'directives': occurences}
+
+
+class Occurence(object):
+    zope.interface.implements(IOccurence)
+
+    tag = fieldproperty.FieldProperty(IOccurence['tag'])
+    signature = fieldproperty.FieldProperty(IOccurence['signature'])
+    condition = fieldproperty.FieldProperty(IOccurence['condition'])
+
+    def __init__(self, tag, signature, condition=None):
+        self.tag = tag
+        self.signature = signature
+        self.condition = condition
+
+
+class ZeroOrMore(Occurence):
+    """Zero or More
+
+    This sub-directive can occur zero or more times.
+    """
+
+class ZeroOrOne(Occurence):
+    """Zero or one
+
+    This sub-directive can occur zero or one time.
+    """
+
+class OneOrMore(Occurence):
+    """One or More
+
+    This sub-directive can occur one or more times.
+    """


Property changes on: z3c.rml/trunk/src/z3c/rml/occurence.py
___________________________________________________________________
Name: svn:keywords
   + Id

Modified: z3c.rml/trunk/src/z3c/rml/page.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/page.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/page.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -17,7 +17,7 @@
 """
 __docformat__ = "reStructuredText"
 import cStringIO
-from z3c.rml import attr, element, interfaces
+from z3c.rml import attrng, directive, interfaces
 
 try:
     import pyPdf
@@ -48,11 +48,25 @@
         return outputFile
 
 
-class MergePage(element.FunctionElement):
-    args = ( attr.File('filename'), attr.Int('page') )
+class IMergePage(interfaces.IRMLDirectiveSignature):
+    """Merges an existing PDF Page into the one to be generated."""
 
+    filename = attrng.File(
+        title=u'File',
+        description=(u'Reference to the PDF file to extract the page from.'),
+        required=True)
+
+    page = attrng.Integer(
+        title=u'Page Number',
+        description=u'The page number of the PDF file that is used to merge..',
+        required=True)
+
+
+class MergePage(directive.RMLDirective):
+    signature = IMergePage
+
     def getProcessor(self):
-        manager = attr.getManager(self, interfaces.IPostProcessorManager)
+        manager = attrng.getManager(self, interfaces.IPostProcessorManager)
         procs = dict(manager.postProcessors)
         if 'MERGE' not in procs:
             proc = MergePostProcessor()
@@ -64,8 +78,9 @@
         if pyPdf is None:
             raise Exception(
                 'pyPdf is not installed, so this feature is not available.')
-        inputFile, inPage = self.getPositionalArguments()
-        outPage = self.context.getPageNumber()-1
+        inputFile, inPage = self.getAttributeValues(valuesOnly=True)
+        manager = attrng.getManager(self, interfaces.ICanvasManager)
+        outPage = manager.canvas.getPageNumber()-1
 
         proc = self.getProcessor()
         pageOperations = proc.operations.setdefault(outPage, [])

Modified: z3c.rml/trunk/src/z3c/rml/platypus.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/platypus.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/platypus.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -17,7 +17,11 @@
 """
 __docformat__ = "reStructuredText"
 import reportlab.platypus.flowables
+import zope.interface
 
+from z3c.rml import interfaces
+
+
 class BaseFlowable(reportlab.platypus.flowables.Flowable):
     def __init__(self, *args, **kw):
         reportlab.platypus.flowables.Flowable.__init__(self)
@@ -44,7 +48,9 @@
         from z3c.rml import canvas
         self.canv.saveState()
         drawing = canvas.Drawing(
-            self.processor.element, self.processor, self.canv)
+            self.processor.element, self.processor)
+        zope.interface.alsoProvides(drawing, interfaces.ICanvasManager)
+        drawing.canvas = self.canv
         drawing.process()
         self.canv.restoreState()
 
@@ -55,8 +61,7 @@
 
 class OutlineAdd(BaseFlowable):
     def draw(self):
-        title, key = self.args
-        if key is None:
-            key = str(hash(self))
-        self.canv.bookmarkPage(key)
-        self.canv.addOutlineEntry(title, key, **self.kw)
+        if self.kw.get('key', None) is None:
+            self.kw['key'] = str(hash(self))
+        self.canv.bookmarkPage(self.kw['key'])
+        self.canv.addOutlineEntry(**self.kw)

Modified: z3c.rml/trunk/src/z3c/rml/special.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/special.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/special.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -16,25 +16,45 @@
 $Id$
 """
 __docformat__ = "reStructuredText"
-from z3c.rml import attr, element, interfaces
+from z3c.rml import attrng, directive, interfaces
 
 
-class Name(element.FunctionElement):
-    args = (
-        attr.Text('id'),
-        attr.Text('value'), )
+class IName(interfaces.IRMLDirectiveSignature):
+    """Defines a name for a string."""
 
+    id = attrng.String(
+        title=u'Id',
+        description=u'The id under which the value will be known.',
+        required=True)
+
+    value = attrng.Text(
+        title=u'Value',
+        description=u'The text that is displayed if the id is called.',
+        required=True)
+
+class Name(directive.RMLDirective):
+    signature = IName
+
     def process(self):
-        id, value = self.getPositionalArguments()
-        manager = attr.getManager(self, interfaces.INamesManager)
+        id, value = self.getAttributeValues(valuesOnly=True)
+        manager = attrng.getManager(self)
         manager.names[id] = value
 
 
-class GetName(element.Element):
+class IGetName(interfaces.IRMLDirectiveSignature):
+    """Get the text for the id."""
 
+    id = attrng.String(
+        title=u'Id',
+        description=u'The id as which the value is known.',
+        required=True)
+
+class GetName(directive.RMLDirective):
+    signature = IGetName
+
     def process(self):
-        id = attr.Text('id').get(self.element)
-        manager = attr.getManager(self, interfaces.INamesManager)
+        id = dict(self.getAttributeValues()).pop('id')
+        manager = attrng.getManager(self)
         text = manager.names[id] + (self.element.tail or u'')
         # Now replace the element with the text
         parent = self.element.getparent()
@@ -45,12 +65,23 @@
         parent.remove(self.element)
 
 
-class Alias(element.FunctionElement):
-    args = (
-        attr.Text('id'),
-        attr.Style('value'), )
+class IAlias(interfaces.IRMLDirectiveSignature):
+    """Defines an alias for a given style."""
 
+    id = attrng.String(
+        title=u'Id',
+        description=u'The id as which the style will be known.',
+        required=True)
+
+    value = attrng.Style(
+        title=u'Value',
+        description=u'The style that is represented.',
+        required=True)
+
+class Alias(directive.RMLDirective):
+    signature = IAlias
+
     def process(self):
-        id, value = self.getPositionalArguments()
-        manager = attr.getManager(self, interfaces.IStylesManager)
+        id, value = self.getAttributeValues(valuesOnly=True)
+        manager = attrng.getManager(self)
         manager.styles[id] = value

Modified: z3c.rml/trunk/src/z3c/rml/stylesheet.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/stylesheet.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/stylesheet.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -20,122 +20,311 @@
 import reportlab.lib.styles
 import reportlab.lib.enums
 import reportlab.platypus
-from z3c.rml import attr, element, error, interfaces, special
+from z3c.rml import attrng, directive, interfaces, occurence, special
 
 
-class Initialize(element.ContainerElement):
+class IInitialize(interfaces.IRMLDirectiveSignature):
+    """Do some RML processing initialization."""
+    occurence.containing(
+        occurence.ZeroOrMore('name', special.IName),
+        occurence.ZeroOrMore('alias', special.IAlias),
+        )
 
-    subElements = {
+class Initialize(directive.RMLDirective):
+    signature = IInitialize
+    factories = {
         'name': special.Name,
         'alias': special.Alias,
         }
 
-class ParagraphStyle(element.Element):
-    attrs = (
-        attr.Text('name'),
-        attr.Text('alias'),
-        attr.Style('parent'),
-        attr.Text('fontName'),
-        attr.Measurement('fontSize'),
-        attr.Measurement('leading'),
-        attr.Measurement('leftIndent'),
-        attr.Measurement('rightIndent'),
-        attr.Measurement('firstLineIndent'),
-        attr.Measurement('spaceBefore'),
-        attr.Measurement('spaceAfter'),
-        attr.Choice('alignment',
-            {'left':reportlab.lib.enums.TA_LEFT,
-             'right':reportlab.lib.enums.TA_RIGHT,
-             'center':reportlab.lib.enums.TA_CENTER,
-             'justify':reportlab.lib.enums.TA_JUSTIFY}),
-        attr.Text('bulletFontName'),
-        attr.Measurement('bulletFontSize'),
-        attr.Measurement('bulletIndent'),
-        attr.Color('textColor'),
-        attr.Color('backColor'),
-        attr.Bool('keepWithNext')
-        )
 
+class IBaseParagraphStyle(interfaces.IRMLDirectiveSignature):
+
+    fontName = attrng.String(
+        title=u'Font Name',
+        description=u'The name of the font for the paragraph.',
+        required=False)
+
+    fontSize = attrng.Measurement(
+        title=u'Font Size',
+        description=u'The font size for the text of the paragraph.',
+        required=False)
+
+    leading = attrng.Measurement(
+        title=u'Leading',
+        description=(u'The height of a single paragraph line. It includes '
+                     u'character height.'),
+        required=False)
+
+    leftIndent = attrng.Measurement(
+        title=u'Left Indentation',
+        description=u'General indentation on the left side.',
+        required=False)
+
+    rightIndent = attrng.Measurement(
+        title=u'Right Indentation',
+        description=u'General indentation on the right side.',
+        required=False)
+
+    firstLineIndent = attrng.Measurement(
+        title=u'First Line Indentation',
+        description=u'The indentation of the first line in the paragraph.',
+        required=False)
+
+    spaceBefore = attrng.Measurement(
+        title=u'Space Before',
+        description=u'The vertical space before the paragraph.',
+        required=False)
+
+    spaceAfter = attrng.Measurement(
+        title=u'Space After',
+        description=u'The vertical space after the paragraph.',
+        required=False)
+
+    alignment = attrng.Choice(
+        title=u'Alignment',
+        description=u'The text alignment.',
+        choices=interfaces.ALIGN_CHOICES,
+        required=False)
+
+    bulletFontName = attrng.String(
+        title=u'Bullet Font Name',
+        description=u'The font in which the bullet character will be rendered.',
+        required=False)
+
+    bulletFontSize = attrng.Measurement(
+        title=u'Bullet Font Size',
+        description=u'The font size of the bullet character.',
+        required=False)
+
+    bulletIndent = attrng.Measurement(
+        title=u'Bullet Indentation',
+        description=u'The indentation that is kept for a bullet point.',
+        required=False)
+
+    textColor = attrng.Color(
+        title=u'Text Color',
+        description=u'The color in which the text will appear.',
+        required=False)
+
+    backColor = attrng.Color(
+        title=u'Background Color',
+        description=u'The background color of the paragraph.',
+        required=False)
+
+    keepWithNext = attrng.Boolean(
+        title=u'Keep with Next',
+        description=(u'When set, this paragraph will always be in the same '
+                     u'frame as the following flowable.'),
+        required=False)
+
+
+class IParagraphStyle(IBaseParagraphStyle):
+    """Defines a paragraph style and gives it a name."""
+
+    name = attrng.String(
+        title=u'Name',
+        description=u'The name of the style.',
+        required=True)
+
+    alias = attrng.String(
+        title=u'Alias',
+        description=u'An alias under which the style will also be known as.',
+        required=False)
+
+    parent = attrng.Style(
+        title=u'Parent',
+        description=(u'The apragraph style that will be used as a base for '
+                     u'this one.'),
+        required=False)
+
+class ParagraphStyle(directive.RMLDirective):
+    signature = IParagraphStyle
+
     def process(self):
-        attrs = element.extractKeywordArguments(
-            [(attrib.name, attrib) for attrib in self.attrs], self.element,
-            self.parent)
+        kwargs = dict(self.getAttributeValues())
 
-        parent = attrs.pop(
+        parent = kwargs.pop(
             'parent', reportlab.lib.styles.getSampleStyleSheet()['Normal'])
         style = copy.deepcopy(parent)
 
-        for name, value in attrs.items():
+        for name, value in kwargs.items():
             setattr(style, name, value)
 
-        manager = attr.getManager(self, interfaces.IStylesManager)
+        manager = attrng.getManager(self)
         manager.styles[style.name] = style
 
 
-class TableStyleCommand(element.Element):
+class ITableStyleCommand(interfaces.IRMLDirectiveSignature):
+
+    start = attrng.Sequence(
+        title=u'Start Coordinates',
+        description=u'The start table coordinates for the style instruction',
+        value_type=attrng.Combination(
+            value_types=(attrng.Integer(),
+                         attrng.Choice(choices=interfaces.SPLIT_CHOICES))
+            ),
+        default=[0, 0],
+        min_length=2,
+        max_length=2,
+        required=True)
+
+    end = attrng.Sequence(
+        title=u'End Coordinates',
+        description=u'The end table coordinates for the style instruction',
+        value_type=attrng.Combination(
+            value_types=(attrng.Integer(),
+                         attrng.Choice(choices=interfaces.SPLIT_CHOICES))
+            ),
+        default=[-1, -1],
+        min_length=2,
+        max_length=2,
+        required=True)
+
+class TableStyleCommand(directive.RMLDirective):
     name = None
-    attrs = (
-        attr.Sequence('start', attr.Combination(
-            valueTypes=(attr.Int(),
-                        attr.Choice(choices=('splitfirst', 'splitlast')) )),
-            [0, 0], length=2),
-        attr.Sequence('stop', attr.Combination(
-            valueTypes=(attr.Int(),
-                        attr.Choice(choices=('splitfirst', 'splitlast')) )),
-            [-1, -1], length=2) )
 
     def process(self):
         args = [self.name]
-        for attribute in self.attrs:
-            value = attribute.get(self.element, context=self)
-            if value is not attr.DEFAULT:
-                args.append(value)
-        self.context.add(*args)
+        args += self.getAttributeValues(valuesOnly=True)
+        self.parent.style.add(*args)
 
+
+class IBlockFont(ITableStyleCommand):
+    """Set the font properties for the texts."""
+
+    name = attrng.String(
+        title=u'Font Name',
+        description=u'The name of the font for the cell.',
+        required=False)
+
+    size = attrng.Measurement(
+        title=u'Font Size',
+        description=u'The font size for the text of the cell.',
+        required=False)
+
+    leading = attrng.Measurement(
+        title=u'Leading',
+        description=(u'The height of a single text line. It includes '
+                     u'character height.'),
+        required=False)
+
 class BlockFont(TableStyleCommand):
+    signature = IBlockFont
     name = 'FONT'
-    attrs = TableStyleCommand.attrs + (
-        attr.Text('name'),
-        attr.Measurement('size'),
-        attr.Measurement('leading') )
 
+class IBlockLeading(ITableStyleCommand):
+    """Set the text leading."""
+
+    length = attrng.Measurement(
+        title=u'Length',
+        description=(u'The height of a single text line. It includes '
+                     u'character height.'),
+        required=True)
+
 class BlockLeading(TableStyleCommand):
+    signature = IBlockLeading
     name = 'LEADING'
-    attrs = TableStyleCommand.attrs + (attr.Measurement('length'), )
 
+class IBlockTextColor(ITableStyleCommand):
+    """Set the text color."""
+
+    colorName = attrng.Color(
+        title=u'Color Name',
+        description=u'The color in which the text will appear.',
+        required=True)
+
 class BlockTextColor(TableStyleCommand):
+    signature = IBlockTextColor
     name = 'TEXTCOLOR'
-    attrs = TableStyleCommand.attrs + (attr.Color('colorName'), )
 
+class IBlockAlignment(ITableStyleCommand):
+    """Set the text alignment."""
+
+    value = attrng.Choice(
+        title=u'Text Alignment',
+        description=u'The text alignment within the cell.',
+        choices=interfaces.ALIGN_TEXT_CHOICES,
+        required=True)
+
 class BlockAlignment(TableStyleCommand):
+    signature = IBlockAlignment
     name = 'ALIGNMENT'
-    attrs = TableStyleCommand.attrs + (
-        attr.Choice('value',
-                    {'left': 'LEFT', 'right': 'RIGHT',
-                     'center': 'CENTER', 'decimal': 'DECIMAL'}), )
 
+class IBlockLeftPadding(ITableStyleCommand):
+    """Set the left padding of the cells."""
+
+    length = attrng.Measurement(
+        title=u'Length',
+        description=u'The size of the padding.',
+        required=True)
+
 class BlockLeftPadding(TableStyleCommand):
+    signature = IBlockLeftPadding
     name = 'LEFTPADDING'
-    attrs = TableStyleCommand.attrs + (attr.Measurement('length'), )
 
+class IBlockRightPadding(ITableStyleCommand):
+    """Set the right padding of the cells."""
+
+    length = attrng.Measurement(
+        title=u'Length',
+        description=u'The size of the padding.',
+        required=True)
+
 class BlockRightPadding(TableStyleCommand):
+    signature = IBlockRightPadding
     name = 'RIGHTPADDING'
-    attrs = TableStyleCommand.attrs + (attr.Measurement('length'), )
 
+class IBlockBottomPadding(ITableStyleCommand):
+    """Set the bottom padding of the cells."""
+
+    length = attrng.Measurement(
+        title=u'Length',
+        description=u'The size of the padding.',
+        required=True)
+
 class BlockBottomPadding(TableStyleCommand):
+    signature = IBlockBottomPadding
     name = 'BOTTOMPADDING'
-    attrs = TableStyleCommand.attrs + (attr.Measurement('length'), )
 
+class IBlockTopPadding(ITableStyleCommand):
+    """Set the top padding of the cells."""
+
+    length = attrng.Measurement(
+        title=u'Length',
+        description=u'The size of the padding.',
+        required=True)
+
 class BlockTopPadding(TableStyleCommand):
+    signature = IBlockTopPadding
     name = 'TOPPADDING'
-    attrs = TableStyleCommand.attrs + (attr.Measurement('length'), )
 
+class IBlockBackground(ITableStyleCommand):
+    """Define the background color of the cells.
+
+    It also supports alternating colors.
+    """
+
+    colorName = attrng.Color(
+        title=u'Color Name',
+        description=u'The color to use as the background for every cell.',
+        required=False)
+
+    colorsByRow = attrng.Sequence(
+        title=u'Colors By Row',
+        description=u'A list of colors to be used circularly for rows.',
+        value_type=attrng.Color(acceptNone=True),
+        required=False)
+
+    colorsByCol = attrng.Sequence(
+        title=u'Colors By Column',
+        description=u'A list of colors to be used circularly for columns.',
+        value_type=attrng.Color(acceptNone=True),
+        required=False)
+
 class BlockBackground(TableStyleCommand):
+    signature = IBlockBackground
     name = 'BACKGROUND'
-    attrs = TableStyleCommand.attrs + (
-        attr.Color('colorName'),
-        attr.Sequence('colorsByRow', attr.Color()),
-        attr.Sequence('colorsByCol', attr.Color()) )
 
     def process(self):
         args = [self.name]
@@ -144,64 +333,148 @@
         elif 'colorsByCol' in self.element.keys():
             args = [BlockColBackground.name]
 
-        for attribute in self.attrs:
-            value = attribute.get(self.element, context=self)
-            if value is not attr.DEFAULT:
-                args.append(value)
-        self.context.add(*args)
+        args += self.getAttributeValues(valuesOnly=True)
+        self.parent.style.add(*args)
 
+class IBlockRowBackground(ITableStyleCommand):
+    """Define the background colors for rows."""
+
+    colorNames = attrng.Sequence(
+        title=u'Colors By Row',
+        description=u'A list of colors to be used circularly for rows.',
+        value_type=attrng.Color(),
+        required=True)
+
 class BlockRowBackground(TableStyleCommand):
+    signature = IBlockRowBackground
     name = 'ROWBACKGROUNDS'
-    attrs = TableStyleCommand.attrs + (
-        attr.Sequence('colorNames', attr.Color()), )
 
+class IBlockColBackground(ITableStyleCommand):
+    """Define the background colors for columns."""
+
+    colorNames = attrng.Sequence(
+        title=u'Colors By Row',
+        description=u'A list of colors to be used circularly for rows.',
+        value_type=attrng.Color(),
+        required=True)
+
 class BlockColBackground(TableStyleCommand):
+    signature = IBlockColBackground
     name = 'COLBACKGROUNDS'
-    attrs = TableStyleCommand.attrs + (
-        attr.Sequence('colorNames', attr.Color()), )
 
+class IBlockValign(ITableStyleCommand):
+    """Define the vertical alignment of the cells."""
+
+    value = attrng.Choice(
+        title=u'Vertical Alignment',
+        description=u'The vertical alignment of the text with the cells.',
+        choices=interfaces.VALIGN_TEXT_CHOICES,
+        required=True)
+
 class BlockValign(TableStyleCommand):
+    signature = IBlockValign
     name = 'VALIGN'
-    attrs = TableStyleCommand.attrs + (
-        attr.Choice('value',
-                    {'top': 'TOP', 'middle': 'MIDDLE', 'bottom': 'BOTTOM'}), )
 
+class IBlockSpan(ITableStyleCommand):
+    """Define a span over multiple cells (rows and columns)."""
+
 class BlockSpan(TableStyleCommand):
+    signature = IBlockSpan
     name = 'SPAN'
 
+class ILineStyle(ITableStyleCommand):
+    """Define the border line style of each cell."""
+
+    kind = attrng.Choice(
+        title=u'Kind',
+        description=u'The kind of line actions to be taken.',
+        choices=('GRID', 'BOX', 'OUTLINE', 'INNERGRID',
+                 'LINEBELOW', 'LINEABOVE', 'LINEBEFORE', 'LINEAFTER'),
+        required=True)
+
+    thickness = attrng.Measurement(
+        title=u'Thickness',
+        description=u'Line Thickness',
+        default=1,
+        required=True)
+
+    colorName = attrng.Color(
+        title=u'Color',
+        description=u'The color of the border line.',
+        default=None,
+        required=True)
+
+    cap = attrng.Choice(
+        title=u'Cap',
+        description=u'The cap at the end of a border line.',
+        choices=interfaces.CAP_CHOICES,
+        default=1,
+        required=True)
+
+    dash = attrng.Sequence(
+        title=u'Dash-Pattern',
+        description=u'The dash-pattern of a line.',
+        value_type=attrng.Measurement(),
+        default=None,
+        required=False)
+
+    join = attrng.Choice(
+        title=u'Join',
+        description=u'The way lines are joined together.',
+        choices=interfaces.JOIN_CHOICES,
+        default=1,
+        required=False)
+
+    count = attrng.Integer(
+        title=u'Count',
+        description=(u'Describes whether the line is a single (1) or '
+                     u'double (2) line.'),
+        default=1,
+        required=False)
+
 class LineStyle(TableStyleCommand):
-    attrs = TableStyleCommand.attrs + (
-        attr.Measurement('thickness', default=1),
-        attr.Color('colorName', default=None),
-        attr.Choice('cap', ('butt', 'round', 'square'), default=1),
-        attr.Sequence('dash', attr.Measurement(), default=None),
-        attr.Bool('join', default=1),
-        attr.Int('count', default=1),
+    signature = ILineStyle
+
+    def process(self):
+        name = self.getAttributeValues(select=('kind',), valuesOnly=True)[0]
+        args = [name]
+        args += self.getAttributeValues(ignore=('kind',), valuesOnly=True)
+        self.parent.style.add(*args)
+
+class IBlockTableStyle(interfaces.IRMLDirectiveSignature):
+    """A style defining the look of a table."""
+    occurence.containing(
+        occurence.ZeroOrMore('blockFont', IBlockFont),
+        occurence.ZeroOrMore('blockLeading', IBlockLeading),
+        occurence.ZeroOrMore('blockTextColor', IBlockTextColor),
+        occurence.ZeroOrMore('blockAlignment', IBlockAlignment),
+        occurence.ZeroOrMore('blockLeftPadding', IBlockLeftPadding),
+        occurence.ZeroOrMore('blockRightPadding', IBlockRightPadding),
+        occurence.ZeroOrMore('blockBottomPadding', IBlockBottomPadding),
+        occurence.ZeroOrMore('blockTopPadding', IBlockTopPadding),
+        occurence.ZeroOrMore('blockBackground', IBlockBackground),
+        occurence.ZeroOrMore('blockRowBackground', IBlockRowBackground),
+        occurence.ZeroOrMore('blockColBackground', IBlockColBackground),
+        occurence.ZeroOrMore('blockValign', IBlockValign),
+        occurence.ZeroOrMore('blockSpan', IBlockSpan),
+        occurence.ZeroOrMore('lineStyle', ILineStyle)
         )
 
-    @property
-    def name(self):
-        cmds = ['GRID', 'BOX', 'OUTLINE', 'INNERGRID',
-                'LINEBELOW', 'LINEABOVE', 'LINEBEFORE', 'LINEAFTER']
-        return attr.Choice(
-            'kind', dict([(cmd.lower(), cmd) for cmd in cmds])
-            ).get(self.element, context=self)
+    id = attrng.String(
+        title=u'Id',
+        description=u'The name/id of the style.',
+        required=True)
 
-    def process(self):
-        args = [self.name]
-        for attribute in self.attrs:
-            value = attribute.get(self.element, context=self)
-            if value is not attr.DEFAULT:
-                args.append(value)
-        self.context.add(*args)
+    keepWithNext = attrng.Boolean(
+        title=u'Keep with Next',
+        description=(u'When set, this paragraph will always be in the same '
+                     u'frame as the following flowable.'),
+        required=False)
 
-class BlockTableStyle(element.ContainerElement):
+class BlockTableStyle(directive.RMLDirective):
+    signature = IBlockTableStyle
 
-    attrs = (
-        attr.Bool('keepWithNext'),
-        )
-
-    subElements = {
+    factories = {
         'blockFont': BlockFont,
         'blockLeading': BlockLeading,
         'blockTextColor': BlockTextColor,
@@ -219,25 +492,34 @@
         }
 
     def process(self):
-        id = attr.Text('id').get(self.element, context=self)
+        kw = dict(self.getAttributeValues())
+        id  = kw.pop('id')
         # Create Style
-        style = reportlab.platypus.tables.TableStyle()
-        attrs = element.extractAttributes(self.attrs, self.element, self)
-        for name, value in attrs.items():
-            setattr(style, name, value)
+        self.style = reportlab.platypus.tables.TableStyle()
+        for name, value in kw.items():
+            setattr(self.style, name, value)
         # Fill style
-        self.processSubElements(style)
+        self.processSubDirectives()
         # Add style to the manager
-        manager = attr.getManager(self, interfaces.IStylesManager)
-        manager.styles[id] = style
+        manager = attrng.getManager(self)
+        manager.styles[id] = self.style
 
 
-class Stylesheet(element.ContainerElement):
+class IStylesheet(interfaces.IRMLDirectiveSignature):
+    """A styleheet defines the styles that can be used in the document."""
+    occurence.containing(
+        occurence.ZeroOrOne('initialize', IInitialize),
+        occurence.ZeroOrMore('paraStyle', IParagraphStyle),
+        occurence.ZeroOrMore('blockTableStyle', IBlockTableStyle),
+        # TODO:
+        #occurence.ZeroOrMore('boxStyle', IBoxStyle),
+        )
 
-    subElements = {
+class Stylesheet(directive.RMLDirective):
+    signature = IStylesheet
+
+    factories = {
         'initialize': Initialize,
         'paraStyle': ParagraphStyle,
         'blockTableStyle': BlockTableStyle,
-        # TODO: 'boxStyle': BoxStyle,
         }
-    order = ('initialize', 'paraStyle', 'blockTableStyle')

Modified: z3c.rml/trunk/src/z3c/rml/template.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/template.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/template.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -16,136 +16,256 @@
 $Id$
 """
 __docformat__ = "reStructuredText"
+import zope.interface
 from reportlab import platypus
-from z3c.rml import attr, canvas, element, flowable, interfaces, stylesheet
+from z3c.rml import attrng, directive, interfaces, occurence
+from z3c.rml import canvas, flowable, stylesheet
 
 
+class IStory(flowable.IFlow):
+    """The story of the PDF file."""
+
+    firstPageTemplate = attrng.Text(
+        title=u'First Page Template',
+        description=u'The first page template to be used.',
+        default=None,
+        required=False)
+
 class Story(flowable.Flow):
+    signature = IStory
 
-    def getFirstPageTemplateIndex(self, doc):
-        fpt = attr.Text('firstPageTemplate').get(self.element, None)
+    def process(self):
+        self.parent.flowables = super(Story, self).process()
+        self.parent.doc._firstPageTemplateIndex = self.getFirstPTIndex()
+
+    def getFirstPTIndex(self):
+        args = dict(self.getAttributeValues(select=('firstPageTemplate',)))
+        fpt = args.pop('firstPageTemplate', None)
         if fpt is None:
             return 0
-        for idx, pageTemplate in enumerate(doc.pageTemplates):
+        for idx, pageTemplate in enumerate(self.parent.doc.pageTemplates):
             if pageTemplate.id == fpt:
                 return idx
         raise ValueError('%r is not a correct page template id.' %fpt)
 
-class Frame(element.FunctionElement):
-    args = (
-        attr.Measurement('x1', allowPercentage=True),
-        attr.Measurement('y1', allowPercentage=True),
-        attr.Measurement('width', allowPercentage=True),
-        attr.Measurement('height', allowPercentage=True),
-        )
-    kw = (
-        ('id', attr.Text('id')),
-        # Non-RML compliant extensions
-        ('leftPadding', attr.Measurement('leftPadding', 0)),
-        ('rightPadding', attr.Measurement('rightPadding', 0)),
-        ('topPadding', attr.Measurement('topPadding', 0)),
-        ('bottomPadding', attr.Measurement('bottomPadding', 0)),
-        ('showBoundary', attr.Bool('showBoundary')),
-        )
 
+class IFrame(interfaces.IRMLDirectiveSignature):
+    """A frame on a page."""
+
+    x1 = attrng.Measurement(
+        title=u'X-Position',
+        description=u'The X-Position of the lower-left corner of the frame.',
+        allowPercentage=True,
+        required=True)
+
+    y1 = attrng.Measurement(
+        title=u'Y-Position',
+        description=u'The Y-Position of the lower-left corner of the frame.',
+        allowPercentage=True,
+        required=True)
+
+    width = attrng.Measurement(
+        title=u'Width',
+        description=u'The width of the frame.',
+        allowPercentage=True,
+        required=True)
+
+    height = attrng.Measurement(
+        title=u'Height',
+        description=u'The height of the frame.',
+        allowPercentage=True,
+        required=True)
+
+    id = attrng.Text(
+        title=u'Id',
+        description=u'The id of the frame.',
+        required=False)
+
+    leftPadding = attrng.Measurement(
+        title=u'Left Padding',
+        description=u'The left padding of the frame.',
+        default=0,
+        required=False)
+
+    rightPadding = attrng.Measurement(
+        title=u'Right Padding',
+        description=u'The right padding of the frame.',
+        default=0,
+        required=False)
+
+    topPadding = attrng.Measurement(
+        title=u'Top Padding',
+        description=u'The top padding of the frame.',
+        default=0,
+        required=False)
+
+    bottomPadding = attrng.Measurement(
+        title=u'Bottom Padding',
+        description=u'The bottom padding of the frame.',
+        default=0,
+        required=False)
+
+    showBoundary = attrng.Boolean(
+        title=u'Show Boundary',
+        description=u'A flag to show the boundary of the frame.',
+        required=False)
+
+
+class Frame(directive.RMLDirective):
+    signature = IFrame
+
     def process(self):
         # get the page size
-        size = self.context.pagesize
+        size = self.parent.pt.pagesize
         if size is None:
-            size = self.parent.context.pagesize
+            size = self.parent.parent.parent.doc.pagesize
         # Get the arguments
-        args = self.getPositionalArguments()
-        kw = self.getKeywordArguments()
+        args = dict(self.getAttributeValues())
         # Deal with percentages
-        if isinstance(args[0], basestring) and args[0].endswith('%'):
-            args[0] = float(args[0][:-1])/100*size[0]
-        if isinstance(args[1], basestring) and args[1].endswith('%'):
-            args[1] = float(args[1][:-1])/100*size[1]
-        if isinstance(args[2], basestring) and args[2].endswith('%'):
-            args[2] = float(args[2][:-1])/100*size[0]
-        if isinstance(args[3], basestring) and args[3].endswith('%'):
-            args[3] = float(args[3][:-1])/100*size[1]
-        frame = platypus.Frame(*args, **kw)
+        for name, dir in (('x1', 0), ('y1', 1), ('width', 0), ('height', 1)):
+            if isinstance(args[name], basestring) and args[name].endswith('%'):
+                args[name] = float(args[name][:-1])/100*size[dir]
+        frame = platypus.Frame(**args)
         self.parent.frames.append(frame)
 
 
-class PageGraphics(element.Element):
+class IPageGraphics(canvas.IDrawing):
+    """Define the page graphics for the page template."""
 
+class PageGraphics(directive.RMLDirective):
+    zope.interface.implements(interfaces.ICanvasManager)
+    signature = IPageGraphics
+
     def process(self):
         def drawOnCanvas(canv, doc):
             canv.saveState()
-            drawing = canvas.Drawing(self.element, self, canv)
+            self.canvas = canv
+            drawing = canvas.Drawing(self.element, self)
             drawing.process()
             canv.restoreState()
 
-        self.context.onPage = drawOnCanvas
+        self.parent.pt.onPage = drawOnCanvas
 
 
-class PageTemplate(element.FunctionElement, element.ContainerElement):
-    args = (attr.Text('id'),)
-    kw = (
-        ('pagesize', attr.PageSize('pageSize',)),
-        ('rotation', attr.Int('rotation')) )
+class IPageTemplate(interfaces.IRMLDirectiveSignature):
+    """Define a page template."""
+    occurence.containing(
+        occurence.OneOrMore('frame', IFrame),
+        occurence.ZeroOrOne('pageGraphics', IPageGraphics),
+        )
 
-    subElements = {
+    id = attrng.Text(
+        title=u'Id',
+        description=u'The id of the template.',
+        required=True)
+
+    pagesize = attrng.PageSize(
+        title=u'Page Size',
+        description=u'The Page Size.',
+        required=False)
+
+    rotation = attrng.Integer(
+        title=u'Rotation',
+        description=u'The rotation of the page in multiples of 90 degrees.',
+        required=False)
+
+
+
+class PageTemplate(directive.RMLDirective):
+    signature = IPageTemplate
+    factories = {
         'frame': Frame,
         'pageGraphics': PageGraphics,
         }
 
     def process(self):
-        args = self.getPositionalArguments()
+        args = dict(self.getAttributeValues())
+        pagesize = args.pop('pagesize', None)
+
         self.frames = []
-        pt = platypus.PageTemplate(*args)
-        self.processSubElements(pt)
-        pt.frames = self.frames
+        self.pt = platypus.PageTemplate(**args)
 
-        kw = self.getKeywordArguments()
-        if 'pagesize' in kw:
-            pt.pagesize = kw['pagesize']
+        self.processSubDirectives()
+        self.pt.frames = self.frames
 
-        self.context.addPageTemplates(pt)
+        if pagesize:
+            self.pt.pagesize = pagesize
 
+        self.parent.parent.doc.addPageTemplates(self.pt)
 
-class Template(element.ContainerElement):
 
-    templateArgs = (
-        ('pagesize', attr.PageSize('pageSize',)),
-        ('rotation', attr.Int('rotation')),
-        ('leftMargin', attr.Measurement('leftMargin')),
-        ('rightMargin', attr.Measurement('rightMargin')),
-        ('topMargin', attr.Measurement('topMargin')),
-        ('bottomMargin', attr.Measurement('bottomMargin')),
-        ('showBoundary', attr.Bool('showBoundary')),
-        ('allowSplitting', attr.Bool('allowSplitting')),
-        ('title', attr.Text('title')),
-        ('author', attr.Text('author')) )
+class ITemplate(interfaces.IRMLDirectiveSignature):
+    """Define a page template."""
+    occurence.containing(
+        occurence.OneOrMore('pagetemplate', IPageTemplate),
+        )
 
-    documentArgs = (
-        ('_debug', attr.Bool('debug')),
-        ('pageCompression', attr.DefaultBool('compression')),
-        ('invariant', attr.DefaultBool('invariant')) )
+    pagesize = attrng.PageSize(
+        title=u'Page Size',
+        description=u'The Page Size.',
+        required=False)
 
-    subElements = {
-        'pageTemplate': PageTemplate,
-        'stylesheet': stylesheet.Stylesheet,
-        }
+    rotation = attrng.Integer(
+        title=u'Rotation',
+        description=u'The rotation of the page in multiples of 90 degrees.',
+        required=False)
 
-    def process(self, outputFile):
-        docElement = self.element
-        self.processSubElements(None)
+    leftMargin = attrng.Measurement(
+        title=u'Left Margin',
+        description=u'The left margin of the template.',
+        default=0,
+        required=False)
 
-        self.element = self.element.find('template')
+    rightMargin = attrng.Measurement(
+        title=u'Right Margin',
+        description=u'The right margin of the template.',
+        default=0,
+        required=False)
 
-        kw = element.extractKeywordArguments(
-            self.documentArgs, docElement, self)
-        kw.update(element.extractKeywordArguments(
-            self.templateArgs, self.element, self))
-        doc = platypus.BaseDocTemplate(outputFile, **kw)
+    topMargin = attrng.Measurement(
+        title=u'Top Margin',
+        description=u'The top margin of the template.',
+        default=0,
+        required=False)
 
-        self.processSubElements(doc)
+    bottomMargin = attrng.Measurement(
+        title=u'Bottom Margin',
+        description=u'The bottom margin of the template.',
+        default=0,
+        required=False)
 
-        story = Story(docElement.find('story'), self, doc)
-        flowables = story.process()
+    showBoundary = attrng.Boolean(
+        title=u'Show Boundary',
+        description=u'A flag to show the boundary of the template.',
+        required=False)
 
-        doc._firstPageTemplateIndex = story.getFirstPageTemplateIndex(doc)
-        doc.multiBuild(flowables)
+    allowSplitting = attrng.Boolean(
+        title=u'Allow Splitting',
+        description=u'A flag to allow splitting over multiple templates.',
+        required=False)
+
+    title = attrng.Text(
+        title=u'Title',
+        description=u'The title of the PDF document.',
+        required=False)
+
+    author = attrng.Text(
+        title=u'Author',
+        description=u'The author of the PDF document.',
+        required=False)
+
+class Template(directive.RMLDirective):
+    signature = ITemplate
+    factories = {
+        'pageTemplate': PageTemplate,
+        }
+
+    def process(self):
+        args = self.getAttributeValues()
+        args += self.parent.getAttributeValues(
+            select=('debug', 'compression', 'invariant'),
+            attrMapping={'debug': '_debug', 'compression': 'pageCompression'})
+
+        self.parent.doc = platypus.BaseDocTemplate(
+            self.parent.outputFile, **dict(args))
+        self.processSubDirectives()

Added: z3c.rml/trunk/src/z3c/rml/tests/input/tag-color.rml
===================================================================
--- z3c.rml/trunk/src/z3c/rml/tests/input/tag-color.rml	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/tests/input/tag-color.rml	2007-03-28 03:02:22 UTC (rev 73805)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
+<!DOCTYPE document SYSTEM "rml.dtd">
+<document filename="tag-color.pdf">
+  <docinit>
+    <color id="favorite-color" RGB="yellow" />
+  </docinit>
+  <template>
+    <pageTemplate id="main">
+      <frame id="first" x1="1cm" y1="1cm" width="19cm" height="26cm"/>
+    </pageTemplate>
+  </template>
+  <story>
+    <para fontSize="40" textColor="favorite-color">
+      This is my favorite color!
+    </para>
+  </story>
+</document>

Modified: z3c.rml/trunk/src/z3c/rml/tests/input/tag-image-1.rml
===================================================================
--- z3c.rml/trunk/src/z3c/rml/tests/input/tag-image-1.rml	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/tests/input/tag-image-1.rml	2007-03-28 03:02:22 UTC (rev 73805)
@@ -1,19 +1,22 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE document SYSTEM "rml.dtd">
-
 <document filename="drawString.pdf">
   <pageDrawing>
 
-    <image file="zope3logo.gif" x="2in" y="10in"
+    <image file="[z3c.rml.tests]/input/zope3logo.gif"
+           x="2in" y="10in"
            showBoundary="true" />
 
-    <image file="zope3logo.gif" x="2in" y="8in" width="5in"
+    <image file="[z3c.rml.tests]/input/zope3logo.gif"
+           x="2in" y="8in" width="5in"
            preserveAspectRatio="true" />
 
-    <image file="zope3logo.gif" x="2in" y="4in" height="3in"
+    <image file="[z3c.rml.tests]/input/zope3logo.gif"
+           x="2in" y="4in" height="3in"
            preserveAspectRatio="true" />
 
-    <image file="zope3logo.gif" x="2in" y="2in" width="0.5in" height="3in"
+    <image file="[z3c.rml.tests]/input/zope3logo.gif"
+           x="2in" y="2in" width="0.5in" height="3in"
            preserveAspectRatio="true" />
 
   </pageDrawing>

Modified: z3c.rml/trunk/src/z3c/rml/tests/input/tag-image.rml
===================================================================
--- z3c.rml/trunk/src/z3c/rml/tests/input/tag-image.rml	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/tests/input/tag-image.rml	2007-03-28 03:02:22 UTC (rev 73805)
@@ -1,16 +1,19 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE document SYSTEM "rml.dtd">
-
-<document filename="drawString.pdf">
+<document filename="tag-image.pdf">
   <pageDrawing>
 
-    <image file="zope3logo.gif" x="2in" y="10in" />
+    <image file="[z3c.rml.tests]/input/zope3logo.gif"
+           x="2in" y="10in" />
 
-    <image file="zope3logo.gif" x="2in" y="8in" width="5in" />
+    <image file="[z3c.rml.tests]/input/zope3logo.gif"
+           x="2in" y="8in" width="5in" />
 
-    <image file="zope3logo.gif" x="2in" y="4in" height="3in" />
+    <image file="[z3c.rml.tests]/input/zope3logo.gif"
+           x="2in" y="4in" height="3in" />
 
-    <image file="zope3logo.gif" x="2in" y="2in" width="0.5in" height="3in" />
+    <image file="[z3c.rml.tests]/input/zope3logo.gif"
+           x="2in" y="2in" width="0.5in" height="3in" />
 
   </pageDrawing>
 </document>

Added: z3c.rml/trunk/src/z3c/rml/tests/input/tag-path.rml
===================================================================
--- z3c.rml/trunk/src/z3c/rml/tests/input/tag-path.rml	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/tests/input/tag-path.rml	2007-03-28 03:02:22 UTC (rev 73805)
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE document SYSTEM "rml.dtd">
+
+<document filename="tag-path.pdf">
+  <pageDrawing>
+
+    <path x="2cm" y="26cm" close="true">
+      6cm 26cm
+      8cm 23cm
+      4cm 23cm
+    </path>
+
+    <path x="10cm" y="26cm" close="true" fill="true">
+      14cm 26cm
+      16cm 23cm
+      12cm 23cm
+    </path>
+
+    <path x="2cm" y="22cm" >
+      4cm 22cm
+      <moveto>
+        6cm 22cm
+      </moveto>
+      7cm 20.5cm
+      <moveto>
+        8cm 19cm
+      </moveto>
+      6cm 19cm
+      <moveto>
+        4cm 19cm
+      </moveto>
+      3cm 20.5cm
+    </path>
+
+  </pageDrawing>
+</document>

Added: z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerCidFont.rml
===================================================================
--- z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerCidFont.rml	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerCidFont.rml	2007-03-28 03:02:22 UTC (rev 73805)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<!DOCTYPE document SYSTEM "rml.dtd">
+<document filename="tag-registerTTFont.pdf">
+  <docinit>
+    <registerCidFont faceName="HeiseiMin-W3" />
+  </docinit>
+  <template>
+    <pageTemplate id="main">
+      <frame id="first" x1="1cm" y1="1cm" width="19cm" height="26cm"/>
+    </pageTemplate>
+  </template>
+  <story>
+    <para fontName="HeiseiMin-W3" fontSize="40">
+      日本語は難しいですね!
+    </para>
+  </story>
+</document>

Added: z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerTTFont.rml
===================================================================
--- z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerTTFont.rml	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerTTFont.rml	2007-03-28 03:02:22 UTC (rev 73805)
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
+<!DOCTYPE document SYSTEM "rml.dtd">
+<document filename="tag-registerTTFont.pdf">
+  <docinit>
+    <registerTTFont faceName="rina" fileName="rina.ttf"/>
+  </docinit>
+  <template>
+    <pageTemplate id="main">
+      <frame id="first" x1="1cm" y1="1cm" width="19cm" height="26cm"/>
+    </pageTemplate>
+  </template>
+  <story>
+    <para fontName="rina" fontSize="40">Hello World!</para>
+  </story>
+</document>

Added: z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerType1Face.rml
===================================================================
--- z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerType1Face.rml	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/tests/input/tag-registerType1Face.rml	2007-03-28 03:02:22 UTC (rev 73805)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
+<!DOCTYPE document SYSTEM "rml.dtd">
+<document filename="tag-registerType1Face.pdf">
+  <docinit>
+    <registerType1Face afmFile="LeERC___.AFM" pfbFile="LeERC___.PFB" />
+    <registerFont
+        name="LettErrorRobot-Chrome"
+        faceName="LettErrorRobot-Chrome"
+        encName="WinAnsiEncoding" />
+  </docinit>
+  <template>
+    <pageTemplate id="main">
+      <frame id="first" x1="1cm" y1="1cm" width="19cm" height="26cm"/>
+    </pageTemplate>
+  </template>
+  <story>
+    <para fontName="LettErrorRobot-Chrome" fontSize="40">Hello World!</para>
+  </story>
+</document>

Added: z3c.rml/trunk/src/z3c/rml/tests/input/tag-textAnnotation.rml
===================================================================
--- z3c.rml/trunk/src/z3c/rml/tests/input/tag-textAnnotation.rml	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/tests/input/tag-textAnnotation.rml	2007-03-28 03:02:22 UTC (rev 73805)
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE document SYSTEM "rml.dtd">
+
+<document filename="tag-textAnnotation.pdf">
+  <pageDrawing>
+    <textAnnotation>
+      <param name="Rect">0,0,1,1</param>
+      <param name="F">3</param>
+      <param name="escape">6</param>
+X::PDF
+PX(S)
+MT(PINK)
+    </textAnnotation>
+  </pageDrawing>
+</document>

Modified: z3c.rml/trunk/src/z3c/rml/tests/test_rml.py
===================================================================
--- z3c.rml/trunk/src/z3c/rml/tests/test_rml.py	2007-03-27 19:48:05 UTC (rev 73804)
+++ z3c.rml/trunk/src/z3c/rml/tests/test_rml.py	2007-03-28 03:02:22 UTC (rev 73805)
@@ -19,7 +19,7 @@
 import unittest
 import sys
 import z3c.rml.tests
-from z3c.rml import rml2pdf, attr
+from z3c.rml import rml2pdf, attrng
 
 class RMLRenderingTestCase(unittest.TestCase):
 
@@ -30,17 +30,17 @@
 
     def setUp(self):
         # Switch file opener for Image attibute
-        self._imageOpen = attr.Image.open
+        self._fileOpen = attrng.File.open
         def testOpen(img, filename):
             path = os.path.join(os.path.dirname(self._inPath), filename)
             return open(path)
-        attr.Image.open = testOpen
+        attrng.File.open = testOpen
         import z3c.rml.tests.module
         sys.modules['module'] = z3c.rml.tests.module
         sys.modules['mymodule'] = z3c.rml.tests.module
 
     def tearDown(self):
-        attr.Image.open = self._imageOpen
+        attrng.File.open = self._fileOpen
         del sys.modules['module']
         del sys.modules['mymodule']
 
@@ -58,7 +58,7 @@
        inPath = os.path.join(inputDir, filename)
        outPath = os.path.join(outputDir, filename[:-4] + '.pdf')
        # Create new type, so that we can get test matching
-       TestCase = type(filename[:-4].title(), (RMLRenderingTestCase,), {})
+       TestCase = type(filename[:-4], (RMLRenderingTestCase,), {})
        case = TestCase(inPath, outPath)
        suite.addTest(case)
    return suite



More information about the Checkins mailing list