Search This Blog

Wednesday 3 October 2018

Converting CDATA to XML and mapping it to the root node in BizTalk map using XSLT


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('&lt;Customers&gt;')" />
    <xsl:with-param name="replace-string" select="string('&lt;Customers xmlns=&quot;http://BizTalkConcepts.CustomerXmlSchema&quot;&gt;')" />
  </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(&apos;&quot;http://BizTalkConcepts.CustomerCDATASchema&quot;&apos;)" />
    <xsl:with-param name="replace-string" select="string(&apos;&quot;http://BizTalkConcepts.CustomerXmlSchema&quot;&apos;)" />
  </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.


No comments:

Post a Comment

Sending Email to Multiple recipients using PowerShell

Hi All,  Below is the working code to Send emails to Multiple recipients using PowerShell. [STRING]$PSEmailServer = "YourSMTPServerIPOr...