# HG changeset patch # User cmlenz # Date 1152018576 0 # Node ID 1f3cd91325d99509bf91c0287acb569b2c0335c1 # Parent 512eb72dbb1956957543e90e7602480c8d3b8405 Fix a number of escaping problems: * `Markup` instances were get escaped * Expressions in text nodes no longer escape quotes (#9) diff --git a/markup/core.py b/markup/core.py --- a/markup/core.py +++ b/markup/core.py @@ -238,8 +238,8 @@ def __repr__(self): return '<%s "%s">' % (self.__class__.__name__, self) - def join(self, seq): - return Markup(unicode(self).join([escape(item, quotes=False) + def join(self, seq, escape_quotes=True): + return Markup(unicode(self).join([escape(item, quotes=escape_quotes) for item in seq])) def stripentities(self, keepxmlentities=False): diff --git a/markup/filters.py b/markup/filters.py --- a/markup/filters.py +++ b/markup/filters.py @@ -118,7 +118,7 @@ if kind is Stream.TEXT: textbuf.append(data) elif prev_kind is Stream.TEXT: - text = Markup('').join(textbuf) + text = Markup('').join(textbuf, escape_quotes=False) text = self._TRAILING_SPACE.sub('', text) text = self._LINE_COLLAPSE.sub('\n', text) yield Stream.TEXT, Markup(text), pos @@ -128,7 +128,9 @@ yield kind, data, pos if textbuf: - text = self._LINE_COLLAPSE.sub('\n', ''.join(textbuf)) + text = Markup('').join(textbuf, escape_quotes=False) + text = self._TRAILING_SPACE.sub('', text) + text = self._LINE_COLLAPSE.sub('\n', text) yield Stream.TEXT, Markup(text), pos diff --git a/markup/template.py b/markup/template.py --- a/markup/template.py +++ b/markup/template.py @@ -157,9 +157,10 @@ class Directive(object): """Abstract base class for template directives. - A directive is basically a callable that takes two parameters: `ctxt` is - the template data context, and `stream` is an iterable over the events that - the directive applies to. + A directive is basically a callable that takes three positional arguments: + `ctxt` is the template data context, `stream` is an iterable over the + events that the directive applies to, and `directives` is is a list of + other directives on the same stream that need to be applied. Directives can be "anonymous" or "registered". Registered directives can be applied by the template author using an XML attribute with the @@ -470,7 +471,7 @@ """ __slots__ = [] - def __call__(self, stream, ctxt, directives=None): + def __call__(self, stream, ctxt, directives): kind, data, pos = stream.next() yield Template.EXPR, self.expr, pos @@ -507,7 +508,7 @@ """ __slots__ = [] - def __call__(self, stream, ctxt, directives=None): + def __call__(self, stream, ctxt, directives): if self.expr: strip = self.expr.evaluate(ctxt) else: @@ -580,7 +581,7 @@ See the documentation of `py:choose` for usage. """ - def __call__(self, stream, ctxt, directives=None): + def __call__(self, stream, ctxt, directives): choose = ctxt['_choose'] if choose.matched: return [] @@ -602,7 +603,7 @@ See the documentation of `py:choose` for usage. """ - def __call__(self, stream, ctxt, directives=None): + def __call__(self, stream, ctxt, directives): choose = ctxt['_choose'] if choose.matched: return [] @@ -809,7 +810,7 @@ # succeeds, and the string will be chopped up into individual # characters if isinstance(result, basestring): - yield Stream.TEXT, unicode(result), pos + yield Stream.TEXT, result, pos else: # Test if the expression evaluated to an iterable, in which # case we yield the individual items diff --git a/markup/tests/template.py b/markup/tests/template.py --- a/markup/tests/template.py +++ b/markup/tests/template.py @@ -15,7 +15,7 @@ import unittest import sys -from markup.core import Stream +from markup.core import Markup, Stream from markup.template import BadDirectiveError, Context, Template, \ TemplateSyntaxError @@ -35,6 +35,30 @@ """, str(tmpl.generate(Context(items=items)))) + def test_update_existing_attr(self): + """ + Verify that an attribute value that evaluates to `None` removes an + existing attribute of that name. + """ + tmpl = Template(""" + + """) + self.assertEqual(""" + + """, str(tmpl.generate())) + + def test_remove_existing_attr(self): + """ + Verify that an attribute value that evaluates to `None` removes an + existing attribute of that name. + """ + tmpl = Template(""" + + """) + self.assertEqual(""" + + """, str(tmpl.generate())) + class ChooseDirectiveTestCase(unittest.TestCase): """Tests for the `py:choose` template directive and the complementary @@ -339,6 +363,40 @@ self.assertEqual(2, e.lineno) self.assertEqual(10, e.offset) + def test_markup_noescape(self): + """ + Verify that outputting context data that is a `Markup` instance is not + escaped. + """ + tmpl = Template("""
+ $myvar +
""") + self.assertEqual("""
+ foo +
""", str(tmpl.generate(Context(myvar=Markup('foo'))))) + + def test_text_noescape_quotes(self): + """ + Verify that outputting context data in text nodes doesn't escape quotes. + """ + tmpl = Template("""
+ $myvar +
""") + self.assertEqual("""
+ "foo" +
""", str(tmpl.generate(Context(myvar='"foo"')))) + + def test_attr_escape_quotes(self): + """ + Verify that outputting context data in attribtes escapes quotes. + """ + tmpl = Template("""
+ +
""") + self.assertEqual("""
+ +
""", str(tmpl.generate(Context(myvar='"foo"')))) + def suite(): suite = unittest.TestSuite()