how to store xml payload in variable and refer it in stylesheet in apigee

I am working on a scenario where from the input the codes at name field inside the pair tag should be replaced with respective json field and also for this respective pair the respective Int and double values also should be printed under the json field.Below are the json field names for the codes:I am not sure where to store these below codes and access in xsl stylesheet for doing the transformation.

<Data> 
  <code id="30031"> 
    <Jsonfield>Clearing</Jsonfield> 
  </code> 
  <code id="30030"> 
    <Jsonfield>memo</Jsonfield> 
  </code> 
  <code id="30027">
    <Jsonfield>notes</Jsonfield> 
  </code> 
  <code id="30027">
    <Jsonfield>wsdl</Jsonfield> 
  </code> 
  <code id="30027">
    <Jsonfield>phoneBook</Jsonfield> 
  </code> 
</Data>

Request:

0 6 4,820
6 REPLIES 6

I am not quite clear on the end goal you have. Specifically I cannot understand this part:

the input the codes at name field inside the pair tag should be replaced with respective json field and also for this respective pair the respective Int and double values also should be printed under the json field.

I tried reading it several times but I don't get it. But I can provide some information about reading from a second XML document in the XSLT. And also a suggestion that maybe you don't really want to do that.

OK, on item #1. Normally an XSLT has two inputs: the source XML document, and the XSL sheet. I think you are saying that you would like some additional information to be accessible by the XSL, and that information itself is formatted as XML. This is possible to do, but not with the builtin XSL policy included with Apigee Edge.

Some time ago I built a custom Java callout that performs XSL transforms, and adds a few capabilities that the builtin XSL policy lacks. One of those capabilities is the ability to instantiate a document object within the XSL, from a string. Find the custom callout here: https://github.com/DinoChiesa/ApigeeEdge-Java-Xslt

There is a readme there that describes how to use the data: URI scheme to instantiate a document within your XSL, from the string that contains the XML. You only need to assign your XML string value to a variable, possibly in the policy config, like this:

<JavaCallout name='JavaCallout-Xslt-1'>
  <Properties>
     <Property name='xslt'>{xslturl}</Property>
     <Property name='engine'>saxon</Property>
     <Property name='input'>response</Property>
     <Property name='output'>response.content</Property>
     <!-- parameter to pass to the XSLT -->
     <Property name='param_map'><Data> 
  <code id="30031"> 
    <Jsonfield>Clearing</Jsonfield> 
  </code> 
  <code id="30030"> 
    <Jsonfield>memo</Jsonfield> 
  </code> </Data></Property>
  </Properties>
  <ClassName>com.google.apigee.edgecallouts.xslt.XsltCallout</ClassName>
  <ResourceURL>java://edge-custom-xslt-1.0.9.jar</ResourceURL>
</JavaCallout>

...then in the XSL, you would do something like this:

<xsl:param name="map" select="''"/>
<xsl:variable name="mapxml" select="document(concat('data:text/xml,',$map))"/>

 ...


<xsl:for-each select="$mapxml/Data/code"> ...

You cannot use the data: URI scheme in the builtin XSL policy. You need the callout for that to work.

Now for item #2. While I am confident that this custom callout will work for your purposes, I think maybe it might be more than you need. If you can format your configuration in something that is not XML, then you can just parameterize the builtin XSL policy. This might be a good example:

<XSL name='XSL-1'>
  <Source>message</Source>
  <OutputVariable>transformedContent</OutputVariable>
  <ResourceURL>xsl://1.xsl</ResourceURL>
  <Parameters ignoreUnresolvedVariables='true'>
    <Parameter name='map'>30031:Clearing|30030:memo|30027:notes</Parameter>
  </Parameters>
</XSL>

You can see I've transformed the data that was in your original configuration XML above, into a delimited string. In the XSL itself, you can reference that parameter like this:

      <xsl:param name="map" select="''"/>

You would then need to parse the map string... split it by pipes, then split those things by colons. In XSL 2.0, you can use the tokenize() function to do that. like this:

