Виды сущностей

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

2 августа 2011


Задача.

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

Сущности бывают разными. По своему назначению они делятся на два вида: обычные (general entity) и параметрические (parameter entity).


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

Чтобы воспользоваться какой-либо сущностью, ее сначала надо определить. Синтаксис определения обычной сущности:

<!ENTITY имя сущности  "значение">

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

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

&имя сущности;

Интересным свойством сущностей является то, что они могут участвовать в значении других сущностей:

 <!DOCTYPE xsl:stylesheet
  [
    <!ENTITY block   "div | p">
    <!ENTITY inline  "span | ins">
    <!ENTITY all     "&block; | &inline;">
  ]
>

Однако тут важно не увлекаться и следить за тем, чтобы сущности не ссылались друг на друга (чтобы не было циклических зависимостей). Хотя в этом случае трансформатор, конечно, сообщит, что обнаружил цикл и попросит прекратить хулиганить.


Теперь давайте вспомним, как определяется сущность неразрывного пробела:

 <!ENTITY nbsp  "&#160;">

Значением этой сущности с именем nbsp является &#160;, что тоже очень похоже на обычную сущность. Возникает вопрос: если XML-процессор не знает, что такое nbsp, то откуда ему знать про какой-то #160? На самом деле &#160; — это не сущность, а ссылка на символ с кодом 160, и код этот XML-процессор, понятное дело, должен знать, как «Отче наш».

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

  1. &lt; меньше
  2. &gt; больше
  3. &amp; амперсанд
  4. &apos; одинарная кавычка
  5. &quot; двойная кавычка

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


Указанный способ определения обычных сущностей:

<!ENTITY имя сущности  "значение">

не единственный. Дело в том, что так определяются внутренние (internal) обычные сущности. А бывают еще и внешние (external) обычные сущности, которые ссылаются на внешний файл. Они пригождаются в ситуации, когда в сущность хочется запихнуть не просто текст, а целый кусок XML-я. Определяются они так:

<!ENTITY имя сущности  SYSTEM "путь до файла">

Во внешнем файле может быть любая разметка. Рассмотрим это на примере отдельного файла с копирайтом.

XSL-шаблон:

 <!DOCTYPE xsl:stylesheet
  [
    <!ENTITY copyright SYSTEM "copyright.xml">
  ]
>
  
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:template match="/">
    <p class="copyright">
      &copyright;
    </p>
  </xsl:template>
  
</xsl:stylesheet>

copyright.xml:

 © 1995–2011 <span>Студия Артемия Лебедева</span>

На выходе получим:

 <p class="copyright">
  © 1995–2011 <span>Студия Артемия Лебедева</span>
</p>

К сожалению, в повседневной работе этот вид сущностей малополезен. Даже не знаю, кому могут понадобиться такие извращения, ведь мы, имея XSL, подобные задачи (когда хочется несколько раз использовать одну и ту же разметку) решаем выделением простого именованного шаблона. Короче, здесь внешние обычные сущности я упомянул лишь для полноты изложения.

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

Еще раз, чтобы не было каши в голове. Сущности можно подключать к XML-документу тремя способами: внутренним (внутри XML-документа), внешним (отдельным файлом) и комбинированным. Сами сущности тоже могут быть внешними и внутренними — внутренние хранят простой текст, а внешние ссылаются на отдельный файл, в котором может быть любая сложная разметка.


Переходим к параметрическим сущностям. Это такие сущности, которые, кроме простого текста или разметки, могут еще содержать код других сущностей. Если точнее, то они могут содержать код DTD (напомню, что сущности являются частью языка DTD). Параметрические сущности тоже бывают внешними и внутренними.

Начнем с внешних, так как они представляют наибольший интерес. Определяются внешние параметрические сущности так:

<!ENTITY % имя сущности  SYSTEM "путь до файла">

Обращаем внимание на знак процента перед именем сущности — он как раз и отличает обычные сущности от параметрических. В файле, на который ссылается параметрическая сущность, могут храниться определения других сущностей:

<!ENTITY % ent  SYSTEM "entities.dtd">

Дальше мы можем подключить к XML-документу сущности, хранящиеся во внешнем файле (на который ссылается параметрическая сущность):

 <!DOCTYPE xsl:stylesheet
  [
    <!ENTITY % ent  SYSTEM "entities.dtd">
    %ent;
  ]
>

%ent; — это использование определенной ранее параметрической сущности, и это все равно, что написать содержимое файла entities.dtd в том же самом месте и подключить таким образом сущности из этого файла. Итак, формат использования параметрической сущности:

%имя сущности;

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

А зачем нужны параметрические сущности, если мы можем подключать внешний файл с сущностями обычным способом? Да, последний пример действительно эквивалентен внешнему подключению, которое мы уже проделывали:

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

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

 <!DOCTYPE xsl:stylesheet
  [
    <!ENTITY % ent_global   SYSTEM "entities_global.dtd">
    %ent_global;
  
    <!ENTITY % ent_package  SYSTEM "entities_package.dtd">
    %ent_package;
  ]
