XSLT-шаблонизатор для PHP

Привіт! Мене звати Артем, я — PHP-програміст. У цій статті розглянемо основи XSLT і приклади його використання як шаблонизатора для веб-сайтів, які створено мовою PHP.

Зазвичай, коли створюють сайти з PHP, для динамічного формування HTML-сторінок виведення використовують один з багатьох Smarty-подібних шаблонизаторів. На моє глибоке переконання, XSLT у цій ролі дуже недооцінили, і я спробую виправити цю прикру помилку.

Коли я лише починав вивчати PHP, шаблонизатори не використовували взагалі, а виведення даних з PHP у HTML було заведено здійснювати безпосередньо. Згодом з’явився та швидко набув популярності шаблонизатор Smarty, тож створювати представлення згідно з архітектурним шаблоном MVC стало набагато простіше й цікавіше. Але коли я випадково, читаючи все поспіль у посібнику про PHP, познайомився з XSL-трансформацією, то зрозумів, що підсів на неї й це надовго.

Схема роботи XSLT

Зізнаюся відверто, що не відразу зрозумів цю технологію, а тим паче оцінив її переваги. Насамперед мене спантеличувала парадигма програмування, яка істотно відрізняється від звичної для PHP об’єктно-орієнтованої, або навіть процедурної. До того ж мова Extensible Stylesheet складніша, як порівняти з тим же Smarty. І це не дивно, адже XSLT — цілком незалежна самодостатня мова, розроблена W3C як міжнародний стандарт для перетворення структур даних. Але згодом, коли мій мозок звик до нової парадигми, я зрозумів, що мої перестороги були марними.

Документ у форматі XML за допомогою шаблонів XSL можна змінити на XML-документ з іншою структурою або на HTML-сторінку, Plain Text чи навіть PDF. Але саме зараз нас цікавить на виході лише HTML. І для цього нам навіть спочатку не знадобиться PHP: більшість сучасних веб-оглядачів уміє здійснювати XSL-перетворення самостійно. Налаштуймо для цих потреб локальний тестовий веб-сайт (наприклад, цей) і створімо в ньому кілька файлів.

Шаблони

Як на мене, шаблони найскладніші й водночас найцікавіші в XSLT, і тому дуже важливо зрозуміти, як вони працюють. Саме тому, перед тим як переходити до інших простіших інструкцій, потрібно приділити якнайбільше уваги вивченню цього питання.

