如何使用XSLT从平面XML文件创建HTML列表(基于先前的问题)

时间:2019-03-26 17:43:12

标签: xml xslt xslt-1.0

我需要使用XSLT 1.0从平面XML结构创建HTML无序列表。输入XML由一系列要转换为列表项的节点组成。但是,此系列可能会被不同类型的非列表节点打断:

<input>
  <paragraph>abc</paragraph>
  <paragraph>def</paragraph>
    <listable>123</listable>
    <listable>456</listable>
  <other-block>
    <other-text>Foo</other-text>
  </other-block>
    <listable>789</listable>
    <listable>012</listable>
</input>

我的目标是:

<div class="output">
  <p>abc</p>
  <p>def</p>
  <ul>
    <li>123</li>
    <li>456</li>
  </ul>
  <div class="my-block">
    <p class="other">Foo</p>
  </div>
  <ul>
    <li>789</li>
    <li>012</li>
  </ul>
</div>

我找到了old thread with a solution that almost works for me(页面上的最后一个解决方案,作者Dimitre Novatchev)并对其进行了修改。这是基于该解决方案的最小样式表:

<?xml version="1.0"?>

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

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

    <!-- NON-LIST ITEMS: -->
    <xsl:template match="input">
        <div class="output">
            <xsl:apply-templates />
        </div>
    </xsl:template>

    <xsl:template match="paragraph">
        <p>
            <xsl:apply-templates />
        </p>
    </xsl:template>

    <xsl:template match="other-block">
        <div class="my-block">
            <xsl:apply-templates select="descendant::other-text" /> 
        </div>
    </xsl:template> 

    <xsl:template match="other-text">
        <p class="other">
            <xsl:copy-of select="text()" />
        </p>
    </xsl:template>

    <!-- LIST HANDLING: -->
    <xsl:key name="kFollowingUL" match="listable" 
                use="generate-id(preceding-sibling::*[not(self::listable)][1])"/>

    <xsl:template match="*[not(self::listable) and following-sibling::*[1][self::listable]]">

        <xsl:call-template name="identity" />

        <xsl:variable name="vFolUL"
                select="key('kFollowingUL',generate-id())"/>

        <xsl:if test="$vFolUL">
            <ul>
                <xsl:apply-templates mode="copy"
                        select="key('kFollowingUL',generate-id())" />
            </ul>
        </xsl:if>

    </xsl:template>

    <xsl:template match="listable" mode="copy">
        <li>
            <xsl:value-of select="normalize-space()" />
        </li>
    </xsl:template>   

    <xsl:template match="listable" />

</xsl:stylesheet>

此方法的问题在于,它不会将转换应用于每个列表之前的最后一个不可列出的节点。尽管模板已应用于<paragraph>的后代,但输入中的<other-block><other-block>节点被直接复制到输出:

<div class="output">
  <p>abc</p>
  <paragraph>def</paragraph>
  <ul>
    <li>123</li>
    <li>456</li>
  </ul>
  <other-block>
    <p class="other">Foo</p>
  </other-block>
  <ul>
    <li>789</li>
    <li>012</li>
  </ul>
</div>

有人可以提出一种方法来修改早期的XSLT 1.0解决方案,并在可列出组之前添加最后一个节点的转换吗?

2 个答案:

答案 0 :(得分:1)

我会这样:

XSLT 1.0

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

<xsl:template match="/input">
    <div class="output">
        <xsl:apply-templates/>
    </div>
</xsl:template>

<xsl:template match="paragraph">
    <p>
        <xsl:apply-templates/>
    </p>
</xsl:template>

<xsl:template match="other-block">
    <div class="my-block">
        <xsl:apply-templates/> 
    </div>
</xsl:template> 

<xsl:template match="other-text">
    <p class="other">
        <xsl:apply-templates/>      
    </p>
</xsl:template>

<xsl:template match="listable">
    <xsl:if test="not(preceding-sibling::*[1][self::listable])">
        <ul>
            <xsl:apply-templates select="." mode="list"/>       
        </ul>
    </xsl:if>
</xsl:template>

<xsl:template match="listable" mode="list">
    <li>
        <xsl:apply-templates/>      
    </li>
    <xsl:apply-templates select="following-sibling::*[1][self::listable]" mode="list"/>         
</xsl:template>

</xsl:stylesheet>

答案 1 :(得分:0)

您的问题来自此模板:

<xsl:template match="*[not(self::listable) and following-sibling::*[1][self::listable]]">

    <xsl:call-template name="identity" />

    <xsl:variable name="vFolUL"
            select="key('kFollowingUL',generate-id())"/>

    <xsl:if test="$vFolUL">
        <ul>
            <xsl:apply-templates mode="copy"
                    select="key('kFollowingUL',generate-id())" />
        </ul>
    </xsl:if>

</xsl:template>

此匹配与listable元素作为后一个同级元素的任何元素。然后,在内容模板中,它调用名为identity的模板(在这种情况下,这是标识规则)。与other-block元素的其他模板相比,它的default priority更好:

<xsl:template match="other-text">
    <p class="other">
        <xsl:apply-templates/>      
    </p>
</xsl:template>

我喜欢michael.hor257k解决方案,这是我在original answer中提供的方法。另一个可能的解决方案是遵循相同的原则:

<xsl:template match="*[not(self::listable) and following-sibling::*[1][self::listable]]">

    <xsl:call-template name="separator" />

    <xsl:variable name="vFolUL"
            select="key('kFollowingUL',generate-id())"/>

    <xsl:if test="$vFolUL">
        <ul>
            <xsl:apply-templates mode="copy"
                    select="key('kFollowingUL',generate-id())" />
        </ul>
    </xsl:if>

</xsl:template>

<xsl:template match="other-text" name="separator">
    <p class="other">
        <xsl:apply-templates/>      
    </p>
</xsl:template>

但是,请注意,这不能很好地扩展。