In Biztalk, how do I split an envelope with an extra element? - biztalk

I'm trying to split an incoming message in the following form:
<Items>
<BatchID>123</BatchID>
<Item>...</Item>
<Item>...</Item>
<Item>...</Item>
</Items>
I've a pipeline with a XML disassembler which takes the Items schema and outputs the Item schema. On the Items schema, the Envelope property is set to true, and the "Body XPath" property points to the Items element.
However, when I try to process a file, I get the error: Finding the document specification by message type "BatchID" failed. Verify the schema deployed properly., presumably because it's expect only Item elements, and it doesn't know what to do with the BatchID element.
If I make the BatchID an attribute on the Items element (like a sensible person would have), everything works fine. Unfortunately, I'm stuck with the layout.
At this time, I do not care what is the value of BatchID.
How do I get it to split the message?

AFAIK the Xml disassembler always extracts all child nodes of the specified body_xpath element - it would have been a nice feature to be able to specify Item Xpath instead :(.
You can workaround this limitation by either:
Creating a schema for the undesirable <BatchID> element, and then just eat instances of it, e.g. creating a subscribing send port using TomasR's Null Adapter
or, Transform the envelope in a map on the receive port, before the envelope is debatched, where the transform strips out the unwanted Batch element
The following XSLT should do the trick:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var" version="1.0" xmlns:ns0="http://BizTalk_Server_Project3.Envelope">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="/ns0:Items" />
</xsl:template>
<xsl:template match="/ns0:Items">
<ns0:Items>
<!--i.e. Copy all Item elements and discard the Batch elements-->
<xsl:copy-of select="ns0:Item"/>
</ns0:Items>
</xsl:template>
</xsl:stylesheet>
See here on how to convert a btm from a visual map to xslt

Related

Saxon HE 9.7 XQuery results and existing document

I am new to Saxon.
In my java application, I have a requirement that I need to XQuery an existing dom4j document. The XQuery is to order few elements in an descending order by serialNo:
<?xml version="1.0" encoding="UTF-8"?>
<dataOfBooks:DataOfBooks xmlns:dataOfBooks="DataOfBooks">
<Id>ID123</Id>
<books>
<book>
<name>ccc</name>
<serialNo>77</serialNo>
</book>
<book>
<name>aaa</name>
<serialNo>99</serialNo>
</book>
</books>
</dataOfBooks:DataOfBooks>
Once I get the XQuery results, I need to add those back to the above existing document. I tried using net.sf.saxon.s9api. I was able to get the XQuery results back as below:
<?xml version="1.0" encoding="UTF-8"?>
<result:sequence
xmlns:result="http://saxon.sf.net/xquery-results"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<result:element>
<book xmlns:data="dataOfBooks">
<name>aaa</name>
<serialNo>99</serialNo>
</book>
<book xmlns:data="dataOfBooks">
<name>aaa</name>
<serialNo>77</serialNo>
</book>
</result:element>
</result:sequence>
But I have two issues. 1) the result has namespaces and extra stuff that I do not want. 2) It is not very clear to me as which Saxon API to use to add the XQuery results to the existing document. So that the resultant document looks as:
<?xml version="1.0" encoding="UTF-8"?>
<dataOfBooks:DataOfBooks xmlns:dataOfBooks="DataOfBooks">
<Id>ID123</Id>
<books>
<book>
<name>aaa</name>
<serialNo>99</serialNo>
</book>
<book>
<name>ccc</name>
<serialNo>77</serialNo>
</book>
</books>
</dataOfBooks:DataOfBooks>
One more question - I tried using dynamicContext and treeinfo classes since I though the usage of treeinfo API might be more optimal, but no luck. If you think, usage of TreeInfo API is efficient, I really appreciate a code example for my requirement. Your help is much appreciated.
Thanks in advance for your time and interest.
The fact that your XQuery code is producing unwanted namespaces is because your query is wrong, but we can't tell you how it is wrong unless you show us the code.
The result:sequence in your output suggests that you have somehow contrived to ask for output in "wrapped" format, which suggests some kind of misuse of Saxon APIs. Again, without seeing your code, we can't tell you exactly what you have done wrong.
To make small changes to an existing document, leaving the rest unchanged, I would normally recommend XSLT over XQuery. In XSLT 3.0, you can sort the books by name using the following stylesheet:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="books">
<xsl:copy>
<xsl:perform-sort select="book">
<xsl:sort select="name"/>
</xsl:perform-sort>
</xsl:copy>
</xsl:template>
</xsl:transform>
In both XQuery and XSLT, the result of your query/transformation is a new document, which you can use in place of the original. If you want to make in-situ updates to an existing document, you can do this using XQuery Update; however Saxon does not support XQuery Update against documents in DOM4J format.
Saxon does allow you to capture the result of a query or transformation as a DOM4J Document, and you could use DOM4J APIs to graft this document (or rather, its outermost element) back into the original DOM4J document.
Later
You have now provided your code (you should have provided it as an edit to the original question, not as an answer).
I guess your DOMWriter is the DOM4J class of that name, which like much of DOM4J is rather badly documented. But I think it is copying the DOM4J tree to a DOM tree, which you definitely don't want to do. If you really want to copy the tree to make it convenient for Saxon, you should copy it to a Saxon tree, but for this use case it's best to leave it in DOM4J form. Use
DocumentBuilder builder = processor.newDocumentBuilder();
XdmNode inDoc = builder.wrap(dom4jdoc);
When you run your query, the resulting XdmValue will now be a sequence of XdmNode objects, each of which is a wrapper around a DOM4J Element node. These element nodes are still attached to the original DOM4J tree, and they still have their original namespaces. There is no need to serialize the result to lexical XML.
You can copy the result to a List value by writing
List<Element> sortedNodes = new ArrayList<Element>();
for (XdmItem item : result) {
sortedNodes.add(((Element)((XdmNode)item).getExternalNode()));
}
and then (if I read the DOM4J documentation correctly) you can replace the content of the containing books element with
Element books = (Element)sortedNodes.get(0).getParent();
List booksContent = books.elements();
booksContent.clear();
booksContent.addAll(sortedBooks);