<?xml version="1.0" encoding="UTF-8" ?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/*">
        ...
        <xsl:apply-templates select="country/city" />
        ...
        <xsl:call-template name="map" />
        ...
    </xsl:template>

    <xsl:template match="country/city">
        ...
    </xsl:template>

    <xsl:template name="map">
        ...
    </xsl:template>

</xsl:stylesheet>

XSL-файл складається зі щонайменше одного або кількох шаблонів template. У них за допомогою простих інструкцій у вигляді XSLT-елементів, створюють нову структуру даних на основі вхідних даних. Або, інакше кажучи, шаблон XSLT описує, які дані з вхідного XML-файлу й у якому місці виводити (у цьому разі — поміж HTML), і як саме.

Шаблони XSLT, так само як і функції в програмуванні, мають два призначення: основне й додаткове. Основне — це уникнення дублювання коду шляхом його повторного використання. А додаткове — поділ великих шматків коду на менші, зручніші для сприйняття.

Шаблон <xsl:template match="/*">...</xsl:template> обробляє головний елемент вхідних даних, який розташовано в самому корені XML-документа, тому це головний шаблон. Він автоматично запускається першим, і зазвичай через нього застосовують усі інші шаблони, прописуючи відповідні виклики шаблонів з відповідними умовами або іменами в певних місцях.

Шаблони в XSLT викликаються двома різними способами та мають різні підходи до області видимості вхідних даних XML у самому шаблоні. Я називаю їх, за аналогією з умовним переходом в програмуванні, шаблонами з умовним або безумовним викликом.

Шаблон з умовним викликом застосовують за допомогою елемента apply-templates з атрибутом select, у якому вказано умову виклику шаблона. Виклик зактивізується лише тоді, коли хоч якась частина вхідного документа відповідає умові виклику. Цей тип шаблонів, по-моєму, є родзинкою XSLT і робить його таким, яким він є. Він за своєю природою реалізує принцип обробки потокових даних, а саме тут — ще й ієрархічної структури.

<!-- Застосувати шаблон, якщо
 на поточному рівні дерева присутній елемент country -->
<xsl:apply-templates select="country" />

<!-- Застосувати шаблон, якщо
 на поточному рівні дерева присутній елемент country,
  який містить дочірній елемент city -->
<xsl:apply-templates select="country/city" />

<!-- Застосувати шаблон, якщо
 на поточному рівні дерева присутній елемент country,
  який містить дочірній елемент city,
    який містить атрибут size -->
<xsl:apply-templates select="country/city[@size]" />

<!-- Застосувати шаблон, якщо
 на поточному рівні дерева присутній елемент country,
  який містить дочірній елемент city,
    який містить атрибут size,
      який має значення 'big' -->
<xsl:apply-templates select="country/city[@size='big']" />

<!-- Застосувати шаблон з атрибутом mode,
 який має значення article, якщо
  на поточному рівні дерева присутній елемент country,
    який містить дочірній елемент city,
      який містить атрибут size,
        який має значення 'big' -->
<xsl:apply-templates select="country/city[@size='big']" mode="article" />

Особливо зауважте, що звернення до вхідних даних XML у шаблоні, який викликали в такий спосіб, здійснюється відносно того елемента, який зазначено останнім в умові виклику шаблона. У першому прикладі звертання в самому шаблоні до даних здійснюватиметься відносно елемента country. У решті чотирьох випадків — відносно елемента city.

Якщо ви хочете отримати дані з елементів чи атрибутів, які містяться за межами кореневого елемента цього шаблона, то до них можна звернутися або за допомогою абсолютного шляху (наприклад: /document/country/@title), або за допомогою відносного (наприклад: ../@title).

Шаблон з безумовним викликом викликають за допомогою інструкції call-template і, на противагу попередньому, абсолютно без жодних умов. Як наслідок, в інструкції й у шаблоні, який вона викликає, указують власну назву шаблона — name, а не умову його застосування.

За своєю суттю він — аналог виклику функції в програмуванні, за винятком підходу до області видимості даних. У разі виклику шаблона за допомогою цього способу доступ до вхідних даних відбувається так само, як і в місці виклику шаблона. Тобто цей метод виклику під час виконання ніби вставляє весь код із шаблона в те місце, де його викликають, замість інструкції виклику.

Для того щоб краще зрозуміти шаблони, методи їхнього виклику й інші пов’язані із цим речі, пропоную створити невеликий практичний приклад. Створімо спочатку простенький XML-файл templates.xml лише з кількома елементами.

<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="/templates.xsl" ?>
<document>
    <element attribute="Значення атрибута елемента">
        <subelement>Значення дочірнього елемента</subelement>
    </element>
</document>

Хоча виклик шаблонів у різних режимах і використовують на практиці дуже рідко, але це дає можливість запускати два різних шаблони для однієї умови. Передавання параметрів у шаблон використовують частіше й, на відміну від режиму, можуть застосовувати для обох способів виклику шаблона.

<?xml version="1.0" encoding="UTF-8" ?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="html" indent="yes" encoding="UTF-8" media-type="text/html" />

    <xsl:template match="/document">
        <html>
            <head>
                <title>Шаблони</title>
            </head>
            <body>
                <h1>Приклад головного шаблону</h1>
                <div class="class1">...</div>
                <!-- Приклад умовного виклику шаблону -->
                <xsl:apply-templates select="element" />
                <!-- Приклад безумовного виклику шаблону -->
                <xsl:call-template name="name1" />
                <!-- Приклад безумовного виклику шаблону з параметром -->
                <xsl:call-template name="name2">
                    <xsl:with-param name="title" select="'Приклад параметра до шаблона'" />
                </xsl:call-template>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="element">
        <div class="class2">
            <h2>Приклад шаблону з умовним викликом</h2>
            <em><xsl:value-of select="@attribute" /></em>
            <p>...</p>
            <!-- Приклад умовного виклику вкладеного шаблону в режимі mode1 -->
            <xsl:apply-templates select="subelement" mode="mode1" />
            <!-- Приклад умовного виклику вкладеного шаблону в режимі mode2 -->
            <xsl:apply-templates select="subelement" mode="mode2" />
        </div>
    </xsl:template>

    <xsl:template match="element/subelement" mode="mode1">
        <div class="class3">
            <h3>Приклад вкладеного шаблону з умовним викликом в режимі mode1</h3>
            <em><xsl:value-of select="." /> (mode1)</em>
            <p>...</p>
        </div>
    </xsl:template>

    <xsl:template match="element/subelement" mode="mode2">
        <div class="class4">
            <h3>Приклад вкладеного шаблону з умовним викликом в режимі mode2</h3>
            <em><xsl:value-of select="." /> (mode2)</em>
            <p>...</p>
        </div>
    </xsl:template>

    <xsl:template name="name1">
        <div class="class5">
            <h2>Приклад шаблону з безумовним викликом</h2>
            <p>...</p>
        </div>
    </xsl:template>

    <xsl:template name="name2">
        <xsl:param name="title" />
        <div class="class6">
            <h2>Приклад шаблону з безумовним викликом з параметром</h2>
            <em><xsl:value-of select="$title" /></em>
            <p>...</p>
        </div>
    </xsl:template>

</xsl:stylesheet>

А тепер найцікавіше — процес трансформації. Запускаємо створений нами XML-файл за адресою і насолоджуємося нашою першою HTML-сторінкою, згенерованою за допомогою XSL-перетворення з боку клієнта за допомогою веб-оглядача.

Приклад роботи шаблонів в XSLT

Якщо подивитися на початковий код сторінки, то побачимо там не HTML-теги, як ми звикли, а дані XML. І це правильно, тому що саму HTML-сторінку створює веб-оглядач, а XML-файл для нього — це першоджерело.

Для того щоб усе-таки побачити HTML-код новоствореної сторінки, потрібно скористатися внутрішніми інструментами розроблення, які надає веб-оглядач (наприклад Chrome DevTools). Якщо ви зробили все правильно, то код вашої сторінки матиме приблизно такий вигляд:

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Шаблони</title>
    </head>
    <body>
        <h1>Приклад головного шаблону</h1>
        <div class="class1">...</div>
        <div class="class2">
            <h2>Приклад шаблону з умовним викликом</h2>
            <em>Значення атрибута елемента</em>
            <p>...</p>
            <div class="class3">
                <h3>Приклад вкладеного шаблону з умовним викликом в режимі mode1</h3>
                <em>Значення дочірнього елемента (mode1)</em>
                <p>...</p>
            </div>
            <div class="class4">
                <h3>Приклад вкладеного шаблону з умовним викликом в режимі mode2</h3>
                <em>Значення дочірнього елемента (mode2)</em>
                <p>...</p>
            </div>
        </div>
        <div class="class5">
            <h2>Приклад шаблону з безумовним викликом</h2>
            <p>...</p>
        </div>
        <div class="class6">
            <h2>Приклад шаблону з безумовним викликом з параметром</h2>
            <em>Приклад параметра до шаблона</em>
            <p>...</p>
        </div>
    </body>
</html>

Решта інструкцій

Для демонстрації решти поширених XSLT-інструкцій я підготував новий приклад, який, на відміну від попередніх штучних, хоча б віддалено нагадує шаблон з реального проєкту.

XML

Але спочатку для демонстрації можливостей шаблона потрібно створити файл index.xml з певними структурою й даними.

<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="/index.xsl" ?>

<root title="Новини України та світу" copyright="2019"
      description="Самі свіжі та точні новини України та світу">
    <menu>
        <item title="Домашня" description="Домашня сторінка сайту" section="home" />
        <item title="Новини" description="Стрічка новин" section="news" />
        <item title="Категорії" description="Перелік категорій публікацій" section="category" />
        <item title="Мітки" description="Список міток публікацій" section="tag" />
        <item title="Автори" description="Перелік авторів публікацій" section="user" />
    </menu>
    <main />
    <notifications>
        &lt;p&gt;Редакція може не погоджуватись з автором публікації&lt;/p&gt;
        &lt;p&gt;Передрук частини публікації дозволено тільки за наявності посилання&lt;/p&gt;
        &lt;p&gt;Рекламні матеріали публікуються виключно з відповідною позначкою&lt;/p&gt;
    </notifications>
</root>

Стандарт XML, як і решта поширених форматів даних (CSV, YAML, JSON), настільки простий, що я не витрачатиму час на його опис. Лише в таблиці нижче наведу кілька прикладів найпоширеніших способів здійснення навігації в ієрархічній структурі за допомогою мови запитів XPath, яку, до речі, теж створили в W3C. Якщо вам цих прикладів буде замало, завжди можна підглянути в розділі «Синтаксис XPath».

/rootПряме звернення до кореневого елемента root
/root/@titleПряме звернення до атрибута title кореневого елемента root
/root/menuЗвернення до дочірніх елементів menu батьківського кореневого елементу root
/root/menu/*Звернення до нащадків батьківського елемента menu кореневого елемента root
menu/*/@titleВідносне звернення до атрибутів title нащадків батьківського menu
menu/item[1]/@sectionВідносне звернення до атрибута section першого дочірнього елемента item батьківського елемента menu

