【问题标题】:Sorting XML with XSLT - entire XML-schema is not known使用 XSLT 对 XML 进行排序 - 整个 XML 模式未知
【发布时间】:2011-10-01 02:15:53
【问题描述】:

如果我不知道整个 XML 模式,我想知道 XSLT 是否可以对 XML 文件进行排序。

例如,我想对以下 XML 文件进行排序。
按 /CATALOG/CD/TITLE 对 /CATALOG/CD 元素进行排序

<CATALOG attrib1="value1">
  <DVD2>
    <TITLE>The Godfather2</TITLE>
  </DVD2>
  <CD>
    <TITLE>Hide your heart</TITLE>
    <ARTIST>Bonnie Tyler</ARTIST>
    <COUNTRY>UK</COUNTRY>
    <COMPANY>CBS Records</COMPANY>
    <PRICE>9.90</PRICE>
    <YEAR>1988</YEAR>
  </CD>
  <CD attrib4="value4">
    <TITLE>Empire Burlesque</TITLE>
    <ARTIST>Bob Dylan</ARTIST>
    <COUNTRY>USA</COUNTRY>
    <COMPANY>Columbia</COMPANY>
    <PRICE>
      <CATALOG>
        <CD><TITLE>E</TITLE></CD>
        <CD><TITLE>I</TITLE></CD>
        <CD><TITLE>D</TITLE></CD>
      </CATALOG>
    </PRICE>
    <YEAR>1985</YEAR>
  </CD>
  <CD attrib2="value2">
    <TITLE attrib3="value3">Greatest Hits</TITLE>
    <ARTIST>Dolly Parton</ARTIST>
    <COUNTRY>USA</COUNTRY>
    <COMPANY>RCA</COMPANY>
    <PRICE>9.90</PRICE>
    <YEAR>1982</YEAR>
  </CD>
  <DVD>
    <TITLE>The Godfather1</TITLE>
  </DVD>
</CATALOG>

输出应该是:

<CATALOG attrib1="value1">
  <CD attrib4="value4">
    <TITLE>Empire Burlesque</TITLE>
    <ARTIST>Bob Dylan</ARTIST>
    <COUNTRY>USA</COUNTRY>
    <COMPANY>Columbia</COMPANY>
    <PRICE>
      <CATALOG>
        <CD><TITLE>E</TITLE></CD>
        <CD><TITLE>I</TITLE></CD>
        <CD><TITLE>D</TITLE></CD>
      </CATALOG>
    </PRICE>
    <YEAR>1985</YEAR>
  </CD>
  <CD attrib2="value2">
    <TITLE attrib3="value3">Greatest Hits</TITLE>
    <ARTIST>Dolly Parton</ARTIST>
    <COUNTRY>USA</COUNTRY>
    <COMPANY>RCA</COMPANY>
    <PRICE>9.90</PRICE>
    <YEAR>1982</YEAR>
  </CD>
  <CD>
    <TITLE>Hide your heart</TITLE>
    <ARTIST>Bonnie Tyler</ARTIST>
    <COUNTRY>UK</COUNTRY>
    <COMPANY>CBS Records</COMPANY>
    <PRICE>9.90</PRICE>
    <YEAR>1988</YEAR>
  </CD>
  <DVD2>
    <TITLE>The Godfather2</TITLE>
  </DVD2>
  <DVD>
    <TITLE>The Godfather1</TITLE>
  </DVD>
</CATALOG>

以下是我做过的众多尝试之一:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <!--<CATALOG>-->
    <xsl:for-each select="CATALOG/CD">
      <xsl:sort select="TITLE" />
      <xsl:copy-of select="."/>
    </xsl:for-each>
    <!--</CATALOG>-->
  </xsl:template>
</xsl:stylesheet>

