You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

XSLT3.0 XML转JSON:根节点丢失、属性转子节点及键名小写问题

XML转JSON的XSLT 3.0实现优化

需求

  • 将所有JSON键名转为小写
  • 若节点存在首个属性(属性名称不固定),需将其转换为JSON子节点

输入示例XML

<FOO id="1">
    <BAR xy="2">
        <SPAM>N</SPAM>
    </BAR>
</FOO>

期望输出JSON

{ 
  "foo" : { 
     "id" : "1",
     "bar" : { 
         "xy" : "2",
         "spam" : "N" 
     } 
  }
}

当前代码及问题

当前使用的XSLT代码

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns="http://www.w3.org/2005/xpath-functions"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

  <xsl:output method="xml" indent='true' omit-xml-declaration='yes'/>


  <xsl:template match="dummy">
      <xsl:variable name="json-xml">
          <xsl:apply-templates/>
      </xsl:variable>
      <xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
  </xsl:template>

  
  <!--no children-->
  <xsl:template match="*[not(*)]">
    <string key="{lower-case(local-name())}">{.}</string>
  </xsl:template>


  <xsl:template match="*[*]">
    <xsl:param name="key" as="xs:boolean" select="true()"/>
    <map>
      <xsl:if test="$key">
        <xsl:attribute name="key" select="lower-case(local-name())"/>
      </xsl:if>
      <xsl:for-each-group select="*" group-by="node-name()">
          <xsl:choose>
              <xsl:when test="current-group()[2]">
                  <array key="{lower-case(local-name())}">
                        <xsl:apply-templates select="current-group()">
                          <xsl:with-param name="key" select="false()"/>
                        </xsl:apply-templates>
                  </array>
              </xsl:when>
              <xsl:otherwise>
                  <xsl:apply-templates select="current-group()">
                    <xsl:with-param name="key" select="true()"/>
                  </xsl:apply-templates>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
    </map>
  </xsl:template>
</xsl:stylesheet>

遇到的问题

  1. 当将匹配模板改为/触发转换时,结果丢失根节点foo,输出为:
{ "bar" : 
  { "spam" : "N" } }
  1. 尚未实现将节点首个属性转为JSON子节点的逻辑
  2. 当前每个模板单独处理键名小写,希望能统一处理,避免重复代码

解决方案代码

以下是修复问题并优化后的XSLT代码:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns="http://www.w3.org/2005/xpath-functions"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

  <xsl:output method="text" indent="true"/>

  <!-- 根节点模板:生成包含根元素的JSON结构,避免丢失根节点 -->
  <xsl:template match="/">
      <xsl:variable name="json-xml">
          <map>
              <xsl:apply-templates select="*"/>
          </map>
      </xsl:variable>
      <xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
  </xsl:template>

  <!-- 处理无子元素的节点 -->
  <xsl:template match="*[not(*)]">
    <string key="{get-lowercase-name(.)}">{.}</string>
    <!-- 处理首个属性 -->
    <xsl:if test="@*[1]">
        <string key="{lower-case(local-name(@*[1]))}">{@*[1]}</string>
    </xsl:if>
  </xsl:template>

  <!-- 处理有子元素的节点 -->
  <xsl:template match="*[*]">
    <xsl:param name="key" as="xs:boolean" select="true()"/>
    <map>
      <xsl:if test="$key">
        <xsl:attribute name="key" select="get-lowercase-name(.)"/>
      </xsl:if>
      <!-- 先处理首个属性 -->
      <xsl:if test="@*[1]">
          <string key="{lower-case(local-name(@*[1]))}">{@*[1]}</string>
      </xsl:if>
      <!-- 处理子元素 -->
      <xsl:for-each-group select="*" group-by="node-name()">
          <xsl:choose>
              <xsl:when test="current-group()[2]">
                  <array key="{get-lowercase-name(current-group()[1])}">
                        <xsl:apply-templates select="current-group()">
                          <xsl:with-param name="key" select="false()"/>
                        </xsl:apply-templates>
                  </array>
              </xsl:when>
              <xsl:otherwise>
                  <xsl:apply-templates select="current-group()">
                    <xsl:with-param name="key" select="true()"/>
                  </xsl:apply-templates>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
    </map>
  </xsl:template>

  <!-- 统一处理节点名小写的函数 -->
  <xsl:function name="get-lowercase-name" as="xs:string">
      <xsl:param name="node" as="node()"/>
      <xsl:sequence select="lower-case(local-name($node))"/>
  </xsl:function>

</xsl:stylesheet>

关键优化点说明

  1. 修复根节点丢失问题:根模板匹配/后,手动生成一个外层map,将根元素作为子节点放入其中,确保根节点的键值对被保留。
  2. 实现属性转换逻辑:在两个元素模板中,添加<xsl:if test="@*[1]">逻辑,将首个属性转为对应的string类型键值对,键名同样转为小写。
  3. 统一处理键名小写:定义get-lowercase-name函数,所有需要转换节点名为小写的地方统一调用该函数,避免重复编写lower-case(local-name())代码,提升可维护性。

内容的提问来源于stack exchange,提问作者yan-hic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 02:45:31