xml 的分组部分

Grouping section of xml

我在将输入树的一部分分组到容器元素中并保持其他部分不变时遇到了一些问题。我正在尝试使用 for-each-group 作为练习。

逻辑:

  1. 处理具有模板匹配的元素,并尝试检测元素何时仅包含 w 个元素。如果是其他内容,则继续“正常”处理,否则按此顺序继续下一步。
  2. 用当前节点内容构建一个 container 元素,并尝试将不包含 w 元素的以下相邻兄弟拉入 container。带有 w 元素的 step 应该在容器外部。作为单独的元素(如果有 w 和其他元素),或作为新容器(如果只有 w 个子元素)。

输入示例(示例中的body元素可以看作是一棵大树的片段):

<?xml version="1.0" encoding="UTF-8"?>
<body>
    <step>
        <p>step 1</p>
    </step>
    <step>
        <p>step 2</p>
    </step>
    <step>
        <w>Warning A</w>
        <p>step 3</p>
    </step>
    <step>
        <p>step 4</p>
    </step>
    <step>
        <p>step 5</p>
    </step>
    <step>
        <w>Spec Warning X</w>
        <w>Spec Warning Y</w>
    </step>
    <step>
        <p>step 6</p>
    </step>
    <step>
        <p>step 7</p>
    </step>
    <step>
        <p>step 8</p>
    </step>
    <step>
        <p>step 9</p>
    </step>
    <step>
        <p>step 10</p>
    </step>
    <step>
        <p>step 11</p>
    </step>
    <step>
        <w>Warning B</w>
        <p>step 12</p>
    </step>
    <step>
        <p>step 13</p>
    </step>
    <step>
        <p>step 14</p>
    </step>    
</body>

期望的输出:

<?xml version="1.0" encoding="UTF-8"?>
<body>
    <step>
        <p>step 1</p>
    </step>
    <step>
        <p>step 2</p>
    </step>
    <step>
        <w>Warning A</w>
        <p>step 3</p>
    </step>
    <step>
        <p>step 4</p>
    </step>
    <step>
        <p>step 5</p>
    </step>
    <container>
        <w>Spec Warning X</w>
        <w>Spec Warning Y</w>
         <step>
            <p>step 6</p>
        </step>
        <step>
            <p>step 7</p>
        </step>
        <step>
            <p>step 8</p>
        </step>
        <step>
            <p>step 9</p>
        </step>
        <step>
            <p>step 10</p>
        </step>
        <step>
            <p>step 11</p>
        </step>
    </container>
    <step>
        <w>Warning B</w>
        <p>step 12</p>
    </step>
    <step>
        <p>step 13</p>
    </step>
    <step>
        <p>step 14</p>
    </step>    
</body>

初步测试:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />

    <xsl:template match="/">
        <xsl:element name="body">
          <xsl:apply-templates select="*"/>  
        </xsl:element>        
    </xsl:template>

    <xsl:template match="step[w and not(p)]">
        <xsl:element name="container">
           <xsl:apply-templates/>
            <xsl:for-each-group select="following-sibling::*" group-adjacent="self::step[not(w)]">
                <xsl:copy-of select="current-group()"/>
            </xsl:for-each-group>
        </xsl:element>
    </xsl:template>    
    
    <xsl:template match="step[p]">
        <xsl:copy-of select="."/>
    </xsl:template>
    
    <xsl:template match="w">
        <xsl:copy-of select="."/>
    </xsl:template>
    
    <xsl:template match="step[p and not(w)][preceding-sibling::step[w][1][not(p)]]"/>
</xsl:transform>

