Элемент <xsl:for-each>

Элемент <xsl:for-each>

Элемент <xsl:for-each> позволяет применять тело шаблона в цикле снова и снова для всех элементов набора узлов. С технической точки зрения, он работает с набором узлов, который возвращает выражение XPath и выполняет одно и то же действие с каждым узлом в наборе. При каждом шаге цикла тело шаблона применяется к следующему узлу из набора узлов, что дает возможность легко обрабатывать несколько узлов.

<XSL:FOR-EACH> ПРОТИВ <XSL:APPLY-TEMPLATES>

Вы могли заметить, что это описание практически такое же, как и у элемента <xsl:apply-templates>, и я сравню элементы <xsl:for-each> и <xsl:apply-templates> через несколько страниц.

У элемента <xsl:for-each> один атрибут:

• select (обязательный). Принимает значение выражения XPath, возвращающее набор узлов, который нужно обработать в цикле.

Элемент может содержать ноль или более элементов <xsl:sort>, за которыми следует тело шаблона. Работу с элементом <xsl:sort> мы изучим позже в этой главе.

В теле шаблона функция position возвращает позицию текущего узла в наборе узлов, a last возвращает число узлов в наборе. Если <xsl:sort> не используется, узлы обрабатываются в порядке документа (в порядке, в котором они перечислены в документе); если же используется элемент <xsl:sort>, набор узлов будет сначала отсортирован в порядке, заданном этим элементом.

Предположим, нам нужно отформатировать все названия планет, заключив их в элементы HTML <Р>, — это можно сделать следующим образом:

<xsl:template match="PLANET">

 <Р>

  <xsl:value-of select="NAME"/>

 </P>

</xsl:template>

Но что делать, если у некоторых планет по два названия, как, например:

<PLANET>

 <NAME>Mercury</NAME>

 <NAME>Closest planet to the sun</NAME>

 <MASS UNITS="(Earth = 1)">.0553</MASS>

 <DAY UNITS="days">58.65</DAY>

 <RADIUS UNITS="miles">1516</RADIUS>

 <DENSITY UNITS="(Earth = 1)">.983</DENSITY>

 <DISTANCE UNITS="million miles">43.4</DISTANCE><!--B перигелии-->

</PLANET>

Это проблема, поскольку атрибут select элемента <xsl:value-of> сам по себе выберет только первый элемент <NAME>. Чтобы пройти в цикле все возможные варианты, вместо него следует применить элемент <xsl:for-each> (листинг 5.7).

Листинг 5.7. Применение <xsl:for-each>

<?xml version="1.0"?>

<xsl:stylesheet version="1.0"

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <xsl:template match="PLANETS">

  <HTML>

   <xsl:apply-templates/>

  </HTML>

 </xsl:template>

 <xsl:template match="PLANET">

  <xsl:for-each select="NAME">

   <P>

    <xsl:value-of select="."/>

   </P>

  </xsl:for-each>

 </xsl:template>

</xsl:stylesheet>

Эта таблица стилей охватывает все элементы <NAME>, помещает их значения в элемент <Р> и добавляет их в выходной документ следующим образом:

<HTML>

 <P>Mercury</P>

 <P>Closest planet to the sun</P>

 <P>Venus</P>

 <P>Earth</P>

</HTML>

Вот еще один пример, впервые появившийся в главе 3, «Создание и применение шаблонов», где при помощи элемента <xsl:for-each> в цикле перебирались все атрибуты элемента:

<?xml version="1.0"?>

<xsl:stylesheet version="1.0"

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <xsl:output method="xml"/>

 <xsl:template match="*">

  <xsl:copy>

   <xsl:for-each select="@*">

    <xsl:copy/>

   </xsl:for-each>

   <xsl:apply-templates/>

  </xsl:copy>

 </xsl:template>

</xsl:stylesheet>

Следующий пример появился в главе 2, «Создание и применение таблиц стилей». Это упрощенная таблица стилей, в которой нельзя использовать какие-либо элементы высокого уровня, то есть нельзя использовать <xsl:template> или <xsl:apply-templates>, однако можно пройти по узлам в цикле при помощи <xsl:for-each>:

<HTML xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xsl:version="1.0">

 <HEAD>

  <TITLE>

   The Planets Table

  </TITLE>

 </HEAD>

 <BODY>

  <H1>

   The Planets Table

  </H1>

  <TABLE BORDER="2">

   <TR>

    <TD>Name</TD>

    <TD>Mass</TD>

    <TD>Radius</TD>

    <TD>Day</TD>

   </TR>

   <xsl:for-each select="//PLANET">

    <TR>

     <TD><xsl:value-of select="NAME"/></TD>

     <TD><xsl:value-of select="MASS"/></TD>

     <TD><xsl:value-of select="RADIUS"/></TD>

     <TD><xsl:value-of select="DAY"/></TD>

    </TR>

   </xsl:for-each>

  </TABLE>

 </BODY>

</HTML>

Эта упрощенная таблица стилей форматирует planets.xml в planets.html практически так же хорошо, как и шаблон, использующий <xsl:apply-templates>, в связи с чем появляется интересный вопрос: когда следует для прохода по узлам применять <xsl:for-each>, а когда <xsl:apply-templates>?

Как правило, <xsl:apply-templates> хорошо применять в тех случаях, когда организация дочерних узлов неизвестна, и вы хотите применить различные шаблоны к потомкам разных видов — независимо от количества уровней, на которые углубляется их структура. С другой стороны, если дочерние узлы обладают регулярной, хорошо определенной организацией, можно задать <xsl:for-each> для обработки всех этих узлов.

Элемент <xsl:for-each> работает во многом так же, как и <xsl:apply-templates>; можно даже вкладывать шаблоны при помощи <xsl:for-each>, как это делается при помощи последовательных элементов <xsl:apply-templates>. В листинге 5.8 я прохожу в цикле по каждому элементу <PLANET>, а затем во вложенном в него цикле по всем элементам, содержащимся в элементе <PLANET>, перечисляя их данные из элементов <DATA> следующим образом.

Листинг 5.8. Второй пример <xsl:for-each>

<?xml version="1.0"?>

<xsl:stylesheet version="1.0"

 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <xsl:output method="xml"/>

 <xsl:template match="PLANETS">

  <PLANETS>

   <xsl:for-each select="PLANET">

    <PLANET>

     <xsl:for-each select="*">

      <DATA>

       <xsl:value-of select="."/>

      </DATA>

     </xsl:for-each>

    </PLANET>

   </xsl:for-each>

  </PLANETS>

 </xsl:template>

</xsl:stylesheet>

И вот результат:

<?xml version="1.0" encoding="UTF-8"?>

<PLANETS>

 <PLANET>

  <DATA>Mercury</DATA>

  <DATA>.0553</DATA>

  <DATA>58.65</DATA>

  <DATA>1516</DATA>

  <DATA>.983</DATA>

  <DATA>43.4</DATA>

 </PLANET>

 <PLANET>

  <DATA>Venus</DATA>

  <DATA>.815</DATA>

  <DATA>116.75</DATA>

  <DATA>3716</DATA>

  <DATA>.943</DATA>

  <DATA>66.8</DATA>

 </PLANET>

 <PLANET>

  <DATA>Earth</DATA>

  <DATA>1</DATA>

  <DATA>1</DATA>

  <DATA>2107</DATA>

  <DATA>1</DATA>

  <DATA>128.4</DATA>

 </PLANET>

</PLANETS>