Сущности против переменных

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

28 июля 2011


Задача.

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

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

 <!DOCTYPE xsl:stylesheet SYSTEM "entities.dtd"
  [
    <!ENTITY UPPER_CASE  "'ABCDEFGHIJKLMNOPQRSTUVWXYZАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'">
    <!ENTITY LOWER_CASE  "'abcdefghijklmnopqrstuvwxyzабвгдеёжзийклмнопрстуфхцчшщъыьэюя'">
  ]
>
  
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:template name="change_case">
    <xsl:param name="input_string" />
    <xsl:param name="direction" select="'low'" />
  
    <xsl:choose>
      <xsl:when test="$direction = 'low'">
        <xsl:value-of select="translate($input_string, &UPPER_CASE;, &LOWER_CASE;)" />
      </xsl:when>
      <xsl:when test="$direction = 'up'">
        <xsl:value-of select="translate($input_string, &LOWER_CASE;, &UPPER_CASE;)" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$input_string" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  
</xsl:stylesheet>

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


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

 <!DOCTYPE xsl:stylesheet SYSTEM "entities.dtd"
  [
    <!ENTITY product_type_one  "common">
    <!ENTITY product_type_two  "promo">
  ]
>
  
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:template match="/">
    <!--
      &content; – сущность для легкого проникновения в глубины входящего XML-я,
      которая определена в entities.dtd
      -->
    <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>

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

Важно, что в выражении:

match="product[@type = '&product_type_one;']"

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

<!ENTITY product_type_one  "common">

находится между кавычками, в нашем случае это common. Поэтому после подстановки (разворачивания) сущности матч будет выглядеть так:

match="product[@type = 'common']"

что нам и нужно, ведь мы проверяем атрибут type на равенство строке. Если одинарные кавычки убрать, то выражение будет иметь совсем другой смысл. Короче, при использовании сущностей за кавычками надо следить особенно зорко.

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

А можно ли в данном примере использовать переменные? Спешу огорчить: в XSLT 1.0 переменные внутри матчей использовать нельзя, будет ошибка компиляции шаблона. Однако в XSLT 2.0 такая возможность появилась, поэтому для трансформатора Saxon наш пример можно переписать так:

 <!DOCTYPE xsl:stylesheet SYSTEM "entities.dtd">
  
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:variable name="product_type_one" select="'common'" />
  <xsl:variable name="product_type_two" select="'promo'" />
  
  
  <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>

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

 <xsl:template match="div | p | ul | ..." mode="html">
  ...
</xsl:template>

А если в другом шаблоне нам нужно будет не заматчить, а отправить на обработку все блоковые элементы (которых не один десяток)? Опять их все писать, что ли? Нет, конечно. Делаем сущность:

 <!ENTITY Block  "div | p | ul | ...">

и используем ее везде, где нужны блоковые элементы:

 <xsl:template match="&Block;" mode="html">
  ...
</xsl:template>
  
<xsl:template match="/">
  <xsl:apply-templates select="&Block;" mode="html" />
</xsl:template>

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


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


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

1
2
3