tags:

views:

58

answers:

2

This is my Source-XML. It is originally a Word-ML which have been reduced to an own structure. Elements with name "aaa" can have any kind of name. The problem is the handling of footnotes:

<root>
  <doc>
    <comment>text text text <footnote id="1" > text text text</comment>
    <aaa>text text text</aaa>
    <aaa>text text text<footnote id="2" /> text text text </aaa>
    <aaa>text text text<aaa/> text <footnote id="3" symbol="*"/></aaa>
    <aaa>text text text text</aaa>
    <aaa>text text text text<aaa>text text text<footnote id="4"  /></aaa></aaa>
 <aaa>text text text text<aaa>text text text<footnote id="4"  /></aaa></aaa>
 <aaa>text text text text<aaa>text text text<footnote id="5"  /></aaa></aaa>
    <aaa>text text text</aaa>
  </doc>
</root>

I have to transform this Source-XML in another XML. One thing i have to do is to replace the footnotes with the correspondig Number.

But it is not possible to only use the ID-Attribute. The ID is only an internal number. The footnotes should be progressive an there are following conditions:

  • Condition 1: "footnotes" that are a child of node "comment" should be ignored. the whole node "comment" is ignored.
  • Condition 2: "footnotes" that have a symbol-attribute do not count (i have to show the symbol and not the number)
  • Condition 3: it is possible that some notes have the same id (only a low percentage, but sometimes there are more links to the same footnote).

The Output should look like this (structure is not the same, only the handling of the footnotes is for interest now):

<root>
  <doc>
    <aaa>text text text</aaa>
    <aaa>text text text[1] text text text </aaa>
    <aaa>text text text<aaa/> text [*]</aaa>
    <aaa>text text text text</aaa>
    <aaa>text text text text<aaa>text text text[2]</aaa></aaa>
 <aaa>text text text text<aaa>text text text[2]</aaa></aaa>
 <aaa>text text text text<aaa>text text text[3]</aaa></aaa>
    <aaa>text text text</aaa>
  </doc>
</root>

My first thought was to have a global counter-variable which i increment according to the conditions. But since variables in XSLT are immutable it is not really an option.

I also tried to count the previous footnotes which occur before the current footnote.

        <xsl:template match="footnote">
         <xsl:call-template name="getFootnoteNumber">
          <xsl:with-param name="footNodeId" select="@id"/>
         </xsl:call-template>
        </xsl:template>

    <!-- simple template which only counts the footnotes
    that occur before the current footnode 
    the conditions 1, 2 and 3 are not yet included -->
        <xsl:template name="getFootnoteNumber">
           <xsl:param name="footNodeId"/>
           <xsl:value-of select="count(//footnote[position()&lt;=$footNodeId])"></xsl:value-of>     
    </xsl:template>
    <!-- i mostly get value "0", 
    or other strange numbers: for footNodeID=45 i get value 75, 
    doesn't really work-->

Is there a possibility to develop a global counter?

Or do i have to go on the other way with counting the previous footnotes? Although i don't really know how to implement condition 3.

Greetings and thx for any answer cpt.oneeye

+2  A: 

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:key name="foot" match="footnote[not(ancestor::comment)][not(@symbol)]" use="@id"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="footnote[ancestor::comment]" priority="1" />
    <xsl:template match="footnote[@symbol]">
        <xsl:value-of select="concat('[',@symbol,']')"/>
    </xsl:template>
    <xsl:template match="footnote">
        <xsl:value-of select="concat('[',count(key('foot',@id)[1]/preceding::footnote[generate-id(.)=generate-id(key('foot',@id)[1])])+1,']')"/>
    </xsl:template>
</xsl:stylesheet>

With proper input:

<root>
    <doc>
        <comment>text text text <footnote id="1" /> text text text</comment>
        <aaa>text text text</aaa>
        <aaa>text text text <footnote id="2" /> text text text</aaa>
        <aaa>text text text <aaa/>text <footnote id="3" symbol="*"/></aaa>
        <aaa>text text text text</aaa>
        <aaa>text text text text <aaa>text text text <footnote id="4"  /></aaa></aaa>
        <aaa>text text text text <aaa>text text text <footnote id="4"  /></aaa></aaa>
        <aaa>text text text text <aaa>text text text <footnote id="5"  /></aaa></aaa>
        <aaa>text text text</aaa>
    </doc>
