Mule - wrap http parameters with a fixed soap request - http

What transformer should I use to extract parameters from an http message like http://localhost:8088/?id=xxx&type=yyyy ans put the values of the id and the type parameters into a fixed soap request like :
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsd="http://wsdouane/">
<soapenv:Header/>
<soapenv:Body>
<wsd:find>
<entity>
<id>xxxx</id>
<type>yyyy</type>
</entity>
</wsd:find>
</soapenv:Body>
</soapenv:Envelope>
Http output
Cannot bind to address "http://127.0.0.1:8088/" No component registered on that endpoint
File output
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:findResponse xmlns:ns2="http://wsdouane/"/>
</S:Body>
</S:Envelope>
in order to pass to a JAX-WS web service?
thank you

The process of retrieving query params and setting them in an object and then into a SOAP object, is not a single step process with a single transformer.
Step 1: You can use
<http-request-to-parameter-map>
transformer to get the query params as a Map.
Step 2: Then use this map to create a object with the map vlaues.
Step 3: And then send the object to your JAX-WS client component.
More reference of the transformers and the http and servlet module references can be found at below links.
HTTP Transport Reference.
Servlet Transport Reference
Updated answer to use XSLT and proxy client.
<flow name="SOAPWebService" >
<http:inbound-endpoint address="http://localhost:8088/" exchange-pattern="request-response">
</http:inbound-endpoint>
<set-variable value="#[message.inboundProperties['id']]" variableName="paramId"></set-variable>
<set-variable value="#[message.inboundProperties['type']]" variableName="paramType"></set-variable>
<component class="com.example.components.SampleComponent" ></component>
<mule-xml:xslt-transformer
maxIdleTransformers="2" maxActiveTransformers="5"
xsl-file="C:\WorkSpace\MyProject\src\main\resources\xslt\PrepareRequestXML.xsl">
<mule-xml:context-property key="paramId"
value="#[flowVars['paramId']]" />
<mule-xml:context-property key="paramType"
value="#[flowVars['paramType']]" />
</mule-xml:xslt-transformer>
<cxf:proxy-client payload="body"
enableMuleSoapHeaders="true">
</cxf:proxy-client>
<http:outbound-endpoint exchange-pattern="request-response"
address="http://localhost:8080/ClientsDB/douane" doc:name="HTTP">
</http:outbound-endpoint>
<byte-array-to-string-transformer doc:name="Byte Array to String" />
<file:outbound-endpoint ....... .. />
</flow>
Below is the correct XSLT
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns="http://wsdouane/">
<xsl:output method="xml" indent="yes" />
<xsl:param name="paramType"></xsl:param>
<xsl:param name="paramId"></xsl:param>
<xsl:template match="*" >
<xsl:element name="find" namespace="http://wsdouane/">
<xsl:element name="entity">
<xsl:element name="id">
<xsl:value-of select="$paramType"/>
</xsl:element>
<xsl:element name="type">
<xsl:value-of select="$paramId"/>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="text()|processing-instruction()|comment()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The code for SampleComponent class is
package com.example.components;
import org.mule.api.MuleEventContext;
import org.mule.api.lifecycle.Callable;
public class SampleComponent implements Callable {
#Override
public Object onCall(MuleEventContext eventContext) throws Exception {
String str = "<sample> </sample>";
return str;
}
}

Related

Generating a Cross Product from two input schemas using BizTalk Map

