.. / XSLT. Краткий курс для HTML-верстальщиков (Техника мягкого ввода в XSLT)

  1. short_course_for_html-coder

Вводная

Зачем и почему это написано

XSLT технологии  начинают входить в реальную жизнь и практику HTML-верстальщиков. Несмотр на кризис в  Яндексе и Рэмблере практически постоянно были открыты вакансии XSLT-верстальщиков. И не только там. Многие отечественные веб-студии, использующие такие распространенные CMS  как   hostCMS и UMI CMS,  сталкивались с с тем, что найти верстальщиков, знающих XSLT  достаточно сложно. Проще обучать на месте.  
Одна из причин такого положения   в том, что пособий по XSLT на русском языке  нет или почти нет. При этом  большая часть из того, что есть,   мало приспособлено для быстрого изучения предмета.  То что издано на бумаге найти в магазинах невозможно - книги изданы давно, микроскопическими тиражами и давно раскуплены.   
Эти записки никак не изменят ситуацию. Но м.б. кому-то пригодится.  Писал  я их по большей части для себя. В феврале-марте 2009 в период вынужденного простоя.

Источники:

Увы. Найти книги, перечисленные ниже, найти в печатном виде невозможно. В электронном виде некотороые из них есть в электронных библиотеках.

  1. Стивен Холзнер. XSLT библиотека программиста.   ( Издательство "Питер" · 2002 г.   тираж 3000 экз)
    Это единственная книга, которую я могу порекомендовать в качестве  учебника. Она  достаточно лаконична и написана живым человеческим языком  и рассчитана на тех,  кто не имеет многолетней привычки читать на ночь спецификации W3С.  Книга построена таким образом, что все примеры берут за основу один и тот же XML-источник, поэтому путь от одного примера к другому идет существенно легче.   При желании pdf-версию этого учебника  можно найти в онлайновых библиотеках 
  2. Валиков А. Технология XSLT ( BHV-СПб. 2001 год, тираж 3000 экз.)
    "Технология XSLT" считается одной из лучших книг, хотя я это мнение не разделяю. Но она была первым русскоязычным печатным источником по этой теме.  Наверное в этом и кроется ее успех.  А может и в другом. В ней  превосходно подобраны примеры решения классических XSLT-задач (сортировки, группировки, операции с множествами и и т.п.), описаны достаточно просто и подробно.
  3. Майкл Кэй. XSLT. Справочник программиста   (Издательство "Символ-Плюс" · 2002 г.  тираж 2000 экз)   
    Увы это не учебник. Это справочник. Очень хороший и подробный. Однако отдельные главы очень хорошо объясняют сложные для понимания места в XSLT, а так же саму идеологию и принципы работы XSLT.

 

Структура заметок


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

Так же как и в HTML-верстке в XSLT существуют разные подходы к верстке и точно так же существуют разногласия по поводу возможности использования  тех или иных приемов, стилей программирования (вспомним древнюю дискуссию о дивно-табличной верстке).  На практике же,  применяются все методы в различной комбинации, в зависимости от задачи. 

Я начну с легких для понимания методов, и  относительно сложными. Относительно, потому что то с чем приходится сталкиваться при верстке в большинстве случаев не требует особых ухищрений. Сложности возникают, или на очень нетривиальных сайтах,  или там,  где архитекторы проекта поленились проработать структуру данных  и вывалили на  XSLT выполнение нехарактерных и ресурсоемких вычислительных функций.  Сам по себе  XSLT такой же язык стилевых таблиц как и CSS, но с большими возможностями. Большие возможности определяются наличием элементов, характерных для обычных языков программирования (обработка условий, циклы, процедуры, переменные) и специфических элементов функциональных языков программирования,  к которым относится XSLT. Там где возможно я стараюсь проводить аналогии между XSLT, CSS и JavaScript(PHP) и SQL, полагая , что HTML-верстальщик должен иметь представление об этих  языках, и уж в обязательном порядке знать CSS.  

Примеры и структура  данных

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

  1. список поэтов
  2. страница поэта
  3. страница стихотворения

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

Инструментарий

Лучшим инструментом верстки и отладки, на мой взгляд, является  Oxygen XML Editor. На официальном сайте вы можете скачать его trial-версию. Обход триала достаточно простой, но лучше купить. Академическая версия стоит очень немного.   Ниже я напишу как им пользоваться для отладки XSLT-шаблонов.

Есть так же  freeware инструменты, например EditIx, но к сожалению,  они сильно отстают по функционалу от Oxygen.

1. XML-минимум

1.1. Деревья и узлы

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

Узлы могут быть нескольких видов. Чтобы было более понятно сразу приведу HTML-код, на  примере которого рассмотрю эту типологию.

Листинг 1