</root>

Result:

<root>
    <doc>
        <comment>text text text  text text text</comment>
        <aaa>text text text</aaa>
        <aaa>text text text [1] text text text</aaa>
        <aaa>text text text <aaa></aaa>text [*]</aaa>
        <aaa>text text text text</aaa>
        <aaa>text text text text <aaa>text text text [2]</aaa></aaa>
        <aaa>text text text text <aaa>text text text [2]</aaa></aaa>
        <aaa>text text text text <aaa>text text text [3]</aaa></aaa>
        <aaa>text text text</aaa>
    </doc>
</root>

Edit: Miss braquets. Next, I will post an answer without preceding axis.

Alejandro
Good solution, but counting on the `preceding::` axis could be a little-bit inefficient (O(N^2)) :)
Dimitre Novatchev
@Dimitre: Yes! XSLT 1.0 does not have a p/3 `key` function. But, how do you calculate a O(N^2) complexity? It's a general interpretation for O(N/2 * N + 1) (assuming "find a preceding" as algorithm complexity unit?
Alejandro
@Alejandro: In XSLT 1.0 you can use the `key()` function on more that one document. There is a trick to do this.
Dimitre Novatchev
@Alejandro: Thanks you a lot for the solution. It works great when i use the xsl on my original xmlsource. But when i add the code to my existing XSL it only works for some footnodes. mostly i get a "1" as an output. there has to be a conflict with another part of the xsl which i haven't yet figured it out.
cpt.oneeye
@Dimitre: Yes. It's posible to use `key` with any document in context (changing context with `for-each`, as example) but for building a document of "first of a kind" `footnote` I'd have to use `node-set()` extension in XSLT 1.0 and I'm a stubborn standar XSLT 1.0 guy.
Alejandro
@cpt.oneeye: Provide complete stylesheet and we could find you a solution.
Alejandro
@Alejandro: There is a de facto standard in XSLT 1.0 and it is called EXSLT. People regularly use common:node-set().
Dimitre Novatchev
+1  A: 

XSLT 2.0 solution:

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

 <xsl:key name="kPosById" match="@pos" use="../@id"/>

 <xsl:variable name="vdistinctFootnotes">
  <xsl:for-each-group group-by="@id" select=
    "/*/*/*[not(self::comment)]//footnote[not(@symbol)]">

    <xsl:copy>
      <xsl:copy-of select="@*"/>
      <xsl:attribute name="pos" select="position()"/>
    </xsl:copy>
  </xsl:for-each-group>
 </xsl:variable>

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

    <xsl:template match="footnote">
      <xsl:value-of select=
      "concat('[', key('kPosById', @id, $vdistinctFootnotes), ']')"/>
    </xsl:template>

    <xsl:template match="footnote[@symbol]">
     <xsl:value-of select="concat('[', @symbol, ']')"/>
    </xsl:template>

    <xsl:template match="footnote[ancestor::comment]"/>
</xsl:stylesheet>

when this transformation is applied on the provided document (corrected to be wellformed):

<root>
  <doc>
    <comment>text text text <footnote id="1" /> text text text</comment>
    <aaa>text text text</aaa>
    <aaa>text text text<footnote id="2" /> text text text </aaa>
    <aaa>text text text<aaa/> text <footnote id="3" symbol="*"/></aaa>
    <aaa>text text text text</aaa>
    <aaa>text text text text<aaa>text text text<footnote id="4"  /></aaa></aaa>
 <aaa>text text text text<aaa>text text text<footnote id="4"  /></aaa></aaa>
 <aaa>text text text text<aaa>text text text<footnote id="5"  /></aaa></aaa>
    <aaa>text text text</aaa>
  </doc>
</root>

the wanted result is produced:

<root>
  <doc>
    <comment>text text text  text text text</comment>
    <aaa>text text text</aaa>
    <aaa>text text text[1] text text text </aaa>
    <aaa>text text text<aaa/> text [*]</aaa>
    <aaa>text text text text</aaa>
    <aaa>text text text text<aaa>text text text[2]</aaa></aaa>
 <aaa>text text text text<aaa>text text text[2]</aaa></aaa>
 <aaa>text text text text<aaa>text text text[3]</aaa></aaa>
    <aaa>text text text</aaa>
  </doc>
</root>
Dimitre Novatchev
thank you very very much. that worked perfectly.
cpt.oneeye