使用匹配特定选择器的 XSL 对 XML 进行排序并保持 XML 相同

Posted

技术标签:

【中文标题】使用匹配特定选择器的 XSL 对 XML 进行排序并保持 XML 相同【英文标题】:Sort XML with XSL matching specific selectors and keep XML the same 【发布时间】:2021-12-02 05:25:28 【问题描述】:

我正在尝试使用 XSLT 1.0 对我的 XML 进行仅排序转换。除了 order/sequence 之外,我不需要对转换后的 XML 进行任何更改。

我创建了一个精简版的 XML,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<mpcconfiguration>
   <lineitem id="0">
      <seriesdesc>series1</seriesdesc>
      <modeldesc>model1</modeldesc>
      <labels>
         <label id="ExtPrice">Extended Price</label>
      </labels>
      <category id="Mstr_Information">
         <description>Model Information</description>
         <option id="Mstr_Information">
            <description>descr1</description>
            <unitprice>0</unitprice>
            <property id="ExtPrice">0</property>
            <property id="Mstr_ModelSortOrder">3</property>
         </option>
      </category>
      <category id="Category1">
         <description>a cool category</description>
         <option id="option123">
            <description>a cool option</description>
            <unitprice>0</unitprice>
            <property id="Mstr_ModelSortOrder">777</property>
         </option>
      </category>
   </lineitem>
   <lineitem id="1">
      <seriesdesc>series2</seriesdesc>
      <modeldesc>model2</modeldesc>
      <labels>
         <label id="ExtPrice">Extended Price</label>
      </labels>
      <category id="Mstr_Information">
         <description>Model Information</description>
         <option id="Mstr_Information">
            <description>descr1</description>
            <unitprice>0</unitprice>
            <property id="ExtPrice">0</property>
            <property id="Mstr_ModelSortOrder">1</property>
         </option>
      </category>
      <category id="Category2">
         <description>a cool category</description>
         <option id="option123">
            <description>a cool option</description>
            <unitprice>0</unitprice>
            <property id="Mstr_ModelSortOrder">999</property>
         </option>
      </category>
   </lineitem>
   <lineitem id="2">
      <seriesdesc>series3</seriesdesc>
      <modeldesc>model3</modeldesc>
      <labels>
         <label id="ExtPrice">Extended Price</label>
      </labels>
      <category id="Mstr_Information">
         <description>Model Information</description>
         <option id="Mstr_Information">
            <description>descr1</description>
            <unitprice>0</unitprice>
            <property id="ExtPrice">0</property>
            <property id="Mstr_ModelSortOrder">2</property>
         </option>
      </category>
      <category id="Category3">
         <description>a cool category</description>
         <option id="option123">
            <description>a cool option</description>
            <unitprice>0</unitprice>
            <property id="Mstr_ModelSortOrder">555</property>
         </option>
      </category>
   </lineitem>
</mpcconfiguration>