问题在于,使用此 XSLT,CD 列表之外的 XML 部分不会显示。
我可以取消注释代码的两个注释掉的部分,但这正是我想要避免的。
在这种情况下,如果将任何属性添加到 CATALOG 元素,它们将不会被复制到输出 XML。
我不想重新构建 XML 文件:我只想知道关于 XML 模式的某些部分的确切信息。

此功能很容易实现,例如使用 .NET(使用 XmlDocument 和 XmlNode 对象)或 Python 的 lxmx 库,但使用 XSLT 是否可以实现?

谢谢!

注意:要在所有情况下避免误解问题的示例输入 XML 并不容易。但我会尽量详细说明问题:

  • 只有 CATALOG 下的 CD 元素应该被排序(例如 Bob Dylan 部分下的 CD 元素应该保持不变)
  • 除了 CD 以外的元素(例如 DVD 和 DVD2)是在列表的开头还是结尾都是一样的
  • 没有元素、属性、值、cmets,因此输出 XML 中不应缺少任何内容
  • 非 CD 元素(例如 DVD 和 DVD2)不应按 TITLE 子元素排序

【问题讨论】:

  • 对不起。由于措辞较弱,不清楚我只想对 CD 元素进行排序。所以我不得不稍微修改一下这个问题。

标签: xml xslt sorting


【解决方案1】:

这是身份转换的工作吗?这可用于复制架构未知的 XML

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

我认为您需要的只是添加一个新模板以匹配 CATALOG 元素,然后您可以在此采取一些压倒一切的操作(在您的情况下,对 CD 元素进行排序)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:template match="@*|node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="CATALOG">
      <xsl:copy>
         <xsl:apply-templates select="@*" />
         <xsl:apply-templates select="CD">
            <xsl:sort select="TITLE"/>
         </xsl:apply-templates>
         <xsl:apply-templates select="*[local-name() != 'CD']" />
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

因此,在匹配 CATALOG 时,您仍然可以复制模式中的任何属性和任何非 CD 子级,而无需明确知道它们的名称。请注意,如果在 CATALOG 之前还有 DVD 元素,在这种情况下,这些元素将全部移到排序后的 CD 元素之后。

【讨论】:

  • 乍一看,这个解决方案是合适的。我正在做更多的测试和分析。谢谢!
  • 在 CATALOG 元素下 CD 元素之后和所有其他元素之前,有很多空行。你认为有可能删除它们吗?我不太了解 Kragen 的 XSLT 代码,但我认为他已经解决了这个具体问题。
  • 如果你想再试一次,我已经用 select="*[..." 替换了 select="node()[..." 子句。
  • 我认为您的最后修改使解决方案非常完整。谢谢!
【解决方案2】:

坚持只是修改身份转换(这可能不是很安全),我认为以下应该等同于@Tim的答案。

注意我根本不提倡这种技术,除非您了解身份转换的一般行为。

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

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* 
                | node()[not(self::CD[parent::CATALOG])]"/>
            <xsl:apply-templates select="CD[parent::CATALOG]">
                <xsl:sort select="TITLE"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

或者,如果您关心DVDDVD2 的其他元素,您可以这样做:

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

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates select="CD[parent::CATALOG]">
                <xsl:sort select="TITLE"/>
            </xsl:apply-templates>
            <xsl:apply-templates select="node()
                [not(self::CD[parent::CATALOG])]"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

【讨论】:

  • 你能最终解释一下这个转换的问题吗?
  • 对不起。我现在稍微修改了原来的问题。因为你是第二个误解它的人。
  • 嗯,这正是我的转换所做的。有什么问题?
  • 如果 CATALOG 下不仅有 CD 元素,则不会。
  • 我明白了,只是因为我对我的答案的正确性感兴趣,我已经对其进行了更改以提供正确的答案,即使您已经对@Tim 的答案感到满意。
【解决方案3】:

