使用 XSL 样式表 Java Transformer Factory 对 XMLTag 进行排序

Posted

技术标签:

【中文标题】使用 XSL 样式表 Java Transformer Factory 对 XMLTag 进行排序【英文标题】:Sorting XMLTag using XSL Stylesheet Java Transformer Factory 【发布时间】:2021-06-10 13:50:52 【问题描述】:

我有下面的 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/xsltindent-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);

【问题讨论】:

JAXP 是一个 JRE/JDK API,它一方面具有内置处理器仅支持 XSLT 1,但另一方面在 Java 世界中可以轻松地与 Saxon 10 或 9 一起使用以具有XSLT 2 或 3 支持。你能把例如Saxon 10 HE 在课堂路径上? 是的,我可以将 saxon 放在类路径中。请提供版本#。 Saxon HE 10.3 是当前版本。 Saxon 10 HE 在 Sourceforge sourceforge.net/projects/saxon/files/Saxon-HE/10/Java 或 Maven 上可用。 【参考方案1】:

在具有高阶函数支持的 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>

【讨论】:

感谢发帖。我更改了代码以使用 SAXON transofrmer 工厂并添加了此 XSL 样式表,但我遇到了一堆错误。 TransformerFactorytransformerFactory = TransformerFactory.newInstance("net.sf.saxon.TransformerFactoryImpl",null);xsl:param 在 profile.xsl 的第 14 行第 18 列出错: XPST0003: XPath 语法错误在第 14 行的字符 0 map QName:节点构造函数表达式只允许在 XQuery 中使用,而不是在 XPath 错误中 xsl:param on line 14 column 18 of profile.xsl: @darkest,我无法解释该错误消息,除非它来自早于 XSLT 3 的 Saxon 版本,例如 Saxon 9.7 或更早版本。无论如何,我为基于元素名称的排序添加了一个不太灵活但更兼容的替代方案。 我正在使用 saxon 9.1.8 但仍然出现错误。我尝试使用您发布的早期版本,但出现了不同的错误:profile.xsl 的第 13 行第 65 列 xsl:for-each-group 处出错:XPST0017:node-name( ): 函数 node-name() 必须有 1 个参数错误在 xsl:sort on line 14 column 70 of profile.xsl: 我发布了 XSLT 3 代码,假设至少 Saxon 9.8。对于像 9.1 这样的旧版本,我认为只有 XSLT 2 支持您需要进行两项更改,即 group-by="node-name(.)" 而不是 group-by="node-name()",然后是 select="string(current-grouping-key())" 而不是 select="current-grouping-key() =&gt; string()" 您需要删除xsl:mode 声明,而是保留您发布的代码中的第一个xsl:template

以上是关于使用 XSL 样式表 Java Transformer Factory 对 XMLTag 进行排序的主要内容,如果未能解决你的问题,请参考以下文章

java xslt 转换本地名称

使用 XSL 样式表 Java Transformer Factory 对 XMLTag 进行排序

预处理 XSL 样式表 - 包括外部文档

使用参数作为 XSL 选择语句的参数

xsl 转换在 .net 环境中生成 xsl 样式表

我需要一个简单的命令行程序来使用 XSL 样式表转换 XML [关闭]