首页 > 解决方案 > 使用 XSL 样式表 Java Transformer Factory 对 XMLTag 进行排序

问题描述

我有以下 XML 文件,我想以特定方式对其进行排序。基本上,XMLTags 将首先按字母顺序排序,然后在每个 XMLtag 中,其中一个 XML 元素也将用于按字母顺序对它们进行排序。请查看当前的 XML 和我需要的最终结果

原始 XML

    <?xml version="1.0" encoding="UTF-8"?><Profile xmlns="http://soap.sforce.com/2006/04/metadata">
<applicationVisibilities>
    <application>Test</application>
    <default>true</default>
    <visible>true</visible>
</applicationVisibilities>
<classAccesses>
    <apexClass>TestClass</apexClass>
    <enabled>false</enabled>
</classAccesses>
<applicationVisibilities>
    <application>Class</application>
    <default>true</default>
    <visible>false</visible>
</applicationVisibilities>
<classAccesses>
    <apexClass>FooClass</apexClass>
    <enabled>false</enabled>
</classAccesses>
<fieldPermissions>
    <editable>false</editable>
    <field>Hello</field>
    <readable>true</readable>
</fieldPermissions>
<applicationVisibilities>
    <application>Foo</application>
    <default>true</default>
    <visible>false</visible>
</applicationVisibilities>
<fieldPermissions>
    <editable>false</editable>
    <field>Blah</field>
    <readable>true</readable>
</fieldPermissions>
</Profile>

最终输出

    <?xml version="1.0" encoding="UTF-8"?><Profile xmlns="http://soap.sforce.com/2006/04/metadata">
<applicationVisibilities>
    <application>Class</application>
    <default>true</default>
    <visible>false</visible>
</applicationVisibilities>
<applicationVisibilities>
    <application>Foo</application>
    <default>true</default>
    <visible>false</visible>
</applicationVisibilities>
<applicationVisibilities>
    <application>Test</application>
    <default>true</default>
    <visible>true</visible>
</applicationVisibilities>
 <classAccesses>
    <apexClass>FooClass</apexClass>
    <enabled>false</enabled>
</classAccesses>
<classAccesses>
    <apexClass>TestClass</apexClass>
    <enabled>false</enabled>
</classAccesses>
<fieldPermissions>
    <editable>false</editable>
    <field>Blah</field>
    <readable>true</readable>
</fieldPermissions>
<fieldPermissions>
    <editable>false</editable>
    <field>Hello</field>
    <readable>true</readable>
</fieldPermissions>
</Profile>

对标签进行排序后,对于每个标签,我将使用特定元素对它们进行排序。示例:applicaitonVisibilities 标记将使用应用程序 xmlelement 值按字母顺序排序。对于 classAccesses,apexClass 将用于按字母顺序排序,最后对于 fieldPermissions,将使用字段元素进行排序。我正在玩弄当前的 XSL 样式表,但它不起作用。

 <?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*" />
<xsl:template match="node()|@*">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" />
    </xsl:copy>
</xsl:template>

<xsl:template match="Profile/applicationVisibilities">
    <xsl:copy>
        <xsl:apply-templates select="application">
            <xsl:sort select="node()" data-type="text" order="ascending" />
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>
</xsl:stylesheet>

我当前使用当前 XSL 的 java 代码

XSL   TransformerFactory transformerFactory = TransformerFactory.newInstance();
    Transformer transformer = transformerFactory.newTransformer(new StreamSource(new File("profile.xsl")));
    //transformerFactory.setAttribute("indent-number", 10);
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
    transformer.setOutputProperty(OutputKeys.METHOD, "xml");
    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
    DOMSource source = new DOMSource(document);
    StreamResult result = new StreamResult(new File(originalFile));
    transformer.transform(source, result);

标签: javaxmlsortingxslt

解决方案


在具有高阶函数支持的 XSLT 3(Saxon 10 或更高版本,Saxon 9.8 和 9.9 PE 和 EE,Saxon-JS 2)中,您可以对节点名称使用分组,然后根据来自选择子元素的函数的名称:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xpath-default-namespace="http://soap.sforce.com/2006/04/metadata"
    exclude-result-prefixes="#all"
    version="3.0">
    
  <xsl:param name="sort-keys"
    as="map(xs:QName, function(*))"
    select="map {
              QName('http://soap.sforce.com/2006/04/metadata', 'applicationVisibilities') : function($el) { $el/application }, 
              QName('http://soap.sforce.com/2006/04/metadata', 'classAccesses') : function($el) { $el/apexClass },
              QName('http://soap.sforce.com/2006/04/metadata', 'fieldPermissions') : function($el) { $el/field } 
            }"/>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/*">
      <xsl:copy>
          <xsl:for-each-group select="*" group-by="node-name()">
              <xsl:sort select="string(current-grouping-key())"/>
              <xsl:apply-templates select="current-group()">
                  <xsl:sort select="$sort-keys(node-name())(.)"/>
              </xsl:apply-templates>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

在早期版本中,您可以使用

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xpath-default-namespace="http://soap.sforce.com/2006/04/metadata"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/*">
      <xsl:copy>
          <xsl:for-each-group select="*" group-by="node-name()">
              <xsl:sort select="current-grouping-key() => string()"/>
              <xsl:apply-templates select="current-group()">
                  <xsl:sort select="if (. instance of element(applicationVisibilities))
                                    then application
                                    else if (. instance of element(classAccesses))
                                    then apexClass
                                    else if (. instance of element(fieldPermissions))
                                    then field
                                    else ()"/>
              </xsl:apply-templates>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

一个(希望)纯 XSLT 2 版本将是

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xpath-default-namespace="http://soap.sforce.com/2006/04/metadata"
    exclude-result-prefixes="#all"
    version="2.0">

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

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/*">
      <xsl:copy>
          <xsl:for-each-group select="*" group-by="node-name(.)">
              <xsl:sort select="string(current-grouping-key())"/>
              <xsl:apply-templates select="current-group()">
                  <xsl:sort select="if (. instance of element(applicationVisibilities))
                                    then application
                                    else if (. instance of element(classAccesses))
                                    then apexClass
                                    else if (. instance of element(fieldPermissions))
                                    then field
                                    else ()"/>
              </xsl:apply-templates>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

推荐阅读