<html>
                    <!-- Комментарий -->
                  <title>
                          Hello  World!
                 </title>
                  <meta name="description" content="Первый пример" />
                  <body style="background:red">
                           <h1>Hello XSLT World!</h1>
                           <div>Подражание  Кернигану-Ричи</div>
                 </body>
            </html>


  1. Корневой узел. Это абстракция. Его в примере не видно. Корневой узел расположен на уровне выше первого элемента.   Ближайшим аналогом может быть объект document  из DOM-модели HTML. К счастью, больше таких абстракций не потребуется.
  2. Узел элемента. Элемент - в примере любой тег с каким-то содержимым.  У него есть начало - открывающий тег (например <div>) и закрывающий тег (</div>). Элемент может ничего не содержать - быть пустым как тег <br />. Очень важно сразу почувствовать разницу между  - "узлом" и "элементом".
    Узел - понятие более широкое.  
  3. Узел атрибута. То что расположено в теле открывающего тега сразу после его имени. В примере - параметры name и description тега <meta> или стиль, заданный в background.
    У узла атрибута есть имя и значение.  Узел элемента <meta> содержит два узла атрибутов. Узел элемента body - один узел атрибутов и два  вложенных узлов элементов -  <h1> и <div>
    (!!! В XSLT  Узел элемента для своих атрибутов явлется родительским. Но узлы его атрибутов не рассматриваются как дочерние)
  4. Текстовый узел.  Это обычный текст без тегов. Например, текст "Hello World!"  в теге h1.
    При работе с текстовыми узлами у вас обязательно появятся некоторые проблемы, связанные с тем, что пробелы, табуляции и переносы строк  между тегами так же являются текстовыми узлами и могут по разному обрабатываться в процессе XSLT-преобразований. Об этом я подробно напишу позже.
  5. Узел комментария. Он и в HTML комментарий. 

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

1.2. Верстайте валидно. Или хотя бы "веллформенно"

Дискуссия о валидности верстки   - тема отдельная и неплодотворная, но для верстки в  XSLT  это совершенно безразлично. Тем кто привык верстать невалидно - может  упорствовать в ереси столько сколько нужно и даже больше. HTML-верстка может быть сколь угодно кривой, но XSLT-шаблон как любой другой XML-документ должен быть обязательно  "хорошо-сформированным" (well-formed). Т.е. должны соблюдаться три основных требования:

  1. XML-документ должен содержать один или более элементов(тегов), один из которых должен быть корневым (в xHTML это <html>)
  2. Каждый элемент должен содержать содержать закрывающий тег. Пустые элементы должны быть закрыты, подобно html-тегам  <hr/> и <br/>
  3. Должны соблюдаться правила вложенности. 

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

1.3 Кодировка

Кодировка - UTF-8. Про остальные забудьте. Считайте, что их больше не существует.

1.4 DOCTYPE

XSLT позволяет вывести документ в любом выходном формате. Но  в примерах я рассматривают только xHTML1.0  Для этого есть две причины. Во-первых, использование HTML4 просто нелогично, хотя бы потому что оcновное назначение XSLT - преобразование одного XML-документа в другой. Во вторых, HTML  не является веллформенным.  И в третьих,   в случае если на выходе преобразования получается веллфоменный XML его можно обработать еще одним или несколькими последовательными преобразованиями. В качестве примера можно привести получение какой-нибудь специфической версии   HTML-страницы. Например версии для печати или мобильного устройства. Ну и самое главное - надо смотреть в будущее. При очередной переверстке через 20-25 лет будет поще перейти на очередную версию HTML..

1.5 Мнемоники

Мнемоники - постоянная головная боль для HTML-верстальщиков, которые впервые сталкивается с XSLT. В HTML верстке привычные спецсимволы типа неразрывного пробела (&nbsp;), копирайта (&copy;) и т.п. обычно заменяются псевдонимами- именными мнемониками. В XSLT эти именные мнемоники не работают, за небольшим исключением:  "больше"(&gt;), "меньше" (&lt;), амперсанда (&amp;).   Символы  ", и ' также могут быть заменены на   &quot; и &apos; В остальных случаях вместо  именных мнемоник  должны использоваться цифровые мнемоники. Часть из них  в таблице ниже:

Символ Назначение Мнемоника Код
  неразрывный пробел &nbsp; &#160;
« направленная влево двойная угловая кавычка &laquo; &#171;
» направленная вправо двойная угловая кавычка &raquo; &#187;
маркер списка (буллит) &bull; &#8226;
" двойная кавычка &quot; &#34;
© знак охраны авторского права &copy; &#169;


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

2. Hello XSLT World!

2.1. Инструментальное вступление, не обязательное к прочтению

Если у вас нет никакого инструмента для работы с XSLT - скачайте   trial-версию Oxygen XML  editor. Инсталлируйте и запустите его.Срока работы trial-версии более чем достаточно для изучения XSLT в тех рамках, которыми я ограничусь.  Если есть другой инструмент, перейдите к следующему разделу. 
Итак по шагам.

  1. Нажмите на кнопку с пиктограмкой бага  и подписью XSLT. См. рисунок  ниже (можете нажать на картинку для ее увеличения)oxigen
  2. Откройте в директории  2 примеров файлы pushkin.xml и hello.xsl
  3. В крайнем левом листбоксе выберите опцию Xalan  (этот процессор в Oxygen нормально выводит кириллицу, может так же попробовать другие). 

  4. Нажмите кнопку с синей стрелкой для запуска преобразования.  
  5. В правом окне вы получили желаемый результат.
  6. В дальнейшей работе, когда будет открыто много XML и XSLT-файлов назначайте XML- источник и XSLT-шаблон, при помощи соответствующих листбоксов в верхней панели редактора.

Хотелось бы чуть подробнее остановиться на XSLT-процессорах . Их достаточно много. В  Oxygen они написаны на Java.  Но с Java мало кто имеет счастье сталкиваться на хостинге. Реально приходится работать с PHP или .NET  На этих платформах будут другие процессоры возможно результат их работы будет несколько отличаться в мелочах, например при обработке проблельных сиволов или UTF.  Поэтому возможны определенные нестыковки. Как правило фатального ничего в этом нет. Но это надо иметь ввиду. Если форма вывода предложенного мной Xalan   вас не устроит - попробуйте другой процессор.
На первых порах этого будет больше чем достаточно для отладки XSLT  в Oxygen.  Дальше я не возвращаюсь к   теме инструментария  и считаю что вы можете все самостоятельно отладить   

