| 7 | | Markup provides a simple XML-based template language that is heavily inspired |
| 8 | | by Kid_, which in turn was inspired by a number of existing template languages, |
| 9 | | namely XSLT_, TAL_, and PHP_. |
| 10 | | |
| 11 | | .. _kid: http://kid-templating.org/ |
| 12 | | .. _python: http://www.python.org/ |
| 13 | | .. _xslt: http://www.w3.org/TR/xslt |
| 14 | | .. _tal: http://www.zope.org/Wikis/DevSite/Projects/ZPT/TAL |
| 15 | | .. _php: http://www.php.net/ |
| 16 | | |
| 17 | | This document describes the template language and will be most useful as |
| 18 | | reference to those developing Markup templates. Templates are XML files of some |
| 19 | | kind (such as XHTML) that include processing directives_ (elements or |
| 20 | | attributes identified by a separate namespace) that affect how the template is |
| 21 | | rendered, and template expressions_ that are dynamically substituted by |
| 22 | | variable data. |
| 23 | | |
| 24 | | |
| 25 | | .. contents:: Contents |
| 26 | | :depth: 3 |
| 27 | | .. sectnum:: |
| 28 | | |
| 29 | | ---------- |
| 30 | | Python API |
| 31 | | ---------- |
| 32 | | |
| 33 | | The Python code required for templating with Markup is generally based on the |
| 34 | | following pattern: |
| 35 | | |
| 36 | | * Attain a ``Template`` object from a string or file object containing the |
| 37 | | template XML source. This can either be done directly, or through a |
| 38 | | ``TemplateLoader`` instance. |
| 39 | | * Call the ``generate()`` method of the template, passing any data that should |
| 40 | | be made available to the template as keyword arguments. |
| 41 | | * Serialize the resulting stream using its ``render()`` method. |
| 42 | | |
| 43 | | For example:: |
| 44 | | |
| 45 | | from markup.template import Template |
| 46 | | |
| 47 | | tmpl = Template('<h1>$title</h1>') |
| 48 | | stream = tmpl.generate(title='Hello, world!') |
| 49 | | print stream.render('xhtml') |
| 50 | | |
| 51 | | That code would produce the following output:: |
| 52 | | |
| 53 | | <h1>Hello, world!</h1> |
| 54 | | |
| 55 | | However, if you want includes_ to work, you should attain the template instance |
| 56 | | through a ``TemplateLoader``, and load the template from a file:: |
| 57 | | |
| 58 | | from markup.template import TemplateLoader |
| 59 | | |
| 60 | | loader = TemplateLoader([templates_dir]) |
| 61 | | tmpl = loader.load('test.html') |
| 62 | | stream = tmpl.generate(title='Hello, world!') |
| 63 | | print stream.render('xhtml') |
| 64 | | |
| 65 | | |
| 66 | | .. _`expressions`: |
| 67 | | |
| 68 | | -------------------- |
| 69 | | Template Expressions |
| 70 | | -------------------- |
| 71 | | |
| 72 | | Python_ expressions can be used in text and attribute values. An expression is |
| 73 | | substituted with the result of its evaluation against the template data. |
| 74 | | Expressions need to prefixed with a dollar sign (``$``) and usually enclosed in |
| 75 | | curly braces (``{…}``). |
| 76 | | |
| 77 | | If the expression starts with a letter and contains only letters and digits, |
| 78 | | the curly braces may be omitted. In all other cases, the braces are required so |
| 79 | | that the template processors knows where the expression ends:: |
| 80 | | |
| 81 | | >>> from markup.template import Context, Template |
| 82 | | >>> tmpl = Template('<em>${items[0].capitalize()} item</em>') |
| 83 | | >>> print tmpl.generate(Context(items=['first', 'second'])) |
| 84 | | <em>First item</em> |
| 85 | | |
| 86 | | Expressions support the full power of Python. In addition, it is possible to |
| 87 | | access items in a dictionary using “dotted notation” (i.e. as if they were |
| 88 | | attributes), and vice-versa (i.e. access attributes as if they were items in a |
| 89 | | dictionary):: |
| 90 | | |
| 91 | | >>> from markup.template import Context, Template |
| 92 | | >>> tmpl = Template('<em>${dict.foo}</em>') |
| 93 | | >>> print tmpl.generate(Context(dict={'foo': 'bar'})) |
| 94 | | <em>bar</em> |
| 95 | | |
| 96 | | |
| 97 | | .. _`directives`: |
| 98 | | |
| 99 | | ------------------- |
| 100 | | Template Directives |
| 101 | | ------------------- |
| 102 | | |
| 103 | | Directives are elements and/or attributes in the template that are identified |
| 104 | | by the namespace ``http://markup.edgewall.org/``. They can affect how the |
| 105 | | template is rendered in a number of ways: Markup provides directives for |
| 106 | | conditionals and looping, among others. |
| 107 | | |
| 108 | | To use directives in a template, the namespace should be declared, which is |
| 109 | | usually done on the root element:: |
| 110 | | |
| 111 | | <html xmlns="http://www.w3.org/1999/xhtml" |
| 112 | | xmlns:py="http://markup.edgewall.org/" |
| 113 | | lang="en"> |
| 114 | | ... |
| 115 | | </html> |
| 116 | | |
| 117 | | In this example, the default namespace is set to the XHTML namespace, and the |
| 118 | | namespace for Markup directives is bound to the prefix “py”. |
| 119 | | |
| 120 | | All directives can be applied as attributes, and some can also be used as |
| 121 | | elements. The ``if`` directives for conditionals, for example, can be used in |
| 122 | | both ways:: |
| 123 | | |
| 124 | | <html xmlns="http://www.w3.org/1999/xhtml" |
| 125 | | xmlns:py="http://markup.edgewall.org/" |
| 126 | | lang="en"> |
| 127 | | ... |
| 128 | | <div py:if="foo"> |
| 129 | | <p>Bar</p> |
| 130 | | </div> |
| 131 | | ... |
| 132 | | </html> |
| 133 | | |
| 134 | | This is basically equivalent to the following:: |
| 135 | | |
| 136 | | <html xmlns="http://www.w3.org/1999/xhtml" |
| 137 | | xmlns:py="http://markup.edgewall.org/" |
| 138 | | lang="en"> |
| 139 | | ... |
| 140 | | <py:if test="foo"> |
| 141 | | <div> |
| 142 | | <p>Bar</p> |
| 143 | | </div> |
| 144 | | </py:if> |
| 145 | | ... |
| 146 | | </html> |
| 147 | | |
| 148 | | The rationale behind the second form is that directives do not always map |
| 149 | | naturally to elements in the template. In such cases, the ``py:strip`` |
| 150 | | directive can be used to strip off the unwanted element, or the directive can |
| 151 | | simply be used as an element. |
| 152 | | |
| 153 | | |
| 154 | | Available Directives |
| 155 | | ==================== |
| 156 | | |
| 157 | | |
| 158 | | .. _`py:attrs`: |
| 159 | | |
| 160 | | ``py:attrs`` |
| 161 | | ------------ |
| 162 | | |
| 163 | | This directive adds, modifies or removes attributes from the element:: |
| 164 | | |
| 165 | | <ul> |
| 166 | | <li py:attrs="foo">Bar</li> |
| 167 | | </ul> |
| 168 | | |
| 169 | | Given ``foo={'class': 'collapse'}`` in the template context, this would |
| 170 | | produce:: |
| 171 | | |
| 172 | | <ul> |
| 173 | | <li class="collapse">Bar</li> |
| 174 | | </ul> |
| 175 | | |
| 176 | | Attributes with the value ``None`` are omitted, so given ``foo={'class': None}`` |
| 177 | | in the context for the same template this would produce:: |
| 178 | | |
| 179 | | <ul> |
| 180 | | <li>Bar</li> |
| 181 | | </ul> |
| 182 | | |
| 183 | | This directive can only be used as an attribute. |
| 184 | | |
| 185 | | |
| 186 | | .. _`py:choose`: |
| 187 | | .. _`py:when`: |
| 188 | | .. _`py:otherwise`: |
| 189 | | |
| 190 | | ``py:choose`` / ``py:when`` / ``py:otherwise`` |
| 191 | | ---------------------------------------------- |
| 192 | | |
| 193 | | This set of directives provides advanced contional processing for rendering one |
| 194 | | of several alternatives. The first matching ``py:when`` branch is rendered, or, |
| 195 | | if no ``py:when`` branch matches, the ``py:otherwise`` branch is be rendered. |
| 196 | | |
| 197 | | If the ``py:choose`` directive is empty the nested ``py:when`` directives will |
| 198 | | be tested for truth:: |
| 199 | | |
| 200 | | <div py:choose=""> |
| 201 | | <span py:when="0 == 1">0</span> |
| 202 | | <span py:when="1 == 1">1</span> |
| 203 | | <span py:otherwise="">2</span> |
| 204 | | </div> |
| 205 | | |
| 206 | | This would produce the following output:: |
| 207 | | |
| 208 | | <div> |
| 209 | | <span>1</span> |
| 210 | | </div> |
| 211 | | |
| 212 | | If the ``py:choose`` directive contains an expression the nested ``py:when`` |
| 213 | | directives will be tested for equality to the parent ``py:choose`` value:: |
| 214 | | |
| 215 | | <div py:choose="1"> |
| 216 | | <span py:when="0">0</span> |
| 217 | | <span py:when="1">1</span> |
| 218 | | <span py:otherwise="">2</span> |
| 219 | | </div> |
| 220 | | |
| 221 | | This would produce the following output:: |
| 222 | | |
| 223 | | <div> |
| 224 | | <span>1</span> |
| 225 | | </div> |
| 226 | | |
| 227 | | |
| 228 | | .. _`py:content`: |
| 229 | | |
| 230 | | ``py:content`` |
| 231 | | -------------- |
| 232 | | |
| 233 | | This directive replaces any nested content with the result of evaluating the |
| 234 | | expression:: |
| 235 | | |
| 236 | | <ul> |
| 237 | | <li py:content="bar">Hello</li> |
| 238 | | </ul> |
| 239 | | |
| 240 | | Given ``bar='Bye'`` in the context data, this would produce:: |
| 241 | | |
| 242 | | <ul> |
| 243 | | <li>Bye</li> |
| 244 | | </ul> |
| 245 | | |
| 246 | | This directive can only be used as an attribute. |
| 247 | | |
| 248 | | |
| 249 | | .. _`py:def`: |
| 250 | | .. _`macros`: |
| 251 | | |
| 252 | | ``py:def`` |
| 253 | | ---------- |
| 254 | | |
| 255 | | The ``py:def`` directive can be used to create macros, i.e. snippets of |
| 256 | | template code that have a name and optionally some parameters, and that can be |
| 257 | | inserted in other places:: |
| 258 | | |
| 259 | | <div> |
| 260 | | <p py:def="greeting(name)" class="greeting"> |
| 261 | | Hello, ${name}! |
| 262 | | </p> |
| 263 | | ${greeting('world')} |
| 264 | | ${greeting('everyone else')} |
| 265 | | </div> |
| 266 | | |
| 267 | | The above would be rendered to:: |
| 268 | | |
| 269 | | <div> |
| 270 | | <p class="greeting"> |
| 271 | | Hello, world! |
| 272 | | </p> |
| 273 | | <p class="greeting"> |
| 274 | | Hello, everyone else! |
| 275 | | </p> |
| 276 | | </div> |
| 277 | | |
| 278 | | If a macro doesn't require parameters, it can be defined as well as called |
| 279 | | without the parenthesis. For example:: |
| 280 | | |
| 281 | | <div> |
| 282 | | <p py:def="greeting" class="greeting"> |
| 283 | | Hello, world! |
| 284 | | </p> |
| 285 | | ${greeting} |
| 286 | | </div> |
| 287 | | |
| 288 | | The above would be rendered to:: |
| 289 | | |
| 290 | | <div> |
| 291 | | <p class="greeting"> |
| 292 | | Hello, world! |
| 293 | | </p> |
| 294 | | </div> |
| 295 | | |
| 296 | | This directive can also be used as an element:: |
| 297 | | |
| 298 | | <div> |
| 299 | | <py:def function="greeting(name)"> |
| 300 | | <p class="greeting">Hello, ${name}!</p> |
| 301 | | </py:def> |
| 302 | | </div> |
| 303 | | |
| 304 | | |
| 305 | | .. _`py:for`: |
| 306 | | |
| 307 | | ``py:for`` |
| 308 | | ---------- |
| 309 | | |
| 310 | | The element is repeated for every item in an iterable:: |
| 311 | | |
| 312 | | <ul> |
| 313 | | <li py:for="item in items">${item}</li> |
| 314 | | </ul> |
| 315 | | |
| 316 | | Given ``items=[1, 2, 3]`` in the context data, this would produce:: |
| 317 | | |
| 318 | | <ul> |
| 319 | | <li>1</li><li>2</li><li>3</li> |
| 320 | | </ul> |
| 321 | | |
| 322 | | This directive can also be used as an element:: |
| 323 | | |
| 324 | | <ul> |
| 325 | | <py:for each="item in items"> |
| 326 | | <li>${item}</li> |
| 327 | | </py:for> |
| 328 | | </ul> |
| 329 | | |
| 330 | | |
| 331 | | .. _`py:if`: |
| 332 | | |
| 333 | | ``py:if`` |
| 334 | | ------------ |
| 335 | | |
| 336 | | The element is only rendered if the expression evaluates to a truth value:: |
| 337 | | |
| 338 | | <div> |
| 339 | | <b py:if="foo">${bar}</b> |
| 340 | | </div> |
| 341 | | |
| 342 | | Given the data ``foo=True`` and ``bar='Hello'`` in the template context, this |
| 343 | | would produce:: |
| 344 | | |
| 345 | | <div> |
| 346 | | <b>Hello</b> |
| 347 | | </div> |
| 348 | | |
| 349 | | This directive can also be used as an element:: |
| 350 | | |
| 351 | | <div> |
| 352 | | <py:if test="foo"> |
| 353 | | <b>${bar}</b> |
| 354 | | </py:if> |
| 355 | | </div> |
| 356 | | |
| 357 | | |
| 358 | | .. _`py:match`: |
| 359 | | .. _Match Templates: |
| 360 | | |
| 361 | | ``py:match`` |
| 362 | | ------------ |
| 363 | | |
| 364 | | This directive defines a *match template*: given an XPath expression, it |
| 365 | | replaces any element in the template that matches the expression with its own |
| 366 | | content. |
| 367 | | |
| 368 | | For example, the match template defined in the following template matches any |
| 369 | | element with the tag name “greeting”:: |
| 370 | | |
| 371 | | <div> |
| 372 | | <span py:match="greeting"> |
| 373 | | Hello ${select('@name')} |
| 374 | | </span> |
| 375 | | <greeting name="Dude" /> |
| 376 | | </div> |
| 377 | | |
| 378 | | This would result in the following output:: |
| 379 | | |
| 380 | | <div> |
| 381 | | <span> |
| 382 | | Hello Dude |
| 383 | | </span> |
| 384 | | </div> |
| 385 | | |
| 386 | | Inside the body of a ``py:match`` directive, the ``select(path)`` function is |
| 387 | | made available so that parts or all of the original element can be incorporated |
| 388 | | in the output of the match template. See [wiki:MarkupStream#UsingXPath] for |
| 389 | | more information about this function. |
| 390 | | |
| 391 | | This directive can also be used as an element:: |
| 392 | | |
| 393 | | <div> |
| 394 | | <py:match path="greeting"> |
| 395 | | <span>Hello ${select('@name')}</span> |
| 396 | | </py:match> |
| 397 | | <greeting name="Dude" /> |
| 398 | | </div> |
| 399 | | |
| 400 | | |
| 401 | | .. _`py:replace`: |
| 402 | | |
| 403 | | ``py:replace`` |
| 404 | | -------------- |
| 405 | | |
| 406 | | This directive replaces the element itself with the result of evaluating the |
| 407 | | expression:: |
| 408 | | |
| 409 | | <div> |
| 410 | | <span py:replace="bar">Hello</span> |
| 411 | | </div> |
| 412 | | |
| 413 | | Given ``bar='Bye'`` in the context data, this would produce:: |
| 414 | | |
| 415 | | <div> |
| 416 | | Bye |
| 417 | | </div> |
| 418 | | |
| 419 | | This directive can only be used as an attribute. |
| 420 | | |
| 421 | | |
| 422 | | .. _`py:strip`: |
| 423 | | |
| 424 | | ``py:strip`` |
| 425 | | ------------ |
| 426 | | |
| 427 | | This directive conditionally strips the top-level element from the output. When |
| 428 | | the value of the ``py:strip`` attribute evaluates to ``True``, the element is |
| 429 | | stripped from the output:: |
| 430 | | |
| 431 | | <div> |
| 432 | | <div py:strip="True"><b>foo</b></div> |
| 433 | | </div> |
| 434 | | |
| 435 | | This would be rendered as:: |
| 436 | | |
| 437 | | <div> |
| 438 | | <b>foo</b> |
| 439 | | </div> |
| 440 | | |
| 441 | | As a shorthand, if the value of the ``py:strip`` attribute is empty, that has |
| 442 | | the same effect as using a truth value (i.e. the element is stripped). |
| 443 | | |
| 444 | | |
| 445 | | .. _`with`: |
| 446 | | |
| 447 | | ``py:with`` |
| 448 | | ----------- |
| 449 | | |
| 450 | | The ``py:with`` directive lets you assign expressions to variables, which can |
| 451 | | be used to make expressions inside the directive less verbose and more |
| 452 | | efficient. For example, if you need use the expression ``author.posts`` more |
| 453 | | than once, and that actually results in a database query, assigning the results |
| 454 | | to a variable using this directive would probably help. |
| 455 | | |
| 456 | | For example:: |
| 457 | | |
| 458 | | <div> |
| 459 | | <span py:with="y=7; z=x+10">$x $y $z</span> |
| 460 | | </div> |
| 461 | | |
| 462 | | Given ``x=42`` in the context data, this would produce:: |
| 463 | | |
| 464 | | <div> |
| 465 | | <span>42 7 52</span> |
| 466 | | </div> |
| 467 | | |
| 468 | | This directive can also be used as an element:: |
| 469 | | |
| 470 | | <div> |
| 471 | | <py:with vars="y=7; z=x+10">$x $y $z</py:with> |
| 472 | | </div> |
| 473 | | |
| 474 | | Note that if a variable of the same name already existed outside of the scope |
| 475 | | of the ``py:with`` directive, it will **not** be overwritten. Instead, it |
| 476 | | will have the same value it had prior to the ``py:with`` assignment. |
| 477 | | Effectively, this means that variables are immutable in Markup. |
| 478 | | |
| 479 | | |
| 480 | | .. _order: |
| 481 | | |
| 482 | | Processing Order |
| 483 | | ================ |
| 484 | | |
| 485 | | It is possible to attach multiple directives to a single element, although not |
| 486 | | all combinations make sense. When multiple directives are encountered, they are |
| 487 | | processed in the following order: |
| 488 | | |
| 489 | | #. `py:def`_ |
| 490 | | #. `py:match`_ |
| 491 | | #. `py:when`_ |
| 492 | | #. `py:otherwise`_ |
| 493 | | #. `py:for`_ |
| 494 | | #. `py:if`_ |
| 495 | | #. `py:choose`_ |
| 496 | | #. `py:with`_ |
| 497 | | #. `py:replace`_ |
| 498 | | #. `py:content`_ |
| 499 | | #. `py:attrs`_ |
| 500 | | #. `py:strip`_ |
| 501 | | |
| 502 | | |
| 503 | | .. _includes: |
| 504 | | |
| 505 | | -------- |
| 506 | | Includes |
| 507 | | -------- |
| 508 | | |
| 509 | | To reuse common snippets of template code, you can include other files using |
| 510 | | XInclude_. |
| 511 | | |
| 512 | | .. _xinclude: http://www.w3.org/TR/xinclude/ |
| 513 | | |
| 514 | | For this, you need to declare the XInclude namespace (commonly bound to the |
| 515 | | prefix “xi”) and use the ``<xi:include>`` element where you want the external |
| 516 | | file to be pulled in:: |
| 517 | | |
| 518 | | <html xmlns="http://www.w3.org/1999/xhtml" |
| 519 | | xmlns:py="http://markup.edgewall.org/" |
| 520 | | xmlns:xi="http://www.w3.org/2001/XInclude"> |
| 521 | | <xi:include href="base.html" /> |
| 522 | | ... |
| 523 | | </html> |
| 524 | | |
| 525 | | Include paths are relative to the filename of the template currently being |
| 526 | | processed. So if the example above was in the file "``myapp/index.html``" |
| 527 | | (relative to the template search path), the XInclude processor would look for |
| 528 | | the included file at "``myapp/base.html``". You can also use Unix-style |
| 529 | | relative paths, for example "``../base.html``" to look in the parent directory. |
| 530 | | |
| 531 | | Any content included this way is inserted into the generated output instead of |
| 532 | | the ``<xi:include>`` element. The included template sees the same context data. |
| 533 | | `Match templates`_ and `macros`_ in the included template are also available to |
| 534 | | the including template after the point it was included. |
| 535 | | |
| 536 | | By default, an error will be raised if an included file is not found. If that's |
| 537 | | not what you want, you can specify fallback content that should be used if the |
| 538 | | include fails. For example, to to make the include above fail silently, you'd |
| 539 | | write: |
| 540 | | |
| 541 | | <xi:include href="base.html"><xi:fallback /></xi:include> |
| 542 | | |
| 543 | | See the XInclude_ for more about fallback content. Note though that Markup |
| 544 | | currently only supports a small subset of XInclude. |
| 545 | | |
| 546 | | Incudes in Markup are fully dynamic: Just like normal attributes, the `href` |
| 547 | | attribute accepts expressions_, and directives_ can be used on the |
| 548 | | ``<xi:include />`` element just as on any other element, meaning you can do |
| 549 | | things like conditional includes:: |
| 550 | | |
| 551 | | <xi:include href="${name}.html" py:if="not in_popup" |
| 552 | | py:for="name in ('foo', 'bar', 'baz')" /> |
| 553 | | |
| 554 | | |
| 555 | | .. _comments: |
| 556 | | |
| 557 | | -------- |
| 558 | | Comments |
| 559 | | -------- |
| 560 | | |
| 561 | | Normal XML/HTML comment syntax can be used in templates:: |
| 562 | | |
| 563 | | <!-- this is a comment --> |
| 564 | | |
| 565 | | However, such comments get passed through the processing pipeline and are by |
| 566 | | default included in the final output. If that's not desired, prefix the comment |
| 567 | | text with an exclamation mark:: |
| 568 | | |
| 569 | | <!-- !this is a comment too, but one that will be stripped from the output --> |
| 570 | | |
| 571 | | Note that it does not matter whether there's whitespace before or after the |
| 572 | | exclamation mark, so the above could also be written as follows:: |
| 573 | | |
| 574 | | <!--! this is a comment too, but one that will be stripped from the output --> |
| 575 | | }}} |