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.
Related
I had asked this question 2 years ago (Problem with BizTalk multi-input map), but then the project was shelved and I never did any further testing. I'm having to dust it off again, with some more details and screenshots.
I have a BizTalk HIPAA solution that needs to merge an 837 claim schema with some data from our system. We're doing this with a map that has two input schemas, as shown here:
(I've greatly simplified the schemas for testing purposes).
The accepted answer to my original post (using an equal functoid and value mappers) works fine with a simple schema like I had originally shown, but fails with the actual EDI schema.
In the first input message, if IsRepriced = 1, I want to use our values for HCP_01, HCP_02, and HCP_03. If it's 0, use the values in the second message (the original 837 claim). The functoids shown work fine as long as the original 837 claim actually contains the HCP node (segment), but if it's not there I'm unable to generate one from our data.
Replacing these with a scripting functoid using some if-then-else C# code has the same effect.
So, is there a way to do this using functoids, or do I need to resort to XSLT? Unfortunately I know next to nothing about XSLT, so that's going to be difficult...
Thanks!
Edit: I would up using an Inline XSLT Call Template, with this code:
<xsl:template name="Repricing_2000B_HCP">
<xsl:param name="IsRepriced" />
<xsl:choose>
<xsl:when test="$IsRepriced='1'">
<xsl:for-each select="//InputMessagePart_1/ns0:X12_00401_837_I/ns0:TS837Q3_2000A_Loop/ns0:TS837Q3_2000B_Loop/ns0:TS837Q3_2300_Loop/ns0:HCP_ClaimPricingRepricingInformation_TS837Q3_2300">
<xsl:element name="ns0:HCP_ClaimPricingRepricingInformation_TS837Q3_2300">
<xsl:copy-of select="./#*" />
<xsl:copy-of select="./*" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="//InputMessagePart_0/ns0:X12_00401_837_I/ns0:TS837Q3_2000A_Loop/ns0:TS837Q3_2000B_Loop/ns0:TS837Q3_2300_Loop/ns0:HCP_ClaimPricingRepricingInformation_TS837Q3_2300">
<xsl:element name="ns0:HCP_ClaimPricingRepricingInformation_TS837Q3_2300">
<xsl:copy-of select="./#*" />
<xsl:copy-of select="./*" />
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I frequently hit a brick wall with with the if missing-else paradign in a Map. Sometimes Looping Functioids do the trick, sometimes not. Sometimes a carefully placed Logical Existence -> Not works, sometimes no.
It's mostly because if one of the source Records is minOccurs=0, the Mapper will wrap everything in a for-each. Since the Element doesn't exist, the code never gets executed.
I'd say >50%, I resort to inline Xslt because at some point, it becomes cleaner than a page of mostly duplicate Functoid groups.
However, I'd bet the Xslt won't turn out as difficult a proposition as you think. The Mapper can do most of the work for you. You can build the bulk of the Map using Functoids, then just copy the resulting Xslt. You will have to modify for some things, like namespaces.
(Same answer)
I'm trying to understand this xslt.
What does the below xslt command select exactly? what are "following-sibling", "aic" and "pstyle"?
"aic" seems to be a namespace.
What xml input the below xslt work with?
<xsl:stylesheet exclude-result-prefixes="aic"
version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:aic="http://ns.adobe.com/AdobeInCopy/2.0/" >
<xsl:template match="/">
<xsl:value-of select="following-sibling::aic:pstyle"/>
</xsl:template>
</xsl:stylesheet>
following-sibling::aic:pstyle
following-sibling is the axis, denoting which "direction" to look for nodes, in this case it looks at nodes which are after the current context node in document order but share the same parent as the current node. If you don't specify an axis the default is child, which looks for child nodes of the current context node.
aic:pstyle is a selector that looks for elements whose local name is pstyle and whose namespace URI is http://ns.adobe.com/AdobeInCopy/2.0/ (the one that is mapped to the prefix aic in the stylesheet).
The source XML need not use the same prefix, e.g. the expression would match an element that looks like
<pstyle xmlns="http://ns.adobe.com/AdobeInCopy/2.0/">
or
<foo:pstyle xmlns:foo="http://ns.adobe.com/AdobeInCopy/2.0/">
in the original XML.
As JLRishe points out, this particular XPath will not match anything if the current context is the document node /, for the expression to be meaningful it would have to be executed in a context where the current node is an element (or comment, processing instruction or text node) at least two levels down i.e. a child of the document element or deeper.
<example xmlns="http://ns.adobe.com/AdobeInCopy/2.0/">
<pstyle id="1"/>
<foo/>
<pstyle id="2"/>
<pstyle id="3"/>
</example>
If executed with the foo element as the context node, the expression would select pstyle elements 2 and 3, but not 1.
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
Is there anyway in a BizTalk map to force destination elements to be created when the source elements don't exist without using an xslt call template?
I'm mapping parent/child xml to a wcf-sql adapter generated schema that has table-valued parameters for stored proc parameters.
So my source xml is:
<Category>
<CategoryId>1</CategoryId>
<CategoryName>Test</CategoryName>
</Category>
and/or a Category with Media
<Category>
<CategoryId>1</CategoryId>
<CategoryName>Test</CategoryName>
<Media>
<Medium>
<MediumId>1</MediumId>
<MediumName>test.jpg</MediumName>
</Medium>
</Media>
</Category>
The schema for the TypedProcedure is something like:
<ImportCategoryRequest>
<Category>
<CategoryId>1</CategoryId>
<CategoryName>Test</CategoryName>
</Category>
<Media>
<Medium>
<MediumId>1</MediumId>
<MediumName>test.jpg</MediumName>
</Medium>
</Media>
</ImportCategoryRequest>
So it doesn't like it when is all that shows up in the destination XML. Instead of passing null for a table-valued parameter it wants at least 1 row and to pass null values for the columns in the tvp. I can create the dummy xml with a xslt call-template but I'd like to avoid that.
The BizTalk mapper seems to use <xsl:for-each> and as a result won't generate an output element if there is no input.
But using xslt is really easy - see here how to scrape the xslt out of your existing map (and just remove the escaping around double quotes and slashes), and to change the map to custom XSLT.
The bit you need to change is around the Media is something like:
<xsl:choose>
<xsl:when test="count(ns0:Media)!=0">
<!-- Copy the mapper generated XSLT in the for each here-->
<xsl:foreach >
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<Media>
<Medium>
<MediumId>1</MediumId>
<MediumName>test.jpg</MediumName>
</Medium>
</Media>
</xsl:otherwise>
</xsl:choose>
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.