Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bugs parsing Chameleon TALES expressions #26

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions src/lingua/extractors/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from chameleon.program import ElementProgram
from chameleon.zpt.program import MacroProgram
from chameleon.tal import parse_defines
from chameleon.tales import split_parts
from chameleon.tales import match_prefix
from chameleon.utils import decode_htmlentities

from .python import _extract_python
Expand All @@ -23,6 +25,7 @@ def _open(filename):
WHITESPACE = re.compile(u"\s+")
EXPRESSION = re.compile(u"\s*\${(.*?)}\s*")
UNDERSCORE_CALL = re.compile("_\(.*\)")
REPEAT_EXPR = re.compile(u"(?:(?:\([^)]+\))|(?:[^(]\S*))\s+(?P<expr>.*)")


class TranslateContext(object):
Expand Down Expand Up @@ -154,15 +157,40 @@ def add_message(self, msgid, comment=u''):
def get_code_for_attribute(self, attribute, value):
if attribute[0] == TAL_NS:
if attribute[1] in ['content', 'replace']:
yield value
for exp in self.iter_python_expressions(value):
yield exp
if attribute[1] == 'define':
for (scope, var, value) in parse_defines(value):
yield value
for exp in self.iter_python_expressions(value):
yield value
elif attribute[1] == 'repeat':
yield value.split(None, 1)[1]
yield REPEAT_EXPR.match(value.strip()).group('expr')
else:
for source in EXPRESSION.findall(value):
yield source
for exp in self.iter_python_expressions(source):
yield exp

def iter_python_expressions(self, expr):
"""Iterates over translatable python expressions in a TALES expression.

Yields each Python expression in the TALES expression that has an
underscore call in it.
"""
if not UNDERSCORE_CALL.search(expr):
# Short-circuit when there's nothing we're interested in
raise StopIteration

for part in split_parts.split(expr):
part = part.strip()
prefix = match_prefix(part)
if prefix is not None:
part = part[len(prefix.group(0)):]
elif part.startswith('structure '):
# Backwards compatibility for structure as a keyword
part = part[10:]

if UNDERSCORE_CALL.search(part):
yield part

def parse_python(self, source):
if not isinstance(source, bytes):
Expand Down
58 changes: 58 additions & 0 deletions tests/extractors/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,61 @@ def test_translate_entities_in_python_expression():
</html>
'''
list(extract_xml('filename', _options()))


@pytest.mark.usefixtures('fake_source')
def test_translate_repeat_expression():
global source
source = b'''\
<html xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="lingua">
<div tal:repeat="(key, value) dict(a=1).iteritems()"></div>
</html>
'''
messages = list(extract_xml('filename', _options()))
assert len(messages) == 0


@pytest.mark.usefixtures('fake_source')
def test_translate_prefixed_expressions():
global source
source = b'''\
<html xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="lingua">
<div tal:content="string:Foobar"></div>
<div tal:content="structure:123"></div>
<div tal:content="python:2 + 2"></div>
${string:Foobar}
${structure:123}
</html>
'''
messages = list(extract_xml('filename', _options()))
assert len(messages) == 0


@pytest.mark.usefixtures('fake_source')
def test_translate_structure_as_keyword():
global source
source = b'''\
<html xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="lingua">
<div tal:content="structure 123"></div>
</html>
'''
messages = list(extract_xml('filename', _options()))
assert len(messages) == 0


@pytest.mark.usefixtures('fake_source')
def test_translate_python_expressions():
global source
source = b'''\
<html xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="lingua">
<div tal:content="_(u'Foo')|_(u'Bar')"></div>
<div tal:replace="python:_(u'Fuz')|python:_(u'Baz')"></div>
</html>
'''
messages = list(extract_xml('filename', _options()))
assert len(messages) == 4
assert {m.msgid for m in messages} == {u'Foo', u'Bar', u'Fuz', u'Baz'}