结果(http://xsltransform.net/eixk6Sw/2):

<body>
    <step>
        <p>step 1</p>
    </step>
    <step>
        <p>step 2</p>
    </step>
    <step>
        <w>Warning A</w>
        <p>step 3</p>
    </step>
    <step>
        <p>step 4</p>
    </step>
    <step>
        <p>step 5</p>
    </step>
    <container>
        <w>Spec Warning X</w>
        <w>Spec Warning Y</w>
      <step>
        <p>step 6</p>
      </step>
      <step>
        <p>step 7</p>
      </step>
      <step>
        <p>step 8</p>
      </step>
      <step>
        <p>step 9</p>
      </step>
      <step>
        <p>step 10</p>
      </step>Error on line 14 
  XTTE1100: An empty sequence is not allowed as the @group-adjacent attribute of xsl:for-each-group
  in built-in template rule
  at xsl:apply-templates (#7)
     processing /body

我目前的问题是我看不到如何使用分组技术,以及如何将处理限制在第一组(即上下文节点之后的组),而不是处理所有组。

第二次尝试:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
    
    <xsl:template match="/">
        <body>
            <xsl:apply-templates select="*"/>
        </body>
    </xsl:template>
    
    <xsl:template match="step[w and not(p)]">   <!-- Find a step with w elements only. -->
        <xsl:element name="container">
            <xsl:apply-templates/>  <!-- Get content from current node. -->
            
            <!-- This where it gets dicey and I'm guessing a lot -->
            <!-- Get all following adjacent elements in groups, where the interesting group is 
                 the first one containing step elements with no w elements.
                 So group element that doesn's include a w element.-->
            <xsl:for-each-group select="following-sibling::*" group-adjacent="boolean(self::step[not(w)])">
                <!-- Check if the group actually is according to the criteria. The group can include other nodes as well? -->
                <!-- And also check if the first preceding step with a w element also lacks any p elements. 
                     If so, this has to be the first group. -->
                <xsl:if test="current-grouping-key() and preceding-sibling::step[w][1][not(p)]">
                    <xsl:sequence select="current-group()"/>
                </xsl:if>
            </xsl:for-each-group>
        </xsl:element>
    </xsl:template>    
    
    <xsl:template match="step[w and p] | step[p][not(preceding-sibling::step[w][1][not(p)])]">
        <xsl:copy-of select="."/>
    </xsl:template>
    
    <xsl:template match="w ">
        <xsl:copy-of select="."/>
    </xsl:template>
    
    <xsl:template match="step[p and not(w)][preceding-sibling::step[w][1][not(p)]]"/>
</xsl:transform>

我知道我可以通过仅使用 w 元素找到我的步骤来实现这一点,然后应用一个模板以特殊模式处理下一步兄弟姐妹,并让该模板拉下一个兄弟姐妹没有 w 元素等等。这按预期工作,但我想为此学习其他技术:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />

    <xsl:template match="/">
        <xsl:element name="body">
          <xsl:apply-templates select="*"/>  
        </xsl:element>        
    </xsl:template>

    <xsl:template match="step[w and not(p)]">
        <xsl:element name="container">
           <xsl:apply-templates/>
            <xsl:apply-templates select="following-sibling::*[1][self::step[p and not(w)]]" mode="keep"/>
        </xsl:element>
    </xsl:template>    
    
    <xsl:template match="step[p]" mode="keep">
        <xsl:copy-of select="."/>
        <xsl:apply-templates select="following-sibling::*[1][self::step[p and not(w)]]" mode="keep"/>
    </xsl:template>
    
    <xsl:template match="step[p]">
        <xsl:copy-of select="."/>
    </xsl:template>
    
    <xsl:template match="w">
        <xsl:copy-of select="."/>
    </xsl:template>
    
    <xsl:template match="step[p and not(w)][preceding-sibling::step[w][1][not(p)]]"/>
</xsl:transform>

我的第二次尝试似乎得到了我想要的结果,但这是反复试验的结果,以及对结果的一些自由解释...

欢迎对我的方法和问题发表评论。

在使用for-each-group时,我倾向于在父模板(例如body)中使用它,并使用项目(例如steps)作为总体.我不确定我是否已经完全理解唯一样本的要求,但假设我们可以重新表述第二个要求,试图找到具有 w 的第一个项目,嵌套分组可能会起作用:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">
    
  <xsl:strip-space elements="*"/>
  <xsl:output indent="yes"/>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="body">
      <xsl:copy>
          <xsl:for-each-group select="step" group-starting-with="step[w and not(p)]">
              <xsl:choose>
                  <xsl:when test="w and not(p)">
                      <xsl:variable name="wrapper" select="."/>
                      <xsl:for-each-group select="tail(current-group())" group-ending-with="step[w]">
                          <xsl:choose>
                              <xsl:when test="position() = 1">
                                <container>
                                    <xsl:apply-templates select="$wrapper, current-group()[position() lt last()]"/>
                                </container>
                                <xsl:apply-templates select="current-group()[last()]"/>
                              </xsl:when>
                              <xsl:otherwise>
                                  <xsl:apply-templates select="current-group()"/>
                              </xsl:otherwise>
                          </xsl:choose>
                      </xsl:for-each-group>
                  </xsl:when>
                  <xsl:otherwise>
                      <xsl:apply-templates select="current-group()"/>
                  </xsl:otherwise>
              </xsl:choose>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>
  
</xsl:stylesheet>

外部 xsl:for-each-group select="step" group-starting-with="step[w and not(p)]" 应该标识您的 container 元素,与 group-starting-with 一样,您可以获得一个不是由内部模式形成的组,如果我们只换行有一个想要的 step 组,我们必须重新检查条件 test="w and not(p)".

然后在里面,为了识别要包装的项目的“结束”,使用了第二个分组:xsl:for-each-group select="tail(current-group())" group-ending-with="step[w]",它基本上允许我们 select 相邻的 steps没有 w。我们只想包装第一个这样的序列或组,因此使用 xsl:when test="position() = 1"

所有 xsl:otherwise 分支只是将收集到的所有内容推送到身份转换。