2.2. Дань традиции

По традиции, введенной 30 лет назад великими  Керниганом и Ричи первая программа должна  выводить  страницу с фразой "Hello World!".   Для HTML-верстальщика это должно выглядеть так:

Листинг 2.2

<html>
                  <body>
                           <h1>Hello   World!</h1>
                 </body>
            </html>

2.3 Поехали

Для преобразования по-любому нужно взять что-то, что мы должны преобразовать. Для примера  "Hello XSLT World" совершенно безразличен XML-источник, так как на выходе должен получиться статический текст. Для примера возьмем  файл pushkin.xml, даже не заглядывая в него. А вот xslt-файл, рассмотрим чуть подробнее:

 Листинг 2.3 hello.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
            
                <xsl:template match="/">
                    <html>
                        <body>
                            <h1>Hello  World!</h1>
                        </body>
                    </html>
                </xsl:template>
            </xsl:stylesheet>



Все элементы, относящиеся к XSLT, имеют префикс "xsl:". Это инструкции процессору, осуществляющему преобразование.
Первый элемент xsl:stylesheet указывает процессору, что это  XSLT версии 1.0 (есть еще и 2.0).  Он общий для всех шаблонов и в текстах я его буду опускать.
Второй элемент  xsl:template указывает процессору, правило, по которому должен быть преобразован корневой узел.  

Хотя этот элемент самый важный в XSLT, я подробнее рассмотрю в отдельном разделе (*).  В этом разделе будет один единственный подобный шаблон и в его опции match задается  или корневой узел или узел корневого элемента.
Процессор выводит все что не относится к XSLT  - те HTML-теги с содержимым, и ищет другие инструкции с префиксом "xsl:".  Поскольку в примере больше никаких инструкций нет, он больше ничего и не делает, кроме копирования  HTML-кода страницы "Hello world!"

Для того чтобы добавить DOCTYPE, соответствующий xHTML  необходимо добавить опцию вывода, сразу после строки xsl:stylesheet:

   

<xsl:output doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
                        doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" method="xml"
                        encoding="utf-8" indent="yes" />



Я пока оставляю параметры этой инструкции без комментариев, и  в дальнейшем не буду приводить ни эту строку ни обертку xsl:stylesheet Они в примерах меняться не будут.
 
Необходимо отметить две особенности копирования содержимого xsl инструкций, не относящихся к xsl: 
1) комментарии съедаются. Для включения комментариев во входной поток необходима специальная xsl-инструкция.
2) пробельные симолы (в том числе переносы строк)  обрабатываются достаточно странно и это отдельная тема.

3. XSLT. Техника мягкого ввода

3.1. По пути xPATH

В примере "Hello  World" я показал как вывести статический текст. Это не интересно, потому главная задача - научиться строить шаблоны. Т.е. переводить XML-данные в HTML-страницу. Для этого нужно научиться вытаскивать данные из XML. Чтобы было более понятно, я приведу пример простого XML файла с биографическими данным, на котором будет рассматриваться большинство примеров.

Листинг 3.2.1
   

<author id="1">
                    <fio>
                        <f>Пушкин</f>
                        <i>Александр</i>
                        <o>Сергеевич</o>
                    </fio>
                    <born>1799</born>
                    <rip>1837</rip>
                    <registry country="Россия" city="Москва" />
                    <text>
                        Александр Сергеевич <b>Пушкин</b>  - наше все! <br /> 
                        Пушкин разбудил <a href="http://en.wikipedia.org/wiki/Lermontov">Лермонтова</a>
                    </text>
            </author>




Адресация к данным, хранящимя в XML, строится на выражениях языка xPATH.  XSLT и XPATH связаны настолько тесно, что я местами не буду упоминать где из них что.  Самые простые выражения XPATH похожи на пути в файловых системах. Чтобы было понятно о чем речь приведу очевидные фрагменты и HTML-кода:

<img src="project/images/article/zero.gif" />
            <link rel="stylesheet" href="/project/css/css.css" type="text/css" />
            <script language="JavaScript" src="project/js/js.js" type="text/javascript"></script>



Cодержимое атрибутов src и href - это пути в фаловой системе.  Пути состоят из цепочек директорий, разделенных слэшем.   Для XML XPATH-пути будут выглядеть аналогичным  образом:
"/" - путь к корневому узлу
"/author" - путь к элементу author
"/author/fio/" - путь к элементу fio, содержащие ФИО
"/author/fio/f" - путь к элементу, содержащемк фамилию

Так же как и в фаловых системах есть понятие относительного и абсолютного пути.
Абсолютный путь отсчитывается от корневого узла и начинается с одинарного слэша  "/" как в примерах выше. Относительный путь отсчитывается от текущего положения.  В примерах выше были показаны абсолютные пути.

Есть еще один способ адресации, крайне нежелательный, но  служащий хорошим посдпорьем для  начинающих. Если точная структура XML неизвестна, но точно известно что есть элемент с именем "MYNAME", к нему можно обратиться так:  "//MYNAME". Два слеша означают  означает адресацию к  узлам с указанным именем, независимо от того где они встретятся.   Главное не запутаться, ведь в XML может встретиться не один элемент с именем  MYNAME, а рассмотренные выражения XPATH -  несмотря на сходство не  то же самое что путь в файловой  системе.  В XSLT эти выражения определяют множество узлов, и  используются  для формирования правил преобразования из входного XML в выходной.

Для адресации к атрибуту элемента необходимо перед его именем добавить префикс "@":
registry/@country - обращение к атрибуту  country, элемента  registry

Пока этого представления о языке xPATH будет достаточно. 

3.2   Буквально xsl:value

Для первого примера рассмотрим пример с выводом страницы биографических данных Пушкина. Исходный файл  pushkin.xml   выглядит так:

 Листинг 3.2.1 (pushkin.xml)

   

<author id="1">
                    <fio>
                        <f>Пушкин</f>
                        <i>Александр</i>
                        <o>Сергеевич</o>
                    </fio>
                    <born>1799</born>
                    <rip>1837</rip>
                    <registry country="Россия" city="Москва" />
                    <text>
                        Александр Сергеевич <b>Пушкин</b>  - наше все! <br /> 
                        Пушкин разбудил <a href="http://en.wikipedia.org/wiki/Lermontov">Лермонтова</a>
                    </text>
            </author>




Поробуем изобразить шаблон выводящий только фамилию. Например так:

   

<html>
                <body>
                   <h1>
                      Пушкин
                  </h1>
                </body>
            </html>



Листинг XSLT будет выглядеть так:

 Листинг 3.2.2 (puschkin-family.xsl)
   

     <xsl:template match="/">
                    <html>
                        <body>
                            <h1>
                                <xsl:value-of select="author/fio/f"/>
                            </h1>
                        </body>
                    </html>
                </xsl:template>

  


Как и в предыдущем примере процессор  по инструкции xsl:template начинает обработку корневого узла  "/", выводит все тексты и HTML-теги из XSLT-шаблона до тех пор, пока не встретит новые xsl-инструкции. Единственная инструкция в примере  xsl:value-of. Эта инструкция указывает процессору, что нужно вывести содержимое узла, указанного в параметре select. В этом параметре указан путь  к интересующему нас элементу входного дерева - элементу,  содержащему фамилию.  

В  предыдущем примере (Листинг 3.2.1)  путь к выбираемому элементу "author/fio/f" можно сократить следующим образом:
Листинг 3.2.3 (puschkin-family2.xsl)

<xsl:template match="author">
                <html>
                <body>
                <h1>
                    <xsl:value-of select="fio/f"/>
                </h1>
                </body>
                </html>
            </xsl:template>



В листинге 3.2.2  я задал инструкцию обработки входного XML  начиная от корневого узла  (xsl:template match="/"), в листинге 3.2.4 обработка пошла от узла элемента "author" (xsl:template match="author"). Соответственно изменилась и адресация в выборке ФИО автора. Внутри инструкции xsl:template  работают относительные пути. Отсчет идет от  узла указаного в опции match.
                         

3.3 Разбираемся с атрибутами

3.3.1 Вывод атрибутов из XML
Теперь нужно вывести страну проживания А.С.Пушкина. Но адрес задан в атрибутах узла элемента "registry". 
<registry country="Россия" city="Москва" />
Для того чтобы вывести страну  нужно применить ту же инструкцию, но в качестве параметра select задается путь к  атрибуту, содержащему страну.
 

Листинг 3.3.1 (puschkin-family3.xsl)
   

     <xsl:template match="author">
                    <html>
                        <body>
                            <h1>
                                <xsl:value-of select="fio/f"/>
                            </h1>
                            <h2>
                                <xsl:value-of select="registry/@country" />
                            </h2>
                        </body>
                    </html>
                </xsl:template>



Узел атрибута элемента (в данном случае элемента "registry"), является дочерним по отношению к самому элементу, поэтому появляется дополнительный слэш в пути.  Знак "@" перед именем "country", указывает что что это именно атрибут, а не другой элемент, который может быть вложен  в "registry" (например отдельные отметки в паспорте типа названия УВД, количества детей, жен и т.п. ).


3.3.2  Атрибуты HTML

Предположим, что необходимо для JS-обработки вывести ID автора из элемента "author" в какой-нибудь HTML-тег, например в h1, чтобы получить такой код:
<h1 id="author_id_1">Пушкин</h1>
Это можно сделать двумя способами. Рассмотрим самый короткий и простой:

Листинг 3.3.2 (puschkin-family3.xsl)

<xsl:template match="author">
            ...............................................
                <h1 id="author_id_{@id}">
                    <xsl:value-of select="fio/f"/>
                </h1>
            ...............................................
            </xsl:template>



Фигурные скобки указывают процессору, что нужно вывести содержимое указанное в пути. В примере указан путь к атрибуту "id" элемента "author".  Есть и другой способ, позволяющий определять атрибуты тегов как результат вычислений через элемент xsl:atribute. Его я рассмотрю в следующей части. (*).

3.  Копирование HTML-фрагментов.

3.1 Кошерное копирование xsl:copy-of

Попробуем точно так же вывести содержимое элемента "text".  

Листинг 3.4.1  (copytext1.xsl)

<xsl:template match="author">
                <html>
                <body>
                <h1><xsl:value-of select="fio/f"/></h1>
                <h2><xsl:value-of select="registry/@country" /></h2>   
                 <div>
                      <xsl:value-of select="text"/>
                 </div>
                </body>
                </html>
            </xsl:template>



Вывести текст получилось, но не так как хотелось бы.   Инструкция xsl:value-of съела все HTML-теги, содержащиеся в элементе text. Для того, чтобы выводить фрагмент HTML здеcь можно использовать команду xsl:copy-of, которая выполняет буквальное копирование:

Листинг 3.4.2  (copytext2.xsl)

