我牢记@Michael Kay 在如何使用 XSLT 将 JSON 转换为 XML? 中的评论,他说:
XSLT 3.0 实际上并不擅长使用模板处理 JSON 规则:可以做,但是不太方便。通常会更多 方便使用的功能。
给定一个比简单的 JSON 作为输入,即:
{
"name": "Alice",
"age": 30,
"children": [
{"name": "Charlie", "age": 5},
{"name": "Daisy", "age": 3}
]
}
我想要
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 规范中哪里说正在发生这样的深复制?
年龄是原始值。然而
<xsl:variable name="older-children" />
的选择表达式似乎引用了 $children-variable
内部的映射,至少在查看代码的表面值时是这样:
XPath 3.1/XDM 3.1/XSLT 3.0 中的映射是不可变的,因此任何
map:put
都不会操作原始映射,而是返回具有更改后的属性的新映射。
https://www.w3.org/TR/xpath-functions-31/#map-functions:
与所有其他值一样,本规范中的函数将 映射为不可变。例如,map:remove函数返回一个地图 与提供的地图不同之处在于(通常)省略了一个 条目,但提供的地图不会被该操作更改。两次通话 在地图上:使用相同的参数删除返回的地图 彼此无法区分;没有办法询问是否 这些是“同一张地图”。