Skip to content
alexeyten edited this page Oct 4, 2012 · 14 revisions

Основной синтаксис

Лингвистическое замечание

В xslt используются два слова — "stylesheet" и "template". Оба они часто переводятся на русский как "шаблон", что создает путаницу. Я не буду переводить "stylesheet" — это собственно файл, в котором содержатся все определения/выражения, в частности, определения шаблонов (templates).

Еще одно замечание

Все конструкции и идеи yate очень сильно похожи на xslt. Поэтому знакомство с xslt будет плюсом. Когда-нибудь я напишу независимую документацию, в которой все будет объяснено с нуля...

Hello World

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

В определении многих конструкций используется 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 всегда вычисляется относительно чего-либо. Сам по себе синтаксис jpath не предполагает абсолютных путей (например, как /items/item в xslt). Но можно вычислять jpath от любого выражения с типом nodeset (переменной, результата вызова функции и т.д.):

/.items.item

items = .items
items.item // !!! Не тоже самое, что .items.item

items().item[42]

( .foo | .bar ).id

XML-фрагменты

Строка, начинающаяся с символа < (пробелы в начале не учитываются, конечно), представляет собой 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> // Ошибка!
}

XML-атрибуты

Аналог 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"
}

if

Аналог 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"
}

Замечание. Скобки вокруг условия не обязательны (можно их использовать для улучшения читаемости).

for

Аналог xsl:for-each.

Синтаксис:

for expr body

expr должно быть инлайновым выражением и [[types | иметь тип nodeset]].

Например:

<ul>
    for .items.item {
        <li>{ .title }</li>
    }
</ul>

@class = (
    "b-item"
    for .mods {
        " b-item-{ . }"
    }
)

apply

Аналог 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

include

Можно вставить в текущий stylesheet другой файл:

include "foo.yate"
include "../bar/bar.yate"
include "/usr/local/lib/foo/foo.yate"

match / {
    ...
}

Относительные пути резолвятся от файла, в котором расположен вызов include "...".

В теории include является блочным выражением, т.е. может встречаться в любом блоке. Но, так как в настоящий момент шаблоны (match ...) могут быть только на верхнем уровне, то подключая файл с шаблонами не на верхнем уровне, можно получить не то, что хочется.

Если в подключаем файле только выражения и определения функций/переменных, то такой файл можно подключать в любом внутреннем блоке.

Важно. Подключаемый файл парсится как отдельный stylesheet, после чего все его выражения и определения (переменных, функций, ...) добавляются в начало соответствующего блока. В частности все шаблоны из включённого файла будут иметь приоритет ниже, чем из основного.