Xpoint
   [напомнить пароль]

Разбиение строк (split)

Оглавление

Постановка задачи

Дано

<data>foo;bar;baz</data>

или

<data>
foo
bar
baz
</data>

Иными словами — последовательность строк с неким разделителем между ними.

Требуется получить

<ul>
 <li>foo</li>
 <li>bar</li>
 <li>baz</li>
</ul>

Необходимые замечания

Прежде всего, следует иметь в виду, что XSLT (по крайней мере, в верcии 1.0), строго говоря, не предназначен для такого рода задач. Так что, если есть возможность, следует использовать для этой цели другие языки.

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

Решение средствами XSLT 1.0.

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

Создание собственной библиотеки

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

Исходный код библиотеки

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
 exclude-result-prefixes="xsl"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template name="token">
 <xsl:param name="token"/>
 <li>
  <xsl:value-of select="$token"/>
 </li>
</xsl:template>


<xsl:template name="split" match="text()" mode="split">
<xsl:param name="str" select="."/>
<xsl:param name="worddiv" select="','"/>

<xsl:choose>
 
 <xsl:when test="contains($str,$worddiv)">
  <xsl:call-template name="token">
   <xsl:with-param name="token" select="substring-before($str, $worddiv)"/>
  </xsl:call-template>
  <xsl:call-template name="split"> 
   <xsl:with-param name="str" select="substring-after($str, $worddiv)"/>
   <xsl:with-param name="worddiv" select="$worddiv"/>
  </xsl:call-template>
 </xsl:when>
 
 <xsl:otherwise>
  <xsl:call-template name="token">
   <xsl:with-param name="token" select="$str"/>
  </xsl:call-template>
 </xsl:otherwise>
 
</xsl:choose>
</xsl:template>

</xsl:stylesheet>

Его использoвaние

Использовать это можно так:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
 exclude-result-prefixes="xsl"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
 
<xsl:import href="lib/split.xsl"/>
<xsl:output method="xml" indent="yes"/>

<xsl:template match="data">
 <ul>
  <xsl:apply-templates mode="split">
   <xsl:with-param name="worddiv" select="';'"/>
  </xsl:apply-templates>
 </ul>
</xsl:template>
</xsl:stylesheet>

Если же в результирующем документе нужен не <li>, а допустим, <p> (если мы разбиваем исходные данные по переводу строки, часто именно это и бывает нужно), мы можем переопределить шаблон token в нашем преобразовании:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
 exclude-result-prefixes="xsl"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
 
<xsl:import href="lib/split.xsl"/>
<xsl:output method="xml" indent="yes"/>

<xsl:template name="token">
 <xsl:param name="token"/>
 <p><xsl:value-of select="$token"/></p>
</xsl:template>
 
<xsl:template match="data">
 <xsl:apply-templates mode="split">
  <xsl:with-param name="worddiv" select="'&#10;'"/>
 </xsl:apply-templates>
</xsl:template>

</xsl:stylesheet>

Частный случай: преобразование перевода строки в <br/>

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

<xsl:template name="token">
 <xsl:param name="token"/>
 <xsl:value-of select="$token"/><br/>
</xsl:template>

Однако, строго говоря, <br/> после последнего элемента нам по условию задачи не требуется: мы хотим заменить на эти теги только переводы строк. Кроме того, решение для этого частного случая существенно проще:

<xsl:template name="nl2br">
   <xsl:param name="s"/>
   <xsl:value-of select="normalize-space(substring-before($s,'&#10;'))"/>
   <xsl:if test="contains($s,'&#10;')">
      <br/>
      <xsl:call-template name="nl2br">
         <xsl:with-param name="s" select="substring-after($s,'&#10;')"/>
      </xsl:call-template>
   </xsl:if>
</xsl:template>

Этот шаблон легко можно преобразовать в более универсальный вариант — аналог ф-ции EXSLT str:replace. Оставляю это желающим...

Использование EXSLT

В случае, если процессой поддерживает EXSLT, можно возпользоваться предлагаемой для этой цели ф-цией str:split. Решение с ее использованием будет таким:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
  exclude-result-prefixes="xsl str"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:str="http://exslt.org/strings"
  extension-element-prefixes="str"
 >

<xsl:output method="xml" indent="yes"/>

<xsl:template match='data'>
  <ul>
    <xsl:apply-templates select="str:split(.,';')"/>
  </ul>
</xsl:template>

<xsl:template match='token'>
  <li><xsl:value-of select='.'/></li>
</xsl:template>
               
</xsl:stylesheet>

Для случая когда разделителем служит перевод строки, соответсвенно, достатоточно заменить ; на &#10;.

Использование XSLT 2.0

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

По этому, приведенная выше задача не представляет здесь проблемы. Решить ее можно легко, даже двумя способами: с использованием средств XPath 2.0 и с использованием элемента xsl:analyze-string.

В обоих вариантах используются регуларные выражения, что позволяет решать весьма сложные задачи...

Решение через ф-ции XPath

Для данной цели в XPath 2.0 определна ф-ция tokenize. Использовать ее можно так:

<xsl:template match='data'>
  <ul>
    <xsl:for-each select="tokenize(., '\n')">
      <li><xsl:value-of select='.'/></li>
    </xsl:for-each>
 </ul>
</xsl:template>

Обращаю внимание читателей на второй параметр ф-ции tokenize. Это — регуларное выражение. В данном случае оно состоит из одного метасимвола \n, что означает, как не трудно догадаться, перевод строки.

Решение через xsl:analyze-string

На самом деле, в данном случае даже нет необходимости использовать ф-ции XPath, можно сделать то же самое средствами самого XSLT, заменив xsl:for-each на xsl:analyze-string. Для разнообразия, рассмотрим случай с заменой на <br/>.

Отметим, что он настолько распространен, что даже вошел в примеры, приведенные в спецификации

<xsl:analyze-string select="data" regex="\n">
  <xsl:matching-substring>
    <br/>
  </xsl:matching-substring>
  <xsl:non-matching-substring>
    <xsl:value-of select="."/>
  </xsl:non-matching-substring>
</xsl:analyze-string>

Oбсуждения по теме в форуме

Сохранение символов перевода строки в результирующем HTML
Разбиение текста на параграфы

Powered by POEM™ Engine Copyright © 2002-2005