Need help nesting siblings

2019-01-29 12:38发布

问题:

I want to make a transformation that is able to nest a set of sibling elements depending on value of attribute (outline-level).

This is the input xml file:

<text>
    <para style="Heading 1" outline-level="1">Level 1</para>
    <para xml:lang="en" style="Directive">Concept: 1</para>
    <para xml:lang="en" style="Heading 2" outline-level="2">Level 2</para>
    <para xml:lang="en" style="Directive">Concept: 2</para>
    <para xml:lang="en" style="Heading 2" outline-level="2">Level 2</para>
    <para xml:lang="en" style="Directive">Concept: 3</para>
    <para xml:lang="en" style="Heading 3" outline-level="3">Level 3</para>
    <para xml:lang="en" style="Directive">Concept: 4</para>
    <para xml:lang="en" style="Heading 3" outline-level="3">Level 3</para>
    <para xml:lang="en" style="Directive">Concept: 5</para>
    <para xml:lang="en" style="Heading 1" outline-level="1">Level 1</para>
    <para xml:lang="en" style="Directive">Concept: 6</para>
    <para xml:lang="en" style="Heading 2" outline-level="2">Level 2</para>
    <para xml:lang="en" style="Directive">Concept: 7</para>
    <para xml:lang="en" style="Heading 3" outline-level="3">Level 3</para>
    <para xml:lang="en" style="Directive">Concept: 8</para>
</text>

And this is what I want to acheive:

<root>
    <para id="Concept: 1">Level 1
        <para id="Concept: 2">Level 2</para>
        <para id="Concept: 3">Level 2
            <para id="Concept: 4">Level 3</para>
            <para id="Concept: 5">Level 3</para>
        </para>
    </para>
    <para id="Concept: 6">Level1
        <para id="Concept: 7">Level 2
            <para id="Concept: 8"/>
        </para>
    </para>
</root>

The nesting level have to be determined by the value of @outline-level. The bigger is that number the deeper is the element nested.

Any ideas would be highly appreciated.

回答1:

Try it this way?

XSLT 1.0

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

<xsl:key name="child-paras" match="para" use="generate-id(preceding-sibling::para[@outline-level = current()/@outline-level - 1][1])" />

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

<xsl:template match="/text">
    <root>
        <xsl:apply-templates select="para[@outline-level=1]"/>
    </root>
</xsl:template>

<xsl:template match="para">
    <para id="{following-sibling::para[1]}">
        <xsl:value-of select="." />
        <xsl:apply-templates select="key('child-paras', generate-id())"/>
    </para>
</xsl:template>

</xsl:stylesheet>

When applied to your example input, the result will be:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <para id="Concept: 1">Level 1<para id="Concept: 2">Level 2</para>
      <para id="Concept: 3">Level 2<para id="Concept: 4">Level 3</para>
         <para id="Concept: 5">Level 3</para>
      </para>
   </para>
   <para id="Concept: 6">Level 1<para id="Concept: 7">Level 2<para id="Concept: 8">Level 3</para>
      </para>
   </para>
</root>

which (other than differences in indenting) is identical to your expected output - except that the last para where id="Concept: 8" contains the text node "Level 3" - which I believe is correct.



标签: xml xslt xpath