BizTalk Map Name Value Pair to Hierarchical Schema - xslt

I'm going to be working the MS AX2010. When accessing data through an AX WCF service, the response is XML containing name / value pairs - known as a key data list. I'll be collecting this XML in BizTalk and needing to transform it to a canonical hierarchical schema. So for example, if I read a source Name element with "OrderNumber", then I would map the associated Value to an OrderNumber element in the destination schema.
Has anyone discovered a nice way to do this using a BizTalk map?

I acknowledge that you prefer to use the graphical functoids, but if you can accept an xslt route, it is pretty straightforward (See here for converting a visual map to an xslt). eg. the following XSLT
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="inputxmlns"
xmlns:ns1="outputxmlns"
exclude-result-prefixes="ns0"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/ns0:Root">
<ns1:Root>
<ns1:Elements>
<xsl:for-each select="ns0:Elements/ns0:Element">
<xsl:element name="ns1:{normalize-space(*[local-name()='Name']/text())}">
<xsl:value-of select="ns0:Value/text()"/>
</xsl:element>
</xsl:for-each>
</ns1:Elements>
</ns1:Root>
</xsl:template>
</xsl:stylesheet>
Will transform a quasi EAV schema:
<?xml version="1.0" encoding="utf-8"?>
<Root xmlns="inputxmlns">
<Elements>
<Element>
<Name>
NameOfElement1
</Name>
<Value>
ValueOfElement1
</Value>
</Element>
<Element>
<Name>
NameOfElement2
</Name>
<Value>
ValueOfElement2
</Value>
</Element>
</Elements>
</Root>
To this:
<?xml version="1.0" encoding="utf-8"?>
<ns1:Root xmlns:ns1="outputxmlns">
<ns1:Elements>
<ns1:NameOfElement1>
ValueOfElement1
</ns1:NameOfElement1>
<ns1:NameOfElement2>
ValueOfElement2
</ns1:NameOfElement2>
</ns1:Elements>
</ns1:Root>

Related

How to use Key in XSLT Oxygen