I am using a many to one mapping in BizTalk, to generate an output schema with data generated using a cross product logic on a node of input schemas.
Following figure depicts what I've done yet:
The sample input xmls are as follows:
<!-Schema1 Instance-->
<Root>
<Data>
<ItemCode>10</ItemCode>
<ItemCost>1024</ItemCost>
</Data>
<Data>
<ItemCode>20</ItemCode>
<ItemCost>2048</ItemCost>
</Data>
</Root>
<!-Schema2 Instance-->
<Root>
<Data>
<Code>10</Code>
<ShipAddr>addr11101</ShipAddr>
</Data>
<Data>
<Code>30</Code>
<ShipAddr>addr33301</ShipAddr>
</Data>
<Data>
<Code>20</Code>
<ShipAddr>addr22201</ShipAddr>
</Data>
<Data>
<Code>10</Code>
<ShipAddr>addr11102</ShipAddr>
</Data>
</Root>
The required output is based on a cross product performed based on equality of Schema1.ItemCode and Schema2.Code. Sample is as follows:
<!--Output Schema Instance required; Order of records is irrelevant-->
<Root>
<Data>
<Code>10</Code>
<ItemCost>1024</ItemCost>
<ShipAddr>addr11101</ShipAddr>
</Data>
<Data>
<Code>20</Code>
<ItemCost>2048</ItemCost>
<ShipAddr>addr22201</ShipAddr>
</Data>
<Data>
<Code>10</Code>
<ItemCost>1024</ItemCost>
<ShipAddr>addr11102</ShipAddr>
</Data>
</Root>
Actual output:
Output with no looping functoid
XML Output
<ns0:Root xmlns:ns0="http://TestTO_DELETE.SchemaOut">
<Data>
<Code>10</Code><ItemCost>1024</ItemCost><ShipAddr>addr11101</ShipAddr>
</Data>
<Data><Code>20</Code></Data>
</ns0:Root>
Output with both looping functoids connections 1, and 2
XML Output
<ns0:Root xmlns:ns0="http://TestTO_DELETE.SchemaOut">
<Data>
<Code>10</Code>
</Data>
<Data>
<Code>20</Code>
</Data>
<Data />
<Data />
<Data />
<Data />
</ns0:Root>
Output with single looping functoid connection 1
XML Output
<ns0:Root xmlns:ns0="http://TestTO_DELETE.SchemaOut">
<Data>
<Code>10</Code><ItemCost>1024</ItemCost><ShipAddr>addr11101</ShipAddr>
</Data>
<Data>
<Code>20</Code>
</Data>
</ns0:Root>
Please suggest how to proceed in such scenario?
I tried various combinations of functoids to get the required output schema, but nothing worked. So, I finally moved on to use scripting functoid, which served my purpose. I am posting my finding as it could be helpful to someone else.
This is how I proceeded:
Remove all connections and functoids from the map
Add a scripting functoid
Connect InputMesagePart_0 to input of scripting functoid
Connect Scripting functoid to the first element node of the output schema
In the Script Functoid Configuration, add the transformation logic. For e.g., in my case the logic was:
<xsl:template name="Template1">
<xsl:param name="MessagePart_0_Xml" /> <!--Not used anywhere-->
<xsl:variable name="Msg_0_RootNode" select="/*[local-name()='Root']/*[local-name()='InputMessagePart_0']/*[local-name()='Root']" />
<xsl:variable name="Msg_1_RootNode" select="/*[local-name()='Root']/*[local-name()='InputMessagePart_1']/*[local-name()='Root']" />
<xsl:for-each select="$Msg_0_RootNode/Data">
<xsl:variable name="Msg_0_DataNode" select="." />
<xsl:for-each select="$Msg_1_RootNode/Data">
<xsl:variable name="Msg_1_DataNode" select="." />
<xsl:if test="$Msg_0_DataNode/ItemCode/text() = $CostCenterDataNode/Code/text()">
<ItemCost>
<xsl:value-of select="$Msg_0_DataNode/ItemCost/text()" />
</ItemCost>
<ShipAddr>
<xsl:value-of select="$CostCenterDataNode/ShipAddr/text()" />
</ShipAddr>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
If there is a better way to approach this problem, please suggest.

Mule - variable not found

I'm trying to retrieve some variables from a GET http request like
http://localhost:8088/?id=xxx&type=yyyy
using this flow
<flow name="SOAPWebService" >
<http:inbound-endpoint address="http://localhost:8088/" exchange-pattern="request-response">
</http:inbound-endpoint>
<set-variable value="#[message.inboundProperties['id']]" variableName="paramId"></set-variable>
<set-variable value="#[message.inboundProperties['type']]" variableName="paramType"></set-variable>
<component class="com.example.components.SampleComponent" ></component>
<mule-xml:xslt-transformer
maxIdleTransformers="2" maxActiveTransformers="5"
xsl-file="C:\WorkSpace\MyProject\src\main\resources\xslt\PrepareRequestXML.xsl">
<mule-xml:context-property key="paramId"
value="#[flowVars['paramId']]" />
<mule-xml:context-property key="paramType"
value="#[flowVars['paramType']]" />
</mule-xml:xslt-transformer>
<cxf:proxy-client payload="body"
enableMuleSoapHeaders="true">
</cxf:proxy-client>
<http:outbound-endpoint exchange-pattern="request-response"
address="http://localhost:8080/ClientsDB/douane" doc:name="HTTP">
</http:outbound-endpoint>
<byte-array-to-string-transformer doc:name="Byte Array to String" />
<file:outbound-endpoint ....... .. />
</flow>
here is the xslt stylesheet
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsd="http://wsdouane/">
<xsl:output method="xml" indent="yes" />
<xsl:param name="paramType"></xsl:param>
<xsl:param name="paramId"></xsl:param>
<xsl:template match="*" >
<xsl:element name="wsd:find" namespace="http://wsdouane/">
<xsl:element name="entity">
<xsl:element name="id">
<xsl:value-of select="$paramId"/>
</xsl:element>
<xsl:element name="type">
<xsl:value-of select="$paramType"/>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="text()|processing-instruction()|comment()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
but when I run the flow I get the following error:
INFO 2013-05-17 10:10:10,839 [[mediation_mod].connector.http.mule.default.receiver.04] org.mule.transformer.simple.AddFlowVariableTransformer: Variable with key "paramId", not found on message using "#[message.inboundProperties['id']]". Since the value was marked optional, nothing was set on the message for this variable
INFO 2013-05-17 10:10:10,841 [[mediation_mod].connector.http.mule.default.receiver.04] org.mule.transformer.simple.AddFlowVariableTransformer: Variable with key "paramType", not found on message using "#[message.inboundProperties['type']]". Since the value was marked optional, nothing was set on the message for this variable
INFO 2013-05-17 10:10:10,895 [[mediation_mod].connector.http.mule.default.receiver.04] org.mule.transport.service.DefaultTransportServiceDescriptor: Loading default outbound transformer: org.mule.transport.http.transformers.ObjectToHttpClientMethodRequest
INFO 2013-05-17 10:10:10,895 [[mediation_mod].connector.http.mule.default.receiver.04] org.mule.transport.service.DefaultTransportServiceDescriptor: Loading default response transformer: org.mule.transport.http.transformers.MuleMessageToHttpResponse
INFO 2013-05-17 10:10:10,895 [[mediation_mod].connector.http.mule.default.receiver.04] org.mule.transport.service.DefaultTransportServiceDescriptor: Loading default outbound transformer: org.mule.transport.http.transformers.ObjectToHttpClientMethodRequest
INFO 2013-05-17 10:10:10,895 [[mediation_mod].connector.http.mule.default.receiver.04] org.mule.lifecycle.AbstractLifecycleManager: Initialising: 'connector.http.mule.default.dispatcher.28622115'. Object is: HttpClientMessageDispatcher
INFO 2013-05-17 10:10:10,896 [[mediation_mod].connector.http.mule.default.receiver.04] org.mule.lifecycle.AbstractLifecycleManager: Starting: 'connector.http.mule.default.dispatcher.28622115'. Object is: HttpClientMessageDispatcher
INFO 2013-05-17 10:10:10,921 [[mediation_mod].connector.http.mule.default.receiver.04] org.mule.lifecycle.AbstractLifecycleManager: Initialising: 'connector.file.mule.default.dispatcher.27894808'. Object is: FileMessageDispatcher
INFO 2013-05-17 10:10:10,921 [[mediation_mod].connector.http.mule.default.receiver.04] org.mule.lifecycle.AbstractLifecycleManager: Starting: 'connector.file.mule.default.dispatcher.27894808'. Object is: FileMessageDispatcher
INFO 2013-05-17 10:10:10,923 [[mediation_mod].connector.http.mule.default.receiver.04] org.mule.transport.file.FileConnector: Writing file to: C:\MuleStudio\SandBox\output\17-05-13_1368778210922.xml
it gives a null instead of the value passed through the http .
any idea??
Use the following http inbound.
<http:inbound-endpoint address="http://localhost:8088" exchange-pattern="request-response">
</http:inbound-endpoint>
This should work with your request of
http://localhost:8088/?id=xxx&type=yyyy

