Home > database >  XML transformation using XSLT doesn't work
XML transformation using XSLT doesn't work

Time:07-09

I am in phase of learning XSLT,

Following is my XML

    <?xml version="1.0" encoding="UTF-8"?>
     <catalog>
     <cd>
      <title>xyz</title>
      <artist>pqr</artist>
      <country>USA</country>
     </cd>
      <cd>
      <title>abc</title>
      <artist>def</artist>
      <country>France</country>
     </cd>

</catalog>

Output Expected

  Title |Artist |Country
  xyz |pqr |USA
  abc |def |France

My First attempt is to read the title.

 <?xml version="1.0" encoding="UTF-8"?>
 <xsl:stylesheet version="1.0" 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:template match="title">
   <xsl:value-of select="catalog/cd/title"/>
   </xsl:template>
 </xsl:stylesheet>

But the display is

      pqr 
      USA
     
      
      
      def
      France

I am just trying to read the title here. Don't understand what is wrong . I am sure I have to use looping and concatenation to achieve the output.

Can someone help?

Thank you Regards Prat


Edit New xml

  <?xml version='1.0' encoding='UTF-8'?>
   <multimap:Messages xmlns:multimap="http://sap.com/xi/XI/SplitAndMerge">
    <multimap:Message1>
     <Products>
      <Product>
        <OrderID>9000625868</OrderID>
      </Product>
     </Products>
</multimap:Message1>
<multimap:Message2>
 <CustomerOrderCollection>
    <CustomerOrder>
        <ZZCASE_KUT/>
        <DivisionCodeText>Test</DivisionCodeText>
        <ApprovalStatusCode/>
        <DateTime>2022-06-07T12:06:25.068</DateTime>
        <TaxAmount>0.000000</TaxAmount> 
    </CustomerOrder>
    <CustomerOrder>
        <ZZCASE_KUT/>
        <DivisionCodeText>Test2</DivisionCodeText>
        <ApprovalStatusCode/>
        <DateTime>2022-06-08T12:06:25.068</DateTime>
        <TaxAmount>10.000000</TaxAmount>    
    </CustomerOrder>

Output desired

   ZZCASE_KUT|DivisionCodeText|ApprovalStatusCode|DateTime|TaxAmount
    |Test||2022-06-07T12:06:25.068|0.000000
    |Test1||2022-06-08T12:06:25.068|10.000000

XSLT i used

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

   <xsl:variable name="delimiter" select="' |'"/>

   <xsl:template match="CustomerOrderCollection">
    <xsl:for-each select="CustomerOrder[1]/*">
        <xsl:value-of select="concat(
            translate(substring(local-name(), 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') , 
            substring(local-name(), 2))"/>
        <xsl:if test="following-sibling::*">
            <xsl:value-of select="$delimiter"/>
        </xsl:if>
    </xsl:for-each>
    <xsl:text>&#10;</xsl:text>
    <xsl:apply-templates select="*" />
  </xsl:template>

  <xsl:template match="CustomerOrder">
    <xsl:for-each select="*">
        <xsl:value-of select="."/>
        <xsl:if test="following-sibling::*">
            <xsl:value-of select="$delimiter"/>
        </xsl:if>
    </xsl:for-each>
    <xsl:text>&#10;</xsl:text>
  </xsl:template>  

 </xsl:stylesheet>

Output is very different form expected.

               9000625868
      
     


 ZZCASE_KUT |DivisionCodeText |ApprovalStatusCode |DateTime |TaxAmount
 |Test | |2022-06-07T12:06:25.068 |0.000000
 |Test2 | |2022-06-08T12:06:25.068 |10.000000

Version 3

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

     <xsl:variable name="delimiter" select="' |'"/>

    <xsl:template match="/">
       <xsl:apply-templates select="/*/*/CustomerOrderCollection" />

     <xsl:for-each select="CustomerOrder[1]/*">
    <xsl:value-of select="concat(
        translate(substring(local-name(), 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') , 
        substring(local-name(), 2))"/>
    <xsl:if test="following-sibling::*">
        <xsl:value-of select="$delimiter"/>
    </xsl:if>
  </xsl:for-each>
  <xsl:text>&#10;</xsl:text>
  <xsl:apply-templates select="*" />
 </xsl:template>

 <xsl:template match="CustomerOrder">
   <xsl:for-each select="*">
    <xsl:value-of select="."/>
    <xsl:if test="following-sibling::*">
        <xsl:value-of select="$delimiter"/>
    </xsl:if>
 </xsl:for-each>
 <xsl:text>&#10;</xsl:text>
 </xsl:template>  

</xsl:stylesheet>

Output

     |0E-14 | | |2022-06-07T12:06:25.068 |0.000000 | |1





    
        9000625868
    




     |0E-14 | | |2022-06-07T12:06:25.068 |0.000000 | |1

CodePudding user response:

It is important to know that there are built-in template rules that will be applied to the content. Unless you provide your own that override the default behavior, XSLT will match on any node() apply-templates to the children, and text() will return their value. Therefore, if all you did was execute the stylesheet, it will generate output will all of the text().

Now, for your current template, you are matching on title elements, and then attempt to get the value-of with a relative XPath expression that is looking for catalog/cd/title that are children of the title (there are none, so it produces nothing. This has the effect of filtering out all of the matched title, rather than producing the desired output. If you wanted to select the value-of that title, you could select . (the context node).

To produce your desired output, you would want a stylesheet like:

<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" />
    
    <xsl:variable name="delimiter" select="' |'"/>
    
    <xsl:template match="catalog">
        <xsl:for-each select="cd[1]/*">
            <xsl:value-of select="concat(
                translate(substring(local-name(), 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') , 
                substring(local-name(), 2))"/>
            <xsl:if test="following-sibling::*">
                <xsl:value-of select="$delimiter"/>
            </xsl:if>
        </xsl:for-each>
        <xsl:text>&#10;</xsl:text>
        <xsl:apply-templates select="*" />
    </xsl:template>
    
    <xsl:template match="cd">
        <xsl:for-each select="*">
            <xsl:value-of select="."/>
            <xsl:if test="following-sibling::*">
                <xsl:value-of select="$delimiter"/>
            </xsl:if>
        </xsl:for-each>
        <xsl:text>&#10;</xsl:text>
    </xsl:template>  

</xsl:stylesheet>

For your second XML, you could add a template matching the root node, and push the CustomerOrderCollection, bypassing all of the built-in template rules that would be processing those multimap elements before they hit on CustomerOrderCollection:

<xsl:template match="/">
  <xsl:apply-templates select="/*/*/CustomerOrderCollection" />
</xsl:template>

You could further consolidate and avoid some duplication by using named templates and modes, but this demonstrates one way to achieve the desired output.

  • Related