Как разделить текст и сохранить теги HTML (XSLT 2.0) - PullRequest
4 голосов
/ 21 июня 2011

У меня есть xml с узлом описания:

<config>
  <desc>A <b>first</b> sentence here. The second sentence with some link <a href="myurl">The link</a>. The <u>third</u> one.</desc>
</config>

Я пытаюсь разбить предложения, используя точку в качестве разделителя, но в то же время сохраняю в выводе HTML возможные теги HTML. Пока что у меня есть шаблон, который разбивает описание, но теги HTML теряются в выводе из-за функций normalize-space и substring-before. Мой текущий шаблон приведен ниже:

<xsl:template name="output-tokens">
  <xsl:param name="sourceText" />

  <!-- Force a . at the end -->
  <xsl:variable name="newlist" select="concat(normalize-space($sourceText), ' ')" />
  <!-- Check if we have really a point at the end -->
  <xsl:choose>
    <xsl:when test ="contains($newlist, '.')">
      <!-- Find the first . in the string -->
      <xsl:variable name="first" select="substring-before($newlist, '.')" />

      <!-- Get the remaining text -->
      <xsl:variable name="remaining" select="substring-after($newlist, '.')" />
      <!-- Check if our string is not in fact a . or an empty string -->
      <xsl:if test="normalize-space($first)!='.' and normalize-space($first)!=''">
        <p><xsl:value-of select="normalize-space($first)" />.</p>
      </xsl:if>
      <!-- Recursively apply the template for the remaining text -->
      <xsl:if test="$remaining">
        <xsl:call-template name="output-tokens">
          <xsl:with-param name="sourceText" select="$remaining" />
        </xsl:call-template>
      </xsl:if>
    </xsl:when>
    <!--If no . was found -->
    <xsl:otherwise>
      <p>
        <!-- If the string does not contains a . then display the text but avoid 
           displaying empty strings 
         -->
        <xsl:if test="normalize-space($sourceText)!=''">
          <xsl:value-of select="normalize-space($sourceText)" />.
        </xsl:if>
      </p>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

и я использую его следующим образом:

<xsl:template match="config">
  <xsl:call-template name="output-tokens">
       <xsl:with-param name="sourceText" select="desc" />
  </xsl:call-template>
</xsl:template>

Ожидаемый результат:

<p>A <b>first</b> sentence here.</p>
<p>The second sentence with some link <a href="myurl">The link</a>.</p>
<p>The <u>third</u> one.</p>

Ответы [ 4 ]

4 голосов
/ 21 июня 2011

Хороший вопрос, и его нелегко решить.Особенно, конечно, если вы используете XSLT 1.0 (вам действительно нужно сообщить нам, если это так).

Я видел два подхода к проблеме.И то, и другое включает разбивку на более мелкие задачи.

Первый подход - преобразовать разметку в текст (например, заменить <b>first</b> на [b]first[/b]), а затем использовать операции манипуляции с текстом (xsl: analysis-string) дляразбить его на предложения, а затем восстановить разметку внутри предложений.

Второй подход (который я лично предпочитаю) - преобразовать разделители текста в разметку (преобразовать "." в <stop/>), а затем использоватьметоды позиционной группировки (обычно <<code>xsl:for-each-group group-ending-with="stop"/> для преобразования предложений в абзацы.)

3 голосов
/ 22 июня 2011

Вот один из способов реализации второго подхода , предложенного Майклом Кей с использованием XSLT 2.

Эта таблица стилей демонстрирует двухпроходное преобразование, при котором первый проход вводит маркеры <stop/> после каждого предложения, а второй проход включает все группы, заканчивающиеся <stop/> в абзаце.

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

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

  <!-- two-pass processing -->
  <xsl:template match="/">
    <xsl:variable name="intermediate">
      <xsl:apply-templates mode="phase-1"/>
    </xsl:variable>
    <xsl:apply-templates select="$intermediate" mode="phase-2"/>
  </xsl:template>

  <!-- identity transform -->
  <xsl:template match="@*|node()" mode="#all" priority="-1">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" mode="#current"/>
    </xsl:copy>
  </xsl:template>

  <!-- phase 1 -->

  <!-- insert <stop/> "milestone markup" after each sentence -->
  <xsl:template match="text()" mode="phase-1">
    <xsl:analyze-string select="." regex="\.\s+">
      <xsl:matching-substring>
        <xsl:value-of select="regex-group(0)"/>
        <stop/>
      </xsl:matching-substring>
      <xsl:non-matching-substring>
        <xsl:value-of select="."/>
      </xsl:non-matching-substring>
    </xsl:analyze-string>
  </xsl:template>

  <!-- phase 2 -->

  <!-- turn each <stop/>-terminated group into a paragraph -->
  <xsl:template match="*[stop]" mode="phase-2">
    <xsl:copy>
      <xsl:for-each-group select="node()" group-ending-with="stop">
        <p>
          <xsl:apply-templates select="current-group()" mode="#current"/>
        </p>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>

  <!-- remove the <stop/> markers -->
  <xsl:template match="stop" mode="phase-2"/>

</xsl:stylesheet>
2 голосов
/ 22 июня 2011

Это мое скромное решение, основанное на втором предложении ответа @Michael Kay.

В отличие от ответа @Jukka (что действительно очень элегантно), я не использую xsl:analyse-string, так как функций XPath 1.0 contains и substring-after достаточно для выполнения разделения.Я также начал шаблон сопоставления с config.

Вот преобразование:

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

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

    <!-- two pass processing -->
    <xsl:template match="config">
        <xsl:variable name="pass1">
            <xsl:apply-templates select="node()"/>
        </xsl:variable>
        <xsl:apply-templates mode="pass2" select="$pass1/*"/>
    </xsl:template>

    <!-- 1. Copy everything as is (identity) -->
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>

    <!-- 1. Replace "text. text" with "text<dot/> text" -->
    <xsl:template match="text()[contains(.,'. ')]">
        <xsl:value-of select="substring-before(.,'. ')"/>
        <dot/>
        <xsl:value-of select="substring-after(.,'. ')"/>
    </xsl:template>

    <!-- 2. Group by examining in population order ending with dot -->
    <xsl:template match="desc" mode="pass2">
        <xsl:for-each-group select="node()" 
            group-ending-with="dot">
            <p><xsl:apply-templates select="current-group()" mode="pass2"/></p>
        </xsl:for-each-group>
    </xsl:template>

    <!-- 2. Identity -->
    <xsl:template match="node()|@*" mode="pass2">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*" mode="pass2"/>
        </xsl:copy>
    </xsl:template>

    <!-- 2. Replace dot with mark -->
    <xsl:template match="dot" mode="pass2">
        <xsl:text>.</xsl:text>
    </xsl:template>

</xsl:stylesheet>

Применительно к вводу, показанному в вашем вопросе, выдает:

<p>A <b>first</b> sentence here.</p>
<p>The second sentence with some link <a href="myurl">The link</a>.</p>
<p>The <u>third</u> one.</p>
0 голосов
/ 21 июня 2011
...