Каскадные Таблицы Трансформации

3 июля 2009 года, 20:58

Единообразие. Довольно просто познать природу вещей, если можно привести их свойства к общему знаменателю. Наш мир многогранен, даже сложен, но, в то же время, он довольно унифицирован. Многое в природе построено по одному и тому же принципу, что позволяет переносить свойства одних объектов на другие. Или даже использовать одни предметы в качестве других. Природа на наших глазах создаёт книгу жизни, и, как хороший автор, использует там метафоры, эпитеты и, разумеется, олицетворения.

В 105 выпуске уже был обозначен факт, что человеку свойственно всё упорядочивать и систематизировать. Сегодня пришло время упомянуть ещё одно важное человеческое качество: стремление к унификации, созданию единого образа окружающего нас мира. Однако унификация не заканчивается на живой природе, она переносится и на творения человека. Что бы мы не создали, мы не хотим создавать вновь что-то, пусть даже слабо похожее на уже сотворённое, — гораздо мудрым выбором будет для нас использование или приспособление уже созданного.

Сегодняшний разговор в очередной раз пойдёт о CSS, а точнее об одном из его нераскрытых возможностей, которое способно вывести CSS на серверную, тёмную для него сторону силы.

Лирическое преступление

Отходя от шока после прочтения столь необычного внетематического введения, перейдём непосредственно к сути теоретических изысканий. Сегодня мы будем применять CSS на сервере, а не на клиенте. Что же это значит? CSS будет выступать для нас не в роли Каскадных Таблиц Стилей, а в роли… Чего-то иного. Давайте разбираться вместе, чего же.

Для разборок нам потребуется самый обычный XHTML-документ, над которым мы будем экспериментировать все следующие часы.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html lang="ru" xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Экспериментальный блог</title> <link rel="stylesheet" type="text/css" href="stylesheet/screen.css" media="screen" /> </head> <body> <h1>Экспериментальный блог</h1> <!-- Начало статьи --> <div id="article"> <h2>Название статьи</h2> <p>Первый параграф статьи.</p> <p>Второй параграф статьи.</p> <h3>Подраздел статьи</h3> <p>Первый параграф раздела.</p> <div class="tags"> Метки: <ul id="tags"> <li>метка</li> <li>метка</li> <li>метка</li> </ul> </div> </div> <!-- Окончание статьи --> </body> </html>

Перед нами самый обычный документ, который мы создали для наших экспериментов. Мы видим, что он чудесным образом был уже стилизован, так как мы разглядели подключение CSS-файла. Такой документ можно прямо сейчас открыть в браузере и увидеть его во всей красе. Просто чудеса.

Теперь давайте представим, что нам нужно этот документ связать с динамическими данными, которые могут приходить к нам в любой форме. Какую форму лучше выбрать? Какой формат шаблонов лучше всего использовать? Однозначно, выбор шаблонизатора — выбор читателя. Однако справедливости ради стоит заметить, что не каждая выбранная форма привязки будет удобной всем сторонам. Зачастую выбранная привязка удовлетворяет лишь одной из сторон процесса разработки, но не той, которая занимается вёрсткой документов. Данная запись пытается склонить весы на сторону верстальщиков и FE-разработчиков: мы представляем сегодня интересы именно этого слоя населения Земли.

Ранее в записи уже упоминался серверный CSS. Пришло время окончательно раскрыть это понятие. Прямо сейчас мы теоретически создадим и оформим новый формат на основе CSS. Наш формат позаимствует у своего предшественника структуру и душевную составляющую, а остальное мы добавим ему самостоятельно. Серверный CSS давайте лучше будем называть Каскадными Таблицами ТрансформацииCTS. Далее вы поймёте, почему мы их так назвали.

CTS и будет нашим шаблонизатором. Его главная задача — связывать динамические данные со статической структурой XHTML-документа. Посмотрим, как CTS будут справляться с ней.

Осуществление связывания

