Re: [xsl] Deepening a flat structure and numbering nodes

Subject: Re: [xsl] Deepening a flat structure and numbering nodes
From: Emmanuel Bégué <medusis@xxxxxxxxx>
Date: Sat, 10 Aug 2013 18:19:07 +0200
Grouping in XSLT 1.0 is best done with keys; this key:

<xsl:key name="records" match="*[not(self::Category)]"
use="generate-id(preceding-sibling::Category[1])"/>

sets a key for any element that is not a <Category> as the id of the
preceding Category element.

Then the grouping can be done according to that key, and so can be the
counting within a "group".

Here's the full stylesheet:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
  exclude-result-prefixes="xsl">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:key name="records" match="*[not(self::Category)]"
use="generate-id(preceding-sibling::Category[1])"/>

<xsl:template match="/*">
  <data>
    <xsl:apply-templates select="Story/Category"/>
    </data>
  </xsl:template>

<xsl:template match="Category">
  <xsl:variable name="key" select="generate-id(.)"/>
  <newRecord>
    <Category>
      <xsl:value-of select="substring-before(., ':')"/>
      </Category>
    <Subcategory>
      <xsl:value-of select="normalize-space(substring-after(., ':'))"/>
      </Subcategory>
    <Case>
      <xsl:number/>
      </Case>
    <xsl:apply-templates select="key('records', $key)">
      <xsl:with-param name="key" select="$key"/>
      </xsl:apply-templates>
    </newRecord>
  </xsl:template>

<xsl:template match="*">
  <xsl:copy-of select="."/>
  </xsl:template>

<xsl:template match="DifferentialDiagnosis">
  <xsl:param name="key"/>
  <xsl:element name="{concat(name(),
count(preceding-sibling::DifferentialDiagnosis[generate-id(preceding-sibling::Category[1])
= $key]) + 1)}">
    <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>

This produces the following result:

<data>
   <newRecord>
      <Category>Category</Category>
      <Subcategory>Subcategory</Subcategory>
      <Case>1</Case>
      <CaseTitle>Title One</CaseTitle>
      <Institution>Institution One</Institution>
      <Author>Authors One</Author>
      <History>History One</History>
      <DifferentialDiagnosis1>Sick</DifferentialDiagnosis1>
      <DifferentialDiagnosis2>Sicker</DifferentialDiagnosis2>
      <DifferentialDiagnosis3>Sickest</DifferentialDiagnosis3>
      <TeachingPoint>Point1</TeachingPoint>
      <TeachingPoint>Point2</TeachingPoint>
   </newRecord>
   <newRecord>
      <Category>Category One</Category>
      <Subcategory>Subcategory</Subcategory>
      <Case>2</Case>
      <CaseTitle>Title Two</CaseTitle>
      <Institution>Title Two</Institution>
      <Author>Author Two</Author>
      <History>History Two</History>
      <DifferentialDiagnosis1>Sick</DifferentialDiagnosis1>
      <DifferentialDiagnosis2>Sicker</DifferentialDiagnosis2>
      <DifferentialDiagnosis3>Sickest</DifferentialDiagnosis3>
      <TeachingPoint>Point1</TeachingPoint>
      <TeachingPoint>Point2</TeachingPoint>
   </newRecord>
</data>

Hope this helps.
Regards,
EB

