Combine two queries using WSO2 ESB - xpath

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>

Related

WSO2 XSLT Mediator

Below is the sample from my sequence file
<log level="full">
<property name="upsertResponse" expression="$body" scope="default"/>
</log>
<xslt description="QuerySfdcForNONcsfTransactions" key="conf:xslt/QuerySfdcForNONcsfTransactions.xslt"/>
<log level="full">
<property name="MESSAGE" value="Before SFDC Connection 9010"/>
</log>
<salesforce.retrieve configKey="sfdc_connection">
<fieldList>id,Opportunity_External_ID__c,Contact_External_ID__c</fieldList>
<objectType>Opportunity</objectType>
<objectIDS xmlns:sfdc="sfdc">{//sfdc:sObjects}</objectIDS>
</salesforce.retrieve>
<log level="full">
<property name="MESSAGE" value="After SFDC connection here 9010"/>
</log>
<log level="full">
<property name="SFDCresponse" expression="$body" scope="default"/>
</log>
below is my xslt code
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="http://ws.rb.com/datamodel" xmlns:sfdc="sfdc">
<xsl:template match="/">
<sfdc:sObjects xmlns:sfdc="sfdc" xmlns:ns="http://ws.rb.com/datamodel" type="Opportunity">
<xsl:for-each select="//upsertResponse/result">
<xsl:if test="'true' = success">
<sfdc:Ids>
<xsl:value-of select="id">
</xsl:value-of>
</sfdc:Ids>
</xsl:if>
</xsl:for-each>
</sfdc:sObjects>
</xsl:template>
</xsl:stylesheet>
Below is the output needed
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
<soapenv:Body>
<sfdc:sObjects xmlns:sfdc="sfdc" xmlns:ns="http://ws.rb.com/datamodel">
<sfdc:Ids>a071a000009Ke8gAAC</sfdc:Ids>
<sfdc:Ids>006R000000954zQIAQ</sfdc:Ids>
</sfdc:sObjects>
</soapenv:Body>
</soapenv:Envelope>
But below is the output which i got after transformation which differs from my desired output.
<?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com"><soapenv:Header><LimitInfoHeader><limitInfo><current>21251</current><limit>5000000</limit><type>API REQUESTS</type></limitInfo></LimitInfoHeader></soapenv:Header><soapenv:Body><sfdc:sObjects xmlns:sfdc="sfdc" xmlns:ns="http://ws.rb.com/datamodel" type="Opportunity"></sfdc:sObjects></soapenv:Body></soapenv:Envelope>
After executing the sequence below is the log file
WSAction: , SOAPAction: , MessageID: urn:uuid:9d93d66d-8a60-4ecb-9a97-3fef5cb99b23, Direction: request, upsertResponse = <soapenv:Body xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><upsertResponse xmlns="urn:partner.soap.sforce.com"><result><created>false</created><id>0061a00000BoFD0AAN</id><success>true</success></result></upsertResponse></soapenv:Body>, Envelope: <?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com"><soapenv:Header><LimitInfoHeader><limitInfo><current>21251</current><limit>5000000</limit><type>API REQUESTS</type></limitInfo></LimitInfoHeader></soapenv:Header><soapenv:Body><upsertResponse><result><created>false</created><id>0061a00000BoFD0AAN</id><success>true</success></result></upsertResponse></soapenv:Body></soapenv:Envelope>
WSAction: , SOAPAction: , MessageID: urn:uuid:9d93d66d-8a60-4ecb-9a97-3fef5cb99b23, Direction: request, MESSAGE = Before SFDC Connection 9010, Envelope: <?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com"><soapenv:Header><LimitInfoHeader><limitInfo><current>21251</current><limit>5000000</limit><type>API REQUESTS</type></limitInfo></LimitInfoHeader></soapenv:Header><soapenv:Body><sfdc:sObjects xmlns:sfdc="sfdc" xmlns:ns="http://ws.rb.com/datamodel" type="Opportunity"></sfdc:sObjects></soapenv:Body></soapenv:Envelope>
Please assist me where i am wrong
Thanks

Can you use the xpath3 to copy the contents to the filename?

Im am trying to get the xml tag to be a part of the file name but im not getting any value. Am I doing something wrong? Im getting an error or a null value when I change the transformer
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:file="http://www.mulesoft.org/schema/mule/file" xmlns:mulexml="http://www.mulesoft.org/schema/mule/xml" xmlns:email="http://www.mulesoft.org/schema/mule/email" xmlns:pop3="http://www.mulesoft.org/schema/mule/pop3" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:spring="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/pop3 http://www.mulesoft.org/schema/mule/pop3/current/mule-pop3.xsd
http://www.mulesoft.org/schema/mule/email http://www.mulesoft.org/schema/mule/email/current/mule-email.xsd
http://www.mulesoft.org/schema/mule/file http://www.mulesoft.org/schema/mule/file/current/mule-file.xsd
http://www.mulesoft.org/schema/mule/xml http://www.mulesoft.org/schema/mule/xml/current/mule-xml.xsd">
<flow name="email_notificationFlow">
<file:inbound-endpoint path="C:\Users\pd00h\Desktop\Knauf\testxML" responseTimeout="10000" doc:name="File"/>
<file:file-to-byte-array-transformer doc:name="File to Byte Array"/>
<set-variable variableName="order" value="#[xpath3('//ns0:Payload/WhsDockets/WhsDocket/Reference')]" doc:name="Variable"/>
<logger message="#[flowVars.order]" level="INFO" doc:name="Logger"/>
<file:outbound-endpoint path="C:\Users\pd00h\Desktop\Knauf" outputPattern="Knauf-#[message.id].xml" responseTimeout="10000" doc:name="File"/>
</flow>
</mule>
Sample Input File
<?xml version="1.0" encoding="utf-8"?><XmlInterchange xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1" xmlns="http://www.edi.com.au/EnterpriseService/">
<InterchangeInfo>
<Date>2016-02-19T09:31:34.969+09:00</Date>
<XmlType>LightWeight</XmlType>
<Source>
<EnterpriseCode>AWH</EnterpriseCode>
<CompanyCode>ADL</CompanyCode>
<OriginServer>ADL</OriginServer>
</Source>
<Target />
<EDIOrganisation EDICode="AWHLOG_AU" OwnerCode="AWHLOG_AU">
<OrganisationDetails>
<Name>AWH LOGISTICS</Name>
<Location Country="Australia" City="Adelaide">AUADL</Location>
<Addresses>
<Address AddressType="MAIN">
<AddressLine1>Gillman Wool Complex</AddressLine1>
<AddressLine2>GRAND TRUNKWAY,</AddressLine2>
<AddressCode>Gillman Wool Complex</AddressCode>
<CityOrSuburb>GILLMAN</CityOrSuburb>
<StateOrProvince>SA</StateOrProvince>
<PostCode>5013</PostCode>
<TelephoneNumbers>
<TelephoneNumber NumberType="Business">+618 8240 8400</TelephoneNumber>
<TelephoneNumber NumberType="Fax">+618 8240 0080</TelephoneNumber>
</TelephoneNumbers>
<Email>containers#awh.com.au</Email>
<Language>ENG</Language>
<Location>AUADL</Location>
<Sequence>1</Sequence>
<AddressCapabilities>
<AddressCapability AddressType="MAIN" />
<AddressCapability IsMainAddress="true" AddressType="OFC" />
<AddressCapability IsMainAddress="true" AddressType="PAD" />
</AddressCapabilities>
</Address>
</Addresses>
</OrganisationDetails>
</EDIOrganisation>
</InterchangeInfo>
<Payload>
<WhsDockets>
<WhsDocket>
<Identifier>
<Client EDICode="KNAINSBNE" OwnerCode="KNAINSBNE">
<OrganisationDetails>
<Name>Knauf Insulation Pty Ltd</Name>
<Location Country="Australia" City="Brisbane">AUBNE</Location>
<Addresses>
<Address AddressType="MAIN">
<AddressLine1>2/44 BorthwickAve</AddressLine1>
<AddressCode>2/44 BorthwickAve</AddressCode>
<CityOrSuburb>MURARRIE</CityOrSuburb>
<StateOrProvince>QLD</StateOrProvince>
<PostCode>4172</PostCode>
<TelephoneNumbers>
<TelephoneNumber NumberType="Business">+61 (7) 3393-7300</TelephoneNumber>
<TelephoneNumber NumberType="Mobile">+61 (438) 488-000</TelephoneNumber>
<TelephoneNumber NumberType="Fax">+61 (7) 3343-1898</TelephoneNumber>
</TelephoneNumbers>
<Email>orders.au#knaufinsulation.com</Email>
<Language>ENG</Language>
<Location>AUBNE</Location>
<Sequence>1</Sequence>
<AddressCapabilities>
<AddressCapability AddressType="MAIN" />
<AddressCapability IsMainAddress="true" AddressType="OFC" />
</AddressCapabilities>
</Address>
</Addresses>
</OrganisationDetails>
</Client>
<Reference>2363867</Reference>
<DocketType>WOH</DocketType>
<ActionType>CON</ActionType>
</Identifier>
<DocketDetail>
<WarehouseCode>ROC</WarehouseCode>
<CustomerReference>3330826</CustomerReference>
<Units>80</Units>
<Packages>0</Packages>
<Pallets>0</Pallets>
<Weight DimensionType="KG">673</Weight>
<Cubic DimensionType="M3">7.624</Cubic>
<TransportInsurance>0.0000</TransportInsurance>
<ShipperCODAmount>0.0000</ShipperCODAmount>
<CustomerOrderDetail>
<OrderType>ORD</OrderType>
<DateRequired>2015-08-25T00:00:00</DateRequired>
<Consignee AddressType="CEA">
<AddressLine1>71-83 Kenny Street</AddressLine1>
<CityOrSuburb>PORTSMITH</CityOrSuburb>
<StateOrProvince>QLD</StateOrProvince>
<PostCode>4870</PostCode>
<CompanyName>Bunnings Cairns Central Warehouse</CompanyName>
<CountryCode>AU</CountryCode>
<ContactName>The Import Manager</ContactName>
</Consignee>
</CustomerOrderDetail>
<CustomAttributes />
</DocketDetail>
<DocketLines>
<DocketLine>
<Product>E2271</Product>
<Description>R 3 0 145mm x 580mm x 1160mm</Description>
<QuantityFromClientOrder>4</QuantityFromClientOrder>
<QuantityActuallyOrdered>4</QuantityActuallyOrdered>
<ProductUQ>MST</ProductUQ>
<LineAttributes />
<LineNumber>1</LineNumber>
<Confirmation>
<Lines>
<Line>
<Quantity>16</Quantity>
<QuantityUQ>PAC</QuantityUQ>
</Line>
</Lines>
<Quantity>16</Quantity>
</Confirmation>
</DocketLine>
<DocketLine>
<Product>E4386</Product>
<Description>R 2 0 90mm x 580mm x 19000mm</Description>
<QuantityFromClientOrder>4</QuantityFromClientOrder>
<QuantityActuallyOrdered>4</QuantityActuallyOrdered>
<ProductUQ>MST</ProductUQ>
<LineAttributes />
<LineNumber>2</LineNumber>
<Confirmation>
<Lines>
<Line>
<Quantity>32</Quantity>
<QuantityUQ>PAC</QuantityUQ>
</Line>
</Lines>
<Quantity>32</Quantity>
</Confirmation>
</DocketLine>
<DocketLine>
<Product>450521</Product>
<Description>XPS300 30 x 600 x 1200 B1 / On / SE</Description>
<QuantityFromClientOrder>20</QuantityFromClientOrder>
<QuantityActuallyOrdered>20</QuantityActuallyOrdered>
<ProductUQ>PC</ProductUQ>
<LineAttributes />
<LineNumber>3</LineNumber>
<Confirmation>
<Lines>
<Line>
<Quantity>20</Quantity>
<QuantityUQ>PC</QuantityUQ>
</Line>
</Lines>
<Quantity>20</Quantity>
</Confirmation>
</DocketLine>
<DocketLine>
<Product>450523</Product>
<Description>XPS300 50 x 600 x 1200 B1 / On / SE</Description>
<QuantityFromClientOrder>12</QuantityFromClientOrder>
<QuantityActuallyOrdered>12</QuantityActuallyOrdered>
<ProductUQ>PC</ProductUQ>
<LineAttributes />
<LineNumber>4</LineNumber>
<Confirmation>
<Lines>
<Line>
<Quantity>12</Quantity>
<QuantityUQ>PC</QuantityUQ>
</Line>
</Lines>
<Quantity>12</Quantity>
</Confirmation>
</DocketLine>
</DocketLines>
</WhsDocket>
</WhsDockets>
</Payload>
Can you use this to complex XML structures?
Add namespace-manager
replace file:file-to-byte-array-transformer
with byte-array-to-string-transformer
fix xpath usage
updated code:
<mule xmlns:file="http://www.mulesoft.org/schema/mule/file"
xmlns:mulexml="http://www.mulesoft.org/schema/mule/xml" xmlns:email="http://www.mulesoft.org/schema/mule/email"
xmlns:pop3="http://www.mulesoft.org/schema/mule/pop3" xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:spring="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/pop3 http://www.mulesoft.org/schema/mule/pop3/current/mule-pop3.xsd
http://www.mulesoft.org/schema/mule/email http://www.mulesoft.org/schema/mule/email/current/mule-email.xsd
http://www.mulesoft.org/schema/mule/file http://www.mulesoft.org/schema/mule/file/current/mule-file.xsd
http://www.mulesoft.org/schema/mule/xml http://www.mulesoft.org/schema/mule/xml/current/mule-xml.xsd">
<mulexml:namespace-manager
includeConfigNamespaces="true">
<mulexml:namespace prefix="ns0"
uri="http://www.example.com/something.xsd" />
</mulexml:namespace-manager>
<flow name="email_notificationFlow">
<file:inbound-endpoint path="C:\Users\pd00h\Desktop\Knauf\testxML"
responseTimeout="10000" doc:name="File" />
<byte-array-to-string-transformer
doc:name="Byte Array to String" />
<set-variable variableName="order"
value="#[xpath3('//ns0:WhsDockets/ns0:WhsDocket/ns0:Reference')]"
doc:name="Variable" />
<logger message="#[flowVars.order]" level="INFO" doc:name="Logger" />
<file:outbound-endpoint path="C:\Users\pd00h\Desktop\Knauf"
outputPattern="Knauf-#[message.id].xml" responseTimeout="10000"
doc:name="File" />
</flow>
</mule>
Sample xml file:
<?xml version='1.0' encoding='UTF-8'?>
<ns0:WhsDockets xmlns:ns0="http://www.example.com/something.xsd">
<ns0:WhsDocket>
<ns0:Reference>GotIt!</ns0:Reference>
</ns0:WhsDocket>
</ns0:WhsDockets>
Cheers

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

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>

Magento soap error

I'm developing an iOS app that should integrate Magento e-commerce.
Actually I'm trying to put objects into the cart, when I try to do that my app sends to server this WSDL:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="urn:Magento" xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<call>
<sessionId xsi:type="xsd:string">e976c086bf862d23fcc98e59fb23b499</sessionId>
<resourcePath xsi:type="xsd:string">cart_product.add</resourcePath>
<args xsi:type="SOAP-ENC:Array">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">quoteId</key>
<value xsi:type="xsd:int">85</value>
</item>
<item>
<key xsi:type="xsd:string">products</key>
<value xsi:type="SOAP-ENC:Array">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">product_id</key>
<value xsi:type="xsd:string">1</value>
</item>
<item>
<key xsi:type="xsd:string">qty</key>
<value xsi:type="xsd:int">1</value>
</item>
</item>
</value>
</item>
</item>
</args>
</call>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I tried it with this extension for Google Chrome, when I try to run the WSDL
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>1</faultcode>
<faultstring>SQLSTATE[21000]: Cardinality violation: 1241 Operand should contain 1 column(s)</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
What's wrong in my request? Can anyone help me?
Solved now I'm sending this:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="urn:Magento" xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<call>
<sessionId xsi:type="xsd:string">38fd5af2c7fcfd55884b73ffded29225</sessionId>
<resourcePath xsi:type="xsd:string">cart_product.add</resourcePath>
<args xsi:type="SOAP-ENC:Array">
<item xsi:type="xsd:int">142</item>
<item xsi:type="SOAP-ENC:Array">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">product_id</key>
<value xsi:type="xsd:string">2</value>
</item>
<item>
<key xsi:type="xsd:string">qty</key>
<value xsi:type="xsd:int">1</value>
</item>
</item>
</item>
</args>
</call>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I was formatting wrong the request, indeed, if you compare the 2 request you will see the difference: I was sending the key "quoteId", but it's not necessary.

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