【问题标题】:How to create an HTML list from flat XML file using XSLT (based on earlier question)如何使用 XSLT 从平面 XML 文件创建 HTML 列表(基于早先的问题)
【发布时间】:2019-08-17 04:47:28
【问题描述】:

我需要使用 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>

这种方法的问题是它不会对每个列表之前的最后一个不可列表节点应用转换。输入中的 &lt;paragraph&gt;&lt;other-block&gt; 节点直接复制到输出中,尽管模板应用于 &lt;other-block&gt; 的后代:

<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 解决方案并在可列出组之前添加最后一个节点的转换?

【问题讨论】:

  • 我们希望能够回答您关于如何做某事的特定问题,而不是仅仅为您编写代码。考虑到这一点,如果您能提供 minimal, complete, and verifiable example 以及您提出的关于某事如何运作或如何运作的具体问题,将会很有帮助。例如,我不知道你不知道什么。此外,我尝试将 Dimitre 的代码应用于您的示例输入,并且从您的帖子中得到了不同的输出。因此,请同时发布您正在使用的 XSL。
  • 我修改了我的问题以包含输入、XSLT 和输出的工作示例。

标签: xml xslt xslt-1.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>

【讨论】:

  • 我也会这样做。您甚至可以使用模板中的测试来摆脱xsl:if 指令,并为listable 元素提供一个空模板。
  • 这个答案对我有用。感谢您分享您的专业知识。
【解决方案2】:

您的问题来自此模板:

<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>

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-09
    • 1970-01-01
    • 2012-03-10
    • 1970-01-01
    • 2014-11-02
    • 2016-02-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多