XSLT技术问题:如何将元素沿自身轴左右移动n个节点?
问题:XSLT中沿兄弟轴移动元素的实现
需要实现将指定元素沿自身兄弟轴向左/向右移动n个节点:
$axis-move为负数:左移(向兄弟节点的前方移动)$axis-move为正数:右移(向兄弟节点的后方移动)
已完成目标位置的计算,但不清楚如何将元素从原位置“剪切”并插入到目标位置之后。
示例XML
<root> <p>foo 1</p> <p>foo 2</p> <p>foo 3</p> <p>foo 4</p> <p>foo 5</p> <moveMe>Hello World!</moveMe> <!-- 当前元素 --> </root>
预期效果(左移2位)
<root> <p>foo 1</p> <p>foo 2</p> <p>foo 3</p> <!-- 目标位置 --> <moveMe>Hello World!</moveMe> <!-- 新位置 --> <p>foo 4</p> <p>foo 5</p> </root>
原XSLT代码
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs"> <xsl:mode on-no-match="shallow-copy" /> <xsl:template match="*" name="move-element"> <!-- 获取当前元素名称 --> <xsl:variable name="current-element" select="local-name()" /> <!-- 为当前元素创建$MoveRule --> <xsl:variable name="MoveRule" select="doc('config.xml')/matches[source = $current-element]" /> <!-- $axis-move定义元素的移动量: 负值:左移。 正值:右移。 --> <xsl:variable name="axis-move" select="-3" /> <xsl:choose> <xsl:when test="MoveRule"> <xsl:choose> <!-- 元素左移逻辑 --> <xsl:when test="$axis-move lt 0"> <xsl:variable name="preceding-sibling-count" select="count(preceding-sibling::*)" /> <xsl:variable name="target-position" select="$preceding-sibling-count - ($axis-move * -1)" /> <xsl:variable name="insert-after" select="parent::*/*[position() = $target-position]" /> <!-- 如何选择$insert-after元素,并将当前元素插入到其右侧? --> </xsl:when> <!-- 元素右移逻辑 --> <xsl:when test="$axis-move gt 0"> <xsl:variable name="preceding-sibling-count" select="count(preceding-sibling::*)" /> <xsl:variable name="target-position" select="$preceding-sibling-count - ($axis-move)" /> <xsl:variable name="insert-after" select="parent::*/*[position() = $target-position]" /> <!-- 如何选择$insert-after元素,并将当前元素插入到其右侧? --> </xsl:when> <xsl:otherwise> <xsl:copy> <xsl:apply-templates select="@* | node()" /> </xsl:copy> </xsl:otherwise> </xsl:choose> </xsl:when> </xsl:choose> </xsl:template> </xsl:stylesheet>
解决方案
核心思路:
- 剪切原元素:在匹配要移动元素的模板中,不输出原元素,避免它保留在原位置。
- 插入到目标位置:通过自定义模板处理父元素,在遍历子节点时,找到目标位置并插入移动的元素。
修改后的完整XSLT代码
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs"> <xsl:mode on-no-match="shallow-copy" /> <!-- 匹配需要移动的元素 --> <xsl:template match="*" name="move-element"> <xsl:variable name="current-element" select="local-name()" /> <xsl:variable name="MoveRule" select="doc('config.xml')/matches[source = $current-element]" /> <xsl:variable name="axis-move" select="-2" /> <!-- 示例:左移2位 --> <xsl:if test="$MoveRule"> <xsl:choose> <!-- 左移逻辑 --> <xsl:when test="$axis-move lt 0"> <xsl:variable name="preceding-count" select="count(preceding-sibling::*)" /> <!-- 计算目标位置:前置兄弟数 - 移动量绝对值 --> <xsl:variable name="target-pos" select="$preceding-count - abs($axis-move)" /> <xsl:variable name="insert-after-node" select="parent::*/*[position() = $target-pos]" /> <!-- 调用父节点模板,传入要移动的节点和目标节点 --> <xsl:apply-templates select="parent::*" mode="insert-moved-node"> <xsl:with-param name="moved-node" select="." /> <xsl:with-param name="insert-after" select="$insert-after-node" /> </xsl:apply-templates> </xsl:when> <!-- 右移逻辑 --> <xsl:when test="$axis-move gt 0"> <xsl:variable name="preceding-count" select="count(preceding-sibling::*)" /> <!-- 计算目标位置:前置兄弟数 + 移动量 --> <xsl:variable name="target-pos" select="$preceding-count + $axis-move" /> <!-- 若目标位置超出子节点总数,则插入到最后 --> <xsl:variable name="insert-after-node" select=" if (parent::*/*[position() = $target-pos]) then parent::*/*[position() = $target-pos] else parent::*/*[last()] " /> <!-- 调用父节点模板 --> <xsl:apply-templates select="parent::*" mode="insert-moved-node"> <xsl:with-param name="moved-node" select="." /> <xsl:with-param name="insert-after" select="$insert-after-node" /> </xsl:apply-templates> </xsl:when> <xsl:otherwise> <!-- 不移动时正常复制元素 --> <xsl:copy> <xsl:apply-templates select="@* | node()" /> </xsl:copy> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:template> <!-- 自定义模板:处理父节点,插入移动的元素 --> <xsl:template match="*" mode="insert-moved-node"> <xsl:param name="moved-node" /> <xsl:param name="insert-after" /> <xsl:copy> <!-- 复制父节点的属性 --> <xsl:apply-templates select="@*" /> <!-- 遍历父节点的所有子节点,排除要移动的节点 --> <xsl:for-each select="*[not(. is $moved-node)]"> <!-- 复制当前子节点 --> <xsl:apply-templates select="." /> <!-- 如果当前节点是目标节点,插入要移动的元素 --> <xsl:if test=". is $insert-after"> <xsl:apply-templates select="$moved-node" /> </xsl:if> </xsl:for-each> <!-- 若目标节点不存在(比如要插入到最前面),则在开头插入 --> <xsl:if test="not(exists($insert-after))"> <xsl:apply-templates select="$moved-node" /> </xsl:if> </xsl:copy> </xsl:template> </xsl:stylesheet>
关键说明
- 剪切原元素:在
move-element模板中,当触发移动逻辑时,没有直接复制当前元素,而是调用父节点的自定义模板,原元素不会保留在原位置。 - 插入逻辑:在
insert-moved-node模板中,遍历父节点的所有子节点(排除要移动的节点),每输出一个子节点后检查是否是目标位置,若是则插入移动的元素。 - 边界处理:右移时如果目标位置超出子节点总数,会自动将元素插入到父节点的最后;左移时如果目标位置为0(比如移动量超过前置兄弟数),会将元素插入到最前面。
内容的提问来源于stack exchange,提问作者Stefan Gentz
相关产品推荐
相关产品推荐

