简体   繁体   中英

Breaking the flow in output of XSLT

How can I make a XSLT to transform this:

<root>
 <element>This
  <element>is</element>
 </element>
 <element>normal!</element>
 <element>This</element>
 <element>will
  <special>break here</special>
  <element>and
   <element>also
    <special>here!</special>
   </element>
  </element>
 </element>
</root>

Into this:

<root>
 <content>This is normal! This will</content>
 <special>break here</special>
 <content>and also</content>
 <special>here!</special>
</root>

Considering <special> can appear any number of times and anywhere. I think I need to process the file two times to be able to do that, but I need to do it at once.

EDIT : Just a clarification, it isn't a simple copy, the content is also transformed from one end to another (that why I thought I need two different transformations).

EDIT2 : Here is the algorithm of what I want:

1) Go proccessing all elements until you find some special element, put it all so far inside <content>

2) If the next element is special put it inside <specialContent>

3) If there is something left go to step 1.

EDIT3 I changed my sample xml to make it clearer.

It seems that all existing answers at this time do stumble on this XML document :

<root>
    <element>This   
        <element>is</element></element>
    <element>normal!</element>
    <element>This</element>
    <element>will   
        <special>break here</special>
        <element>and    
            <element>also     
                <special>here!</special>
            </element>
        </element>
        <element>But not here</element>
    </element>
</root>

Here is a transformation that processes it correctly :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:key name="content" match="element"
  use="generate-id((ancestor::special|preceding::special)[last()])"/>

 <xsl:template match="/*">
   <xsl:copy>
    <content>
      <xsl:apply-templates select="key('content', '')"/>
    </content>
    <xsl:apply-templates select="//special" />
   </xsl:copy>
 </xsl:template>

 <xsl:template match="special">
   <xsl:copy-of select="." />
   <content>
    <xsl:apply-templates select=
       "key('content', generate-id())" />
   </content>
 </xsl:template>

 <xsl:template match="element">
   <xsl:value-of select="normalize-space(text())" />
   <xsl:text> </xsl:text>
 </xsl:template>
</xsl:stylesheet>

When applied on the XML document above, the wanted, correct result is produced :

<root>
   <content>This is normal! This will </content>
   <special>break here</special>
   <content>and also </content>
   <special>here!</special>
   <content>But not here </content>
</root>
<xsl:template match="@* | node()">
  <xsl:copy>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="content//*[not(self::SpecialContent)]">
  <xsl:apply-templates/>
</xsl:template>

should suffice.

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="root">
        <content>
            <xsl:copy-of select=".//content/text()[following-sibling::node()[1][self::subcontent]]" />
            <xsl:copy-of select=".//subcontent/text()[following-sibling::node()[1][self::subsubcontent]]" />         
            <xsl:copy-of select=".//subsubcontent/text()[following-sibling::node()[1][self::SpecialContent]]" />               
        </content>
        <xsl:for-each select=".//SpecialContent">
            <xsl:copy-of select="." />
        </xsl:for-each>
        <content>
            <xsl:copy-of select=".//subsubcontent/text()[preceding-sibling::node()[1][self::SpecialContent]]" />  
            <xsl:copy-of select=".//subcontent/text()[preceding-sibling::node()[1][self::subsubcontent]]" />
            <xsl:copy-of select=".//content/text()[preceding-sibling::node()[1][self::subcontent]]" />
        </content>
    </xsl:template>
    <xsl:template match="text()" />
</xsl:stylesheet>

not very pretty, but should work with other transformations if any

I think one way you could achieve this is to group all the element* elements by the first **special element that is either a descendant or a following element,

<xsl:key name="content" 
   match="element" 
   use="generate-id((descendant::special|following::special)[1])" />

You can then start off by matching all the speical elements in the document

 <xsl:apply-templates select="//special" />

And for each special element, you match, you can then group together all the associated element elements that have this current element as their first decendant or following element.

<content>
   <xsl:apply-templates select="key('content', generate-id())" />
</content>

So, given the following XSLT

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

   <xsl:key name="content" 
      match="element" 
      use="generate-id((descendant::special|following::special)[1])" />

   <xsl:template match="/root">
      <xsl:copy>
         <xsl:apply-templates select="//special" />
      </xsl:copy>
   </xsl:template>

   <xsl:template match="special">
      <content>
         <xsl:apply-templates select="key('content', generate-id())" />
      </content>
      <xsl:copy-of select="." />
   </xsl:template>

   <xsl:template match="element">
      <xsl:value-of select="normalize-space(text())" /><xsl:text> </xsl:text>
   </xsl:template>
</xsl:stylesheet>

When applied to you sample XML, the following is output

<root>
   <content>This is normal! This will </content>
   <special>break here</special>
   <content>and also </content>
   <special>here!</special>
</root>

(Note there is a space at the end of each content tag, but I could easily tweak the XSLT to remove this if you needed).

Dimitri answer was very good, but it would break somethings in my XSLT due to generated content and a very deep template hierarchy.

Here is the solution I end up using:

First I do all the normal processing, then I call a post-process:

<xsl:variable name="content">
   <xsl:apply-templates />
</xsl:variable>
<xsl:apply-templates select="msxsl:node-set($content)" mode="postProcess" />

In the post-process it searches for anything that is not a div with WideContent tag in its class attribute and groups the adjacent siblings inside another div, the ones with the WideContent tag are simply copied (but could as well be also group in the same manner).

<xsl:template match="/*[not(self::xhtml:div[contains(@class,'WideContent')])][preceding-sibling::*[1][self::xhtml:div[contains(@class,'WideContent')]] or position()=1]" mode="postProcess">
  <div class="NormalContent">
    <xsl:call-template name="postProcessNormalContent" />
  </div>
</xsl:template>

<xsl:template name="postProcessNormalContent" match="/*[not(self::xhtml:div[contains(@class,'WideContent')])]" mode="postProcessNormalContent">
  <xsl:copy-of select="."/>
  <xsl:apply-templates select="following-sibling::*[1]" mode="postProcessNormalContent" />
</xsl:template>

<xsl:template match="xhtml:div[contains(@class,'WideContent')]" mode="postProcess">
  <xsl:copy-of select="."/>
</xsl:template>

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

Any suggestions on how to improved that will be gladly taken.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM