如何使用 XSLT 1.0 对 xml 的多个嵌套元素组进行排序?

How to sort multiple nested element groups of xml with XSLT 1.0?

我找到了一个名为 XSLT sort by attribute value 的 SO post,它的 objective 与我的相似。我正在尝试使用 XSLT 1.0 按属性值对 xml 节点进行排序。但是当我尝试应用公认的解决方案时,我得到了意想不到的结果。

这是我的系统中的一些非常 stripped-down xml 的东西,我试图在其上实现 XSLT-based 排序。

<?xml version="1.0" encoding="UTF-8"?>
<mpcbom xmlns:bv="urn:BomViewXmlUtil" xmlns:msxsl="urn:schemas-microsoft-com:xslt" seriesid="Flatbed_A" seriesdesc="Flatbed A" modelid="FB2" modeldesc="Flatbed Aluminum 123" pricelistid="0">
   <propDefn>
      <prop seriesid="Flatbed_A" id="AddCharges" text="AddCharges" />
   </propDefn>
   <assembly seriesid="Flatbed_A" id="102B-1" name="Flatbed_B" qty="1" price="123" catid="Finish_unit">
      <prop id="PriceOverride" val="False" />
      <prop id="SuggestedPrice" val="54005" />
      <prop id="TabSeqNum" val="0" src="Assembly" />
      <operation seriesid="Flatbed_A" id="ALC-FOB" name="Client delivery" qty="1" price="0" catid="O_ABC123">
         <prop id="HostOptionID" val="ALC-FOB" />
         <prop id="ProcessTimeStd" val="0" />
         <prop id="ProcessTimeXtra" val="0" />
      </operation>
      <assembly seriesid="Flatbed_A" id="102B-2" name="Unit_Offline" qty="1" price="0" catid="Unit_Offline">
         <prop id="HostOptionID" val="" />
         <prop id="ERP_RouteId" val="" />
         <assembly seriesid="Flatbed_A" id="Step70" name="Step70" qty="1" price="0" catid="Step70">
            <prop id="HostOptionID" val="Step70" />
            <prop id="BOMUOM_From_BYOD" val="UN / EA" />
            <operation seriesid="Flatbed_A" id="523-110" name="Washing" qty="1" price="0" catid="O_523_110">
               <prop id="HostOptionID" val="523-110" />
               <prop id="ProcessTimeStd" val="3.5" />
            </operation>
            <operation seriesid="Flatbed_A" id="523-120" name="Finish1" qty="1" price="0" catid="O_523_120">
               <prop id="HostOptionID" val="523-120" />
               <prop id="ProcessTimeStd" val="0.5" />
            </operation>
            <material seriesid="Flatbed_A" id="3UN5020000" name="Rubber1" qty="1" price="0" catid="Rear_Bumper_Option_Assy">
               <prop id="Cost" val="124.9899768" />
               <prop id="ExtCost" val="124.9899768" />
            </material>
            <material seriesid="Flatbed_A" id="3UN5990000" name="Round1" qty="1" price="0" catid="Doc_Holder_Assy">
               <prop id="Cost" val="5.11" />
               <prop id="ExtCost" val="5.11" />
            </material>
         </assembly>
         <assembly seriesid="Flatbed_A" id="102B-3" name="ABC1" qty="1" price="0" catid="Assembled_Unit">
            <prop id="CatID" val="Assembled_Unit" />
            <prop id="HostOptionID" val="102B-3" />
            <assembly seriesid="Flatbed_A" id="Step10" name="Step10" qty="1" price="0" catid="Step10">
               <prop id="HostOptionID" val="Step10" />
               <prop id="BOMUOM_From_BYOD" val="UN / EA" />
               <operation seriesid="Flatbed_A" id="510-110" name="Assemblage 1" qty="1" price="0" catid="O_510_110">
                  <prop id="HostOptionID" val="510-110" />
                  <prop id="ProcessTimeStd" val="13.5" />
               </operation>
               <material seriesid="Flatbed_A" id="XYZ123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
                  <prop id="Cost" val="80.679768" />
                  <prop id="ExtCost" val="80.679768" />
               </material>
               <material seriesid="Flatbed_A" id="FB2C53XXXA" name="53ft 2 axles Flatbed" qty="1" price="0" catid="Structure_Assy">
                  <prop id="Cost" val="12901.27129" />
                  <prop id="ExtCost" val="12901.27129" />
               </material>
               <material seriesid="Flatbed_A" id="ABC123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
                  <prop id="Cost" val="80.679768" />
                  <prop id="ExtCost" val="80.679768" />
               </material>
            </assembly>
            <assembly seriesid="Flatbed_A" id="Step20" name="Step20" qty="1" price="0" catid="Step20">
               <prop id="HostOptionID" val="Step20" />
               <prop id="BOMUOM_From_BYOD" val="UN / EA" />
               <operation seriesid="Flatbed_A" id="510-120" name="Assemblage 2" qty="1" price="0" catid="O_510_120">
                  <prop id="HostOptionID" val="510-120" />
                  <prop id="ProcessTimeStd" val="14" />
               </operation>
               <material seriesid="Flatbed_A" id="DEF123" name="PLANCHER 53" qty="1" price="0" catid="FloorAlu_Assy">
                  <prop id="Cost" val="3629.53568" />
                  <prop id="ExtCost" val="3629.53568" />
               </material>
               <material seriesid="Flatbed_A" id="STU123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
                  <prop id="Cost" val="773.383414" />
                  <prop id="ExtCost" val="773.383414" />
               </material>
               <material seriesid="Flatbed_A" id="ABC123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
                  <prop id="Cost" val="773.383414" />
                  <prop id="ExtCost" val="773.383414" />
               </material>
            </assembly>
         </assembly>
      </assembly>
   </assembly>
