RU | EN

Спецификация языка шаблонов 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   Краткий пример

<?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&amp;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?>:

  1. <?python?> размещенные вне элемента документа (корневого элемента) содержат код уровня документа. Этот код будет выполнен в области глобальной видимости документа. Этот код будет выполнен однажды, при загрузке шаблона и разделяется между всеми вызовами шаблона.
  2. <?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 сущностями (&lt; &amp;). Если вы имеете часть XML документа в виде строки и желаете вывести его как есть, не подвергая обработке вам необходимо передать эту строку функции XML.

Например, пусть функция, hello, возвращает данные XML которые должны быть вставлены в выходной документ (скажем <hello>world</hello>). Используя следующее:

<p>${hello()}</p>

Вы получите:

<p>&lt;hello>world&lt;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) и тело шаблона (элемент и его дочерниие элементы).

Подстановочный шаблон обрабатывается следующим образом:

  1. Каждый выводимый в выходной документ элемент пропускается через фильтр совпадений.
  2. Фильтр совпадений перебирает все подстановочные шаблоны в файле шаблона в порядке, в котором они были определены и вычисляет выражение шаблона.
  3. Если выражение шаблона возвращает истину, элемент и все его дочерние заменяются телом шаблона.

И в выражении шаблона и в его теле, переменная item ссылается на проверяемый элемент. Однако, существуют некотрые ограничения в доступе к элементу на каждой фазе:

  1. В выражении шаблона, доступен только сам элемент item но не его дочернии. Это означает, что выражение шаблона ограничено проверкой тэга элемента и его аттрибутов [1].
  2. При подстановке тела шаблона (то есть, когда выражение шаблона истинно), можно ссылаться на дочерние элементы из конструкций вывода.
[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: следующий:

  1. py:def
  2. py:match
  3. py:for
  4. py:if
  5. py:replace
  6. py:strip
  7. py:attrs
  8. 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