James Hameson
James Hameson

Reputation: 341

Variable in xslt and loop

I have the following xml

<Request>
   <Record>
      <ID>123456789</ID>
      <OtherID>ABC123</OtherID>
      <Title>Example</Title>
      <Properties>
         <Attribute type="Main">
            <Name>Description</Name>
            <Value>This is an example</Value>
         </Attribute>
         <Attribute type="Main">
            <Name>Source</Name>
            <Value>A1</Value>
         </Attribute>
         <Attribute type="Main">
            <Name>Source</Name>
            <Value>B</Value>
         </Attribute>
         <Attribute type="Main">
            <Name>Represenative</Name>
            <Value>Mike</Value>
         </Attribute>
         <Attribute type="Main">
            <Name>Animal</Name>
            <Value>Elephant</Value>
         </Attribute>
      </Properties>
   </Record>
</Request>

I want to have the following json.

{
   "Record":{
      "ID":"123456789",
      "OtherID":"ABC123",
      "Title":"Example",
      "Properties":[
         {
            "Type":"Main",
            "Value":"Source",
            "Name":"A1"
         },
         {
            "Type":"Main",
            "Value":"Source",
            "Name":"B"
         },
         {
            "Type":"Main",
            "Value":"Representative",
            "Name":"Mike"
         },
         {
            "Type":"Main",
            "Value":"Animal",
            "Name":"Elephant"
         }
      ],
      "Description":"This is an example"
   }
}

Please notice that the description property is not part of the array, it is at the same level as properties, ID, OtherID and Title.

I am applying the following xslt to transform the xml to json. In this xml I'm declaring a variable, which will contain the description and will be added at the end of the object

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0">
<xsl:output method="text" />

<xsl:template match="/">
    <xsl:variable name="description2" select="''" />
    {
    <xsl:for-each select="Request/Record">
        "Record": {
        "ID":"<xsl:value-of select="ID" />",
        "OtherId": "<xsl:value-of select="OtherID" />",
        "Title":"Example",
        <xsl:if test="Properties">
            "Properties": [
            <xsl:for-each select="Properties/Attribute">
                <xsl:if test="soi:Name = 'Description'">
                    <xsl:variable name="description2" select="<xsl:value-of select="Value" />" />
                    <xsl:if test="position() != last()">,</xsl:if>
                </xsl:if>
                <xsl:if test="Name != 'Description'">
                    {
                    "Type": "Main",
                    "Name": "<xsl:value-of select="Name" />",
                    "Value": "<xsl:value-of select="Value" />"
                    }
                    <xsl:if test="position() != last()">,</xsl:if>
                </xsl:if>

            </xsl:for-each>
            ]
        </xsl:if>
        }<xsl:if test="position() != last()">,</xsl:if>
    </xsl:for-each>
    <xsl:if test="description2!=''">
    ,"Description":"<xsl:value-of select="$description2"/>"
    </xsl:if>
    }
</xsl:template>

Unfortunately, I'm getting this output

{
   "Request":{
      "ID":"123456789",
      "OtherID":"ABC123",
      "Title":"Example",
      "Properties":[
         {
            "Type":"Main",
            "Value":"Source",
            "Name":"A1"
         },
         {
            "Type":"Main",
            "Value":"Source",
            "Name":"B"
         },
         {
            "Type":"Main",
            "Value":"Representative",
            "Name":"Mike"
         },
         {
            "Type":"Main",
            "Value":"Animal",
            "Name":"Elephant"
         }
      ],
      "Description":""
   }
}

The value of description is empty because I do not know how to reassign the value of the variable description in the loop. I was using a double loop to get just the description, but it is very inefficient.

Any advise is welcomed

Thanks in advance.

Upvotes: 0

Views: 513

Answers (2)

Ryan
Ryan

Reputation: 1

I've modified your original XSLT below, in order to populate the value for "Description".

I commented out the parts related to variable $description2. Instead of using a variable, I used XPath to select the element where [../Name='Description']

Good luck!

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

    <xsl:template match="/">
        <!--<xsl:variable name="description2" select="''" />-->
        {
        <xsl:for-each select="Request/Record">
            "Record": {
            "ID":"<xsl:value-of select="ID" />",
            "OtherId": "<xsl:value-of select="OtherID" />",
            "Title":"Example",
            <xsl:if test="Properties">
                "Properties": [
                <xsl:for-each select="Properties/Attribute">
                    <!--<xsl:if test="soi:Name = 'Description'">
                        <xsl:variable name="description2" select="<xsl:value-of select="Value" />" />
                            <xsl:if test="position() != last()">,</xsl:if>
                    </xsl:if>-->
                    <xsl:if test="Name != 'Description'">
                        {
                        "Type": "Main",
                        "Name": "<xsl:value-of select="Name" />",
                        "Value": "<xsl:value-of select="Value" />"
                        }
                        <xsl:if test="position() != last()">,</xsl:if>
                    </xsl:if>

                </xsl:for-each>
                ]
            </xsl:if>
            }<xsl:if test="position() != last()">,</xsl:if>
        </xsl:for-each>
        <xsl:if test="/Request/Record/Properties/Attribute/Value[../Name='Description']">
            ,"Description":"<xsl:value-of select="/Request/Record/Properties/Attribute/Value[../Name='Description']"/>"
        </xsl:if>
        }
    </xsl:template>
</xsl:stylesheet>

Upvotes: 0

Martin Honnen
Martin Honnen

Reputation: 167581

In XSLT 3 (as available with Saxon 9.8 or later and AltovaXML 2017 R3 and later) you can simply transform your XML to the XML format that the xml-to-json function https://www.w3.org/TR/xpath-functions/#func-xml-to-json expects and then apply that function:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns="http://www.w3.org/2005/xpath-functions"
    expand-text="yes"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:output method="text"/>

  <xsl:template match="Request">
      <xsl:variable name="json-xml">
          <map>
              <xsl:apply-templates/>
          </map>
      </xsl:variable>
      <xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
  </xsl:template>

  <xsl:template match="Record">
      <map key="{local-name()}">
          <xsl:apply-templates/>
      </map>
  </xsl:template>

  <xsl:template match="ID | OtherID | Title">
      <string key="{local-name()}">{.}</string>
  </xsl:template>

  <xsl:template match="Properties">
      <array key="{local-name()}">
          <xsl:apply-templates select="Attribute[not(Name = 'Description')]"/>
      </array>
      <string key="Description">{Attribute[Name = 'Description']/Value}</string>
  </xsl:template>

  <xsl:template match="Attribute">
      <map>
          <xsl:apply-templates select="@type, Value, Name"/>
      </map>
  </xsl:template>

  <xsl:template match="Attribute/@* | Attribute/*">
      <string key="{local-name()}">{.}</string>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/bnnZWz/2

The same selection to distinguish Attribute[not(Name = 'Description')] and Attribute[Name = 'Description']/Value would of course also help with your original code.

Upvotes: 1

Related Questions