XSLT 1.0 Group By Tags

I have the following XML data:
<result>
<row>
<CountryId>26</CountryId>
<CountryName>United Kingdom</CountryName>
<NoOfNights>1</NoOfNights>
<AccommodationID>6004</AccommodationID>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RatePlanID>1</RatePlanID>
<RoomRatePlan>Advance</RoomRatePlan>
<NoOfSameTypeRoom>0</NoOfSameTypeRoom>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<Rate>79.00</Rate>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</row>
<row>
<CountryId>26</CountryId>
<CountryName>United Kingdom</CountryName>
<NoOfNights>1</NoOfNights>
<AccommodationID>6004</AccommodationID>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RatePlanID>2</RatePlanID>
<RoomRatePlan>Standard</RoomRatePlan>
<NoOfSameTypeRoom>0</NoOfSameTypeRoom>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>5</NoOfRoomsAvailable>
<Rate>89.00</Rate>
<RatePerDay>27 Mar 2013_89.00</RatePerDay>
</row>
<row>
<CountryId>26</CountryId>
<CountryName>United Kingdom</CountryName>
<NoOfNights>1</NoOfNights>
<AccommodationID>6004</AccommodationID>
<RoomID>2</RoomID>
<RoomName>Double Room</RoomName>
<RatePlanID>1</RatePlanID>
<RoomRatePlan>Advance</RoomRatePlan>
<NoOfSameTypeRoom>0</NoOfSameTypeRoom>
<RoomSize/>
<Max_Person>2</Max_Person>
<RackRate>199</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>5</NoOfRoomsAvailable>
<Rate>89.00</Rate>
<RatePerDay>27 Mar 2013_89.00</RatePerDay>
</row>
</result>
My XSLT for the above xml is this:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes" method="xml" />
<xsl:strip-space elements="*"/>
<!-- Default template : ignore unrecognized elements and text -->
<xsl:template match="*|text()" />
<!-- Match document root : add hotels element and process each children node of result -->
<xsl:template match="/">
<hotels>
<!-- We assume that the XML documents are always going to follow the structure:
result as the root node and xml_acc elements as its children -->
<xsl:for-each select="result/row">
<result>
<hotel_rooms>
<xsl:element name="hotel_id">
<xsl:value-of select="AccommodationID"/>
</xsl:element>
<xsl:apply-templates />
</hotel_rooms>
<xsl:element name="Rate">
<xsl:element name="RoomRatePlan">
<xsl:value-of select="RoomRatePlan"/>
</xsl:element>
<xsl:element name="numeric_price">
<xsl:value-of select="Rate"/>
</xsl:element>
</xsl:element>
</result>
</xsl:for-each>
</hotels>
</xsl:template>
<!-- Elements to be copied as they are -->
<xsl:template match="NoOfNights|RoomName|RoomSize|Max_Person|RackRate|RatePerDay|CurrencySymbol|NoOfRoomsAvailable|RoomDescription|RoomFacilities|PolicyComments|Breakfast|Policy|Message">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="Photo_Max60">
<RoomImages>
<Photo_Max60>
<xsl:value-of select="." />
</Photo_Max60>
<Photo_Max300>
<xsl:value-of select="../Photo_Max300" />
</Photo_Max300>
<Photo_Max500>
<xsl:value-of select="../Photo_Max500" />
</Photo_Max500>
</RoomImages>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0, I want to group by Room ID and Hotel ID . so in the above data, I Want the result like this.
<hotels>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Advance</RoomRatePlan>
<numeric_price>79.00</numeric_price>
<RoomRatePlan>Standard</RoomRatePlan>
<numeric_price>89.00</numeric_price>
</Rate>
</result>
</result>
<hotels>
I want a xslt file for the above xml output i need.please help..
Muenchian method:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" />
<xsl:key name="room-per-hotel" match="result" use="concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID)" />
<xsl:template match="/">
<hotels>
<xsl:for-each select="hotels/result[count(. | key('room-per-hotel', concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID))[1]) = 1]">
<xsl:sort select="concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID)" />
<result>
<xsl:copy-of select="hotel_rooms"/>
<Rate>
<xsl:copy-of select="key('room-per-hotel', concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID))/Rate/*"/>
</Rate>
</result>
</xsl:for-each>
</hotels>
</xsl:template>
</xsl:stylesheet>
Working example
Well neither your input nor your wanted result is well-formed with tags not being closed properly and the existence of an entity reference £ to a not declared entity but if you want to group with XSLT 1.0 then have a look at the following XSLT 1.0 sample using Muenchian grouping:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="group" match="result" use="concat(hotel_rooms/hotel_id, '|', hotel_rooms/RoomID)"/>
<xsl:template match="hotels">
<xsl:copy>
<xsl:apply-templates select="result[generate-id() = generate-id(key('group', concat(hotel_rooms/hotel_id, '|', hotel_rooms/RoomID))[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="result">
<xsl:copy>
<xsl:copy-of select="hotel_rooms"/>
<Rate>
<xsl:copy-of select="key('group', concat(hotel_rooms/hotel_id, '|', hotel_rooms/RoomID))/Rate/*"/>
</Rate>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It transforms
<hotels>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>pound</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Advance</RoomRatePlan>
<numeric_price>79.00</numeric_price>
</Rate>
</result>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>pound</CurrencySymbol>
<NoOfRoomsAvailable>5</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_89.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Standard</RoomRatePlan>
<numeric_price>89.00</numeric_price>
</Rate>
</result>
</hotels>
into
<hotels>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize />
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>pound</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Advance</RoomRatePlan>
<numeric_price>79.00</numeric_price>
<RoomRatePlan>Standard</RoomRatePlan>
<numeric_price>89.00</numeric_price>
</Rate>
</result>
</hotels>