XSL

А тепер створимо файл шаблона index.xsl, у якому з метою ознайомлення використаємо найпоширеніші XSL-інструкції.

<?xml version="1.0" encoding="UTF-8" ?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="html" indent="yes" encoding="UTF-8" media-type="text/html" />

    <xsl:variable name="section" select="/root/@section" />

    <xsl:template match="/*">
        <xsl:text disable-output-escaping="yes">&lt;!DOCTYPE html &gt;</xsl:text>
        <html xml:lang="uk" lang="uk" dir="ltr">
            <head>
                <title><xsl:value-of select="@title" /></title>
                <meta name="description" content="{@description}" />
                <link href="/index.css" rel="stylesheet" />
                <script src="/index.js" type="application/javascript" />
            </head>
            <body>
                <header><xsl:call-template name="menu" /></header>
                <main>
                    <h1><xsl:value-of select="@title" /></h1>
                    <div class="body {name(main/.)}">
                        <xsl:apply-templates select="main/*" />
                    </div>
                </main>
                <footer>
                    <div class="copyright">
                        <p>Всі права застережено © <xsl:value-of select="@copyright" /></p>
                    </div>
                    <div class="notifications">
                        <xsl:value-of select="notifications" disable-output-escaping="yes" />
                    </div>
                </footer>
            </body>
        </html>
    </xsl:template>

    <xsl:template name="menu">
        <nav>
            <ul>
                <xsl:for-each select="menu/item">
                    <li>
                        <xsl:if test="@section=$section">
                            <xsl:attribute name="class">active</xsl:attribute>
                        </xsl:if>
                        <a href="/?section={@section}" title="{@title}">
                            <xsl:value-of select="@title" />
                        </a>
                    </li>
                </xsl:for-each>
            </ul>
        </nav>
    </xsl:template>

    <xsl:template match="home">Домашня ...</xsl:template>

    <xsl:template match="news">Новини ...</xsl:template>

    <xsl:template match="category">
        <div id="index" class="index index-{name(.)}">
            <xsl:choose>
                <xsl:when test="items/item">
                    <xsl:for-each select="items/item">
                        <p>
                            <strong><xsl:value-of select="@title" /></strong>
                            - <xsl:value-of select="@description" />
                        </p>
                    </xsl:for-each>
                </xsl:when>
                <xsl:otherwise>
                    <p>Записів не знайдено</p>
                </xsl:otherwise>
            </xsl:choose>
        </div>
    </xsl:template>

    <xsl:template match="tag">Мітки ...</xsl:template>

    <xsl:template match="user">Автори ...</xsl:template>