<xsl:template match="author">
                <html>
                <body>
                <h1><xsl:value-of select="fio/f"/></h1>
                <h2><xsl:value-of select="registry/@country" /></h2>   
                 <div>
                      <xsl:copy-of select="text"/>
                 </div>
                </body>
                </html>
            </xsl:template>



Вторая попытка тоже получилась криво.  Элемент "text" входного XML скопировался полностью и в HTML появился "левый" тег "text". А нам нужен не сам элемент, а только его содержимое  - элементы и текстовые узлы. Для этого опцию select  выбора xsl:copy-of  нужно изменить следующим образом:  select="text/*". Если вы знаете как работать с командной строкой с файлами, то сразу увидите прямую аналогию. А если не умеете - вспомните что делает   в CSS селектор "*".  В обоих случаях так же как и в случае со "*"  в опции select предлагается выбрать все узлы элемента "text" .  Листинг теперь выглядит так:

Листинг 3.4.3  (copytext3.xsl)

<xsl:template match="author">
                <html>
                <body>
                <h1><xsl:value-of select="fio/f"/></h1>
                <h2><xsl:value-of select="registry/@country" /></h2>   
                 <div>
                      <xsl:copy-of select="text/*"/>
                 </div>
                </body>
                </html>
            </xsl:template>



Обратите внимание на то что при указании "*" выбираются именно узлы, а не элементы. Если забыли в чем разница, обязательно
вернитесь и прочитайте

Итак желаемый результат достигнут. Однако это далеко не все, что можно сделать при помощи XSLT . Если фрагмент HTML являетсям веллформенным XML, его можно преобразовать. Например - убрать ссылки или сделать эти ссылки невидимыми для поисковых машин, убрать пустые строки,  удалить отдельные теги и т.п. Но эта тема пока подождет, а пока лучше рассмотреть более часто употребимый,  но порицаемый способ вывода фрагментов HTML.

3.2 Некошерное копирование xsl:value-of

В разделе 1.2 я говорил об обязательности валидного кода. Валидный код прекрасен, но  жизнь обычно вносит свои коррективы. Если приходится переделывать старый сайт, в котором сотни мегабайт контента сверстаны еще в прошлом веке, надеяться на валидность кода глупо. Попробовать перегонять все при помощи Tidy  в валидный xHTML  можно, но результат валидации сложной верстки мягко говоря предсказуем. Точнее заранее предсказуем - получится фигня.  В этом случае невалидный HTML загоняют в XML при помощи специального блока CDATA 
 

<author id="1">
                     ..........................................................................
                    <text>
                     <![CDATA[
                        Александр Сергеевич <FONT SIZE="3" FACE="Courier New" COLOR="Magenta">Пушкин</font>- наше все! <p>
                        Пушкин разбудил <a href=http://en.wikipedia.org/wiki/Lermontov>Лермонтова</a><p>       
                      ]]>
                    </text>
            </author>



Для того чтобы вывести этот блок как есть не взирая на невалидность  нужно использовать xsl:value  со специальной опцией:
<xsl:value-of select="text" disable-output-escaping="yes"/>

 

3.5  Где в этом логика? xsl:if и xsl:choose

Без условного оператора  обойтись никак не получается. В продвинутом шаблонизаторе он обязан быть, и в XSLT, как и в большинстве языков программирования, есть два способа обрабатки  условий

3.5.1 xsl:if  

Предположим на странице автора нужно пометить тех кто усчастливился умереть до Октябрьской революции, от тех кому привелось жить при Советской Власти.  Для этого я буду проверять содержимое элемента rip в XML c  данными автора. При помощи вот такой конструкции:

                   

<xsl:if test="rip > 1917">
                   Советский поэт
            </xsl:if>



Если логическое выражение, содержащееся внутри опции test  истинно, будут выполняться вложенные внутри xsl:if  инструкции процессору. В данном случае инструкций нет. Есть текст, который будет выведен как есть.  
С проверкой на несоветскость сложнее. Следующая конструкция  ошибочна

                   

<xsl:if test="rip <1917">
               Несоветский поэт
            </xsl:if>



Символ '<' зарезервирован в xml, fа авторы XPATH не додумались ввести какую-нибудь удобную текстовую замену. Символ '<' должен заменяться на известную вам сущность: '&lt;'

                   

<xsl:if test="rip &lt; 1917">
                 Несоветский поэт
            </xsl:if>





Ниже приводится полный текст: 

Листинг 3.5.1  (if.xsl)
 
   

<xsl:template match="author">
                    <html>
                        <body>
                            <h1>
                                <xsl:if test="rip &gt; 1917">
                                    Советский поэт
                                </xsl:if>
                                <xsl:if test="rip &lt;= 1917">
                                    Несоветский поэт
                                </xsl:if>
                                <xsl:value-of select="fio/f"/>
                            </h1>
                        </body>
                    </html>
            </xsl:template>


 
Отступление.
Как на самом деле, то как работает xsl:if - отдельная тема. Если вы работали  javascript, php и т.п.,  то должны быть в курсе,  что например результат  сравнения двух переменных в этих языках  не всегда является очевидным - все зависит от типа данных. Точно так же обстоит и с XPATH. Позже я напишу об этом более подробно.

3.5.2   xsl:choose

 Пример из листинга 3.5.1 выполняет две проверки там где достаточно одной из-за того, что элементе if  нет else, как в других языках программирования. Там где возникают сложные условия можно использовать другую инструкцию обработки условий   -  xsl:choose.  В качестве аналогии можно привести swich/case из  javascript и php. Но если в этих языках для прекращения действия оператора приходится ставить break (что меня страшно раздражает), в XSLT действие  xsl:choose прекращается по выполнению первого условия. 

