Спецификация языка шаблонов Kid
Author: | Ryan Tomayko |
---|---|
Contact: | rtomayko@gmail.com |
Revision: | 4 |
Date: | 2005-06-28 05:38:29 -0400 (Tue, 28 Jun 2005) |
Copyright: | 2005, Ryan Tomayko |
Other Formats: | Text |
Kid это простой язык шаблонов на основе XML, использующий встроенный Python для сложных вещей. На создание его синтаксиса нас вдоховило изучение существующих языков шаблонов, а именно XSLT, TAL, and PHP.
Этот документ описывает язык и будет наиболее полезным для разработчиков шаблонов Kid. Для информации об использовании шаблонов из Python`а, командной строки, или web окружения, смотрите Руководство пользователя.
Предупреждение
Продукт находится на ранней стадии разработки и может сильно изменится перед релизом версии 1.0. Большая часть из описываемого здесь может быть изменена. Мы не даем никаких обещаний совместимост между описанным здесь и будущими версиями.
- 1 Краткий пример
- 2 Пространство имен Kid
- 3 Встроенный код python (<?python?>)
- 4 Конструкции вывода
- 5 Подстановка выражений Python (${expr})
- 6 Функции доступные по умолчанию
-
7 Конструкции в аттрибутах тагов
- 7.1 Повторение/Итерация (py:for)
- 7.2 Условные конструкции (py:if)
- 7.3 Динамичное содержимое (py:content)
- 7.4 Замена содержимого (py:replace)
- 7.5 Исключение элемента (py:strip)
- 7.6 Динамичные аттрибуты (py:attrs)
- 7.7 Именованные функции в шаблонах (py:def)
- 7.8 Подстановочный шаблон (py:match)
- 7.9 Расширение шаблонов (py:extends)
- 8 Порядок обработки
- 9 История изменений
1 Краткий пример
<?python title = "Пробный документ Kid" fruits = [u"банан", u"апельсин", u"абрикос", u"M&M"] from platform import system ?> <html xmlns:py="https://purl.org/kid/ns#"> <head> <title py:content="title">Это будет заменено.</title> </head> <body> <p>Вот некотрые из моих любимых фруктов:</p> <ul> <li py:for="fruit in fruits"> Я люблю ${fruit}ы </li> </ul> <p py:if="system() == 'Linux'"> Отличный выбор! </p> </body> </html>
Создаст нечто наподобии:
<?xml version="1.0" encoding="utf-8"?> <html> <head> <title>Пробный документ Kid</title> </head> <body> <p>Вот некотрые из моих любимых фруктов:</p> <ul> <li>Я люблю бананы</li> <li>Я люблю апельсины</li> <li>Я люблю абрикосы</li> <li>Я люблю M&Mы</li> </ul> <p> Отличный выбор! </p> </body> </html>
2 Пространство имен Kid
Все аттрибуты описываемые данным документо должны принадлежать следующему пространству имен:
https://purl.org/kid/ns#
Префикс пространства имен py используется во всем этом документе, чтобы указать, что элемент принадлежит пространству имен Kid/Python.
3 Встроенный код python (<?python?>)
Инструкция обработки(PI) <?python?> содержит код на Python code и может встречаться везде, где допустима инструкция обработки в документе XML.
Правила исполнения кода содержащегося в <?python?>:
- <?python?> размещенные вне элемента документа (корневого элемента) содержат код уровня документа. Этот код будет выполнен в области глобальной видимости документа. Этот код будет выполнен однажды, при загрузке шаблона и разделяется между всеми вызовами шаблона.
- <?python?> размещенный в элементе документа содержит локальный код. Этот код исполняется каждый раз когда вызывается шаблон, и имеет доступ к переменным находящимся в области видимости при вызове и переменным в области глобальной видимости документа.
Код уровеня документа и код локального уровеня подобны коду уровня модуля и коду уровня функции в обчных модулях Python. Например, следующий шаблон:
<?python x = 0 y = 0 ?> <html xmlns:py="https://purl.org/kid/ns#"> <?python x = 1 if x == 1: x = 10 ?> <p py:content="x"/> <?python global y y = 30 ?> <p py:content="y"/> </html>
Можно рассматривать, в виде модуля Python:
x = 0 y = 0 def expand(handler): handler.startDocument() handler.startElement('html') x = 1 if x == 1: x = 10 handler.element('p', content=x) # output p element with x as value global y y = 30 handler.element('p', content=y) # output p element with value of y handler.endElement('html') handler.endDocument()
Инструкции обработки <?python?> могут содержать любые допустимые конструкции Python, включая функции, классы, лямбды.
<?python class Adder: def __init__(self, x, y): self.x = x self.y = y def doit(self): return self.x + self.y foo = Adder(x=400, y=20) x = foo.doit() ?>
Однострочный вариант <?python?> выглядит так:
<?python x = 10 ?>
4 Конструкции вывода
Есть несколько методов для вывода из шаблона: py:content, py:replace, py:attrs, и подстановка выражения ${} . Все они имеют некотрые правила обработки выражений, возвращаемых выражением на Python.
- str, unicode
- строки вставляются в выходной документ как XML CDATA. Это значит, что они не должы содержать таги разметки. '<', '&' в строках заменяются на cущности XML . Значения аттрибутов также экранируются символ "(двойным повтором - '"').
- ElementTree.Element
-
Когда возвращается объект ElementTree.Element, он будет вставлен в документ, то есть, он не будет преобразован в текст, а станет частью структуры выходного документа.
Функции XML() и document() используются чтобы преобразовать строку в структуру документа и чтобы вставить документ, адресуемый URL, соответственно.
Заметим что значения аттрибутов не не должны содержать структуры XML. Это относится к py:attrs и использованию подстановки ${} в значениях аттрибутов.
- последовательности
- Если возвращается последовательность (список, кортеж, или другой перебераемый объект) , описанные выше правила применяется к каждому элементу в последовательности. Напремер, вы можете вернуть список, содержащий Element и строку.
- Другие типы
- Если результатом вычисления выражений будет любой другой тип, будет сделана попытка привести результат к типу unicode, эквивалентная вызову unicode(expr) и обработка продолжится, как если бы результат изначально возвращал строку или unicode.
5 Подстановка выражений Python (${expr})
В аттрибуты не принадлежащие к пространству имен Kid и текстовое содержимое можно вставлять результаты выражений на Python, заключая их в фигурные скобки со знаком доллара впереди: ${expr}. Результат выражения будет вставлен в выходной документ вместе с остатком значения аттрибута или текстового содержимого по правилам описаным выше.
<?python verb = 'ran' noun = 'store' ?> <a title="I ${verb} to the ${noun}">...
... вернет:
<a title="I ran to the store">...
Если аттрибут содержит только подстановку выражений и результат их вычисления None, аттрибут будет удален из выходного документа. Этого можно избежать используя конструкцию expr or '', чтобы вернуть пустую строку вместо None. Например:
<?python # set something to None x = None ?> <a title="${x}">...
... вернет:
<a>...
Тем не менее, следущее:
<?python x = None?> <a title="${x or ''}">...
... выведет:
<a title="">...
5.1 Короткая форма подстановки переменной ($name)
Для простых выражений, содержащих только вывод значения переменной или аттрибута объекта фигурные скобки могут быть опущены:
<a href="https://example.com/$page" title="$title"> Точки тоже можно: $object.another.attribute </a>
Тем не менее, использование фигурных скобок предпочтительней , так, как они явно выделят подстановку в тексте шаблона.
5.2 Экранирование ($$)
$$ экранирует $. $${bla} вернет ${bla}.
6 Функции доступные по умолчанию
Все шаблоны, по умолчанию, имеют доступ к нескольким функциям.
6.1 функция XML()
Подстановка выражения, py:content, и py:replace выводят строки в виде текста. Этот текст приводится в соответствие со спецификацией XML, которая включает, кроме прочего, замену символов < and & их XML сущностями (< &). Если вы имеете часть XML документа в виде строки и желаете вывести его как есть, не подвергая обработке вам необходимо передать эту строку функции XML.
Например, пусть функция, hello, возвращает данные XML которые должны быть вставлены в выходной документ (скажем <hello>world</hello>). Используя следующее:
<p>${hello()}</p>
Вы получите:
<p><hello>world<hello></p>
Вызов функции XML даст желаемый результат:
<p>${XML(hello())}</p>
<p><hello>world</hello></p>
6.2 функция document()
функция document загружает документ XML из файла или по URL и вставляет его в выходной документ:
<div py:content="document('header.html')"></div>
Пути к файлам рассматриваются относительно текущего файла шаблона (если шаблон в файле).
7 Конструкции в аттрибутах тагов
7.1 Повторение/Итерация (py:for)
<element py:for="target_list in expression_list" />
Работает аналогично конструкции for в python.
Аттрибут py:for в любом элементе означает что этот элемент должнен повторится несколько раз, для каждого значения в последовательности:
<?python bottles = range(1, 101) bottles.reverse() ?> <p py:for="num in bottles"> <span py:content="num">X</span> бутылок пива на стене, <span py:content="num">X</span> бутылок пива на стене, одна упала, её не стало, <span py:content="num - 1">X - 1</span> бутылок пива на стене. </p>
Если в элементе присутствует аттрибут py:for он всегда выполняется первым. Все другие py: аттрибуты выполняются для каждой итерации цикла.
7.2 Условные конструкции (py:if)
<element py:if="expr" />
Аттрибут py:if может включатся в любой элемент и означает, что этот элемент и все его дочернии элементы будут выведены только если выражение вернет значение истина:
<p py:if="5 * 5 == 25"> Python выполняет умножение правильно. </p>
Аттрибут py:if выполняется после py:for и вычисляется для каждой итерации. Если результат вычисления expr - ложен, все другие аттрибуты py: не будут выполнены на текущей итерации цикла, или если не в py:for, то вовсе.
Подсказка
None, False, [], (), {}, 0, и '' все их python рассматривает как ложь.
7.3 Динамичное содержимое (py:content)
<element py:content="expr" />
Этот аттрибут может включатся в любой элемент и означает что текст элемента и все дочернии элементы будт заменены результатом вычисления expr.
<p py:content="time.strftime('%C %c')">время</p>
выдаст:
<p>Tues, Jun 26, 2004 02:03:53 AM</p>
py:content это конструкция вывода и может выводить как текст так структуры XML.
7.4 Замена содержимого (py:replace)
<element py:replace='expr' />
py:replace это короткая форма для объявления py:content и py:strip="True" в одном элементе:
<?python x = 10 ?> <p><span py:replace="x">...</span></p>
... выведет:
<p>10</p>
... и эквиваленто объявление:
<?python # x = 10 ?> <p><span py:strip="" py:content="x">...</span></p>
Аттрибут py:replace обрабатывается после аттрибутов py:for и py:if. Если в одном с ним элементе есть аттрибуты py:strip и py:content то они не обрабатываются, а пропускаются.
py:replace это конструкция вывода и может выводить как текст так структуры XML.
7.5 Исключение элемента (py:strip)
<element py:strip="expr" />
Аттрибут py:strip может включатся в любой элемент и означает, что этот элемент не должен выводится в выходной документ. Если у аттрибута пустое значение (нет expr вовсе) или результат вычисления expr булево значение истина, элемент не выводится, но все дочерние элементы обрабатываются обычным путем. Если значение expr не пусто и результат его вычисления ложь, обработка продолжается, как если бы аттрибута не существовало.
Аттрибут py:strip может применятся в элементе с любым другим аттрибутом Kid. Тем не менее, если у элемента установлены py:replace и py:strip, аттрибут py:strip игнорируется.
Аттрибут py:strip обрабатывается после аттрибутов py:for и py:if . If omission is eminent, the py:content attribute is processed normally but attribute interpolation does not occur.
7.6 Динамичные аттрибуты (py:attrs)
<element py:attrs="expr" />
Аттрибут py:attrs может вставлятся в любой элемент и устанавливает множество аттрибутов для элемента в выходном документе. Результатом выражения должны быть следующие типы:
- словарь
- Словарь с ключами соответствующими имени аттрибутов и значениями соответвующими значениям аттрибутов. Они добавляются в элемент вызовом element.attrib.update(mapping), где element это объект ElementTree.Element, соответствующий элементу и mapping словарь возвращенный выраженем. При описании словаря внешние фигурные скобки не обязательны.
- список
- Список кортежей в форме (имя, значение). Элементы списка добавляются в текущий набор аттрибутов вызовом element.set(name, value) для каждого элемента списка.
- ключевые аргументы
- Аттрибуты могут быть также заданы ключевыми аргументами разделенными запятой в виде имя=значение.
Следующие строки:
<elem py:attrs="{'a':1, 'ns:b':2}" /> <elem py:attrs="'a':1, 'ns:b':2" /> <elem py:attrs="(('a',1), ('ns:b',2))" /> <elem py:attrs="a=1, ns:b=2" />
выдадут следующее:
<elem a="1" ns:b="2" />
Заметим, что аттрибуты со значением None будуТ удалены. Если требуется пустое значение для аттрибута, используйте пустую строку.
Если выражение описывает пустой словарь или список, аттрибуты элемента не будут изменены.
py:attrs это конструкция вывода, но не может выводить текст.
7.7 Именованные функции в шаблонах (py:def)
<element py:def="template_name(arg_list)" />
Аттрибут py:def может вставлятся в любой элемент, чтобы создать "именованую функцию". Разметка, содержащаяся в элементе с аттрибутом py:def не выводится в выходной документ, но на нее можно ссылатся из других конструкций вывода, чтобы вставить эту разметку в точку ссылки.
Подобно обычным функциям Python, именованая функция может иметь список аргументов, включая и ключевые аргументы.
Именованая функция вызывается так же как и обычная функция Python. Обычно из конструкций вывода подобных py:content или подстановке ${}.
<ul py:def="display_list(seq)"> <li py:for="item in seq" py:content="item" /> </ul> <table py:def="display_dict(mapping)"> <tr> <th>Key</th> <th>Value</th> </tr> <tr py:for="key, value in mapping.items()"> <td py:content="key" /> <td py:content="value" /> </tr> </table>
Здесь мы определили две именованые функции: display_list и display_dict. Первая получает последовательность, вторая словарь. Мы можем вызывать их в шаблонах из конструкций вывода:
<body> ${display_list(['apple', 'orange', 'kiwi'])} <div py:replace="display_dict({'x' : 'y', 'p' : 'q'})"> Key/Value Table replaces this text </div> </body>
7.8 Подстановочный шаблон (py:match)
<element py:match="expr" />
Аттрибут py:match можно вставлять в любой элемент для создания "подстановочного шаблона". Разметка в элементе с подстановочным шаблоном не выводится в выходной документ. Вместо этого, создается фильтр преобразующий элементы выходного документа.
Подстановочные шаблоны обычно используются для вставки динамичного содержимого и создания "пользовательских тэгов", наподобии тех, что можно найти в JSP и XSLT.
Подстановочный шабон имеет две части: выражение шаблона (expr) и тело шаблона (элемент и его дочерниие элементы).
Подстановочный шаблон обрабатывается следующим образом:
- Каждый выводимый в выходной документ элемент пропускается через фильтр совпадений.
- Фильтр совпадений перебирает все подстановочные шаблоны в файле шаблона в порядке, в котором они были определены и вычисляет выражение шаблона.
- Если выражение шаблона возвращает истину, элемент и все его дочерние заменяются телом шаблона.
И в выражении шаблона и в его теле, переменная item ссылается на проверяемый элемент. Однако, существуют некотрые ограничения в доступе к элементу на каждой фазе:
- В выражении шаблона, доступен только сам элемент item но не его дочернии. Это означает, что выражение шаблона ограничено проверкой тэга элемента и его аттрибутов [1].
- При подстановке тела шаблона (то есть, когда выражение шаблона истинно), можно ссылаться на дочерние элементы из конструкций вывода.
[1] | Эти ограничения вытекают из потоковой архитектуры процессора Kid. Во время вывода, дерево элементов никогда не загружается целиком в память. |
7.8.1 Пример
Следующий простой пример показывает как создать пользовательский тэг <greeting> что выводит одно из двух значений, в зависимости от времени дня в которое выполняется шаблон:
<?xml version="1.0" encoding="utf-8"?> <?python from time import localtime def timeofday(): """Get time of day ('am' or 'pm')""" return localtime().tm_hour < 12 and 'am' or 'pm' ?> <html xmlns:py="https://purl.org/kid/ns#"> <!-- define the greeting match template --> <span py:match="item.tag == 'greeting'" py:replace="item.get(timeofday())"> </span> <head> <title>Время дня</title> </head> <body> <p> Good <greeting am="Morning!" pm="Afternoon" /> </p> </body> </html>
Замете как выражения в py:match и в теле подстановочного шаблона получает доступ к элементу <greeting> через переменную item. item.get(timeofday()) принимает значения am или pm в зависимости от значения возвращаемого функцией timeofday .
В 9:00 AM, выходной документ выглядит как:
<html> <head> <title>Время дня</title> </head> <body> <p> Good Morning! </p> </body> </html>
Здесь возникает очевидный вопрос - как использовать подстановочные шаблоны повторно? Пример выше демонстрирует использование подстановочного шаблона в единственном шаблоне, но часто желательно иметь библиотеку подстановочных шаблонов для использования в разных шаблонах. Ответ - надо делать шаблоны расширяющие общий шаблон содержащий необходимые подстановочные шаблоны. Мы можем разделить вышеприведенный пример на два отдельных файла шаблонов: main.kid и common.kid.
Общий шаблон выглядит как:
<?xml version="1.0" encoding="utf-8"?> <?python from time import localtime def timeofday(): """Get time of day ('am' or 'pm')""" return localtime().tm_hour < 12 and 'am' or 'pm' ?> <html xmlns:py="https://purl.org/kid/ns#"> <!-- define the greeting match template --> <span py:match="item.tag == 'greeting'" py:replace="item.get(timeofday())"> </span> </html>
Шаблон, расширяющий общий выглядит так:
<?xml version="1.0" encoding="utf-8"?> <html py:extends="'common.kid'"> <head> <title>Время дня</title> </head> <body> <p> Good <greeting am="Morning!" pm="Afternoon" /> </p> </body> </html>
Кода шаблон расширяет другой шаблон (или несколько шаблонов), все подстановочные шаблоны и именованые функции расширяемых шаблонов доступны в нем, как если бы они были определены локально.
Предупреждение
Подстановчные шаблоны остаются экспериментальной возможностью. Их синтаксис и семантика могут значительно изменится в следующих версиях. Вы можете применять их, но они остаются нестабильными..
7.9 Расширение шаблонов (py:extends)
<root py:extends="template1, template2, ...">
Аттрибут py:extends может включатся в корневой элемент, чтобы указать, что шаблон должен наследовать именованые функции и подстановочные шаблоны определенные в другом шаблоне (или нескольких шаблонах). Элемент с установленным аттрибутом py:extends, ДОЛЖЕН быть корневым элементом в шаблоне.
py:extends может содержать разделенный запятыми список выражений Python ссылающихся на шаблоны. В списке могут быть значения следующих типов:
- строка
-
Имя файла шаблона, путь к шаблону указывается относительно текущего файла шаблона.
Пример:
<html py:extends="'common.kid'" />
- модуль или класс шаблона
-
py:extends переменная, содержащая модуль или класс шаблона. Если в переменной содержится модуль, делается попытка найти в этом модуле класс с именем Template.
Пример:
<?python import common ?> <html py:extends="common" ...
Можно расширять несколько шаблонов, разделяя ссылки запятыми. Следующий пример расширяет шаблоны common и forms, в импортированных модулях и шаблон в файле other.kid:
<?python import common, forms ?> <html py:extends="common, forms, 'other.kid'" ...
7.9.1 Пример
В шаблоне common.kid определена именованая функция display_errors, и подстановочный шаблон, который преобразует элементы <b> в элементы <strong> с содержимым в верхнем регистре:
<html xmlns:py="https://purl.org/kid/ns#"> <ul py:def="display_errors(errors)"> <li py:for="error in errors" py:content="error" /> </ul> <strong py:match="item.tag == 'b'" py:content="item.text.upper()" /> </html>
Функция и подстановочный шаблон импортируются в другой используя py:extends:
<html py:extends="'common.kid'" xmlns:py="https://purl.org/kid/ns#"> <head> <title>Errors</title> </head> <body> <p>The following <b>errors</b> were found:</p> ${ display_errors(["Field is required", "Must be phone number.."]) } </body> </html>
Элемент <b>errors</b> преобразуется в <strong>ERRORS</strong> и отображется список ошибок. Подстановочные шаблоны и именованые функции доступны в производных шаблонах, как если бы они были определены непосредственно в них.
8 Порядок обработки
Порядок в котором обрабатываются аттрибуты py: следующий:
- py:def
- py:match
- py:for
- py:if
- py:replace
- py:strip
- py:attrs
- py:content
Подстановка переменных обрабатывается после всех других аттрибутов и не производится в значениях аттрибутов py: attributes.
9 История изменений
9.1 Revision 4 (Kid v0.6)
- Add py:extends.
- Add py:match.
- Add py:attrs.
- py:omit renamed py:strip.
- Kid namespace changed from https://nabelis.cx/ns/kid# to https://purl.org/kid/ns#.
- Removed requirement of <?python?> blocks to start with a comment.
- Expression substitution syntax changed from {} to ${}.
9.2 Revision 3 (Kid v0.5)
- Changed processing instruction from <?kid?> to <?python?>.
- Changed namespace prefixes from kid: to py:.
9.3 Revision 2
- Added detail for each attribute on when it is processed in relation to other attributes and whether the result of other attributes modify behavior.
- Fixed a few minor typos and grammatical issues.
- Added editorial notes where things are a bit shaky.
9.4 Revision 1
- Initial public version.
Перевод Олега Комкова (aka PooH)
This documentation is licensed under the GNU Free Documentation License