В XSLT как увеличить глобальную переменную из другой области? - PullRequest
26 голосов
/ 07 мая 2009

Я обрабатываю файл XML, в котором я хочу вести подсчет количества узлов, чтобы использовать его в качестве идентификатора при записи новых узлов.

На данный момент у меня есть глобальная переменная с именем 'counter'. Я могу получить к нему доступ в шаблоне, но я не нашел способа манипулировать им в шаблоне.

Вот сокращенная версия моего XSLT-файла:

<xsl:variable name="counter" select="1" as="xs:integer"/>

<xsl:template match="/"> 
   <xsl:for-each select="section">
      <xsl:call-template name="section"></xsl:call-template>
   </xsl:for-each>
</xsl:template>

<xsl:template name="section">

   <!-- Increment 'counter' here -->

   <span class="title" id="title-{$counter}"><xsl:value-of select="title"/></span>
</xsl:template>

Любые предложения, как идти отсюда?

Ответы [ 8 ]

44 голосов
/ 13 мая 2009

Другие уже объясняли, как переменные являются неизменяемыми - что в XSLT нет операторов присваивания (как в случае с чисто функциональными языками программирования в целом).

У меня есть альтернатива решениям, которые были предложены до сих пор. Это позволяет избежать передачи параметров (что многословно и безобразно в XSLT - даже я это признаю).

В XPath вы можете просто посчитать количество <section> элементов, предшествующих текущему:

<xsl:template name="section">
  <span class="title" id="title-{1 + count(preceding-sibling::section)}">
    <xsl:value-of select="title"/>
  </span>
</xsl:template>

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

Одно большое преимущество этого подхода (в отличие от использования position()) заключается в том, что он зависит только от текущего узла, а не от текущего списка узлов. Если вы каким-то образом изменили свою обработку (например, <xsl:for-each> обработал не только разделы, но и какой-либо другой элемент), то значение position() больше не обязательно будет соответствовать позиции элементов <section> в вашем документе. С другой стороны, если вы используете count(), как указано выше, то оно всегда будет соответствовать положению каждого элемента <section>. Такой подход уменьшает связь с другими частями вашего кода, что, как правило, очень хорошая вещь.

Альтернативой count () было бы использование инструкции <xsl:number>. Поведение по умолчанию нумерует все элементы с одинаковыми именами на одном уровне, что, как вам и нужно

<xsl:template name="section">
  <xsl:variable name="count">
    <xsl:number/>
  </xsl:variable>
  <span class="title" id="title-{$count}">
    <xsl:value-of select="title"/>
  </span>
</xsl:template>

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

Еще больше возможностей для улучшения. Хотя мы удалили зависимость от текущего списка узлов, мы все еще зависимы от текущего узла. Это само по себе неплохо, но не сразу видно из шаблона, каким является текущий узел. Все, что мы знаем, это то, что шаблон называется "section"; Чтобы точно знать, что именно обрабатывается, мы должны посмотреть в другом месте нашего кода. Но даже это не должно быть так.

Если вы когда-нибудь почувствовали необходимость использовать <xsl:for-each> и <xsl:call-template> вместе (как в вашем примере), отступите назад и выясните, как использовать <xsl:apply-templates> вместо этого.

<xsl:template match="/doc">
  <xsl:apply-templates select="section"/>
</xsl:template>

<xsl:template match="section">
  <xsl:variable name="count">
    <xsl:number/>
  </xsl:variable>
  <span class="title" id="title-{$count}">
    <xsl:value-of select="title"/>
  </span>
</xsl:template>

Этот подход не только менее многословен (<xsl:apply-templates/> заменяет <xsl:for-each> и <xsl:call-template/>), но также сразу становится ясно, что является текущим узлом. Все, что вам нужно сделать, это взглянуть на атрибут match, и вы сразу узнаете, что обрабатываете элемент <section>, а элементы <section> - это то, что вы считаете.

Краткое объяснение того, как работают правила шаблонов (т. Е. <xsl:template> элементы с атрибутом match), см. «Как работает XSLT» .

8 голосов
/ 07 мая 2009

Переменные XSLT не могут быть изменены. Вам нужно будет передать значение из шаблона в шаблон.

Если вы используете XSLT 2.0, вы можете иметь параметры и использовать туннелирование для распространения переменной на нужные шаблоны.

Ваш шаблон будет выглядеть примерно так:

<xsl:template match="a">
<xsl:param name="count" select="0">
  <xsl:apply-templates>
     <xsl:with-param select="$count+1"/>
  </xsl:apply-templates>
</xsl:template>

Также обратите внимание на использование generate-id (), если вы хотите создать идентификаторы.

6 голосов
/ 07 мая 2009

Переменные в XSLT являются неизменяемыми, поэтому вы должны решить проблему с учетом этого. Вы можете использовать position() напрямую:

<xsl:template match="/"> 
   <xsl:for-each select="section">
      <xsl:call-template name="section"/>
   </xsl:for-each>
</xsl:template>

<xsl:template name="section">
   <span class="title" id="title-{position()}"><xsl:value-of select="title"/></span>
</xsl:template>

Или в более ориентированном на шаблон шаблоне:

<xsl:template match="/"> 
   <xsl:apply-templates select="section"/>
</xsl:template>

<xsl:template match="section">
   <span class="title" id="title-{position()}"><xsl:value-of select="title"/></span>
</xsl:template>
2 голосов
/ 07 мая 2009

В зависимости от вашего процессора XSLT, вы можете использовать скриптовые функции в вашем XLST. Например, библиотека Microsoft XML поддерживает включение javascript. См. http://msdn.microsoft.com/en-us/library/aa970889(VS.85).aspx для примера. Эта тактика, очевидно, не сработает, если вы планируете развернуть / выполнить XSLT в общедоступных клиентских браузерах; это должно быть сделано определенным процессором XSLT.

2 голосов
/ 07 мая 2009

переменные имеют локальную область видимости и доступны только для чтения в xslt.

1 голос
/ 07 мая 2009

Вы можете использовать функцию position (), чтобы делать то, что вы хотите. Это будет выглядеть примерно так.

<xsl:template match="/">
  <xsl:for-each select="section">
    <xsl:call-template name="section">
      <xsl:with-param name="counter" select="{position()}"/>
    </xsl:call-template>
  </xsl:for-each>
</xsl:template>

<xsl:template name="section">
  <xsl:param name="counter"/>
  <span class="title" id="title-{$counter}">
    <xsl:value-of select="title"/>
  </span>
</xsl:template>
0 голосов
/ 22 февраля 2013

Использование <xsl:variable name="RowNum" select="count(./preceding-sibling::*)" /> и $ RowNum в качестве возрастающего значения.

Например: <xsl:template name="ME-homeTiles" match="Row[@Style='ME-homeTiles']" mode="itemstyle"> <xsl:variable name="RowNum" select="count(./preceding-sibling::*)" /> ...<a href="{$SafeLinkUrl}" class="tile{$RowNum}"><img ....></a>

Это создаст классы для связи со значениями tile1, tile2, tile3 и т. Д. *

0 голосов
/ 07 мая 2009

Сам не пробовал, но вы можете попробовать передать параметр в шаблон. В вашем первом шаблоне вы устанавливаете параметр count () (или может быть current ()?) В операторе for-each, а затем передаете это значение в шаблон «section».

Подробнее о передаче параметров в шаблоны

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...