</mpcbom>

这是我的基本 XSLT,它完全符合我的需要,减去排序

<?xml version="1.0" encoding="utf-8"?>
<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" encoding="UTF-8" omit-xml-declaration="yes" indent="no" version="1.0" standalone="yes" />
   <xsl:variable name="lowercase">abcdefghijklmnopqrstuvwxyz</xsl:variable>
   <xsl:variable name="uppercase">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>

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

   <xsl:template match="propDefn" />

   <xsl:template match="material|operation">
      <xsl:copy>
         <xsl:apply-templates select="@*" />
         <xsl:apply-templates select="prop">
            <xsl:sort select="translate(@id, $uppercase, $lowercase)" />
         </xsl:apply-templates>
         <xsl:apply-templates select="*[not(self::prop)]" />
      </xsl:copy>
   </xsl:template>

   <xsl:template match="@rid" />
   <xsl:template match="material/@name|material/@name|operation/@name" />
   <xsl:template match="@id|@catid">
      <xsl:attribute name="{local-name()}">
         <xsl:value-of select="translate(., $uppercase, $lowercase)" />
      </xsl:attribute>
   </xsl:template>
</xsl:stylesheet>

这里是上面转换的xml结果:

<?xml version="1.0" encoding="UTF-8"?>
<mpcbom xmlns:bv="urn:BomViewXmlUtil" xmlns:msxsl="urn:schemas-microsoft-com:xslt" seriesid="Flatbed_A" seriesdesc="Flatbed A" modelid="FB2" modeldesc="Flatbed Aluminum 123" pricelistid="0">
   <assembly seriesid="Flatbed_A" id="102b-1" name="Flatbed_B" qty="1" price="123" catid="finish_unit">
      <prop id="priceoverride" val="False" />
      <prop id="suggestedprice" val="54005" />
      <prop id="tabseqnum" val="0" src="Assembly" />
      <operation seriesid="Flatbed_A" id="alc-fob" qty="1" price="0" catid="o_abc123">
         <prop id="hostoptionid" val="ALC-FOB" />
         <prop id="processtimestd" val="0" />
         <prop id="processtimextra" val="0" />
      </operation>
      <assembly seriesid="Flatbed_A" id="102b-2" name="Unit_Offline" qty="1" price="0" catid="unit_offline">
         <prop id="hostoptionid" val="" />
         <prop id="erp_routeid" val="" />
         <assembly seriesid="Flatbed_A" id="step70" name="Step70" qty="1" price="0" catid="step70">
            <prop id="hostoptionid" val="Step70" />
            <prop id="bomuom_from_byod" val="UN / EA" />
            <operation seriesid="Flatbed_A" id="523-110" qty="1" price="0" catid="o_523_110">
               <prop id="hostoptionid" val="523-110" />
               <prop id="processtimestd" val="3.5" />
            </operation>
            <operation seriesid="Flatbed_A" id="523-120" qty="1" price="0" catid="o_523_120">
               <prop id="hostoptionid" val="523-120" />
               <prop id="processtimestd" val="0.5" />
            </operation>
            <material seriesid="Flatbed_A" id="3un5020000" qty="1" price="0" catid="rear_bumper_option_assy">
               <prop id="cost" val="124.9899768" />
               <prop id="extcost" val="124.9899768" />
            </material>
            <material seriesid="Flatbed_A" id="3un5990000" qty="1" price="0" catid="doc_holder_assy">
               <prop id="cost" val="5.11" />
               <prop id="extcost" val="5.11" />
            </material>
         </assembly>
         <assembly seriesid="Flatbed_A" id="102b-3" name="ABC1" qty="1" price="0" catid="assembled_unit">
            <prop id="catid" val="Assembled_Unit" />
            <prop id="hostoptionid" val="102B-3" />
            <assembly seriesid="Flatbed_A" id="step10" name="Step10" qty="1" price="0" catid="step10">
               <prop id="hostoptionid" val="Step10" />
               <prop id="bomuom_from_byod" val="UN / EA" />
               <operation seriesid="Flatbed_A" id="510-110" qty="1" price="0" catid="o_510_110">
                  <prop id="hostoptionid" val="510-110" />
                  <prop id="processtimestd" val="13.5" />
               </operation>
               <material seriesid="Flatbed_A" id="xyz123" qty="1" price="0" catid="front_rail_assy">
                  <prop id="cost" val="80.679768" />
                  <prop id="extcost" val="80.679768" />
               </material>
               <material seriesid="Flatbed_A" id="fb2c53xxxa" qty="1" price="0" catid="structure_assy">
                  <prop id="cost" val="12901.27129" />
                  <prop id="extcost" val="12901.27129" />
               </material>
               <material seriesid="Flatbed_A" id="abc123" qty="1" price="0" catid="front_rail_assy">
                  <prop id="cost" val="80.679768" />
                  <prop id="extcost" val="80.679768" />
               </material>
            </assembly>
            <assembly seriesid="Flatbed_A" id="step20" name="Step20" qty="1" price="0" catid="step20">
               <prop id="hostoptionid" val="Step20" />
               <prop id="bomuom_from_byod" val="UN / EA" />
               <operation seriesid="Flatbed_A" id="510-120" qty="1" price="0" catid="o_510_120">
                  <prop id="hostoptionid" val="510-120" />
                  <prop id="processtimestd" val="14" />
               </operation>
               <material seriesid="Flatbed_A" id="def123" qty="1" price="0" catid="flooralu_assy">
                  <prop id="cost" val="3629.53568" />
                  <prop id="extcost" val="3629.53568" />
               </material>
               <material seriesid="Flatbed_A" id="stu123" qty="1" price="0" catid="rear_bumper_assy">
                  <prop id="cost" val="773.383414" />
                  <prop id="extcost" val="773.383414" />
               </material>
               <material seriesid="Flatbed_A" id="abc123" qty="1" price="0" catid="rear_bumper_assy">
                  <prop id="cost" val="773.383414" />
                  <prop id="extcost" val="773.383414" />
               </material>
            </assembly>
         </assembly>
      </assembly>
   </assembly>
