| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2006-2010 Edgewall Software |
|---|
| 4 | # All rights reserved. |
|---|
| 5 | # |
|---|
| 6 | # This software is licensed as described in the file COPYING, which |
|---|
| 7 | # you should have received as part of this distribution. The terms |
|---|
| 8 | # are also available at http://genshi.edgewall.org/wiki/License. |
|---|
| 9 | # |
|---|
| 10 | # This software consists of voluntary contributions made by many |
|---|
| 11 | # individuals. For the exact contribution history, see the revision |
|---|
| 12 | # history and logs, available at http://genshi.edgewall.org/log/. |
|---|
| 13 | |
|---|
| 14 | import doctest |
|---|
| 15 | import re |
|---|
| 16 | import sys |
|---|
| 17 | import unittest |
|---|
| 18 | |
|---|
| 19 | from genshi.template import directives, MarkupTemplate, TextTemplate, \ |
|---|
| 20 | TemplateRuntimeError, TemplateSyntaxError |
|---|
| 21 | |
|---|
| 22 | |
|---|
| 23 | class AttrsDirectiveTestCase(unittest.TestCase): |
|---|
| 24 | """Tests for the `py:attrs` template directive.""" |
|---|
| 25 | |
|---|
| 26 | def test_combined_with_loop(self): |
|---|
| 27 | """ |
|---|
| 28 | Verify that the directive has access to the loop variables. |
|---|
| 29 | """ |
|---|
| 30 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 31 | <elem py:for="item in items" py:attrs="item"/> |
|---|
| 32 | </doc>""") |
|---|
| 33 | items = [{'id': 1}, {'id': 2}] |
|---|
| 34 | self.assertEqual("""<doc> |
|---|
| 35 | <elem id="1"/><elem id="2"/> |
|---|
| 36 | </doc>""", tmpl.generate(items=items).render(encoding=None)) |
|---|
| 37 | |
|---|
| 38 | def test_update_existing_attr(self): |
|---|
| 39 | """ |
|---|
| 40 | Verify that an attribute value that evaluates to `None` removes an |
|---|
| 41 | existing attribute of that name. |
|---|
| 42 | """ |
|---|
| 43 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 44 | <elem class="foo" py:attrs="{'class': 'bar'}"/> |
|---|
| 45 | </doc>""") |
|---|
| 46 | self.assertEqual("""<doc> |
|---|
| 47 | <elem class="bar"/> |
|---|
| 48 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 49 | |
|---|
| 50 | def test_remove_existing_attr(self): |
|---|
| 51 | """ |
|---|
| 52 | Verify that an attribute value that evaluates to `None` removes an |
|---|
| 53 | existing attribute of that name. |
|---|
| 54 | """ |
|---|
| 55 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 56 | <elem class="foo" py:attrs="{'class': None}"/> |
|---|
| 57 | </doc>""") |
|---|
| 58 | self.assertEqual("""<doc> |
|---|
| 59 | <elem/> |
|---|
| 60 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 61 | |
|---|
| 62 | |
|---|
| 63 | class ChooseDirectiveTestCase(unittest.TestCase): |
|---|
| 64 | """Tests for the `py:choose` template directive and the complementary |
|---|
| 65 | directives `py:when` and `py:otherwise`.""" |
|---|
| 66 | |
|---|
| 67 | def test_multiple_true_whens(self): |
|---|
| 68 | """ |
|---|
| 69 | Verify that, if multiple `py:when` bodies match, only the first is |
|---|
| 70 | output. |
|---|
| 71 | """ |
|---|
| 72 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/" py:choose=""> |
|---|
| 73 | <span py:when="1 == 1">1</span> |
|---|
| 74 | <span py:when="2 == 2">2</span> |
|---|
| 75 | <span py:when="3 == 3">3</span> |
|---|
| 76 | </div>""") |
|---|
| 77 | self.assertEqual("""<div> |
|---|
| 78 | <span>1</span> |
|---|
| 79 | </div>""", tmpl.generate().render(encoding=None)) |
|---|
| 80 | |
|---|
| 81 | def test_otherwise(self): |
|---|
| 82 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/" py:choose=""> |
|---|
| 83 | <span py:when="False">hidden</span> |
|---|
| 84 | <span py:otherwise="">hello</span> |
|---|
| 85 | </div>""") |
|---|
| 86 | self.assertEqual("""<div> |
|---|
| 87 | <span>hello</span> |
|---|
| 88 | </div>""", tmpl.generate().render(encoding=None)) |
|---|
| 89 | |
|---|
| 90 | def test_nesting(self): |
|---|
| 91 | """ |
|---|
| 92 | Verify that `py:choose` blocks can be nested: |
|---|
| 93 | """ |
|---|
| 94 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 95 | <div py:choose="1"> |
|---|
| 96 | <div py:when="1" py:choose="3"> |
|---|
| 97 | <span py:when="2">2</span> |
|---|
| 98 | <span py:when="3">3</span> |
|---|
| 99 | </div> |
|---|
| 100 | </div> |
|---|
| 101 | </doc>""") |
|---|
| 102 | self.assertEqual("""<doc> |
|---|
| 103 | <div> |
|---|
| 104 | <div> |
|---|
| 105 | <span>3</span> |
|---|
| 106 | </div> |
|---|
| 107 | </div> |
|---|
| 108 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 109 | |
|---|
| 110 | def test_complex_nesting(self): |
|---|
| 111 | """ |
|---|
| 112 | Verify more complex nesting. |
|---|
| 113 | """ |
|---|
| 114 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 115 | <div py:choose="1"> |
|---|
| 116 | <div py:when="1" py:choose=""> |
|---|
| 117 | <span py:when="2">OK</span> |
|---|
| 118 | <span py:when="1">FAIL</span> |
|---|
| 119 | </div> |
|---|
| 120 | </div> |
|---|
| 121 | </doc>""") |
|---|
| 122 | self.assertEqual("""<doc> |
|---|
| 123 | <div> |
|---|
| 124 | <div> |
|---|
| 125 | <span>OK</span> |
|---|
| 126 | </div> |
|---|
| 127 | </div> |
|---|
| 128 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 129 | |
|---|
| 130 | def test_complex_nesting_otherwise(self): |
|---|
| 131 | """ |
|---|
| 132 | Verify more complex nesting using otherwise. |
|---|
| 133 | """ |
|---|
| 134 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 135 | <div py:choose="1"> |
|---|
| 136 | <div py:when="1" py:choose="2"> |
|---|
| 137 | <span py:when="1">FAIL</span> |
|---|
| 138 | <span py:otherwise="">OK</span> |
|---|
| 139 | </div> |
|---|
| 140 | </div> |
|---|
| 141 | </doc>""") |
|---|
| 142 | self.assertEqual("""<doc> |
|---|
| 143 | <div> |
|---|
| 144 | <div> |
|---|
| 145 | <span>OK</span> |
|---|
| 146 | </div> |
|---|
| 147 | </div> |
|---|
| 148 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 149 | |
|---|
| 150 | def test_when_with_strip(self): |
|---|
| 151 | """ |
|---|
| 152 | Verify that a when directive with a strip directive actually strips of |
|---|
| 153 | the outer element. |
|---|
| 154 | """ |
|---|
| 155 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 156 | <div py:choose="" py:strip=""> |
|---|
| 157 | <span py:otherwise="">foo</span> |
|---|
| 158 | </div> |
|---|
| 159 | </doc>""") |
|---|
| 160 | self.assertEqual("""<doc> |
|---|
| 161 | <span>foo</span> |
|---|
| 162 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 163 | |
|---|
| 164 | def test_when_outside_choose(self): |
|---|
| 165 | """ |
|---|
| 166 | Verify that a `when` directive outside of a `choose` directive is |
|---|
| 167 | reported as an error. |
|---|
| 168 | """ |
|---|
| 169 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 170 | <div py:when="xy" /> |
|---|
| 171 | </doc>""") |
|---|
| 172 | self.assertRaises(TemplateRuntimeError, str, tmpl.generate()) |
|---|
| 173 | |
|---|
| 174 | def test_otherwise_outside_choose(self): |
|---|
| 175 | """ |
|---|
| 176 | Verify that an `otherwise` directive outside of a `choose` directive is |
|---|
| 177 | reported as an error. |
|---|
| 178 | """ |
|---|
| 179 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 180 | <div py:otherwise="" /> |
|---|
| 181 | </doc>""") |
|---|
| 182 | self.assertRaises(TemplateRuntimeError, str, tmpl.generate()) |
|---|
| 183 | |
|---|
| 184 | def test_when_without_test(self): |
|---|
| 185 | """ |
|---|
| 186 | Verify that an `when` directive that doesn't have a `test` attribute |
|---|
| 187 | is reported as an error. |
|---|
| 188 | """ |
|---|
| 189 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 190 | <div py:choose="" py:strip=""> |
|---|
| 191 | <py:when>foo</py:when> |
|---|
| 192 | </div> |
|---|
| 193 | </doc>""") |
|---|
| 194 | self.assertRaises(TemplateRuntimeError, str, tmpl.generate()) |
|---|
| 195 | |
|---|
| 196 | def test_when_without_test_but_with_choose_value(self): |
|---|
| 197 | """ |
|---|
| 198 | Verify that an `when` directive that doesn't have a `test` attribute |
|---|
| 199 | works as expected as long as the parent `choose` directive has a test |
|---|
| 200 | expression. |
|---|
| 201 | """ |
|---|
| 202 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 203 | <div py:choose="foo" py:strip=""> |
|---|
| 204 | <py:when>foo</py:when> |
|---|
| 205 | </div> |
|---|
| 206 | </doc>""") |
|---|
| 207 | self.assertEqual("""<doc> |
|---|
| 208 | foo |
|---|
| 209 | </doc>""", tmpl.generate(foo='Yeah').render(encoding=None)) |
|---|
| 210 | |
|---|
| 211 | def test_otherwise_without_test(self): |
|---|
| 212 | """ |
|---|
| 213 | Verify that an `otherwise` directive can be used without a `test` |
|---|
| 214 | attribute. |
|---|
| 215 | """ |
|---|
| 216 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 217 | <div py:choose="" py:strip=""> |
|---|
| 218 | <py:otherwise>foo</py:otherwise> |
|---|
| 219 | </div> |
|---|
| 220 | </doc>""") |
|---|
| 221 | self.assertEqual("""<doc> |
|---|
| 222 | foo |
|---|
| 223 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 224 | |
|---|
| 225 | def test_as_element(self): |
|---|
| 226 | """ |
|---|
| 227 | Verify that the directive can also be used as an element. |
|---|
| 228 | """ |
|---|
| 229 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 230 | <py:choose> |
|---|
| 231 | <py:when test="1 == 1">1</py:when> |
|---|
| 232 | <py:when test="2 == 2">2</py:when> |
|---|
| 233 | <py:when test="3 == 3">3</py:when> |
|---|
| 234 | </py:choose> |
|---|
| 235 | </doc>""") |
|---|
| 236 | self.assertEqual("""<doc> |
|---|
| 237 | 1 |
|---|
| 238 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 239 | |
|---|
| 240 | def test_in_text_template(self): |
|---|
| 241 | """ |
|---|
| 242 | Verify that the directive works as expected in a text template. |
|---|
| 243 | """ |
|---|
| 244 | tmpl = TextTemplate("""#choose |
|---|
| 245 | #when 1 == 1 |
|---|
| 246 | 1 |
|---|
| 247 | #end |
|---|
| 248 | #when 2 == 2 |
|---|
| 249 | 2 |
|---|
| 250 | #end |
|---|
| 251 | #when 3 == 3 |
|---|
| 252 | 3 |
|---|
| 253 | #end |
|---|
| 254 | #end""") |
|---|
| 255 | self.assertEqual(""" 1\n""", |
|---|
| 256 | tmpl.generate().render(encoding=None)) |
|---|
| 257 | |
|---|
| 258 | |
|---|
| 259 | class DefDirectiveTestCase(unittest.TestCase): |
|---|
| 260 | """Tests for the `py:def` template directive.""" |
|---|
| 261 | |
|---|
| 262 | def test_function_with_strip(self): |
|---|
| 263 | """ |
|---|
| 264 | Verify that a named template function with a strip directive actually |
|---|
| 265 | strips of the outer element. |
|---|
| 266 | """ |
|---|
| 267 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 268 | <div py:def="echo(what)" py:strip=""> |
|---|
| 269 | <b>${what}</b> |
|---|
| 270 | </div> |
|---|
| 271 | ${echo('foo')} |
|---|
| 272 | </doc>""") |
|---|
| 273 | self.assertEqual("""<doc> |
|---|
| 274 | <b>foo</b> |
|---|
| 275 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 276 | |
|---|
| 277 | def test_exec_in_replace(self): |
|---|
| 278 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 279 | <p py:def="echo(greeting, name='world')" class="message"> |
|---|
| 280 | ${greeting}, ${name}! |
|---|
| 281 | </p> |
|---|
| 282 | <div py:replace="echo('hello')"></div> |
|---|
| 283 | </div>""") |
|---|
| 284 | self.assertEqual("""<div> |
|---|
| 285 | <p class="message"> |
|---|
| 286 | hello, world! |
|---|
| 287 | </p> |
|---|
| 288 | </div>""", tmpl.generate().render(encoding=None)) |
|---|
| 289 | |
|---|
| 290 | def test_as_element(self): |
|---|
| 291 | """ |
|---|
| 292 | Verify that the directive can also be used as an element. |
|---|
| 293 | """ |
|---|
| 294 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 295 | <py:def function="echo(what)"> |
|---|
| 296 | <b>${what}</b> |
|---|
| 297 | </py:def> |
|---|
| 298 | ${echo('foo')} |
|---|
| 299 | </doc>""") |
|---|
| 300 | self.assertEqual("""<doc> |
|---|
| 301 | <b>foo</b> |
|---|
| 302 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 303 | |
|---|
| 304 | def test_nested_defs(self): |
|---|
| 305 | """ |
|---|
| 306 | Verify that a template function defined inside a conditional block can |
|---|
| 307 | be called from outside that block. |
|---|
| 308 | """ |
|---|
| 309 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 310 | <py:if test="semantic"> |
|---|
| 311 | <strong py:def="echo(what)">${what}</strong> |
|---|
| 312 | </py:if> |
|---|
| 313 | <py:if test="not semantic"> |
|---|
| 314 | <b py:def="echo(what)">${what}</b> |
|---|
| 315 | </py:if> |
|---|
| 316 | ${echo('foo')} |
|---|
| 317 | </doc>""") |
|---|
| 318 | self.assertEqual("""<doc> |
|---|
| 319 | <strong>foo</strong> |
|---|
| 320 | </doc>""", tmpl.generate(semantic=True).render(encoding=None)) |
|---|
| 321 | |
|---|
| 322 | def test_function_with_default_arg(self): |
|---|
| 323 | """ |
|---|
| 324 | Verify that keyword arguments work with `py:def` directives. |
|---|
| 325 | """ |
|---|
| 326 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 327 | <b py:def="echo(what, bold=False)" py:strip="not bold">${what}</b> |
|---|
| 328 | ${echo('foo')} |
|---|
| 329 | </doc>""") |
|---|
| 330 | self.assertEqual("""<doc> |
|---|
| 331 | foo |
|---|
| 332 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 333 | |
|---|
| 334 | def test_invocation_in_attribute(self): |
|---|
| 335 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 336 | <py:def function="echo(what)">${what or 'something'}</py:def> |
|---|
| 337 | <p class="${echo('foo')}">bar</p> |
|---|
| 338 | </doc>""") |
|---|
| 339 | self.assertEqual("""<doc> |
|---|
| 340 | <p class="foo">bar</p> |
|---|
| 341 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 342 | |
|---|
| 343 | def test_invocation_in_attribute_none(self): |
|---|
| 344 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 345 | <py:def function="echo()">${None}</py:def> |
|---|
| 346 | <p class="${echo()}">bar</p> |
|---|
| 347 | </doc>""") |
|---|
| 348 | self.assertEqual("""<doc> |
|---|
| 349 | <p>bar</p> |
|---|
| 350 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 351 | |
|---|
| 352 | def test_function_raising_typeerror(self): |
|---|
| 353 | def badfunc(): |
|---|
| 354 | raise TypeError |
|---|
| 355 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 356 | <div py:def="dobadfunc()"> |
|---|
| 357 | ${badfunc()} |
|---|
| 358 | </div> |
|---|
| 359 | <div py:content="dobadfunc()"/> |
|---|
| 360 | </html>""") |
|---|
| 361 | self.assertRaises(TypeError, list, tmpl.generate(badfunc=badfunc)) |
|---|
| 362 | |
|---|
| 363 | def test_def_in_matched(self): |
|---|
| 364 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 365 | <head py:match="head">${select('*')}</head> |
|---|
| 366 | <head> |
|---|
| 367 | <py:def function="maketitle(test)"><b py:replace="test" /></py:def> |
|---|
| 368 | <title>${maketitle(True)}</title> |
|---|
| 369 | </head> |
|---|
| 370 | </doc>""") |
|---|
| 371 | self.assertEqual("""<doc> |
|---|
| 372 | <head><title>True</title></head> |
|---|
| 373 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 374 | |
|---|
| 375 | def test_in_text_template(self): |
|---|
| 376 | """ |
|---|
| 377 | Verify that the directive works as expected in a text template. |
|---|
| 378 | """ |
|---|
| 379 | tmpl = TextTemplate(""" |
|---|
| 380 | #def echo(greeting, name='world') |
|---|
| 381 | ${greeting}, ${name}! |
|---|
| 382 | #end |
|---|
| 383 | ${echo('Hi', name='you')} |
|---|
| 384 | """) |
|---|
| 385 | self.assertEqual(""" |
|---|
| 386 | Hi, you! |
|---|
| 387 | |
|---|
| 388 | """, tmpl.generate().render(encoding=None)) |
|---|
| 389 | |
|---|
| 390 | def test_function_with_star_args(self): |
|---|
| 391 | """ |
|---|
| 392 | Verify that a named template function using "star arguments" works as |
|---|
| 393 | expected. |
|---|
| 394 | """ |
|---|
| 395 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 396 | <div py:def="f(*args, **kwargs)"> |
|---|
| 397 | ${repr(args)} |
|---|
| 398 | ${repr(sorted(kwargs.items()))} |
|---|
| 399 | </div> |
|---|
| 400 | ${f(1, 2, a=3, b=4)} |
|---|
| 401 | </doc>""") |
|---|
| 402 | self.assertEqual("""<doc> |
|---|
| 403 | <div> |
|---|
| 404 | [1, 2] |
|---|
| 405 | [('a', 3), ('b', 4)] |
|---|
| 406 | </div> |
|---|
| 407 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 408 | |
|---|
| 409 | |
|---|
| 410 | class ForDirectiveTestCase(unittest.TestCase): |
|---|
| 411 | """Tests for the `py:for` template directive.""" |
|---|
| 412 | |
|---|
| 413 | def test_loop_with_strip(self): |
|---|
| 414 | """ |
|---|
| 415 | Verify that the combining the `py:for` directive with `py:strip` works |
|---|
| 416 | correctly. |
|---|
| 417 | """ |
|---|
| 418 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 419 | <div py:for="item in items" py:strip=""> |
|---|
| 420 | <b>${item}</b> |
|---|
| 421 | </div> |
|---|
| 422 | </doc>""") |
|---|
| 423 | self.assertEqual("""<doc> |
|---|
| 424 | <b>1</b> |
|---|
| 425 | <b>2</b> |
|---|
| 426 | <b>3</b> |
|---|
| 427 | <b>4</b> |
|---|
| 428 | <b>5</b> |
|---|
| 429 | </doc>""", tmpl.generate(items=range(1, 6)).render(encoding=None)) |
|---|
| 430 | |
|---|
| 431 | def test_as_element(self): |
|---|
| 432 | """ |
|---|
| 433 | Verify that the directive can also be used as an element. |
|---|
| 434 | """ |
|---|
| 435 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 436 | <py:for each="item in items"> |
|---|
| 437 | <b>${item}</b> |
|---|
| 438 | </py:for> |
|---|
| 439 | </doc>""") |
|---|
| 440 | self.assertEqual("""<doc> |
|---|
| 441 | <b>1</b> |
|---|
| 442 | <b>2</b> |
|---|
| 443 | <b>3</b> |
|---|
| 444 | <b>4</b> |
|---|
| 445 | <b>5</b> |
|---|
| 446 | </doc>""", tmpl.generate(items=range(1, 6)).render(encoding=None)) |
|---|
| 447 | |
|---|
| 448 | def test_multi_assignment(self): |
|---|
| 449 | """ |
|---|
| 450 | Verify that assignment to tuples works correctly. |
|---|
| 451 | """ |
|---|
| 452 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 453 | <py:for each="k, v in items"> |
|---|
| 454 | <p>key=$k, value=$v</p> |
|---|
| 455 | </py:for> |
|---|
| 456 | </doc>""") |
|---|
| 457 | self.assertEqual("""<doc> |
|---|
| 458 | <p>key=a, value=1</p> |
|---|
| 459 | <p>key=b, value=2</p> |
|---|
| 460 | </doc>""", tmpl.generate(items=(('a', 1), ('b', 2))) |
|---|
| 461 | .render(encoding=None)) |
|---|
| 462 | |
|---|
| 463 | def test_nested_assignment(self): |
|---|
| 464 | """ |
|---|
| 465 | Verify that assignment to nested tuples works correctly. |
|---|
| 466 | """ |
|---|
| 467 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 468 | <py:for each="idx, (k, v) in items"> |
|---|
| 469 | <p>$idx: key=$k, value=$v</p> |
|---|
| 470 | </py:for> |
|---|
| 471 | </doc>""") |
|---|
| 472 | self.assertEqual("""<doc> |
|---|
| 473 | <p>0: key=a, value=1</p> |
|---|
| 474 | <p>1: key=b, value=2</p> |
|---|
| 475 | </doc>""", tmpl.generate(items=enumerate([('a', 1), ('b', 2)])) |
|---|
| 476 | .render(encoding=None)) |
|---|
| 477 | |
|---|
| 478 | def test_not_iterable(self): |
|---|
| 479 | """ |
|---|
| 480 | Verify that assignment to nested tuples works correctly. |
|---|
| 481 | """ |
|---|
| 482 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 483 | <py:for each="item in foo"> |
|---|
| 484 | $item |
|---|
| 485 | </py:for> |
|---|
| 486 | </doc>""", filename='test.html') |
|---|
| 487 | try: |
|---|
| 488 | list(tmpl.generate(foo=12)) |
|---|
| 489 | self.fail('Expected TemplateRuntimeError') |
|---|
| 490 | except TypeError, e: |
|---|
| 491 | assert (str(e) == "iteration over non-sequence" or |
|---|
| 492 | str(e) == "'int' object is not iterable") |
|---|
| 493 | exc_type, exc_value, exc_traceback = sys.exc_info() |
|---|
| 494 | frame = exc_traceback.tb_next |
|---|
| 495 | frames = [] |
|---|
| 496 | while frame.tb_next: |
|---|
| 497 | frame = frame.tb_next |
|---|
| 498 | frames.append(frame) |
|---|
| 499 | self.assertEqual("<Expression u'iter(foo)'>", |
|---|
| 500 | frames[-1].tb_frame.f_code.co_name) |
|---|
| 501 | self.assertEqual('test.html', |
|---|
| 502 | frames[-1].tb_frame.f_code.co_filename) |
|---|
| 503 | self.assertEqual(2, frames[-1].tb_lineno) |
|---|
| 504 | |
|---|
| 505 | def test_for_with_empty_value(self): |
|---|
| 506 | """ |
|---|
| 507 | Verify an empty 'for' value is an error |
|---|
| 508 | """ |
|---|
| 509 | try: |
|---|
| 510 | MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 511 | <py:for each=""> |
|---|
| 512 | empty |
|---|
| 513 | </py:for> |
|---|
| 514 | </doc>""", filename='test.html').generate() |
|---|
| 515 | self.fail('ExpectedTemplateSyntaxError') |
|---|
| 516 | except TemplateSyntaxError, e: |
|---|
| 517 | self.assertEqual('test.html', e.filename) |
|---|
| 518 | if sys.version_info[:2] > (2,4): |
|---|
| 519 | self.assertEqual(2, e.lineno) |
|---|
| 520 | |
|---|
| 521 | |
|---|
| 522 | class IfDirectiveTestCase(unittest.TestCase): |
|---|
| 523 | """Tests for the `py:if` template directive.""" |
|---|
| 524 | |
|---|
| 525 | def test_loop_with_strip(self): |
|---|
| 526 | """ |
|---|
| 527 | Verify that the combining the `py:if` directive with `py:strip` works |
|---|
| 528 | correctly. |
|---|
| 529 | """ |
|---|
| 530 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 531 | <b py:if="foo" py:strip="">${bar}</b> |
|---|
| 532 | </doc>""") |
|---|
| 533 | self.assertEqual("""<doc> |
|---|
| 534 | Hello |
|---|
| 535 | </doc>""", tmpl.generate(foo=True, bar='Hello').render(encoding=None)) |
|---|
| 536 | |
|---|
| 537 | def test_as_element(self): |
|---|
| 538 | """ |
|---|
| 539 | Verify that the directive can also be used as an element. |
|---|
| 540 | """ |
|---|
| 541 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 542 | <py:if test="foo">${bar}</py:if> |
|---|
| 543 | </doc>""") |
|---|
| 544 | self.assertEqual("""<doc> |
|---|
| 545 | Hello |
|---|
| 546 | </doc>""", tmpl.generate(foo=True, bar='Hello').render(encoding=None)) |
|---|
| 547 | |
|---|
| 548 | |
|---|
| 549 | class MatchDirectiveTestCase(unittest.TestCase): |
|---|
| 550 | """Tests for the `py:match` template directive.""" |
|---|
| 551 | |
|---|
| 552 | def test_with_strip(self): |
|---|
| 553 | """ |
|---|
| 554 | Verify that a match template can produce the same kind of element that |
|---|
| 555 | it matched without entering an infinite recursion. |
|---|
| 556 | """ |
|---|
| 557 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 558 | <elem py:match="elem" py:strip=""> |
|---|
| 559 | <div class="elem">${select('text()')}</div> |
|---|
| 560 | </elem> |
|---|
| 561 | <elem>Hey Joe</elem> |
|---|
| 562 | </doc>""") |
|---|
| 563 | self.assertEqual("""<doc> |
|---|
| 564 | <div class="elem">Hey Joe</div> |
|---|
| 565 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 566 | |
|---|
| 567 | def test_without_strip(self): |
|---|
| 568 | """ |
|---|
| 569 | Verify that a match template can produce the same kind of element that |
|---|
| 570 | it matched without entering an infinite recursion. |
|---|
| 571 | """ |
|---|
| 572 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 573 | <elem py:match="elem"> |
|---|
| 574 | <div class="elem">${select('text()')}</div> |
|---|
| 575 | </elem> |
|---|
| 576 | <elem>Hey Joe</elem> |
|---|
| 577 | </doc>""") |
|---|
| 578 | self.assertEqual("""<doc> |
|---|
| 579 | <elem> |
|---|
| 580 | <div class="elem">Hey Joe</div> |
|---|
| 581 | </elem> |
|---|
| 582 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 583 | |
|---|
| 584 | def test_as_element(self): |
|---|
| 585 | """ |
|---|
| 586 | Verify that the directive can also be used as an element. |
|---|
| 587 | """ |
|---|
| 588 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 589 | <py:match path="elem"> |
|---|
| 590 | <div class="elem">${select('text()')}</div> |
|---|
| 591 | </py:match> |
|---|
| 592 | <elem>Hey Joe</elem> |
|---|
| 593 | </doc>""") |
|---|
| 594 | self.assertEqual("""<doc> |
|---|
| 595 | <div class="elem">Hey Joe</div> |
|---|
| 596 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 597 | |
|---|
| 598 | def test_recursive_match_1(self): |
|---|
| 599 | """ |
|---|
| 600 | Match directives are applied recursively, meaning that they are also |
|---|
| 601 | applied to any content they may have produced themselves: |
|---|
| 602 | """ |
|---|
| 603 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 604 | <elem py:match="elem"> |
|---|
| 605 | <div class="elem"> |
|---|
| 606 | ${select('*')} |
|---|
| 607 | </div> |
|---|
| 608 | </elem> |
|---|
| 609 | <elem> |
|---|
| 610 | <subelem> |
|---|
| 611 | <elem/> |
|---|
| 612 | </subelem> |
|---|
| 613 | </elem> |
|---|
| 614 | </doc>""") |
|---|
| 615 | self.assertEqual("""<doc> |
|---|
| 616 | <elem> |
|---|
| 617 | <div class="elem"> |
|---|
| 618 | <subelem> |
|---|
| 619 | <elem> |
|---|
| 620 | <div class="elem"> |
|---|
| 621 | </div> |
|---|
| 622 | </elem> |
|---|
| 623 | </subelem> |
|---|
| 624 | </div> |
|---|
| 625 | </elem> |
|---|
| 626 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 627 | |
|---|
| 628 | def test_recursive_match_2(self): |
|---|
| 629 | """ |
|---|
| 630 | When two or more match templates match the same element and also |
|---|
| 631 | themselves output the element they match, avoiding recursion is even |
|---|
| 632 | more complex, but should work. |
|---|
| 633 | """ |
|---|
| 634 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 635 | <body py:match="body"> |
|---|
| 636 | <div id="header"/> |
|---|
| 637 | ${select('*')} |
|---|
| 638 | </body> |
|---|
| 639 | <body py:match="body"> |
|---|
| 640 | ${select('*')} |
|---|
| 641 | <div id="footer"/> |
|---|
| 642 | </body> |
|---|
| 643 | <body> |
|---|
| 644 | <h1>Foo</h1> |
|---|
| 645 | </body> |
|---|
| 646 | </html>""") |
|---|
| 647 | self.assertEqual("""<html> |
|---|
| 648 | <body> |
|---|
| 649 | <div id="header"/><h1>Foo</h1> |
|---|
| 650 | <div id="footer"/> |
|---|
| 651 | </body> |
|---|
| 652 | </html>""", tmpl.generate().render(encoding=None)) |
|---|
| 653 | |
|---|
| 654 | def test_recursive_match_3(self): |
|---|
| 655 | tmpl = MarkupTemplate("""<test xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 656 | <py:match path="b[@type='bullet']"> |
|---|
| 657 | <bullet>${select('*|text()')}</bullet> |
|---|
| 658 | </py:match> |
|---|
| 659 | <py:match path="group[@type='bullet']"> |
|---|
| 660 | <ul>${select('*')}</ul> |
|---|
| 661 | </py:match> |
|---|
| 662 | <py:match path="b"> |
|---|
| 663 | <generic>${select('*|text()')}</generic> |
|---|
| 664 | </py:match> |
|---|
| 665 | |
|---|
| 666 | <b> |
|---|
| 667 | <group type="bullet"> |
|---|
| 668 | <b type="bullet">1</b> |
|---|
| 669 | <b type="bullet">2</b> |
|---|
| 670 | </group> |
|---|
| 671 | </b> |
|---|
| 672 | </test> |
|---|
| 673 | """) |
|---|
| 674 | self.assertEqual("""<test> |
|---|
| 675 | <generic> |
|---|
| 676 | <ul><bullet>1</bullet><bullet>2</bullet></ul> |
|---|
| 677 | </generic> |
|---|
| 678 | </test>""", tmpl.generate().render(encoding=None)) |
|---|
| 679 | |
|---|
| 680 | def test_not_match_self(self): |
|---|
| 681 | """ |
|---|
| 682 | See http://genshi.edgewall.org/ticket/77 |
|---|
| 683 | """ |
|---|
| 684 | tmpl = MarkupTemplate("""<html xmlns="http://www.w3.org/1999/xhtml" |
|---|
| 685 | xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 686 | <body py:match="body" py:content="select('*')" /> |
|---|
| 687 | <h1 py:match="h1"> |
|---|
| 688 | ${select('text()')} |
|---|
| 689 | Goodbye! |
|---|
| 690 | </h1> |
|---|
| 691 | <body> |
|---|
| 692 | <h1>Hello!</h1> |
|---|
| 693 | </body> |
|---|
| 694 | </html>""") |
|---|
| 695 | self.assertEqual("""<html xmlns="http://www.w3.org/1999/xhtml"> |
|---|
| 696 | <body><h1> |
|---|
| 697 | Hello! |
|---|
| 698 | Goodbye! |
|---|
| 699 | </h1></body> |
|---|
| 700 | </html>""", tmpl.generate().render(encoding=None)) |
|---|
| 701 | |
|---|
| 702 | def test_select_text_in_element(self): |
|---|
| 703 | """ |
|---|
| 704 | See http://genshi.edgewall.org/ticket/77#comment:1 |
|---|
| 705 | """ |
|---|
| 706 | tmpl = MarkupTemplate("""<html xmlns="http://www.w3.org/1999/xhtml" |
|---|
| 707 | xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 708 | <body py:match="body" py:content="select('*')" /> |
|---|
| 709 | <h1 py:match="h1"> |
|---|
| 710 | <text> |
|---|
| 711 | ${select('text()')} |
|---|
| 712 | </text> |
|---|
| 713 | Goodbye! |
|---|
| 714 | </h1> |
|---|
| 715 | <body> |
|---|
| 716 | <h1>Hello!</h1> |
|---|
| 717 | </body> |
|---|
| 718 | </html>""") |
|---|
| 719 | self.assertEqual("""<html xmlns="http://www.w3.org/1999/xhtml"> |
|---|
| 720 | <body><h1> |
|---|
| 721 | <text> |
|---|
| 722 | Hello! |
|---|
| 723 | </text> |
|---|
| 724 | Goodbye! |
|---|
| 725 | </h1></body> |
|---|
| 726 | </html>""", tmpl.generate().render(encoding=None)) |
|---|
| 727 | |
|---|
| 728 | def test_select_all_attrs(self): |
|---|
| 729 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 730 | <div py:match="elem" py:attrs="select('@*')"> |
|---|
| 731 | ${select('text()')} |
|---|
| 732 | </div> |
|---|
| 733 | <elem id="joe">Hey Joe</elem> |
|---|
| 734 | </doc>""") |
|---|
| 735 | self.assertEqual("""<doc> |
|---|
| 736 | <div id="joe"> |
|---|
| 737 | Hey Joe |
|---|
| 738 | </div> |
|---|
| 739 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 740 | |
|---|
| 741 | def test_select_all_attrs_empty(self): |
|---|
| 742 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 743 | <div py:match="elem" py:attrs="select('@*')"> |
|---|
| 744 | ${select('text()')} |
|---|
| 745 | </div> |
|---|
| 746 | <elem>Hey Joe</elem> |
|---|
| 747 | </doc>""") |
|---|
| 748 | self.assertEqual("""<doc> |
|---|
| 749 | <div> |
|---|
| 750 | Hey Joe |
|---|
| 751 | </div> |
|---|
| 752 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 753 | |
|---|
| 754 | def test_select_all_attrs_in_body(self): |
|---|
| 755 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 756 | <div py:match="elem"> |
|---|
| 757 | Hey ${select('text()')} ${select('@*')} |
|---|
| 758 | </div> |
|---|
| 759 | <elem title="Cool">Joe</elem> |
|---|
| 760 | </doc>""") |
|---|
| 761 | self.assertEqual("""<doc> |
|---|
| 762 | <div> |
|---|
| 763 | Hey Joe Cool |
|---|
| 764 | </div> |
|---|
| 765 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 766 | |
|---|
| 767 | def test_def_in_match(self): |
|---|
| 768 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 769 | <py:def function="maketitle(test)"><b py:replace="test" /></py:def> |
|---|
| 770 | <head py:match="head">${select('*')}</head> |
|---|
| 771 | <head><title>${maketitle(True)}</title></head> |
|---|
| 772 | </doc>""") |
|---|
| 773 | self.assertEqual("""<doc> |
|---|
| 774 | <head><title>True</title></head> |
|---|
| 775 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 776 | |
|---|
| 777 | def test_match_with_xpath_variable(self): |
|---|
| 778 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 779 | <span py:match="*[name()=$tagname]"> |
|---|
| 780 | Hello ${select('@name')} |
|---|
| 781 | </span> |
|---|
| 782 | <greeting name="Dude"/> |
|---|
| 783 | </div>""") |
|---|
| 784 | self.assertEqual("""<div> |
|---|
| 785 | <span> |
|---|
| 786 | Hello Dude |
|---|
| 787 | </span> |
|---|
| 788 | </div>""", tmpl.generate(tagname='greeting').render(encoding=None)) |
|---|
| 789 | self.assertEqual("""<div> |
|---|
| 790 | <greeting name="Dude"/> |
|---|
| 791 | </div>""", tmpl.generate(tagname='sayhello').render(encoding=None)) |
|---|
| 792 | |
|---|
| 793 | def test_content_directive_in_match(self): |
|---|
| 794 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 795 | <div py:match="foo">I said <q py:content="select('text()')">something</q>.</div> |
|---|
| 796 | <foo>bar</foo> |
|---|
| 797 | </html>""") |
|---|
| 798 | self.assertEqual("""<html> |
|---|
| 799 | <div>I said <q>bar</q>.</div> |
|---|
| 800 | </html>""", tmpl.generate().render(encoding=None)) |
|---|
| 801 | |
|---|
| 802 | def test_cascaded_matches(self): |
|---|
| 803 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 804 | <body py:match="body">${select('*')}</body> |
|---|
| 805 | <head py:match="head">${select('title')}</head> |
|---|
| 806 | <body py:match="body">${select('*')}<hr /></body> |
|---|
| 807 | <head><title>Welcome to Markup</title></head> |
|---|
| 808 | <body><h2>Are you ready to mark up?</h2></body> |
|---|
| 809 | </html>""") |
|---|
| 810 | self.assertEqual("""<html> |
|---|
| 811 | <head><title>Welcome to Markup</title></head> |
|---|
| 812 | <body><h2>Are you ready to mark up?</h2><hr/></body> |
|---|
| 813 | </html>""", tmpl.generate().render(encoding=None)) |
|---|
| 814 | |
|---|
| 815 | def test_multiple_matches(self): |
|---|
| 816 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 817 | <input py:match="form//input" py:attrs="select('@*')" |
|---|
| 818 | value="${values[str(select('@name'))]}" /> |
|---|
| 819 | <form><p py:for="field in fields"> |
|---|
| 820 | <label>${field.capitalize()}</label> |
|---|
| 821 | <input type="text" name="${field}" /> |
|---|
| 822 | </p></form> |
|---|
| 823 | </html>""") |
|---|
| 824 | fields = ['hello_%s' % i for i in range(5)] |
|---|
| 825 | values = dict([('hello_%s' % i, i) for i in range(5)]) |
|---|
| 826 | self.assertEqual("""<html> |
|---|
| 827 | <form><p> |
|---|
| 828 | <label>Hello_0</label> |
|---|
| 829 | <input value="0" type="text" name="hello_0"/> |
|---|
| 830 | </p><p> |
|---|
| 831 | <label>Hello_1</label> |
|---|
| 832 | <input value="1" type="text" name="hello_1"/> |
|---|
| 833 | </p><p> |
|---|
| 834 | <label>Hello_2</label> |
|---|
| 835 | <input value="2" type="text" name="hello_2"/> |
|---|
| 836 | </p><p> |
|---|
| 837 | <label>Hello_3</label> |
|---|
| 838 | <input value="3" type="text" name="hello_3"/> |
|---|
| 839 | </p><p> |
|---|
| 840 | <label>Hello_4</label> |
|---|
| 841 | <input value="4" type="text" name="hello_4"/> |
|---|
| 842 | </p></form> |
|---|
| 843 | </html>""", tmpl.generate(fields=fields, values=values) |
|---|
| 844 | .render(encoding=None)) |
|---|
| 845 | |
|---|
| 846 | def test_namespace_context(self): |
|---|
| 847 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/" |
|---|
| 848 | xmlns:x="http://www.example.org/"> |
|---|
| 849 | <div py:match="x:foo">Foo</div> |
|---|
| 850 | <foo xmlns="http://www.example.org/"/> |
|---|
| 851 | </html>""") |
|---|
| 852 | # FIXME: there should be a way to strip out unwanted/unused namespaces, |
|---|
| 853 | # such as the "x" in this example |
|---|
| 854 | self.assertEqual("""<html xmlns:x="http://www.example.org/"> |
|---|
| 855 | <div>Foo</div> |
|---|
| 856 | </html>""", tmpl.generate().render(encoding=None)) |
|---|
| 857 | |
|---|
| 858 | def test_match_with_position_predicate(self): |
|---|
| 859 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 860 | <p py:match="body/p[1]" class="first">${select('*|text()')}</p> |
|---|
| 861 | <body> |
|---|
| 862 | <p>Foo</p> |
|---|
| 863 | <p>Bar</p> |
|---|
| 864 | </body> |
|---|
| 865 | </html>""") |
|---|
| 866 | self.assertEqual("""<html> |
|---|
| 867 | <body> |
|---|
| 868 | <p class="first">Foo</p> |
|---|
| 869 | <p>Bar</p> |
|---|
| 870 | </body> |
|---|
| 871 | </html>""", tmpl.generate().render(encoding=None)) |
|---|
| 872 | |
|---|
| 873 | def test_match_with_closure(self): |
|---|
| 874 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 875 | <p py:match="body//p" class="para">${select('*|text()')}</p> |
|---|
| 876 | <body> |
|---|
| 877 | <p>Foo</p> |
|---|
| 878 | <div><p>Bar</p></div> |
|---|
| 879 | </body> |
|---|
| 880 | </html>""") |
|---|
| 881 | self.assertEqual("""<html> |
|---|
| 882 | <body> |
|---|
| 883 | <p class="para">Foo</p> |
|---|
| 884 | <div><p class="para">Bar</p></div> |
|---|
| 885 | </body> |
|---|
| 886 | </html>""", tmpl.generate().render(encoding=None)) |
|---|
| 887 | |
|---|
| 888 | def test_match_without_closure(self): |
|---|
| 889 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 890 | <p py:match="body/p" class="para">${select('*|text()')}</p> |
|---|
| 891 | <body> |
|---|
| 892 | <p>Foo</p> |
|---|
| 893 | <div><p>Bar</p></div> |
|---|
| 894 | </body> |
|---|
| 895 | </html>""") |
|---|
| 896 | self.assertEqual("""<html> |
|---|
| 897 | <body> |
|---|
| 898 | <p class="para">Foo</p> |
|---|
| 899 | <div><p>Bar</p></div> |
|---|
| 900 | </body> |
|---|
| 901 | </html>""", tmpl.generate().render(encoding=None)) |
|---|
| 902 | |
|---|
| 903 | def test_match_with_once_attribute(self): |
|---|
| 904 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 905 | <py:match path="body" once="true"><body> |
|---|
| 906 | <div id="wrap"> |
|---|
| 907 | ${select("*")} |
|---|
| 908 | </div> |
|---|
| 909 | </body></py:match> |
|---|
| 910 | <body> |
|---|
| 911 | <p>Foo</p> |
|---|
| 912 | </body> |
|---|
| 913 | <body> |
|---|
| 914 | <p>Bar</p> |
|---|
| 915 | </body> |
|---|
| 916 | </html>""") |
|---|
| 917 | self.assertEqual("""<html> |
|---|
| 918 | <body> |
|---|
| 919 | <div id="wrap"> |
|---|
| 920 | <p>Foo</p> |
|---|
| 921 | </div> |
|---|
| 922 | </body> |
|---|
| 923 | <body> |
|---|
| 924 | <p>Bar</p> |
|---|
| 925 | </body> |
|---|
| 926 | </html>""", tmpl.generate().render(encoding=None)) |
|---|
| 927 | |
|---|
| 928 | def test_match_with_recursive_attribute(self): |
|---|
| 929 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 930 | <py:match path="elem" recursive="false"><elem> |
|---|
| 931 | <div class="elem"> |
|---|
| 932 | ${select('*')} |
|---|
| 933 | </div> |
|---|
| 934 | </elem></py:match> |
|---|
| 935 | <elem> |
|---|
| 936 | <subelem> |
|---|
| 937 | <elem/> |
|---|
| 938 | </subelem> |
|---|
| 939 | </elem> |
|---|
| 940 | </doc>""") |
|---|
| 941 | self.assertEqual("""<doc> |
|---|
| 942 | <elem> |
|---|
| 943 | <div class="elem"> |
|---|
| 944 | <subelem> |
|---|
| 945 | <elem/> |
|---|
| 946 | </subelem> |
|---|
| 947 | </div> |
|---|
| 948 | </elem> |
|---|
| 949 | </doc>""", tmpl.generate().render(encoding=None)) |
|---|
| 950 | |
|---|
| 951 | # See http://genshi.edgewall.org/ticket/254/ |
|---|
| 952 | def test_triple_match_produces_no_duplicate_items(self): |
|---|
| 953 | tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 954 | <div py:match="div[@id='content']" py:attrs="select('@*')" once="true"> |
|---|
| 955 | <ul id="tabbed_pane" /> |
|---|
| 956 | ${select('*')} |
|---|
| 957 | </div> |
|---|
| 958 | |
|---|
| 959 | <body py:match="body" once="true" buffer="false"> |
|---|
| 960 | ${select('*|text()')} |
|---|
| 961 | </body> |
|---|
| 962 | <body py:match="body" once="true" buffer="false"> |
|---|
| 963 | ${select('*|text()')} |
|---|
| 964 | </body> |
|---|
| 965 | |
|---|
| 966 | <body> |
|---|
| 967 | <div id="content"> |
|---|
| 968 | <h1>Ticket X</h1> |
|---|
| 969 | </div> |
|---|
| 970 | </body> |
|---|
| 971 | </doc>""") |
|---|
| 972 | output = tmpl.generate().render('xhtml', doctype='xhtml') |
|---|
| 973 | matches = re.findall("tabbed_pane", output) |
|---|
| 974 | self.assertNotEqual(None, matches) |
|---|
| 975 | self.assertEqual(1, len(matches)) |
|---|
| 976 | |
|---|
| 977 | def test_match_multiple_times1(self): |
|---|
| 978 | # See http://genshi.edgewall.org/ticket/370 |
|---|
| 979 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 980 | <py:match path="body[@id='content']/h2" /> |
|---|
| 981 | <head py:match="head" /> |
|---|
| 982 | <head py:match="head" /> |
|---|
| 983 | <head /> |
|---|
| 984 | <body /> |
|---|
| 985 | </html>""") |
|---|
| 986 | self.assertEqual("""<html> |
|---|
| 987 | <head/> |
|---|
| 988 | <body/> |
|---|
| 989 | </html>""", tmpl.generate().render()) |
|---|
| 990 | |
|---|
| 991 | def test_match_multiple_times2(self): |
|---|
| 992 | # See http://genshi.edgewall.org/ticket/370 |
|---|
| 993 | tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 994 | <py:match path="body/div[@id='properties']" /> |
|---|
| 995 | <head py:match="head" /> |
|---|
| 996 | <head py:match="head" /> |
|---|
| 997 | <head/> |
|---|
| 998 | <body> |
|---|
| 999 | <div id="properties">Foo</div> |
|---|
| 1000 | </body> |
|---|
| 1001 | </html>""") |
|---|
| 1002 | self.assertEqual("""<html> |
|---|
| 1003 | <head/> |
|---|
| 1004 | <body> |
|---|
| 1005 | </body> |
|---|
| 1006 | </html>""", tmpl.generate().render()) |
|---|
| 1007 | |
|---|
| 1008 | def test_match_multiple_times3(self): |
|---|
| 1009 | # See http://genshi.edgewall.org/ticket/370#comment:12 |
|---|
| 1010 | tmpl = MarkupTemplate("""<?xml version="1.0"?> |
|---|
| 1011 | <root xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1012 | <py:match path="foo/bar"> |
|---|
| 1013 | <zzzzz/> |
|---|
| 1014 | </py:match> |
|---|
| 1015 | <foo> |
|---|
| 1016 | <bar/> |
|---|
| 1017 | <bar/> |
|---|
| 1018 | </foo> |
|---|
| 1019 | <bar/> |
|---|
| 1020 | </root>""") |
|---|
| 1021 | self.assertEqual("""<?xml version="1.0"?>\n<root> |
|---|
| 1022 | <foo> |
|---|
| 1023 | <zzzzz/> |
|---|
| 1024 | <zzzzz/> |
|---|
| 1025 | </foo> |
|---|
| 1026 | <bar/> |
|---|
| 1027 | </root>""", tmpl.generate().render()) |
|---|
| 1028 | |
|---|
| 1029 | # FIXME |
|---|
| 1030 | #def test_match_after_step(self): |
|---|
| 1031 | # tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1032 | # <span py:match="div/greeting"> |
|---|
| 1033 | # Hello ${select('@name')} |
|---|
| 1034 | # </span> |
|---|
| 1035 | # <greeting name="Dude" /> |
|---|
| 1036 | # </div>""") |
|---|
| 1037 | # self.assertEqual("""<div> |
|---|
| 1038 | # <span> |
|---|
| 1039 | # Hello Dude |
|---|
| 1040 | # </span> |
|---|
| 1041 | # </div>""", tmpl.generate().render(encoding=None)) |
|---|
| 1042 | |
|---|
| 1043 | |
|---|
| 1044 | class ContentDirectiveTestCase(unittest.TestCase): |
|---|
| 1045 | """Tests for the `py:content` template directive.""" |
|---|
| 1046 | |
|---|
| 1047 | def test_as_element(self): |
|---|
| 1048 | try: |
|---|
| 1049 | MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1050 | <py:content foo="">Foo</py:content> |
|---|
| 1051 | </doc>""", filename='test.html').generate() |
|---|
| 1052 | self.fail('Expected TemplateSyntaxError') |
|---|
| 1053 | except TemplateSyntaxError, e: |
|---|
| 1054 | self.assertEqual('test.html', e.filename) |
|---|
| 1055 | self.assertEqual(2, e.lineno) |
|---|
| 1056 | |
|---|
| 1057 | |
|---|
| 1058 | class ReplaceDirectiveTestCase(unittest.TestCase): |
|---|
| 1059 | """Tests for the `py:replace` template directive.""" |
|---|
| 1060 | |
|---|
| 1061 | def test_replace_with_empty_value(self): |
|---|
| 1062 | """ |
|---|
| 1063 | Verify that the directive raises an apprioriate exception when an empty |
|---|
| 1064 | expression is supplied. |
|---|
| 1065 | """ |
|---|
| 1066 | try: |
|---|
| 1067 | MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1068 | <elem py:replace="">Foo</elem> |
|---|
| 1069 | </doc>""", filename='test.html').generate() |
|---|
| 1070 | self.fail('Expected TemplateSyntaxError') |
|---|
| 1071 | except TemplateSyntaxError, e: |
|---|
| 1072 | self.assertEqual('test.html', e.filename) |
|---|
| 1073 | self.assertEqual(2, e.lineno) |
|---|
| 1074 | |
|---|
| 1075 | def test_as_element(self): |
|---|
| 1076 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1077 | <py:replace value="title" /> |
|---|
| 1078 | </div>""", filename='test.html') |
|---|
| 1079 | self.assertEqual("""<div> |
|---|
| 1080 | Test |
|---|
| 1081 | </div>""", tmpl.generate(title='Test').render(encoding=None)) |
|---|
| 1082 | |
|---|
| 1083 | |
|---|
| 1084 | class StripDirectiveTestCase(unittest.TestCase): |
|---|
| 1085 | """Tests for the `py:strip` template directive.""" |
|---|
| 1086 | |
|---|
| 1087 | def test_strip_false(self): |
|---|
| 1088 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1089 | <div py:strip="False"><b>foo</b></div> |
|---|
| 1090 | </div>""") |
|---|
| 1091 | self.assertEqual("""<div> |
|---|
| 1092 | <div><b>foo</b></div> |
|---|
| 1093 | </div>""", tmpl.generate().render(encoding=None)) |
|---|
| 1094 | |
|---|
| 1095 | def test_strip_empty(self): |
|---|
| 1096 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1097 | <div py:strip=""><b>foo</b></div> |
|---|
| 1098 | </div>""") |
|---|
| 1099 | self.assertEqual("""<div> |
|---|
| 1100 | <b>foo</b> |
|---|
| 1101 | </div>""", tmpl.generate().render(encoding=None)) |
|---|
| 1102 | |
|---|
| 1103 | |
|---|
| 1104 | class WithDirectiveTestCase(unittest.TestCase): |
|---|
| 1105 | """Tests for the `py:with` template directive.""" |
|---|
| 1106 | |
|---|
| 1107 | def test_shadowing(self): |
|---|
| 1108 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1109 | ${x} |
|---|
| 1110 | <span py:with="x = x * 2" py:replace="x"/> |
|---|
| 1111 | ${x} |
|---|
| 1112 | </div>""") |
|---|
| 1113 | self.assertEqual("""<div> |
|---|
| 1114 | 42 |
|---|
| 1115 | 84 |
|---|
| 1116 | 42 |
|---|
| 1117 | </div>""", tmpl.generate(x=42).render(encoding=None)) |
|---|
| 1118 | |
|---|
| 1119 | def test_as_element(self): |
|---|
| 1120 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1121 | <py:with vars="x = x * 2">${x}</py:with> |
|---|
| 1122 | </div>""") |
|---|
| 1123 | self.assertEqual("""<div> |
|---|
| 1124 | 84 |
|---|
| 1125 | </div>""", tmpl.generate(x=42).render(encoding=None)) |
|---|
| 1126 | |
|---|
| 1127 | def test_multiple_vars_same_name(self): |
|---|
| 1128 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1129 | <py:with vars=" |
|---|
| 1130 | foo = 'bar'; |
|---|
| 1131 | foo = foo.replace('r', 'z') |
|---|
| 1132 | "> |
|---|
| 1133 | $foo |
|---|
| 1134 | </py:with> |
|---|
| 1135 | </div>""") |
|---|
| 1136 | self.assertEqual("""<div> |
|---|
| 1137 | baz |
|---|
| 1138 | </div>""", tmpl.generate(x=42).render(encoding=None)) |
|---|
| 1139 | |
|---|
| 1140 | def test_multiple_vars_single_assignment(self): |
|---|
| 1141 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1142 | <py:with vars="x = y = z = 1">${x} ${y} ${z}</py:with> |
|---|
| 1143 | </div>""") |
|---|
| 1144 | self.assertEqual("""<div> |
|---|
| 1145 | 1 1 1 |
|---|
| 1146 | </div>""", tmpl.generate(x=42).render(encoding=None)) |
|---|
| 1147 | |
|---|
| 1148 | def test_nested_vars_single_assignment(self): |
|---|
| 1149 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1150 | <py:with vars="x, (y, z) = (1, (2, 3))">${x} ${y} ${z}</py:with> |
|---|
| 1151 | </div>""") |
|---|
| 1152 | self.assertEqual("""<div> |
|---|
| 1153 | 1 2 3 |
|---|
| 1154 | </div>""", tmpl.generate(x=42).render(encoding=None)) |
|---|
| 1155 | |
|---|
| 1156 | def test_multiple_vars_trailing_semicolon(self): |
|---|
| 1157 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1158 | <py:with vars="x = x * 2; y = x / 2;">${x} ${y}</py:with> |
|---|
| 1159 | </div>""") |
|---|
| 1160 | self.assertEqual("""<div> |
|---|
| 1161 | 84 %s |
|---|
| 1162 | </div>""" % (84 / 2), tmpl.generate(x=42).render(encoding=None)) |
|---|
| 1163 | |
|---|
| 1164 | def test_semicolon_escape(self): |
|---|
| 1165 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1166 | <py:with vars="x = 'here is a semicolon: ;'; y = 'here are two semicolons: ;;' ;"> |
|---|
| 1167 | ${x} |
|---|
| 1168 | ${y} |
|---|
| 1169 | </py:with> |
|---|
| 1170 | </div>""") |
|---|
| 1171 | self.assertEqual("""<div> |
|---|
| 1172 | here is a semicolon: ; |
|---|
| 1173 | here are two semicolons: ;; |
|---|
| 1174 | </div>""", tmpl.generate().render(encoding=None)) |
|---|
| 1175 | |
|---|
| 1176 | def test_ast_transformation(self): |
|---|
| 1177 | """ |
|---|
| 1178 | Verify that the usual template expression AST transformations are |
|---|
| 1179 | applied despite the code being compiled to a `Suite` object. |
|---|
| 1180 | """ |
|---|
| 1181 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1182 | <span py:with="bar=foo.bar"> |
|---|
| 1183 | $bar |
|---|
| 1184 | </span> |
|---|
| 1185 | </div>""") |
|---|
| 1186 | self.assertEqual("""<div> |
|---|
| 1187 | <span> |
|---|
| 1188 | 42 |
|---|
| 1189 | </span> |
|---|
| 1190 | </div>""", tmpl.generate(foo={'bar': 42}).render(encoding=None)) |
|---|
| 1191 | |
|---|
| 1192 | def test_unicode_expr(self): |
|---|
| 1193 | tmpl = MarkupTemplate(u"""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1194 | <span py:with="weeks=(u'一', u'二', u'三', u'四', u'五', u'六', u'日')"> |
|---|
| 1195 | $weeks |
|---|
| 1196 | </span> |
|---|
| 1197 | </div>""") |
|---|
| 1198 | self.assertEqual(u"""<div> |
|---|
| 1199 | <span> |
|---|
| 1200 | 一二三四五六日 |
|---|
| 1201 | </span> |
|---|
| 1202 | </div>""", tmpl.generate().render(encoding=None)) |
|---|
| 1203 | |
|---|
| 1204 | def test_with_empty_value(self): |
|---|
| 1205 | """ |
|---|
| 1206 | Verify that an empty py:with works (useless, but legal) |
|---|
| 1207 | """ |
|---|
| 1208 | tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 1209 | <span py:with="">Text</span></div>""") |
|---|
| 1210 | |
|---|
| 1211 | self.assertEqual("""<div> |
|---|
| 1212 | <span>Text</span></div>""", tmpl.generate().render(encoding=None)) |
|---|
| 1213 | |
|---|
| 1214 | |
|---|
| 1215 | def suite(): |
|---|
| 1216 | suite = unittest.TestSuite() |
|---|
| 1217 | suite.addTest(doctest.DocTestSuite(directives)) |
|---|
| 1218 | suite.addTest(unittest.makeSuite(AttrsDirectiveTestCase, 'test')) |
|---|
| 1219 | suite.addTest(unittest.makeSuite(ChooseDirectiveTestCase, 'test')) |
|---|
| 1220 | suite.addTest(unittest.makeSuite(DefDirectiveTestCase, 'test')) |
|---|
| 1221 | suite.addTest(unittest.makeSuite(ForDirectiveTestCase, 'test')) |
|---|
| 1222 | suite.addTest(unittest.makeSuite(IfDirectiveTestCase, 'test')) |
|---|
| 1223 | suite.addTest(unittest.makeSuite(MatchDirectiveTestCase, 'test')) |
|---|
| 1224 | suite.addTest(unittest.makeSuite(ContentDirectiveTestCase, 'test')) |
|---|
| 1225 | suite.addTest(unittest.makeSuite(ReplaceDirectiveTestCase, 'test')) |
|---|
| 1226 | suite.addTest(unittest.makeSuite(StripDirectiveTestCase, 'test')) |
|---|
| 1227 | suite.addTest(unittest.makeSuite(WithDirectiveTestCase, 'test')) |
|---|
| 1228 | return suite |
|---|
| 1229 | |
|---|
| 1230 | if __name__ == '__main__': |
|---|
| 1231 | unittest.main(defaultTest='suite') |
|---|