XSLT - how can I filter data source by data from external XML? - xpath

I have the following XML data structure:
<root>
<info>
<creationDate>2015-03-11 11:45:49</creationDate>
</info>
<promotions>
<promotion>
<header>
<id>1</id>
<name>Name 1</name>
</header>
<positions>
<position>
<id>1</id>
<name>Some position name 1</name>
</position>
<position>
<id>2</id>
<name>Some position name 2</name>
</position>
</position>
</positions>
</promotion>
<promotion>
<header>
<id>2</id>
<name>Name 2</name>
</header>
<positions>
<position>
<id>3</id>
<name>Some position name 3</name>
</position>
</positions>
</promotion>
</promotions>
</root>
Which is used as primary data source for my template like this:
<xsl:template match="root">
...
I need to filter the above by "filter.xml" file, containing promotion Id's to filter out, and it needs to work on IE7 . Is something like this possible?
For starters I tried to find a way to add filter in xsl:apply-templates select statement, so that only promotion with Id=2 will be processed by template but failed - is it possible to write Xpath that will say:
"Give me everything from root node but promotions only with Id = 2" ?
Thanks.
EDIT1:
Sorry about the namespace - it shouldn't be there in the first place. As for the filter.xml - it is not clearly defined yet - for now, I'm using the following:
<usedUpPromotions>
<header>
<promotionId>
1
</promotionId>
</header>
<header>
<promotionId>
2
</promotionId>
</header>
<header>
<promotionId>
3
</promotionId>
</header>
</usedUpPromotions>
I think of using something like:
<xsl:apply-templates select="root[hereIsMyWhereId != (document('Load externalXmlHere')/select/IdtoFilterOut)"/>
But I can't seem to find a way to filter out data that way...
EDIT2:
I'll try to explain using code as example - let's assume for a moment that we have the following:
XmlData initialXmlData; <- this is our XML data before filtering
XmlData filter; -< this contains the filter.xml data
Html GenerateHtmlFromTemplate(XmlData initialXmlData) - this is my Xslt template
{
...some precessing here
}
I would like to modify my template to achieve the following:
Html GenerateHtmlFromTemplate(XmlData initialXmlData, XmlData filter)
{
XmlData filteredData = data.FilterBy(filter);
...same processing here as above, but use 'filteredData', instead of 'initialXmlData'
}
I hope it's more clear now :) - the main problem, it would seem is that the Id element, I want to filter by, is inside array variable, so I cannot simply use:
in my primary template - instead I bypassed the problem, by filtering on for-each loop later on but I would still like to know if it is possible to simply tell the template "from now on use filtered data, instead of original".
Edit3:
#michael.hor257k - to answer your question, I have modified templates you provided:
Template.xml:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="#"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ksx="http://www.test.com/test" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<xsl:output encoding="utf-8" indent="no" method="html"/>
<root xmlns="http://www.test.com/test">
<info>
<creationDate>2015-03-11 11:45:49</creationDate>
</info>
<promotions>
<promotion>
<header>
<id atr="tre">1</id>
<name>Promotion 1</name>
</header>
<positions>
<position>
<id>1</id>
<name>Position 1a</name>
</position>
<position>
<id>2</id>
<name>Position 1b</name>
</position>
</positions>
</promotion>
<promotion>
<header>
<id>2</id>
<name>Promotion 2</name>
</header>
<positions>
<position>
<id>3</id>
<name>Position 2a</name>
</position>
</positions>
</promotion>
</promotions>
</root>
<xsl:param name="new-path" select="'new.xml'"/>
<xsl:variable name="new-promotions" select="document($new-path)/newPromotions/promotion" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ksx:root">
<xsl:copy>
<xsl:apply-templates select="ksx:promotions/ksx:promotion[not(ksx:header/ksx:id=$new-promotions/header/id)]"/>
<xsl:apply-templates select="$new-promotions"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
new.xml:
<?xml version="1.0" encoding="utf-8"?>
<newPromotions>
<promotion>
<header>
<id>2</id>
<name>New Promotion 2</name>
</header>
<positions>
<position>
<id>4</id>
<name>New Position 2A</name>
</position>
</positions>
</promotion>
<promotion>
<header>
<id>3</id>
<name>New Promotion 3</name>
</header>
<positions>
<position>
<id>5</id>
<name>New Position 3A</name>
</position>
</positions>
</promotion>
</newPromotions>
If you save those, and open template.xml in Chrome it will work nicely - what I wanted however was to filter out data outside template node:
<xsl:template match="ksx:root">
So, I tried this:
<xsl:template match="ksx:root[ksx:promotion/ksx:header/ksx:id=1]">
And I expected to get all data from root but with promotions filtered to those with Id = 1 - but it gave all data and no errors, now I noticed that this:
<xsl:template match="ksx:root[ksx:promotions/ksx:promotion/ksx:header/ksx:id=$new-promotions/header/id]">
causes an error "Variables cannot be used within this expression" - so I guess, what I want to do is probably impossible from outside the template node...?
Sorry for the confusion - I hope now it is more clear. I simply wanted to treat template node as a method and "pass" filtered data to it, instead of filtering the data inside of it.
Edit4:
In my specific case, I have large "root" dataset of promotions, and small, external XML file that contains promotion Id's to hide during processing. So in my case filtering would mean: "Take everything from root element, but filter promotions so only those which Ids are NOT in the external file, will be processed". So if I have:
<root xmlns="http://www.test.com/test">
<info>
<creationDate>2015-03-11 11:45:49</creationDate>
</info>
<someData1>Some data 1</someData1>
<someData2>Some data 2</someData2>
<someData3>Some data 3</someData3>
<promotions>
<promotion>
<header>
<id>1</id>
<name>Promotion 1</name>
</header>
<positions>
<position>
<id>1</id>
<name>Position 1a</name>
</position>
<position>
<id>2</id>
<name>Position 1b</name>
</position>
</positions>
</promotion>
<promotion>
<header>
<id>2</id>
<name>Promotion 2</name>
</header>
<positions>
<position>
<id>3</id>
<name>Position 2a</name>
</position>
</positions>
</promotion>
<promotion>
<header>
<id>3</id>
<name>Promotion 3</name>
</header>
<positions>
<position>
<id>4</id>
<name>Position 3a</name>
</position>
</positions>
</promotion>
</promotions>
</root>
And filter by:
<?xml version="1.0" encoding="UTF-8"?>
<usedUpPromotions>
<id>1</id>
<id>2</id>
</usedUpPromotions>
Then I would expect to get:
<root xmlns="http://www.test.com/test">
<info>
<creationDate>2015-03-11 11:45:49</creationDate>
</info>
<someData1>Some data 1</someData1>
<someData2>Some data 2</someData2>
<someData3>Some data 3</someData3>
<promotions>
<promotion>
<header>
<id>3</id>
<name>Promotion 3</name>
</header>
<positions>
<position>
<id>4</id>
<name>Position 3a</name>
</position>
</positions>
</promotion>
</promotions>
</root>

