【问题标题】:Tranform XML to JSON using XSLT and remove a specific pair name in the JSON使用 XSLT 将 XML 转换为 JSON 并删除 JSON 中的特定对名称
【发布时间】:2021-02-20 10:21:59
【问题描述】:

我有一个需要转换为 JSON 的 xml,并且正在使用 XSLT 对其进行转换。在其中一个元素中不需要传递对名称,只需传递值。见下文

我的 XML:

    <?xml version="1.0" encoding="UTF-8"?>
<map xmlns="http://www.w3.org/2005/xpath-functions">
    <array key="cars">
        <map>
            <string key="doors">4</string>
            <string key="price">6L</string>
        </map>
        <map>
            <string key="doors">5</string>
            <string key="price">13L</string>
        </map>
    </array>
</map>

使用 XSL:https://xsltfiddle.liberty-development.net/b4GWVd

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="3.0">
  <xsl:output omit-xml-declaration="yes"/>
  <xsl:template match="/">
      <xsl:value-of select="translate(xml-to-json(., map { 'indent' : true() }),'Ø', 'ø')"/>
  </xsl:template>
</xsl:stylesheet>

给出输出:哪个是正确的

  { "cars" : 
    [ 
      { "doors" : "4",
        "price" : "6L" },
      
      { "doors" : "5",
        "price" : "13L" } ] }

但是我想得到一个具有以下结构的 JSON,没有对名称(原因:它需要提交给 API 的结构)

  { "cars" : 
    [ 
      {  "4",
         "6L" },
      
      { "5",
        "13L" } ] }

【问题讨论】:

  • 您所说的“JSON”结果不是 JSON,请尝试在 jsonlint.com 上验证它。如果这是您的 API 真正期望的格式,那么它肯定不会期望 JSON,并且期望像 xml-to-json 这样的工具或函数可用于生成该格式是没有任何意义的。

标签: json xml xslt


【解决方案1】:

由于 XSLT 3.0 规范还提供了 xml-to-json 作为 XSLT 3.0 包的实现,因此您可以使用该代码并在您想要消除 JSON 对象/XDM 映射项的“键”的地方覆盖模板:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:j="http://www.w3.org/2013/XSLT/xml-to-json"
    xmlns:fn="http://www.w3.org/2005/xpath-functions"
    exclude-result-prefixes="#all"
    version="3.0">
    
    <xsl:use-package name="http://www.w3.org/2013/XSLT/xml-to-json" package-version="1.0">
        <xsl:override>
            <!-- Template rule for fn:map elements, representing JSON objects , overridden for content of cars array -->
            
            <xsl:template match="fn:array[@key = 'cars']/fn:map" mode="indent">
                <xsl:value-of>
                    <xsl:variable name="depth" select="count(ancestor::*) + 1"/>
                    <xsl:text>{</xsl:text>
                    <xsl:for-each select="*">
                        <xsl:if test="position() gt 1">
                            <xsl:text>, </xsl:text>
                            <xsl:value-of select="j:indent($depth)"/>
                        </xsl:if>
                        <!--<xsl:apply-templates select="snapshot(@key)" mode="key-attribute"/>
                        <xsl:text> : </xsl:text>-->
                        <xsl:apply-templates select="." mode="#current"/>
                    </xsl:for-each>
                    <xsl:text>}</xsl:text>
                </xsl:value-of>
            </xsl:template>
            
            <xsl:template match="fn:array[@key = 'cars']/fn:map/*" mode="no-indent">
                <xsl:value-of>
                    <xsl:text>{</xsl:text>
                    <xsl:for-each select="*">
                        <xsl:if test="position() gt 1">
                            <xsl:text>,</xsl:text>
                        </xsl:if>
                        <!--<xsl:apply-templates select="snapshot(@key)" mode="key-attribute"/>
                        <xsl:text>:</xsl:text>-->
                        <xsl:apply-templates select="." mode="#current"/>
                    </xsl:for-each>
                    <xsl:text>}</xsl:text>
                </xsl:value-of>
            </xsl:template>
        </xsl:override>
    </xsl:use-package>  
    
    <xsl:output method="text"/>
    
    <xsl:template match="/">
        <xsl:value-of select="j:xml-to-json(., map { 'indent' : true() })"/>
    </xsl:template>
    
