ram.bi
ram.bi

Reputation: 283

Concate Similar XML Tag Values with XSLT

How can i handle this, to concate similar XML Tags in XML File

I have XMl in following format::

<addressbook>
 <address>
    <first-name>Chester Hasbrouck</first-name>
     <Descritpion>Hi</Descritpion>
     <Descritpion>This is Chester </Descritpion>
   <street>1234 Main Street</street>
  <city>Sheboygan</city>
  <state>WI</state>
  <zip>48392</zip>
 </address>
 <address>
    <first-name>Mary</first-name>
     <Descritpion>Hi</Descritpion>
     <Descritpion>This is Mary </Descritpion>
     <Descritpion>Bye</Descritpion>
     <street>283 First Avenue</street>
  <city>Skunk Haven</city>
  <state>MA</state>
  <zip>02718</zip>
 </address>
</addressbook>

I need this in following format somebody please suggest XSLT for that.

<addressbook>
 <address>
    <first-name>Chester Hasbrouck</first-name>
     <Descritpion>Hi | This is Chester </Descritpion>
   <street>1234 Main Street</street>
  <city>Sheboygan</city>
  <state>WI</state>
  <zip>48392</zip>
 </address>
 <address>
    <first-name>Mary</first-name>
     <Descritpion>Hi | This is Mary | Bye</Descritpion>
     <street>283 First Avenue</street>
  <city>Skunk Haven</city>
  <state>MA</state>
  <zip>02718</zip>
 </address>
</addressbook>

Upvotes: 2

Views: 145

Answers (3)

ABach
ABach

Reputation: 3738

Here's one more way to do it.

One thing to note: this solution doesn't maintain the output document ordering (i.e., it doesn't create the new <Descritpion> element directly under the <first-name> element). If that's important to you, have a look at the other provided solutions.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output omit-xml-declaration="no" indent="yes"/>
  <xsl:strip-space elements="*"/>

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

  <!-- TEMPLATE #2 -->
  <xsl:template match="address">
    <xsl:copy>
      <xsl:apply-templates/>
      <Descritpion>
        <xsl:apply-templates select="Descritpion/text()"/>
      </Descritpion>
    </xsl:copy>
  </xsl:template>

  <!-- TEMPLATE #3 -->
  <xsl:template match="Descritpion/text()">
    <xsl:value-of select="." />
    <xsl:if test="position() != last()"> | </xsl:if>    
  </xsl:template>

  <!-- TEMPLATE #4 -->
  <xsl:template match="Descritpion" />
</xsl:stylesheet>

When this is provided on the provided XML document:

<?xml version="1.0" encoding="UTF-8"?>
<addressbook>
  <address>
    <first-name>Chester Hasbrouck</first-name>
    <Descritpion>Hi</Descritpion>
    <Descritpion>This is Chester </Descritpion>
    <street>1234 Main Street</street>
    <city>Sheboygan</city>
    <state>WI</state>
    <zip>48392</zip>
  </address>
  <address>
    <first-name>Mary</first-name>
    <Descritpion>Hi</Descritpion>
    <Descritpion>This is Mary </Descritpion>
    <Descritpion>Bye</Descritpion>
    <street>283 First Avenue</street>
    <city>Skunk Haven</city>
    <state>MA</state>
    <zip>02718</zip>
  </address>
</addressbook>

...the desired result is produced:

<?xml version="1.0" encoding="UTF-8"?><addressbook>
  <address>
    <first-name>Chester Hasbrouck</first-name>
    <street>1234 Main Street</street>
    <city>Sheboygan</city>
    <state>WI</state>
    <zip>48392</zip>
    <Descritpion>Hi | This is Chester </Descritpion>
  </address>
  <address>
    <first-name>Mary</first-name>
    <street>283 First Avenue</street>
    <city>Skunk Haven</city>
    <state>MA</state>
    <zip>02718</zip>
    <Descritpion>Hi | This is Mary  | Bye</Descritpion>
  </address>
</addressbook>

Explanation:

  • Template #1: the "Identity Template" copies everything as-is.

  • Template #2: this template copies the each <address> element as-is, creates a new <Descritpion> element, and instructs the XSLT processor to fill that new element with the results from any template that matches Descritpion/text().

  • Template #3: this template processes the results from Template #2; in particular, it copies the text from all the old <Descritpion> elements and, until it reaches the last one, places a | in between the results.

  • Template #4: this template removes the original <Descritpion> elements from the source document.

