Commits

Rick Copeland committed 57815da

Add basic support for XML serialization

Comments (0)

Files changed (6)

-* XML serialization to HTML4, HTML5
 * Track line number information in Text  templates
 * Write CSS => IR compiler (and document language)
    - Make sure it replicates the features of Sass plus even more
+HTML_EMPTY_ATTRS=set([ 'disabled',  'readonly',  'checked', 'selected' ])
+HTML_OPTIONAL_END_TAGS=set([
+    'area',
+    'base',
+    'body',
+    'br',
+    'col',
+    'colgroup',
+    'dd',
+    'dt',
+    'head',
+    'hr',
+    'html',
+    'img',
+    'input',
+    'li',
+    'link',
+    'meta',
+    'option',
+    'p',
+    'param',
+    'tbody',
+    'td',
+    'tfoot',
+    'th',
+    'thead',
+    'tr',
+    ])
 
 class AttrNode(Node):
 
-    def __init__(self, attr, value, guard=None):
+    def __init__(self, attr, value, guard=None, mode='xml'):
         super(AttrNode, self).__init__()
         self.attr = attr
         self.value = value
         self.guard = guard
+        self.mode = mode
 
     def py(self):
-        s = 'yield \' %s="%s"\'' % (self.attr, self.value)
+        x,gen = gen_name(), gen_name()
+        def _body():
+            yield self.line('def %s():' % gen)
+            for part in self.value:
+                for line in part.py():
+                    yield line.indent()
+            yield self.line("%s = ''.join(%s())" % (gen,gen))
+            yield self.line(
+                'for %s in self.__kj__.render_attrs({%r:%s}, %r):'
+                % (x, self.attr, gen, self.mode))
+            yield self.line('    yield %s' % x)
         if self.guard:
-            yield self.line('if %s: %s' % (self.guard, s))
+            yield self.line('if %s:' % self.guard)
+            for l in _body():
+                yield l.indent()
         else:
-            yield self.line(s)
+            for l in _body(): yield l
 
 class AttrsNode(Node):
 
-    def __init__(self, attrs, guard=None):
+    def __init__(self, attrs, guard=None, mode='xml'):
         super(AttrsNode, self).__init__()
         self.attrs = attrs
         self.guard = guard
+        self.mode = mode
 
     def py(self):
-        k,v = gen_name(), gen_name()
+        x = gen_name()
         def _body():
-            yield self.line('for %s,%s in self.__kj__.iter_attrs(%s):' % (k, v, self.attrs))
-            yield self.line('    yield \' %%s="%%s"\' %% (%s, %s)' % (k,v))
+            yield self.line(
+                'for %s in self.__kj__.render_attrs(%s, %r):' % (x, self.attrs, self.mode))
+            yield self.line('    yield %s' % x)
         if self.guard:
             yield self.line('if %s:' % self.guard)
             for l in _body():
 
 import kajiki
 from .util import flattener
+from .html_utils import HTML_EMPTY_ATTRS
 
 class _obj(object):
     def __init__(self, **kw):
             case=self._case,
             import_=self._import,
             escape=self._escape,
-            iter_attrs=self._iter_attrs)
+            render_attrs=self._render_attrs)
         self._switch_stack = []
         self.__globals__.update(context)
 
         else:
             return escape(unicode(value))
 
-    def _iter_attrs(self, attrs):
+    def _render_attrs(self, attrs, mode):
         if hasattr(attrs, 'items'):
             attrs = attrs.items()
         for k,v in attrs:
-            yield k, self._escape(v)
+            if v is None: continue
+            if mode.startswith('html') and k in HTML_EMPTY_ATTRS: yield ' '+k.upper()
+            else: yield ' %s="%s"' % (k,self._escape(v))
 
 def Template(ns):
     dct = {}

kajiki/tests/test_xml.py

         rsp = tpl(dict(header=False)).__kj__.render()
         assert rsp == '<div>Header</div>', rsp
 
+    def test_html_attrs(self):
+        tpl = XMLTemplate('''<input type="checkbox" checked="$checked"/>''', mode='xml')
+        rsp = tpl(dict(checked=True)).__kj__.render()
+        assert rsp == '<input type="checkbox" checked="True"/>', rsp
+        tpl = XMLTemplate('''<input type="checkbox" checked="$checked"/>''', mode='html')
+        rsp = tpl(dict(checked=True)).__kj__.render()
+        assert rsp == '<input type="checkbox" CHECKED>', rsp
+
 if __name__ == '__main__':
     main()

kajiki/xml_template.py

 from . import ir
 from . import template
 from .markup_template import QDIRECTIVES, QDIRECTIVES_DICT
+from .html_utils import HTML_OPTIONAL_END_TAGS
 
 _pattern = r'''
 \$(?:
 
 def XMLTemplate(
     source=None,
-    filename=None):
+    filename=None,
+    mode='xml'):
     if source is None:
         source = open(filename).read()
     if filename is None:
         filename = '<string>'
     doc = _Parser(filename, source).parse()
     expand(doc)
-    ir_ = _Compiler(filename, doc).compile()
+    ir_ = _Compiler(filename, doc, mode).compile()
     return template.from_ir(ir_)
 
 class _Compiler(object):
             # Handle directives
             compiler = getattr(self, '_compile_%s' % node.tagName.split(':')[-1])
             return compiler(node)
-        elif self.mode == 'xml':
+        else:
             return self._compile_xml(node)
-        else:
-            return self._compile_html(node)
 
     def _compile_xml(self, node):
         content = attrs = guard = None
         yield ir.TextNode(u'<%s' % node.tagName, guard)
         for k,v in node.attributes.items():
             tc = _TextCompiler(self.filename, v, node.lineno)
-            v = u''.join(n.text for n in tc)
+            v = list(tc)
+            # v = u''.join(n.text for n in tc)
             if k == 'py:content':
                 content = node.getAttribute('py:content')
                 continue
             elif k == 'py:attrs':
                 attrs = node.getAttribute('py:attrs')
                 continue
-            yield ir.AttrNode(k, v, guard)
+            yield ir.AttrNode(k, v, guard, self.mode)
         if attrs:
-            yield ir.AttrsNode(attrs, guard)
+            yield ir.AttrsNode(attrs, guard, self.mode)
         if content:
             yield ir.TextNode(u'>', guard)
             yield ir.ExprNode(content)
                 for cn in node.childNodes:
                     for x in self._compile_node(cn):
                         yield x
-                yield ir.TextNode(u'</%s>' % node.tagName, guard)
+                if not (self.mode.startswith('html')
+                        and node.tagName in HTML_OPTIONAL_END_TAGS):
+                    yield ir.TextNode(u'</%s>' % node.tagName, guard)
             else:
-                yield ir.TextNode(u'/>', guard)
+                if (self.mode.startswith('html')
+                    and node.tagName in HTML_OPTIONAL_END_TAGS):
+                    yield ir.TextNode(u'>', guard)
+                else:
+                    yield ir.TextNode(u'/>', guard)
 
     def _compile_replace(self, node):
         yield ir.ExprNode(node.getAttribute('value'))