</xsl:stylesheet>

Перше, що впадає в око тим, хто раніше не працював зі XSLT, — це те, як гармонійно його інструкції вписуються поміж HTML-розмітками. Цьому є просте пояснення: XML, HTML, XSLT і багато інших цікавих речей розробили World Wide Web Consortium, і їх реалізують у схожий спосіб. Тобто XSLT для HTML, на відміну від інших поширених шаблонизаторів, — споріднене середовище.

Нижче я спробую якомога стисліше й водночас досить зрозуміло описати доволі об’ємний матеріал. Заразом я не відволікатимуся на загальні речі на кшталт версій, простору імен або опцій налаштування перетворення, про які ви без проблем зможете дізнатися з довідника самостійно.

<meta name="description" content="{@description}" />

Фігурні дужки (шаблон значення атрибута) — дуже зручний спосіб для виведення рядкових типів даних (елементів, атрибутів, змінних, убудованих функцій тощо) зі вхідного документа саме в значенні атрибута елемента шаблона.

<xsl:value-of select="@title" />
 <xsl:value-of select="notifications" disable-output-escaping="yes"/>

Ця інструкція схожа на попередню, але, на відміну від неї, здійснює виведення за межами значення атрибута елемента. Зверніть увагу на її дуже корисний атрибут disable-output-escaping, який дозволяє виводити текст зі службовими символами на кшталт < та >. Це інколи потрібно, наприклад, коли в тексті, що виводимо, присутні теги з форматуванням тексту, посилання тощо.

