XSLT排序 - 如何按属性对父节点内的xml子节点进行排序

10

起初只是简单的事情,但对于XSLT新手来说却变得相当麻烦。

尝试对子节点进行排序/降序排列,但在将属性添加到它们的父节点后,在VS2010中调试时会收到错误消息:

“在已经添加文本、注释、处理指令或子元素节点之后无法向父元素添加属性和命名空间节点。”

假设我有这个简单的XML:

<posts>
    <year value="2013">
        <post postid="10030" postmonth="1">
            <othernode></othernode>
            <othernode2></othernode2>
        </post>
        <post postid="10040" postmonth="2">
            <othernode></othernode>
            <othernode2></othernode2>
        </post>
        <post postid="10050" postmonth="3">
             <othernode></othernode>
             <othernode2></othernode2>
        </post>
    </year>
    <year value="2012">
        <post postid="10010" postmonth="1">
            <othernode></othernode>
            <othernode2></othernode2>
        </post>
        <post postid="10015" postmonth="2">
            <othernode></othernode>
            <othernode2></othernode2>
        </post>
        <post postid="10020" postmonth="3">
             <othernode></othernode>
             <othernode2></othernode2>
        </post>
    </year>
</posts>

我向xmldatasource传递了一个XPATH以检索相关的<year>节点,例如2013年。 然后我需要使用postid对其子节点<post>进行降序排序,因此对于<year value=2013>,当渲染时,postid = 10050将首先显示出来。

因此,要明确:我只对一个<year>节点内部进行排序。

在我将节点拆分为单独的<post>节点之前(即xml为/posts/post),以下XSLT有效:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="no"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()">
                <xsl:sort select="@postid" data-type="text" order="descending"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

由于上述错误,在运行时xmldatasource为空。如果我将ascending传递到order中,显然会返回相同的xml(没有转换)。

问题:如何更新上面的XSLT(或新的XSLT)以容纳父节点属性(<year value="">)?通过研究,有一个答案说“我需要在元素创建之前添加属性创建”。当观察调试器时,这很有道理,子节点按降序形成,但年份标签缺少其属性。但我真的不知道XSLT。看不出它太复杂了,只是不知道语言。

非常感谢任何帮助。谢谢

2个回答

11

那么您的意思是,您只传递了一部分XML文档(一个<year>节点)给XSLT处理器?

您应该为年份使用单独的模板,这样这就是唯一使用排序的模板。以下是如何实现的:

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

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

  <xsl:template match="year">
    <xsl:copy>
      <xsl:apply-templates select="@*" />
      <xsl:apply-templates select="post">
        <xsl:sort select="@postid" data-type="number" order="descending"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

我认为上述方法更好,但是我认为你的错误根本原因在于在排序时混淆了属性和元素。如果你只是这样做,你原来的XSLT可能会没有错误:

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*">
        <xsl:apply-templates select="node()">
            <xsl:sort select="@postid" data-type="text" order="descending"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

1
就是这样。太棒了,它能正常工作。非常感谢您,先生!是的,我只传递了一年的节点到XSLT中。我选择要显示的年份/帖子。我会尝试弄清楚您的新结构并学习阅读XSLT。 - JimXC
在这种情况下,为什么要使用 data-type="text"?如果 postid 的值长度不同,显然会产生错误的结果。 - Dimitre Novatchev
这是一个很好的观点。我使用它是因为JimXC的原始XSLT使用了它,而且这不是他问题的重点,另外我没有注意到他的示例中的ID是数字。如果可以保证ID是数字,则data-type="numeric"更可取。 - JLRishe
@JLRishe,不仅“应该优先考虑”,而且“必须使用”--基于数字排序键的排序必须指定data-type="number" - Dimitre Novatchev
@DimitreNovatchev 如果这些值的长度都相同,那么两者的行为将是相同的,但从根本上讲,您是正确的:_如果_这些ID都保证是数字,那么他肯定应该使用data-type="number"(我意识到我在我的评论中不小心打出了“numeric”)。由于我不能完全确定Jim的ID始终都是数字,所以我现在会将我的答案保留为它,等待他的澄清。 - JLRishe
@JLRishe 是的,它们是数字ID并且永远都是。但这个问题并没有很清楚地表达出来,而且已经有了文本。就像你所说,这不是问题的核心。感谢你提供具体信息。 - JimXC

2

这个转变:

该转变涉及IT技术的内容。
<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()|@*">
     <xsl:copy>
       <xsl:apply-templates select="@*|node()"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="year">
  <xsl:copy>
    <xsl:apply-templates select="@*"/>
    <xsl:apply-templates select="*">
      <xsl:sort select="@postid" data-type="number" order="descending"/>
    </xsl:apply-templates>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

当应用于提供的XML文档时:

<posts>
    <year value="2013">
        <post postid="10030" postmonth="1">
            <othernode></othernode>
            <othernode2></othernode2>
        </post>
        <post postid="10040" postmonth="2">
            <othernode></othernode>
            <othernode2></othernode2>
        </post>
        <post postid="10050" postmonth="3">
            <othernode></othernode>
            <othernode2></othernode2>
        </post>
    </year>
    <year value="2012">
        <post postid="10010" postmonth="1">
            <othernode></othernode>
            <othernode2></othernode2>
        </post>
        <post postid="10015" postmonth="2">
            <othernode></othernode>
            <othernode2></othernode2>
        </post>
        <post postid="10020" postmonth="3">
            <othernode></othernode>
            <othernode2></othernode2>
        </post>
    </year>
</posts>

能够产生所需的、正确的结果:

<posts>
   <year value="2013">
      <post postid="10050" postmonth="3">
         <othernode/>
         <othernode2/>
      </post>
      <post postid="10040" postmonth="2">
         <othernode/>
         <othernode2/>
      </post>
      <post postid="10030" postmonth="1">
         <othernode/>
         <othernode2/>
      </post>
   </year>
   <year value="2012">
      <post postid="10020" postmonth="3">
         <othernode/>
         <othernode2/>
      </post>
      <post postid="10015" postmonth="2">
         <othernode/>
         <othernode2/>
      </post>
      <post postid="10010" postmonth="1">
         <othernode/>
         <othernode2/>
      </post>
   </year>
</posts>

已确认这也有效。再次为了自己的利益,您在数据类型中使用数字 - 好的,我明白了。但是之前的帖子使用了select=post在apply-template中,而您使用了*。有什么区别?假设您正在处理任何命名节点,而其他人则是特定于post? - JimXC
1
@JimXC,每当我们知道当前节点的任何子元素都是“post”时,使用“*”可能更有效,因为这样可以避免XSLT处理器检查子元素是否为“post”。此外,这种方式代码更短,编写更快,更容易。 - Dimitre Novatchev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接