</xsl:stylesheet>

以上内容可以在 Saxon 10 HE 及更高版本或 Saxon 9.8 PE 或 EE 及更高版本上运行,方法是使用命令行-s:your-xml.xml -xsl:above-xslt-xsl -lib:w3c-xml-to-json.xsl,其中最后一个选项是指从 XSLT 3 规范链接的文件https://www.w3.org/TR/xslt-30/xml-to-json.xsl,带有一个路径/errata 添加,即用&lt;xsl:mode name="j:xml-to-json"/&gt; 声明默认模式,所以它看起来像

<xsl:package
    name="http://www.w3.org/2013/XSLT/xml-to-json"
    package-version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:fn="http://www.w3.org/2005/xpath-functions"
    xmlns:j="http://www.w3.org/2013/XSLT/xml-to-json"
    exclude-result-prefixes="xs fn j" default-mode="j:xml-to-json" version="3.0">
    
    <xsl:variable name="quot" visibility="private">"</xsl:variable>
    <xsl:param name="indent-spaces" select="2"/>
    
    <!-- The static parameter STREAMABLE controls whether the stylesheet is declared as streamable -->
    
    <xsl:param name="STREAMABLE" static="yes" as="xs:boolean" select="true()"/>
    
    <!-- fix for https://github.com/w3c/qtspecs/blob/master/errata/xslt-30/errata.xml#L1154 -->
    <xsl:mode name="j:xml-to-json"/>
    
    <xsl:mode name="indent" _streamable="{$STREAMABLE}" visibility="public"/>
    <xsl:mode name="no-indent" _streamable="{$STREAMABLE}" visibility="public"/>
    <xsl:mode name="key-attribute" streamable="false" on-no-match="fail" visibility="public"/>
    
    <!-- The static parameter VALIDATE controls whether the input, if untyped, should be validated -->
    
    <xsl:param name="VALIDATE" static="yes" as="xs:boolean" select="false()"/>
    <xsl:import-schema namespace="http://www.w3.org/2005/xpath-functions" use-when="$VALIDATE"/>
    
    <!-- Entry point: function to convert a supplied XML node to a JSON string -->
    <xsl:function name="j:xml-to-json" as="xs:string" visibility="public">
        <xsl:param name="input" as="node()"/>
        <xsl:sequence select="j:xml-to-json($input, map{})"/>
    </xsl:function>
    
    <!-- Entry point: function to convert a supplied XML node to a JSON string, supplying options -->
    <xsl:function name="j:xml-to-json" as="xs:string" visibility="public">
        <xsl:param name="input" as="node()"/>
        <xsl:param name="options" as="map(*)"/>
        <xsl:variable name="input" as="node()" use-when="$VALIDATE">
            <xsl:copy-of select="$input" validation="strict"/>
        </xsl:variable>
        <xsl:choose>
            <xsl:when test="$options('indent') eq true()">
                <xsl:apply-templates select="$input" mode="indent">
                    <xsl:with-param name="fallback" as="(function(element()) as xs:string)?"
                        select="$options('fallback')" tunnel="yes"/>
                </xsl:apply-templates>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates select="$input" mode="no-indent">
                    <xsl:with-param name="fallback" as="(function(element()) as xs:string)?"
                        select="$options('fallback')" tunnel="yes"/>
                </xsl:apply-templates>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>
    
    <!-- A document node is ignored -->
    
    <xsl:template match="/" mode="indent no-indent">
        <xsl:apply-templates mode="#current"/>
    </xsl:template>
    
    <!-- Template rule for fn:map elements, representing JSON objects -->
    
    <xsl:template match="fn:map" mode="indent">
        <xsl:value-of>
            <xsl:variable name="depth" select="count(ancestor::*) + 1"/>
            <xsl:text>{</xsl:text>
            <xsl:for-each select="*">
                <xsl:if test="position() gt 1">
                    <xsl:text>, </xsl:text>
                    <xsl:value-of select="j:indent($depth)"/>
                </xsl:if>
                <xsl:apply-templates select="snapshot(@key)" mode="key-attribute"/>
                <xsl:text> : </xsl:text>
                <xsl:apply-templates select="." mode="#current"/>
            </xsl:for-each>
            <xsl:text>}</xsl:text>
        </xsl:value-of>
    </xsl:template>
    
    <xsl:template match="fn:map" mode="no-indent">
        <xsl:value-of>
            <xsl:text>{</xsl:text>
            <xsl:for-each select="*">
                <xsl:if test="position() gt 1">
                    <xsl:text>,</xsl:text>
                </xsl:if>
                <xsl:apply-templates select="snapshot(@key)" mode="key-attribute"/>
                <xsl:text>:</xsl:text>
                <xsl:apply-templates select="." mode="#current"/>
            </xsl:for-each>
            <xsl:text>}</xsl:text>
        </xsl:value-of>
    </xsl:template>
    
    <!-- Template rule for fn:array elements, representing JSON arrays -->
    <xsl:template match="fn:array" mode="indent">
        <xsl:value-of>
            <xsl:variable name="depth" select="count(ancestor::*) + 1"/>
            <xsl:text>[</xsl:text>
            <xsl:for-each select="*">
                <xsl:if test="position() gt 1">
                    <xsl:text>, </xsl:text>
                    <xsl:value-of select="j:indent($depth)"/>
                </xsl:if>
                <xsl:apply-templates select="." mode="#current"/>
            </xsl:for-each>
            <xsl:text>]</xsl:text>
        </xsl:value-of>
    </xsl:template>
    
    <xsl:template match="fn:array" mode="no-indent">
        <xsl:value-of>
            <xsl:text>[</xsl:text>
            <xsl:for-each select="*">
                <xsl:if test="position() gt 1">
                    <xsl:text>,</xsl:text>
                </xsl:if>
                <xsl:apply-templates select="." mode="#current"/>
            </xsl:for-each>
            <xsl:text>]</xsl:text>
        </xsl:value-of>
    </xsl:template>
    
    <!-- Template rule for fn:string elements in which 
         special characters are already escaped -->
    <xsl:template match="fn:string[@escaped='true']" mode="indent no-indent">
        <xsl:sequence select="concat($quot, ., $quot)"/>
    </xsl:template>
    
    <!-- Template rule for fn:string elements in which 
         special characters need to be escaped -->
    <xsl:template match="fn:string[not(@escaped='true')]" mode="indent no-indent">
        <xsl:sequence select="concat($quot, j:escape(.), $quot)"/>
    </xsl:template>
    
    <!-- Template rule for fn:boolean elements -->
    <xsl:template match="fn:boolean" mode="indent no-indent">
        <xsl:sequence select="xs:string(xs:boolean(.))"/>
    </xsl:template>
    
    <!-- Template rule for fn:number elements -->
    <xsl:template match="fn:number" mode="indent no-indent">
        <xsl:value-of select="xs:string(xs:double(.))"/>
    </xsl:template>
    
    <!-- Template rule for JSON null elements -->
    <xsl:template match="fn:null" mode="indent no-indent">
        <xsl:text>null</xsl:text>
    </xsl:template>
    
    <!-- Template rule matching a key within a map where 
         special characters in the key are already escaped -->
    <xsl:template match="fn:*[@key-escaped='true']/@key" mode="key-attribute">
        <xsl:value-of select="concat($quot, ., $quot)"/>
    </xsl:template>
    
    <!-- Template rule matching a key within a map where 
         special characters in the key need to be escaped -->
    <xsl:template match="fn:*[not(@key-escaped='true')]/@key" mode="key-attribute">
        <xsl:value-of select="concat($quot, j:escape(.), $quot)"/>
    </xsl:template>
    
    <!-- Template matching "invalid" elements -->
    <xsl:template match="*" mode="indent no-indent">
        <xsl:param name="fallback" as="(function(element()) as xs:string)?"
            tunnel="yes" required="yes"/>
        <xsl:choose>
            <xsl:when test="exists($fallback)">
                <xsl:value-of select="$fallback(snapshot(.))"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:message terminate="yes">>Inc</xsl:message>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
    <!-- Template rule matching (and discarding) whitespace text nodes in the XML -->
    <xsl:template match="text()[not(normalize-space())]" mode="indent no-indent"/>
    
    <!-- Function to escape special characters -->
    <xsl:function name="j:escape" as="xs:string" visibility="final">
        <xsl:param name="in" as="xs:string"/>
        <xsl:value-of>
            <xsl:for-each select="string-to-codepoints($in)">
                <xsl:choose>
                    <xsl:when test=". gt 65535">
                        <xsl:value-of select="concat('\u', j:hex4((. - 65536) idiv 1024 + 55296))"/>
                        <xsl:value-of select="concat('\u', j:hex4((. - 65536) mod 1024 + 56320))"/>
                    </xsl:when>
                    <xsl:when test=". = 34">\"</xsl:when>
                    <xsl:when test=". = 92">\\</xsl:when>
                    <xsl:when test=". = 08">\b</xsl:when>
                    <xsl:when test=". = 09">\t</xsl:when>
                    <xsl:when test=". = 10">\n</xsl:when>
                    <xsl:when test=". = 12">\f</xsl:when>
                    <xsl:when test=". = 13">\r</xsl:when>
                    <xsl:when test=". lt 32 or (. ge 127 and . le 160)">
                        <xsl:value-of select="concat('\u', j:hex4(.))"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="codepoints-to-string(.)"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each>
        </xsl:value-of>
    </xsl:function>
    
    <!-- Function to convert a UTF16 codepoint into a string of four hex digits -->
    <xsl:function name="j:hex4" as="xs:string" visibility="final">
        <xsl:param name="ch" as="xs:integer"/>
        <xsl:variable name="hex" select="'0123456789abcdef'"/>
        <xsl:value-of>
            <xsl:value-of select="substring($hex, $ch idiv 4096 + 1, 1)"/>
            <xsl:value-of select="substring($hex, $ch idiv 256 mod 16 + 1, 1)"/>
            <xsl:value-of select="substring($hex, $ch idiv 16 mod 16 + 1, 1)"/>
            <xsl:value-of select="substring($hex, $ch mod 16 + 1, 1)"/>
        </xsl:value-of>
    </xsl:function>
    
    <!-- Function to output whitespace indentation based on 
         the depth of the node supplied as a parameter -->
    
    <xsl:function name="j:indent" as="text()" visibility="public">
        <xsl:param name="depth" as="xs:integer"/>
        <xsl:value-of select="'&#xa;', string-join((1 to ($depth + 1) * $indent-spaces) ! ' ', '')"/>
    </xsl:function>
    