CSS нам одолжил свою структуру, поэтому синтаксис CTS-файлов будет полностью совпадать с синтаксисом CSS-файлов. Но почему же мы назвали CTS именно CTS? Всё очень логично: он будет заниматься трансформацией исходной структуры документа, её преобразованием и даже искажением. Исходный XHTML-документ после применения CTS не будет похож сам на себя до этой операции. Уже слышны нарастающие крики: «Документ уже не тот!» Подождите, пока ещё тот.

Давайте вспомним, как мы осуществляем стилизацию документа посредством CSS. Для этого нам доступны так называемые селекторы, с помощью которых мы можем попасть в любой уголок нашего документа. Это наш GPS-навигатор по документу. CSS-селекторы можно создавать на основе трёх контрольных структур: имя элемента в документе, уникальный идентификатор элемента и CSS-класс документа. Подробнее с селекторами вы можете познакомиться в соответствующей рекомендации W3C, а мы посмотрим самые простые примеры различных селекторов:

/* Выбор элементов по названию */ div { } /* Выборка по уникальному идентификатору */ #content { } /* Выбор элементов по установленному CSS-классу */ .article { }

После указания селектора, мы указываем необходимый набор свойств в фигурных скобках , с помощью которого управляем визуальным представлением элемента в браузере. Каждое свойство — это пара имя-значение. Например так:

