Warm tip: This article is reproduced from serverfault.com, please click

xslt-xml的分组部分

(xslt - Grouping section of xml)

发布于 2020-11-29 14:59:47

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

逻辑:

  1. 处理具有模板的元素,并尝试检测何时仅包含w元素的元素。如果有其他内容,则继续“常规”处理,否则继续此序列中的下一步。
  2. container使用当前节点的内容构建一个元素,然后尝试将以下不包含w元素的相邻同级项拉containerstep带有w元素的A应该在容器外部。作为单独的元素(如果有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>

我的第二次尝试似乎给了我想要的结果,但这来自反复试验,并从结果中得到了一些免费的解释...

随意评论我的方法和问题。

Questioner
Zug_Bug
Viewed
0
Martin Honnen 2020-11-30 05:04:26

使用时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]",它基本上使我们可以选择step不具有的相邻s w我们只想包装第一个这样的序列或组,因此xsl:when test="position() = 1"使用。

所有xsl:otherwise分支机构都将收集到的所有信息推送到身份转换中。