To process only promotions with Id=2, you can use (from the context of root):
<xsl:apply-templates select="promotions/promotion[header/id='2']"/>
Added:
Here's an example showing how you can "merge" your input XML with an overriding XML document. Given:
XML
<root>
<info>
<creationDate>2015-03-11 11:45:49</creationDate>
</info>
<promotions>
<promotion>
<header>
<id>1</id>
<name>Promotion 1</name>
</header>
<positions>
<position>
<id>1</id>
<name>Position 1a</name>
</position>
<position>
<id>2</id>
<name>Position 1b</name>
</position>
</positions>
</promotion>
<promotion>
<header>
<id>2</id>
<name>Promotion 2</name>
</header>
<positions>
<position>
<id>3</id>
<name>Position 2a</name>
</position>
</positions>
</promotion>
</promotions>
</root>
new.xml
<newPromotions>
<promotion>
<header>
<id>2</id>
<name>New Promotion 2</name>
</header>
<positions>
<position>
<id>4</id>
<name>New Position 2A</name>
</position>
</positions>
</promotion>
<promotion>
<header>
<id>3</id>
<name>New Promotion 3</name>
</header>
<positions>
<position>
<id>5</id>
<name>New Position 3A</name>
</position>
</positions>
</promotion>
</newPromotions>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="new-path" select="'path/to/new.xml'"/>
<xsl:variable name="new-promotions" select="document($new-path)/newPromotions/promotion" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="promotions">
<xsl:copy>
<xsl:apply-templates select="promotion[not(header/id=$new-promotions/header/id)]"/>
<xsl:apply-templates select="$new-promotions"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<root>
<info>
<creationDate>2015-03-11 11:45:49</creationDate>
</info>
<promotions>
<promotion>
<header>
<id>1</id>
<name>Promotion 1</name>
</header>
<positions>
<position>
<id>1</id>
<name>Position 1a</name>
</position>
<position>
<id>2</id>
<name>Position 1b</name>
</position>
</positions>
</promotion>
<promotion>
<header>
<id>2</id>
<name>New Promotion 2</name>
</header>
<positions>
<position>
<id>4</id>
<name>New Position 2A</name>
</position>
</positions>
</promotion>
<promotion>
<header>
<id>3</id>
<name>New Promotion 3</name>
</header>
<positions>
<position>
<id>5</id>
<name>New Position 3A</name>
</position>
</positions>
</promotion>
</promotions>
</root>
In response to your edit #4:
So in my case filtering would mean: "Take everything from root
element, but filter promotions so only those which Ids are NOT in the
external file, will be processed".
My answer above does two things:
It copies everything from the input XML document except promotions whose IDs are in the external file;
It adds all the promotions listed in the external file.
If you only want to do #1 but not #2, then change this:
<xsl:template match="promotions">
<xsl:copy>
<xsl:apply-templates select="promotion[not(header/id=$new-promotions/header/id)]"/>
<xsl:apply-templates select="$new-promotions"/>
</xsl:copy>
</xsl:template>
to:
<xsl:template match="promotions">
<xsl:copy>
<xsl:apply-templates select="promotion[not(header/id=$new-promotions/header/id)]"/>
</xsl:copy>
</xsl:template>
In the given example, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<info>
<creationDate>2015-03-11 11:45:49</creationDate>
</info>
<promotions>
<promotion>
<header>
<id>1</id>
<name>Promotion 1</name>
</header>
<positions>
<position>
<id>1</id>
<name>Position 1a</name>
</position>
<position>
<id>2</id>
<name>Position 1b</name>
</position>
</positions>
</promotion>
</promotions>
</root>