<xsl:stylesheet version="2.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">


  <xsl:output method="xml"
              encoding="utf-8"
              indent="yes"
              xslt:indent-amount="2" xmlns:xslt="http://xml.apache.org/xslt" />
  <xsl:strip-space elements="*"/>


  <xsl:param name="map" select="''"/>
   <!-- eg, 30031:Clearing|30030:memo|30027:notes -->

    <xsl:template match="/">
        <mapitems>
          <xsl:for-each select="tokenize($map,'\|')">
              <xsl:call-template name="split_one">
                <xsl:with-param name="item" select="." />
              </xsl:call-template>
            </xsl:for-each>
        </mapitems>
    </xsl:template>


    <xsl:template name="split_one">
      <!-- input is like 30030:memo -->
      <xsl:param name="item" />
      <code>
        <xsl:attribute name='id'><xsl:value-of select="tokenize($item,':')[1]"/></xsl:attribute>
        <JsonField><xsl:value-of select="tokenize($item,':')[2]"/></JsonField>
      </code>
    </xsl:template>


</xsl:stylesheet>

Obviously that's not exactly what you want. It sounds like you want to use the 30030 / memo stuff as metadata that somehow drives the operation of the transform. As I said above I don't really understand what you were intending, so I cannot give you a working example that solves your problem. The above XSL just shows you how to tokenize a string and might get you a little further down the path toward your goal.

thanks Dino for your help.

actually for the request that I am sending to apigee should give the below response, because request has the codes 30031 for -clearing, 30030 for -memo. Also these codes will be having either of the one among string.Int or double values, those also should be displayed like below with the respective json field.

Example response: { "clearing":"000", "memo":"5"}

so we have request and the response. my main question here is how i should access the xml/text file containing below code for mapping to do the transformation from incoming xml request to json in style sheet

<Data>
  <code id="30031">
    <Jsonfield>Clearing</Jsonfield>
  </code>
  <code id="30030">
    <Jsonfield>memo</Jsonfield>
  </code>
  <code id="30027">
    <Jsonfield>notes</Jsonfield>
  </code>
  <code id="30027">
    <Jsonfield>wsdl</Jsonfield>
  </code>
  <code id="30027">
    <Jsonfield>phoneBook</Jsonfield>
  </code>
</Data>

my main question here is how i should access the xml/text file containing below code for mapping to do the transformation from incoming xml request to json in style sheet

OK, I'm clear now on what you are asking.

I think I answered that question above. I guess it was not clear, so let me try to restate the answer.

You can read an XML data from within an XSL sheet if you follow these steps:

  1. use the custom Java callout XSLT policy, instead of the builtin XSL policy
  2. make your XML available to the policy. You can do that in one of two ways:
    1. Assign it to a string variable, with assignmessage/assignvariable, or with a KVM lookup, or by some other means
    2. reference that XML string variable as a parameter in the policy config
  3. use the data: uri scheme in the XSL sheet to instantiate a document.
  4. you will then be able to select nodes from the XML from within your stylesheet

ALTERNATIVELY, if you don't want to use the custom java callout and you prefer to use the builtin XSL policy, then you can

  1. reformat the data into something easily parseable, like a delimited string. I suggested pipes and colons. (BTW you could use a separate XSL policy for this purpose!)
  2. use XSL 2.0 and the tokenize() function to parse the incoming data

I would like to give you a working example, but I can't. You showed me the desired output, but not the input that would correspond to that output, so I can't really make a working example.

Thanks Dino. Now i understood clearly. I will do work around on it because I am new to custom java callout.

I am having the xslt code completely for this scenario, but I have done this on datapower where we can access xml file through xsl sheet, now I got a requirement to do the same in apigee.

Also for the input that corresponds to the previously provided output is attached in the first post.I am also attaching again for your reference.input.txt

