×Закрыть

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-сторінок у яких зреалізували за допомогою цієї мови.

LinkedIn

71 комментарий

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

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

Всё оно красиво, когда маленькое. А когда растёт... это тупо паутина, по сравнению с которой спагетти-код выглядит искусством лаконичности.

Таки говорят что в версии 2.0 какие-то регулярки есть

Тут запитання більше, де автор знайшов

и в этом маразме напичкано овердохера регулярных выражений

?

Кстати я пример принес трансофрмации в браузере: https://www.moex.com/export/derivatives/currency-rate.aspx?language=ru&currency=CAD/RUB&moment_start=2019-08-03&moment_end=2019-09-03

Правда эти данные и так отдаются на другой странице в хтмл, а если файл сохранить на пк и открыть потом в браузере — будет белая страница из-за параноидальной безопасности

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

все годные и сложные сайты используют серверный рендеринг и XSLT

Ну не нужно же выдавать желаемое за действительное. И эти все крутые проекты не размещают вакансий?
dou.ua/...​for-senior-php-developer
jobs.dou.ua/...​y=PHP&search=xslt&descr=1

Я против xslt ничего не имею, вроде годная технология, но мейнстримом в связке с php за более чем 15 лет она так и не стала. А то что где-то в редких случаях её используют, спорить не буду.

Вау, как тонко

В далеком 2006, что ли, работал с похожей схемой. Сервер отдавал XML+XSLT, а браузер клиента уже сам себе рендерил это все, чем очень разгружал сервер. Но это было еще до ангуляра, даже до jQery, даже до гуглхрома, вот. В те мезозойские времена, когда для резиновости кнопки со скругленными уголками рисовали 9 вложенных дивов.

XSLT сложен, поскольку является языком трансформации, циклы и особенно рекурсия в нем мучительны, отладка — тоже. Зато он отлично тренирует мозг XPath, что мне лично потом пригодилось в CSS и прочем древовидном.

А як рендеринг на стороні клієнта дуже нагружав сервер?

Не было тогда SPA никаких, приходил запрос на сервер: урл и параметры, ну обычно сервер и собирал при помощи РНР и SMARTY (у особо продвинутых — Twig) статичный HTML и весь скопом отдавал клиенту. Долго все это собирать, долго отдавать, проблемы с кешированием. Поэтому подход, когда сервер пропускал момент преобразования и отдавал только XML+XSLT (а стили и картинки уже, как правило, закешированы) — был весьма новаторским.

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

Да уж было времечко: хрома не было, AWS не было, зато был доллар по 4 и пенопласт из молочной пены..

А — я прочитав «навантажува», а не «розвантажував».

По сравнению со всеми остальными жопорукосделанными так называемыми шаблонизаторами на php, xslt — очень мощный универсальный стандартизированный инструмент. Очень жаль, что он не стал мэйнстримом.

Дуже вдячний за ваш влучний коментар. А то на мене всі накинулись ніби я наркотики рекламую ;(

На dou так бывает ... когда-то тоже написал статью. Жалею, что потом больше не писал. Нужно было продолжить.

З наркотиками — дуже слушна асоціація. Колись давно фірма Баєр винайшла дивні ліки. Вони мали сильний тонізуючий ефект, на рівні кокаїну і до того ж допомагали позбутись кокаїнової залежності, навіть на рівні нейрофізіології вони виглядали наче антагоніст. Їх так і назвали — героїн. Так от ваша стаття виглядає наче рекламна джинса фірми Баєр з 30х яку пустили в наклад минулого тижня :)

Не наркотики, конечно, просто на моей памяти тема php+xslt муссировалась в середине 2000-х, популярности не приобрела. Сам тоже пробовал начать использовать для кастомной cms, не очень понравилось, с тех пор для себя вопрос закрыл.

А кто какие шаблонизаторы вообще использует? Не обязательно для web. Главное — Вам нравится, понятный, предсказуемый, легко читать.
Или же, не просто шаблонизатор, а фреймворк или тул для трансформации одного сложного шаблона или структуры данных в другую, наподобие как это делают с помощью XSLT. Например — есть xml или json (не так важно какой формат у Вас) документ А и его нужно перегнать в другой формат Б или В и т.п.

воно ще живе?

XSLT особо хорош в случае, когда нужно отдать данные пользователю и хочется оставить возможность машинной обработки. Правда это сачала работало хорошо (много лет назад), а сейчас тот же хром не отрендерит xml нормально, ибо ’file:’ URLs are treated as unique security origins. А использовать эту технологию вместо шаблонизатора нет никакого смысла — другие шаблонизаторы проще, пхп сам по себе шаблонизатор, а для более-менее сложных веб-приложений вообще SPA лучше.

Даже не помню когда в последнее время использовал XSLT, дайте вспомнить, никогда, да я никогда его не использовал

Рендерить шаблоны на стороне php в 2019 это как минимум странно.

Абсолютно нормально, якщо на іншій стороні тупа гальмівна залізяка.

А для чого на php шаблонізатор, якщо php і є шаблонізатор? :)

Мабуть автор нещодавно дізнався про xml, бо відсутні власні XPath функції, модулі, xslt-fo, перевірка схемами і, звичайно, трансформації засобами браузера. А ще не згадані чудодійні можливості IE4 по біндінгу таблиць на дата-айленди, завдячуючи яким ціле покоління бухгалтерів мали нервовий зрив від фрази звіт по ПДВ.
Якщо серйозно, то у 2019 році на цю тему можна лише писати огляди, чому це не працює.

