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

不使用全局变量的XSLT 1.0 XML解析:端口坐标计算

问题:XSLT 1.0 实现端口坐标属性计算

需求说明

仅使用XSLT 1.0解析特定结构的XML:

  • XML中包含多个<Block>标签
  • 每个<Block>之后、下一个<Block>之前的<InputPort>和<OutputPort>标签,需添加coordinate属性
  • 属性值通过公式计算:coordinate = (2 * current + 1) / (2 * total)
    • current:同类型端口的序号(从0开始)
    • total:对应<Block>的同类型端口总数(从Block的inputPorts或outputPorts属性获取)

输入XML示例

<root>
    <Block id="1" inputPorts="2" outputPorts="3" />
    <OutputPort id="2" parent="1" /> <!-- first OutputPort after Block so current = 0, should give coordinate(0,3) = 1/6 -->
    <InputPort id="3" parent="1" /> <!-- first InputPort after Block so current = 0, should give coordinate(0,2) = 1/4 -->
    <OutputPort id="4" parent="1" /> <!-- second OutputPort after Block so current = 1, should give coordinate(1,3) = 3/6 -->
    <InputPort id="5" parent="1" /> <!-- second InputPort after Block so current = 1, should give coordinate(1,2) = 3/4 -->
    <OutputPort id="6" parent="1" /> <!-- third OutputPort after Block so current = 2, should give coordinate(2,3) of 5/6 -->

    <Block id="7" inputPorts="0" outputPorts="1" />
    <OutputPort id="8" parent="7" /> <!-- first OutputPort after Block so current = 0, should give coordinate(0,1) = 1/2 -->
</root>

期望输出XML示例

<root>
    <Block id="1" inputPorts="2" outputPorts="3" />
    <OutputPort id="2" parent="1" coordinate="0.1666" />
    <InputPort id="3" parent="1" coordinate="0.25" />
    <OutputPort id="4" parent="1" coordinate="0.5" />
    <InputPort id="5" parent="1" coordinate="0.75" />
    <OutputPort id="6" parent="1" coordinate="0.8333" />

    <Block id="7" inputPorts="0" outputPorts="1" />
    <OutputPort id="8" parent="7" coordinate="0.5" />
</root>

现有尝试及问题

原尝试通过全局变量跟踪端口计数和总数,但XSLT 1.0中全局变量不可修改,模板内的变量仅在当前模板作用域生效,无法跨模板或同模板不同标签复用,导致无法正确计算current和total值。代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:variable name="inputPorts" value="0" />
  <xsl:variable name="outputPorts" value="0" />
  <xsl:variable name="currentInputPort" value="0" />
  <xsl:variable name="currentOutputPort" value="0" />
  <xsl:output method="xml" indent="no" />
  <xsl:template match="@*|node()">
    <xsl:copy>
       <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
  </xsl:template>
  <xsl:template match="comment()" />
  <xsl:template match="Block">
    <xsl:copy>
      <xsl:variable name="inputPorts">
        <xsl:value-of select="@inputPorts" />
      </xsl:variable>
      <xsl:variable name="outputPorts">
        <xsl:value-of select="@outputPorts" />
      </xsl:variable>
      <xsl:variable name="currentInputPort" value="0" />
      <xsl:variable name="currentOutputPort" value="0" />
      <xsl:attribute name="id">
        <xsl:value-of select="@id" />
      </xsl:attribute>
      <xsl:attribute name="inputPorts">
        <xsl:value-of select="@inputPorts" />
      </xsl:attribute>
      <xsl:attribute name="outputPorts">
        <xsl:value-of select="@outputPorts" />
      </xsl:attribute>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="InputPort">
    <xsl:copy>
      <xsl:attribute name="id">
        <xsl:value-of select="@id" />
      </xsl:attribute>
      <xsl:attribute name="parent">
        <xsl:value-of select="@parent" />
      </xsl:attribute>
      <xsl:attribute name="coordinate">
        <xsl:value-of select="(2 * $currentInputPort + 1) div (2 * $inputPorts)" />
      </xsl:attribute>
      <xsl:variable name="currentInputPort" select="$currentInputPort + 1" />
    </xsl:copy>
  </xsl:template>
  <xsl:template match="OutputPort">
    <xsl:copy>
      <xsl:attribute name="id">
        <xsl:value-of select="@id" />
      </xsl:attribute>
      <xsl:attribute name="parent">
        <xsl:value-of select="@parent" />
      </xsl:attribute>
      <xsl:attribute name="coordinate">
        <xsl:value-of select="(2 * $currentOutputPort + 1) div (2 * $outputPorts)" />
      </xsl:attribute>
      <xsl:variable name="currentOutputPort" select="$currentOutputPort + 1" />
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

