不使用全局变量的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是声明式语言,不能依赖可变状态,需通过节点位置查询实现计数:
核心思路
- 对每个端口,通过
@parent属性匹配其所属的<Block> - 计算
current:统计该端口之前,属于同一个Block的同类型端口数量(序号从0开始) - 从所属Block获取
total(inputPorts或outputPorts属性) - 代入公式计算坐标,格式化输出保留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
相关产品推荐
相关产品推荐