<xsl:attribute name="class">active</xsl:attribute>

Згідно з документацією, ця інструкція створює атрибут з певним значенням. Та на практиці в шаблонизаторі її використовують для динамічного створення атрибута або зміни його значення залежно від якоїсь умови, тому що створювати атрибути без умов простіше відразу в шаблоні статично, прописуючи його в елементі.

<xsl:variable name="section" select="/root/@section" />

Створює змінну зі значенням, область видимості якої залежить від місця її створення. Якщо її створили на самому початку файлу, то це глобальна змінна, до якої є доступ з будь-якого місця. А якщо в самому шаблоні, її видно тільки там.

<xsl:text disable-output-escaping="yes">&lt;!DOCTYPE html&gt;</xsl:text>

А ось цей елемент використовують доволі рідко, але я змушений його згадати принаймні через цей випадок. Річ у тому, що стандарт XSLT версії 1.0, який ми зараз використовуємо, розробили аж далекого 1999-го (за 15 років до створення стандарту HTML5). І, як наслідок, у ньому не передбачили нового методу оголошення версії HTML5 штатними засобами, як це зробили для попередніх версій HTML. Тому доводиться розв’язувати цю проблему за допомогою таких от хитрощів. З іншого боку, на цьому прикладі дуже просто продемонстрували принцип роботи атрибута disable-output-escaping.

<!-- Приклад інструкції одинарного умовного переходу -->
<xsl:if test="@section=$section">...</xsl:if>

<!-- Приклад інструкції множинного умовного переходу -->
<xsl:choose>
    <xsl:when test="a &lt; b">...</xsl:when>
    <xsl:when test="a &gt; b">...</xsl:when>
    <xsl:otherwise>...</xsl:otherwise>
</xsl:choose>

<!-- Приклад інструкції перебору елементів з вхідного документа -->
<xsl:for-each select="menu/item">...</xsl:for-each>

Останні три інструкції я об’єднав в одному прикладі, оскільки ви їх і так добре знаєте: вони аналогічні операторам з інших мов програмування. Однак варто зазначити, що знаки «менше» чи «більше» (< чи >) водночас службові і їх використовують для позначення тегів. Тому в умовах інструкцій їх варто замінити на їхні HTML-сутності (&lt; чи &gt;).