以下是需要关注的重要方面:

    根元素将始终为mpcconfiguration。 我需要在mpcconfiguration 下方对&lt;lineitem&gt; 元素进行相对排序。 排序序列应该由/mpcconfiguration/lineitem/category@id=Mstr_Information/option@id=Mstr_Information/property@id=Mstr_ModelSortOrder 的值驱动(该伪代码用简单的英语表示:“按&lt;property&gt; 的值排序,其idMstr_ModelSortOrder,其父级是@987654330 @ id 为Mstr_Information,其父级为&lt;category&gt;,id 为Mstr_Information,其父级为&lt;lineitem&gt;") 注意&lt;property 元素的值,例如 555、777 和 999。出于排序目的,可以忽略这些元素,因为它们的祖先与我在 #3 中描述的模式不匹配。所有这些数据仍然必须在转换后的 XML 中,但这些与排序无关。 每个&lt;lineitem&gt; 将只有一个&lt;property id="Mstr_ModelSortOrder"&gt;XXX&lt;/property&gt;,其祖先与上面#3 中描述的模式匹配。

如果我尝试解决的 XSL 行为正确,这是所需/转换后的 XML:

<?xml version="1.0" encoding="UTF-8"?>
<mpcconfiguration>
   <lineitem id="1">
      <seriesdesc>series2</seriesdesc>
      <modeldesc>model2</modeldesc>
      <labels>
         <label id="ExtPrice">Extended Price</label>
      </labels>
      <category id="Mstr_Information">
         <description>Model Information</description>
         <option id="Mstr_Information">
            <description>descr1</description>
            <unitprice>0</unitprice>
            <property id="ExtPrice">0</property>
            <property id="Mstr_ModelSortOrder">1</property>
         </option>
      </category>
      <category id="Category2">
         <description>a cool category</description>
         <option id="option123">
            <description>a cool option</description>
            <unitprice>0</unitprice>
            <property id="Mstr_ModelSortOrder">999</property>
         </option>
      </category>
   </lineitem>
   <lineitem id="2">
      <seriesdesc>series3</seriesdesc>
      <modeldesc>model3</modeldesc>
      <labels>
         <label id="ExtPrice">Extended Price</label>
      </labels>
      <category id="Mstr_Information">
         <description>Model Information</description>
         <option id="Mstr_Information">
            <description>descr1</description>
            <unitprice>0</unitprice>
            <property id="ExtPrice">0</property>
            <property id="Mstr_ModelSortOrder">2</property>
         </option>
      </category>
      <category id="Category3">
         <description>a cool category</description>
         <option id="option123">
            <description>a cool option</description>
            <unitprice>0</unitprice>
            <property id="Mstr_ModelSortOrder">555</property>
         </option>
      </category>
   </lineitem>
   <lineitem id="0">
      <seriesdesc>series1</seriesdesc>
      <modeldesc>model1</modeldesc>
      <labels>
         <label id="ExtPrice">Extended Price</label>
      </labels>
      <category id="Mstr_Information">
         <description>Model Information</description>
         <option id="Mstr_Information">
            <description>descr1</description>
            <unitprice>0</unitprice>
            <property id="ExtPrice">0</property>
            <property id="Mstr_ModelSortOrder">3</property>
         </option>
      </category>
      <category id="Category1">
         <description>a cool category</description>
         <option id="option123">
            <description>a cool option</description>
            <unitprice>0</unitprice>
            <property id="Mstr_ModelSortOrder">777</property>
         </option>
      </category>
   </lineitem>
</mpcconfiguration>

请注意,这 2 个 xml 示例是相同的,只是 &lt;lineitem&gt; 节点的顺序不同,排序方式为:

<property id="Mstr_ModelSortOrder">1</property>
<property id="Mstr_ModelSortOrder">2</property>
<property id="Mstr_ModelSortOrder">3</property>

这是我对 xsl 的微弱尝试,虽然它不正确:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <xsl:output method="xml" encoding="utf-8" indent="no" />
   <xsl:template match="/">
      <xsl:copy-of select="*" />
   </xsl:template>
   
   <xsl:template match="mpcconfiguration">
    <xsl:copy>
        <xsl:apply-templates select="//mpcconfiguration/category/option/property">
            <xsl:sort select="@id"/>
        </xsl:apply-templates>
    </xsl:copy>
   </xsl:template>
   
</xsl:stylesheet>

我知道上面有相当多的 XML 和 XSL,但总结非常简单:按 Mstr_ModelSortOrder XML &lt;property&gt; 对所有 &lt;lineitem&gt; 节点进行排序,只要该属性在 XML 中有正确的祖先树。

【问题讨论】:

【参考方案1】:

这个 XSLT 1.0 转换符合您的描述

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

   <xsl:template match="node() | @*">
      <xsl:copy>
        <xsl:apply-templates select="node() | @*" />
      </xsl:copy>
   </xsl:template>
   
   <xsl:template match="mpcconfiguration">
      <xsl:copy>
        <xsl:apply-templates select="@*" />
        <xsl:apply-templates select="lineitem">
          <xsl:sort select="category[@id='Mstr_Information']/option[@id='Mstr_Information']/property[@id='Mstr_ModelSortOrder']" data-type="number" />
        </xsl:apply-templates>
      </xsl:copy>
   </xsl:template>
 </xsl:stylesheet>

输出:

<?xml version="1.0" encoding="utf-8"?>
<mpcconfiguration>
   <lineitem id="1">
      <seriesdesc>series2</seriesdesc>
      <modeldesc>model2</modeldesc>
      <labels>
         <label id="ExtPrice">Extended Price</label>
      </labels>
      <category id="Mstr_Information">
         <description>Model Information</description>
         <option id="Mstr_Information">
            <description>descr1</description>
            <unitprice>0</unitprice>
            <property id="ExtPrice">0</property>
            <property id="Mstr_ModelSortOrder">1</property>
         </option>
      </category>
      <category id="Category2">
         <description>a cool category</description>
         <option id="option123">
            <description>a cool option</description>
            <unitprice>0</unitprice>
            <property id="Mstr_ModelSortOrder">999</property>
         </option>
      </category>
   </lineitem>
   <lineitem id="2">
      <seriesdesc>series3</seriesdesc>
      <modeldesc>model3</modeldesc>
      <labels>
         <label id="ExtPrice">Extended Price</label>
      </labels>
      <category id="Mstr_Information">
         <description>Model Information</description>
         <option id="Mstr_Information">
            <description>descr1</description>
            <unitprice>0</unitprice>
            <property id="ExtPrice">0</property>
            <property id="Mstr_ModelSortOrder">2</property>
         </option>
      </category>
      <category id="Category3">
         <description>a cool category</description>
         <option id="option123">
            <description>a cool option</description>
            <unitprice>0</unitprice>
            <property id="Mstr_ModelSortOrder">555</property>
         </option>
      </category>
   </lineitem>
   <lineitem id="0">
      <seriesdesc>series1</seriesdesc>
      <modeldesc>model1</modeldesc>
      <labels>
         <label id="ExtPrice">Extended Price</label>
      </labels>
      <category id="Mstr_Information">
         <description>Model Information</description>
         <option id="Mstr_Information">
            <description>descr1</description>
            <unitprice>0</unitprice>
            <property id="ExtPrice">0</property>
            <property id="Mstr_ModelSortOrder">3</property>
         </option>
      </category>
      <category id="Category1">
         <description>a cool category</description>
         <option id="option123">
            <description>a cool option</description>
            <unitprice>0</unitprice>
            <property id="Mstr_ModelSortOrder">777</property>
         </option>
      </category>
   </lineitem>
</mpcconfiguration>

模板#1 是身份模板。它匹配任何不匹配更具体模板的节点,并将其逐字复制到输出。

模板 #2 是唯一更具体的模板 - 它只匹配 &lt;mpcconfiguration&gt;,复制它,并为任何属性节点 @* 调用匹配模板(您的输入样本中恰好没有)和任何&lt;lineitem&gt; 孩子,按他们各自的 &lt;property id="Mstr_ModelSortOrder"&gt; 排序。这些节点的唯一匹配模板是身份模板,它会按原样完成工作并复制它们。

&lt;xsl:strip-space elements="*" /&gt; 是为了方便,用&lt;xsl:output indent="yes" /&gt; 实现漂亮的输出。


较短的版本假定 &lt;mpcconfiguration&gt; 是***元素,并使用 &lt;xsl:for-each&gt; 复制 &lt;lineitem&gt; 子元素:

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

   <xsl:template match="/mpcconfiguration">
      <xsl:copy>
        <xsl:for-each select="lineitem">
          <xsl:sort select="category[@id='Mstr_Information']/option[@id='Mstr_Information']/property[@id='Mstr_ModelSortOrder']" data-type="number" />
          <xsl:copy-of select="." />
        </xsl:for-each>
      </xsl:copy>
   </xsl:template>
   
</xsl:stylesheet>

【讨论】:

Tomalak,感谢您的出色回答。效果很好。 Tomalak:我希望您可以在这里查看我的后续问题:***.com/questions/69563180/… - 这与原来的问题有很大不同,我认为最好提出一个新问题。提前致谢。

以上是关于使用匹配特定选择器的 XSL 对 XML 进行排序并保持 XML 相同的主要内容,如果未能解决你的问题,请参考以下文章

使用 CDATA 元素对 xml 中的 uuid 进行排序的 xslt 模板

如何使用 XSL 选择特定元素节点中的所有文本节点?

XSL1.0:在重复中选择特定元素

Java+XSL合并多个XML文件

使用 XSLT 对子元素进行排序

XSL xsl:template match =“/”