使用 XSLT 在 XML 到 JSON 转换代码中生成数组时出现问题

问题描述 投票:0回答:1

使用通用 xslt 代码将 XML 转换为 JSON 时,未按预期生成所需的输出。以下是当前和所需输出的示例:

电流输出:

{
  "School": "St. Louis",
  "Class": "7th Grade",
  "Roll_number": "1234",
  "Subject": {
    "Mathematics": "A",
    "Physics": "A",
    "Chemistry": "A"
  }
} 

所需输出:

{
    "School": "St. Louis",
    "Class": "7th Grade",
    "Roll_number": "1234",
    "Subject": [
        {
            "Mathematics": "A",
            "Physics": "A",
            "Chemistry": "A"
        }
    ]
}

当存在多个“主题”项时,我的代码可以正常工作,但即使只出现一次,我们也需要一个数组。

具有多个主题项的输出:

{
    "School": "St. Louis",
    "Class": "7th Grade",
    "Roll_number": "1234",
    "Subject": [
        {
            "Mathematics": "A",
            "Physics": "A",
            "Chemistry": "A"
        },
        {
            "Mathematics": "A",
            "Physics": "A",
            "Chemistry": "A"
        }
    ]
}

我们有多个接口,我们在其中使用这个通用 XSLT 代码,我们如何在不破坏通用功能的情况下实现它。

