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

Posted

技术标签:

【中文标题】为具有不同名称和不同@class 属性值的多个元素创建一个包装器元素【英文标题】:creating a wrapper element for multiple elements with different names and different @class attribute values 【发布时间】:2013-02-27 07:05:14 【问题描述】:

我有以下平面 XML 结构

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

  <!-- other elements -->

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

  <!-- other elements -->

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

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

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

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

  <!-- other elements -->

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

  <!-- other elements -->

  <div class="figure">
    <p class="para">
      <img src="..."  title="..." />
    </p>
    <p class="figure-caption-german">
      <img src="..."  title="..." />
    </p>
    <p class="figure-caption-english">
      <img src="..."  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 技术”(http://getsymphony.com/learn/articles/view/html-ninja-technique/),因为它允许我只处理那些我需要转换的特定元素,同时将所有其他元素原封不动地发送到输出树。 到目前为止一切正常,但现在我真的似乎遇到了障碍。我什至不确定是否可以依靠“Html Ninja Technique”来完成所需的任务。

任何帮助或指示将不胜感激。

最好的问候,谢谢你,马蒂亚斯·艾因布罗德

【问题讨论】:

【参考方案1】:

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

<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="..."  title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..."  title="..." />
  </p>
  <p class="figure-caption-english">
    <img src="..."  title="..." />
  </p>

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

  <p class="para">
    <img src="..."  title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..."  title="..." />
  </p>
  <misc-element>...</misc-element>
  <p class="figure-caption-english">
    <img src="..."  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="..."  title="..." />
    </p>
    <p class="figure-caption-german">
      <img src="..."  title="..." />
    </p>
    <p class="figure-caption-english">
      <img src="..."  title="..." />
    </p>
  </div>
  <div>hello</div>
  <div>hello</div>
  <div>hello</div>
  <div class="figure">
    <p class="para">
      <img src="..."  title="..." />
    </p>
    <p class="figure-caption-german">
      <img src="..."  title="..." />
    </p>
    <p class="figure-caption-english">
      <img src="..."  title="..." />
    </p>
  </div>
  <div>hello</div>
  <div>hello</div>
  <div>hello</div>
</div>

【讨论】:

非常感谢您的回答 JLRishe。特别是在图像上下文节点之后分别选择包含德语和英语标题的第一个元素的 XPath 表达式非常有见地和鼓舞人心。我利用它来实现该问题的多遍解决方案(请参阅下面的答案)。【参考方案2】:

这是另一种方法。这确实涉及迭代 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>

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

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

(这里的group模式将用于表示对于分组的元素匹配模板只会输出它们,而不是在下一个兄弟处进行处理。您可以使用xsl :copy-of 这里也可以)

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

<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>

identity这里会调用身份模板。

这是完整的 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="..."  title="..."/>
      </p>
      <p class="figure-caption-german">
         <img src="..."  title="..."/>
      </p>
      <p class="figure-caption-english">
         <img src="..."  title="..."/>
      </p>
   </div>
   <!-- other elements -->
   <div class="figure">
      <p class="para">
         <img src="..."  title="..."/>
      </p>
      <p class="figure-caption-german">
         <img src="..."  title="..."/>
      </p>
      <p class="figure-caption-english">
         <img src="..."  title="..."/>
      </p>
   </div>
</div>

这种方法的一个优点是您可以将除英语和德语之外的其他语言混合使用,它应该仍然可以工作,并且语言的顺序也无关紧要。 (当然,你可能想忽略其他语言,这样就行不通了!)

【讨论】:

您好 Tim C。谢谢您的回答。在接下来的几天里,我只能更详细地研究它。但是,我对在 XSLT 中不只有一种实现方式这一事实非常感兴趣。【参考方案3】:

一个简单 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="..."  title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..."  title="..." />
  </p>
  <p class="figure-caption-english">
    <img src="..."  title="..." />
  </p>

  <!-- other elements -->

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

产生了想要的正确结果:

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

【讨论】:

您好 Dimitre,非常感谢您的解决方案。简短而简单。在接下来的几天里,我会尝试一下,让你知道结果如何。【参考方案4】:

基于 JLRishe 的解决方案,我受到启发,使用不同的模板模式来实现该问题的多通道解决方案。

给定以下平面 XML 结构

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

  <!-- other elements -->

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

  <!-- other elements -->

  <p class="para">
    <img src="..."  title="..." />
  </p>
  <p class="figure-caption-german">
    <img src="..."  title="..." />
  </p>
  <misc-element>...</misc-element>
  <p class="figure-caption-english">
    <img src="..."  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="..."  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="..."  title="..."></img>
    <p class="figure-caption-german">
      figure caption in german
    </p>
    <p class="figure-caption-english">
      figure caption in english 
    </p>
  </div>
</div>

【讨论】:

以上是关于为具有不同名称和不同@class 属性值的多个元素创建一个包装器元素的主要内容,如果未能解决你的问题,请参考以下文章

具有多个图层和不同属性名称的 WFS GetFeature

具有相同元素名称但属性值不同的XML的XSD架构[关闭]

详解CSS样式选择器都有哪些?

仅将具有不同值的多个 GROUP_CONCAT() 的结果组合在一起

具有特定属性名称和值的 Scrapy .css 选择元素

创建/修改具有不同 QUOTED_IDENTIFIER 值的多个对象(在存储过程中)