XSLT:在特定标记处分解标记结构

时间:2014-06-09 11:08:31

标签: xslt

我有一个使用XSLT 2.0 处理的XML文件,它可以包含一个附加到此DTD片段的标记

<!ELEMENT p (#PCDATA|foo|bar|table)* >

这就是输入文件看起来像这样(只是所有可能变体之一)

<p>foobar
  <table attr1="a" attr2="b">...</table>
  <foo fooattr="FOO">fdaghd</foo><bar>something</bar>sometext
  <table attr1="B">...</table>
</p>

我需要将其转换为(必须保留名称空间,不使用副本或副本,因为这些会创建xmlns=""属性)

<p>foobar</p>
<table attr1="a" attr2="b">...</table>
<p>
  <foo fooattr="FOO">fdaghd</foo>
  <bar>something</bar>
  sometext
</p>
<table attr1="B">...</table>

那是&#34;分裂&#34;每当找到<p> - 代码时都会<table> - 标记,并在之后继续<p>(如果还有剩下的孩子)。

请注意,这也是一个有效的输入示例

    <p><table attr1="a" attr2="b">...</table></p>

应该转换为

    <table attr1="a" attr2="b">...</table>

并且这也是一个有效的输入示例

    <p>bbbb<foo>aaaa</foo></p>

根本不应该转换,输出应该是

    <p>bbbb<foo>aaaa</foo></p>

到目前为止我的XSLT包含了这个

<xsl:template match="p[table]">
  <xsl:call-template name="split-paragraph">
    <xsl:with-param name="tables" select="table"/>
  </xsl:call-template>
</xsl:template>

<xsl:template name="split-paragraph">
  <xsl:param name="tables"/>

  <xsl:if test="$tables">
    <xsl:for-each select="$tables[1]">
      <xsl:if test="not(preceding-sibling::node//table)">
          <p><xsl:apply-templates select="preceding-sibling::node()[not(table)]"/></p>
      </xsl:if>

      <xsl:apply-templates select="."/>
      <xsl:if test="not(following-sibling::node()//table)">
          <p><xsl:apply-templates select="following-sibling::node[. &lt;&lt; following-sibling::node()[not(name()='table')][1]]"/></p>
      </xsl:if>

      <xsl:call-template name="split-paragraph">
        <xsl:with-param name="tables"
                        select="$tables[position() > 1]"/>
      </xsl:call-template>
    </xsl:for-each>
  </xsl:if>
</xsl:template>

<xsl:template match="table">
  <xsl:element name="table">
    <xsl:apply-templates select="attribute()"/>
    <xsl:apply-templates select="node()"/>
  </xsl:element>
</xsl:template>

<xsl:template match="element()"><xsl:copy/></xsl:template>

适用于

<p>foo
  <table attr1="gazonk"><a>bar</a></table>
  <bar>xyzzy</bar>
  <table attr2="2"><b>fie</b></table>
  shfjkdashndk
</p>

产生

<p>foo</p>
<table attr1="gazonk">
  <a>bar</a>
</table>
<p>
</p>
<p>foo
  <table attr1="gazonk">
    <a>bar</a>
  </table>
  <bar>xyzzy</bar>
</p>
<table attr2="2">
  <b>fie</b>
</table>
<p>shfjkdashndk</p>

这不是所需的输出。我想要这个

<p>foo</p>
<table attr1="gazonk">
  <a>bar</a>
</table>
<p><bar>xyzzy</bar></p>
<table attr2="2">
  <b>fie</b>
</table>
<p>shfjkdashndk</p>

2 个答案:

答案 0 :(得分:0)

尝试以下样式表:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">

    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>

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

    <xsl:template match="p[table]/child::text()">
        <p><xsl:value-of select="normalize-space(.)"/></p>
    </xsl:template>

    <xsl:template match="p[table]/*[not(self::table)]">
        <p>
            <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
            </xsl:copy>
        </p>
    </xsl:template>

    <xsl:template match="p[table]">
        <xsl:apply-templates select="node()|@*"/>
    </xsl:template>

</xsl:stylesheet>

答案 1 :(得分:0)

如果您使用的是XSLT 1.0,则可以将其视为分组问题。您正在将非表元素分组在一起,并且对要在节点之前分组的表元素的数量进行分组。使用Muenchian grouping,您可以像这样定义一个键

<xsl:key name="group" match="p/node()[not(self::table)]" use="concat(generate-id(..), '-', count(preceding-sibling::table))" />

generate-id()可以处理XML中的多个 p 元素

然后你可以通过一个模板来匹配 p 元素开始,你会跳过元素,但是他们选择它的子元素,使用“模式”来指示特殊处理(和避免使用两个匹配同一节点的模板)

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

您需要一个模板来匹配每个组中出现的第一个非表格元素,这样您就可以在其周围添加 p 标记以及该组中的其他项目,就像这样< / p>

<xsl:template match="node()[generate-id() = generate-id(key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))[1])]" mode="group">
    <p>
        <xsl:apply-templates select="key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))"/>
    </p>
</xsl:template>

(注意,XSLT将使用Identity Template将其他节点输出到输出)

匹配表格更直接:

 <xsl:template match="table" mode="group"> 
    <xsl:apply-templates select="." />
</xsl:template>

最后,您需要一个模板来匹配前两个模板未拾取的其他节点,以忽略它们(因为之前的“组”模板已经输出它们。这就是“模式”的使用变为表观的)

<xsl:template match="node()" mode="group" /> 

试试这个XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:key name="group" match="p/node()[not(self::table)]" use="concat(generate-id(..), '-', count(preceding-sibling::table))" />

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

    <xsl:template match="node()[generate-id() = generate-id(key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))[1])]" mode="group">
        <p>
            <xsl:apply-templates select="key('group', concat(generate-id(..), '-', count(preceding-sibling::table)))"/>
        </p>
    </xsl:template>

     <xsl:template match="table" mode="group"> 
        <xsl:apply-templates select="." />
    </xsl:template>

    <xsl:template match="node()" mode="group" /> 

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

编辑:为了应对命名空间,在XSLT 1.0中,您必须使用p替换xpath表达式中的*[local-name() = 'p']的所有实例,对于表也是如此。您还必须使用 xsl:element 来创建具有相同命名空间的新 p 元素。

试试这个XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:key name="group" match="*[local-name() = 'p']/node()[not(self::*[local-name() = 'table'])]" use="concat(generate-id(..), '-', count(preceding-sibling::*[local-name() = 'table']))" />

    <xsl:template match="*[local-name() = 'p']">
        <xsl:apply-templates mode="group" />
    </xsl:template>

    <xsl:template match="node()[generate-id() = generate-id(key('group', concat(generate-id(..), '-', count(preceding-sibling::*[local-name() = 'table'])))[1])]" mode="group">
        <xsl:element name="p" namespace="{namespace-uri(..)}">
            <xsl:apply-templates select="key('group', concat(generate-id(..), '-', count(preceding-sibling::*[local-name() = 'table'])))"/>
        </xsl:element>

    </xsl:template>

     <xsl:template match="*[local-name() = 'table']" mode="group"> 
        <xsl:apply-templates select="." />
    </xsl:template>

    <xsl:template match="node()" mode="group" /> 

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

EDIT 2.0

以下是XSLT 2.0解决方案,您可以使用 xsl:for-each-group 对元素进行分组。在这种情况下,我将相邻的元素组合在一起,这取决于它们是否是一个表。

也不是在检查命名空间时使用通配符(这将会处理名称空间是否存在)

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="*:p">
        <xsl:for-each-group select="node()" group-adjacent="boolean(self::*:table)">
            <xsl:choose>
                <xsl:when test="self::*:table">
                    <xsl:apply-templates select="current-group()" />
                </xsl:when>
                <xsl:otherwise>
                    <xsl:element name="p" namespace="{namespace-uri(..)}">
                        <xsl:apply-templates select="current-group()" />
                    </xsl:element>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each-group>
    </xsl:template>

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