Defining XML File as a resource and then reading it from an XSLT

Is it possible to define an XML file as a resource? I would like to use XML file as a "properties" file so I can read it in an XSLT Policy.The purpose of the Properties file is to externalize data instead of hardcoding inside an API Proxy Policy.

1 7 1,241
7 REPLIES 7

Not applicable

Hi Prabhu,

Yes it is possible to have XML as input to XSLT using XSLT policy

Here is the sample code reference..source here can be the file itself

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <XSL async="false" continueOnError="false" enabled="true" name="Format-Response"> <DisplayName>Format Response</DisplayName> <Properties/> <Source>response</Source> <ResourceURL>xsl://XSL-Transform-1.xslt</ResourceURL> <Parameters ignoreUnresolvedVariables="true"/> <OutputVariable>response.content</OutputVariable> </XSL>

Please let me know if you need more information.

--Regards

Nagesh

@nagesh

Nagesh, thanks for responding.

Two questions:

a. If the source itself can be an xml file- -- do you have an example of the syntax? Is it just <Source>xml://properties.xml</Source> If so, how do I define the XML to Apigee? Is it a script? I don't see an option to select "XML" when I do "New Script".

b. What I was really looking for is this -

I want the Source to be "request" or "response" (depending upon whether the flow is request or response) because that is the context where the input XML is stored. I will be transforming that input XML to a new XML using XSLT.

Inside the XSLT, I want to read an external XML file using the following syntax -

<xsl:variable name="properties" select="document('properties.xml')"/>

Can I do that? If so, how do I define the properties.xml as a resource/script ?

Good question!

For me, I've found it to be easier to define settings in a .JS file, as a JS object, rather than in XML.

That way, you can store it as a JSC resource. You can also have a brief, boilerplate applySettings.js file that reads data from the settings file, and sets context variables accordingly.

Example of a generic applySettings.js:

// settings must be available in settings.js and included via
//   <IncludeURL>jsc://settings.js</IncludeURL>

function flatten(target, opts) {
  opts = opts || {};

  var delimiter = opts.delimiter || '.',
      maxDepth = opts.maxDepth,
      currentDepth = 1,
      output = {};

  function step(object, prev) {
    Object.keys(object).forEach(function(key) {
      var value = object[key],
          isarray = opts.safe && Array.isArray(value),
          type = Object.prototype.toString.call(value),
          isobject = (type === "[object Object]" || type === "[object Array]" ),
          newKey = prev ? prev + delimiter + key : key;

      if (!opts.maxDepth) {
        maxDepth = currentDepth + 1;
      }

      if (!isarray && isobject && Object.keys(value).length && currentDepth < maxDepth) {
        ++currentDepth;
        return step(value, newKey);
      }

      output[newKey] = value;
    });
  }

  step(target);

  return output;
}

var flatObj = flatten(settings);

Object.keys(flatObj).forEach(function(key){
  context.setVariable('settings.' + key, flatObj[key]);
});

And then your settings.js file would look like this:

var settings =  {
      baas : {
        org: "orgname",
        app: "appname",
        client_id : "1234xxxx",
        client_secret : "5678xxxx"
      }
    };

Configuring the settings policy would be like this:

<Javascript name='JS-ApplySettings' timeLimit='200' >
  <IncludeURL>jsc://settings.js</IncludeURL>
  <ResourceURL>jsc://applySettings.js</ResourceURL>
</Javascript>

And then you'll want to insert this policy in the PreFlow of the Proxy, so that the settings would be available at every point thereafter. In this example, you would have these context variables:

settings.baas.org = orgname
settings.baas.app = appname
settings.baas.client_id = 1234xxxx
settings.baas.client_secret 5678xxxx

So, for example, you could include a ServiceCallout policy referencing these variables like so:

<ServiceCallout name="ServiceCallout-GetBaasCreds">
    <Request clearPayload="true" variable="baasCredsRequest">
        <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
        <Set>
          <!-- Shows how to request a token from usergrid. -->
          <!-- FIXME: should retrieve secrets from vault -->
           <Payload contentType='application/json' variablePrefix='%'
                    variableSuffix='#'><![CDATA[{
    "grant_type":"client_credentials",
    "client_id":"%settings.baas.client_id#",
    "client_secret":"%settings.baas.client_secret#"
}]]></Payload>
         <Verb>POST</Verb>
         <Path>/{settings.baas.org}/{settings.baas.app}/token</Path>
      </Set>
    </Request>
    <Response>baasCredsResponse</Response>
    <HTTPTargetConnection>
        <Properties/>
        <URL>https://api.usergrid.com</URL>
    </HTTPTargetConnection>
</ServiceCallout>

@Dino. Thanks Dino. I will give that a try.

Hi @Dino

I am using your above JS code (applySettings.js and settings.js) as it is for my substitution of target url in API Proxy.

It works well when we have string as key in settings.js but it doesn't work when we have key as number in settings.js

Example it doesn't work with below

var settings = {
    "12345" : {
        beurl: "rhel7-8.int.test.com"
    }
};

when I want to get the below value it gives me null

	settings.12345.beurl = null

Can we have number also covered in above applySettings.js

Thanks in advance for your support

Hi @GAURAV

yes, I think you should be able to get what you want by using the underscore as the delimiter. The applySettings.js code would look like this:

function flatten(target, opts) {
  opts = opts || {};

  var delimiter = opts.delimiter || '_',
      maxDepth = opts.maxDepth,
      currentDepth = 1,
      output = {};

  function step(object, prev) {
    Object.keys(object).forEach(function(key) {
      var value = object[key],
          isarray = opts.safe && Array.isArray(value),
          type = Object.prototype.toString.call(value),
          isobject = (type === "[object Object]" || type === "[object Array]" ),
          newKey = prev ? prev + delimiter + key : key;

      if (!opts.maxDepth) {
        maxDepth = currentDepth + 1;
      }

      if (!isarray && isobject && Object.keys(value).length && currentDepth < maxDepth) {
        ++currentDepth;
        return step(value, newKey);
      }

      output[newKey] = value;
    });
  }
  step(target);
  return output;
}

var flatObj = flatten(settings);
Object.keys(flatObj).forEach(function(key){
  context.setVariable('settings_' + key, flatObj[key]);
});

You can see I've replaced the dot with the underscore in two places. Then, you will need to refer to the variables like so:

settings_12345_beurl

In the future, can you please ask new questions in new threads?

It's easier for people to see them, that way. Asking a new question in a comment... means it is more likey your question will get lost. You can refer to the older comment using a hyperlink in the new question.

eg

title of question: "About applySettings.js and numeric keys"

text of question: "Hey Dino, in this comment (hyperlink here) you provided an applySettings.js module. I have a question about that..."

Not applicable

@prabhup1

Hi Prabhu,

I would rather suggest to use Key Value Map Operations policy on edge, so that you would be able to create and manage properties at environment, org or api level as scope.

You can access the values using GET operations and use the required properties as flow variables.

For more information: KVM Operations

--Regards,

Nagesh