1. Problem
You need to map an incoming
document with a node containing a document that matches exactly the
destination schema. All of the nodes in the source document need to be
mapped in their entirety to the destination document. Both the source
and the destination schemas have target namespaces, and the namespaces
must be preserved.
2. Solution
By using inline XSLT within
a Scripting functoid, you can create the target document with the
appropriate nodes and the correct namespace. Listing 1 shows a sample input document.
Example 1. Sample Input Document for the Inline XSLT Example
<ns0:SampleSource xmlns:ns0="http://Sample.Source"> <ID>ID_0</ID> <Payload> <SampleDestination> <Name>Name_0</Name> <Address>Address_0</Address> <Company>Company_0</Company> </SampleDestination> </Payload> </ns0:SampleSource>
|
The inline XSLT is written
to parse the incoming document as a whole; there is no need to have an
input to the Scripting functoid. The output of the Scripting functoid
occurs under the node that the output is tied to—in this case, the XSLT
output will be directly below the SampleDestination element. When XML documents contain namespaces, use the local-name()
qualifier to reference a specific node by name. Because of the
requirement that the namespaces be preserved, it is necessary to copy
each of the nodes in the source document's Payload node (which consists of an Any element that can contain any XML structure) separately to the destination document.
Figure 1 shows the configuration for the Scripting functoid with the inline XSLT for the <Name> element. Additional nodes (such as Address and Company) can be added using the same code pattern. See Listing 2 for the XSLT code.
Example 2. XSLT Code
<xsl:if test="//*[local-name()='SampleDestination'])/*[local-name()='Name']"> <xsl:element name="Name"> <xsl:value-of select="//*[local-name()='SampleDestination']/*[local-name()='Name']"/> </xsl:element> </xsl:if>
|
This XSLT script will produce the desired XML document on output, as shown in Listing 3.
Example 3. Sample Output Document for the Inline XSLT Example
<ns0:SampleDestination xmlns:ns0="http://Sample.Dest"> <Name>Name_0</Name> <Address>Address_0</Address> <Company>Company_0</Company> </ns0:SampleDestination>
|
3. How It Works
The two expected solutions to this, the Mass Copy functoid or the <xsl:copy-of>
function, both end up requiring additional logic and code outside the
map due to the presence of the namespaces. These can be solved in
orchestration mapping but not in maps outside orchestrations. An
additional approach would be to reference an external XSLT file where
more complex functions are available.
The Mass Copy functoid,
designed specifically to copy entire documents to a destination node,
is a graphic representation of the XSLT <xsl:copy-of>
function. Both of these copy the entire source document to the
destination document. The problem is that the source document namespace
will be copied to the target document, regardless of the node level
being copied. There is no simple way in XSLT to remove the source
document namespace. For instance, if in the given solution for this
recipe, the inline XSLT was changed to simply read:
<xsl:copy-of select="//*[local-name()='Payload']"/>
The following document would be
created on output of the map (note that the root node contains the
namespace of the source schema):
<Payload xmlns:ns0="Sample.Source">
<SampleDestination>
<Name>Name_0</Name>
<Address>Address_0</Address>
<Company>Company_0</Company>
</SampleDestination>
</Payload>
If a Mass Copy functoid were used on the Payload
node, the following document would be produced (note that the root node
repeats itself and that it contains the namespace of the source
schema):
<ns0:SampleDestination xmlns:ns0="http://Sample.Dest">
<SampleDestination xmlns:ns0="http://Sample.Source">
<Name>Name_0</Name>
<Address>Address_0</Address>
<Company>Company_0</Company>
</SampleDestination>
</ns0:SampleDestination>
The output document for both of
these approaches is not the desired outcome. However, if the mapping is
being done in an orchestration, the output document will be in a
message that can now be accessed and corrected in a Message Assignment
shape, using the code in Listing 4.
Example 4. Message Assignment
// xmlDoc is a variable of type System.Xml.XmlDocument() // msgSampleDestination contains the output of the map
// xpath will access the contents of the SampleDestination node xmlDoc = xpath(msgSampleDestination, "/*/*");
// populate the message msgSampleDestination = xmlDoc;
|
Another option would be
to move the entire solution to an external XSLT document, where more
complex functions such as templates are available, and reference it in
the map directly rather than using the mapping functions. This can be
done by selecting properties on the map being developed and indicating
the path to the XSLT file in the Custom XSLT Path property.
NOTE
One of the greatest
skills in BizTalk mapping is becoming proficient with XSLT. Functoids
are great, but knowing how to code is even better! Do yourself a favor
and learn how to apply Inline XSLT scripting to your maps. You will be
astounded at how much easier it is to write a map that is intelligent
and maintainable.
In summary, a number of
approaches can be taken to map an incoming document with a node
containing a document that matches exactly the destination schema. The
cleanest and most viable approach is described in this recipe's
solution. Quicker and more convoluted solutions involve using the Mass
Copy functoid and <xsl:copy-of>
function. An additional approach is to move the mapping completely out
of the map into a separate XSLT document. The appropriate solution must
be decided by the developer.