Skip to content
Snippets Groups Projects
Commit 45d26bc2 authored by Simon Lejeune's avatar Simon Lejeune
Browse files

[ADD] QWeb: xml namespaces support in static template

With this patch, QWeb is able to support xml namespace declaration and
prefixes. As this patch only deals with static template, the only method
we have to patch is `_compile_static_node`. We make it able to serialize
etree node having an nsmap by formating them to restore the xml prefix
if present or to declare the xmlns attribute if needed. To know if the
namespace definition is needed, we compare the tuples of (xmlprefix,
xmldefinition) of the node and its parent. The namespace definition of
the parent is passed by a dict in the `options` key. We took care of
dealing with the mutable + recursion thing by working with copy of this
namespace definition dict.

Two tests have been introduced, basically what we pass to QWeb is what
it has to return.
parent b3a11093
Branches
Tags
No related merge requests found
......@@ -8,7 +8,7 @@ from itertools import count
from textwrap import dedent
import werkzeug
from werkzeug.utils import escape as _escape
from itertools import izip, tee
from itertools import chain, izip, tee
import __builtin__
builtin_defaults = {name: getattr(__builtin__, name) for name in dir(__builtin__)}
......@@ -269,6 +269,7 @@ class QWeb(object):
_options['ast_calls'] = []
_options['root'] = element.getroottree()
_options['last_path_node'] = None
_options['nsmap'] = {}
# generate ast
......@@ -700,14 +701,42 @@ class QWeb(object):
def _compile_static_node(self, el, options):
""" Compile a purely static element into a list of AST nodes. """
content = self._compile_directive_content(el, options)
if el.tag == 't':
extra_attrib = {}
if not el.nsmap:
unqualified_el_tag = el_tag = el.tag
content = self._compile_directive_content(el, options)
else:
# Etree will remove the ns prefixes indirection by inlining the corresponding
# nsmap definition into the tag attribute. Restore the tag and prefix here.
unqualified_el_tag = etree.QName(el.tag).localname
el_tag = unqualified_el_tag
if el.prefix:
el_tag = '%s:%s' % (el.prefix, el_tag)
# If `el` introduced new namespaces, write them as attribute by using the
# `extra_attrib` dict.
for ns_prefix, ns_definition in set(el.nsmap.items()) - set(options['nsmap'].items()):
if ns_prefix is None:
extra_attrib['xmlns'] = ns_definition
else:
extra_attrib['xmlns:%s' % ns_prefix] = ns_definition
# Update the dict of inherited namespaces before continuing the recursion. Note:
# since `options['nsmap']` is a dict (and therefore mutable) and we do **not**
# want changes done in deeper recursion to bevisible in earlier ones, we'll pass
# a copy before continuing the recursion and restore the original afterwards.
original_nsmap = options['nsmap']
options['nsmap'] = dict(options['nsmap'], **el.nsmap)
content = self._compile_directive_content(el, options)
options['nsmap'] = original_nsmap
if unqualified_el_tag == 't':
return content
tag = u'<%s%s' % (el.tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in el.attrib.iteritems()]))
if el.tag in self._void_elements:
tag = u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in chain(el.attrib.iteritems(), extra_attrib.iteritems())]))
if unqualified_el_tag in self._void_elements:
return [self._append(ast.Str(tag + '/>'))] + content
else:
return [self._append(ast.Str(tag + '>'))] + content + [self._append(ast.Str('</%s>' % el.tag))]
return [self._append(ast.Str(tag + '>'))] + content + [self._append(ast.Str('</%s>' % el_tag))]
def _compile_static_attributes(self, el, options):
""" Compile the static attributes of the given element into a list of
......
......@@ -15,6 +15,10 @@ from odoo.tests.common import TransactionCase
from odoo.addons.base.ir.ir_qweb import QWebException
def dedent_and_strip(string):
return ''.join([line.strip() for line in string.splitlines()])
class TestQWebTField(TransactionCase):
def setUp(self):
super(TestQWebTField, self).setUp()
......@@ -65,6 +69,106 @@ class TestQWebTField(TransactionCase):
self.engine.render(field, {'company': None})
class TestQWebNS(TransactionCase):
def test_render_static_xml_with_namespace(self):
""" Test the rendering on a namespaced view with no static content. The resulting string should be untouched.
"""
expected_result = """
<root>
<h:table xmlns:h="http://www.example.org/table">
<h:tr>
<h:td xmlns:h="http://www.w3.org/TD/html4/">Apples</h:td>
<h:td>Bananas</h:td>
</h:tr>
</h:table>
<f:table xmlns:f="http://www.example.org/furniture">
<f:width>80</f:width>
</f:table>
</root>
"""
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
'arch': """
<t t-name="base.dummy">%s</t>
""" % expected_result
})
self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
def test_render_static_xml_with_namespace_2(self):
""" Test the rendering on a namespaced view with no static content. The resulting string should be untouched.
"""
expected_result = """
<html xmlns="http://www.w3.org/HTML/1998/html4" xmlns:xdc="http://www.xml.com/books">
<head>
<title>Book Review</title>
</head>
<body>
<xdc:bookreview>
<xdc:title>XML: A Primer</xdc:title>
<table>
<tr align="center">
<td>Author</td><td>Price</td>
<td>Pages</td><td>Date</td>
</tr>
<tr align="left">
<td><xdc:author>Simon St. Laurent</xdc:author></td>
<td><xdc:price>31.98</xdc:price></td>
<td><xdc:pages>352</xdc:pages></td>
<td><xdc:date>1998/01</xdc:date></td>
</tr>
</table>
</xdc:bookreview>
</body>
</html>
"""
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
'arch': """
<t t-name="base.dummy">%s</t>
""" % expected_result
})
self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
def test_render_static_xml_with_useless_distributed_namespace(self):
""" Test that redundant namespaces are stripped upon rendering.
"""
view1 = self.env['ir.ui.view'].create({
'name': "dummy",
'type': 'qweb',
'arch': """
<t t-name="base.dummy">
<root>
<h:table xmlns:h="http://www.example.org/table">
<h:tr xmlns:h="http://www.example.org/table">
<h:td xmlns:h="http://www.w3.org/TD/html4/">Apples</h:td>
<h:td xmlns:h="http://www.example.org/table">Bananas</h:td>
</h:tr>
</h:table>
</root>
</t>
"""
})
expected_result = """
<root>
<h:table xmlns:h="http://www.example.org/table">
<h:tr>
<h:td xmlns:h="http://www.w3.org/TD/html4/">Apples</h:td>
<h:td>Bananas</h:td>
</h:tr>
</h:table>
</root>
"""
self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
from copy import deepcopy
class FileSystemLoader(object):
def __init__(self, path):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment