为具有不同名称和不同@class属性值的多个元素创建包装元素

时间:2013-03-11 22:25:48

标签: xslt xslt-2.0 xslt-grouping

我有以下平面XML结构

<div class="section-level-1">

  <!-- other elements -->

  <p class="para">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-english">
    <img src="..." alt="..." title="..." />
  </p>

  <!-- other elements -->

  <p class="para">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..." alt="..." title="..." />
  </p>
  <misc-element>...</misc-element>
  <p class="figure-caption-english">
    <img src="..." alt="..." title="..." />
  </p>
</div>

这些元素的顺序总是相同的(para - &gt; figure-caption-german - &gt; figure-caption-english),但是我不能排除它会被其他元素打断(这里是MISC-元素)。

我想将这三个元素包装在一个元素

<div class="section-level-1">

  <!-- other elements -->

  <div class="figure">
    <p class="para">
      <img src="..." alt="..." title="..." />
    </p>
    <p class="figure-caption-german">
      <img src="..." alt="..." title="..." />
    </p>
    <p class="figure-caption-english">
      <img src="..." alt="..." title="..." />
    </p>
  </div>

  <!-- other elements -->

  <div class="figure">
    <p class="para">
      <img src="..." alt="..." title="..." />
    </p>
    <p class="figure-caption-german">
      <img src="..." alt="..." title="..." />
    </p>
    <p class="figure-caption-english">
      <img src="..." alt="..." title="..." />
    </p>
  </div>
</div>

不需要保留中断元素,可以将其删除。

到目前为止我有什么

<xsl:template match="/">
  <xsl:apply-templates />
</xsl:template>

<!-- Html Ninja Pattern -->

<xsl:template match="*">
  <xsl:element name="{name()}">
    <xsl:apply-templates select="* | @* | text()"/>
  </xsl:element>
</xsl:template>

<xsl:template match="body//@*">
  <xsl:attribute name="{name(.)}">
    <xsl:value-of select="."/>
  </xsl:attribute>
</xsl:template>

<!-- Modify certain elements -->

<xsl:template match="" priority="1">
  <!-- do something -->
</xsl:template>

作为一种基本模式,我在“Html Ninja Technique”(http://getsymphony.com/learn/articles/view/html-ninja-technique/)上绘制,因为它允许我只处理我需要转换的那些特定元素,同时将所有其他元素发送到输出树不变。 到目前为止一切正常,但现在我似乎真的遇到了障碍。我甚至不确定我是否可以依靠“Html忍者技术”来完成所期望的任务。

任何帮助或指示都将受到高度赞赏。

致以最诚挚的问候,谢谢Matthias Einbrodt

4 个答案:

答案 0 :(得分:0)

它有点牵扯,但我认为应该这样做:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="*" name="Copy">
    <xsl:element name="{name()}">
      <xsl:apply-templates select="* | @* | text()"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="@*">
    <xsl:attribute name="{name(.)}">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>

  <xsl:template match="div[starts-with(@class, 'section-level')]">
    <xsl:copy>
      <xsl:apply-templates select="@*" />
      <!-- Apply templates to paras and anything with no preceding sibling
           or with a figure-caption-english preceding sibling-->
      <xsl:apply-templates select="p[@class = 'para'] | 
                                 *[not(preceding-sibling::*) or
                                    preceding-sibling::*[1][self::p]
                                      [@class = 'figure-caption-english']
                                  ]"
                           mode="iter"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="p[@class = 'para']" mode="iter">
    <div class="figure">
      <xsl:call-template name="Copy" />
      <!-- Apply templates to the next english and german figure captions -->
      <xsl:apply-templates
        select="following-sibling::p[@class = 'figure-caption-german'][1] |
                following-sibling::p[@class = 'figure-caption-english'][1]" />
    </div>
  </xsl:template>

  <xsl:template match="*" mode="iter">
    <xsl:call-template name="Copy" />
    <xsl:apply-templates 
        select="following-sibling::*[1]
                      [not(self::p[@class = 'para'])]"
        mode="iter"/>
  </xsl:template>
</xsl:stylesheet>

应用于此样本数据时:

<div class="section-level-1">

  <!-- other elements -->
  <div>hello</div>
  <div>hello</div>
  <div>hello</div>
  <div>hello</div>
  <p class="para">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-english">
    <img src="..." alt="..." title="..." />
  </p>

  <!-- other elements -->
  <div>hello</div>
  <div>hello</div>
  <div>hello</div>

  <p class="para">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..." alt="..." title="..." />
  </p>
  <misc-element>...</misc-element>
  <p class="figure-caption-english">
    <img src="..." alt="..." title="..." />
  </p>
  <div>hello</div>
  <div>hello</div>
  <div>hello</div>
</div>

它产生:

<div class="section-level-1">
  <div>hello</div>
  <div>hello</div>
  <div>hello</div>
  <div>hello</div>
  <div class="figure">
    <p class="para">
      <img src="..." alt="..." title="..." />
    </p>
    <p class="figure-caption-german">
      <img src="..." alt="..." title="..." />
    </p>
    <p class="figure-caption-english">
      <img src="..." alt="..." title="..." />
    </p>
  </div>
  <div>hello</div>
  <div>hello</div>
  <div>hello</div>
  <div class="figure">
    <p class="para">
      <img src="..." alt="..." title="..." />
    </p>
    <p class="figure-caption-german">
      <img src="..." alt="..." title="..." />
    </p>
    <p class="figure-caption-english">
      <img src="..." alt="..." title="..." />
    </p>
  </div>
  <div>hello</div>
  <div>hello</div>
  <div>hello</div>
</div>

答案 1 :(得分:0)

这是另一种方法。这个涉及迭代 div 的子元素,但也使用 xsl:key 来分组相关的 p 元素。

首先,定义一个键,按照第一个最前面的'para'元素对'figure-caption'元素进行分组:

<xsl:key name="para" 
     match="p[starts-with(@class, 'figure-caption')]" 
     use="generate-id(preceding-sibling::p[@class='para'][1])"/>

然后,您首先匹配 div 元素,然后选择第一个元素

<xsl:template match="div">
   <div>
      <xsl:apply-templates select="node()[1]" mode="iterate"/>
   </div>
</xsl:template>

模式迭代用于指示将递归匹配其后续兄弟的模板。您首先需要一个模板来匹配'para'元素,您可以使用该键对相关元素进行分组

<xsl:template match="p[@class='para']" mode="iterate">
   <div class="figure">
      <xsl:apply-templates select=".|key('para', generate-id())" mode="group"/>
   </div>

(此处的模式将用于表示对于分组元素,匹配模板将仅输出它们,但不会在下一个兄弟处进行处理。您可以使用 xsl :复制这里或者替换)

在此模板中,您可以通过选择组中最后一个元素之后的节点来继续迭代

<xsl:apply-templates 
     select="key('para', generate-id())[last()]/following-sibling::node()[1]" mode="iterate"/>

然后,迭代中的其他元素可以与更通用的模板匹配以复制它们,并在下一个兄弟中继续

<xsl:template match="node()" mode="iterate">
   <xsl:call-template name="identity"/>
   <xsl:apply-templates select="following-sibling::node()[1]" mode="iterate"/>
</xsl:template>

身份这里将调用身份模板。

这是完整的XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" indent="yes"/>
   <xsl:key name="para" match="p[starts-with(@class, 'figure-caption')]" use="generate-id(preceding-sibling::p[@class='para'][1])"/>

   <xsl:template match="div">
      <div>
         <xsl:copy-of select="@*"/>
         <xsl:apply-templates select="node()[1]" mode="iterate"/>
      </div>
   </xsl:template>

   <xsl:template match="p[@class='para']" mode="iterate">
      <div class="figure">
         <xsl:apply-templates select=".|key('para', generate-id())" mode="group"/>
      </div>
      <xsl:apply-templates select="key('para', generate-id())[last()]/following-sibling::node()[1]" mode="iterate"/>
   </xsl:template>

   <xsl:template match="node()" mode="group">
      <xsl:call-template name="identity"/>
   </xsl:template>

   <xsl:template match="node()" mode="iterate">
      <xsl:call-template name="identity"/>
      <xsl:apply-templates select="following-sibling::node()[1]" mode="iterate"/>
   </xsl:template>

   <xsl:template match="@*|node()" name="identity">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

当应用于您的示例XML时,输出以下内容

<div class="section-level-1">
   <!-- other elements -->
   <div class="figure">
      <p class="para">
         <img src="..." alt="..." title="..."/>
      </p>
      <p class="figure-caption-german">
         <img src="..." alt="..." title="..."/>
      </p>
      <p class="figure-caption-english">
         <img src="..." alt="..." title="..."/>
      </p>
   </div>
   <!-- other elements -->
   <div class="figure">
      <p class="para">
         <img src="..." alt="..." title="..."/>
      </p>
      <p class="figure-caption-german">
         <img src="..." alt="..." title="..."/>
      </p>
      <p class="figure-caption-english">
         <img src="..." alt="..." title="..."/>
      </p>
   </div>
</div>

这种方法的一个优点是你可以抛出除英语和德语之外的其他语言,它应该仍然可以工作,语言的顺序也无关紧要。 (当然,你可能想忽略其他语言,在这种情况下它不起作用!)

答案 2 :(得分:0)

简单 XSLT 2.0解决方案

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>
 <xsl:param name="pClasses" select=
 "'para', 'figure-caption-german', 'figure-caption-english'"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="/*">
  <xsl:copy>
   <xsl:apply-templates select="@*"/>

   <xsl:for-each-group select="p[@class=$pClasses]"
     group-starting-with="p[@class eq $pClasses[1]]">
     <div class="figure">
       <xsl:apply-templates select="current-group()"/>
     </div>
    </xsl:for-each-group>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

在提供的XML文档上应用此转换时:

<div class="section-level-1">

  <!-- other elements -->

  <p class="para">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-english">
    <img src="..." alt="..." title="..." />
  </p>

  <!-- other elements -->

  <p class="para">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..." alt="..." title="..." />
  </p>
  <misc-element>...</misc-element>
  <p class="figure-caption-english">
    <img src="..." alt="..." title="..." />
  </p>
</div>

产生了想要的正确结果:

<div class="section-level-1">
   <div class="figure">
      <p class="para">
         <img src="..." alt="..." title="..."/>
      </p>
      <p class="figure-caption-german">
         <img src="..." alt="..." title="..."/>
      </p>
      <p class="figure-caption-english">
         <img src="..." alt="..." title="..."/>
      </p>
   </div>
   <div class="figure">
      <p class="para">
         <img src="..." alt="..." title="..."/>
      </p>
      <p class="figure-caption-german">
         <img src="..." alt="..." title="..."/>
      </p>
      <p class="figure-caption-english">
         <img src="..." alt="..." title="..."/>
      </p>
   </div>
</div>

答案 3 :(得分:0)

基于JLR的解决方案,我受到启发,使用不同的模板模式实现了对问题的多遍解决方案。

鉴于以下平面XML结构

<div class="section-level-1">

  <!-- other elements -->

  <p class="para">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-english">
    <img src="..." alt="..." title="..." />
  </p>

  <!-- other elements -->

  <p class="para">
    <img src="..." alt="..." title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..." alt="..." title="..." />
  </p>
  <misc-element>...</misc-element>
  <p class="figure-caption-english">
    <img src="..." alt="..." title="..." />
  </p>
</div>

我采用了以下方法。

<xsl:template match="/">
  <xsl:variable name="pass0">
    <xsl:apply-templates mode="pass0" />
  </xsl:variable>

  <xsl:variable name="pass1">
    <xsl:for-each select="$pass0">
      <xsl:apply-templates mode="pass1" />
    </xsl:for-each>
  </xsl:variable>

  <xsl:copy-of select="$pass1" />        
</xsl:template>

<!--###############
    ### Pass 0 #### 
    ###############-->

<xsl:template match="*" mode="pass0">
  <xsl:element name="{name()}">
    <xsl:apply-templates select="* | @* | text()" mode="pass0"/>
  </xsl:element>
</xsl:template>

<xsl:template match="@*" mode="pass0">
  <xsl:attribute name="{name(.)}">
    <xsl:value-of select="."/>
  </xsl:attribute>
</xsl:template>

<!-- wraps figures and their associated captions within <div class="figure"> element -->
<xsl:template match="p[@class = 'para'][img]" mode="pass0" priority="1">
  <div class="figure">
    <xsl:copy-of select="./img" />
      <xsl:apply-templates 
        select="following-sibling::p[@class = 'figure-caption-german'][1] |
                following-sibling::p[@class = 'figure-caption-english'][1]" 
        mode="fig- captions-pass0" />
  </div>
</xsl:template>

<xsl:template match="*" mode="fig-captions-pass0" priority="1">
  <xsl:copy-of select="." />
</xsl:template>

<!--###############
    ### Pass 1 #### 
    ###############-->

<xsl:template match="*" mode="pass1">
  <xsl:element name="{name()}">
    <xsl:apply-templates select="* | @* | text()" mode="pass1"/>
  </xsl:element>
</xsl:template>

<xsl:template match="@*" mode="pass1">
  <xsl:attribute name="{name(.)}">
    <xsl:value-of select="."/>
  </xsl:attribute>
</xsl:template>

<!-- removes all elements with figure captions that don't reside within <div class="figure"> element and all other unnecessary elements -->
<xsl:template match="
  p[@class = 'figure-caption-german'][not(parent::div[@class = 'figure'])] |
  p[@class = 'figure-caption-english'][not(parent::div[@class = 'figure'])] |
  misc-element" 
  mode="pass1" priority="1" />

结果我获得了所需的输出

<div class="section-level-1">

  <p class="para">normal paragraph etc.</p>
  <p class="para">normal paragraph etc.</p>
  <p class="para">normal paragraph etc.</p>

  <div class="figure">
    <img src="..." alt="..." title="..."></img>
    <p class="figure-caption-german">
      figure caption in german
    </p>
    <p class="figure-caption-english">
      figure caption in english
    </p>
  </div>

  <p class="para">normal paragraph etc.</p>
  <p class="para">normal paragraph etc.</p>
  <p class="para">normal paragraph etc.</p>

  <div class="figure">
    <img src="..." alt="..." title="..."></img>
    <p class="figure-caption-german">
      figure caption in german
    </p>
    <p class="figure-caption-english">
      figure caption in english 
    </p>
  </div>
</div>