Related

Total of true and false in current time

I have got this XML document. Is there way how to sort the nodes by ascending time and by brand and then count how many cars (samples), true and false was in this time from start time (always the lowest time in the group)?
<?xml version="1.0" encoding="UTF-8"?>
<trade>
<car time="1950" brand="audi" trend="true">
</car>
<car time="1200" brand="renault" trend="true">
</car>
<car time="1000" brand="audi" trend="true">
</car>
<car time="2800" brand="renault" trend="true">
</car>
<car time="2000" brand="audi" trend="true">
</car>
<car time="1500" brand="renault" trend="true">
</car>
<car time="1900" brand="audi" trend="false">
</car>
<car time="2300" brand="audi" trend="false">
</car>
<car time="2100" brand="renault" trend="false">
</car>
</trade>
Wanted result in HTML
Consider to include what you have got next time in the question text. If you already know how to group and how to sort you can then easily process the sorted sequence and take the subsequence until each item and check the count of the items with a certain trend as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
expand-text="yes"
version="3.0">
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:template match="/">
<html>
<head>
<title>.NET XSLT Fiddle Example</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="trade">
<xsl:for-each-group select="car" group-by="#brand">
<h2>{current-grouping-key()}</h2>
<table>
<thead>
<tr>
<th>Total of samples</th>
<th>Time</th>
<th>Total of TRUE</th>
<th>Total of FALSE</th>
</tr>
</thead>
<xsl:variable name="sorted-cars" as="element(car)*">
<xsl:perform-sort select="current-group()">
<xsl:sort select="xs:integer(#time)"/>
</xsl:perform-sort>
</xsl:variable>
<tbody>
<xsl:for-each select="$sorted-cars">
<tr>
<td>{position()}</td>
<td>{#time}</td>
<xsl:variable name="car-group" select="subsequence($sorted-cars, 1, position())"/>
<td>{count($car-group[#trend = 'true'])}</td>
<td>{count($car-group[#trend = 'false'])}</td>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/nc4NzQ1

Combine two queries using WSO2 ESB

I've been trying to figure out how to get WSO2's ESB to make calls to two different APIs and combine their results into a single response, and running into nothing but trouble. At its most basic, I've got two backends I'm making requests to that respond something like this:
http://example.com/items:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<response xmlns="http://example.com/response">
<query name="items" xmlns="http://example.com/query">
<row>
<id>1</id>
<name>Item 1</name>
</row>
<row>
<id>2</id>
<name>Item 2</name>
</row>
</query>
</response>
</soapenv:Body>
</soapenv:Envelope>
http://example.com/parts:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<response xmlns="http://example.com/response">
<query name="parts" xmlns="http://example.com/query">
<row>
<id>1</id>
<part>Part 1.1</part>
</row>
<row>
<id>1</id>
<part>Part 1.2</part>
</row>
<row>
<id>1</id>
<part>Part 1.3</part>
</row>
<row>
<id>2</id>
<part>Part 2.1</part>
</row>
<row>
<id>2</id>
<part>Part 2.2</part>
</row>
</query>
</response>
</soapenv:Body>
</soapenv:Envelope>
I'd like to request both of those, then combine their results to look something like this:
<items>
<item>
<id>1</id>
<name>Item 1</name>
<parts>
<part>
<id>1</id>
<name>Part 1.1</name>
</part>
<part>
<id>1</id>
<name>Part 1.2</name>
</part>
<part>
<id>1</id>
<name>Part 1.3</name>
</part>
</parts>
</item>
<item>
<id>2</id>
<name>Item 2</name>
<parts>
<part>
<id>2</id>
<name>Part 2.1</name>
</part>
<part>
<id>2</id>
<name>Part 2.2</name>
</part>
</parts>
</item>
</items>
Basically, every response from both APIs has a list of rows, each of which contains an id element. The ids in the call to /items are unique within that response, and each row in the response from parts has an id that ties it to a row from /items.
I've got the following API definition in the ESB:
<?xml version="1.0" encoding="UTF-8"?>
<api context="/item_list" name="ItemList" xmlns="http://ws.apache.org/ns/synapse">
<resource methods="POST" uri-template="/">
<inSequence>
<header name="Content-Type" scope="transport" value="text/xml; charset=utf-8"/>
<clone>
<target>
<sequence>
<send>
<endpoint>
<address format="soap11" uri="http://example.com/items"/>
</endpoint>
</send>
</sequence>
</target>
<target>
<sequence>
<send>
<endpoint>
<address format="soap11" uri="http://example.com/parts"/>
</endpoint>
</send>
</sequence>
</target>
</clone>
</inSequence>
<outSequence>
<aggregate>
<correlateOn expression="//*[name()='response']/*[name()='query']/*[name()='row']/*[name()='id']" />
<completeCondition>
<messageCount max="2" min="2"/>
</completeCondition>
<onComplete expression="//*[name()='response']/*[name()='query']/*[name()='row']">
<send/>
</onComplete>
</aggregate>
</outSequence>
<faultSequence/>
</resource>
</api>
The inSequence here is heavily simplified, but it does send two valid queries and gets back the expected responses. The outSequence as it's written here never sends a response to the client or logs an error on the server. If I remove the correlateOn element from aggregate, I get back a single row, seemingly at random, from one of the two API calls. I think correlateOn is something I want to be using here, but I can't find any useful documentation on it from either WSO2 or Apache, so I'm sure I'm using it incorrectly. My XPath background is pretty weak, so I'm sure that expression could also use some work.
Am I at least on the right track here with the clone/aggregate pattern? How would I go about combining the results from these two queries into something similar to my example? If I can get something even sort of close, I should be able to do the rest with XSLT.
take a look at this demo:
Backend 1 with the items response:
<?xml version="1.0" encoding="UTF-8"?>
<proxy xmlns="http://ws.apache.org/ns/synapse"
name="items"
transports="https http"
startOnLoad="true">
<target>
<inSequence>
<payloadFactory media-type="xml">
<format>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<response xmlns="http://example.com/response">
<query xmlns="http://example.com/query" name="items">
<row>
<id>1</id>
<name>Item 1</name>
</row>
<row>
<id>2</id>
<name>Item 2</name>
</row>
</query>
</response>
</soapenv:Body>
</soapenv:Envelope>
</format>
<args/>
</payloadFactory>
<log level="full"/>
<loopback/>
</inSequence>
<outSequence>
<send/>
</outSequence>
<faultSequence/>
</target>
</proxy>
Backend 2 with the parts response:
<?xml version="1.0" encoding="UTF-8"?>
<proxy xmlns="http://ws.apache.org/ns/synapse"
name="parts"
transports="https http"
startOnLoad="true">
<target>
<inSequence>
<payloadFactory media-type="xml">
<format>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<response xmlns="http://example.com/response">
<query xmlns="http://example.com/query" name="parts">
<row>
<id>1</id>
<part>Part 1.1</part>
</row>
<row>
<id>1</id>
<part>Part 1.2</part>
</row>
<row>
<id>1</id>
<part>Part 1.3</part>
</row>
<row>
<id>2</id>
<part>Part 2.1</part>
</row>
<row>
<id>2</id>
<part>Part 2.2</part>
</row>
</query>
</response>
</soapenv:Body>
</soapenv:Envelope>
</format>
<args/>
</payloadFactory>
<log level="full"/>
<loopback/>
</inSequence>
<outSequence>
<send/>
</outSequence>
<faultSequence/>
</target>
</proxy>
My API calling backend 1 and backend 2 and transforming with xslt:
<?xml version="1.0" encoding="UTF-8"?>
<api xmlns="http://ws.apache.org/ns/synapse"
name="ItemList"
context="/item_list">
<resource methods="POST" uri-template="/">
<inSequence>
<header name="Action" scope="default" value="urn:mediate"/>
<call>
<endpoint>
<address uri="http://localhost:8283/services/items.itemsHttpSoap11Endpoint"
format="soap11"/>
</endpoint>
</call>
<enrich>
<source type="inline" clone="true">
<Payloads/>
</source>
<target type="property" property="Items"/>
</enrich>
<enrich>
<source clone="true" xpath="$body/*"/>
<target action="child" xpath="$ctx:Items"/>
</enrich>
<payloadFactory media-type="xml">
<format>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body/>
</soapenv:Envelope>
</format>
<args/>
</payloadFactory>
<call>
<endpoint>
<address uri="http://localhost:8283/services/parts.partsHttpSoap11Endpoint"
format="soap11"/>
</endpoint>
</call>
<enrich>
<source clone="true" xpath="$body/*[name()='response']/*[name()='query']"/>
<target type="property" property="Parts"/>
</enrich>
<enrich>
<source type="property" clone="true" property="Parts"/>
<target action="child" xpath="$ctx:Items"/>
</enrich>
<enrich>
<source type="property" property="Items"/>
<target type="body"/>
</enrich>
<xslt key="transformTwoSourcesToOneResult"/>
<loopback/>
</inSequence>
<outSequence>
<send/>
</outSequence>
<faultSequence/>
</resource>
</api>
And my xslt transformation:
<?xml version="1.0" encoding="UTF-8"?>
<localEntry key="transformTwoSourcesToOneResult" xmlns="http://ws.apache.org/ns/synapse">
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://example.com/query"
xmlns:ns1="http://example.com/response"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:exslt="http://exslt.org/common"
xmlns:saxon="http://saxon.sf.net/"
xmlns:syn="http://ws.apache.org/ns/synapse"
exclude-result-prefixes="ns0 ns1 xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="var1_instance_Payloads" select="."/>
<items>
<xsl:for-each select="$var1_instance_Payloads/syn:Payloads">
<xsl:variable name="var2_Payloads" select="."/>
<xsl:for-each select="$var2_Payloads/ns1:response/ns0:query/ns0:row">
<xsl:variable name="var2_row" select="."/>
<item>
<id>
<xsl:value-of select="number(string($var2_row/ns0:id))"/>
</id>
<name>
<xsl:value-of select="string($var2_row/ns0:name)"/>
</name>
<parts>
<xsl:for-each select="$var2_Payloads/ns0:query/ns0:row">
<xsl:variable name="var4_row" select="."/>
<xsl:if test="string((number(string($var2_row/ns0:id)) = number(string($var4_row/ns0:id)))) != 'false'">
<part>
<id>
<xsl:value-of select="number(string($var4_row/ns0:id))"/>
</id>
<name>
<xsl:value-of select="string($var4_row/ns0:part)"/>
</name>
</part>
</xsl:if>
</xsl:for-each>
</parts>
</item>
</xsl:for-each>
</xsl:for-each>
</items>
</xsl:template>
</xsl:stylesheet>
</localEntry>
The API response:
<items xmlns="http://ws.apache.org/ns/synapse" xmlns:syn="http://ws.apache.org/ns/synapse" xmlns:saxon="http://saxon.sf.net/" xmlns:exslt="http://exslt.org/common">
<item>
<id>1</id>
<name>Item 1</name>
<parts>
<part>
<id>1</id>
<name>Part 1.1</name>
</part>
<part>
<id>1</id>
<name>Part 1.2</name>
</part>
<part>
<id>1</id>
<name>Part 1.3</name>
</part>
</parts>
</item>
<item>
<id>2</id>
<name>Item 2</name>
<parts>
<part>
<id>2</id>
<name>Part 2.1</name>
</part>
<part>
<id>2</id>
<name>Part 2.2</name>
</part>
</parts>
</item>
</items>

How to access variable as parameter from another template XSLT

Hello I have one problem, I have 2 variables(min_value and max_value) they are in different templates MIN and MAX, in MAX I want to calculate the difference between max_value and min_value but the result is NAN since min_value values are not transferred from one template to another.
<xsl:template match="MAX">
<xsl:param name= "min_value"/>
<xsl:variable name="max_value" select= "SHARE_RATE"/>
<span class="max_rate">
MAX: <xsl:apply-templates select="SHARE_RATE"/>
</span>
<br/>
<span class= "diff">
Diff: (<xsl:value-of select="$max_value - $min_value"/>)
</span>
</xsl:template>
<xsl:template match="MIN">
<xsl:variable name="min_value" select="SHARE_RATE"/>
<span class="SHARE_RATE">
MIN: <xsl:apply-templates select="SHARE_RATE"/>
</span>
<xsl:apply-templates select="MAX">
<xsl:with-param name="min_value" select= "$min_value"/>
</xsl:apply-templates>
</xsl:template>
UPDATE
<page shareid="%" min_rate="%" max_rate="%" skip="0">
<TRANSACTIONS>
<MIN num="1">
<SHAREID>0</SHAREID>
<SHARE_RATE>1200</SHARE_RATE>
</MIN>
<MIN num="2">
<SHAREID>1</SHAREID>
<SHARE_RATE>4200</SHARE_RATE>
</MIN>
<MIN num="3">
<SHAREID>2</SHAREID>
<SHARE_RATE>1600</SHARE_RATE>
</MIN>
<MIN num="4">
<SHAREID>3</SHAREID>
<SHARE_RATE>6100</SHARE_RATE>
</MIN>
<MIN num="5">
<SHAREID>4</SHAREID>
<SHARE_RATE>550</SHARE_RATE>
</MIN>
<MIN num="6">
<SHAREID>5</SHAREID>
<SHARE_RATE>420</SHARE_RATE>
</MIN>
<MIN num="7">
<SHAREID>6</SHAREID>
<SHARE_RATE>2000</SHARE_RATE>
</MIN>
</TRANSACTIONS>
<TRANSACTIONS>
<MAX num="1">
<SHAREID>0</SHAREID>
<SHARE_RATE>2100</SHARE_RATE>
</MAX>
<MAX num="2">
<SHAREID>1</SHAREID>
<SHARE_RATE>5200</SHARE_RATE>
</MAX>
<MAX num="3">
<SHAREID>2</SHAREID>
<SHARE_RATE>2000</SHARE_RATE>
</MAX>
<MAX num="4">
<SHAREID>3</SHAREID>
<SHARE_RATE>7000</SHARE_RATE>
</MAX>
<MAX num="5">
<SHAREID>4</SHAREID>
<SHARE_RATE>1000</SHARE_RATE>
</MAX>
<MAX num="6">
<SHAREID>5</SHAREID>
<SHARE_RATE>2520</SHARE_RATE>
</MAX>
<MAX num="7">
<SHAREID>5</SHAREID>
<SHARE_RATE>2520</SHARE_RATE>
</MAX>
</TRANSACTIONS>
</page>
UPDATE 2
<?xml version="1.0" encoding="ISO-8859-2"?>
<?xml-stylesheet type="text/xsl" href="share.xsl"?>
<page connection="labor"
xmlns:xsql="urn:oracle-xsql"
shareid="%"
min_rate= "%"
max_rate= "%"
skip="0">
<xsql:query rowset-element="TRANSACTIONS"
row-element="MIN"
skip-rows="{#skip}"
max-rows="{#max-rows}"
bind-params="shareid min_rate ">
SELECT
f77inq.shares.shareid, A.share_rate
FROM
f77inq.trans A
INNER JOIN f77inq.shares ON A.shareid = f77inq.shares.shareid
WHERE A.shareid LIKE ?
AND A.share_rate=
(
SELECT
MIN(share_rate)
FROM
f77inq.trans B
WHERE
B.shareid = A.shareid
)
AND A.share_rate LIKE?
</xsql:query>
<xsql:query rowset-element="TRANSACTIONS"
row-element="MAX"
skip-rows="{#skip}"
max-rows="{#max-rows}"
bind-params="shareid">
SELECT
f77inq.shares.shareid, A.share_rate
FROM
f77inq.trans A
INNER JOIN f77inq.shares ON A.shareid = f77inq.shares.shareid
WHERE A.shareid LIKE ?
AND A.share_rate=
(
SELECT
MAX(share_rate)
FROM
f77inq.trans B
WHERE
B.shareid = A.shareid
)
</xsql:query>
</page>
I would suggest you use a key to link the MIN and MAX values, based on a common SHAREID. Here's an example that uses XML output for better clarity:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="max" match="MAX" use="SHAREID" />
<xsl:template match="/page">
<root>
<xsl:apply-templates select="TRANSACTIONS/MIN"/>
</root>
</xsl:template>
<xsl:template match="MIN">
<xsl:variable name="id" select="SHAREID" />
<xsl:variable name="min" select="SHARE_RATE" />
<xsl:variable name="max" select="key('max', $id)/SHARE_RATE" />
<share id="{$id}">
<min><xsl:value-of select="$min"/></min>
<max><xsl:value-of select="$max"/></max>
<diff><xsl:value-of select="$max - $min"/></diff>
</share>
</xsl:template>
</xsl:stylesheet>
Applied to your example input, the result is:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<share id="0">
<min>1200</min>
<max>2100</max>
<diff>900</diff>
</share>
<share id="1">
<min>4200</min>
<max>5200</max>
<diff>1000</diff>
</share>
<share id="2">
<min>1600</min>
<max>2000</max>
<diff>400</diff>
</share>
<share id="3">
<min>6100</min>
<max>7000</max>
<diff>900</diff>
</share>
<share id="4">
<min>550</min>
<max>1000</max>
<diff>450</diff>
</share>
<share id="5">
<min>420</min>
<max>2520</max>
<diff>2100</diff>
</share>
<share id="6">
<min>2000</min>
<max>2520</max>
<diff>520</diff>
</share>
</root>
ok, what I understand is, that you want to match MIN/MAX values with the same SHAREID. In this case the following xslt might do what you need:
<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="MAX">
<xsl:variable name="id" select="SHAREID"/>
<!-- or '0' handles the case where no coresponding MIN/SHARE_RATE is found -->
<xsl:variable name="min_value" select="//MIN[SHAREID=$id]/SHARE_RATE or '0'"/>
<xsl:variable name="max_value" select= "SHARE_RATE"/>
<span class="max_rate">
MAX: <xsl:value-of select="$max_value"/>
</span>
<br/>
<span class= "diff">
Diff: (<xsl:value-of select="$max_value - $min_value"/>)
</span>
</xsl:template>
<!-- MAX does it all, so ignore output of MIN -->
<xsl:template match="MIN"/>
<xsl:template match="*">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>

Xpath add a field from an item to another item with a reference id

This is the structure of the file I need to import.
<channel>
<item>
<type>image</type>
<title>title image</title>
<id>1</id>
<image_url>url_to_image</image_url>
</item>
<item>
<type>page</type>
<title>node title</title>
<id>2</id>
<ref>
<entity>image_ref</entity>
<ref_value>1</ref_value>
</ref>
<ref>
<entity>category</entity>
<ref_value>5</ref_value>
</ref>
</item>
</channel>
In the page item the tag contains the id of the image item.
How do I add the image url from the image item to the page item?
I'm trying to use
/channel/item[id=ref/ref_value[../entity/text() = 'image_ref']]/image_url but it does not work...
What's the XPath expression to not import the image item but just the page item?
Thanks in advance
Use:
/*/item[type='image' and id=../item[type='page']
/ref[entity = 'image_ref']/ref_value]
/image_url/text()
XSLT - based verification:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:copy-of select=
"/*/item[type='image' and id=../item[type='page']
/ref[entity = 'image_ref']/ref_value]
/image_url/text()"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied to the provided XML document:
<channel>
<item>
<type>image</type>
<title>title image</title>
<id>1</id>
<image_url>url_to_image</image_url>
</item>
<item>
<type>page</type>
<title>node title</title>
<id>2</id>
<ref>
<entity>image_ref</entity>
<ref_value>1</ref_value>
</ref>
<ref>
<entity>category</entity>
<ref_value>5</ref_value>
</ref>
</item>
</channel>
the XPath expression is evaluated and the result of this evaluation is copied to the output:
url_to_image
Update:
The OP has implied in comments that there may be many "page items" and "image items" and that he needs an expression, getting the image url for only a specific page.
This XPath expression:
/*/item[type='image'
and id=../item[type='page'][1]
/ref[entity = 'image_ref']/ref_value
]
/image_url/text()"/>
produces the wanted image url for the first "page item" in the following XML document:
<channel>
<item>
<type>image</type>
<title>title image</title>
<id>1</id>
<image_url>url_to_image</image_url>
</item>
<item>
<type>image</type>
<title>title image</title>
<id>2</id>
<image_url>url2_to_image</image_url>
</item>
<item>
<type>page</type>
<title>node title</title>
<id>3</id>
<ref>
<entity>image_ref</entity>
<ref_value>1</ref_value>
</ref>
<ref>
<entity>category</entity>
<ref_value>5</ref_value>
</ref>
</item>
<item>
<type>page</type>
<title>node title</title>
<id>4</id>
<ref>
<entity>image_ref</entity>
<ref_value>2</ref_value>
</ref>
<ref>
<entity>category</entity>
<ref_value>5</ref_value>
</ref>
</item>
</channel>
The result produced is:
url_to_image
To get the wanted url for the second page item, we simply modify the above XPath expression to:
/*/item[type='image'
and id=../item[type='page'][2]
/ref[entity = 'image_ref']/ref_value
]
/image_url/text()"/>
and now the result is:
url2_to_image

How to remove most namespaces from input XML in XSLT (nokogiri example)

I have this piece of nokogiri code, thats runs slower then I like on large input. How would you redo this in XSLT? Any other ideas to make it run faster?
# remove namespaces (other then soapenv) from input xml, and move
# them to type attribute.
# xml is Nokogiri::XML object
def cleanup_namespaces(xml)
protected_ns = %w( soapenv )
xml.traverse do |el|
next unless el.respond_to? :namespace
if (ns=el.namespace) &&
!protected_ns.include?(ns.prefix) then
el['type'] = "#{ns.prefix}:#{el.name}"
el.namespace = nil
end
end
xml
end
The sample input I am testing with is:
<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:getAccountDTOResponse xmlns:ns1="http://www.example.com/pw/services/PWServices"
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<getAccountDTOReturn xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:ns2="urn:PWServices"
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xsi:type="ns2:Account">
<ns1:ID soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xsi:type="xsd:long">0</ns1:ID>
<ns1:accountNumber xsi:type="soapenc:string" />
<ns1:accountType xsi:type="soapenc:string" />
<ns1:clientData xsi:type="soapenc:Array" xsi:nil="true" />
<ns1:name xsi:type="soapenc:string" />
<ns1:parentRef xsi:type="soapenc:string" />
</getAccountDTOReturn>
</ns1:getAccountDTOResponse>
</soapenv:Body>
</soapenv:Envelope>
The expected output is:
<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<getAccountDTOResponse xmlns:ns1="http://www.example.com/pw/services/PWServices"
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
type="ns1:getAccountDTOResponse">
<getAccountDTOReturn xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:ns2="urn:PWServices"
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xsi:type="ns2:Account">
<ID soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xsi:type="xsd:long" type="ns1:ID">0</ID>
<accountNumber xsi:type="soapenc:string"
type="ns1:accountNumber" />
<accountType xsi:type="soapenc:string"
type="ns1:accountType" />
<clientData xsi:type="soapenc:Array" xsi:nil="true"
type="ns1:clientData" />
<name xsi:type="soapenc:string" type="ns1:name" />
<parentRef xsi:type="soapenc:string"
type="ns1:parentRef" />
</getAccountDTOReturn>
</getAccountDTOResponse>
</soapenv:Body>
</soapenv:Envelope>
This input is a SOAP response. A tangential question is, what is the
point of the ns1 type namespace in the SOAP response, and is it
reasonable to throw them away completely. I don't seem to need to
reference them when parsing the response.
This XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[namespace-uri() =
'http://www.example.com/pw/services/PWServices']">
<xsl:element name="{local-name()}">
<xsl:attribute name="type">
<xsl:value-of select="name()"/>
</xsl:attribute>
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Against your sample will produce this result:
<?xml version="1.0" encoding="UTF-16"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<getAccountDTOResponse type="ns1:getAccountDTOResponse" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<getAccountDTOReturn soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns2:Account" xmlns:ns1="http://www.example.com/pw/services/PWServices" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns2="urn:PWServices">
<ID type="ns1:ID" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="xsd:long">0</ID>
<accountNumber type="ns1:accountNumber" xsi:type="soapenc:string"></accountNumber>
<accountType type="ns1:accountType" xsi:type="soapenc:string"></accountType>
<clientData type="ns1:clientData" xsi:type="soapenc:Array" xsi:nil="true"></clientData>
<name type="ns1:name" xsi:type="soapenc:string"></name>
<parentRef type="ns1:parentRef" xsi:type="soapenc:string"></parentRef>
</getAccountDTOReturn>
</getAccountDTOResponse>
</soapenv:Body>
</soapenv:Envelope>

Resources