我们当前的通用代码:

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:json="http://json.org/" xmlns:map="http://www.w3.org/2005/xpath-functions/map" xmlns:array="http://www.w3.org/2005/xpath-functions/array" xmlns:saxon="http://saxon.sf.net/">
    <xsl:output indent="yes" omit-xml-declaration="yes" method="xml" encoding="utf-8"/>
    <xsl:strip-space elements="*"/>
    <xsl:param name="header"/>
    <xsl:param name="valueLookup"/>
    <xsl:variable name="EnvHeader" select="parse-xml($header)"/>
    <xsl:variable name="xLU" select="if ($valueLookup!='') then parse-xml($valueLookup) else()"/>
    <xsl:variable name="cs" select="$xLU/*/dataKeys/dataKey[@name='GenericXMLtoJSON_xsl']"/>
    <xsl:variable name="debug" as="xs:boolean" select="if ($cs/row[@src='debug' and @tgt = '1']) then true() else false()"/>
    <xsl:variable name="use-rabbitfish" as="xs:boolean" select="if ($cs/row[@src='use-rabbitfish' and @tgt = '1']) then true() else false()"/>
    <xsl:variable name="use-badgerfish" as="xs:boolean" select="if ($cs/row[@src='use-badgerfish' and @tgt = '1']) then true() else false()"/>
    <xsl:variable name="use-namespaces" as="xs:boolean" select="if ($cs/row[@src='use-namespaces' and @tgt = '1']) then true() else false()"/>
    <xsl:variable name="use-rayfish" as="xs:boolean" select="if ($cs/row[@src='use-rayfish' and @tgt = '1']) then true() else false()"/>
    <xsl:variable name="jsonp" as="xs:string" select="if ($cs/row[@src='jsonp' and @tgt != '']) then $cs/row[@src='jsonp']/xs:string(@tgt) else ''"/>
    <xsl:variable name="force-string-in" as="xs:string" select="if ($cs/row[@src='force-string-in' and @tgt != '']) then $cs/row[@src='force-string-in']/xs:string(@tgt) else ''"/>
    <xsl:variable name="skip-root" as="xs:boolean" select="if ($cs/row[@src='skip-root' and @tgt = '1']) then true() else false()"/>
    <xsl:variable name="force-string" select="if ($force-string-in !='*') then tokenize($force-string-in) else $force-string-in"/>
    <xsl:template match="/*">
        <xsl:element name="jroot">
            <xsl:value-of select="json:generate(.)"/>
        </xsl:element>
    </xsl:template>
    <xsl:function name="json:generate" as="xs:string">
        <xsl:param name="input" as="node()"/>
        <xsl:variable name="json-tree">
            <json:object>
                <xsl:copy-of select="if (not($use-rayfish)) then json:create-node($input, false()) else json:create-simple-node($input)"/>
            </json:object>
        </xsl:variable>
        <xsl:variable name="json-mtree">
            <xsl:choose>
                <xsl:when test="$skip-root">
                    <xsl:copy-of select="$json-tree/json:object/json:member/json:value/child::node()"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="$json-tree"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>
        <xsl:variable name="output">
            <xsl:choose>
                <xsl:when test="normalize-space($jsonp)">
                    <xsl:value-of select="$jsonp"/>
                    <xsl:text>(</xsl:text>
                    <xsl:apply-templates select="$json-mtree" mode="json"/>
                    <xsl:text>)</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:text/>
                    <xsl:apply-templates select="$json-mtree" mode="json"/>
                    <xsl:text/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>
        <xsl:sequence select="$output"/>
    </xsl:function>
    <xsl:template name="json:build-tree">
        <xsl:param name="input" as="node()"/>
        <json:object>
            <xsl:copy-of select="if (not($use-rayfish)) then json:create-node($input, false()) else json:create-simple-node($input/child::node())"/>
        </json:object>
    </xsl:template>
    <xsl:function name="json:create-simple-node-member" as="node()">
        <xsl:param name="type" as="xs:string"/>
        <xsl:param name="value"/>
        <json:member>
            <json:name>
                <xsl:value-of select="$type"/>
            </json:name>
            <json:value>
                <xsl:copy-of select="$value"/>
            </json:value>
        </json:member>
    </xsl:function>
    <xsl:function name="json:create-simple-node" as="node()*">
        <xsl:param name="node" as="node()"/>
        <xsl:copy-of select="json:create-simple-node-member('#name', $node/local-name())"/>
        <xsl:copy-of select="json:create-simple-node-member('#text', $node/child::text())"/>
        <xsl:variable name="empty-array">
            <json:array/>
        </xsl:variable>
        <xsl:variable name="children">
            <json:array>
                <xsl:for-each select="$node/@*">
                    <json:array-value>
                        <json:value>
                            <json:object>
                                <xsl:copy-of select="json:create-simple-node-member('#name', concat('@',./local-name()))"/>
                                <xsl:copy-of select="json:create-simple-node-member('#text', string(.))"/>
                                <xsl:copy-of select="json:create-simple-node-member('#children', $empty-array)"/>
                            </json:object>
                        </json:value>
                    </json:array-value>
                </xsl:for-each>
                <xsl:for-each select="$node/child::element()">
                    <json:array-value>
                        <json:value>
                            <json:object>
                                <xsl:copy-of select="json:create-simple-node(.)"/>
                            </json:object>
                        </json:value>
                    </json:array-value>
                </xsl:for-each>
            </json:array>
        </xsl:variable>
        <xsl:copy-of select="json:create-simple-node-member('#children', $children)"/>
    </xsl:function>
    <xsl:function name="json:create-node" as="node()">
        <xsl:param name="node" as="node()"/>
        <xsl:param name="in-array" as="xs:boolean"/>
        <xsl:choose>
            <xsl:when test="$in-array">
                <json:array-value>
                    <json:value>
                        <xsl:copy-of select="json:create-children($node)"/>
                    </json:value>
                </json:array-value>
            </xsl:when>
            <xsl:otherwise>
                <json:member>
                    <xsl:copy-of select="json:create-string($node)"/>
                    <json:value>
                        <xsl:copy-of select="json:create-children($node)"/>
                    </json:value>
                </json:member>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>
    <xsl:function name="json:create-children">
        <xsl:param name="node" as="node()"/>
        <xsl:choose>
            <xsl:when test="exists($node/child::text()) and count($node/child::node()) eq 1">
                <xsl:choose>
                    <xsl:when test="(count($node/namespace::*) gt 0 and $use-namespaces) or count($node/@*[not(../@json:force-array) or count(.|../@json:force-array)=2]) gt 0">
                        <json:object>
                            <xsl:copy-of select="json:create-namespaces($node)"/>
                            <xsl:copy-of select="json:create-attributes($node)"/>
                            <json:member>
                                <json:name>$</json:name>
                                <json:value>
                                    <xsl:value-of select="$node"/>
                                </json:value>
                            </json:member>
                        </json:object>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy-of select="json:create-text-value($node)"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:when>
            <xsl:when test="exists($node/child::text())">
                <xsl:choose>
                    <xsl:when test="(count($node/namespace::*) gt 0 and $use-namespaces) or count($node/@*[not(../@json:force-array) or count(.|../@json:force-array)=2]) gt 0">
                        <json:object>
                            <xsl:copy-of select="json:create-namespaces($node)"/>
                            <xsl:copy-of select="json:create-attributes($node)"/>
                            <json:member>
                                <json:name>$</json:name>
                                <json:value>
                                    <xsl:copy-of select="json:create-mixed-array($node)"/>
                                </json:value>
                            </json:member>
                        </json:object>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy-of select="json:create-mixed-array($node)"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:when>
            <xsl:when test="exists($node/child::node()) or ((count($node/namespace::*) gt 0 and $use-namespaces) or count($node/@*[not(../@json:force-array) or count(.|../@json:force-array)=2]) gt 0)">
                <json:object>
                    <xsl:copy-of select="json:create-namespaces($node)"/>
                    <xsl:copy-of select="json:create-attributes($node)"/>
                    <xsl:for-each-group select="$node/child::node()" group-adjacent="local-name()">
                        <xsl:choose>
                            <xsl:when test="count(current-group()) eq 1 and (not(exists(./@json:force-array)) or ./@json:force-array eq 'false')">
                                <xsl:copy-of select="json:create-node(current-group()[1], false())"/>
                            </xsl:when>
                            <xsl:otherwise>
                                <json:member>
                                    <json:name>
                                        <xsl:value-of select="if($use-namespaces) then current-group()[1]/name() else current-group()[1]/local-name()"/>
                                    </json:name>
                                    <json:value>
                                        <json:array>
                                            <xsl:for-each select="current-group()">
                                                <xsl:copy-of select="json:create-node(.,true())"/>
                                            </xsl:for-each>
                                        </json:array>
                                    </json:value>
                                </json:member>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each-group>
                </json:object>
            </xsl:when>
        </xsl:choose>
    </xsl:function>
    <xsl:function name="json:create-mixed-array" as="node()">
        <xsl:param name="node" as="node()"/>
        <json:array>
            <xsl:for-each select="$node/child::node()">
                <json:array-value>
                    <json:value>
                        <xsl:choose>
                            <xsl:when test="self::text()">
                                <xsl:copy-of select="json:create-text-value(.)"/>
                            </xsl:when>
                            <xsl:otherwise>
                                <json:object>
                                    <xsl:copy-of select="json:create-node(.,false())"/>
                                </json:object>
                            </xsl:otherwise>
                        </xsl:choose>
                    </json:value>
                </json:array-value>
            </xsl:for-each>
        </json:array>
    </xsl:function>
    <xsl:function name="json:create-text-value" as="node()">
        <xsl:param name="node" as="node()"/>
        <xsl:choose>
            <xsl:when test="$use-badgerfish">
                <json:object>
                    <json:member>
                        <json:name>$</json:name>
                        <json:value>
                            <xsl:value-of select="$node"/>
                        </json:value>
                    </json:member>
                </json:object>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$node"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>
    <xsl:function name="json:create-string" as="node()">
        <xsl:param name="node" as="node()"/>
        <xsl:choose>
            <xsl:when test="$use-namespaces">
                <json:name>
                    <xsl:value-of select="$node/name()"/>
                </json:name>
            </xsl:when>
            <xsl:otherwise>
                <json:name>
                    <xsl:value-of select="$node/local-name()"/>
                </json:name>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>
    <xsl:function name="json:create-attributes" as="node()*">
        <xsl:param name="node" as="node()"/>
        <xsl:for-each select="$node/@*[not(../@json:force-array) or count(.|../@json:force-array)=2]">
            <json:member>
                <json:name>
                    <xsl:if test="$use-badgerfish or $use-rabbitfish">@</xsl:if>
                    <xsl:value-of select="if($use-namespaces) then name() else local-name()"/>
                </json:name>
                <json:value>
                    <xsl:value-of select="."/>
                </json:value>
            </json:member>
        </xsl:for-each>
    </xsl:function>
    <xsl:function name="json:create-namespaces" as="node()*">
        <xsl:param name="node" as="node()"/>
        <xsl:if test="$use-namespaces">
            <xsl:if test="count($node/namespace::*) gt 0">
                <json:member>
                    <json:name>
                        <xsl:if test="$use-badgerfish or $use-rabbitfish">@</xsl:if>xmlns</json:name>
                    <json:value>
                        <json:object>
                            <xsl:for-each select="$node/namespace::*">
                                <json:member>
                                    <xsl:choose>
                                        <xsl:when test="local-name(.) eq ''">
                                            <json:name>$</json:name>
                                        </xsl:when>
                                        <xsl:otherwise>
                                            <json:name>
                                                <xsl:value-of select="local-name(.)"/>
                                            </json:name>
                                        </xsl:otherwise>
                                    </xsl:choose>
                                    <json:value>
                                        <xsl:value-of select="."/>
                                    </json:value>
                                </json:member>
                            </xsl:for-each>
                        </json:object>
                    </json:value>
                </json:member>
            </xsl:if>
        </xsl:if>
    </xsl:function>
    <xsl:template match="json:parameter" mode="json">
        <xsl:variable name="parameters">
            <xsl:apply-templates mode="json"/>
        </xsl:variable>
        <xsl:value-of select="string-join($parameters/parameter, ', ')"/>
    </xsl:template>
    <xsl:template match="json:object" mode="json">
        <xsl:variable name="members">
            <xsl:apply-templates mode="json"/>
        </xsl:variable>
        <parameter>
            <xsl:text/>{<xsl:text/>
            <xsl:value-of select="string-join($members/member,',')"/>
            <xsl:text/>}<xsl:text/>
        </parameter>
    </xsl:template>
    <xsl:template match="json:member" mode="json">
        <xsl:text/>
        <member>
            <xsl:apply-templates mode="json"/>
        </member>
        <xsl:text/>
    </xsl:template>
    <xsl:function name="json:encode-string" as="xs:string">
        <xsl:param name="string" as="xs:string"/>
        <xsl:sequence select="replace(
          replace(
          replace(
          replace(
          replace(
          replace(
          replace(
          replace(
          replace($string,
            '\\','\\\\'),
            '/', '\\/'),
            '&quot;', '\\&quot;'),
            '&#xA;','\\n'),
            '&#xD;','\\r'),
            '&#x9;','\\t'),
            '\n','\\n'),
            '\r','\\r'),
            '\t','\\t')"/>
    </xsl:function>
    <xsl:template match="json:name" mode="json">
        <xsl:text/>"<xsl:value-of select="json:encode-string(.)"/>":<xsl:text/>
    </xsl:template>
    <xsl:template match="json:value" mode="json">
        <xsl:choose>
            <xsl:when test="node() and not(text())">
                <xsl:apply-templates mode="json"/>
            </xsl:when>
            <xsl:when test="text()">
                <xsl:choose>
                    <xsl:when test="$force-string = '*' or preceding-sibling::*/text() = $force-string">
                        <xsl:text/>"<xsl:value-of select="json:encode-string(.)"/>"<xsl:text/>
                    </xsl:when>
                    <xsl:when test="normalize-space(.) ne . or not((string(.) castable as xs:integer and not(starts-with(string(.),'+') or starts-with(string(.),'-')) and not(starts-with(string(.),'0') and not(. = '0'))) or (string(.) castable as xs:decimal and not(starts-with(string(.),'+')) and not(starts-with(.,'-.')) and not(starts-with(.,'.')) and not(starts-with(.,'-0') and not(starts-with(.,'-0.'))) and not(ends-with(.,'.')) and not(starts-with(.,'0') and not(starts-with(.,'0.'))))) and not(. = 'false') and not(. = 'true') and not(. = 'null')">
                        <xsl:text/>"<xsl:value-of select="json:encode-string(.)"/>"<xsl:text/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:text/>
                        <xsl:value-of select="."/>
                        <xsl:text/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:when>
            <xsl:otherwise>
                <xsl:text/>null<xsl:text/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="json:array-value" mode="json">
        <xsl:text/>
        <value>
            <xsl:apply-templates mode="json"/>
        </value>
        <xsl:text/>
    </xsl:template>
    <xsl:template match="json:array" mode="json">
        <xsl:variable name="values">
            <xsl:apply-templates mode="json"/>
        </xsl:variable>
        <xsl:text/>[<xsl:text/>
        <xsl:value-of select="string-join($values/value,',')"/>
        <xsl:text/>]<xsl:text/>
    </xsl:template>
