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

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>

解决方案

核心思路:

  1. 剪切原元素:在匹配要移动元素的模板中,不输出原元素,避免它保留在原位置。
  2. 插入到目标位置:通过自定义模板处理父元素,在遍历子节点时,找到目标位置并插入移动的元素。

修改后的完整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>

关键说明

  1. 剪切原元素:在move-element模板中,当触发移动逻辑时,没有直接复制当前元素,而是调用父节点的自定义模板,原元素不会保留在原位置。
  2. 插入逻辑:在insert-moved-node模板中,遍历父节点的所有子节点(排除要移动的节点),每输出一个子节点后检查是否是目标位置,若是则插入移动的元素。
  3. 边界处理:右移时如果目标位置超出子节点总数,会自动将元素插入到父节点的最后;左移时如果目标位置为0(比如移动量超过前置兄弟数),会将元素插入到最前面。

内容的提问来源于stack exchange,提问作者Stefan Gentz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 13:07:02