XSLT:在保留HTML的同时将回车符转换为混合节点中的段落

时间:2010-09-17 07:26:58

标签: xslt

我正在尝试在段落中换行新行而不删除混合节点中的HTML。我可以让一个或另一个工作,但不是两个。

XML:

<root>
    <mixed html="true">
        line 1

        <a href="http://google.com">line 2</a>

        <em>line 3</em>
    </mixed>
</root>

期望的输出:

 <div>
     <p>line 1</p>
     <p><a href="http://google.com">line 2</a></p>
     <p><em>line 3</em></p>
 </div>

这些模板与HTML匹配:

<xsl:template match="//*[@html]//*">
    <xsl:element name="{name()}">
    <xsl:apply-templates select="* | @* | text()"/>
    </xsl:element>
</xsl:template>

<xsl:template match="//*[@html]//@*">
    <xsl:attribute name="{name(.)}">
        <xsl:copy-of select="."/>
    </xsl:attribute>
</xsl:template>

这些模板将新行转换为段落:

<xsl:template name="nl2p">

    <xsl:param name="input" />

    <xsl:variable name="output">
        <xsl:call-template name="newline-to-paragraph">
            <xsl:with-param name="input">
                <xsl:copy-of select="$input" />
            </xsl:with-param>
        </xsl:call-template>
    </xsl:variable>

    <xsl:copy-of select="$output" />

</xsl:template>

<!-- convert newline characters to <p></p> -->
<xsl:template name="newline-to-paragraph">

    <xsl:param name="input" />

    <xsl:variable name="output">

        <xsl:choose>
            <xsl:when test="contains($input, '&#10;')">
                <xsl:if test="substring-before($input, '&#10;') != ''">
                    <xsl:element name="p"><xsl:copy-of select="substring-before($input, '&#10;')" /></xsl:element>
                </xsl:if>
                <xsl:call-template name="newline-to-paragraph">
                    <xsl:with-param name="input">
                        <xsl:copy-of select="substring-after($input, '&#10;')" />
                    </xsl:with-param>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:if test="$input != ''">
                    <xsl:element name="p"><xsl:copy-of select="$input" /></xsl:element>
                </xsl:if>
            </xsl:otherwise>
        </xsl:choose>

    </xsl:variable>

    <xsl:copy-of select="$output" />

</xsl:template>

这可能吗?我意识到nl2p模板在节点集上运行字符串函数 - 这会破坏HTML吗?我可以保留它或使用特定的操作顺序来实现这个结果吗?

提前致谢。

编辑:我正在使用XSLT 1.0

3 个答案:

答案 0 :(得分:2)

编辑:抱歉,我错过了拆分文字节点!

最常见的问题:使用p元素包装非空混合内容行

这里的问题是输入树提供程序如何处理仅空白区域的文本节点。 只有Saxon似乎只保留空白区域的文本节点...当然,在输入中添加xml:space="preserve",解决了每个其他XSLT处理器的问题。

此样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes" />
    <xsl:preserve-space elements="*" />
    <xsl:template match="*[@html='true' or @nl2p='true']">
        <div>
            <xsl:apply-templates select="node()[1]"/>
        </div>
    </xsl:template>
    <xsl:template match="node()" mode="open" name="open">
        <xsl:copy-of select="." />
        <xsl:apply-templates select="following-sibling::node()[1]" 
                             mode="open" />
    </xsl:template>
    <xsl:template match="*[@html='true' or @nl2p='true']/node()">
        <xsl:param name="pTail" select="''" />
        <p>
            <xsl:value-of select="$pTail" />
            <xsl:call-template name="open" />
        </p>
        <xsl:variable name="vNext" 
        select="following-sibling::text()[contains(., '&#xA;')][1]" />
        <xsl:apply-templates select="$vNext">
            <xsl:with-param name="pString" 
            select="substring-after($vNext, '&#xA;')" />
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="text()[contains(., '&#xA;')]" 
                  mode="open" priority="1">
        <xsl:value-of select="substring-before(., '&#xA;')" />
    </xsl:template>
    <xsl:template match="*[@html='true' or @nl2p='true']
                          /text()[contains(., '&#xA;')]"
                  priority="1" name="text">
        <xsl:param name="pString" select="."/>
        <xsl:choose>
            <xsl:when test="contains($pString, '&#xA;')">
                <xsl:variable name="vOutput" 
                select="normalize-space(substring-before($pString, '&#xA;'))" />
                <xsl:if test="$vOutput">
                    <p>
                        <xsl:value-of select="$vOutput"/>
                    </p>
                </xsl:if>
                <xsl:call-template name="text">
                    <xsl:with-param name="pString"
                    select="substring-after($pString, '&#xA;')" />
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates select="following-sibling::node()[1]">
                    <xsl:with-param name="pTail" select="$pString" />
                </xsl:apply-templates>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

使用此输入(比问题更复杂):

<root>
    <mixed html="true" xml:space="preserve">
        line 1
        line 2
        <a href="http://google.com">line 2</a> after

        before <em>line 3</em><img src="http://example.org"/>
    </mixed>
</root>

输出:

<div>
<p>line 1</p>
<p>line 2</p>
<p>            <a href="http://google.com">line 2</a> after</p>
<p>            before <em>line 3</em><img src="http://example.org" /></p>
</div>

减少问题:用非p元素包装非空文本节点行和每个其他节点子元素