On Sat, Aug 10, 2013 at 2:53 PM, Rick Quatro <rick@xxxxxxxxxxxxxx> wrote:
> Hi All,
>
> Here is my input XML:
>
> <?xml version="1.0" encoding="UTF-8"?>
> <Cases>
>     <Story>
>         <Category>Category: Subcategory</Category>
>         <CaseTitle>Title One</CaseTitle>
>         <Institution>Institution One</Institution >
>         <Author>Authors One</Author>
>         <History>History One</History>
>         <DifferentialDiagnosis>Sick</DifferentialDiagnosis>
>         <DifferentialDiagnosis>Sicker</DifferentialDiagnosis>
>         <DifferentialDiagnosis>Sickest</DifferentialDiagnosis>
>         <TeachingPoint>Point1</TeachingPoint>
>         <TeachingPoint>Point2</TeachingPoint>
>         <Category>Category One: Subcategory</Category>
>         <CaseTitle>Title Two</CaseTitle>
>         <Institution>Title Two</Institution >
>         <Author>Author Two</Author>
>         <History>History Two</History>
>         <DifferentialDiagnosis>Sick</DifferentialDiagnosis>
>         <DifferentialDiagnosis>Sicker</DifferentialDiagnosis>
>         <DifferentialDiagnosis>Sickest</DifferentialDiagnosis>
>         <TeachingPoint>Point1</TeachingPoint>
>         <TeachingPoint>Point2</TeachingPoint>
>     </Story>
> </Cases>
>
> I want to organize the data in a deeper structure like this:
>
> <?xml version="1.0" encoding="UTF-8"?>
> <data>
>    <newRecord>
>       <Category>Category</Category>
>       <Subcategory>Subcategory</Subcategory>
>       <Case>1</Case>
>       <CaseTitle>Title One</CaseTitle>
>       <Institution>Institution One</Institution>
>       <Author>Authors One</Author>
>       <History>History One</History>
>       <DifferentialDiagnosis>Sick</DifferentialDiagnosis>
>       <DifferentialDiagnosis>Sicker</DifferentialDiagnosis>
>       <DifferentialDiagnosis>Sickest</DifferentialDiagnosis>
>       <TeachingPoint>Point1</TeachingPoint>
>       <TeachingPoint>Point2</TeachingPoint>
>    </newRecord>
>    <newRecord>
>       <Category>Category One</Category>
>       <Subcategory>Subcategory</Subcategory>
>       <Case>2</Case>
>       <CaseTitle>Title Two</CaseTitle>
>       <Institution>Title Two</Institution>
>       <Author>Author Two</Author>
>       <History>History Two</History>
>       <DifferentialDiagnosis>Sick</DifferentialDiagnosis>
>       <DifferentialDiagnosis>Sicker</DifferentialDiagnosis>
>       <DifferentialDiagnosis>Sickest</DifferentialDiagnosis>
>       <TeachingPoint>Point1</TeachingPoint>
>       <TeachingPoint>Point2</TeachingPoint>
>    </newRecord>
> </data>
>
> With help from the list, I am using this to get the results.
>
> <?xml version="1.0" encoding="UTF-8"?>
> <xsl:stylesheet version="1.0"
>     xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
>
>     <xsl:output indent="yes" />
>
>     <xsl:template match="Cases/Story">
>         <data>
>             <xsl:apply-templates select="Category[1]" />
>         </data>
>     </xsl:template>
>
>     <xsl:template match="Category">
>         <xsl:element name="newRecord">
>             <Category><xsl:value-of select="substring-before(.,':
> ')"/></Category>
>             <Subcategory><xsl:value-of select="substring-after(.,':
> ')"/></Subcategory>
>             <Case><xsl:number count="Category"/></Case>
>             <xsl:apply-templates
>                 select="following-sibling::*[1][not(self::Category)]"
> mode="in-case-siblings"/>
>         </xsl:element>
>         <xsl:apply-templates select="following-sibling::Category[1]"/>
>     </xsl:template>
>
>     <xsl:template match="*" mode="in-case-siblings">
>         <xsl:copy-of select="." />
>         <xsl:apply-templates
>             select="following-sibling::*[1][not(self::Category)]"
> mode="in-case-siblings"/>
>     </xsl:template>
>
>     <xsl:template match="Category" mode="in-case-siblings"/>
>
> </xsl:stylesheet>
>
> This is working well, but now I need to do something different to the
> <DifferentialDiagnosis> elements. I want to change the element names by
> numbering them sequentially within my <newRecord> element, like this:
>
> <?xml version="1.0" encoding="UTF-8"?>
> <data>
>    <newRecord>
>       <Category>Category</Category>
>       <Subcategory>Subcategory</Subcategory>
>       <Case>1</Case>
>       <CaseTitle>Title One</CaseTitle>
>       <Institution>Institution One</Institution>
>       <Author>Authors One</Author>
>       <History>History One</History>
>       <DifferentialDiagnosis1>Sick</DifferentialDiagnosis1>
>       <DifferentialDiagnosis2>Sicker</DifferentialDiagnosis2>
>       <DifferentialDiagnosis3>Sickest</DifferentialDiagnosis3>
>       <TeachingPoint>Point1</TeachingPoint>
>       <TeachingPoint>Point2</TeachingPoint>
>    </newRecord>
>    <newRecord>
>       <Category>Category One</Category>
>       <Subcategory>Subcategory</Subcategory>
>       <Case>2</Case>
>       <CaseTitle>Title Two</CaseTitle>
>       <Institution>Title Two</Institution>
>       <Author>Author Two</Author>
>       <History>History Two</History>
>       <DifferentialDiagnosis1>Sick</DifferentialDiagnosis1>
>       <DifferentialDiagnosis2>Sicker</DifferentialDiagnosis2>
>       <DifferentialDiagnosis3>Sickest</DifferentialDiagnosis3>
> <TeachingPoint>Point1</TeachingPoint>
>       <TeachingPoint>Point2</TeachingPoint>
>    </newRecord>
> </data>
>
> I added this rule to my stylesheet:
>
>     <xsl:template match="DifferentialDiagnosis" mode="in-case-siblings">
>         <xsl:variable name="diagnosis"><xsl:number
> count="DifferentialDiagnosis" from="Category"/></xsl:variable>
>         <xsl:element name="{concat(name(),$diagnosis)}"><xsl:value-of
> select="."/></xsl:element>
>         <xsl:apply-templates
>             select="following-sibling::*[1][not(self::Category)]"
> mode="in-case-siblings"/>
>     </xsl:template>
>
> This basically "works" except the numbers are sequential throughout the
> document.
>
> <data>
>    <newRecord>
>         ...
>       <DifferentialDiagnosis1>Sick</DifferentialDiagnosis1>
>       <DifferentialDiagnosis2>Sicker</DifferentialDiagnosis2>
>       <DifferentialDiagnosis3>Sickest</DifferentialDiagnosis3>
>         ...
>    </newRecord>
>    <newRecord>
>         ...
>       <DifferentialDiagnosis1>Sick</DifferentialDiagnosis5>
>       <DifferentialDiagnosis2>Sicker</DifferentialDiagnosis6>
>       <DifferentialDiagnosis3>Sickest</DifferentialDiagnosis7>
>         ...
>    </newRecord>
> </data>
>
> I need the numbers to reset for each <newRecord> element. I am sorry for the
> long post, but I want to be complete as possible. Also, for this project, I
> need to use XSLT 1.0. Thank you very much.
>
> Rick

Current Thread