• Техногрет
  • Сущности и способы их подключения

    HTML и CSSXSLTJavaScriptИзображенияСофтEtc
    Александр Самиляк

    26 июля 2011


    Задача.

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

    В студийных XSL-ях мы нередко используем сущности (они же entity или «энтити»). Куда же без них, ведь мы стараемся не забывать про типографику — то неразрывный пробел   надо поставить, то длинное тире —. Но не только типографикой ограничивается польза сущностей. Об этой пользе и пойдет речь в данной статье.


    Перво-наперво разберемся в том, как подключать сущности к XML-документу. Вариантов подключения (никому не падать в обморок) три: внешнее, внутреннее и комбинированное.

    Начнем с более привычного подключения — внешнего. Оно имеет вид:

    <!DOCTYPE имя корневого XML-элемента SYSTEM "путь до файла">

    Такая запись часто присутствует в начале XSL-файлов:

     <!DOCTYPE xsl:stylesheet SYSTEM "entities.dtd">
    

    Здесь xsl:stylesheet — это имя корневого элемента любого XSL-документа, а entities.dtd — путь до файла, содержащего определения всех используемых сущностей. Кстати, путь до файла может быть не только локальным адресом, но и адресом в интернете:

     <!DOCTYPE xsl:stylesheet
      SYSTEM "http://www.artlebedev.ru/tools/technogrette/xslt/entity-1/entities.dtd"
    >
    

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

    Надо сказать, что все эти правила подключения касаются не столько сущностей, сколько языка DTD (Document Type Definition), особого языка, с помощью которого можно описывать структуру XML-документов. Сущности являются лишь малой частью этого языка, однако из всего DTD широкое применение в XSL нашли именно сущности, поэтому мы фокусируем наше внимание исключительно на них.

    Хозяйке на заметку

    DTD с внешним подключением делятся на два подвида: приватные, private, о которых знает узкий круг лиц (упомянутый entities.dtd), и публичные, public, о которых знает весь земной шар (HTML-доктайп).

    Указанный синтаксис относится только к приватным DTD.

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

     <!DOCTYPE xsl:stylesheet SYSTEM "entities.dtd">
      
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      
      <xsl:template match="/">
        Трансформеры без Меган Фокс&nbsp;&mdash; незачет.
      </xsl:template>
      
    </xsl:stylesheet>
    

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

    Обычно в этом entities.dtd объявляется множество разных сущностей, которые используются по всему проекту. Определение, скажем, неразрывного пробела выглядит так:

     <!ENTITY nbsp  "&#160;">
    

    Второй способ подключения сущностей — подключение внутреннее. Делается это так:

    <!DOCTYPE имя корневого XML-элемента
      [
        определение сущностей
      ]
    >
    

    Перепишем последний пример, использовав внутреннее подключение:

    <!DOCTYPE xsl:stylesheet
      [
        <!ENTITY nbsp   "&#160;">
        <!ENTITY mdash  "&#8212;">
      ]
    >
     
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      
      <xsl:template match="/">
        Трансформеры без Меган Фокс&nbsp;&mdash; незачет.
      </xsl:template>
      
    </xsl:stylesheet>
    

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


    И наконец, последний, самый интересный, способ — комбинированный. Он заключается в том, что мы можем одновременно как подключить сущности внешним файлом, так и определить новые прямо в XSL-шаблоне. Формат комбинированного подключения таков:

    <!DOCTYPE имя корневого XML-элемента SYSTEM "путь до файла"
      [
        определение сущностей
      ]
    >
    

    Таким подключением удобно пользоваться, когда нам нужны сущности из глобального entities.dtd и при этом хочется определить какие-то константы, необходимые только в текущем XSL-файле (например, CSS-классы, используемые в нескольких местах).

    Рассмотрим такую ситуацию на примере корзины в магазине. Это должна быть HTML-таблица, имеющая строку заголовков и несколько строк — товаров, положенных в корзину:

    <!DOCTYPE xsl:stylesheet SYSTEM "entities.dtd"
      [
        <!ENTITY CLASS_TITLE  "title">
        <!ENTITY CLASS_COST   "cost">
      ]
    >
      
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      
      <xsl:template match="/">
        <table class="cart">
          <xsl:call-template name="cart_head" />
          <xsl:call-template name="cart_content" />
        </table>
      </xsl:template>
      
      <xsl:template name="cart_head">
        <tr>
          <th class="&CLASS_TITLE;">Название</th>
          <th class="&CLASS_COST;">Стоимость</th>
        </tr>
      </xsl:template>
      
      <xsl:template name="cart_content">
        <xsl:for-each select="&content;/cart/product">
          <tr>
            <td class="&CLASS_TITLE;">
              <xsl:apply-templates select="." mode="cart_content_product_title" />
            </td>
            <td class="&CLASS_COST;">
              <xsl:apply-templates select="." mode="cart_content_product_cost" />
            </td>
          </tr>
        </xsl:for-each>
      </xsl:template>
      
      ...
      
    </xsl:stylesheet>
    

    В этой таблице есть два столбца — название товара и его стоимость. CSS-классы для этих столбцов фигурируют у нас в двух местах, в заголовке таблицы и в теле, поэтому я вынес эти классы в отдельные внутренние сущности &CLASS_TITLE; и &CLASS_COST;. Но одновременно с этим я воспользовался сущностью &content;, определенной в entities.dtd. В этой сущности хранится XPath до элемента <content> входящего XML-я, который содержит данные о текущей странице, в том числе список продуктов, положенных в корзину.

    А теперь самое «вкусное» место — сущности с внутренним подключением не видны нигде, кроме текущего XSL-файла. То есть если мы написали шаблон, определив внутри него нужные нам сущности, и наш шаблон кто-то импортировал, то наши сущности не будут мешаться ему под ногами. Более того, импортирующий шаблон внутри себя может определить сущность с таким же именем, как и у нас. При этом никакого конфликта имен не возникнет, каждый будет пользоваться своей сущностью, определенной в его XSL-файле.

    Главный шаблон main.xsl:

    <!DOCTYPE xsl:stylesheet
      [
        <!ENTITY suchnost  "main">
      ]
    >
      
    <xsl:stylesheet
      version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    >
      
      <xsl:import href="import.xsl" />
      
      <xsl:template match="/">
        Suchnost in main.xsl: &suchnost;
        <xsl:call-template name="import" />
      </xsl:template>
      
    </xsl:stylesheet>
    

    Импортируемый шаблон import.xsl:

    <!DOCTYPE xsl:stylesheet
      [
        <!ENTITY suchnost  "import">
      ]
    >
      
    <xsl:stylesheet
      version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    >
      
      <xsl:template name="import">
        Suchnost in import.xsl: &suchnost;
      </xsl:template>
      
    </xsl:stylesheet>
    

    Выполнение главного шаблона выдаст результат:

    Suchnost in main.xsl: main
    Suchnost in import.xsl: import
    

    Такое поведение свойственно не только внутреннему подключению сущностей, но и внешнему тоже. Поэтому общее правило: сущности видны только в том XSL-файле, к которому они подключены, и ни в каком другом. Оно и понятно, ведь «сущность» — это термин, относящийся не только к XSL, а в целом к XML, который ничего не знает ни про какие импортирования шаблонов. Просто есть отдельно взятый XML-документ (в нашем случае это XSL-шаблон), к которому подключается DTD.

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


    В следующей части нас ждет небольшое сравнение сущностей с XSL-переменными.

    1
    2
    3