/* Изменяем стиль отображения элемента в браузере */ .article { margin: 1em 0; background: #efefef; color: #222; font-size: 1.2em; }

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

Теперь представим, что вместо браузера у нас другая программа — шаблонизатор CTS, а вместо визуального представления — структура документа. Таким образом, мы приходим к определению CTS: он предназначен для управления структурой документа на стороне сервера с помощью заданного набора свойств.

В CSS мы имеем предопределённый набор свойств, с помощью которых мы управляем визуальной составляющей документа. В CTS мы должны создать точно такой же набор свойств, который должен реализовывать по меньшей мере следующий набор операций:

  • Управление содержимым элементов: добавление текста к концу элемента, установка текста в начало элемента или на указанное в текст местоположение (ключевая точка);
  • Манипуляции с атрибутами: добавление новых атрибутов, возможность изменения и удаления существующих;
  • Управление элементами: сокрытие (удаление из дерева) элементов при определённых условиях, добавление новых элементов в дерево при определённых условиях или без них;
  • Работа с CSS-классами: специальные свойства для изменения содержимого атрибута class;
  • Поддержка операций цикла и дополнительных селекторов для управления цикла, например: :last-loop-element — это последний элемент, который будет создан в цикле, и с помощью этого селектора можно будет управлять его параметрами.

Это базовые группы свойств, которые следует реализовать в первую очередь.

В CTS мы добавим единственный новый синтаксический элемент: подстановочные переменные и переменные, созданные внутри CTS-объявлений. Подстановочные переменные должны начинаться со знака %, а внутренние подстановочные переменные — со знака %%. Например, на серверной стороне нам доступна переменная с именем page.title, тогда, чтобы подставить её в CTS-файле, мы используем следующую конструкцию: %page.title. Внутренние переменные используются, в основном, в циклах, для того, чтобы не было перекрещивающихся определений переменных, так как их имена должны быть уникальными в пределах документа.

Сейчас важно понять одну большую разницу между CSS и CTS, которая поможет нам в дальнейшем создавать правильные имена для свойств: если CSS оперирует существительными, то в CTS используются глаголы. CTS — это язык действий, где каждое свойство определённого правила производит операцию над элементом, который указан в селекторе (или над группой элементов).

Ключевая особенность CTS заключается в использовании существующего дерева элементов и связанных с ними атрибутов. Селекторы помогают нам по уже определённым классам и идентификаторам, а также по предопределённым в XHTML именам элементов, добираться до нужных и применять к ним необходимые действия.

Давайте посмотрим на пример правила, с помощью которого можно установить заголовок страницы из динамической переменной:

title { append: %page.title; }

О предопределённых свойствах, в данном случае об append, мы поговорим чуть позже. Следует только знать, что оно добавляет к содержимому элемента по селектору новое (если у элемента не было содержимого до этой операции, то весь элемент будет содержать лишь то, что мы добавим через CTS). Главное здесь — не забыть установить %page.title, чтобы эта переменная успешно была добавлена к содержимому заголовка страницы.

Функции подстановки значений

В CSS мы можем использовать следующую конструкцию для установки фона элемента:

#element { background: url(/path/to/image.png); }

В данном случае мы видим ключевое слово url и круглые скобки после него, в которых заключено непосредственное значение (аргумент). Такую конструкцию мы будем называть функцией CTS. Результат выполнения функции подставляется вместо неё и передаётся свойству.

В CTS определено две ключевых функции. Первая — это selector, с помощью которой можно находить элементы в документе для подстановки в качестве значения данного свойства. Вторая — это expression, который является логическим выражением.

Функция selector обладает следующим синтаксисом:

#element { replace-element: div selector(#another-element); }

Функция expression обладает следующим синтаксисом:

#element { add-attribute: class "active" expression(%page.is_active is true); }

Аргументом данной функции является логическое выражение, которое возвращает true или false. Выражение состоит из двух частей, каждая из которых определяется либо собственным значением, либо переменной, либо значением элемента или атрибута, полученного по селектору. Если выражение в скобках истинно, то указанное действие по свойству будет выполнено, иначе этого не произойдёт. Данную функцию можно применять абсолютно к любому свойству, просто приписывая её в конец как ещё один аргумент.

При реализации CTS-шаблонизатора, можно встраивать дополнительные функции, которые будут доступны для вызова точно таким же образом. Можно реализовать абсолютно любой набор таких функций.

Предопределённые операции

В дальнейшем нам понадобится следующая памятка. Если вы запутаетесь при чтении записи, где селекторы, где свойства и значения, то смотрите на неё:

селектор { /* комментарий */ свойство: значение; свойство: значение; свойство: функция(аргумент); }

Ранее мы разбивали набор предопределённых свойств (операций) по группам. Пришло время разобрать какие именно свойства мы должны определить.

Управление содержимым элемента
  • append — присоединение содержимого переменной или строки в конец содержимого элемента;
  • prepend — присоединение содержимого переменной или строки в начало содержимого элемента;
  • replace — замена содержимого элемента указанным содержимым;
  • clear — удаление содержимого из элемента;
  • store — помещение содержимого элемента во внутреннюю переменную, например: store: %%new.variable. В данном коде мы помещаем содержимое элемента во внутреннюю переменную %new.variable. В дальнейшем мы можем использовать эту переменную по всему CTS-документу, например, для вставки в другие места;
  • substitute — замена указанной строки другой строкой. Позволяет подставлять необходимое содержимое в нужное место в содержимом элемента. Например, если у нас есть строка Hello, My name is ###. I'm glad to see you there!, то мы можем заменить ### на нужную нам строку следующим образом: substitute: «» %page.name для замены на содержимое переменной или substitute: "###" "Din" для замены на статичную строку;

Рассмотрим пример применения каждого из этих атрибутов (мы будем совершать все операции на одном элементе, чтобы сохранить минимальный объём кода). Для примера мы представим, что у нас есть такой XHTML-элемент:

<div id="element">Моя страница</div>

Теперь произведём примерную его модификацию:

#element { /* Присоединяем необходимое содержимое к элементу Результат: Моя страница &rarr; "содержимое %my.variable" */ append: " &rarr;"; append: %my.variable; /* Добавляем содержимое перед существующим Результат: Заголовок страницы: Моя страница &rarr; "содержимое %my.variable" */ prepend: "Заголовок страницы: "; /* Заменяем всё содержимое нужным Результат: "содержимое %my.variable" */ replace: %my.variable; /* Сохраняем содержимое элемента */ store: %%my.another.variable; /* Очищаем содержимое элементов. Если свойство не принимает никаких значений (после знака «:»), то его значение должно совпадать с его именем. Результат: элемент не содержит содержимого */ clear: clear; /* Вновь добавляем новый текст */ append: "Заголовок страницы: ###. Привет!"; /* Заменяем искомую подстроку ### на содержимое переменной %my.variable */ substitute: "###" %my.variable; }
Манипуляции с атрибутами
  • add-attribute — создание нового атрибута данного элемента. Необходимо передать в качестве свойств имя нового атрибута и его содержимое, в качестве которого может выступать как строка, так и переменная;
  • remove-attribute — удаление атрибута по имени из элемента. Если атрибут не существует, то он, естественно, не будет удалён;
  • append-attribute-content — добавление содержимого к концу значения существующего атрибута. Необходимо передать в качестве свойств имя атрибута и добавляемое значение, которое может быть как строкой, так и переменной;
  • prepend-attribute-content — добавление содержимого в начало значения существующего атрибута. В качестве свойств — имя атрибута и значение (строка или переменная);
  • replace-attribute-content — замена содержимого атрибута новым содержимым (строкой или переменной). В качестве свойств передаётся имя существующего атрибута и новое значение;
  • clear-attribute-content — очищение содержимого атрибута. Необходимо передать имя атрибута, содержимое которого следует обнулить;
  • store-attribute-content — сохранение содержимого атрибута в определённой внутренней переменной;
  • substitute-attribute-content — замена определённой подстроки содержимого элемента указанной строкой или переменной.

Для примера возьмём следующий элемент:

<a class="link" />

Попробуем произвести некоторые из описанных выше операций:

a.link { /* Результат: <a class="link">Моя ссылка</a> */ append: "Моя ссылка"; /* Результат: <a class="link" href="http://example.com/">Моя ссылка</a> */ add-attribute: href "http://example.com/"; /* Результат: <a class="link" href="http://example.com/my.html">Моя ссылка</a> */ append-attribute-content: href "my.html"; /* Результат: <a class="link" href="http://example.com/my.html" id="myelement">Моя ссылка</a> */ add-attribute: id "myelement"; /* Результат: <a class="link" href="http://example.com/my.html">Моя ссылка</a> */ remove-attribute: id; }
Управление элементами
  • hide — сокрытие (удаление) элемента. В качестве значения необходимо передать условное выражение;
  • show — отображение элемента. В качестве значения — условное выражение;
  • append-element — добавление дочернего элемента относительно текущего после всех остальных дочерних его элементов. Для проведения операции необходимо передать имя нового элемента или селектор элемента, который будет содеражть CSS-класс элемента или идентификатор элемента;
  • prepend-element — добавление дочернего элемента относительно текущего перед всеми остальными дочерними его элементами. Для проведения операции необходимо передать имя нового элемента (или селектор);
  • place-element — добавление дочернего элемента в позицию указанной строки. Для проведения операции необходимо передать строку для замены и имя нового элемента (либо селектор элемента). Новый элемент будет включать в себя заменённую строку;
  • remove-element — удаление дочернего элемента относительно текущего. Необходимо передать имя элемента для удаления или селектор для нахождения нужного элемента;
  • replace-element — замена дочернего элемента новым элементом. В качестве значения следует использовать селектор для нахождения элемента, которым следует заменить дочерний (который можно определить либо по имени элемента, либо таким же образом по селектору);

В качестве примера возьём элемент как в прошлом разделе:

<a class="link" />

Попробуем произвести некоторые манипуляции над ним:

a.link { /* Скрываем, если %link_active равняется false */ hide: expression(%link_active is false); /* Добавляем дочерний элемент span к текущему с идентификатором element */ append-element: selector(span#element) expression(%link_current is true); } a.link span { append: "Это ссылка"; } /* Результат: <a class="link"><span id="element">Это ссылка</span></a> */
Работа с CSS-классами
  • add-class — добавление CSS-класса элементу;
  • remove-class — удаление CSS-класса;
  • replace-class — замена класса элемента, указанного первым значением, вторым значением;
  • switch-class — циклическая смена одного CSS-класса другим. В качестве значения — два имени CSS-класса.

Дополнительных иллюстраций приводить здесь нет необходимости: названия свойств говорят сами за себя.

Циклы

Для циклов определены не только необходимые свойства, но и некоторые селекторы для удобства работы с первыми. Для начала разберём набор свойств:

  • loop — добавление цикла. В качестве значений нужно передать переменную массива и внутреннюю (%%) переменную для доступа к элементам массива на каждой итерации цикла;

В качестве дополнительных селекторов выступают следующие:

  • :loop-first — первый элемент цикла;
  • :loop-last — последний элемент цикла;
  • :loop-n(N) — N-ный элемент цикла.

Для примера, допустим, у нас есть следующая переменная:

%my.variable = [{"author" : "enhydra", "title" : "First article"}, {"author" : "SvartalF", "title" : "Second article"}, {"author" : "Hlomzik", "title" : "Third article"}]

И такой блок кода:

<ul class="articles"> <li> <h2 /> <span class="author" /> </li> </ul>

Преобразуем его с помощью цикла:

.articles li { loop: %my.variable %%article; } .articles li h2 { append: %%article.title; } .articles li .author { append: "Автор: "; append: %%article.author; } .artices li:loop-last { add-class: last; } /* Результат: <ul class="articles"> <li> <h2>First article</h2> <span class="author">enhydra</span> </li> <li> <h2>Second article</h2> <span class="author">SvartalF</span> </li> <li class="last"> <h2>Third article</h2> <span class="author">Hlomzik</span> </li> </ul> */

Подключение CTS-файлов

В зависимости от реализации CTS-шаблонизатора, подключение CTS-файлов к документам можно сделать различными способами. Мы же сделаем это так, как это сделано в CSS: inline-декларация CTS-стилей и подключение внешних файлов.

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>Моя страница</title> <link href="template/sample.cts" type="text/cts" rel="transformation" /> </head> <body> <!-- Содержимое документа --> </body> </html>

А также можно использовать inline-декларацию (однако данный способ не рекомендуется из-за его семантической нестабильности):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>Моя страница</title> <style type="text/cts"> <!-- Правила трансформации --> </style> </head> <body> <!-- Содержимое документа --> </body> </html>

После обработки таких документов CTS-шаблонизатором, им следует удалить все следы подключений, чтобы они не попали в публичный документ.

Теория реализации

Со стороны реализации CTS мы видим два элемента: основанный на CSS парсер и XML-парсер (или XHTML-парсер). Сначала будет обработан XHTML-документ, будет создано дерево его элементов. XML-парсер определяет, есть ли подключения внешних CTS-файлов или inline-декларации CTS. Если они присутствуют, к работе подключается CTS-парсер, основанный на CSS-парсере. В соответствии с внутренним алгоритмом, структура XML-дерева преобразуется с учётом указанного набора правил и выдаётся конечный результат.

Автор планирует в дальнейшем реализовать шаблонизатор Каскадных Таблиц Трансформации и применять его на практике. Помимо этого, планируется улучшение самого CTS и добавление в него новых свойств.

В заключение

Кажется, что такая структура очень подходит верстальщикам. При наличии хорошей документации, все свойства становятся понятными и ими довольно легко манипулировать. Использование такого шаблонизатора упрощает работу, однако со стороны производительности такое упрощение стоит работы двух разнородных парсеров. К слову, производительность системы можно увеличить, если добавить кеширование на разных этапах шаблонизации.

В эту прекрасную пятницу традиция «Забавы ради», которая продолжается уже несколько серий, обретает довольно интересные формы, не правда ли?

Мнения (11)

Все эти хорошие люди уже прокомментировали запись. Поделитесь собственным мнением, расскажите, что вы думаете о поставленной проблеме, задаче, озвученных мыслях.

  • Curly Brace

    05 июля 2009 г.01:29

    Уииииииииииииии!!!

    Да ты же упоротый! Вот это круто! Нет, серьезно. Я блин чуть за валидолом не побежал, когда дочитал до момента где написано что ничего еще не реализовано!

    Это же гремучая смесь из XSLT, Jquery и CSS! Хочухочухочухочу.

    Капля дегтя, Дин. /*вкрадчиво*/ А как же быть с обработчиками событий? Что если я хочу запустить трансформацию не сразу, а после какого-либо действия? Или хотя бы применить оопределенную таблицу трансформаций не сразу?

    З.Ы. :last-loop-elemenet поправь

  • Дин автор

    05 июля 2009 г.03:44

    По поводу событий: если мы всё-таки решились реализовывать не только на серверной, но и на клиентской стороне подобные трансформации, то следует добавить необходимые CTS-селекторы специальные, например, :click или :hover или сделать специальное свойство listen-event, которое и будет определять, что данное применяется только при определённом Javascript-событии на странице. Я всё-таки за второй способ, так как он не требует введения новых селекторов, что мне кажется более экономным.

    А вообще, твой толчок реализовать и клиентскую версию — это хороший толчок. Спасибо!

  • игорь

    05 июля 2009 г.05:01

    серверный яваскрипт вас всех победит :) по крайней мере там где есть ajax и хоть какая-то динамика на клиенте..

    зы. хоть я фэн css я не считаю его синтаксис уж очень клёвым гибким и подходящим для таких монстро-решений. вообще конечно должно что-то с ним произойти. точно могу сказать что плохо каждый раз писать полный путь до вложенного элемента.

  • Дин автор

    05 июля 2009 г.05:23

    @Игорь, а почему монстро-решение? Оно вполне себе является лаконичным в меру. Ничего нового в CSS практически не добавляется, монстра из него сделать не получилось.

    Синтаксис CSS не должен быть гибким! В том-то и дело, что его синтаксис максимально лаконичен и использует однотипные конструкции для большинства операций. Эта простота, минимализм, в конце концов.

    Путь до вложенного элемента при постоянном обращении к нему следует всё-таки сокращать путём добавления элементу CSS-класса или идентификатора.

    А что там с серверным JavaScript? Серьёзно сравнивать CSS и JavaScript? ;-)

  • Волька

    05 июля 2009 г.16:37

    Возможно что лучше будет если подключать через processing-instruction(): <?xml-stylesheet href="articles.cts" type="text/css"?> Только тип надо заменить на что-нибудь навроде «application/cts».

  • Дин автор

    05 июля 2009 г.16:41

    @Волька, так CTS — это не stylesheet, а уж тем более не xml-stylesheet, поэтому я считаю, что данный способ не является достаточно семантичным для его применения на практике.

  • Sam

    05 июля 2009 г.19:05

    Не оно? http://icss.soulstream.ru/

  • Дин автор

    05 июля 2009 г.19:12

    @Sam, разумеется, нет. Абсолютно никакого сходства. При внимательном чтении записи и примеров в ней, это должно стать предельно ясным.

  • Аноним

    26 декабря 2009 г.14:15

    Использование такого шаблонизатора упрощяет работу

    упрощяет работу

    упрощяет

    Если вы ещё следите за блогом, то было бы неплохо поправить ☺

  • Дин

    11 января 2010 г.17:49

    Спасибо, исправил!

  • Kuroki Kaze

    12 июля 2010 г.21:27

    возможно, некоторые вещи из Less такому шаблонзатору не повредят :) Раз уж есть переменные, как там. В частности, возможно не помешают mixins и nested rules.

Я тоже знаю!

Для обращения к человеку используйте символ @, после которого следует имя того, к кому обращаетесь (пробелы заменяются на знак подчёркивания). Если вам интересно, можете подписаться на комментарии по RSS или по эл. почте. Ведите себя достойно, вы же не роботы, правда?

Вы можете использовать следующие XHTML-элементы в разметке комментария: strong, em, span[class=crossline], a[href=uri], code[type=язык], blockquote, ul и ol. В качестве языка кода может быть указан, например, javascript или css.