Зверніть увагу, що звернення до елементів XML-файлу в середині конструкції перебору елементів відбувається відносно елемента, який указано останнім в атрибуті select, так само, як і в шаблонах з умовним викликом.

Якщо я нічого не забув, то описав усі поширені інструкції, за допомогою яких ви вже зараз зможете використовувати XSLT як шаблонизатор. А якщо їх вам буде замало, ось вам посилання на повний перелік елементів з описом і прикладами щодо кожного: XSLT Elements Reference.

XML + XSLT = HTML

Перед тим як запустити процес трансформації, раджу витратити трохи часу на створення двох додаткових маленьких файлів. До самого перетворення вони жодного стосунку не мають, але своєю чергою вможливлять ознайомлення з шаблонизатором відразу в комплексі з іншими технологіями, потрібними для створення сайтів.

Спочатку створимо index.css із трьома рядками, щоб надати меню більш-менш людського вигляду.

header nav ul {list-style: none;padding: 0;}
header nav ul li {display: inline;}
header nav ul li.active {font-weight: bold;}

Потім раджу створити файл index.js і додати в нього мінімум два рядки, щоб перевірити роботу JavaScript.

let time=new Date().toLocaleTimeString('uk-UA');
console.log('JavaScript завантажено успішно ['+time+']');

Якщо ми все робили правильно, то в кореневій теці нашого тестового сайту, окрім створених у попередніх розділах файлів templates.xml і templates.xsl, мають бути ще 4 файли: index.xml, index.xsl, index.css та index.js.

Тоді запускаємо раніше створений XML-файл і вже вдруге за сьогодні насолоджуємося магією XSLT.

Приклад роботи інших інструкцій XSLT

Цього разу вихідний HTML-файл має трохи цікавіший вигляд.


<!DOCTYPE html>
<html xml:lang="uk" lang="uk" dir="ltr" data-lt-installed="true">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Новини України та світу</title>
        <meta name="description" content="Самі свіжі та точні новини України та світу">
        <link href="/index.css" rel="stylesheet">
        <script src="/index.js" type="application/javascript"></script>
    </head>
    <body>
        <header>
            <nav>
                <ul>
                    <li><a href="/?section=home" title="Домашня">Домашня</a></li>
                    <li><a href="/?section=news" title="Новини">Новини</a></li>
                    <li><a href="/?section=category" title="Категорії">Категорії</a></li>
                    <li><a href="/?section=tag" title="Мітки">Мітки</a></li>
                    <li><a href="/?section=user" title="Автори">Автори</a></li>
                </ul>
            </nav>
        </header>
        <main>
            <h1>Новини України та світу</h1>
            <div class="body main"></div>
        </main>
        <footer>
            <div class="copyright"><p>Всі права застережено © 2019</p></div>
            <div class="notifications">
                <p>Редакція може не погоджуватись з автором публікації</p>
                <p>Передрук частини публікації дозволено тільки за наявності посилання</p>
                <p>Рекламні матеріали публікуються виключно з відповідною позначкою</p>
            </div>
        </footer>
    </body>
</html>

PHP + XML + XSLT = HTML

Якщо у вас все вийшло й ви побачили HTML-файл, гадаю, ви неодмінно спробували перевірити роботу меню. Але під час переходів по його пунктах на сторінці нічого не відбувається, бо XSLT відповідає тільки за виведення інформації. А якщо ви хочете її змінювати залежно від дій користувача, потрібно створити хоча б якийсь примітивний контролер.

PHP має дуже багато різних інструментів для роботи з XML, і найпростіший з них — це SimpleXML. За його допомогою можна працювати з XML-документом надзвичайно просто, як з масивом чи об’єктом. Він може створювати новий чи відкривати наявний XML на основі рядка, уносити в нього зміни й зберігати його у файл чи конвертувати в рядок для виведення.

Ось невеличкий приклад того, як просто з ним працювати:

$xml = '<?xml version="1.0" encoding="UTF-8" ?><document time="" />';