Below is the input:

	<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <Update>
         <Account>12345</Account>
         <Records>
            <Record>
               <Id>MST</Id>
               <Fields>
                  <Pair>
                     <Name>30031</Name>
                     <IntValue>4</IntValue>
                     <DoubleValue>1.2</DoubleValue>
                  </Pair>
                  <Pair>
                     <Name>30030</Name>
                     <StringValue>5</StringValue>
                     <IntValue>0</IntValue>
                     <DoubleValue>0</DoubleValue>
                  </Pair>
               </Fields>
            </Record>
            <Record>
               <Id>MEM</Id>
               <Fields>
                  <Pair>
                     <Name>30027</Name>
                     <StringValue>00000</StringValue>
                     <IntValue>0</IntValue>
                     <DoubleValue>0</DoubleValue>
                  </Pair>
               </Fields>
               <Fields>
                  <Pair>
                     <Name>31016</Name>
                     <StringValue>N</StringValue>
                     <IntValue>0</IntValue>
                     <DoubleValue>0</DoubleValue>
                  </Pair>
               </Fields>
            </Record>
         </Records>
      </Update>
   </soap:Body>
</soap:Envelope>

Hi Annapurna,

You cannot directly instantiate an XML document from a string within a stylesheet, in Apigee Edge. Wait, This isn't entirely accurate. the Stylesheet used by the XSL policy does implicitly instantiate a document, by reading the input document in the "message" being processed by the proxy. A more accurate statement is, when using the builtin XSL policy within Apigee Edge, you cannot instantiate a second XML document from a string.

HOWEVER,

Option 1

You can instantiate a second document from an external URL. If the document you wish to address is at https://foo.bar/doc1 , then within the XSL you can reference that URL and instantiate a doc from it. The XSL can then process the "message" document differently based on data in the second document. An example of instantiating from a URL:

The XSL policy:

<XSL name='XSL-1'>
  <Source>response</Source>
  <OutputVariable>transformed_content</OutputVariable>
  <ResourceURL>xsl://sheet.xsl</ResourceURL>
  <Parameters ignoreUnresolvedVariables='true'>
    <Parameter name='url1'>https://foo.bar/doc1</Parameter>
  </Parameters>
</XSL>

The XSL sheet:

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

      <!-- this parameter is set in the XSL Policy config -->
      <xsl:param name="url1" select="''"/>


      <!-- the following retrieves the remote document into a variable
           available to the stylesheet -->
      <xsl:variable name="xdoc1" select="document($url1)"/>
       ...

Option 2

ALTERNATIVELY, if your XML document is not available at a specific URL, but is instead available only as a string to your proxy, then, you CAN use the built-in XSL policy to transform that XML document into a delimited string. And then you can reference that delimited string in a second XSL.

The steps would be:

  1. assign a "contrived" message, use content-type = application/xml, with the content being your metadata, like this:

    <AssignMessage name='AM-MessageHoldingMetadata'>
      <AssignTo createNew='true' transport='http' type='response'>MessageHoldingMetadata</AssignTo>
      <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
      <Set>
        <Payload contentType='application/xml'>
          <Data>
      <code id="30031">
        <Jsonfield>clearing</Jsonfield>
      </code>
      <code id="30030">
        <Jsonfield>memo</Jsonfield>
      </code>
      <code id="30027">
        <Jsonfield>notes</Jsonfield>
      </code>
      <code id="31016">
        <Jsonfield>wsdl</Jsonfield>
      </code>
      <code id="30099">
        <Jsonfield>phoneBook</Jsonfield>
      </code>
          </Data>
        </Payload>
        <StatusCode>200</StatusCode>
        <ReasonPhrase>OK</ReasonPhrase>
      </Set>
    </AssignMessage>
    	
  2. Use an XSL to transform that into a delimited string. The output looks like this:

    30031:clearing|30030:memo|30027:notes|31016:wsdl|30099:phoneBook
    	
  3. Use a 2nd XSL to transform the original SOAP into the desired format, relying on the delimited string to dictate the "map" between numbers and labels (30031 = clearing, etc).

Attached please find a working example proxy showing how this works.

annapurna-xsl.zip

Hi Dino,

I have tested the given zip content and it is not giving the relevant response.

It is giving as below :

{ "Account": "12345",

"Record": [

{ "Id": "MST" },

{ "Id": "MEM" } ]

}

But it should be like:

{ "Account":"12345", "clearing":"000", "memo":"5" }

Also the output of reformatMetaData.xsl is not comming as 30031:Clearing|30030:memo|30027:notes