xslt transform list and take max

I want to:
select all attributes "foo", which are string values, and store the values in a list.
transform each value of attribute "foo" in this list using some map in my xslt to a number.
select the max value of the list and output that.
So given the following xml:
<t>
<tag foo="A">apples</tag>
<tag foo="C">oranges</tag>
<tag foo="B">trees</tag>
</t>
And the following mapping:
<xsl:variable name="myMap">
<entry key="A">1</entry>
<entry key="B">2</entry>
<entry key="C">3</entry>
</xsl:variable>
The output would be:
<max>3</max>
Another question, why can't I indent my code? I'm putting spaces but it's not working.
I This standard XSLT 1.0 transformation (most resembling your approach):
<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:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
<entry key="X" value="8"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#foo">
<xsl:attribute name="foo">
<xsl:value-of select="$vMap[#key = current()]/#value"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document (as you didn't provide any):
<t foo="X">
<a foo="A">
<b foo="B"/>
</a>
<c foo="C"/>
</t>
produces the wanted, correct result:
<t foo="8">
<a foo="1">
<b foo="2"/>
</a>
<c foo="3"/>
</t>
Explanation: Appropriate use of the XSLT current() function.
II. XSLT 1.0 solution using keys for speed
<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:key name="kValFromKey" match="entry/#value" use="../#key"/>
<xsl:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
<entry key="X" value="8"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#foo">
<xsl:variable name="vCur" select="."/>
<xsl:attribute name="foo">
<xsl:for-each select="document('')">
<xsl:value-of select="key('kValFromKey', $vCur)"/>
</xsl:for-each>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the same correct result is produced.
Explanation:
Use of <xsl:for-each select="document('')"> to set the current document to the stylesheet, so that the key() function will use the key index built for this document.
Saving the node matched by the template in a variable so that we can use it inside the xsl:for-each -- current() cannot be correctly used here, because it gets the current node on which xsl:for-each operates.
UPDATE: The OP has now clarified in a comment that his biggest problem is finding the maximum.
<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:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="/">
<xsl:variable name="vDoc" select="."/>
<xsl:variable name="vFoosMapped"
select="$vMap[#key = $vDoc/*/*/#foo]"/>
<max>
<xsl:value-of select=
"$vFoosMapped
[not($vFoosMapped/#value > #value)]
/#value
"/>
</max>
</xsl:template>
</xsl:stylesheet>
When given this XML document (the one provided by the OP lacks a singlr top element):
<t>
<tag foo="A">apples</tag>
<tag foo="C">oranges</tag>
<tag foo="B">trees</tag>
</t>
the wanted, correct result is produced:
<max>3</max>
Remark: A more efficient way of calculating maximum (or minimum -- in a similar way) in XSLT 1.0 is to do this:
<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:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="/">
<xsl:variable name="vDoc" select="."/>
<xsl:variable name="vFoosMapped"
select="$vMap[#key = $vDoc/*/*/#foo]"/>
<max>
<xsl:for-each select="$vFoosMapped">
<xsl:sort select="#value" data-type="number" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="#value"/>
</xsl:if>
</xsl:for-each>
</max>
</xsl:template>
</xsl:stylesheet>
Another question, why can't I indent my code? I'm putting spaces but
it's not working.
This is a SO bug that they failed to fix for many months.
Most probably you are using IE. If your version is 9, then do the following:
Press F12.
In the window that pops up click on the right-most menu and select: "Document mode: IE9 Standards"
Now you should be able to see the code with indentation.

How do I pass a xml attribute to xslt parameter?

I got everything working (thank empo) except the ctrlname column. I don't know the syntax well enough. What I am trying to do is use the xslt to sort the xml in the gridview by the column name. Everything is working but the ctrlname column. How do I pass an attribute to the XSLT? I've tried: #name, Data/#name, Data[#name], ctrlname. Nothing works.
XmlDataSource1.EnableCaching = False
Dim xslTrnsform As System.Xml.Xsl.XsltArgumentList = New System.Xml.Xsl.XsltArgumentList
xslTrnsform.AddParam("sortby", "", sortAttr)
xslTrnsform.AddParam("orderas", "", orderby)
XmlDataSource1.TransformArgumentList = xslTrnsform
XmlDataSource1.DataFile = "~/App_LocalResources/DST_Test.xml"
XmlDataSource1.XPath = "//data"
XmlDataSource1.TransformFile = xsltFileName
'XmlDataSource1.DataBind()
GridView1.DataSource = XmlDataSource1
GridView1.DataBind()
XSL
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:param name="sortby"></xsl:param>
<xsl:param name="orderas"></xsl:param>
<xsl:output method="xml" indent="yes"/>
<!--<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>-->
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select="*[name()=$sortby]" data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="data">
<data>
<xsl:attribute name="ctrlname">
<xsl:value-of select="#name"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="value" />
</xsl:attribute>
<xsl:attribute name="comment">
<xsl:value-of select="comment" />
</xsl:attribute>
</data>
</xsl:template>
</xsl:stylesheet>
XML input
<?xml version="1.0" encoding="utf-8" ?>
<root>
<data name="Test1.Text" xml:space="preserve">
<value>Please Pick Bare Pump</value>
<comment>Tab - Pump Configuration</comment>
</data>
<data name="Test2.Text" xml:space="preserve">
<value>Complete</value>
<comment>A07</comment>
</data>
<data name="Test3.Text" xml:space="preserve">
<value>Confirmed</value>
<comment>A01</comment>
</data>
</root>
The currently accepted answer has one flaw: Whenever there is an attribute of data with the same name as a child element of data, the sort will always be performed using as keys the values of the attribute. Also, it is too long.
This solution solves the problem (and is shorter) allowing to specify whether the sort should be by attribute-name or by element-name:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="sortby" select="'attrib!name'"/>
<xsl:param name="orderas" select="'ascending'"/>
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select=
"*[name()=substring-after($sortby, 'elem!')]
|
#*[name()=substring-after($sortby, 'attrib!')]"
data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="data">
<data ctrlname="{#name}" value="{value}"
comment="{comment}"/>
</xsl:template>
</xsl:stylesheet>
When applied on this XML document (based on the provided one, but made a little-bit more interesting):
<root>
<data name="Test3.Text" xml:space="preserve">
<value>Please Pick Bare Pump</value>
<comment>Tab - Pump Configuration</comment>
<name>X</name>
</data>
<data name="Test2.Text" xml:space="preserve">
<value>Complete</value>
<comment>A07</comment>
<name>Z</name>
</data>
<data name="Test1.Text" xml:space="preserve">
<value>Confirmed</value>
<comment>A01</comment>
<name>Y</name>
</data>
</root>
the correct result (sorted by the name attribute) is produced:
<root>
<data ctrlname="Test1.Text" value="Confirmed" comment="A01"/>
<data ctrlname="Test2.Text" value="Complete" comment="A07"/>
<data ctrlname="Test3.Text" value="Please Pick Bare Pump" comment="Tab - Pump Configuration"/>
</root>
Now, replace the <xsl:param name="sortby" select="'attrib!name'"/> with:
<xsl:param name="sortby" select="'elem!name'"/>
and apply the transformation again on the same XML document. This time we get the result correctly sorted by the values of the child-element name:
<root>
<data ctrlname="Test3.Text" value="Please Pick Bare Pump" comment="Tab - Pump Configuration"/>
<data ctrlname="Test1.Text" value="Confirmed" comment="A01"/>
<data ctrlname="Test2.Text" value="Complete" comment="A07"/>
</root>
Explanation:
To distinguish whether we want to sort by an element-child or by an attribute, we use the convention that elem!someName means the sort must be by the values of a child element named someName. Similarly, attrib!someName means the sort must be by the values of an attribute named someName.
The <xsl:sort> insruction is modified accordingly to select as key correctly either an attribute or a child element. No ambiguity is allowed, because the starting substring of the sortby parameter now uniquely identifies whether the key should be an attribute or a child element.
Yes, I'm sorry didnt notice that you wanted also sort by attributes. Note also that you have changed the syntax of xsl:param and it's not correct in that way. It's very important that you keep the single quotes inside the double ones. Here is the final template:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:param name="sortby" select="'value'"/>
<xsl:param name="orderas" select="'ascending'"/>
<xsl:output method="xml" indent="yes"/>
<!--<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>-->
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select="*[name()=$sortby]|#*[name()=$sortby]" data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="data">
<data>
<xsl:attribute name="ctrlname">
<xsl:value-of select="#name"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="value" />
</xsl:attribute>
<xsl:attribute name="comment">
<xsl:value-of select="comment" />
</xsl:attribute>
</data>
</xsl:template>
</xsl:stylesheet>
OK, I think this should work for you, allowing you to specify either attribute or element names in the $sortby parameter:
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select="*[name()=$sortby] | #*[name()=$sortby]" data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
(you would just pass in "name" as the value of the $sortby parameter)
The problem was that the sort value node selection was only matching elements (* matches elements only).

Resources