Предыдущий пример с проставлением штампа  "советский с применением  xsl:choose будет выглядеть следующим образом:

Листинг 3.5.2  (choose.xsl)

   

<xsl:template match="author">
                    <html>
                        <body>
                            <h1>
                                <xsl:choose>
                                    <xsl:when test="born > 1991">Постсоветский поэт</xsl:when>
                                    <xsl:when test="rip &lt; 1917 ">Несоветский поэт</xsl:when>
                                    <xsl:when test="(rip >= 1917) and (rip &lt; 1991)">Советский поэт</xsl:when>
                                    <xsl:otherwise>Просто поэт</xsl:otherwise>
                                </xsl:choose>                          
                                <span><xsl:value-of select="fio/f"/></span>
                            </h1>
                        </body>
                    </html>
            </xsl:template>



Первые две проверки практически не отличаются от примера в листинге 3.5.2   А в третьей проверке я усложнил условие и ввел логическую операцию "И" ("and")  Естественно что в  XSLT присутствует полный набор логических операций ("and", "or", "not").  
Элемент xsl:otherwise - финальный. Все инструкции вложенные в него  выполняются если ни одно из предыдущих условий не выполнено.

3.6  Зацикливаемся. xsl:for-each

Любой традиционный язык программирования немыслим без операторов цикла. В XSLT похожая конструкция тоже присутствует.  Я не случайно написал "похожая".  Она похожа на  обычные операторы цикла, к которым вы привыкли,  очень отдаленно. Отдаленно xsl:for-each  напоминает foreach в php или for в javascript, когда требуется перебор свойств объекта. Что-то  типа:

<script language="JavaScript" type="text/javascript">
for (i in document) {....}
</script>

Рассмотрим пример вывода списка всех авторов, используя новый XML-документ:
Листинг 3.6.1  (authors.xml)

<authors>
                <author id="1">
                    <fio>
                        <f>Пушкин</f>
                        <i>Александр</i>
                        <o>Сергеевич</o>
                    </fio>
                    <born>1799</born>
                </author> 
                <author id="2">
                    <fio>
                        <f>Лермонтов</f>
                        <i>Михаил</i>
                        <o>Юрьевич</o>            
                    </fio>
                    <born>1814</born> 
                </author> 
            ...............................................................
            ...............................................................
            ...............................................................
            ...............................................................
            </authors>






Необходимо сформировать такой список:

<table>
                <tr><td>1799</td><td><a href="author1.htm">Пушкин А.С.</a></td></tr>
                ............................................................................
                <tr><td>1703</td><td><a href="author7.htm">Кантемир А.Д.</td></tr>
            </table>



Вот как будет выглядеть xslt-шаблон для обработки этого XML:
Листинг 3.6.2  (foreach.xsl)
   

<xsl:template match="authors">
                    <html>
                        <body>
                        <table>
                            <xsl:for-each select="author">
                            <tr>
                                <td><xsl:value-of select="born"/></td>
                                <td><xsl:value-of select="fio/f"/></td>
                            </tr>
                            </xsl:for-each>    
                        </table>
                        </body>
                    </html>
                </xsl:template>
            </xsl:stylesheet>



В этом примере   xsl:for-each организует перебор всех элементов "author". Далее все что находится внутри этого этого элемента адресуется относительно текущего элемента "author", заданного в опции select  инструкции xsl:for-each  Поэтому для вывода значения года рождения, заданного элементом с именем "born"  используется конструкцию:
<xsl:value-of select="born"/>
Для вывода ФИО поэта используется конструкция
<xsl:value-of select="fio/f"/>

Можно усложнить задачу и пронумеровать строки.  Модифицируем предыдущий (Листинг 3.6.2 ) пример 
 
  Листинг 3.6.2  (foreach2.xsl)

.....................................................................................
             <table>
                            <xsl:for-each select="author">
                         <tr>
                                <td><xsl:value-of select="position()"/></td> 
                                <td><xsl:value-of select="born"/></td>
                                <td><xsl:value-of select="fio/f"/></td>
                        
                            </tr>
                            </xsl:for-each>    
             </table>
            ................................................................................



Здесь в первой строке появляется незнакомая конструкция position(). Как несложно догадаться это функция,  определяющая позицию текущего элемента. В данном случае элемента rec в списке авторов. Это одна из функций языка XPATH. По сравнению с традиционными языками программирования перечень функций не очень большой. В нем есть минимальный набор  строковых, арифметических и логических  функций, функции преобразования типа, а так же специфические для XSLT/XPATH функции работы с деревьями. Полный список этих функций можно посмотреть в приложении 1. !!!

Еще раз хотелось бы подчеркнуть, что цикл с использованием  xsl:for-each позволяет только перебрать имеющиеся элементы. Повторить некоторую операцию заданное число раз при помощи xsl:for-each, как это делают арифметические операторы цикла в javascript или php, вообще говоря невозможно.  Для этого в XSLT существуют другие способы. Но они не вполне тривиальные. 

Поскольку, я уже упомянул  встроенную функцию position(), трудно не рассмотреть  классическую задачу верстки - раскраску таблицы "зеброй". Каждая четная строка - должна быть покрашена одним цветом, а нечетная - другим.. Заодно    выведем список авторов с ссылками на страницы авторов. На выходе преобразования должно получиться следующее:

<table>
                <tr class="color0"><td>1799</td><td><a href="author/1">Пушкин А.С.</a></td></tr>
                <tr class="color1"><td>1814</td><td><a href="author/2">Лермонтов М.Ю.</td></tr>
                <tr class="color0"><td>1814</td><td><a href="author/3">Фет А.А.</td></tr>
                <tr class="color1"><td>1905</td><td><a href="author/4">Хармс Д.</td></tr>
                ............................................................................
                
            </table>



 Число в адресе ссылки должно соответствовать атрибуту id автора (Листинг 3.6.1), а класс color0 и color1 - цветам четных и нечетных строк.

Шаблон для такого преобразования будет следующим:
 
  Листинг 3.6.2  (foreach3.xsl)

   

<xsl:template match="authors">
                    <html>
                        <body>
                            <table>
                                    <xsl:for-each select="author">
                                        <tr class="color{position() mod 2}">
                                            <td><xsl:value-of select="position()"/></td>
                                            <td><xsl:value-of select="born"/></td>
                                            <td><a href="/author/{@id}"><xsl:value-of select="fio/f"/></a></td>
                                                  
                                        </tr>
                                    </xsl:for-each>   
                         </table> 
                        </body>
                    </html>
                
            </xsl:template>



Нового здесь почти ничего нет. Для формирования   адреса в ссылке и класс, определяющего цвет строки используется тот же прием что и в разделе 3.3.2  (Атрибуты HTML), а для    того чтобы задать четность используется арифметическое выражение деления по модулю 2 позиции элемента:
 (position() mod 2)
Результат деления   по модулю 2 равен 0 для четных позиций и 1 для нечетных. 
 

3.7  Наводим порядок. xsl:sort

В предыдущем разделе порядок вывода списка авторов повторяет порядок следования их в xml (Листинг 3.6.1).  Порядок достаточно произвольный, а для реального сайта необходимо, чтобы данные были упорядочены по алфавиту,  дате рождения или каким-то другим способом. Для сортировки служит элемент xsl:sort. С его использованием модификация примера (Листинг 3.6.2 )  будет выглядеть так:

Листинг 3.7.1  (sort1.xsl)
           

<table>
                    <xsl:for-each select="author">
                            <xsl:sort select="born"/>    
                            <tr>
                                <td><xsl:value-of select="position()"/></td> 
                                <td><xsl:value-of select="born"/></td>
                                <td><xsl:value-of select="fio/f"/></td>
                            </tr>
                    </xsl:for-each>  
            </table>



Опция select  элемента   xsl:sort  определяет по какому ключу идет сортировка. В примере - по дате рождения. Наверное еще раз надо повторить - внутри элемента xsl:for-each  действует относительная адресация. Т.е. отсчет идет от текущего элемента "author",  а символ "@" указывает на то что выбирается атрибут элемента author.  Если же надо упорядочить по имени автора, элемент сортировки должен выглядеть так:
<xsl:sort select="."/>
В XPATH  select="." означает выборку текущего элемента. В данном случае содержимое элемента - ФИО автора, и по нему производится сортировка.
Элемент xsl:sort имеет  дополнительные параметры: порядок сортировки ( убывание/возрастание), тип данных (числовой/текстовый) и др.
см. примеры на  zvon.org [1] [2] [3]  

3.7  Процедурный кабинет. xsl:call-template и xsl:import

В предыдущих разделах я сделал шаблоны для вывода списка авторов и страницы автора. При наличии данных, можно было бы сделать относительно полноценный сайт.  Но полноценному сайту не хватает   копирайта, логотипа, CSS,  метатегов и всякой бесполезной ерунды, типа  счетчика Рэмблера. Добавим  эти бебехи в шаблон списка авторов (модификация Листинга 3.6.2)

Листинг 3.7.1  (index0.xsl)

<?xml version="1.0" encoding="UTF-8"?>
            <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
                <xsl:template match="authors">
                    <html>
                        <head>
                            <title>Поэтический портал</title>
                            <meta  name="generator"  content="XSLT" />
                            <meta name="Yandex" content="Люби меня как я тебя!" />
                            <link rel="stylesheet" href="D:/WEB/aps/zpsy/js/RTE/screen.css" type="text/css" />
                        </head>
                              
                        <body>
                            <div id="header"><img src="logo.jpg" alt="Логотип сайта" /></div>
                            <table>
                                <xsl:for-each select="author">
                                    <tr>
                                        <td><xsl:value-of select="born"/></td>
                                        <td><a href="/author/{@id}"><xsl:value-of select="fio/f"/></a></td>
                                    </tr>
                                </xsl:for-each>   
                            </table>
                            <div id="footer">
                                (c) Исаак Тынгылчав
                                <img src="http://rambler/блаблабла"  alt="Rambler Top100" />
                            </div>
                        </body>
                    </html>
                </xsl:template>
                
            </xsl:stylesheet>



Этот шаблон применительно к authors.xml выведет  список авторов в формате блога.

Аналогичную модификацию шаблона страницы автора  (Листинг 3.5.2 )  я не привожу. В архиве он называется author.xsl и применяется к pushkin.xml

Рассматривая оба шалона, можно обнаружить явные излишества - одни и те же элементы присутствуют дважды.  При этом они содержат совершенно одинаковые данные. Копипаст в таких случаях не только  не нужен, но и вреден. Для его устранения  в традиционных языках программирования как правило используются процедуры. Похожий механизм есть и в XSLT. Он носит название "именованные шаблоны".  Сразу создадим отдельный библиотечный файл lib.xsl  Этот файл будет содержать описание процедур (именованных шаблонов), отвечающих за вывод общих для  index.xsl и author.xsl фрагментов HTML-кода:

Листинг 3.7.2  (lib.xsl)

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
                <xsl:template name="head">
                    <head>
                        <title>Поэтический портал</title>
                        <meta  name="generator"  content="XSLT" />
                        <meta name="Yandex" content="Люби меня как я тебя!" />
                        <link rel="stylesheet" href="D:/WEB/aps/zpsy/js/RTE/screen.css" type="text/css" />
                    </head>              
                </xsl:template>
            
                <xsl:template name="header">
                    <div id="header"><img src="logo.jpg" alt="Логотип сайта" /></div>           
                </xsl:template>
            
                
                <xsl:template name="footer">
                    <div id="footer">
                        (c) Исаак Тынгылчав
                        <img src="http://rambler/блаблабла"  alt="Rambler Top100" />
                    </div>
                </xsl:template>  
            
            </xsl:stylesheet>



Для того, чтобы подгрузить этот библиотечный файл в index.xsl и authors.xsl после инструкции xsl:stylesheet нужно добавить строку:
 

<xsl:import href="lib.xsl"/>


Наверное было бы лишним проводить прямую аналогию с инструкцией @import в CSS или include в php

Вызов же процедур (именованных шаблонов) в index.xsl и authors.xsl  выглядит так:
 

<xsl:call-template name="head" />
             <xsl:call-template name="footer" />



В том месте xslt-шаблона, где будет произведен вызов именованных шаблонов  head и footer  будет произведен вывод содержащихся в них фрагментов HTML-кода.  

Листинг 3.7.1 c с использованием процедур - именованных шаблонов  будет переписан в виде:
  

Листинг 3.7.2  (index2.xsl)

<?xml version="1.0" encoding="UTF-8"?>
            <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
             <xsl:import href="lib.xsl"/>
                <xsl:template match="authors">
                    <html>
                         <xsl:call-template name="head" />
                        <body>
                         <xsl:call-template name="header" />
                            <table>
                                <xsl:for-each select="author">
                                    <tr>
                                        <td><xsl:value-of select="born"/></td>
                                        <td><xsl:value-of select="fio/f"/></td>
                                    </tr>
                                </xsl:for-each>   
                            </table>
                            <xsl:call-template name="footer" />
                        </body>
                    </html>
                </xsl:template>
             </xsl:stylesheet>



Аналогичную операцию со страницей автора вы можете сделать самостоятельно, но если лениво - смотрите файл author2.xsl
На этом можно было бы пока и закончить, но процедура без  параметров выглядит немного странно.  В XSLT  точно так же как и в традиционных языках в процедуры (именованные шаблоны) можно передавать параметры. Рассмотрим на примерах  страниц index2.xsl и author2.xsl

Очевидно, что заголовки title страниц должны быть уникальными. В примерах с применением шаблонов author2.xsl и index2.xsl они  общие для всего сайта что совершенно неправильно. Для того чтобы передать параметры в процедуру ее описание должно быть следующим:
Листинг 3.7.3
   

<xsl:template name="head">
                 <xsl:param name="title">Поэтический портал </xsl:param>
                    <head>
                        <title><xsl:value select="$title" /></title>
                        ........................................................................
                        ........................................................................
                        ........................................................................
                    </head>              
            </xsl:template>


Или в сокращенной форме:
Листинг 3.7.3
   

<xsl:template name="head">
                 <xsl:param name="title" select="Поэтический портал"/>
                 ......................................................................................        
                 ......................................................................................        
                 ......................................................................................        
            </xsl:template>


Наличие передаваемого параметра определяется инструкцией xsl:param. В опции этой инструкции select  задается значение параметра по умолчанию (если вы знакомы с  PHP,  вам это понятие должно быть знакомо). Если производится вызов  процедуры (именованного шаблона) xsl:call-template   без указания параметра, его значение будет равно значению по умолчанию.
Для того чтобы вывести значение праметра используется знакомая инструкция xsl:value. Имени параметра в опции select должен прешествовать знак доллара.

Для главной страицы содержимое title фиксированно, так как страница уникальна. И оно может быть введено в качестве title, которое будет по умолчанию ставится на всех несущественных страницах сайта.  А вот для страницы автора содержимое title должно содержать имя автора. Это данные из  входного XML. Если забыли - откатитесь в раздел 3.2 и посмотрте как выглядел XML и XSLT. Формирование параметра для title на странице автора будет выглядеть так:
Листинг 3.7.4
               

<xsl:call-template name="head" >
                                <xsl:with-param name="title">
                                    <xsl:value-of select="fio/f"/>. Биография.
                                </xsl:with-param>
              </xsl:call-template>


Кроме  инструкции  xsl:import существует аналогичная инструкция xsl:include  их отличия в правилах разрешения конфликтов, в случае если одному и тому же узлу соответствуют несколько правил обработки с одинаковым приоритетом.    

3.8  Заключение 

На этом раздел   можно закончить.  Полученной информации достаточно чтобы сверстать среднестатистический сайт - персональный блог, несложный интернет-магазин и т.п.  Сверстанные в таком стиле шаблоны мало чем отличаются  от традиционных PHP-шаблонизаторов.  Но это очень ограниченный стиль программирования, в котором от XSLT-шаблонов почти ничего нет.  В следующем разделе я постараюсь объяснить именно ту специфику работы с шаблонами, которая отличает ее от всех прочих шаблонизаторов.


ПРОДОЛЖЕНИЕ  СЛЕДУЕТ

  1. 2009-11-27
  2. short_course_for_html-coder
  1. Примеры к тексту - erum.ru/doc/xslt_for_beginners.zip
  2. zvon.org/xxl/XSLTutorial/Output_rus/index.htm - XSLT в примерах (zvon.org)
Go Index Test