解决方案

XSLT 1.0是声明式语言,不能依赖可变状态,需通过节点位置查询实现计数:

核心思路

  1. 对每个端口,通过@parent属性匹配其所属的<Block>
  2. 计算current:统计该端口之前,属于同一个Block的同类型端口数量(序号从0开始)
  3. 从所属Block获取total(inputPorts或outputPorts属性)
  4. 代入公式计算坐标,格式化输出保留4位小数

完整XSLT代码

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" encoding="UTF-8"/>
  
  <!-- 身份转换模板,复制所有未匹配的节点和属性 -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
  
  <!-- 移除注释 -->
  <xsl:template match="comment()"/>
  
  <!-- 处理InputPort -->
  <xsl:template match="InputPort">
    <xsl:copy>
      <!-- 复制原有属性 -->
      <xsl:apply-templates select="@*"/>
      
      <!-- 获取所属Block的ID -->
      <xsl:variable name="block-id" select="@parent"/>
      <!-- 获取该Block的inputPorts总数 -->
      <xsl:variable name="total" select="//Block[@id=$block-id]/@inputPorts"/>
      
      <!-- 计算current:当前端口之前,属于同一Block的InputPort数量(从0开始) -->
      <xsl:variable name="current" select="count(preceding-sibling::InputPort[@parent=$block-id])"/>
      
      <!-- 计算坐标值,保留4位小数 -->
      <xsl:variable name="coordinate" select="(2 * $current + 1) div (2 * $total)"/>
      
      <xsl:attribute name="coordinate">
        <xsl:value-of select="format-number($coordinate, '0.0000')"/>
      </xsl:attribute>
    </xsl:copy>
  </xsl:template>
  
  <!-- 处理OutputPort -->
  <xsl:template match="OutputPort">
    <xsl:copy>
      <!-- 复制原有属性 -->
      <xsl:apply-templates select="@*"/>
      
      <!-- 获取所属Block的ID -->
      <xsl:variable name="block-id" select="@parent"/>
      <!-- 获取该Block的outputPorts总数 -->
      <xsl:variable name="total" select="//Block[@id=$block-id]/@outputPorts"/>
      
      <!-- 计算current:当前端口之前,属于同一Block的OutputPort数量(从0开始) -->
      <xsl:variable name="current" select="count(preceding-sibling::OutputPort[@parent=$block-id])"/>
      
      <!-- 计算坐标值,保留4位小数 -->
      <xsl:variable name="coordinate" select="(2 * $current + 1) div (2 * $total)"/>
      
      <xsl:attribute name="coordinate">
        <xsl:value-of select="format-number($coordinate, '0.0000')"/>
      </xsl:attribute>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

代码说明

  • 身份转换模板负责复制大部分节点,无需重复编写Block的处理逻辑
  • 针对InputPort和OutputPort分别编写模板,通过preceding-sibling统计同类型同父Block的端口数量,得到current值
  • 通过//Block[@id=$block-id]定位所属Block,获取total值
  • 使用format-number格式化坐标值为4位小数,与期望输出一致

内容的提问来源于stack exchange,提问作者Sunil Shetye

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 18:59:50