Про xmlns. Часть вторая

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

16 июня 2011


Задача.

Рассказать о неймспейсах в XSLT.

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

 <company xmlns:tn="http://snartneft.ru/">
  <otsv>
    <tn:stolen value="$4 Billion" />
  </otsv>
</company>

Здесь объявлен неймспейс с URI, равным http://snartneft.ru/. Элемент <tn:stolen> находится в этом неймспейсе, и, положим, нам надо достать у него значение атрибута value. Тогда наш XSL должен выглядеть так:

 <xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:tn="http://snartneft.ru/"
>
  
  <xsl:template match="/">
    <p>
      <xsl:value-of select="company/otsv/tn:stolen/@value" />
    </p>
  </xsl:template>
  
</xsl:stylesheet>

И на выходе мы получим следующее:

 <p xmlns:tn="http://snartneft.ru/">$4 Billion</p>

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

 <xsl:template match="/">
  <p>
    <tn:span>
      <xsl:value-of select="company/otsv/tn:stolen/@value" />
    </tn:span>
  </p>
</xsl:template>

Но мы-то вовсе не хотим этого делать. Нам всего лишь нужно обратиться к элементу <tn:stolen> входящего XML, поэтому мы и сделали объявление xmlns:tn="http://snartneft.ru/".

Чтобы побороть эту маленькую проблемку, нужно использовать атрибут exclude-result-prefixes у элемента <xsl:stylesheet>:

 <xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:tn="http://snartneft.ru/"
  exclude-result-prefixes="tn"
>
  
  <xsl:template match="/">
    <p>
      <xsl:value-of select="company/otsv/tn:stolen/@value" />
    </p>
  </xsl:template>
  
</xsl:stylesheet>

Все, теперь на выходе получаем чистый HTML:

 <p>$4 Billion</p>

А вот другая ситуация, когда в XSL могут потребоваться неймспейсы с префиксом: мы хотим использовать функции какого-то XSL-расширения (например, EXSLT), и для этого нам нужно объявить его неймспейс. Рассмотрим пример генерации случайного числа:

 <xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:math="http://exslt.org/math"
>
  
  <xsl:template match="/">
    <span>
      <xsl:value-of select="math:random()" />
    </span>
  </xsl:template>
  
</xsl:stylesheet>

Здесь мы использовали функцию random() из модуля расширения http://exslt.org/math, для чего потребовалось объявить этот неймспейс с префиксом math. В результате получаем:

 <span xmlns:math="http://exslt.org/math">0.5867770494440748</span>

Наверное, вы уже признали себя прокаженным — треклятый xmlns неотступно следует за нами. Побороть его можно уже описанным способом, с помощью exclude-result-prefixes. Однако XSL-спецификация предусматривает для таких случаев другой атрибут — extension-element-prefixes, который как раз предназначен для ликвидации префиксов расширений:

 <xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:math="http://exslt.org/math"
  extension-element-prefixes="math"
>
  
  <xsl:template match="/">
    <span>
      <xsl:value-of select="math:random()" />
    </span>
  </xsl:template>
  
</xsl:stylesheet>

Снова наша взяла — кристальной чистоты результат:

 <span>0.16965380698437693</span>

Если нужно убрать не один, а несколько префиксов, то их необходимо разделять пробелом:

 extension-element-prefixes="math strings common"

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

 <!-- utils.xsl -->
<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  
  <xsl:variable name="UTILS_ALPHABET" select="'abcdefghijklmnopqrstuvwxyz'" />
  
  ...
  
</xsl:stylesheet>

Константа $UTILS_ALPHABET нужна только этому шаблону utils.xsl, однако несмотря на свой префикс она засоряет глобальную область видимости. Это можно исправить, заменив префикс на неймспейс:

 <xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:utils="http://localhost/xsl/utils"
  exclude-result-prefixes="utils"
>
  
  <xsl:variable name="utils:ALPHABET" select="'abcdefghijklmnopqrstuvwxyz'" />
  
  <xsl:template match="/">
    <xsl:value-of select="$utils:ALPHABET" />
  </xsl:template>
  
</xsl:stylesheet>

Мы объявили свой неймспейс с URI, равным http://localhost/xsl/utils, и префиксом utils. Здесь можно использовать любой URI, лишь бы он был уникальным, но я использовал такой намеренно. Напомню, что, по задумке W3C, URI неймспейса — это адрес страницы в интернете, которая рассказывает об этом неймспейсе и о его назначении. Так как здесь неймспейсы служат весьма частной цели (объявлению приватной переменной), то, конечно, никто никакую информационную страничку делать не будет. Исходя из этих идеологических (но не практических) соображений, рекомендуется использовать URI, начинающийся с http://localhost/. Такой URI всем своим видом говорит, что у этого неймспейса страницы в интернете нет и не будет.

Вернемся к нашему примеру. Мы дали префикс utils переменной, и это было основной целью. Теперь в любом шаблоне, который импортирует наш utils.xsl, переменная $utils:ALPHABET будет не видна.

Конечно, это не настоящая приватность. При большом желании в импортирующем шаблоне тоже можно объявить неймспейс с этим URI http://localhost/xsl/utils и добраться до нашей бедной константы:

 <xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:i_said_utils="http://localhost/xsl/utils"
  exclude-result-prefixes="i_said_utils"
>
  
  <xsl:import href="utils.xsl" />
  
  <xsl:template match="/">
    <xsl:value-of select="$i_said_utils:ALPHABET" />
  </xsl:template>
  
</xsl:stylesheet>

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

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

 <xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:utils="http://localhost/xsl/utils"
  exclude-result-prefixes="utils"
>
  
  <xsl:template match="/">
    <xsl:apply-templates select="*" mode="utils:hidden_template" />
    <xsl:call-template name="utils:i_am_invisible" />
  </xsl:template>
  
  <xsl:template match="*" mode="utils:hidden_template">
    ...
  </xsl:template>
  
  <xsl:template name="utils:i_am_invisible">
    ...
  </xsl:template>
  
</xsl:stylesheet>
1
2