如何使用xsl根据属性将xml的兄弟节点变成父子节点

How to change the siblings of xml into parent and child nodes according to their attributes using xsl

输入是

<p style="abc">hbfg</p>
<r style="cds">bhf</r>
<r style="cds"> bhfsh</r>
<p style="abc">pofj</p>
<r style="abc"> bchs</r>

预期输出应该是

<p style="abc">hbfg
    <r style="cds">bhf</r>
    <r style="cds"> bhfsh</r></p>
<p style="abc">pofj
    <r style="abc"> bchs</r></p>

如何使用 xslt 进行转换。

这里有两个版本的 XSLT 样式表,它们将处理 XML 您发布的文件,一个用于 的文件,它引入了一个方便的 xsl:for-each-group group-starting-with=pattern 这个元素 用例,并且为了获得最大的可移植性,一个用于 使用 XPath 做分组。两个版本都使用 doc/text 作为逻辑 树的根和 xsl:apply-templates 以充分利用 built-in 模板规则。注意空格处理。

平面文件转换的更多示例位于 SO 和 XSLT 1.0 FAQ,现在位于 archive.org.


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

  <xsl:template match="doc/text">
    <chapter>
      <title>
        <xsl:apply-templates select="p[@style='TRH2']"/>
      </title>
      <research>
        <title>
          <xsl:apply-templates select="p[@style='TRRef']"/>
        </title>
        <reftext>
          <xsl:apply-templates select="p[@style='TRRefText']"/>
        </reftext>
      </research>
      <sections>
        <xsl:for-each-group 
          select="p[not(@style) or @style='TRH7']"
          group-starting-with="p[@style='TRH7']"
        >
          <title>
            <xsl:apply-templates select="self::p[1]"/>
          </title>
          <paragraphs>
            <xsl:for-each select="current-group()[self::p][position()>1]">
              <para-text>
                <xsl:apply-templates/>
              </para-text>
            </xsl:for-each> 
          </paragraphs>
        </xsl:for-each-group>
      </sections>
    </chapter>
  </xsl:template>


  <xsl:template match="p[@style='TRRefText']">
     <xsl:value-of select="."/><br/>
  </xsl:template>

  <xsl:template match="foot-note">
    <footnoteref>
      <id><xsl:value-of select="@id-rel"/></id>
      <xsl:apply-templates/>
    </footnoteref>
  </xsl:template>

</xsl:transform>

XSLT 1.0 版本(在第三个 xsl:template 中)使用 XPath 用于对当前和之间的 non-title p 元素进行分组的表达式 下一个小节标题元素 (p[@style='TRH7']),以及一个 mode="para" 避免将标题同时作为标题和段落处理的子句。

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

  <xsl:template match="doc/text">
    <chapter>
      <title>
        <xsl:apply-templates select="p[@style='TRH2']" />
      </title>
      <research>
        <title>
          <xsl:apply-templates select="p[@style='TRRef']" />
        </title>
        <reftext>
          <xsl:apply-templates select="p[@style='TRRefText'] "/>
        </reftext>
      </research>
      <sections>
        <xsl:apply-templates select="p[@style='TRH7']" />
      </sections>
    </chapter>
  </xsl:template>


  <xsl:template match="p[@style='TRRefText']">
     <xsl:value-of select="."/><br/>    
  </xsl:template>

  <xsl:template match="p[@style='TRH7']">
    <title><xsl:apply-templates/></title>
    <paragraphs>
      <xsl:apply-templates mode="para"
        select="following-sibling::p[not(@style='TRH7')]
               [generate-id(preceding-sibling::p[@style='TRH7'][1])
              = generate-id(current())]"
      />
    </paragraphs>
  </xsl:template>

  <xsl:template match="p" mode="para">
    <para-text><xsl:apply-templates/></para-text>
  </xsl:template>

  <xsl:template match="foot-note">
    <footnoteref>
      <id><xsl:value-of select="@id-rel"/></id>
      <xsl:apply-templates/>
    </footnoteref>
  </xsl:template>

</xsl:transform>

更新:评论中要求的补充说明。

您自己的代码与我发布的代码非常接近,因此我将详细介绍如何使用 XSLT 1.0 对元素进行分组。文档中的每个 sub-section 都由其标题样式 (p[@style='TRH7']) 触发,激活第三个模板:

<xsl:template match="p[@style='TRH7']">
  <title><xsl:apply-templates/></title>
  <paragraphs>
    <xsl:apply-templates mode="para"
      select="following-sibling::p[not(@style='TRH7')]
             [generate-id(preceding-sibling::p[@style='TRH7'][1])
            = generate-id(current())]"
    />
  </paragraphs>
</xsl:template>

此模板发出一个 sub-section 标题(使用 built-in 模板规则),然后收集以下 non-title 段落 (following-sibling::p[not(@style='TRH7')]) 有当前 标题作为最近的逻辑 parent。回想一下 preceding-sibling 是一个反向轴,因此 p[…][1] 指的是反向文档顺序中最近的兄弟。由于 following-sibling::p[…] 选择所有后续 non-title 段落,第二个谓词 [generate-id(…)] 将选择限制为当前标题的逻辑 children。