且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

使用 XSLT 将 XML 排序为 XML

更新时间:2022-11-28 16:24:01

你的问题都出在这个匹配的模板里

首先 符号匹配文档级元素,它与根 ns:Root 不一样,而是一个级别.这意味着当你执行 时,所有将选择的是 ns:root 元素,其中只有一个,所以没有意义排序它!

您可能需要首先像这样匹配根元素,复制它,然后开始对子元素进行排序.

您遇到的下一个问题是 sort 语句.您正在使用 xpath 表达式 /ns:Root/ns:element1/@id,但这是一个绝对路径,而不是相对路径,因此只会选择第一个的 @id 属性ns:element1 在文档中.

假设你已经定位在根元素上,并且假设它只有 ns:element1 元素作为子元素,你可以这样

 <xsl:sort select="@id"/></xsl:apply-templates>

您遇到的最后一个问题是您有一个 <xsl:copy-of select="."/> 在您的 xsl:apply-templates 中是不允许的.您可能应该在此处使用 xsl:copy,如上所示.

还值得指出的是,如果您还不知道,***使用 XSLT identity transform 复制现有元素,除非您想以某种方式更改它们.这样您就不必为每种特定类型的元素创建模板.

试试下面的 XSLT

应用于您的 XML 时,输出如下

<ns:Root xmlns:ns="urn:Test.Namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="urn:Test.Namespace Test1.xsd"><ns:element1 id="001"><ns:element2 id="001.1" order="1"><ns:element3 id="001.1.1"/></ns:element2><ns:element2 id="001.2" order="2"><ns:element3 id="001.1.2"/></ns:element2></ns:element1><ns:element1 id="002"><ns:element2 id="002.1" order="3"><ns:element3 id="002.1.1"/></ns:element2><ns:element2 id="002.2" order="4"><ns:element3 id="002.1.2"/></ns:element2></ns:element1><ns:element1 id="003"><ns:element2 id="007.0" order="1"><ns:element3 id="007.1.1"/></ns:element2></ns:element1></ns:Root>

I have a found a few similar questions to this, but struggled to 'bend' the solution to what I need, so apologies for asking again.

I have some XML like this:

<?xml version="1.0" encoding="UTF-8"?>

<ns:Root
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ns="urn:Test.Namespace"  
    xsi:schemaLocation="urn:Test.Namespace Test1.xsd"
    >
    <ns:element1 id="001">
        <ns:element2 id="001.1" order="1">
            <ns:element3 id="001.1.1" />
        </ns:element2>
        <ns:element2 id="001.2" order="2">
            <ns:element3 id="001.1.2" />
        </ns:element2>        
    </ns:element1>
    <ns:element1 id="003">
        <ns:element2 id="007.0" order="1">
            <ns:element3 id="007.1.1" />
        </ns:element2>
    </ns:element1>
    <ns:element1 id="002">
        <ns:element2 id="002.1" order="3">
            <ns:element3 id="002.1.1" />
        </ns:element2>
        <ns:element2 id="002.2" order="4">
            <ns:element3 id="002.1.2" />
        </ns:element2> 
    </ns:element1>    
</ns:Root>

I have written this XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:ns="urn:Test.Namespace"
                >
    <xsl:output indent="no" />
    <xsl:template match="text()[not(string-length(normalize-space()))]"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/">
        <xsl:apply-templates>
            <xsl:sort select="/ns:Root/ns:element1/@id" />
            <xsl:copy-of select="." />
        </xsl:apply-templates>
    </xsl:template>

    <xsl:template match="ns:element1">
        <xsl:copy-of select="." />
        <xsl:apply-templates />        
    </xsl:template>

    <xsl:template match="ns:element2">
        <xsl:copy-of select="." />
        <xsl:apply-templates />
    </xsl:template>

    <xsl:template match="ns:element3">
        <xsl:copy-of select="." />
    </xsl:template>

</xsl:stylesheet>

(I cribbed the outline for this from here how to sort xml?)

What I want to be able to do, is use this XSLT to sort my original XML by the id attribute of element1 and produce XML. The idea being that once it is sorted, I can process it with some other XSLT to get the final result.

Unfortunately, this does not give me any output, which makes me think there is a really stupid typo somewhere, but I cannot see it.

Your problems all lie in this matching template here

<xsl:template match="/">
    <xsl:apply-templates>
        <xsl:sort select="/ns:Root/ns:element1/@id" />
        <xsl:copy-of select="." />
    </xsl:apply-templates>
</xsl:template>

Firstly the symbol matches the document level element, which is not the same as the root ns:Root, but one level about it. What this means is that when you do <xsl:apply-templates> all that will select is the ns:root element, of which there is only one, and so no point sorting it!

What you probably need to start of with is to match on the root element like so, copy it and then start sorting the children.

<xsl:template match="/*">
    <xsl:copy>
        <!-- Code to select and sort childrens -->
    </xsl:copy>
</xsl:template>

The next problem you have is with the sort statement. You are using the xpath expression /ns:Root/ns:element1/@id, but this is an absolute path, not a relative one, so will only ever pick up the @id attribute of the first ns:element1 in the document.

Assuming you were positioned on the root element already, and assuming it only had ns:element1 elements as children, you could just this

    <xsl:apply-templates>
        <xsl:sort select="@id" />
    </xsl:apply-templates>

The final problem you have is that you have an <xsl:copy-of select="." /> statement in your xsl:apply-templates which is not allowed. You probably should be using xsl:copy here, as shown above.

It is also worth pointing out, if you weren't aware already, that it is better to use the XSLT identity transform to copy existing elements, unless you want to change them in some way. That way you don't have to create templates for each particular type of element.

Try the following XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:ns="urn:TestNamespace"                >
    <xsl:output indent="yes" />

    <xsl:strip-space elements="*"/>

    <xsl:template match="text()[not(string-length(normalize-space()))]"/>

    <xsl:template match="/*">
        <xsl:copy>
        <xsl:apply-templates select="@*" />
        <xsl:apply-templates>
            <xsl:sort select="@id" />
        </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

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

When applied to your XML, the following is output

<ns:Root xmlns:ns="urn:Test.Namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="urn:Test.Namespace Test1.xsd">
   <ns:element1 id="001">
      <ns:element2 id="001.1" order="1">
         <ns:element3 id="001.1.1"/>
      </ns:element2>
      <ns:element2 id="001.2" order="2">
         <ns:element3 id="001.1.2"/>
      </ns:element2>
   </ns:element1>
   <ns:element1 id="002">
      <ns:element2 id="002.1" order="3">
         <ns:element3 id="002.1.1"/>
      </ns:element2>
      <ns:element2 id="002.2" order="4">
         <ns:element3 id="002.1.2"/>
      </ns:element2>
   </ns:element1>
   <ns:element1 id="003">
      <ns:element2 id="007.0" order="1">
         <ns:element3 id="007.1.1"/>
      </ns:element2>
   </ns:element1>
</ns:Root>