XSLT 3.0 处理 JSON:函数与模板:哪个更好? (需要指标和优雅的解决方案)

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

我将 @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>

比较我最直观的指标,我进行了并排比较:

  1. 基于函数的代码行数胜出(但只有 2 行,并且只有当冗长的
    array:for-each
    循环被压缩到一行时)。
  2. 我认为基于模板的方法的最大优势是 XSLT 处理器的隐式迭代。对于基于函数的方法,必须有效地绕过这一点,并且必须手动编写 2 个循环(
    array:for-each
    <xsl:for-each>
    )。
  3. 就从原始 JSON 导出结果 XML 标识符而言,我看到基于模板的版本获胜(使用
    {fn:concat('older',fn:upper-case(fn:substring(@key,1,1)),fn:substring(@key,2))}
    {fn:substring(../@key,1,5)}
    等表达式),因为我在调试器中观察到名称“children”在将原始 JSON 转换为映射和数组。
  4. 在模块化方面,我看到基于模板的版本再次获胜,它的 3 个整洁清晰的小模板很好地反映了原始 JSON 的结构层次结构。相比之下,基于函数的版本是
    <xsl:template match="/">
    中的一大块。
  5. 基于函数的解决方案的最大优势可能是时间和资源,因为与首先解析和转换 JSON 的基于模板的版本相比,将 JSON 解析为可以直接读取和操作的映射和数组可能会更省力。 JSON 转换为该标准 XML,这是 XSLT 处理的基础。但这只是一个假设。我还没有做过任何基准测试。

总的来说,考虑到这两个解决方案中使用的技术,我看到基于模板的解决方案有很多优点。 所以我的第一个问题是:我是否错过了更重要的指标,这些指标会偏向基于功能的解决方案?

我的第二个问题是:我是否缺少一些更多基于函数的技术来弥补我所认为的基于函数的解决方案的缺点?

我的第三个问题是:你能否编写一个更好的基于函数的解决方案,该解决方案明显优于我的两个解决方案,并且在所有或大多数指标上明显获胜?

除此之外我还有一个更技术性的问题:

问题 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 规范中哪里说正在发生这样的深复制?

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

年龄是原始值。然而 的选择表达式似乎引用了 $children 变量内的映射,至少在查看代码的面值时是这样:

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