I am trying to get the output based on two separate nodes in XML using key concept of XSLT
I have below XML
<?xml version="1.0" encoding="UTF-8"?>
<mdti:Input xmlns:mdti="urn:com.workday/multiDocumentTransform/Input">
<mdti:Files xmlns:mdti="urn:com.workday/multiDocumentTransform/Input">
<mdti:EventFiles>
<mdti:File mdti:filename="first.xml" mdti:contentType="text/xml">
<wd:Report_Data xmlns:wd="urn:com.workday/bsvc">
<wd:Report_Entry>
<wd:key>1234</wd:key>
<wd:comp>ABC</wd:comp>
<wd:asof>2021-03-24T04:59:32.179-07:00</wd:asof>
<wd:emplid>33333333</wd:emplid>
<wd:worker_type>EMP</wd:worker_type>
<wd:emp_type>Regular</wd:emp_type>
<wd:orig_hire_dt>2021-11-27</wd:orig_hire_dt>
<wd:rehire_dt>2019-04-01</wd:rehire_dt>
<wd:home_host_class>M</wd:home_host_class>
<wd:service_dt>2014-11-27</wd:service_dt>
</wd:Report_Entry>
</wd:Report_Data>
</mdti:File>
<mdti:File mdti:filename="second.xml" mdti:contentType="text/xml">
<wd:Report_Data xmlns:wd="urn:com.workday/bsvc">
<wd:Report_Entry>
<wd:key>1234</wd:key>
<wd:supervisor_lname>xyz</wd:supervisor_lname>
<wd:hr_status>A</wd:hr_status>
<wd:hr_status_descr>Active</wd:hr_status_descr>
<wd:empl_status>A</wd:empl_status>
<wd:empl_status_descr>Active</wd:empl_status_descr>
<wd:ben_status>A</wd:ben_status>
<wd:home_address_change_dt>2019-07-30</wd:home_address_change_dt>
<wd:location>444</wd:location>
<wd:location_descr>Ind</wd:location_descr>
</wd:Report_Entry>
</wd:Report_Data>
</mdti:File>
</mdti:EventFiles>
</mdti:Files>
</mdti:Input>
**I am using below XSLT for my data. Please let me know if I am missing something and way to do it. The only identifier in each node is the mdti:filename **
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:mdti="urn:com.workday/multiDocumentTransform/Input"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:wd="urn:com.workday/bsvc"
exclude-result-prefixes="#all">
<xsl:key name="share" match="mdti:Input/mdti:Files/mdti:EventFiles/mdti:File/wd:Report_Data/wd:Report_Entry" use="mdti:Input/mdti:Files/mdti:EventFiles/mdti:File/wd:Report_Data/wd:Report_Entry/wd:key"/>
<xsl:template match="/">
<data>
<key>1234</key>
<xsl:copy-of select="key('share', wd:key)/wd:hr_status"/>
<emp_type>Regular</emp_type>
<supervisor_lname>xyz</supervisor_lname>
<hr_status>A</hr_status>
<location>444</location>
</data>
</xsl:template>
</xsl:stylesheet>
You have several mistakes.
First, <xsl:template match="/"> puts you in the context of the root node; from this context, the expression wd:key selects nothing - so your instruction <xsl:copy-of select="key('share', wd:key)/wd:hr_status"/> does nothing.
Next, if you want your key to match nodes in the second file, you should restrict it to match only nodes in the second file. Also, the use attribute must be relative to the matched node.
Furthermore, if you want the result to be in no-namespace, you cannot copy nodes from the input - at least not in XSLT 1.0.
There is more, but these are the ones that stand out immediately.
Consider the following example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:mdti="urn:com.workday/multiDocumentTransform/Input"
xmlns:wd="urn:com.workday/bsvc"
exclude-result-prefixes="mdti wd">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="entry2" match="mdti:File[#mdti:filename='second.xml']/wd:Report_Data/wd:Report_Entry" use="wd:key"/>
<xsl:template match="/mdti:Input">
<root>
<xsl:for-each select="mdti:Files/mdti:EventFiles/mdti:File[#mdti:filename='first.xml']/wd:Report_Data/wd:Report_Entry">
<data>
<!-- data from file1 -->
<key>
<xsl:value-of select="wd:key"/>
</key>
<emplid>
<xsl:value-of select="wd:emplid"/>
</emplid>
<!-- data from file2 -->
<xsl:variable name="entry2" select="key('entry2', wd:key)" />
<supervisor_lname>
<xsl:value-of select="$entry2/wd:supervisor_lname"/>
</supervisor_lname>
<hr_status>
<xsl:value-of select="$entry2/wd:hr_status"/>
</hr_status>
</data>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, this will produce:
Result
<?xml version="1.0" encoding="utf-8"?>
<root>
<data>
<key>1234</key>
<emplid>33333333</emplid>
<supervisor_lname>xyz</supervisor_lname>
<hr_status>A</hr_status>
</data>
</root>
You can add fields from both branches as required.

Merge data from two node sets using XSLT [duplicate]

