-
Notifications
You must be signed in to change notification settings - Fork 28
syntax
В xslt
используются два слова — "stylesheet" и "template".
Оба они часто переводятся на русский как "шаблон", что создает путаницу.
Я не буду переводить "stylesheet" — это собственно файл, в котором содержатся все определения/выражения,
в частности, определения шаблонов (templates).
Все конструкции и идеи yate
очень сильно похожи на xslt
.
Поэтому знакомство с xslt
будет плюсом.
Когда-нибудь я напишу независимую документацию, в которой все будет объяснено с нуля...
match / {
<h1>Hello, World</h1>
}
Т.е. весь stylesheet в этом случае представляет собой один шаблон, матчащийся на /
.
Бывают блочные:
/* Block comments. */
/*
Block
comments.
*/
И строчные:
// Line comments.
answer = 42 // Главный ответ!
При этом важно помнить, что комментарий может начинаться в любом месте, где может быть незначащий пробел. Например:
<h1>Hello, // World</h1>
Внутри <h1>...</h1>
все пробелы являются частью текста, так что это не комментарий.
Шаблоны имеют абсолютно такой же смысл, что и в xslt
.
В качестве селектора (атрибут match
в xslt
) может быть либо /
, либо jpath
(напоминаю, что все jpath
относительные).
Синтаксис:
match / body
match / mode body
match jpath body
match jpath mode body
Примеры:
match / {
"Hello"
}
match .foo.bar {
"Hello"
}
Как и в xslt
существуют моды (атрибут mode
в xslt
):
match / content {
<div class="content">
apply .items.item content
</div>
}
match .item content {
<div class="item">{ .title }</div>
}
Важно! Применяется последний шаблон, который матчится на ноду. Никаких специальных методов для повышения приоритета шаблона нет.
В определении многих конструкций используется body
.
Это block
, заключенный в фигурные скобки:
{
a = 42
"Hello"
}
Может быть пустым:
{}
Внутри block
находятся определения (переменных, функций, ключей) и выражения. См. ниже.
Есть четкое правило: одна строка — одно выражение/оператор/конструкция/...
При этом внутри выражения может встретиться block
, завернутый в фигурные или круглые скобки,
в котором может быть несколько выражений, разделенных переводом строки.
Но если не учитывать содержимое этих блоков, все остальное будет на одной строке:
match / { ... }
func foo() { ... }
if .count > 42 { ... } else { ... }
for .items.item { ... }
a = ( ... )
Вот так неправильно:
match /
{
...
}
if .count > 42
{ "Hello, World" }
a =
(
...
)
Перевод строки может быть только между выражениями или определениями.
Синтаксис:
name = expr
Например:
a = 42
b = "Hello, { .username }"
c = .count > 0 && .count < 5
d = .items.item[ .id ]
e = (
"Hello, "
.username
)
f = <h1>Hello, { .username }</h1>
g = if .count > 0 {
.count
} else {
0
}
Важно! Переменные неизменяемы:
a = 42
a = 24 // Ошибка!
Аналог именованных шаблонов в xslt
. Синтаксис:
func name() body
func name(params) body
Функция без параметров:
func title() {
<h1>
.title // Текущий контекст совпадает с контекстом вызова.
</h1>
}
Функция с параметрами:
func foo(a, b) {
if a > b {
<b>{ a + b }</b>
} else {
<i>{ a - b }</i>
}
}
Использование функций:
match / {
title(page)
foo(3, 4)
}
Если тип параметров не задан явно, то предполагается, что это scalar
:
func add(a, b) {
a + b
}
Например, такой код вызовет ошибку:
func foo(a) {
apply a
}
Нужно явно указать тип параметра:
func foo(nodeset a) {
apply a
}
Возможное значение для типа: scalar
, nodeset
, boolean
, attr
и xml
.
Описание будет позже.
Примеры определения ключей:
key data( .data, .id ) { . }
key item( .items.item, .id ) {
<li>{ .title }</li>
}
Использование ключей:
apply data("12345")
<ul>
for .items.item {
item(.id)
}
</ul>
Каждый блок определяет область видимости переменных и функций, в нем определенных.
a = 42
(
a = 73 // внутри этого блока переменная a имеет значение 73.
b = "Hello"
)
// переменная b здесь уже не определена, переменная a имеет значение 42.
Аналоги xsl:text
и xsl:value-of
:
"Hello" // простой текст.
"Hello, { .username }" // текст с внутренним подвыражением.
<h1>
.title // текстовое значение jpath'а.
</h1>
42 + 24 // значение арифметического выражения.
foo() // (текстовый) результат вызова функции.
Внутри всех строковых литералов и текстовых нод фрагменты { ... }
заменяются на значение выражения,
находящегося внутри { ... }
:
<h1>Hello, { .username }</h1>
"Hello, { .username }"
<h1 class="b-hello-{ .type }">
Чтобы вывести символы {
и }
их нужно удвоить:
<h1>Hello, {{ .username }}</h1> // <h1>Hello, { .username }</h1>
jpath всегда вычисляется относительно чего-либо.
Сам по себе синтаксис jpath
не предполагает абсолютных путей (например, как /items/item
в xslt
).
Но можно вычислять jpath
от любого выражения с типом nodeset
(переменной, результата вызова функции и т.д.):
/.items.item
items = .items
items.item // !!! Не тоже самое, что .items.item
items().item[42]
( .foo | .bar ).id
Строка, начинающаяся с символа <
(пробелы в начале не учитываются, конечно), представляет собой xml-фрагмент.
При этом содержимое строки целиком не обязано быть well-formed.
Но внутри любого блока сумарный xml должен быть well-formed.
При этом текстовые ноды допускаются только внутри well-formed фрагментов.
<h1>Hello, { .username }</h1>
xml = (
<h1 class="b-hello" id="{ .id }">
"Hello, { .username }"
</h1>
)
Вот тут все хорошо:
<b><i>Hello</i>
", World"
</b>
Текстовая нода Hello
находится внутри well-formed фрагмента <i>...</i>
(вся строка целиком не является well-formed).
А здесь ошибка:
<b><i>Hello // Ошибка! "Висящая" текстовая нода.
, World</i></b> // Ошибка! Это вообще не xml-строка.
Внутри каждого блока вида { ... }
или ( ... )
xml должен быть well-formed:
$xml = (
<h1>
"Hello"
// Ошибка! Не well-formed xml внутри блока.
// Здесь должен быть закрывающий тег </h1>.
)
match / {
<h1>
"Hello"
// Ошибка!
}
if .count > 0 {
<h1> // Ошибка!
}
Аналог xsl:attribute
.
Синтаксис:
@name = expr
@name += expr
Пример:
<h1 class="b-foo">
@class = "b-hello" // Это выражение переписывает атрибут, определенный в <h1 class="...">
</h1>
Можно добавить что-нибудь к "дефолтному" значению атрибута:
<h1 class="b-hello">
if .special {
@class += " b-hello-special"
}
</h1>
Тоже самое, но по-другому:
<h1>
@class = "b-hello"
if .special {
@class += " b-hello-special"
}
</h1>
И еще по-другому:
<h1>
@class = (
"b-hello"
if .special { " b-folder-user" }
)
</h1>
Атрибуты можно положить в переменную:
<ul>
attrs = (
@class = "b-hello"
@data = "42"
)
for .items.item {
<li>
attrs // Аналог xsl:copy-of в xslt.
@id = .id
.title
</li>
}
</ul>
Атрибуты можно добавлять к узлу, в рамках которого вызван текущий шаблон:
match / {
<div>
apply . extend
</div>
}
match / extend {
@class = "works"
}
Аналог xsl:if
.
Синтаксис:
if expr body
if expr body else body
expr
должно быть инлайновым и [[types | иметь тип boolean
]] (или же приводиться к boolean
).
Пример:
if .count > 0 {
<div class="b-count">{ .count }</div>
}
if .username {
"Hello, { .username }"
} else {
"Hello"
}
Замечание. Скобки вокруг условия не обязательны (можно их использовать для улучшения читаемости).
Аналог xsl:for-each
.
Синтаксис:
for expr body
expr
должно быть инлайновым выражением и [[types | иметь тип nodeset
]].
Например:
<ul>
for .items.item {
<li>{ .title }</li>
}
</ul>
@class = (
"b-item"
for .mods {
" b-item-{ . }"
}
)
Аналог xsl:apply-templates
.
Синтаксис:
apply expr
apply expr mode
expr
должно быть инлайновым выражением и [[types | иметь тип nodeset
]].
Например:
apply .items.item
apply .*
apply /.page.messages.message[ .new ] message-title
apply items.item
apply folders().folder[1]
Аналога для <xsl:apply-templates/>
без параметров нет. Аналог:
apply .*
В шаблоны можно передавать параметры:
match / {
apply .items ( "b-my-items" )
}
// У этого шаблона есть один параметр, которому задано дефолтное значение (это необязательно).
match .items ( class = "b-items" ) {
<div class="{ class }">
...
</div>
}
Если явно не указано, то параметры имеют тип scalar
, но можно задать и другой тип:
match / items ( nodeset items ) {
apply items
}
html = (
<h1>Hello, { .username }</h1>
<ul>
@class = apply . class
apply .items.item
</ul>
)
<div>
html
</div>
if
, for
и apply
являются выражениями и, в частности, могут быть сохранены в переменную. Например:
items = for .items.item {
<li>{ .title }</li>
}
title = if /.page.title {
<h1>{ /.page.title }</h1>
} // А если условие не выполняется, то значением title будет пустая строка.
result = apply .page.folders block
Можно вставить в текущий stylesheet
другой файл:
include "foo.yate"
include "../bar/bar.yate"
include "/usr/local/lib/foo/foo.yate"
match / {
...
}
Относительные пути резолвятся от файла, в котором расположен вызов include "..."
.
В теории include
является блочным выражением, т.е. может встречаться в любом блоке.
Но, так как в настоящий момент шаблоны (match ...
) могут быть только на верхнем уровне,
то подключая файл с шаблонами не на верхнем уровне, можно получить не то, что хочется.
Если в подключаем файле только выражения и определения функций/переменных, то такой файл можно подключать в любом внутреннем блоке.
Важно. Подключаемый файл парсится как отдельный stylesheet
, после чего
все его выражения и определения (переменных, функций, ...) добавляются в начало соответствующего блока. В частности все шаблоны из включённого файла будут иметь приоритет ниже, чем из основного.