[FIX] translate: separate html_translate from xml_translate, fix serialization

The serialization gives different results for some elements.  For instance, an
empty element `i` must be serialized as `<i/>` in XML and `<i></i>` in HTML.
It must be done like that, otherwise the world (of web browsers) would break.

Add a test case for `html_translate`.
This commit is contained in:
Raphael Collet
2015-10-22 16:33:56 +02:00
parent 060a898f9f
commit 6c8e53a087
3 changed files with 50 additions and 26 deletions
+9 -1
View File
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import unittest
from openerp.tools.translate import quote, unquote, xml_translate
from openerp.tools.translate import quote, unquote, xml_translate, html_translate
class TranslationToolsTestCase(unittest.TestCase):
@@ -142,3 +142,11 @@ class TranslationToolsTestCase(unittest.TestCase):
self.assertEquals(result, source)
self.assertItemsEqual(terms,
['<span class="oe_menu_text">Blah</span>', 'More <b class="caret"/>'])
def test_translate_html(self):
""" Test xml_translate() and html_translate() with <i> elements. """
source = """<i class="fa-check"></i>"""
result = xml_translate(lambda term: term, source)
self.assertEquals(result, """<i class="fa-check"/>""")
result = html_translate(lambda term: term, source)
self.assertEquals(result, source)
+3 -3
View File
@@ -15,7 +15,7 @@ import xmlrpclib
from openerp.tools import float_round, frozendict, html_sanitize, ustr, OrderedSet
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT
from openerp.tools.translate import xml_translate
from openerp.tools.translate import html_translate
DATE_LENGTH = len(date.today().strftime(DATE_FORMAT))
DATETIME_LENGTH = len(datetime.now().strftime(DATETIME_FORMAT))
@@ -1245,9 +1245,9 @@ class Html(_String):
def _setup_attrs(self, model, name):
super(Html, self)._setup_attrs(model, name)
# Translated sanitized html fields must use xml_translate or a callable.
# Translated sanitized html fields must use html_translate or a callable.
if self.translate and not callable(self.translate) and self.sanitize:
self.translate = xml_translate
self.translate = html_translate
_column_sanitize = property(attrgetter('sanitize'))
_related_sanitize = property(attrgetter('sanitize'))
+38 -22
View File
@@ -151,16 +151,8 @@ TRANSLATED_ATTRS = {
'string', 'help', 'sum', 'avg', 'confirm', 'placeholder', 'alt', 'title',
}
def serialize(tag, attrib, content):
""" Return a serialized element with the given `tag`, attributes
`attrib`, and already-serialized `content`.
"""
elem = etree.tostring(etree.Element(tag, attrib))
assert elem.endswith("/>")
return "%s>%s</%s>" % (elem[:-2], content, tag) if content else elem
class XMLTranslator(object):
""" A sequence of serialized xml items, with some of them to translate
""" A sequence of serialized XML/HTML items, with some of them to translate
(todo) and others already translated (done). The purpose of this object
is to simplify the handling of phrasing elements (like <b>) that must be
translated together with their surrounding text.
@@ -177,8 +169,9 @@ class XMLTranslator(object):
</div>
"""
def __init__(self, callback):
def __init__(self, callback, method):
self.callback = callback # callback function to translate terms
self.method = method # serialization method ('xml' or 'html')
self._done = [] # translated strings
self._todo = [] # todo strings that come after _done
self.needs_trans = False # whether todo needs translation
@@ -227,12 +220,12 @@ class XMLTranslator(object):
):
# do not translate the contents of the node
tail, node.tail = node.tail, None
self.done(etree.tostring(node))
self.done(etree.tostring(node, method=self.method))
self.todo(escape(tail or ""))
return
# process children nodes locally in child_trans
child_trans = XMLTranslator(self.callback)
child_trans = XMLTranslator(self.callback, self.method)
child_trans.todo(escape(node.text or ""))
for child in node:
child_trans.process(child)
@@ -241,38 +234,61 @@ class XMLTranslator(object):
node.tag in TRANSLATED_ELEMENTS and
not any(attr.startswith("t-") for attr in node.attrib)):
# serialize the node element as todo
self.todo(serialize(node.tag, node.attrib, child_trans.get_todo()),
self.todo(self.serialize(node.tag, node.attrib, child_trans.get_todo()),
child_trans.needs_trans)
else:
# complete translations and serialize result as done
for attr in TRANSLATED_ATTRS:
if node.get(attr):
node.set(attr, self.process_text(node.get(attr)))
self.done(serialize(node.tag, node.attrib, child_trans.get_done()))
self.done(self.serialize(node.tag, node.attrib, child_trans.get_done()))
# add node tail as todo
self.todo(escape(node.tail or ""))
def serialize(self, tag, attrib, content):
""" Return a serialized element with the given `tag`, attributes
`attrib`, and already-serialized `content`.
"""
if content:
elem = etree.tostring(etree.Element(tag, attrib), method='xml')
assert elem.endswith("/>")
return "%s>%s</%s>" % (elem[:-2], content, tag)
else:
return etree.tostring(etree.Element(tag, attrib), method=self.method)
def xml_translate(callback, value):
""" Translate an XML value (string), using `callback` for translating text
appearing in `value`. If `value` is not XML valid, it is parsed with an HTML
parser instead, and some element wrapping is done to make the parsing work.
appearing in `value`.
"""
if not value:
return value
trans = XMLTranslator(callback)
trans = XMLTranslator(callback, 'xml')
try:
root = etree.fromstring(encode(value))
trans.process(root)
return trans.get_done()
except etree.ParseError:
wrapped = "<div>%s</div>" % encode(value)
root = etree.fromstring(wrapped, etree.HTMLParser(encoding='utf-8'))
# html > body > div
trans.process(root[0][0])
return trans.get_done()[5:-6]
# fallback in case it is a translated term...
root = etree.fromstring("<div>%s</div>" % encode(value))
trans.process(root)
return trans.get_done()[5:-6] # remove tags <div> and </div>
def html_translate(callback, value):
""" Translate an HTML value (string), using `callback` for translating text
appearing in `value`.
"""
if not value:
return value
trans = XMLTranslator(callback, 'html')
wrapped = "<div>%s</div>" % encode(value)
root = etree.fromstring(wrapped, etree.HTMLParser(encoding='utf-8'))
# html > body > div
trans.process(root[0][0])
return trans.get_done()[5:-6] # remove tags <div> and </div>
#