I am trying to get the output based on two separate nodes in XML using key concept of XSLT
I have below XML
<?xml version="1.0" encoding="UTF-8"?>
<mdti:Input xmlns:mdti="urn:com.workday/multiDocumentTransform/Input">
<mdti:Files xmlns:mdti="urn:com.workday/multiDocumentTransform/Input">
<mdti:EventFiles>
<mdti:File mdti:filename="first.xml" mdti:contentType="text/xml">
<wd:Report_Data xmlns:wd="urn:com.workday/bsvc">
<wd:Report_Entry>
<wd:key>1234</wd:key>
<wd:comp>ABC</wd:comp>
<wd:asof>2021-03-24T04:59:32.179-07:00</wd:asof>
<wd:emplid>33333333</wd:emplid>
<wd:worker_type>EMP</wd:worker_type>
<wd:emp_type>Regular</wd:emp_type>
<wd:orig_hire_dt>2021-11-27</wd:orig_hire_dt>
<wd:rehire_dt>2019-04-01</wd:rehire_dt>
<wd:home_host_class>M</wd:home_host_class>
<wd:service_dt>2014-11-27</wd:service_dt>
</wd:Report_Entry>
</wd:Report_Data>
</mdti:File>
<mdti:File mdti:filename="second.xml" mdti:contentType="text/xml">
<wd:Report_Data xmlns:wd="urn:com.workday/bsvc">
<wd:Report_Entry>
<wd:key>1234</wd:key>
<wd:supervisor_lname>xyz</wd:supervisor_lname>
<wd:hr_status>A</wd:hr_status>
<wd:hr_status_descr>Active</wd:hr_status_descr>
<wd:empl_status>A</wd:empl_status>
<wd:empl_status_descr>Active</wd:empl_status_descr>
<wd:ben_status>A</wd:ben_status>
<wd:home_address_change_dt>2019-07-30</wd:home_address_change_dt>
<wd:location>444</wd:location>
<wd:location_descr>Ind</wd:location_descr>
</wd:Report_Entry>
</wd:Report_Data>
</mdti:File>
</mdti:EventFiles>
</mdti:Files>
</mdti:Input>
**I am using below XSLT for my data. Please let me know if I am missing something and way to do it. The only identifier in each node is the mdti:filename **
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:mdti="urn:com.workday/multiDocumentTransform/Input"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:wd="urn:com.workday/bsvc"
exclude-result-prefixes="#all">
<xsl:key name="share" match="mdti:Input/mdti:Files/mdti:EventFiles/mdti:File/wd:Report_Data/wd:Report_Entry" use="mdti:Input/mdti:Files/mdti:EventFiles/mdti:File/wd:Report_Data/wd:Report_Entry/wd:key"/>
<xsl:template match="/">
<data>
<key>1234</key>
<xsl:copy-of select="key('share', wd:key)/wd:hr_status"/>
<emp_type>Regular</emp_type>
<supervisor_lname>xyz</supervisor_lname>
<hr_status>A</hr_status>
<location>444</location>
</data>
</xsl:template>
</xsl:stylesheet>
You have several mistakes.
First, <xsl:template match="/"> puts you in the context of the root node; from this context, the expression wd:key selects nothing - so your instruction <xsl:copy-of select="key('share', wd:key)/wd:hr_status"/> does nothing.
Next, if you want your key to match nodes in the second file, you should restrict it to match only nodes in the second file. Also, the use attribute must be relative to the matched node.
Furthermore, if you want the result to be in no-namespace, you cannot copy nodes from the input - at least not in XSLT 1.0.
There is more, but these are the ones that stand out immediately.
Consider the following example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:mdti="urn:com.workday/multiDocumentTransform/Input"
xmlns:wd="urn:com.workday/bsvc"
exclude-result-prefixes="mdti wd">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="entry2" match="mdti:File[#mdti:filename='second.xml']/wd:Report_Data/wd:Report_Entry" use="wd:key"/>
<xsl:template match="/mdti:Input">
<root>
<xsl:for-each select="mdti:Files/mdti:EventFiles/mdti:File[#mdti:filename='first.xml']/wd:Report_Data/wd:Report_Entry">
<data>
<!-- data from file1 -->
<key>
<xsl:value-of select="wd:key"/>
</key>
<emplid>
<xsl:value-of select="wd:emplid"/>
</emplid>
<!-- data from file2 -->
<xsl:variable name="entry2" select="key('entry2', wd:key)" />
<supervisor_lname>
<xsl:value-of select="$entry2/wd:supervisor_lname"/>
</supervisor_lname>
<hr_status>
<xsl:value-of select="$entry2/wd:hr_status"/>
</hr_status>
</data>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, this will produce:
Result
<?xml version="1.0" encoding="utf-8"?>
<root>
<data>
<key>1234</key>
<emplid>33333333</emplid>
<supervisor_lname>xyz</supervisor_lname>
<hr_status>A</hr_status>
</data>
</root>
You can add fields from both branches as required.

How to add namespace and xsi to the Incoming XML with no namespace

