我将 @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>
比较我最直观的指标,我进行了并排比较:
array:for-each
循环被压缩到一行时)。array:for-each
和 <xsl:for-each>
)。{fn:concat('older',fn:upper-case(fn:substring(@key,1,1)),fn:substring(@key,2))}
和 {fn:substring(../@key,1,5)}
等表达式),因为我在调试器中观察到名称“children”在将原始 JSON 转换为映射和数组。<xsl:template match="/">
中的一大块。总的来说,考虑到这两个解决方案中使用的技术,我看到基于模板的解决方案有很多优点。 所以我的第一个问题是:我是否错过了更重要的指标,这些指标会偏向基于功能的解决方案?
我的第二个问题是:我是否缺少一些更多基于函数的技术来弥补我所认为的基于函数的解决方案的缺点?
我的第三个问题是:你能否编写一个更好的基于函数的解决方案,该解决方案明显优于我的两个解决方案,并且在所有或大多数指标上明显获胜?
除此之外我还有一个更技术性的问题:
问题 4:在基于函数的解决方案中,
<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 规范中哪里说正在发生这样的深复制?
年龄是原始值。然而