tags:

views:

53

answers:

2

I have the following XML

<data>
 <records>
   <record name="A record">
     <info>A1</info>
     <info>A2</info>
   </record>
   <record name="B record"/>
   <record name="C record">
     <info>C1</info>
   </record>
  </records>
</data>

how can I transform into following output, the problem is how can I count between on record, and record/info?

<div id="1">
    <p>A record</p>
    <span id="1">A1</span>
    <span id="2">A2</span> 
</div> 
<div id="2">
    <p>C record</p>   
    <span id="3">C1</span> 
</div>
+2  A: 

Solution 1. Fine grained traversal. This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:template match="records">
        <xsl:apply-templates select="*[1]"/>
    </xsl:template>
    <xsl:template match="record"/>
    <xsl:template match="record[node()]">
        <xsl:param name="pRecordNum" select="1"/>
        <xsl:param name="pInfoNum" select="1"/>
        <div id="{$pRecordNum}">
            <xsl:apply-templates select="@*|*[1]">
                <xsl:with-param name="pInfoNum" select="$pInfoNum"/>
            </xsl:apply-templates>
        </div>
        <xsl:apply-templates select="following-sibling::record[node()][1]">
            <xsl:with-param name="pRecordNum" select="$pRecordNum +1"/>
            <xsl:with-param name="pInfoNum" select="$pInfoNum + count(info)"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="info">
        <xsl:param name="pInfoNum"/>
        <span id="{$pInfoNum}">
            <xsl:value-of select="."/>
        </span>
        <xsl:apply-templates select="following-sibling::info[1]">
            <xsl:with-param name="pInfoNum" select="$pInfoNum +1"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="@name">
        <p>
            <xsl:value-of select="."/>
        </p>
    </xsl:template>
</xsl:stylesheet>

Output:

<div id="1">
    <p>A record</p>
    <span id="1">A1</span>
    <span id="2">A2</span>
</div>
<div id="2">
    <p>C record</p>
    <span id="3">C1</span>
</div>

Solution 2: preceding axe. This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:template match="record"/>
    <xsl:template match="record[node()]">
        <div id="{count(preceding-sibling::record[node()])+1}">
            <xsl:apply-templates select="@*|*"/>
        </div>
    </xsl:template>
    <xsl:template match="info">
        <span id="{count(preceding::info)+1}">
            <xsl:value-of select="."/>
        </span>
    </xsl:template>
    <xsl:template match="@name">
        <p>
            <xsl:value-of select="."/>
        </p>
    </xsl:template>
</xsl:stylesheet>

Solution 3: With fn:position() and preceding axe. This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:template match="records">
        <xsl:apply-templates select="record[node()]"/>
    </xsl:template>
    <xsl:template match="record">
        <div id="{position()}">
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates/>
        </div>
    </xsl:template>
    <xsl:template match="info">
        <span id="{count(preceding::info)+1}">
            <xsl:value-of select="."/>
        </span>
    </xsl:template>
    <xsl:template match="@name">
        <p>
            <xsl:value-of select="."/>
        </p>
    </xsl:template>
</xsl:stylesheet>

Note: You need a explict pull style.

Edit: Missed any level numbering for span/@id.

Alejandro
@Alejandro: Good effort. You may be interested to see yet another solution :)
Dimitre Novatchev
@Alejandro: Congratulations on surpassing the 4000 mark!
Dimitre Novatchev
@Dimitre: Thanks!
Alejandro
Thanks! it is wonderful. does it possible to use template name instead of apply-template ? how could I record[node()] in template-name ?
cc96ai
@cc96ai: First, I've edit these stylesheet because I didn't note that you need to numberig span/@id from any level. Check that! I don't see why you could want to use named templates... Do note that with `call-template` you don't change context. You would need to pass position and nodes as param. But then you would also neet to iterate over nodes... It's not a good solution, I think.
Alejandro
+2  A: 

There is a short way to do this in XSLT. Use the <xsl:number> instruction:

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

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

 <xsl:template match="record[info]">
   <xsl:variable name="vPos">
    <xsl:number count="record[info]"/>
   </xsl:variable>

   <div id="{$vPos}">
    <xsl:apply-templates/>
   </div>
 </xsl:template>

 <xsl:template match="info">
  <xsl:variable name="vPos">
   <xsl:number from="/" level="any" count="info" />
  </xsl:variable>
  <span id="{$vPos}"><xsl:apply-templates/></span>
 </xsl:template>
 <xsl:template match="record[not(info)]"/>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<data>
 <records>
   <record name="A record">
     <info>A1</info>
     <info>A2</info>
   </record>
   <record name="B record"/>
   <record name="C record">
     <info>C1</info>
   </record>
  </records>
</data>

the wanted, correct result is produced:

<data>
    <records>
        <div id="1">
            <span id="1">A1</span>
            <span id="2">A2</span>
        </div>
        <div id="2">
            <span id="3">C1</span>
        </div>
    </records>
</data>
Dimitre Novatchev
@Dimitre: +1 For the only correct solution (I've missed that span/@id should be from any level. I'll edit) and for `xsl:number`, the instruction made for this task in XSLT.
Alejandro