>

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

Кто в такой ситуации может удовлетворить нашу страсть к порядку? Конечно, параметрические внешние сущности. Делаем файлик entities_product_card.dtd, кладем в него все обычные сущности карточки товара и подключаем его (наряду с глобальным entities.dtd) с помощью параметрической сущности.

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

XSL:

 <!DOCTYPE xsl:stylesheet
  [
    <!ENTITY % ent               SYSTEM "entities.dtd">
    %ent;
  
    <!ENTITY % ent_product_card  SYSTEM "entities_product_card.dtd">
    %ent_product_card;
  ]
>
  
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:template match="/">
    <xsl:apply-templates select="&content;/products/product" mode="product" />
  </xsl:template>
  
  <xsl:template match="product[@type = '&product_type_one;']" mode="product">
    <!-- Первый вид верстки -->
  </xsl:template>
  
  <xsl:template match="product[@type = '&product_type_two;']" mode="product">
    <!-- Второй вид верстки -->
  </xsl:template>
  
  ...
  
</xsl:stylesheet>

entities_product_card.dtd:

 <!ENTITY product_type_one  "common">
<!ENTITY product_type_two  "promo">

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

 <!DOCTYPE xsl:stylesheet SYSTEM "entities.dtd"
  [
    <!ENTITY % ent_product_card  SYSTEM "entities_product_card.dtd">
    %ent_product_card;
  ]
>

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

 <!DOCTYPE xsl:stylesheet SYSTEM "entities.dtd"
  [
    <!ENTITY % ent_product_card  SYSTEM "entities_product_card.dtd">
    %ent_product_card;
  
    <!ENTITY very_local_suchnost  "4 8 15 16 23 42">
  ]
>

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

Раз уж мы заговорили об иерархии, хотелось бы вставить пять копеек по поводу перекрытия сущностей. Сущность перекрыть нельзя. То есть значение сущности берется из ее первого найденного определения. Рассмотрим пример.

XSL:

 <!DOCTYPE xsl:stylesheet
  [
    <!ENTITY % global   SYSTEM "ent_global.dtd">
    %global;
  
    <!ENTITY % package  SYSTEM "ent_package.dtd">
    %package;
  
    <!ENTITY suchnost  "local">
  ]
>
  
  
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:template match="/">
    &suchnost;
  </xsl:template>
  
</xsl:stylesheet>

ent_global.dtd:

 <!ENTITY suchnost  "global">

ent_package.dtd:

 <!ENTITY suchnost  "package">

XSL-код выдаст:

global

То есть было взято значение первой найденной сущности &suchnost; в файле ent_global.dtd. Но сущности коварны, поэтому тут есть подвох. Если использовать комбинированное подключение:

 <!DOCTYPE xsl:stylesheet SYSTEM "ent_global.dtd"
  [
    <!ENTITY % package  SYSTEM "ent_package.dtd">
    %package;
  
    <!ENTITY suchnost  "local">
  ]
>

то мы получим другой результат:

package

Это говорит о том, что сначала обрабатываются сущности с внутренним подключением, а потом уже с внешним.


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

 <!DOCTYPE xsl:stylesheet
  [
    <!ENTITY % code  "
      <!ENTITY suchnost  'Дизайн спасет мир'>
    ">
    %code;
  ]
>
  
<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  
  <xsl:template match="/">
    &suchnost;
  </xsl:template>
  
</xsl:stylesheet>

Результат:

Дизайн спасет мир

Жуть. Конечно, нормальный человек так писать не будет, а сразу определит обычную сущность без всяких параметрических. Если же говорить о реальном применении, то внутренние параметрические сущности широко используются в коде HTML-доктайпов (они написаны как раз на языке DTD). Вот, скажем, параметрическая сущность %coreattrs;, значением которой является некий код DTD:

 <!ENTITY % coreattrs
  "id       ID              #IMPLIED
   class    CDATA           #IMPLIED
   style    %StyleSheet;    #IMPLIED
   title    %Text;          #IMPLIED"
>

Этот код говорит нам, что основными атрибутами HTML являются id, class, style и title, что типы их значений такие-то и что присутствие этих атрибутов необязательно. Судя по всему, сущность эту сделали затем, чтобы дальше в коде доктайпа много раз использовать ее для определения тех атрибутов, которые может иметь отдельно взятый HTML-элемент.

Но мы доктайпы писать не собираемся. Просто стоит знать, что есть такой «зверь» — внутренние параметрические сущности. Вдруг завтра читателю позвонят и предложат работать в W3C. И если это произойдет, он, несомненно, должен знать формат таких сущностей:

<!ENTITY % имя сущности  "значение">

Резюмируем по сущностям в целом. Они бывают обычными и параметрическими, внешними и внутренними. Наиболее полезны с точки зрения XSL обычные внутренние и параметрические внешние. К XML-документу сущности можно подключать тремя способами: внешним, внутренним и комбинированным. Если нужно сохранить простой текст для многократного использования в XSL-коде, сущности выглядят привлекательнее переменных.

1
2
3