</mpcbom>

到目前为止一切顺利,但同样,尚未尝试进行排序。现在这里是与上面相同的 XSLT,但添加了我的排序尝试:

<?xml version="1.0" encoding="utf-8"?>
<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" encoding="UTF-8" omit-xml-declaration="yes" indent="no" version="1.0" standalone="yes" />
   <xsl:variable name="lowercase">abcdefghijklmnopqrstuvwxyz</xsl:variable>
   <xsl:variable name="uppercase">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>

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

   <xsl:template match="propDefn" />

   <xsl:template match="material|operation">
      <xsl:copy>
         <xsl:apply-templates select="@*" />
         <xsl:apply-templates select="prop">
            <xsl:sort select="translate(@id, $uppercase, $lowercase)" />
         </xsl:apply-templates>
         <xsl:apply-templates select="*[not(self::prop)]" />
      </xsl:copy>
   </xsl:template>

   <xsl:template match="assembly">
    <xsl:copy>
      <xsl:apply-templates select="assembly">
        <xsl:sort select="material/@id"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
  
   <xsl:template match="@rid" />
   <xsl:template match="material/@name|material/@name|operation/@name" />
   <xsl:template match="@id|@catid">
      <xsl:attribute name="{local-name()}">
         <xsl:value-of select="translate(., $uppercase, $lowercase)" />
      </xsl:attribute>
   </xsl:template>
</xsl:stylesheet>

为清楚起见,这是我添加的部分(尝试排序):

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

这是转换的灾难性结果(与上面相同的 xml 来源):