А на практике это где-то за последние лет 5 где-то использовали?
Мне встречалось в каких-то CMS лет 7 назад, но в 2019 существуют шаблонизаторы с которыми намного приятней работать.

А оно того стоит ?
Чем лучше, чем более современные шаблонизаторы современных php-фреймворков ?

Мета статті — ознайомлення. А варто, чи не варто вирішувати кожному особисто.

А де висновки до статті, що ніколи не використовуйте XSLT для чогось складного?

Тому що XSLT був створений для складного

Якщо б ви хоч раз його спробували використати для дійсно складного, то ви б так не раділи.

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

Артем, пока не поздно )

В 2019 формування відображення віддано JS фреймворкам. Але для маленького нішового проекта, чому б ні

Наскільки мені відомо все як раз навпаки. JS фреймворки використовуються для маленьких нішових проєктів, а на XSLT створюють великі проєкти з незалежно від ніш. Буду вдячний якщо ви наведете приклади відомих поширених типів сайтів (блоги, новинні, магазини, форуми, тощо) де замість шаблонизаторів використовуються JS фреймворки.

90% процентов новых проектов делается на ву, реакт и тп

Тоді тим паче немає бути проблем з прикладами

view-source:https://www.facebook.com/
view-source:https://www.youtube.com/

Це спеціалізовані сайти, в яких, звісно, доцільніше використовувати JS для виводу.
Але мене цікавлять типові сайти (блоги, сайти новин, інтернет-магазини, форуми, тощо), про що я писав вище.

leboutique.com магазин на React
cms-demo.netlify.com для новин
gohugo.io для блогів
senior.ua головна сторінка на JS

view-source:leboutique.com/...​zhka-450-ml-krauff-714166
— опис товару в тілі сторінки
cms-demo.netlify.com — цікавлять готові відомі реалізації
gohugo.io — аналогічно до попереднього
view-source:senior.ua/articles/finansovyy-plan-dlya-startapov-zachem-on-nuzhen-kak-sostavit-i-chego-izbegat
— бачу текст статті в тілі сторінки

view-source:leboutique.com/...​zhka-450-ml-krauff-714166
— опис товару в тілі сторінки

Вірно, бо render by JS як і в інших прикладах

Якщо при перегляді початкового коду сторінки текст/дані розміщені відразу поміж html-розмітки, значить сторінка згенерована шаблонизатором на стороні сервера.

частково так, для SEO, інші дані вже на JS

В сучасних js фреймворках є така штука, як SSR (сервер сайд рендерінґ).
Шаблон однаковий для фронтенду та бекенду і в обох випадках його рендерить js, просто на бекенді замість браузера Node.js.

А ще є альтернативний варіант: використовувати шаблонізатор, який має інтерпретацію декількома мовами. Наприклад, Mustache має реалізацію купою мов, тож на бекенді його може рендерити й php з тих самих шаблонів, з яких на фронтенді це робить js.

До речі, як реально/практично розв’язали проблему з індексацією сторінок пошуковими системами?

В лоб, сервер повертає title, description як і раніше для кожної сторінки свій

Все что на yii2, laravel из коробки использует vue, это актуальные фреймворки и проектов на них сотни тысяч

... та ім’я їм легіон ;)

Вы не согласны?)

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

Наприклад яндекс

Я вижу на yandex.ua обычный html. Или они тоже на сервере рендерят?

Так, взагалі не бачив сайтів де шаблони рендеряться на стороні клієнта.

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

XSLT це НЕ шаблонізатор, це трансформатор одного формату данних в інший (XML в інший XML). Просто часто його використовують для трансформації в HTML.

На сьогодні цей підхід напевно програє окремому сінгл пейдж аплікейшону на якомусь джаваскріпті фереймворку, і окремій апці на бекеді — яка по суті буде API для апки яка виконується на клієнті і буде отримувати потік данних в JSON.

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

Думаю єдина причина чому XSLT не «взлетів» і не пішов в маси — це його відносна складність. А ідея і принцип — дуже навіть ОК.

Он наверное действительно опередил своё время и оказался слишком сложным для большинства веб-проектов того периода.

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

ПС. Згадав назву тієї ліби — Sablotron

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

І про швидкість бібліотеки libxslt не забудьте згадати. А що таке проблеми з обслуговуванням я навіть не можу уявити.

Ви уже із групуванням мюнча стикалися? То-ж не від хорошого життя

Дуже цікаво, що це за штука така, а в інтернеті ні слова. Опишіть, будь ласка, в чому саме проблема з тим групуванням.

Ну вибачте але це ази xslt — це найменш моторошний спосіб зробити в xslt групування, але все-ж око сіпається www.parser.ru/forum/?id=6758

А для чого таке робити засобами XSLT? Конкретно в наведеному вами прикладі групування по даті, треба робити в БД при виборці даних (SELECT * FROM `news` ORDER BY `date` LIMIT 10). Тому що потрібно відсортувати всі дані, які є в таблиці, а не тільки ту частину, яка виведена шаблонизатором на сторінку.

Ще пару ітерацій і ви дойдете до думки «а навіщо тут xslt» ;)

Або ви переосмислите своє ставлення до нього ;)

Я його куштував 10 років тому, моє ставлення досить сформоване та закріплене ) але забудьте, ваше право робити як вважаєте за правильне

все должны через это пройти..

Еще написать свой PHP/Node.js фреймверк.

Подписаться на комментарии