</xsl:stylesheet>

XML 是

<?xml version="1.0" encoding="utf-8"?> 
<n0:MT_Student xmlns:n0="ABC/General">
    <School>St. Louis</School> 
    <Class>7th Grade</Class> 
    <Roll_number>1234</Roll_number> 
    <Subject> 
        <Mathematics>A</Mathematics> 
        <Physics>A</Physics> 
        <Chemistry>A</Chemistry> 
    </Subject> 
</n0:MT_Student>
xslt xml-to-json
1个回答
0
投票

我回答这个问题的主要问题是你的代码做了很多这个小例子不需要的事情。所有这些代码显然都是有原因的,但这不是我可以从这个例子中理解的原因;您正在解决您的问题中不明显的要求。您说过您希望解决方案是“通用的”,即您希望它能够处理远远超出您的具体示例的问题,但如果没有对您想要实现的目标的完整描述,则很难知道该提供什么建议。

这是显而易见的,例如,您的程序以几个参数

header
valueLookup
开始,这些参数显然是为了参数化样式表的行为而设计的,但是您没有提供有关这些参数文档内容的任何信息可能含有。

您正在使用 XSLT 3.0,但您似乎没有使用 XSLT 3.0 中提供的任何 JSON 支持,并且不清楚为什么不这样做。您似乎正在构建一个 XML 结构,该结构镜像您希望出现在最终输出中的 JSON 映射和数组,然后“手动”将其序列化为 JSON:为什么不使用“JSON 的 XML 表示形式”来构建此 XML 结构? " XSLT 3.0 规范中定义的词汇,然后您可以通过调用 xml-to-json() 函数直接输出,然后使用 JSON 输出方法序列化生成的映射和数组树?

话虽如此,你的问题中提到的问题,即使只有一个主题,如何生成一组主题对象,似乎是一个需求规范问题,而不是一个编码问题。当您的代码完全通用并且对数据模型一无所知时,您如何决定主题应该是一个数组?我一直在致力于 XPath 4.0 的 XML 到 JSON 转换的设计,基于 Balisage 论文中概述的想法,网址为 https://www.balisage.net/Proceedings/vol28/html/Kay01/BalisageVol28-Kay01。 html,想法是这里数组格式的选择可以取决于以下任何一个:

  • 用户明确要求特定元素使用特定格式
  • 对所有输入进行全局分析 - 如果具有特定名称的任何元素有多个子元素,则以相同的方式对待所有元素
  • 架构信息,如果输入文档已针对架构进行验证,则查看该架构允许哪些内容。

仅查看一个实例不会为您提供足够的信息,并且不清楚您打算如何解决这个问题。无论如何,我尝试过的所有简单的 XML 到 JSON 库在这方面都做得非常糟糕。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.