</xsl:package>

要使用 Java 代码运行代码,您基本上需要使用从 Processor 创建的 XsltCompiler,例如

   Processor processor = new Processor(true);

   XsltCompiler xsltCompiler = processor.newXsltCompiler();

   XsltPackage xmlToJsonPackage = xsltCompiler.compilePackage(new StreamSource("w3c-xml-to-json.xsl"));

   xsltCompiler.importPackage(xmlToJsonPackage);

   XsltExecutable xsltExecutable = xsltCompiler.compile(new StreamSource("sheet.xsl"));

   Xslt30Transformer xslt30Transformer = xsltExecutable.load30();

   // now run stylesheet with e.g. transform() or applyTemplates()
   xslt30Transformer.transform(new StreamSource("input.xml"), xslt30Transformer.newSerializer(System.out));

【讨论】:

  • 谢谢马丁。这看起来很有希望,但出现错误:找不到包w3.org/2013/XSLT/xml-to-json(1.0 版)
  • 正如我所说,您需要下载文件w3.org/TR/xslt-30/xml-to-json.xsl,对其进行修补以包含声明&lt;xsl:mode name="j:xml-to-json"/&gt;,并在命令行中使用-lib 选项命名其文件名/位置。由于代码使用高阶函数,您需要 Saxon HE 10 或 Saxon 9.8 或更高版本的商业版本。
  • 刚开始使用 Saxon 技术,正在挖掘更多关于它的信息,以及如何修补它。我一直在使用 Java TransformFactory 使用 xsl 将 xml 转换为 json。
  • 您必须修补的不是 Saxon,而是 XSLT 3.0 规范中的样式表包。至于使用来自 Java 的包,我建议使用 s9api,而不是 JAXP。如果遇到困难,请参阅 Saxon 文档并提出新问题。
  • 您好,您可以在此处查看有关 s9api 的文档:saxonica.com/documentation/index.html#!javadoc/…
猜你喜欢
  • 2014-06-09
  • 1970-01-01
  • 2020-01-26
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-28
相关资源
最近更新 更多