[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:
@@ -2,7 +2,7 @@
|
|||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
import unittest
|
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):
|
class TranslationToolsTestCase(unittest.TestCase):
|
||||||
|
|
||||||
@@ -142,3 +142,11 @@ class TranslationToolsTestCase(unittest.TestCase):
|
|||||||
self.assertEquals(result, source)
|
self.assertEquals(result, source)
|
||||||
self.assertItemsEqual(terms,
|
self.assertItemsEqual(terms,
|
||||||
['<span class="oe_menu_text">Blah</span>', 'More <b class="caret"/>'])
|
['<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
@@ -15,7 +15,7 @@ import xmlrpclib
|
|||||||
from openerp.tools import float_round, frozendict, html_sanitize, ustr, OrderedSet
|
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_DATE_FORMAT as DATE_FORMAT
|
||||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_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))
|
DATE_LENGTH = len(date.today().strftime(DATE_FORMAT))
|
||||||
DATETIME_LENGTH = len(datetime.now().strftime(DATETIME_FORMAT))
|
DATETIME_LENGTH = len(datetime.now().strftime(DATETIME_FORMAT))
|
||||||
@@ -1245,9 +1245,9 @@ class Html(_String):
|
|||||||
|
|
||||||
def _setup_attrs(self, model, name):
|
def _setup_attrs(self, model, name):
|
||||||
super(Html, self)._setup_attrs(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:
|
if self.translate and not callable(self.translate) and self.sanitize:
|
||||||
self.translate = xml_translate
|
self.translate = html_translate
|
||||||
|
|
||||||
_column_sanitize = property(attrgetter('sanitize'))
|
_column_sanitize = property(attrgetter('sanitize'))
|
||||||
_related_sanitize = property(attrgetter('sanitize'))
|
_related_sanitize = property(attrgetter('sanitize'))
|
||||||
|
|||||||
+38
-22
@@ -151,16 +151,8 @@ TRANSLATED_ATTRS = {
|
|||||||
'string', 'help', 'sum', 'avg', 'confirm', 'placeholder', 'alt', 'title',
|
'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):
|
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
|
(todo) and others already translated (done). The purpose of this object
|
||||||
is to simplify the handling of phrasing elements (like <b>) that must be
|
is to simplify the handling of phrasing elements (like <b>) that must be
|
||||||
translated together with their surrounding text.
|
translated together with their surrounding text.
|
||||||
@@ -177,8 +169,9 @@ class XMLTranslator(object):
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, callback):
|
def __init__(self, callback, method):
|
||||||
self.callback = callback # callback function to translate terms
|
self.callback = callback # callback function to translate terms
|
||||||
|
self.method = method # serialization method ('xml' or 'html')
|
||||||
self._done = [] # translated strings
|
self._done = [] # translated strings
|
||||||
self._todo = [] # todo strings that come after _done
|
self._todo = [] # todo strings that come after _done
|
||||||
self.needs_trans = False # whether todo needs translation
|
self.needs_trans = False # whether todo needs translation
|
||||||
@@ -227,12 +220,12 @@ class XMLTranslator(object):
|
|||||||
):
|
):
|
||||||
# do not translate the contents of the node
|
# do not translate the contents of the node
|
||||||
tail, node.tail = node.tail, None
|
tail, node.tail = node.tail, None
|
||||||
self.done(etree.tostring(node))
|
self.done(etree.tostring(node, method=self.method))
|
||||||
self.todo(escape(tail or ""))
|
self.todo(escape(tail or ""))
|
||||||
return
|
return
|
||||||
|
|
||||||
# process children nodes locally in child_trans
|
# 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 ""))
|
child_trans.todo(escape(node.text or ""))
|
||||||
for child in node:
|
for child in node:
|
||||||
child_trans.process(child)
|
child_trans.process(child)
|
||||||
@@ -241,38 +234,61 @@ class XMLTranslator(object):
|
|||||||
node.tag in TRANSLATED_ELEMENTS and
|
node.tag in TRANSLATED_ELEMENTS and
|
||||||
not any(attr.startswith("t-") for attr in node.attrib)):
|
not any(attr.startswith("t-") for attr in node.attrib)):
|
||||||
# serialize the node element as todo
|
# 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)
|
child_trans.needs_trans)
|
||||||
else:
|
else:
|
||||||
# complete translations and serialize result as done
|
# complete translations and serialize result as done
|
||||||
for attr in TRANSLATED_ATTRS:
|
for attr in TRANSLATED_ATTRS:
|
||||||
if node.get(attr):
|
if node.get(attr):
|
||||||
node.set(attr, self.process_text(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
|
# add node tail as todo
|
||||||
self.todo(escape(node.tail or ""))
|
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):
|
def xml_translate(callback, value):
|
||||||
""" Translate an XML value (string), using `callback` for translating text
|
""" 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
|
appearing in `value`.
|
||||||
parser instead, and some element wrapping is done to make the parsing work.
|
|
||||||
"""
|
"""
|
||||||
if not value:
|
if not value:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
trans = XMLTranslator(callback)
|
trans = XMLTranslator(callback, 'xml')
|
||||||
try:
|
try:
|
||||||
root = etree.fromstring(encode(value))
|
root = etree.fromstring(encode(value))
|
||||||
trans.process(root)
|
trans.process(root)
|
||||||
return trans.get_done()
|
return trans.get_done()
|
||||||
except etree.ParseError:
|
except etree.ParseError:
|
||||||
wrapped = "<div>%s</div>" % encode(value)
|
# fallback in case it is a translated term...
|
||||||
root = etree.fromstring(wrapped, etree.HTMLParser(encoding='utf-8'))
|
root = etree.fromstring("<div>%s</div>" % encode(value))
|
||||||
# html > body > div
|
trans.process(root)
|
||||||
trans.process(root[0][0])
|
return trans.get_done()[5:-6] # remove tags <div> and </div>
|
||||||
return trans.get_done()[5:-6]
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|||||||
Reference in New Issue
Block a user