此样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="*[@html='true']/*">
        <p>
            <xsl:call-template name="identity"/>
        </p>
    </xsl:template>
    <xsl:template match="*[@html='true']/text()" name="text">
        <xsl:param name="pString" select="."/>
        <xsl:choose>
            <xsl:when test="contains($pString,'&#xA;')">
                <xsl:call-template name="text">
                    <xsl:with-param name="pString"
                            select="substring-before($pString,'&#xA;')"/>
                </xsl:call-template>
                <xsl:call-template name="text">
                    <xsl:with-param name="pString"
                            select="substring-after($pString,'&#xA;')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:when test="normalize-space($pString)">
                <p>
                    <xsl:value-of select="normalize-space($pString)"/>
                </p>
            </xsl:when>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

使用问题输入样本,输出:

<root>
    <mixed html="true">
        <p>line 1</p>
        <p><a href="http://google.com">line 2</a></p>
        <p><em>line 3</em></p>
    </mixed>
</root>

使用我自己更复杂的输入(没有@xml:space):

<root>
    <mixed html="true">
        line 1
        line 2
        <a href="http://google.com">line 2</a> after

        before <em>line 3</em><img src="http://example.org"/>
    </mixed>
</root>

输出:

<root>
    <mixed html="true">
        <p>line 1</p>
        <p>line 2</p>
        <p><a href="http://google.com">line 2</a></p>
        <p>after</p>
        <p>before</p>
        <p><em>line 3</em></p>
        <p><img src="http://example.org"></img></p>
    </mixed>
</root>

答案 1 :(得分:1)

在我看到你的评论之前我已经开发了这个,你已经坚持使用1.0了。但是你说你对2.0感到好奇,所以这就是:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
   <xsl:output method="html" indent="yes"/>

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

   <!-- surround all other elements with <p> -->
   <xsl:template match="*" priority="1">
      <p><xsl:copy><xsl:apply-templates select="@* | node()"/></xsl:copy></p>
   </xsl:template>

   <!-- recurse through root and mixed elements, but don't copy them. -->
   <xsl:template match="root | mixed" priority="2">
      <xsl:apply-templates select="node()"/>
   </xsl:template>

   <!-- Surround non-space text content with <p> if there are 
     newlines in the text, or element siblings. -->
   <xsl:template match="text()[contains(., '\n') or ../*]">
      <xsl:analyze-string select="." regex="\s*\n\s*">
         <xsl:non-matching-substring>
            <p><xsl:value-of select="."/></p>
         </xsl:non-matching-substring>
      </xsl:analyze-string>
   </xsl:template>

</xsl:stylesheet>

鉴于输入:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <mixed html="true">
      line 1

      <a href="http://google.com">line 2</a>

      <em>line 3</em>
   </mixed>
</root>

它产生所需的输出:

<p>line 1</p>
<p><a href="http://google.com">line 2</a></p>
<p><em>line 3</em></p>

唯一需要XSLT 2.0的是<xsl:analyze-string>。 您可以通过编写递归处理字符串的模板,使用规范化空间查找“\ n”字符,并使用<p>包围剩余的文本片段来执行类似的操作。

答案 2 :(得分:1)

略微纠正您的转型

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes" />
    <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*" name="identity">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="*[@html='true']">
  <div>
    <xsl:apply-templates/>
  </div>
 </xsl:template>

 <xsl:template match="*[@html='true']/*">
  <p><xsl:call-template name="identity"/></p>
 </xsl:template>

 <xsl:template match="*[@html='true']/text()">
  <xsl:call-template name="nl2p"/>
 </xsl:template>

 <xsl:template name="nl2p">
    <xsl:param name="input" select="."/>

    <xsl:variable name="output">
        <xsl:call-template name="newline-to-paragraph">
            <xsl:with-param name="input">
                <xsl:copy-of select="$input" />
            </xsl:with-param>
        </xsl:call-template>
    </xsl:variable>

    <xsl:copy-of select="$output" />
 </xsl:template>

 <!-- convert newline characters to <p></p> -->
 <xsl:template name="newline-to-paragraph">
    <xsl:param name="input" />

    <xsl:variable name="output">
      <xsl:variable name="vlineText"
       select="normalize-space(substring-before($input, '&#10;'))"/>
      <xsl:variable name="vtextAfter"
       select="normalize-space(substring-after($input, '&#10;'))"/>
        <xsl:choose>
            <xsl:when test="contains($input, '&#10;')">
                <xsl:if test="$vlineText">
                  <p><xsl:copy-of select="$vlineText"/></p>
                </xsl:if>
                <xsl:call-template name="newline-to-paragraph">
                 <xsl:with-param name="input" select="$vtextAfter"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
              <xsl:if test="normalize-space($input)">
                <p><xsl:copy-of select="$input" /></p>
              </xsl:if>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>

    <xsl:copy-of select="$output" />
 </xsl:template>

 <xsl:template match="/*">
  <xsl:apply-templates/>
 </xsl:template>
</xsl:stylesheet>

应用于提供的XML文档

<root>
    <mixed html="true">
        line 1

        <a href="http://google.com">line 2</a>

        <em>line 3</em>
    </mixed>
</root>

产生想要的结果

<div>
   <p>line 1</p>
   <p>
      <a href="http://google.com">line 2</a>
   </p>
   <p>
      <em>line 3</em>
   </p>
</div>