Create an hierarchical xml form an “flat” xml a li

2019-02-20 21:54发布

问题:

This is a following up question to question to "Select all of an element between the current element and the next of the current element". Even If I'm not sure if creating a new question is the right way I do it anyway. Because the original question was answered but changed afterwards. So in my view the changed questions is open. Also I think the changed question should be put back in the stat were it fit to the answer.

The question is how to create an hierarchical xml form an "flat" xml like a book description.

The input xml is something like

<root>
    <heading_1>Section 1</heading_1>
    <para>...</para>
    <list_1>...</list_1>
    <heading_2>Section 1.1</heading_2>
    <para>...</para>
    <heading_3>Section 1.1.1</heading_3>
    <para>...</para>
    <list_1>...</list_1>
    <heading_2>Section 1.2</heading_2>
    <para>...</para>
    <footnote>...</footnote>
    <heading_1>Section 2</heading_1>
    <para>...</para>
    <list_1>...</list_1>
    <heading_2>Section 2.1</heading_2>
    <para>...</para>
    <list_1>...</list_1>
    <list_2>...</list_2>
    <heading_3>Seciton 2.1.1</heading_3>
    <para>...</para>
    <heading_2>Section 2.2</heading_2>
    <para>...</para>
    <footnote>...</footnote>
</root>

Each <heading_*> should be interpreted as start of an <section> The expected output xml is.

<section>
    <title>Section 1</title>
    <para>...</para>
    <list_1>...</list_1>
    <section>
        <title>Section 1.1</title>
        <para>...</para>
        <section>
            <title>Section 1.1.1</title>
            <para>...</para>
            <list_1>...</list_1>
        </section>
    </section>
    <section>
        <title>Section 1.2</title>
        <para>...</para>
        <footnote>...</footnote>
    </section>
</section>
<section>
    <title>Section 2</title>
    <para>...</para>
    <list_1>...</list_1>
    <section>
        <title>Section 2.1</title>
        <para>...</para>
        <list_1>...</list_1>
        <list_2>...</list_2>
        <section>
            <title>Section 2.1.1</title>
            <para>...</para>
        </section>
    </section>
    <section>
        <title>Section 2.2</title>
        <para>...</para>
        <footnote>...</footnote>
    </section>
</section>

Also I tried a while to find a solution for this bases on the original solution from @JLRishe. So I found one and like to put it here as answer as one possibility. And I hope for a more comprehensible solution.

回答1:

Here is a generic solution that will work for any heading depth:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  <xsl:key name="kChildHeader" 
           match="/*/*[starts-with(local-name(), 'heading_')]"
           use="generate-id(preceding-sibling::*
                       [local-name() = 
                        concat('heading_', 
                               substring-after(local-name(current()), '_') - 1
                              )][1]
                            )"/>
  <xsl:key name="kChildItem" 
           match="/*/*[not(starts-with(local-name(), 'heading_'))]"
           use="generate-id(preceding-sibling::*
                             [starts-with(local-name(), 'heading_')][1])"/>

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

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

  <xsl:template match="*[starts-with(local-name(), 'heading_')]">
    <xsl:copy>
      <xsl:apply-templates select="key('kChildHeader', generate-id()) |
                                   key('kChildItem', generate-id())"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

When run on your sample input, this produces:

<root>
  <heading_1>
    <para>...</para>
    <list_1>...</list_1>
    <heading_2>
      <para>...</para>
      <heading_3>
        <para>...</para>
        <list_1>...</list_1>
      </heading_3>
    </heading_2>
    <heading_2>
      <para>...</para>
      <footnote>...</footnote>
    </heading_2>
  </heading_1>
  <heading_1>
    <para>...</para>
    <list_1>...</list_1>
    <heading_2>
      <para>...</para>
      <list_1>...</list_1>
      <list_2>...</list_2>
      <heading_3>
        <para>...</para>
      </heading_3>
    </heading_2>
    <heading_2>
      <para>...</para>
      <footnote>...</footnote>
    </heading_2>
  </heading_1>
</root>


回答2:

Here my current solution

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    extension-element-prefixes="exsl">

    <xsl:output method="xml" indent="yes"/>

    <xsl:variable name="headerLevel_txt">
        <heading name="__none__" level="0"/>
        <heading name="heading_1" level="1"/>
        <heading name="heading_2" level="2"/>
        <heading name="heading_3" level="3"/>
    </xsl:variable>

    <xsl:variable name="headerLevel" select="exsl:node-set($headerLevel_txt)" />


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

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


    <xsl:template match="heading_1 | heading_2 | heading_3" >
        <xsl:param name="previousheaader" select="'__none__'" />

        <xsl:variable name="endOfLevel" >
            <xsl:call-template name="endOfLevel">
                <xsl:with-param name="currentheaader" select="name()" />
                <xsl:with-param name="previousheaader" select="$previousheaader" />
            </xsl:call-template>
        </xsl:variable>

        <xsl:if test ="$endOfLevel != 'end'" >
            <section>
                <title>
                    <xsl:value-of select="normalize-space(.)"/>
                </title>

                <!-- following siblings including next heading  -->
                <xsl:variable name="fsinh" select="following-sibling::*[ 
                              generate-id( preceding-sibling::*
                                           [
                                             name() = 'heading_1' or name() = 'heading_2' or  name() = 'heading_3'
                                           ][1]
                                          ) = generate-id(current()) ]" />

                    <xsl:apply-templates select="$fsinh[ position()!=last()]" >
                        <xsl:with-param name="previousheaader" select="name()" />
                    </xsl:apply-templates>

                <!-- following siblings heading same as next   -->
                <xsl:variable name="fshsan" select="following-sibling::*[  
                              name() = name($fsinh[last()]) and
                              generate-id( preceding-sibling::*
                                           [
                                             name() = name(current())
                                           ][1]
                                          ) = generate-id(current()) ]" />
                <xsl:apply-templates select="$fshsan" >
                    <xsl:with-param name="previousheaader" select="name()" />
                </xsl:apply-templates>

            </section>
        </xsl:if>

    </xsl:template>

    <xsl:template name="endOfLevel">
        <xsl:param name ="currentheaader"/>
        <xsl:param name ="previousheaader"/>
        <!-- The previous heading ends if the current has an higher level (smaller level number)-->
        <xsl:variable name ="cl" select="number($headerLevel/heading[@name=$currentheaader]/@level)"/>
        <xsl:variable name ="pl" select="number($headerLevel/heading[@name=$previousheaader]/@level)"/>
        <xsl:if test ="$cl &lt; $pl">end</xsl:if>
    </xsl:template>

</xsl:stylesheet>

It will generate the following output:

<root>
  <section>
    <title>Section 1</title>
    <para>...</para>
    <list_1>...</list_1>
    <section>
      <title>Section 1.1</title>
      <para>...</para>
      <section>
        <title>Section 1.1.1</title>
        <para>...</para>
        <list_1>...</list_1>
      </section>
    </section>
    <section>
      <title>Section 1.2</title>
      <para>...</para>
      <footnote>...</footnote>
    </section>
  </section>
  <section>
    <title>Section 2</title>
    <para>...</para>
    <list_1>...</list_1>
    <section>
      <title>Section 2.1</title>
      <para>...</para>
      <list_1>...</list_1>
      <list_2>...</list_2>
      <section>
        <title>Seciton 2.1.1</title>
        <para>...</para>
      </section>
    </section>
    <section>
      <title>Section 2.2</title>
      <para>...</para>
      <footnote>...</footnote>
    </section>
  </section>
</root>


标签: xslt