$node = new SimpleXMLElement($xml);

$node['time'] = time();

$nodeCountry = $node->addChild('country');
$nodeCountry->addAttribute('title', 'Україна');
$nodeCountry->addAttribute('image', '/images/ua.jpg');

$nodeCity = $nodeCountry->addChild('city');
$nodeCity->addAttribute('title', 'Львів');
$nodeCity->addAttribute('image', '/images/lviv.jpg');

$nodeObject = $nodeCity->addChild('object', 'Опис ...');
$nodeObject->addAttribute('title', 'Львівська Опера');
$nodeObject->addAttribute('image', '/images/lviv-opera.jpg');

echo $node->asXML();

Спочатку за допомогою XML-рядка ми створили об’єкт SimpleXMLElement і встановили значення атрибута time в кореневому елементі. Потім за допомогою методів addChild та addAttribute додали елементи й атрибути в потрібному нам місці ієрархічної структури документа. І, нарешті, за допомогою методу asXML здійснили конвертацію об’єкта SimpleXMLElement у рядок та вивели його на перегляд.

<?xml version="1.0" encoding="UTF-8"?>
<document time="1566411359">
    <country title="Україна" image="/images/ua.jpg">
        <city title="Львів" image="/images/lviv.jpg">
            <object title="Львівська Опера" image="/images/lviv-opera.jpg">Опис ...</object>
        </city>
    </country>
</document>

Тут все настільки просто, що я навіть не знаю, що ще можна додати. Зрідка трапляється, коли обмеженого функціонала SimpleXMLElement бракує. Тоді по допомогу треба звертатися до надзвичайно просунутого й, відповідно, набагато складнішого розширення DOM (Document Object Model). Також варто звернути увагу на те, що для роботи з XML треба під’єднати в PHP розширення php_xml.

Нарешті, як приклад я створив маленький скрипт на PHP, який надасть сторінці динаміки.

// Отримаємо обраний розділ сайту
define('SECTION', ($_GET['section']) ?? '');

// Імітація отриманих даних з БД
$categories = [
    ['id' => '1', 'title' => 'Політика',    'description' => 'Політика опис ...'],
    ['id' => '2', 'title' => 'Економіка',   'description' => 'Економіка опис ...'],
    ['id' => '3', 'title' => 'Соціум',      'description' => 'Соціум опис ...'],
    ['id' => '4', 'title' => 'Культура',    'description' => 'Культура опис ...'],
    ['id' => '5', 'title' => 'Спорт',       'description' => 'Спорт опис ...']
];

// Відкриваємо XML-файл
$xml = file_get_contents('index.xml');
$node = new SimpleXMLElement($xml);

// Додаємо початкові дані до XML-файлу
$node->addAttribute('host', $_SERVER['HTTP_HOST']);
$node->addAttribute('request', $_SERVER['REQUEST_URI']);

try {

    // Оновлюємо, при потребі, дані поточної сторінки
    $node->addAttribute('section', SECTION);
    foreach($node->menu->item as $item) {
        if ($item['section'] == SECTION) {
            $node['title'] = $item['title'];
            $node['description'] = $item['description'];
        }
    }

    // Імпровізований міні-контролер
    switch(SECTION) {
        case '':
        case 'home': {
            $nodeHome = $node->main->addChild('home');
            // ...
        }; break;
        case 'news': {
            $nodeNews = $node->main->addChild('news');
            // ...
        }; break;
        case 'category':{
            $nodeCategory = $node->main->addChild('category');
            $nodeItems = $nodeCategory->addChild('items');
            foreach($categories as $category) {
                $xmlItem = $nodeItems->addChild('item');
                $xmlItem->addAttribute('id', $category['id']);
                $xmlItem->addAttribute('title', $category['title']);
                $xmlItem->addAttribute('description', $category['description']);
            }
        }; break;
        case 'tag': {
            $nodeTag = $node->main->addChild('tag');
            // ...
        }; break;
        case 'user': {
            $nodeUser = $node->main->addChild('user');
            // ...
        }; break;
        default: {
            $message = sprintf('Невідомий розділ сайту "%s"', SECTION);
            throw new Exception($message);
        }
    }
} catch (Exception $exception) {
    header('HTTP/1.x 404 Not Found');
    throw $exception;
}

