XSLT 3.0 处理 JSON:改进基于函数的解决方案 [已关闭]

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

我牢记@Michael Kay 在如何使用 XSLT 将 JSON 转换为 XML? 中的评论,他说:

XSLT 3.0 实际上并不擅长使用模板处理 JSON 规则:可以做,但是不太方便。通常会更多 方便使用的功能。

给定一个比简单的 JSON 作为输入,即:

{
  "name": "Alice",
  "age": 30,
  "children": [
    {"name": "Charlie", "age": 5},
    {"name": "Daisy", "age": 3}
   ]
}

我想要

  • 把孩子们救出来
  • 通过将他们的年龄分别提高 10 来处理它们
  • 并使用派生元素名称和属性名称创建结果 XML。

XML 结果应该是:

<olderChildren>
    <child name="Charlie" age="15"/>
    <child name="Daisy" age="13"/>
</olderChildren>

为了将较新的基于函数的方法与传统的基于模板的方法进行比较,我创建了 2 个样式表来并排比较它们的指标。 这是基于函数的样式表:

<xsl:stylesheet version="3.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:map="http://www.w3.org/2005/xpath-functions/map"
                xmlns:array="http://www.w3.org/2005/xpath-functions/array"
                exclude-result-prefixes="map array">
                
    <xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
    
    <xsl:param name="json-data">
    {
        "name": "Alice",
        "age": 30,
        "children": [
            {"name": "Charlie", "age": 5},
            {"name": "Daisy", "age": 3}
        ]
    }
    </xsl:param>

    <xsl:variable name="parsed-json" select="parse-json($json-data)"/>

    <xsl:template match="/">
        <xsl:variable name="children" select="map:get($parsed-json, 'children')"/>       <!-- Extract children array -->
        <xsl:variable name="older-children" as="array(*)" select="array:for-each($children, function($child) {map:put($child, 'age', map:get($child, 'age') + 10)})"/> <!-- Create array of children whose age is raised by 10 years-->
        <xsl:element name="olderChildren"><!-- Output result XML -->
            <xsl:for-each select="$older-children?*">
                <xsl:element name="child">
                    <xsl:attribute name="name" select="map:get(., 'name')"/>
                    <xsl:attribute name="age" select="map:get(., 'age')"/>
                </xsl:element>
            </xsl:for-each>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

这是基于模板的样式表:

<xsl:stylesheet version="3.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:fn="http://www.w3.org/2005/xpath-functions" 
                exclude-result-prefixes="fn">
                
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    <xsl:param name="json">
    {
        "name": "Alice",
        "age": 30,
        "children": [
            {"name": "Charlie", "age": 5},
            {"name": "Daisy", "age": 3}
        ]
    }
    </xsl:param>

    <xsl:variable name="XMLfromJSON" select="json-to-xml($json)"/>
    
    <xsl:template match="/">
        <xsl:apply-templates select="$XMLfromJSON/fn:map/fn:array"/>
    </xsl:template>
    
    <xsl:template match="fn:array[@key = 'children']">
        <xsl:element name="{fn:concat('older',fn:upper-case(fn:substring(@key,1,1)),fn:substring(@key,2))}">
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>
    
    <xsl:template match="fn:array[@key = 'children']/fn:map">
        <xsl:element name="{fn:substring(../@key,1,5)}">
            <xsl:attribute name="{./fn:string/@key}" select="./fn:string"/>
            <xsl:attribute name="{./fn:number/@key}" select="./fn:number + 10"/>
        </xsl:element>
    </xsl:template>
    
</xsl:stylesheet>

如何将我的功能解决方案转换为具有基于模板的解决方案的结构,即使用 apply-templates,同时保持使用表达式派生结果 XML 元素名称的可能性,就像我在 template-baes 解决方案中所做的那样(即使用

 substring()
等)?

另一个技术背景问题:在基于函数的解决方案中,

<xsl:variable name="older-children" />
的内容显然是通过在处理新副本之前创建<xsl:variable name="children">
深层副本
创建的(通过将所有子地图中的年龄添加10) )。在 XML 结果生成期间将
<xsl:for-each select="$older-children?*">
替换为
<xsl:for-each select="$children?*">
时可以看到这一点。那么结果就是:

<olderChildren>
    <child name="Charlie" age="5"/>
    <child name="Daisy" age="3"/>
</olderChildren>

,即年龄是原始值。 然而

<xsl:variable name="older-children" />
的选择表达式似乎引用了
$children
变量内的映射,至少在查看代码的表面值时是这样:在该迭代的每个
function($child)
上调用的
$child
似乎覆盖
'age'
的当前
$child
中的字段
$children
,而不是新创建的
$children
(
select="array:for-each($children, function($child) {map:put($child, 'age', map:get($child, 'age') + 10)}
) 的深层副本。 XSLT 3.0 规范中哪里说正在发生这样的深复制?

json xml xslt-3.0 xpath-3.1
2个回答
0
投票

年龄是原始值。然而

<xsl:variable name="older-children" />
的选择表达式似乎引用了
$children-variable
内部的映射,至少在查看代码的表面值时是这样:


0
投票

XPath 3.1/XDM 3.1/XSLT 3.0 中的映射是不可变的,因此任何

map:put
都不会操作原始映射,而是返回具有更改后的属性的新映射。

https://www.w3.org/TR/xpath-functions-31/#map-functions

与所有其他值一样,本规范中的函数将 映射为不可变。例如,map:remove函数返回一个地图 与提供的地图不同之处在于(通常)省略了一个 条目,但提供的地图不会被该操作更改。两次通话 在地图上:使用相同的参数删除返回的地图 彼此无法区分;没有办法询问是否 这些是“同一张地图”。

© www.soinside.com 2019 - 2024. All rights reserved.