I have requirement where I have to add Namespace and xsi to the
element from the source xml with No Namespace.
In Source XML I am just getting the Nodes and there is No namespace
and another program needs BizTalk to add Namespace and XSI to the XML for its processing.
I tried:
Used add namespace pipeline component. (It just added
namespace and not the xsi bits)
Used Map for putting up the desired format and yes no luck as got
just the namespace.
Need your help around this.
My source XML is like
<?xml version="1.0" encoding="UTF-16"?>
<Document>
<CstmrPmtStsRpt>
<GrpHdr>
<MsgId></MsgId>
<CreDtTm></CreDtTm>
<InitgPty>
<Id>
<OrgId>
<BICOrBEI></BICOrBEI>
</OrgId>
</Id>
</InitgPty>
</GrpHdr>
<OrgnlGrpInfAndSts>
<OrgnlMsgId></OrgnlMsgId>
<OrgnlMsgNmId></OrgnlMsgNmId>
<OrgnlNbOfTxs></OrgnlNbOfTxs>
<OrgnlCtrlSum></OrgnlCtrlSum>
<GrpSts>ACCP</GrpSts>
</OrgnlGrpInfAndSts>
</CstmrPmtStsRpt>
</Document>
My Required format is as below:
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="MyNamespace">
<CstmrPmtStsRpt>
<GrpHdr>
<MsgId></MsgId>
<CreDtTm></CreDtTm>
<InitgPty>
<Id>
<OrgId>
<BICOrBEI></BICOrBEI>
</OrgId>
</Id>
</InitgPty>
</GrpHdr>
<OrgnlGrpInfAndSts>
<OrgnlMsgId></OrgnlMsgId>
<OrgnlMsgNmId></OrgnlMsgNmId>
<OrgnlNbOfTxs></OrgnlNbOfTxs>
<OrgnlCtrlSum></OrgnlCtrlSum>
<GrpSts>ACCP</GrpSts>
</OrgnlGrpInfAndSts>
</CstmrPmtStsRpt>
</Document>
Use the namespace attribute of xsl:element like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*">
<xsl:element name="{local-name()}" namespace="MyNamespace">
<xsl:namespace name="xsi" select="'http://www.w3.org/2001/XMLSchema-instance'"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Edit: Since you need to work with XSLT-1.0. Use following stylesheet:
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/Document">
<Document xmlns="MyNamespace"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:apply-templates/>
</Document>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}" namespace="MyNamespace">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Note, that you need to know your rootnode's name for this (in this case Document).
BizTalk Answer:
First, it's a good thing the incoming document has no namespace. Xml Namespaces are far, far, far more trouble than they're worth and should be avoided/removed whenever possible.
Second the output format is not valid Xml. "MyNamespace" is not a valid URI and can't be used for a Namespace. If this is what they are asking for, they need to fix that first.
But, if you must, your process should not be "add a namespace". What you're really doing is Transforming from SysA's Document to SysB's Document. For that, use a Map. You will use to practially identical Schemas, one with and one without the Target Namespace.
The Mapper will handle xsi for you as well, if it's needed.

Combining input XML to produce another XML output

Trying to build a POC that does the following:
Given a short input XML, take the values from it and insert them into a larger XML file of a known format.
So if this was my input XML:
<root>
<transaction ID="TX123" source-system="xyz" timestamp="2015-10-15T14:20:35.954Z" dest-system="abc" status="success" applicationID="some_app" originator="MQ">
</transaction>
</root>
And i'd have to take these values and insert them into this: MQ FTE Transfer Log message format
I'd have to insert the values in the transferSet node (timestamp) and metaDataSet.
What would the XSLT have to look like in order to get this done?
Thanks a bunch in advance!
Slava.
Your XLS should look like this:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<transaction>
<!-- more elements... -->
<xsl:apply-templates select="transaction"/>
</transaction>
</xsl:template>
<xsl:template match="transaction">
<transferSet>
<xsl:attribute name="startTime"><xsl:value-of select="#timestamp" /></xsl:attribute>
<metaDataSet>
<metaData key="com.ibm.wmqfte.SourceAgent"><xsl:value-of select="#source-system"/></metaData>
<!-- more elements... -->
</metaDataSet>
</transferSet>
</xsl:template>
With your source input, it should generate an XML like this:
<?xml version="1.0" encoding="UTF-8"?>
<transaction>
<transferSet startTime="2015-10-15T14:20:35.954Z">
<metaDataSet>
<metaData key="com.ibm.wmqfte.SourceAgent">xyz</metaData>
</metaDataSet>
</transferSet>
</transaction>

Two phase XSLT transformation converting string to XML first

