views:

61

answers:

3

given the following structure:

<TITEL1>...</TITEL1>  
<p>..</p>
<TITEL2>...</TITEL2>  
<TITEL3>...</TITEL3>
<TITEL3>...</TITEL3>  
<P>...<P>  

is there a way to get to this:

<TITEL1>
    <TITEL>...</TITEL>  
    <p>...</p>
    <TITEL2>
        <TITEL>...</TITEL>  
        <TITEL3>
            <TITEL>...</TITEL>
            <P>...</P>
        </TITEL3>
        <TITEL3>
            <TITEL>...</TITEL>
            <P>...</P>
        </TITEL3>
    </TITEL2>
</TITEL1>

or in other words,is there a way to have higher level titels inclose lower level titels and all content that follows them, thus creating a nested structure. The content of each TITEL1,2 and 3 tag should go into a new <TITEL>-element

+2  A: 

There isn't a particularly eligant way of doing what you want. It's (probably) possible, but it would involve some pretty ugly (and slow) XPath queries using the following-sibling axis with filters on the preceding-sibling axis matching back to the current node.

If it's at all a possibility, I would recommend creating the hierarchy outside of XSLT (in C#, Java, etc)

If you choose to go down the scary path, you would be looking to do something like this (untested):

<xsl:template match="TITEL1">
  <TITEL1>
    <xsl:apply-templates 
      select="following-sibling::(p|TITEL2)[(preceding-sibling::TITEL1)[1]=.]" />
  </TITEL1>
</xsl:template>

<xsl:template match="TITEL2">
  <TITEL1>
    <xsl:apply-templates 
      select="following-sibling::(p|TITEL3)[(preceding-sibling::TITEL2)[1]=.]" />
  </TITEL1>
</xsl:template>

...

This is only an example, and I can already see problems with the match. Coming up with the final XPath query would be quite involved, if it's actually possible at all.

Richard Szalay
i don't know anything about c# or java. Maybe i should try to do this in PHP.
AZtec
I've asked the same question on the php-side of the forum. I still am going to try out your solution and am welcoming other solutions if it can be done with XSLT. Thx for the advice.
AZtec
Could you tell me what's the quickest way to get my reputation points to 15 so i can give answers an up-vote?
AZtec
I've voted up your question, but that only puts you on 11. Marking my answer as "the answer" will give you an additional 2, but don't do that unless it has actually helped. Otherwise, ask/answer questions.
Richard Szalay
+2  A: 

With XSLT 2.0 (as implemented by Saxon 9 or AltovaXML tools) you can use xsl:for-each-group group-starting-with and a recursive function:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:mf="http://example.com/2010/mf"
  exclude-result-prefixes="xsd mf">

  <xsl:output indent="yes"/>

  <xsl:function name="mf:nest" as="element()*">
    <xsl:param name="elements" as="element()*"/>
    <xsl:param name="level" as="xsd:integer"/>
    <xsl:for-each-group select="$elements" group-starting-with="*[starts-with(local-name(), concat('TITEL', $level))]">
      <xsl:choose>
        <xsl:when test="self::*[starts-with(local-name(), concat('TITEL', $level))]">
          <xsl:element name="TITEL{$level}">
            <xsl:apply-templates select="."/>
            <xsl:sequence select="mf:nest(current-group() except ., $level + 1)"/>
          </xsl:element>
        </xsl:when>
        <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
  </xsl:function>

  <xsl:template match="ROOT">
    <xsl:sequence select="mf:nest(*, 1)"/>
  </xsl:template>

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

  <xsl:template match="*[starts-with(local-name(), 'TITEL')]">
    <TITEL>
      <xsl:apply-templates select="@* | node()"/>
    </TITEL>
  </xsl:template>

</xsl:stylesheet>

With that stylesheet the input

<ROOT>
<TITEL1>Titel 1, 1</TITEL1>  
<p>..</p>
<TITEL2>Titel 2, 1</TITEL2>  
<TITEL3>Titel 3, 1</TITEL3>
<TITEL3>Titel 3, 2</TITEL3>  
<P>...</P>
</ROOT>

is transformed to the output

<TITEL1>
   <TITEL>Titel 1, 1</TITEL>
   <p>..</p>
   <TITEL2>
      <TITEL>Titel 2, 1</TITEL>
      <TITEL3>
         <TITEL>Titel 3, 1</TITEL>
      </TITEL3>
      <TITEL3>
         <TITEL>Titel 3, 2</TITEL>
         <P>...</P>
      </TITEL3>
   </TITEL2>
</TITEL1>
Martin Honnen
Hi Martin, this is very nice. For my current project i've used a PHP solution i've come up with myself. The code is not half as nice as this is but it has to cater with some other factors which would take too long to explain here. I'll most certainly use this code in the near future. Thanx for the help!!
AZtec
+1  A: 

If you can't use XSLT 2.0, here is an XSLT 1.0 stylesheet that should produce the same result as the XSLT 2.0 stylesheet I posted earlier:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:output indent="yes"/>

  <xsl:template match="ROOT">
    <xsl:apply-templates select="*[1]" mode="nest">
      <xsl:with-param name="level" select="1"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="*[starts-with(local-name(), 'TITEL')]" mode="nest">
    <xsl:param name="level"/>
    <xsl:choose>
      <xsl:when test="$level = substring-after(local-name(), 'TITEL')">
        <xsl:element name="TITEL{$level}">
          <xsl:apply-templates select="."/>
          <xsl:apply-templates select="following-sibling::*[1][not(starts-with(local-name(), concat('TITEL', $level)))]" mode="nest">
            <xsl:with-param name="level" select="$level"/>
          </xsl:apply-templates>
        </xsl:element>
        <xsl:apply-templates select="following-sibling::*[starts-with(local-name(), concat('TITEL', $level))][1]" mode="nest">
          <xsl:with-param name="level" select="$level"/>
        </xsl:apply-templates>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="." mode="nest">
          <xsl:with-param name="level" select="$level + 1"/>
        </xsl:apply-templates>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="*[not(starts-with(local-name(), 'TITEL'))]" mode="nest">
    <xsl:param name="level"/>
    <xsl:apply-templates select="."/>
    <xsl:apply-templates select="following-sibling::*[1][not(starts-with(local-name(), concat('TITEL', $level)))]" mode="nest">
      <xsl:with-param name="level" select="$level"/>
    </xsl:apply-templates>
  </xsl:template>

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

  <xsl:template match="*[starts-with(local-name(), 'TITEL')]">
    <TITEL>
      <xsl:apply-templates select="@* | node()"/>
    </TITEL>
  </xsl:template>

</xsl:stylesheet>
Martin Honnen