header("Content-type: text/xml; charset=utf-8'");
echo $node->asXML();

А зараз, виконавши скрипт, отримаємо модифікований XML-файл, при переході пунктами меню якого зміст сторінки відповідно змінюватиметься.

Приклад роботи XSLT разом з PHP

Виконувати перетворення XSLT з боку клієнта для розробника дуже зручно: це частково зменшує навантаження на сервер. А для веб-оглядача клієнта ця трансформація не надто обтяжлива й відбувається абсолютно непомітно. Але водночас цей спосіб має один типовий недолік: невідомо, як саме різні веб-оглядачі здійснюватимуть це перетворення.

Хоча XSLT — це єдиний, добре описаний і відкритий стандарт, але, як це часто трапляється, кожен тлумачить його на власний розсуд. До речі, так само, як і HTML та багато інших відкритих стандартів. Якщо поширені веб-оглядачі більш-менш добре впораються із цим завданням, то як відбуватиметься цей процес у менш поширених веб-оглядачах, передбачити неможливо. І якщо ви не маєте бажання ризикувати, то доведеться виконати це перетворення на стороні сервера за допомогою, наприклад, того ж PHP.

Здійснити XSLT-перетворення PHP ми зможемо за допомогою об’єкта XSLTProcessor, який приймає XML як об’єкт DOMDocument. Тому перед XSLT-перетворенням треба здійснити конвертацію із SimpleXML у DOMDocument (SimpleXML -> DOMDocument -> XSLTProcessor). Як наслідок, доведеться замінити два останні рядки в нашому останньому прикладі на такий код:

$xmlDOM = new DOMDocument('1.0', 'UTF-8');
$xmlDOM->loadXML($node->asXML());

$xsl = new DOMDocument('1.0', 'UTF-8');
$xsl->load('index.xsl', LIBXML_NOCDATA);

$xslt = new XSLTProcessor();
$xslt->importStylesheet($xsl);

header('Content-Type: text/html; charset=utf-8');
echo $xslt->transformToXML($xmlDOM);

Сам процес трансформації має дещо заплутаний вигляд, але його достатньо раз прописати в кінці скриптів, і ви до нього не повертатиметеся. А для роботи з XML треба використовувати суперпростий SimpleXMLElement.

До речі, робота з XSLT у середовищі PHP реалізується засобами скомпільованої системної бібліотеки libxslt. А це, зокрема, дуже позитивно позначається на швидкодії, як порівняти з іншими шаблонизаторами, які здійснюють таке перетворення за допомогою власних скриптів мовою PHP.

У цій статті я намагався якомога більше спростити опис і приклади, щоб знизити поріг входження в цю дивовижну технологію. Маю надію, що мені це вдалося та згодом кількість адептів XSLT поступово, але невпинно збільшуватиметься. Так само, як і кількість сайтів, генерацію HTML-сторінок у яких зреалізували за допомогою цієї мови.

Похожие статьи:
Коли у лютому 2022 року до військкоматів вишикувались черги добровольців, одним з них став наш співрозмовник. Андрій (імʼя змінено)...
Привіт, мої любі сішники! Цього разу продовжимо підвищувати нашу кваліфікацію з Qt, розглянемо фреймворки та бібліотеки для Unit...
Привіт, мене звати Антон Маслов, я VP of Operations у продуктовій компанії iDeals Solutions. У ній забезпечую і вдосконалюю, зокрема, процес...
Німецький виробник програмного забезпечення для бізнесу SAP запустив спеціальний портал вакансій для людей, які вимушено...
[DOU Hobby — рубрика о нетехнических проектах IT-специалистов: творчество, интересное хобби и другие lifestyle-достижения. Если...
Яндекс.Метрика