I have the following XML:
<?xml version="1.0" encoding="utf-8"?>
<string>
<Table>
<Rows>
<Row Id="0">
<Column Name="INS_NAME" XPath="Ins.Name">Jane</Column>
<Column Name="INS_LASTNAME" XPath="Ins.LastName">Smith</Column>
</Row>
<Row Id="1">
<Column Name="INS_NAME" XPath="Ins.Name">Joe</Column>
<Column Name="INS_LASTNAME" XPath="Ins.LastName">Miller</Column>
</Row>
<Row Id="2">
<Column Name="INS_NAME" XPath="Ins.Name">George</Column>
<Column Name="INS_LASTNAME" XPath="Ins.LastName">Ramsey</Column>
</Row>
</Rows>
</Table>
</string>
and I would like to transform it to this XML using a single XSLT:
<?xml version="1.0" encoding="utf-8"?>
<Customers>
<Customer><Name>Jane</Name><LastName>Smith</LastName></Customer>
<Customer><Name>Joe</Name><LastName>Miller</LastName></Customer>
<Customer><Name>George</Name><LastName>Ramsey</LastName></Customer>
</Customers>
I can do it with two different XSLT's:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="/" disable-output-escaping="yes" />
</xsl:template>
</xsl:stylesheet>
and then:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Customers>
<xsl:for-each select="Table/Rows/Row">
<Customer>
<Name><xsl:value-of select="Column[#Name='INS_NAME']" /></Name>
<LastName><xsl:value-of select="Column[#Name='INS_LASTNAME']" /></LastName>
</Customer>
</xsl:for-each>
</Customers>
</xsl:template>
</xsl:stylesheet>
I have been reading about multi phase transformations but I can't seem to get it. I have tried saving the first XSLT in a variable but it seems disable-output-escaping="yes" does not work when saving to a variable.
Can anybody help?
Thank you.
New information (Edit)
I am now translating the string this way:
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="stringXml">
<?xml version="1.0" encoding="utf-8"?>
<xsl:value-of select="translate(translate(/,'>','>'),'<','<')" />
</xsl:variable>
...
How can I do a transformation on the resulting XML stored in stringXML?
Final Solution (Edit)
<msxml:script implements-prefix="myLib" language="C#">
<msxml:assembly name="System.Web"/>
<msxml:using namespace="System.Web"/>
<![CDATA[
public System.Xml.XPath.XPathNodeIterator convertText(string text)
{
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(text);
return doc.CreateNavigator().Select("/");
}
]]>
</msxml:script>
it seems disable-output-escaping="yes" does not work when saving to a
variable.
Your observation is correct.
DOE only affects the serialization of the (final) result of the transformation and isn't applied on intermediary trees.
Here is what the W3C XSLT 1.0 specification explicitly says:
"An XSLT processor will only be able to disable output escaping if it
controls how the result tree is output. This may not always be the
case. For example, the result tree may be used as the source tree for
another XSLT transformation instead of being output."
The same negative answer holds for trying to use a variable, whose value is a string, containing a textual representation of an XML document.
I had a similar situation where I needed to parse an escaped XML inside my actual XML. I will post up my solution to also help someone else. Please also note that I am also using Saxon-PE parser.
In my situation I have the original XML that contains an escaped XML in a child node. I needed to get the inner XML inside the RootNode of the escaped XML.
Source XML:
<?xml version="1.0" encoding="utf-8"?>
<MyTestXml>
<SomeXmlStuff>
<Text1>Hello</Text1>
<Text2>World</Text2>
</SomeXmlStuff>
<SomeEscapedXml><RootNode><FirstNode>Hello</FirstNode><SecondNode>World</SecondNode><ThirdNode>Again</ThirdNode></RootNode></SomeEscapedXml>
</MyTestXml>
When you unescaped the XML, it looks like this:
<RootNode>
<FirstNode>Hello</FirstNode>
<SecondNode>World</SecondNode>
<ThirdNode>Again</ThirdNode>
</RootNode>
With the following XSLT transformation is applied on the source XML:
<?xml version='1.0' encoding='utf-8' ?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:saxon="http://saxon.sf.net/"
exclude-result-prefixes="xsl saxon">
<xsl:template match="/">
<MyOutput>
<xsl:call-template name="GetRootNodeInnerXml">
<xsl:with-param name="escapedXml" select="MyTestXml/SomeEscapedXml" />
</xsl:call-template>
</MyOutput>
</xsl:template>
<xsl:template name="GetRootNodeInnerXml">
<xsl:param name="escapedXml" required="yes" />
<xsl:copy-of select="saxon:parse($escapedXml)/RootNode/node()"/>
<!-- You can also use this line below if you're not using saxon parser. Just make sure your parser supports XSL 3.0 -->
<!--
<xsl:copy-of select="fn:parse-xml($escapedXml)/RootNode/node()" xmlns:fn="http://www.w3.org/2005/xpath-functions"/>
-->
</xsl:template>
</xsl:stylesheet>
This gives you the following output:
<?xml version='1.0' ?>
<MyOutput>
<FirstNode>Hello</FirstNode>
<SecondNode>World</SecondNode>
<ThirdNode>Again</ThirdNode>
</MyOutput>