How to get a particular child node collection from xml to xslt

I have below xml file
<htmlResponses>
<resultSet></resultSet>
<referencePoint></referencePoint>
<htmlResponse></htmlResponse>
<htmlResponse></htmlResponse>
<htmlResponse></htmlResponse>
</htmlResponses>
And I want to get node "htmlResponse" collection in a xsl variable so I can loop through it by using XSLT.
Can anyone guide me how can I achieve this?
Agreed with Hobbes, but you could do this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="responses" select="//htmlResponse"/>
<xsl:template match="/">
... do something ...
<xsl:for-each select="$responses/*">
...
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The only use case I can think of where this makes sense is if you have a lot of data, and wish to build your node set using xsl:key and then reuse that node set multiple times.

How do I pivot XML nodes to XML rows?

I have an XML file which has hard-coded element names such as FIRST_NAME, LAST_NAME etc.
<employee>
<EMP_NO>1234</EMP_NO>
<FIRST_NAME>Bob</FIRST_NAME>
<SOMETHING_ELSE>Weakly mapped</SOMETHING_ELSE>
<SOME_OTHER_VALUE>Also weakly mapped</SOME_OTHER_VALUE>
<LAST_NAME>Smith</LAST_NAME>
</employee>
The output I am after is
<employee>
<number>123</number>
<values>
<value>
<name>FIRST_NAME</name>
<value>Bob</value>
</value>
<value>
<name>LAST_NAME</name>
<value>Smith</value>
</value>
<value>
<name>SOMETHING_ELSE</name>
<value>Weakly mapped</value>
</value>
<value>
<name>SOME_OTHER_VALUE</name>
<value>Also weakly mapped</value>
</value>
</value>
</employee>
Some of these nodes are expected, emp_no, first_name and last_name and I will create explicit mapping from the XML source to the XML dest for those nodes to match the input.
What I need to achieve next is for every unmapped node to also add items to my target XML. If a new node comes in then it will also be added to the target XML.
If this is not possible then I'd just like a quick way of manually selecting nodes and transforming them into items, otherwise I am going to have to spend hours manually adding nodes in the target and wiring up the name (constant) and value from the source node.
This is the kind of thing I am having to do now...
Obviously for hundreds of nodes this is going to take me hours, and I have a number of files to create mappings for so it will take me days. I'd really like at least to be able to do something like this...
It is possible to do what you want in MapForce with a custom, imported inline xsl template, and a slight modification to your range schema. It is a bit of a hack to do this in MapForce, and it is probably easier and cleaner to use a hand-written xsl transform instead.
Start with the custom template (in a file, e.g. custom.xsl):
<xsl:template name="metaItemIterate">
<xsl:param name="employeeNode"/>
<xsl:for-each select="$employeeNode/*">
<xsl:element name="metaItem">
<xsl:element name="name">
<xsl:value-of select="local-name(.)"/>
</xsl:element>
<xsl:element name="value">
<xsl:value-of select="."/>
</xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:template>
Note that this template generates a single element with multiple children. You could alternatively just generate the necessary child elements, and link them accordingly...
Then use the Add/Remove Libraries... button (found below the Libraries) to add your xsl file as a library.
Drag the new function between the range and domain schemas, and connect the employee node to employeeNode and the result of the function to clientDefinedMetaData.
Note you may need to add the attribute mixed="true" to the schema definition for clientDefinedMetaData in order to allow generation of random elements below it.
The output I get with your data from clicking the Output tab:
Also, if you cannot change the schema definition to allow the mixed elements below it, it is probably not possible to go about this any other way with MapForce.

Enrich message from within same message

