p2p.wrox.com Forums

p2p.wrox.com Forums (http://p2p.wrox.com/index.php)
-   XSLT (http://p2p.wrox.com/forumdisplay.php?f=86)
-   -   Index Page Range (http://p2p.wrox.com/showthread.php?t=78411)

anil_yadav26@hotmail.com March 2nd, 2010 07:34 AM

[XSLT] Index Page Range
 
Hi,

I have an xml file in which pages are appearing under the <link> tag. I have to make the page range for continuous appearing pages e.g. in ITEM 3 pages are appearing like this:

<link>410</link><link>411</link><link>412</link><link>413</link><link>420</link>

and need to transform like this:

<link>410--413</link><link></link><link></link><link></link><link>420</link>

Can anybody suggest me to write the XSLT, XQuery for the same.

NOTE: Please note that all the <link> tags are required because they contains some attribute IDs which can be use in future for updates.

Source XML File

Code:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book>
<endmatter>
<index>
<title><emphasis type="bold">European Court of Justice: Cases in case number order</emphasis></title>
<index-list>
<index-item><index-entry>1/58 Stork v High ECR 17</index-entry>&#x2002;<link>232--233</link></index-item>
<index-item><index-entry>42 SNUPAT v High Authority</index-entry>&#x2002;<link>410</link><link>411</link><link>412</link><link>413</link><link>420</link><link>448</link><link>449</link></index-item>
<index-item><index-entry>16/62 Confration Nationale </index-entry>&#x2002;<link>520</link><link>521</link><link>524</link><link>525</link><link>530</link><link>595</link></index-item>
</index-list>
</index>
</endmatter>
</book>

Output required

Code:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book>
<endmatter>
<index>
<title><emphasis type="bold">European Court of Justice: Cases in case number order</emphasis></title>
<index-list>
<index-item><index-entry>1/58 Stork v High ECR 17</index-entry>&#x2002;<link>232--233</link></index-item>
<index-item><index-entry>42 SNUPAT v High Authority</index-entry>&#x2002;<link>410--413</link><link></link><link></link><link></link><link>420</link><link>448-449</link><link></link></index-item>
<index-item><index-entry>16/62 Confration Nationale </index-entry>&#x2002;<link>520--521</link><link></link><link>524--525</link><link></link><link>530</link><link>595</link></index-item>
</index-list>
</index>
</endmatter>
</book>

I'll really appreciate any help on this.

Regards,
Anil Yadav

mhkay March 2nd, 2010 08:12 AM

Assuming you're using XSLT 2.0, there's a neat trick for this discovered by David Carlisle (if I remember right).

Code:

<xsl:for-each-group select="link" group-adjacent="number(.) - position()">
  <xsl:for-each select="current-group()">
      <xsl:choose>
      <xsl:when test="position() = 1 and count(current-group()) = 1">
        <xsl:copy-of select="."/>
      </xsl:when>
      <xsl:when test="position() = 1">
          <link><xsl:value-of select="concat(., '--', current-group()[last()]"/></link>
      </xsl:when>
      <xsl:otherwise>
            <link/>
      </
      </
  </
</


anil_yadav26@hotmail.com March 2nd, 2010 08:46 AM

Hi Michael,

Thanks for your response!

I run the suggested code but its creating the page range for first and last node of group whereas I need range only for continuous page numbers. In the below example (red) you can see input xml file coding and blue contains output xml coding. The blue part contains two information first is continuous page range and independent page number.

<link>410</link><link>411</link><link>412</link><link>413</link><link>420</link>

and need to transform like this:

<link>410--413</link><link></link><link></link><link></link><link>420</link>


XSLT
Code:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">
 <xsl:template match="index-item">
  <xsl:for-each-group select="link" group-adjacent="number(link) - position()">
  <xsl:for-each select="current-group()">
  <xsl:choose>
    <xsl:when test="position() = 1 and count(current-group()) = 1">
    <xsl:copy-of select="."/>
    </xsl:when>
    <xsl:when test="position() = 1">
    <link><xsl:value-of select="concat(., '--', current-group()[last()])"/></link>
    </xsl:when>
    <xsl:otherwise>
    <link/>
    </xsl:otherwise>
  </xsl:choose>
  </xsl:for-each>
 </xsl:for-each-group>
  </xsl:template>
</xsl:stylesheet>

Could you please look into this and do the needful.

Thanks,
Anil Yadav

Martin Honnen March 2nd, 2010 08:54 AM

What Michael posted outlines the grouping for those 'link' elements, as you have other child elements and also some link elements which already have a "range" I think you need an additional grouping construct as follows:
Code:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">
 
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
 
  <xsl:template match="index-item">
    <xsl:copy>
      <xsl:for-each-group select="node()" group-adjacent="boolean(self::link[matches(., '^[0-9]+$')])">
        <xsl:choose>
          <xsl:when test="current-grouping-key()">
            <xsl:for-each-group select="current-group()" group-adjacent="number(.) - position()">
              <xsl:for-each select="current-group()">
                <xsl:choose>
                  <xsl:when test="position() eq 1 and count(current-group()) eq 1">
                    <xsl:copy-of select="current-group()"/>
                  </xsl:when>
                  <xsl:when test="position() eq 1">
                    <link><xsl:value-of select="concat(current-group()[1], '--', current-group()[last()])"/></link>
                  </xsl:when>
                  <xsl:otherwise>
                    <link/>
                  </xsl:otherwise>
                </xsl:choose>
              </xsl:for-each>
            </xsl:for-each-group>
          </xsl:when>
          <xsl:otherwise>
            <xsl:copy-of select="current-group()"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>


anil_yadav26@hotmail.com March 2nd, 2010 09:50 AM

Hi Martin,

Thanks for your reply. Now, its working perfectly. Thanks a lot!!!

I have one more query regarding the same input XML. Actually I have to filter the XML with following steps:

1. Sorting the <link> nodes
2. Removing the content of duplicate nodes
3. Making range

Now I have three different XSLT for all the TASK. Is it possible to merge all these XSLT into one?

Thanks in advance.


Thanks,
Anil Yadav



1. Sorting the <link> nodes
Code:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">
 
 <xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
 </xsl:template>
 
<xsl:template match="index-item">
 <index-item>
  <xsl:copy-of select="@*"/>
  <xsl:if test="index-entry">
    <xsl:apply-templates select="index-entry"/>
  </xsl:if>
  <xsl:for-each select="link">
  <xsl:sort select="."/>
  <link>
    <xsl:copy-of select="@*"/>
    <xsl:value-of select="."/>
  </link>
  </xsl:for-each>
 </index-item>
 </xsl:template>
</xsl:stylesheet>


2. Removing the content of duplicate nodes

Code:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">
 <xsl:template match="index-item">
  <index-item>
  <xsl:copy-of select="@*"/>
  <xsl:if test="index-entry">
    <xsl:apply-templates select="index-entry"/>
  </xsl:if>
  <xsl:for-each select="link">
    <xsl:sort select="."/>
    <link>
    <xsl:copy-of select="@*"/>
    <xsl:if test="not(node()) or not(preceding-sibling::node()[.=string(current())])">
      <xsl:if test="position()!=last()">
      <xsl:value-of select="."/><xsl:text>, </xsl:text>
    </xsl:if>
      <xsl:if test="position()=last()">
      <xsl:value-of select="."/>
      </xsl:if>
    </xsl:if>
    </link>
  </xsl:for-each>
  </index-item>
 </xsl:template>
</xsl:stylesheet>


3. Making range

Code:

<xsl:template match="@* | node()">
  <xsl:copy>
  <xsl:apply-templates select="@* | node()"/>
  </xsl:copy>
 </xsl:template>
 
 <xsl:template match="index-item">
  <xsl:copy>
  <xsl:for-each-group select="node()" group-adjacent="boolean(self::link[matches(., '^[0-9]+$')])">
    <xsl:choose>
    <xsl:when test="current-grouping-key()">
      <xsl:for-each-group select="current-group()" group-adjacent="number(.) - position()">
      <xsl:for-each select="current-group()">
        <xsl:choose>
        <xsl:when test="position() eq 1 and count(current-group()) eq 1">
          <xsl:copy-of select="current-group()"/>
        </xsl:when>
        <xsl:when test="position() eq 1">
          <link>
          <xsl:copy-of select="@*"/>
          <xsl:value-of select="concat(current-group()[1], '--', current-group()[last()])"/>
          </link>
        </xsl:when>
        <xsl:otherwise>
          <link>
          <xsl:copy-of select="@*"/>
          </link>
        </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>
      </xsl:for-each-group>
    </xsl:when>
    <xsl:otherwise>
      <xsl:copy-of select="current-group()"/>
    </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each-group>
  </xsl:copy>
 </xsl:template>


Martin Honnen March 2nd, 2010 10:01 AM

With XSLT 2.0 it is easily possible to combine several transformation steps into one stylesheet by creating a variable with a temporary result and then applying the next step to the contents of a variable. You might need to use modes on your templates however to distinguish different transformation steps.

anil_yadav26@hotmail.com March 2nd, 2010 10:08 AM

Could you please suggest some code or documentation on this.

Thanks,
Anil Yadav

Martin Honnen March 2nd, 2010 11:34 AM

The approach looks like this, assuming you always want to transform the complete document:
Code:

<xsl:template match="/">
  <xsl:variable name="sorted">
    <xsl:apply-templates mode="sort"/>
  </xsl:variable>
  <xsl:variable name="unique">
    <xsl:apply-templates select="$sorted/node()" mode="unique"/>
  </xsl:variable>
  <xsl:apply-templates select="$unique/node()"/>
</xsl:template>

<!-- now put all templates for sorting in mode="sort" e.g.
      <xsl:template match="index-item" mode="sort">
     
      </xsl:template>
      and all templates to eliminate duplicates in mode="unique"
      and then simply insert the templates from the last stylesheet
      that creates the ranges
-->


anil_yadav26@hotmail.com March 3rd, 2010 04:46 AM

Hi Martin,
Thanks a lot for your kind response!!![:)]
You are genius.

Thanks,
Anil Yadav


All times are GMT -4. The time now is 04:11 PM.

Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
© 2013 John Wiley & Sons, Inc.