Monday, May 23, 2011

Instance wide XML manipulations

If the input file is a plain xml file without namespaces declared the following error occurs (at least when xml validation is set to 'strict'):
<invalidVariables xmlns="http://schemas.oracle.com/bpel/extension"><part name="code">
<code>9710</code></part><part name="summary">
<summary>Invalid xml document.
According to the xml schemas, the xml document is invalid. The reason is: Error::cvc-elt.1: Cannot find the declaration of element 'TestXSD'.
Please make sure that the xml document is valid against your schemas.</summary></part></invalidVariables>


Solution: create in the consumer routing service (the component after the file-reader, postfixed with _RS) the following transformation, first check if the wanted namespace is included in the namespace declarations, otherwise map one field to get all the namespaces declared. Then add the following code in the xsl:
<xsl:stylesheet version="1.0" xmlns:test="http://www.mydomain.com/TestXSD">
<xsl:template match="*">
  <xsl:element name="test:{local-name()}" namespace="http://www.mydomain.com/TestXSD">
    <xsl:apply-templates select="@*|node()"/>
  </xsl:element>
</xl:template>


This code is based on the "identity" template, which was introduced with an example in the XSLT Recommendation itself [ref: www.w3.org]:
<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>


Some explanation here: "node()" is shorthand for child::node(). That is node() matches all nodes (element, text, comment, processing-instruction) being children of other nodes but not attributes since they are not considered children of their parent element. To find all nodes in order to copy them, we need to match both nodes being children of other nodes, node(), and attributes, @*, that is: match="@*|node()".

This template copies everything recursive from source to target, all the attributes and nodes. Copying everything in itself is no fun but becomes very useful when we add exceptions or conditions to the copying. Here two transformations that copy all the nodes and attributes if:
  • They have content: RemoveEmptyNodes.xsl
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:template match="@*|node()">
        <xsl:if test=". != ''">
          <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
          </xsl:copy>
        </xsl:if>
      </xsl:template>
    </xsl:stylesheet>
  • They have content or the node contains an attribute with content: RemoveEmptyNodesKeepAttributes.xsl
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:template match="@*|node()">
        <xsl:if test=". != '' or ./@* != ''">
          <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
          </xsl:copy>
        </xsl:if>
      </xsl:template>
    </xsl:stylesheet>

These templates can replace one of Oracle's best practices [ref: Oracle® Application Integration Architecture - Foundation Pack 2.5: Integration Developer's Guide Release 2.5 Part No. E16465-01 December 2009], adding the RemoveEmptyNodes transformation after the main mapping fulfills:
Transformation logic and XPath should be designed with the possibility of missing or empty elements
<xsl:if test="normalize-space(Line3/text())">
  <Line3>
    <xsl:value-of select="Line3/text()"/>
  </Line3>
</xsl:if>

The above templates can save you the time-consuming conditional mapping per element. Just don't forget if the mapping contains empty placeholders (like EBMHeader/EBMTracking/ExecutionUnitID) then fill these first with an assign before calling the RemoveEmptyNodes. Also the element EBMHeader/DataArea/[verb] is a required field. This can be solved with both templates:
  • RemoveEmptyNodes.xsl: include in the template an exclusion of this element (the [verb] is Create here):
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:corecom="http://xmlns.oracle.com/EnterpriseObjects/Core/Common/V2">
      <xsl:template match="@*|node()">
        <xsl:if test=". != ''">
          <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
          </xsl:copy>
        </xsl:if>
      </xsl:template>
      <xsl:template match="corecom:Create">
        <xsl:copy-of select="."/>
      </xsl:template>
    </xsl:stylesheet>
  • RemoveEmptyNodesKeepAttributes.xsl: fill an optional attribute of this field, although not the most elegant solution...

No comments:

Post a Comment