Using Biztalk 2010 I have a incoming message with this structure:
<xml><blocks>
<block id="level">
<message id="code">100</message>
<message id="description">Some description</message>
</block>
<block id="level">
<message id="code">101</message>
<message id="description">More description</message>
</block>
</blocks>
<blocks>
<block id="change">
<message id="table">1</message>
<message id="oldvalue">100</message>
<message id="newvalue">101</message>
</block>
</blocks>
</xml>
I need to map the above to this structure:
<terms>
<termItem>
<code>100</code>
<description>Some description</description>
<deleted>false</deleted>
</termItem>
<termItem>
.....and so on with values from the above xml file, except that the item from the "change" block should be added as a new record to output, so the total output will be 3 items (<block>).
The map view is like this:
I need some help in choosing the right combination of either functoids to use, or maybe another approach to solve this challenge.
I'm able to either choose all blocks with the "level" value and filter out the "change" block, but unable to make a combination of the two.
Any hints, suggestions are very welcome.
Thanks
There seems to be more than meets the eye
The incoming xml seems to be nested (as per the schema in the visual mapper), so the example input xml structure might not quite be right?
Also, it might be that the schema on the RHS is debatched, i.e. one PaymentTerms message per company id, so unless you only need to map the first Company, you will need to create a wrapper schema for all mapped companies, with an arbitrary root node, and then debatch them before sending.
That said, it is relatively straightforward to get the general structure of the output by using a custom xslt instead of the visual mapper. I've assumed the RHS schema on your diagram for the real output schema (not your terms example).
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
exclude-result-prefixes="xsl xsi">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<!--Outer template-->
<xsl:template match="/">
<PaymentTerms CompanyCode="Unsure">
<xsl:apply-templates />
</PaymentTerms>
</xsl:template>
<!--Root blocks only-->
<xsl:template match="block[#id='level']">
<PaymentTerm>
<Code>
<xsl:value-of select="message[#id='code']/text()"/>
</Code>
<Description>
<xsl:value-of select="message[#id='description']/text()"/>
</Description>
<Deleted>
<!--No idea how you want this populated-->
<xsl:value-of select="'false'"/>
</Deleted>
</PaymentTerm>
<xsl:apply-templates select="blocks/block"></xsl:apply-templates>
</xsl:template>
<!--Nested blocks only-->
<xsl:template match="block[#id='change']">
<PaymentTerm>
<Code>
NestedCode
</Code>
<Description>
NestedDescription
</Description>
<Deleted>
NestedDeleted
</Deleted>
</PaymentTerm>
</xsl:template>
</xsl:stylesheet>
You didn't provide much info on how the nested blocks are to be mapped, so I've provided placeholders in the meantime.
HTH!

How would I dynamically add a new XML node based on the values of other nodes?

Background:
I have an old web CMS that stored content in XML files, one XML file per page. I am in the process of importing content from that CMS into a new one, and I know I'm going to need to massage the existing XML in order for the import process to work properly.
Existing XML:
<page>
<audience1>true</audience>
<audience2>false</audience>
<audience3>true</audience>
<audience4>false</audience>
<audience5>true</audience>
</page>
Desired XML:
<page>
<audience1>true</audience>
<audience2>false</audience>
<audience3>true</audience>
<audience4>false</audience>
<audience5>true</audience>
<audiences>1,3,5</audiences>
</page>
Question:
The desired XML adds the node, with a comma-delimited list of the other nodes that have a "true" value. I need to achieve the desired XML for several files, so what is the best way to accomplish this? Some of my ideas:
Use a text editor with a regex find/replace. But what expression? I wouldn't even know where to begin.
Use a programming language like ASP.NET to parse the files and append the desired node. Again, not sure where to begin here as my .NET skills are only average.
Suggestions?
I would probably use the XmlDocument class in .net, but that's just me because I've never been that fond of regexs.
You could then use XPath expressions to pull out the child nodes of each page node, evaluate them, and append a new node at the end of the page children, save the XmlDocument when you are done.
Xsl is an option too, but the initial learning curve is a bit painful.
There's probably a more elegant way with a regex, but if you are only running it once, it only matters that it works.
I would likely use an XSLT stylesheet to solve this problem. I built the following stylesheet to be a little bit generic that exactly what you asked for, but it could easily be modified to give you the exact output you had specified if you truly need that exact output.
<?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:apply-templates select="/*"/>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="*"/>
<xsl:element name="nodes">
<xsl:apply-templates select="*[normalize-space(.) = 'true']"/>
</xsl:element>
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*">
<xsl:value-of select="concat(',', local-name())"/>
</xsl:template>
<xsl:template match="/*/*[1]">
<xsl:value-of select="local-name()"/>
</xsl:template>
</xsl:stylesheet>
This XSLT's output would be:
<page>
<audience1>
true
</audience1>
<audience2>
false
</audience2>
<audience3>
true
</audience3>
<audience4>
false
</audience4>
<audience5>
true
</audience5>
<nodes>audience1,audience3,audience5</nodes>
</page>
XSLT would be a good fit for this because you can use from almost any programming language you want or you could use Visual Studio to apply the template. There are also many free tools out there that you could use to apply the transformations.

Resources