代码之家  ›  专栏  ›  技术社区  ›  Adam Schmideg

在xml层次结构中向上移动分隔符元素

  •  3
  • Adam Schmideg  · 技术社区  · 14 年前

    我有一个xml文档,在层次结构的深处有分隔符。

    <A>
      <B>
        <C id='1'/>
        <separator/>
        <C id='2'/>
      </B>
      <B>
        <C id='3'/>
        <separator/>
      </B>
      <B>
        <C id='4'/>
      </B>
    </A>
    

    我想把隔板向上移动,保持元件整齐。所以期望的输出是

    <A>
      <B>
        <C id='1'/>
      </B>
    </A>
    <separator/>
    <A>
      <B>
        <C id='2'/>
      </B>
      <B>
        <C id='3'/>
      </B>
    </A>
    <separator/>
    <A>
      <B>
        <C id='4'/>
      </B>
    </A>
    

    for-each ,仅使用模板匹配?

    更新:

    2 回复  |  直到 14 年前
        1
  •  2
  •   user357812 user357812    14 年前

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:key name="kCByFollSep" match="C"
                 use="generate-id(following::separator[1])"/>
        <xsl:template match="A">
            <xsl:for-each select="B/separator|B[last()]/*[last()]">
                <A>
                    <xsl:apply-templates
                         select="key('kCByFollSep',
                                     substring(generate-id(),
                                               1 div boolean(self::separator)))"/>
                </A>
                <xsl:copy-of select="self::separator"/>
            </xsl:for-each>
        </xsl:template>
        <xsl:template match="C">
            <B>
                <xsl:copy-of select="."/>
            </B>
        </xsl:template>
    </xsl:stylesheet>
    

    输出:

    <A>
        <B>
            <C id="1" />
        </B>
    </A>
    <separator />
    <A>
        <B>
            <C id="2" />
        </B>
        <B>
            <C id="3" />
        </B>
    </A>
    <separator />
    <A>
        <B>
            <C id="4" />
        </B>
    </A>
    

    :按以下方式分组 separator ,添加posible的最后三级元素 C 不跟随 分离器 .

    编辑 :更多的拉式样式,更多的模式无关性,此样式表:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:key name="kCByFollSep" match="C"
                 use="generate-id(following::separator[1])"/>
        <xsl:template match="text()"/>
        <xsl:template match="separator|*[not(*)][not(following::*)]">
            <A>
                <xsl:apply-templates
                         select="key('kCByFollSep',
                                     substring(generate-id(),
                                               1 div boolean(self::separator)))"
                         mode="output"/>
            </A>
            <xsl:copy-of select="self::separator"/>
        </xsl:template>
        <xsl:template match="C" mode="output">
            <B>
                <xsl:copy-of select="."/>
            </B>
        </xsl:template>
    </xsl:stylesheet>
    

    :更一般的解决方案(有一件事我不相信,哈!)

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:template match="node()|@*" name="identity">
            <xsl:param name="pRemains"/>
            <xsl:copy>
                <xsl:apply-templates select="node()[descendant-or-self::node()
                                                       [not(self::separator)]
                                                       [count(following::separator)
                                                        = $pRemains]
                                                   ][1]|@*">
                    <xsl:with-param name="pRemains" select="$pRemains"/>
                </xsl:apply-templates>
            </xsl:copy>
            <xsl:apply-templates select="following-sibling::node()
                                            [descendant-or-self::node()
                                               [not(self::separator)]
                                               [count(following::separator)
                                                = $pRemains]
                                            ][1]">
                <xsl:with-param name="pRemains" select="$pRemains"/>
            </xsl:apply-templates>
        </xsl:template>
        <xsl:template match="/*">
            <xsl:variable name="vCurrent" select="."/>
            <xsl:for-each select="descendant::separator|node()[last()]">
                <xsl:variable name="vRemains" select="last()-position()"/>
                <xsl:for-each select="$vCurrent">
                    <xsl:copy>
                        <xsl:apply-templates
                             select="node()[descendant::node()
                                              [not(self::separator)]
                                              [count(following::separator)
                                               = $vRemains]
                                           ][1]">
                            <xsl:with-param name="pRemains" select="$vRemains"/>
                        </xsl:apply-templates>
                    </xsl:copy>
                </xsl:for-each>
                <xsl:copy-of select="self::separator"/>
            </xsl:for-each>
        </xsl:template>
        <xsl:template match="separator"/>
    </xsl:stylesheet>
    

    注意 :主要是细粒度遍历。楼层层次结构规则(在本例中为根元素)复制自身和分隔符(最后一个组的虚拟节点,没有后面的分隔符),将剩余分隔符传递给处理第一个子级,并有足够的后面的分隔符进行处理。一种改进的细粒度遍历标识规则,复制自身并再次使用足够的后续分隔符处理第一个子级和后续同级。最后,一个分隔符规则打破了这个过程。

    编辑3 :其他更通用的解决方案,现在使用递归标识规则

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output indent="yes"/>
        <xsl:key name="kNodeByFolSep" match="node()[not(self::separator)]"
              use="generate-id((descendant::separator|following::separator)[1])"/>
        <xsl:template match="node()|@*" name="identity">
            <xsl:param name="pGroup"/>
            <xsl:copy>
                <xsl:apply-templates
                   select="node()[descendant-or-self::node()[count(.|$pGroup)
                                                             = count($pGroup)]]|@*">
                    <xsl:with-param name="pGroup" select="$pGroup"/>
                </xsl:apply-templates>
            </xsl:copy>
        </xsl:template>
        <xsl:template match="/*">
            <xsl:variable name="vCurrent" select="."/>
            <xsl:for-each select="descendant::separator|node()[last()]">
                <xsl:variable name="vGroup"
                     select="key('kNodeByFolSep',generate-id(self::separator))"/>
                <xsl:for-each select="$vCurrent">
                    <xsl:call-template name="identity">
                        <xsl:with-param name="pGroup" select="$vGroup"/>
                    </xsl:call-template>
                </xsl:for-each>
                <xsl:copy-of select="self::separator"/>
            </xsl:for-each>
        </xsl:template>
        <xsl:template match="separator"/>
    </xsl:stylesheet>
    

    :现在与之前相同,但使用键测试而不是节点集交集。

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output indent="yes"/>
        <xsl:key name="kNodeByFolSep" match="node()[not(self::separator)]"
              use="concat(generate-id(),'+',
                          generate-id((descendant::separator|
                                       following::separator)[1]))"/>
        <xsl:template match="node()|@*" name="identity">
            <xsl:param name="pSeparator"/>
            <xsl:copy>
                <xsl:apply-templates
                   select="@*|node()[descendant-or-self::node()
                                        [key('kNodeByFolSep',
                                             concat(generate-id(),
                                                    '+',
                                                    $pSeparator))]]">
                    <xsl:with-param name="pSeparator" select="$pSeparator"/>
                </xsl:apply-templates>
            </xsl:copy>
        </xsl:template>
        <xsl:template match="/*">
            <xsl:variable name="vCurrent" select="."/>
            <xsl:for-each select="descendant::separator|node()[last()]">
                <xsl:variable name="vSeparator"
                              select="generate-id(self::separator)"/>
                <xsl:for-each select="$vCurrent">
                    <xsl:call-template name="identity">
                        <xsl:with-param name="pSeparator" select="$vSeparator"/>
                    </xsl:call-template>
                </xsl:for-each>
                <xsl:copy-of select="self::separator"/>
            </xsl:for-each>
        </xsl:template>
        <xsl:template match="separator"/>
    </xsl:stylesheet>
    
        2
  •  4
  •   Dimitre Novatchev    14 年前

    <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output omit-xml-declaration="yes" indent="yes"/>
        <xsl:strip-space elements="*"/>
    
        <xsl:key name="kFollowing" match="C"
             use="generate-id(preceding::separator[1])"/>
    
     <xsl:template match="/">
      <xsl:apply-templates select="*/*/separator"/>
     </xsl:template>
    
     <xsl:template match="separator" name="commonSep">
      <separator/>
      <xsl:call-template name="genAncestors">
       <xsl:with-param name="pAncs" select="ancestor::*"/>
       <xsl:with-param name="pLeaves"
            select="key('kFollowing', generate-id())"/>
      </xsl:call-template>
     </xsl:template>
    
     <xsl:template match="separator[not(preceding::separator)]">
      <xsl:call-template name="genAncestors">
       <xsl:with-param name="pAncs" select="ancestor::*"/>
       <xsl:with-param name="pLeaves"
            select="key('kFollowing', '')"/>
      </xsl:call-template>
      <xsl:call-template name="commonSep"/>
     </xsl:template>
    
     <xsl:template name="genAncestors">
       <xsl:param name="pAncs" select="ancestor::*"/>
       <xsl:param name="pLeaves" select="."/>
    
       <xsl:choose>
        <xsl:when test="not($pAncs[2])">
         <xsl:apply-templates select="$pLeaves" mode="gen"/>
        </xsl:when>
        <xsl:otherwise>
         <xsl:for-each select="$pAncs[1]">
          <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:call-template name="genAncestors">
                   <xsl:with-param name="pAncs" select="$pAncs[position()>1]"/>
                   <xsl:with-param name="pLeaves" select="$pLeaves"/>
            </xsl:call-template>
          </xsl:copy>
         </xsl:for-each>
        </xsl:otherwise>
       </xsl:choose>
     </xsl:template>
    
     <xsl:template match="C" mode="gen">
      <xsl:variable name="vCur" select="."/>
      <xsl:for-each select="..">
       <xsl:copy>
        <xsl:copy-of select="@*|$vCur"/>
       </xsl:copy>
      </xsl:for-each>
     </xsl:template>
    </xsl:stylesheet>
    

    :

    <A>
      <B>
        <C id='1'/>
        <separator/>
        <C id='2'/>
      </B>
      <B>
        <C id='3'/>
        <separator/>
      </B>
      <B>
        <C id='4'/>
      </B>
    </A>
    

    :

    <A>
       <B>
          <C id="1"/>
       </B>
    </A>
    <separator/>
    <A>
       <B>
          <C id="2"/>
       </B>
       <B>
          <C id="3"/>
       </B>
    </A>
    <separator/>
    <A>
       <B>
          <C id="4"/>
       </B>
    </A>