根据子节点的值对父节点的XML进行排序

4

I have a following XML

<Root> 
  <Element A/>
  <Element B/>
  <Data1> 
    <DataElement/> 
    <Values>
       <Value>2222</Value> 
       <Name>field1</Name>
    </Values> 
    <Values>
       <Value>ABC</Value> 
       <Name>field2</Name>
    </Values> 
  </Data1> 
  <Data2> 
    <DataElement/> 
    <Values>
       <Value>1111</Value> 
       <Name>field1</Name>
    </Values> 
    <Values>
       <Value>XYZ</Value> 
       <Name>field2</Name>
    </Values> 
  </Data2>
  <DataN> 
    ... 
  </DataN> 
</Root> 

我需要获取具有指定字段名称的数据部分按“值”排序的相同XML,例如: 按“field1”排序将返回

    <Root> 
  <Element A/>
  <Element B/>
  <Data2> 
    <DataElement/> 
    <Values>
       <Value>1111</Value> 
       <Name>field1</Name>
    </Values> 
    <Values>
       <Value>XYZ</Value> 
       <Name>field2</Name>
    </Values> 
  </Data2>
  <Data1> 
    <DataElement/> 
    <Values>
       <Value>2222</Value> 
       <Name>field1</Name>
    </Values> 
    <Values>
       <Value>ABC</Value> 
       <Name>field2</Name>
    </Values> 
  </Data1>   
  <DataN> 
    ... 
  </DataN> 
</Root> 

此外,我需要将排序字段的名称作为参数发送...

提供的 XML 不是格式良好的。 - Kirill Polishchuk
好问题,+1。保留DataX元素和所有其他元素的相对顺序有点具有挑战性,但绝对是可行的。 - Dimitre Novatchev
2个回答

2

此转换完全实现了所述要求。它特别注意保留不需要排序的元素的确切顺序。目前没有其他答案可以做到这一点:

<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:param name="vField" select="'field1'"/>
 <xsl:param name="pSortType" select="'number'"/>

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

   <xsl:template match="*[starts-with(name(),'Data')]">
    <xsl:variable name="vPos" select=
      "count(preceding-sibling::*[starts-with(name(),'Data')])+1"/>

  <xsl:for-each select="/*/*[starts-with(name(),'Data')]">
   <xsl:sort select="Values[Name=$vField]/Value"
             data-type="{$pSortType}"/>
   <xsl:if test="position() = $vPos">
    <xsl:copy-of select="."/>
   </xsl:if>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

当应用于以下XML文档时(与提供的相同,但在Data1和Data2之间插入了一个<Element C=""/>,以便我们可以验证非排序元素的顺序保留):
<Root>
    <Element A=""/>
    <Element B=""/>
    <Data1>
        <DataElement/>
        <Values>
            <Value>2222</Value>
            <Name>field1</Name>
        </Values>
        <Values>
            <Value>ABC</Value>
            <Name>field2</Name>
        </Values>
    </Data1>
        <Element C=""/>
    <Data2>
        <DataElement/>
        <Values>
            <Value>1111</Value>
            <Name>field1</Name>
        </Values>
        <Values>
            <Value>XYZ</Value>
            <Name>field2</Name>
        </Values>
    </Data2>
</Root>

生成所需的、正确的结果 -- 请注意 <Element C=""/> 的位置被保留:

<Root>
   <Element A=""/>
   <Element B=""/>
   <Data2>
      <DataElement/>
      <Values>
         <Value>1111</Value>
         <Name>field1</Name>
      </Values>
      <Values>
         <Value>XYZ</Value>
         <Name>field2</Name>
      </Values>
   </Data2>
   <Element C=""/>
   <Data1>
      <DataElement/>
      <Values>
         <Value>2222</Value>
         <Name>field1</Name>
      </Values>
      <Values>
         <Value>ABC</Value>
         <Name>field2</Name>
      </Values>
   </Data1>
</Root>

0

我认为你可以使用这个简单的转换:

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

    <xsl:param name="field" select="'field1'"/>

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

    <xsl:template match="Root">
        <xsl:copy>
            <xsl:apply-templates select="@*|*">
                <xsl:sort select="Values[Name=$field]/
                    Value[string(number(.))!='NaN']" 
                    data-type="number"/>
                <xsl:sort select="Values[Name=$field]/
                    Value[string(number(.))='NaN']"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

当应用于您的输入时(已更正为使其格式正确):

<Root> 
    <Element A=""/>
    <Element B=""/>
    <Data1> 
        <DataElement/> 
        <Values>
            <Value>2222</Value> 
            <Name>field1</Name>
        </Values> 
        <Values>
            <Value>ABC</Value> 
            <Name>field2</Name>
        </Values> 
    </Data1> 
    <Data2> 
        <DataElement/> 
        <Values>
            <Value>1111</Value> 
            <Name>field1</Name>
        </Values> 
        <Values>
            <Value>XYZ</Value> 
            <Name>field2</Name>
        </Values> 
    </Data2> 
</Root>

产生:

<Root>
    <Element A=""></Element>
    <Element B=""></Element>
    <Data2>
        <DataElement></DataElement>
        <Values>
            <Value>1111</Value>
            <Name>field1</Name>
        </Values>
        <Values>
            <Value>XYZ</Value>
            <Name>field2</Name>
        </Values>
    </Data2>
    <Data1>
        <DataElement></DataElement>
        <Values>
            <Value>2222</Value>
            <Name>field1</Name>
        </Values>
        <Values>
            <Value>ABC</Value>
            <Name>field2</Name>
        </Values>
    </Data1>
</Root>

说明:

  • 使用身份规则将所有内容原样复制。
  • 覆盖所需的DataN元素,并根据$field输入参数简单应用排序条件。
  • 按元素字段类型动态更改排序的数据类型。

这个转换根本没有产生想要的输出!-1。 - Dimitre Novatchev
@Dimitre,抱歉给您带来不便。我已经进行了正确的测试,并为我的解决方案提供了修复。方法仍然是相同的。 - Emiliano Poggi
@_empo:我明白了。尝试使用我回答中的XML文档来测试你的解决方案,看看元素排序是否得以保留(提示:不会)。 - Dimitre Novatchev
感谢您的解释,我将元素名称从“Data1(2...N)”更改为仅“Data”,取消了排序字段作为参数,并按照Data/Values/Value进行了排序。这对我来说已经足够好了。 - HelenD

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