<?xml version="1.0" encoding="UTF-8"?>
<mpcbom xmlns:bv="urn:BomViewXmlUtil" xmlns:msxsl="urn:schemas-microsoft-com:xslt" seriesid="Flatbed_A" seriesdesc="Flatbed A" modelid="FB2" modeldesc="Flatbed Aluminum 123" pricelistid="0">
   <assembly>
      <assembly>
         <assembly>
            <assembly />
            <assembly />
         </assembly>
         <assembly />
      </assembly>
   </assembly>
</mpcbom>

显然我是 XSLT 的新手...但这就是我想要实现的目标:每当 <assembly> 节点内有 <material> 个节点时,我想要所有 <material> 个节点按其 id 属性以字母数字方式排序。比如从源码中取这组节点xml:

<material seriesid="Flatbed_A" id="XYZ123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
   <prop id="Cost" val="80.679768" />
   <prop id="ExtCost" val="80.679768" />
</material>
<material seriesid="Flatbed_A" id="FB2C53XXXA" name="53ft 2 axles Flatbed" qty="1" price="0" catid="Structure_Assy">
   <prop id="Cost" val="12901.27129" />
   <prop id="ExtCost" val="12901.27129" />
</material>
<material seriesid="Flatbed_A" id="ABC123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
   <prop id="Cost" val="80.679768" />
   <prop id="ExtCost" val="80.679768" />
</material>

注意 3 个 <material> 节点的 idXYZ123FB2C53XXXAABC123。我需要按 id 排序的那些,以便转换后的 xml(仅针对该部分)看起来像这样:

<material seriesid="Flatbed_A" id="ABC123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
   <prop id="Cost" val="80.679768" />
   <prop id="ExtCost" val="80.679768" />
</material>
<material seriesid="Flatbed_A" id="FB2C53XXXA" name="53ft 2 axles Flatbed" qty="1" price="0" catid="Structure_Assy">
   <prop id="Cost" val="12901.27129" />
   <prop id="ExtCost" val="12901.27129" />
</material>
<material seriesid="Flatbed_A" id="XYZ123" name="Front rail" qty="1" price="0" catid="Front_Rail_assy">
   <prop id="Cost" val="80.679768" />
   <prop id="ExtCost" val="80.679768" />
</material>

第二组 <material> 节点也是如此。原文:

<material seriesid="Flatbed_A" id="DEF123" name="PLANCHER 53" qty="1" price="0" catid="FloorAlu_Assy">
   <prop id="Cost" val="3629.53568" />
   <prop id="ExtCost" val="3629.53568" />
</material>
<material seriesid="Flatbed_A" id="STU123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
   <prop id="Cost" val="773.383414" />
   <prop id="ExtCost" val="773.383414" />
</material>
<material seriesid="Flatbed_A" id="ABC123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
   <prop id="Cost" val="773.383414" />
   <prop id="ExtCost" val="773.383414" />
</material>

需要排序,转换后:

<material seriesid="Flatbed_A" id="ABC123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
   <prop id="Cost" val="773.383414" />
   <prop id="ExtCost" val="773.383414" />
</material>
<material seriesid="Flatbed_A" id="DEF123" name="PLANCHER 53" qty="1" price="0" catid="FloorAlu_Assy">
   <prop id="Cost" val="3629.53568" />
   <prop id="ExtCost" val="3629.53568" />
</material>
<material seriesid="Flatbed_A" id="STU123" name="6-4in" qty="1" price="0" catid="Rear_Bumper_Assy">
   <prop id="Cost" val="773.383414" />
   <prop id="ExtCost" val="773.383414" />
</material>

怎么做到的?

您添加的模板的问题在于它仅将模板应用于子 assembly 元素 - 因此删除了父 assembly 可能具有的所有其他子节点(及其后代)。

另一件事是您正在对 assembly 元素进行排序 - 但随后您解释说您实际上想要对 material 元素进行排序。

现在,由于要排序的 material 个元素还有其他兄弟元素,例如 propoperationassembly,这些兄弟元素也有一个id 属性,你基本上有两个选择:

  1. 对所有模板应用模板,但按只有 material 具有的节点排序:

    <xsl:template match="assembly">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()">
                <xsl:sort select="@id[parent::material]"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    

    但是,这将始终将 material 组放在末尾, 即使原版中没有。

  2. 分别对每个子节点(或子节点组)应用模板 节点)并仅对 material 元素组进行排序 - 例如

    <xsl:template match="assembly">
        <xsl:copy>
            <xsl:apply-templates select="@* | prop | operation | assembly"/>
            <xsl:apply-templates select="material">
                <xsl:sort select="@id"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    

    在这里您可以使用单独的 xsl:apply-templates 每组说明,按照你的顺序 想要它们。