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

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