Upvotes: 0

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243469

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

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

 <xsl:template match="Descritpion[not(preceding-sibling::*[1][self::Descritpion])]">
  <Descritpion>
   <xsl:value-of select="."/>
   <xsl:apply-templates select="following-sibling::Descritpion/text()"/>
  </Descritpion>
 </xsl:template>

 <xsl:template match="Descritpion/text()">
   <xsl:value-of select="concat(' | ', .)"/>
 </xsl:template>
 <xsl:template match="Descritpion"/>
</xsl:stylesheet>

when applied on the provided XML document:

<addressbook>
    <address>
        <first-name>Chester Hasbrouck</first-name>
        <Descritpion>Hi</Descritpion>
        <Descritpion>This is Chester </Descritpion>
        <street>1234 Main Street</street>
        <city>Sheboygan</city>
        <state>WI</state>
        <zip>48392</zip>
    </address>
    <address>
        <first-name>Mary</first-name>
        <Descritpion>Hi</Descritpion>
        <Descritpion>This is Mary </Descritpion>
        <Descritpion>Bye</Descritpion>
        <street>283 First Avenue</street>
        <city>Skunk Haven</city>
        <state>MA</state>
        <zip>02718</zip>
    </address>
</addressbook>

produces the wanted, correct result:

<addressbook>
   <address>
      <first-name>Chester Hasbrouck</first-name>
      <Descritpion>Hi | This is Chester </Descritpion>
      <street>1234 Main Street</street>
      <city>Sheboygan</city>
      <state>WI</state>
      <zip>48392</zip>
   </address>
   <address>
      <first-name>Mary</first-name>
      <Descritpion>Hi | This is Mary  | Bye</Descritpion>
      <street>283 First Avenue</street>
      <city>Skunk Haven</city>
      <state>MA</state>
      <zip>02718</zip>
   </address>
</addressbook>

Explanation:

  1. The identity rule copies every node "as-is".

  2. Descritpion elements are generally ignored / deleted by overriding the identity template with an empty-bodied template matching Descritpion.

  3. Only a Descritpion element whose immediately preceding sibling element isn't a Descritpion itself is treated specially by another overriding template. Here the string value of the element is copied and then templates are applied to all text-node-children of the following siblings Descritpion elements.

  4. A template matching a text-node-child of aDescritpion element implements the wanted text concatenation.

Upvotes: 1

Tim C
Tim C

Reputation: 70608

To do this, you can build upon the identity transform, but add in extra rules to handle the elements you are interested in

I think you first need to match elements which don't have child elements in them

<xsl:template match="*[not(*)]" priority="1">

You would then add code to this to output the current text value, and then select the text value of all following siblings with the same name (which would be output with the separator before them)

<xsl:apply-templates 
   select="following-sibling::*[local-name() = local-name(current())]/text()" 
   mode="concat"/>

You would also need to add a template to ignore elements which have the same name as a preceding sibling so they do not get output twice

<xsl:template 
    match="*[not(*)][local-name() = local-name(preceding-sibling::*[1])]" 
    priority="2" />

Do note the use of the priority attribute here, so the XSLT processor matches the one with the higher priority first.

Here is the full XSLT

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

   <xsl:template match="*[not(*)]" priority="1">
      <xsl:copy>
         <xsl:value-of select="normalize-space(.)"/>
         <xsl:apply-templates select="following-sibling::*[local-name() = local-name(current())]/text()" mode="concat"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="*[not(*)][local-name() = local-name(preceding-sibling::*[1])]" priority="2"/>

   <xsl:template match="text()" mode="concat">
      <xsl:value-of select="concat(' | ', normalize-space(.))"/>
   </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

<addressbook>
   <address>
      <first-name>Chester Hasbrouck</first-name>
      <Descritpion>Hi | This is Chester</Descritpion>
      <street>1234 Main Street</street>
      <city>Sheboygan</city>
      <state>WI</state>
      <zip>48392</zip>
   </address>
   <address>
      <first-name>Mary</first-name>
      <Descritpion>Hi | This is Mary | Bye</Descritpion>
      <street>283 First Avenue</street>
      <city>Skunk Haven</city>
      <state>MA</state>
      <zip>02718</zip>
   </address>
</addressbook>

Upvotes: 0

Related Questions