tags:

views:

39

answers:

2

I have to make the attribute into single element. Please guide me.

Here my input and requirement.

Input:

<book pagenum="01">
<title pagenum="01">Book title</title>
<chapter pagenum="02">
<chaptertitle pagenum="02">CHAPTER TITLE</chaptertitle>
<section pagenum="03">
<sectiontitle pagenum="03">SECTION TITLE</chaptertitle>
<subsection pagenum="04">
<para pagenum="04">body content</para>
<para pagenum="04">body content</para>
<para pagenum="04">body content</para>
<para pagenum="05">body content</para>
<para pagenum="05">body content</para>
<para pagenum="05">body content</para>
<para pagenum="06">body content</para>
<para pagenum="06">body content</para>
</subsection></section></chapter></book>

output:

<book>
<?docpage num="01"?><pagenum id="p01">01</pagenum>
<booktitle>Book title</booktitle>
<chapter>
<?docpage num="02"?><pagenum id="p02">02</pagenum>
<chaptertitle>CHAPTER TITLE</chaptertitle>
<section>
<?docpage num="03"?><pagenum id="p03">03</pagenum>
<sectiontitle>SECTION TITLE</chaptertitle>
<subsection>
<?docpage num="04"?><pagenum id="p04">04</pagenum>
<para>body content</para>
<para>body content</para>
<para>body content</para>
<?docpage num="05"?><pagenum id="p05">05</pagenum>
<para>body content</para>
<para>body content</para>
<para>body content</para>
<?docpage num="06"?><pagenum id="p06">06</pagenum>
<para>body content</para>
<para>body content</para>
</subsection></section></chapter></book>

How to convert this in XSLT scripts...? please guide me.

Thanks, Micheal

+2  A: 

Something along these lines:

<!-- Identity transform - copy all elements as is by default -->
<xsl:template match="node() | @*">
  <xsl:copy>
    <xsl:apply-templates select="node() | @*" />
  </xsl:copy>
</xsl:template>

<!-- Match any element for which @pagenum is different from preceding one.
     Note that <para> is handled specially below. -->
<xsl:template match="*[@pagenum != preceding::*[1]/@pagenum]">
  <xsl:copy>
    <!-- Insert the PI and <pagenum> as first child -->
    <xsl:call-template name="insert-pagenum"/>
    <xsl:apply-templates select="node() | @*" />
  </xsl:copy>
</xsl:template>

<!-- Match <para> for which @pagenum is different from preceding one. -->
<xsl:template match="para[@pagenum != preceding::*[1]/@pagenum]">
  <!-- Insert the PI and <pagenum> before the opening tag of <para> -->
  <xsl:call-template name="insert-pagenum"/>
  <xsl:copy>
    <xsl:apply-templates select="node() | @*" />
  </xsl:copy>
</xsl:template>

<xsl:template name="insert-pagenum">
  <xsl:processing-instruction name="docpage">
    <xsl:text>num=</xsl:text><xsl:value-of select="@pagenum"/>
  </xsl:processing-instruction>
  <pagenum id="p{@pagenum}">
    <xsl:value-of select="@pagenum"/>
  </pagenum>
</xsl:template>
Pavel Minaev
Thanks a lot Pavel.. It works fine! :)
Micheal
Michael mark Pavel's answer as accepted (check mark) if it worked for you. Also upvote him :)
spoon16
For info, "preceeding::" is a very expensive way of doing this; the Muenchian approach (with "key") is far quicker.
Marc Gravell
how to "upvote" Pavel..? i dont know the way to do, i clicked "vote" place in his profile, but i couldnot access anything there.. spoon16, could you tell me ?
Micheal
@Marc: What's wrong with `preceding::*[1]`? It's O(1) and very fast in any sane implementation (one that maintains a doubly linked list of nodes), so this will work as a straightforward scan through all nodes.
Pavel Minaev
+2  A: 

So you only want to write @pagenum for the first of each? Something like:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:output method="xml" indent="yes"/>
  <xsl:key name="pages" match="//@pagenum" use="." />

  <xsl:template match="@* | node()">
    <xsl:copy><xsl:apply-templates select="@* | node()"/></xsl:copy>
  </xsl:template>
  <xsl:template match="@pagenum">
    <xsl:if test="generate-id() = generate-id(key('pages', .))">
      <xsl:processing-instruction name="docpage">num=<xsl:value-of select="."/></xsl:processing-instruction>
      <pagenum id="p{.}"><xsl:value-of select="."/></pagenum>
    </xsl:if>
  </xsl:template>
  <xsl:template match="title">
    <booktitle><xsl:apply-templates select="@* | node()"/></booktitle>
  </xsl:template>
</xsl:stylesheet>
Marc Gravell
+1, That's the methodically superior solution. And shorter, though I would probably indent it more strictly.
Tomalak
For info, the "generate-id() = generate-id(key('pages', .))" line means "am I the first?"
Marc Gravell
This will always put generated elements as children of the element on which we had @pagenum. If you look at sample output closely, this isn't true for `<para>` - for it, you have to put generated elements _before_ the element, not inside.
Pavel Minaev
fair enough; not a biggie to fix, but good eyes.
Marc Gravell