要获取CATALOG 元素中的所有属性,您可以编写:

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

    <xsl:template match="CATALOG">
        <xsl:copy>
            <xsl:copy-of select="@*"/>

            <xsl:copy-of select="CD[1]/preceding-sibling::*"/>
            <xsl:for-each select="CD">
                <xsl:sort select="TITLE"/>
                <xsl:copy-of select="."/>
            </xsl:for-each>
            <xsl:copy-of select="CD[last()]/following-sibling::*"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

结果:

<CATALOG atr1="value1" atr2="value2">
    <DVD>
        <FORMAT>DVD-9</FORMAT>
    </DVD>
    <CD>
        <TITLE>1999 Grammy Nominees</TITLE>
        <ARTIST>Many</ARTIST>
        <COUNTRY>USA</COUNTRY>
        <COMPANY>Grammy</COMPANY>
        <PRICE>10.20</PRICE>
        <YEAR>1999</YEAR>
    </CD>
    <CD>
        <TITLE>Big Willie style</TITLE>
        <ARTIST>Will Smith</ARTIST>
        <COUNTRY>USA</COUNTRY>
        <COMPANY>Columbia</COMPANY>
        <PRICE>9.90</PRICE>
        <YEAR>1997</YEAR>
    </CD>
    ...
    <BLUERAY>
        <TITLE>Contact</TITLE>
        <YEAR>1997</YEAR>
    </BLUERAY>
</CATALOG>

【讨论】:

  • 您的加号代码行和额外的一些修改可能会让我们更接近解决方案。但是,例如,如果我在“CATALOG”下添加一个名为“DVD”的元素,这个新元素将被忽略。
  • “忽略”是指“排除”。将 DVD 元素添加到列表的开头还是结尾都是一样的。但它应该包含在输出 XML 中。
  • @user834929:看,我认为您可以使用带有preceding-siblingfollowing-sibling 轴的深拷贝来获取此类元素。但是我不确定它是否是最有效的方式(对于大型 XML 文档)。
  • 如果我进行与您完全相同的修改,它仍然无法正常工作。结果 XML 也将包含 BLURAY 和 DVD 元素,但 CD 元素未排序。
  • 你是对的,对此感到抱歉。尝试更改为CD[1]/preceding-sibling::*CD[last()]/following-sibling::*
【解决方案4】:

试试这个:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes" />

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

  <xsl:template match="CATALOG">
    <xsl:copy>
      <xsl:apply-templates select="@* | *">
        <xsl:sort select="TITLE" />
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

这是标准复制模板match="@* | node()" 加上指定排序条件的CATALOG 节点的特殊情况。

请注意,我在 secod 模板中为 apply-templates 规则制作的选择器与标准复制模板 (@* | *) 略有不同。这是因为标准的复制模板选择器还包括文本节点,但是文本节点没有 TITLE 元素,因此排序指令最终将它们全部放在首位,这看起来有点奇怪(试试看)。

【讨论】:

  • 此 XSLT 按 TITLE 对 CATALOG 下的所有节点进行排序。没有 TITLE 的元素放在列表的开头。实际上这对我来说有点混乱,因为我的目标有点不同。无论如何,您提到了有关“文本节点”的内容。这是关于从输出 XML 中删除空行吗?
  • 你的目标是什么?文本节点是 XML dom 中仅包含文本的节点。例如1999 Grammy Nominees(在Title 元素内)是一个文本节点,但节点之间的空格也是如此。使用node()(包括文本节点)意味着所有文本节点都被复制到CATALOG元素的开头——试试看我的意思。
  • 我的目标是只对 CATALOG 下的 CD 元素进行排序。 (其他元素可以放在列表的开头或结尾。)
  • 所以基本上 Tim C 的解决方案是合适的。我认为您可以解决的唯一问题是输出 XML 包含太多空行(空格)。
  • @user834929:尝试使用&lt;xsl:strip-space elements="*"/&gt;
猜你喜欢
  • 1970-01-01
  • 2020-09-11
  • 1970-01-01
  • 2020-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-13
相关资源
最近更新 更多