In one of my recent projects, I’ve come across the
requirement where the source system is sending the XML wrapped under the CDATA
in string element. Source system can
send its own namespace in the content of the CDATA. I had a requirement to
extract the CDATA and change the namespace in the CDATA and map it to the
destination schema. Below is the procedure that I’ve followed to achieve the
task.
There are 2 things that I have explored as part of this.
1). If the Source system doesn’t provide namespace
2). If Source System provides its own namespace and we need
to change the source namespace to the destination namespace.
The Source and destination schemas that I have used to explain
the concepts are shown below.
Source Schema CustomerCDATASchema.xsd
<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns="http://BizTalkConcepts.CustomerCDATASchema" xmlns:b="http://schemas.microsoft.com/BizTalk/2003" elementFormDefault="qualified" targetNamespace="http://BizTalkConcepts.CustomerCDATASchema" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Customers">
<xs:complexType>
<xs:sequence>
<xs:element name="CustomersCDATA" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Destination Schema CustomerXmlSchema.xsd
<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns="http://BizTalkConcepts.CustomerXmlSchema" xmlns:b="http://schemas.microsoft.com/BizTalk/2003" elementFormDefault="qualified" targetNamespace="http://BizTalkConcepts.CustomerXmlSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Customers">
<xs:complexType>
<xs:sequence>
<xs:element name="Customer">
<xs:complexType>
<xs:sequence>
<xs:element name="CustomerId" type="xs:string" />
<xs:element name="CustomerName" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
The above requirement can be achieved by using custom XSLT
templates.
After Googling and spending some time I have managed to
solve the problem.
The sample input message is as shown below.
<ns0:Customers xmlns:ns0="http://BizTalkConcepts.CustomerCDATASchema">
<ns0:CustomersCDATA><![CDATA[
<ns0:Customers xmlns:ns0="http://BizTalkConcepts.CustomerCDATASchema">
<ns0:Customer>
<ns0:CustomerId>CustomerId_0</ns0:CustomerId>
<ns0:CustomerName>CustomerName_0</ns0:CustomerName>
</ns0:Customer>
</ns0:Customers>]]>
</ns0:CustomersCDATA>
</ns0:Customers>
The Output that I’m expecting is as shown below. Make
observation on the namespaces highlighted.
<ns0:Customers xmlns:ns0="http://BizTalkConcepts.CustomerXmlSchema">
<ns0:Customer>
<ns0:CustomerId>CustomerId_0</ns0:CustomerId>
<ns0:CustomerName>CustomerName_0</ns0:CustomerName>
</ns0:Customer>
</ns0:Customers>
Create a map CustomerCDATASchema_To_CustomerXmlSchema.btm.
This map takes the CDATA and converts to the XML.
Drag and drop the scripting functiod and select Script type as Inline XSLT Call Template, copy and paste the below code.
1. If the Source system doesn’t provide namespace
<!-- This Template extracts the CDATA field from the Source and adds
the namespace when outputting -->
<xsl:template name="TemplateToExtractCDATAandAddNamespace">
<xsl:param name="param1" />
<xsl:variable name="localCustomersCDATA" select="." />
<xsl:call-template name="search-and-replace">
<xsl:with-param name="input" select="string($localCustomersCDATA)" />
<xsl:with-param
name="search-string" select="string('<Customers>')" />
<xsl:with-param name="replace-string" select="string('<Customers xmlns="http://BizTalkConcepts.CustomerXmlSchema">')" />
</xsl:call-template>
</xsl:template>
<!-- This Template replaces the given string with the specified
string. -->
<xsl:template name="search-and-replace">
<xsl:param name="input"/>
<xsl:param name="search-string"/>
<xsl:param name="replace-string"/>
<!-- Find the
substring before the search string and store it in a
variable -->
<xsl:variable name="temp"
select="substring-before($input,$search-string)"/>
<xsl:choose>
<!-- If $temp is
not empty or the input starts with the search
the string then we know we have to do a
replace. This eliminates the
need to use contains( ). -->
<xsl:when test="$temp or
starts-with($input,$search-string)">
<xsl:value-of disable-output-escaping="yes" select="concat($temp,$replace-string)"/>
<xsl:call-template name="search-and-replace">
<!-- We
eliminate the need to call substring-after
by using the length of temp
and the search string
to extract the remaining
string in the recursive
call. -->
<xsl:with-param name="input"
select="substring($input,string-length($temp)+
string-length($search-string)+1)"/>
<xsl:with-param name="search-string"
select="$search-string"/>
<xsl:with-param name="replace-string"
select="$replace-string"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of disable-output-escaping="yes"
select="$input"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
2. If Source System provides its own namespace
and we need to change the source namespace to the destination namespace.
If the Input that we are getting in the CDATA filed has source namespace that needs to be replaced with the destination namespace then just change the TemplateToExtractCDATAandAddNamespace template to the below shown then it will work. Make an observation to the highlighted section.
<xsl:template name="TemplateToExtractCDATAandAddNamespace">
<xsl:param name="param1" />
<xsl:variable name="localCustomersCDATA" select="." />
<xsl:call-template name="search-and-replace">
<xsl:with-param name="input" select="string($localCustomersCDATA)" />
<xsl:with-param
name="search-string" select="string('"http://BizTalkConcepts.CustomerCDATASchema"')" />
<xsl:with-param name="replace-string" select="string('"http://BizTalkConcepts.CustomerXmlSchema"')" />
</xsl:call-template>
</xsl:template>
Map the CustomersCDATA field to the Scripting template and the output of the scripting functiod to the root node in the destination as shown
below.
NB: Please let me know if this article has helped you. Feel
free to ask questions in the comments below if you have any requirements on the map that you are struggling to solve.