0
votes

My XML structure is like

<Client>
  <Document>
    <service class="ABC">
      <author name="x"/>
      <Details>
        ...
      </Details>
    </service>
   </Document>
   <Document>
     <service class="ABC">
       <author name="x"/>
       <Details>
         ...
       </Details>
     </service>
   </Document>
   <Document>
     <service class="ABC">
       <author name="x"/>
       <Details>
         ...
       </Details>
     </service>
   </Document>'
</Client>

I want it like

<Client>
  <Document>
    <service class="ABC">
      <author name="x"/>
       <Details>
         ...
       </Details>
    </service>
   </Document>
   <Document>
     <service class="ABC">
       <author name="x"/>
       <Details>
         ...
       </Details>
     </service>
   </Document>
   <Document>
     <service class="ABC">
       <author name="x"/>
       <Details>
         ...
       </Details>
     </service>
   </Document>'
   <component>
     <service class="ABC">
       <author name="x"/>
     </service>
   </component>'
</Client>

My xsl file contains

<xsl:template match="//v3:Client">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
      <component>
        <service class="">
          <xsl:apply-templates select="//v3:Client/v3:Document/v3:service/v3:author"/>
        </service>
      </component>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="//v3:Client/v3:component/v3:service">
    <xsl:element name="service" namespace="urn:hl7-org:v3">
      <xsl:attribute name="class">
        <xsl:value-of select="//v3:Client/v3:Document/v3:service/@class"/>
      </xsl:attribute>
    </xsl:element>
  </xsl:template>

But its not giving desired output. Its giving output like

Client
  Document
    service class="ABC"
      author name="x"
      details
        ...
      details
     service
    Document
   Document
    service class="ABC"
      author name="x"
      details
        ...
      details
    service
  Document
  Document
    service class="ABC"
      author name="x"
      details
        ...
      details
    service
  Document
  component
    service class=""
      author name="x"
    service
    service class=""
      author name="x"
    service
    service class=""
      author name="x"
    service
  component
Client

I'm new to xsl. Can anybody please help me to solve this problem?

3

3 Answers

0
votes

Use group-by feature:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" indent="yes" />

  <xsl:template match="/Client">

    <xsl:copy-of select="Document" />

    <!-- Group nodes by attribute value, produce one <component>
         per each group -->
    <xsl:for-each-group select="Document" group-by="service/@class">
      <component>
        <xsl:copy-of select="./service" />
      </component>
    </xsl:for-each-group>

  </xsl:template>

</xsl:stylesheet>

Stylesheet above produces one <component> per each Document class="" attribute value.

0
votes

If you don't have access to group-by you can consider using the following-sibling:: axis.

In other words you're looking for something like this:

<component>
   <xsl:apply-templates select="Document/service[@class != following-sibling::service/@class]" mode="component" />
</component>

<xsl:template match="service" mode="component">
     <service class="{@class}">
         <!-- etc. -->
     </service>
</xsl:template>
0
votes

If you want to output the distinct service elements in your component element, then in XSTL1.0 you need to use a technique called Muenchian Grouping. I am assuming the class attribute uniquely identifies a service, so you would start by defining a key to look up the services like so

<xsl:key name="service" match="service" use="@class" />

Then, when creating your component element, to get the distinct service elements you have look at all such elements, but only select the ones that occur first in the key for their given class attribute.

<xsl:apply-templates 
     select="Document/service[generate-id() = generate-id(key('service', @class)[1])]" 
     mode="component" />

Within the template that matches the service element, you can just output the details as requires. In your sample it looks like you only want the author element here, as opposed to the details too.

<xsl:template match="service" mode="component">
   <xsl:copy>
     <xsl:apply-templates select="author|@*"/>
   </xsl:copy>
</xsl:template>

The use of mode is because you only want this template matching when you are outputing the compenent element.

Here is the full XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:key name="service" match="service" use="@class" />

<xsl:template match="Client">
   <xsl:copy>
     <xsl:apply-templates select="node()|@*"/>
     <component>
        <xsl:apply-templates select="Document/service[generate-id() = generate-id(key('service', @class)[1])]" mode="component" />
     </component>
   </xsl:copy>
</xsl:template>

<xsl:template match="service" mode="component">
    <xsl:copy>
      <xsl:apply-templates select="author|@*"/>
    </xsl:copy>
</xsl:template>

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

When applied to your sample XML, the following is output

<Client>
   <Document>
      <service class="ABC">
         <author name="x"/>
         <Details> ... </Details>
      </service>
   </Document>
   <Document>
      <service class="ABC">
         <author name="x"/>
         <Details> ... </Details>
      </service>
   </Document>
   <Document>
      <service class="ABC">
         <author name="x"/>
         <Details> ... </Details>
      </service>
   </Document>
   <component>
      <service class="ABC">
         <author name="x"/>
      </service>
   </component>
</Client>