Tuesday, January 03, 2006

Remove empty nodes in BizTalk by using XSLT

If you have ever had a map which begins to look unmanageable with tons of value mappings or extra functoids to simply manage empty nodes you don’t want in your output then you should consider using an XSLT mapping to clean the document up for you.

This involves actually making 2 mappings instead of one, but the upside is faster development with a nominal performance cost. Instead of adding functoids, testing and retesting the output, let the mapper generate the empty nodded and then add an extra mapping step with the following XSLT embedded in it:


<?xml version="1.0" ?>

<xsl:stylesheet xmlns:xsl="@@ YOUR NAMESPACE @@" version="1.0" xmlns:ns0="http://Stater.Isvcs.Iface.BO.GetLoanData.ElfV2">

    <xsl:output method="xml" indent="yes" />

    <xsl:template match="node()">

        <xsl:if test="count(descendant::text()[string-length(normalize-space(.))>0]|@*)">

            <xsl:copy>

                <xsl:apply-templates select="@*|node()" />

            </xsl:copy>

        </xsl:if>

    </xsl:template>

    <xsl:template match="@*">

        <xsl:copy />

    </xsl:template>

    <xsl:template match="text()">

        <xsl:value-of select="normalize-space(.)" />

    </xsl:template>

</xsl:stylesheet>




Save this snippet (or download it here) into your project (open the XSLT and change the namespace!) then create a new map. Make your source and destination schemas the same and in the map properties add your XSLT in the “Custom XSLT path” setting. Place the new map after the “dirty” map; the output should give you an XML document free of extra nodes and a map free of extra functoids.

Of course, if you are doing a strictly messaging based implementation then you are out of luck unless you chain ports together but within an orchestration using XSLT to remove empty nodes might work well for you.

4 comments:

AK said...

Hello,
do i have to make any other changes apart from changing the namespace. Because, when i use this xslt, i get an error message:

Error encountered while executing the transform Etcgs.Capita.WebItem.Maps.CleanDfesCreateWebItemReqA. Error:Unable to create the transform.

thanks,
AK

Mike said...

Hi

Do you have a mini sample project using this Xslt, I am getting errors when I try to use the xslt file (and I have changed the namespace.)
Error received is:-

Uncaught exception (see the 'inner exception' below) has suspended an instance of service 'BizTalk_Server_Project1.BizTalk_Orchestration1(9cfb164b-dcb9-c0f8-02ea-05a837673e4a)'.
The service instance will remain suspended until administratively resumed or terminated.
If resumed the instance will continue from its last persisted state and may re-throw the same unexpected exception.
InstanceId: e17cdd88-3de3-44ee-b131-5379a5cc2fb5
Shape name: ConstructMessage_2
ShapeId: 36153bce-191b-471a-93e3-dc6897fc2199
Exception thrown from: segment 1, progress 9
Inner exception: Error encountered while executing the transform BizTalk_Server_Project1.Transform_2. Error:Unable to create the transform..

Exception type: XTransformationFailureException
Source: Microsoft.XLANGs.Engine
Target Site: Void ApplyTransform(System.Type, System.Object[], System.Object[])
The following is a stack trace that identifies the location where the exception occured

at Microsoft.XLANGs.Core.Service.ApplyTransform(Type mapRef, Object[] outParams, Object[] inParams)
at BizTalk_Server_Project1.BizTalk_Orchestration1.segment1(StopConditions stopOn)
at Microsoft.XLANGs.Core.SegmentScheduler.RunASegment(Segment s, StopConditions stopCond, Exception& exp)
Additional error information:

XSLT compile error at (13,1). See InnerException for details.

Exception type: XsltCompileException
Source: System.Data.SqlXml
Target Site: Void Compile(System.Xml.Xsl.XsltOld.NavigatorInput, System.Xml.XmlResolver, System.Security.Policy.Evidence)
The following is a stack trace that identifies the location where the exception occured

at System.Xml.Xsl.XsltOld.Compiler.Compile(NavigatorInput input, XmlResolver xmlResolver, Evidence evidence)
at System.Xml.Xsl.XslTransform.Compile(XPathNavigator stylesheet, XmlResolver resolver, Evidence evidence)
at System.Xml.Xsl.XslTransform.Load(XPathNavigator stylesheet, XmlResolver resolver, Evidence evidence)
at System.Xml.Xsl.XslTransform.Load(XmlReader stylesheet, XmlResolver resolver, Evidence evidence)
at Microsoft.XLANGs.BaseTypes.TransformBase.get_Transform()
at Microsoft.XLANGs.RuntimeTypes.TransformMetaData..ctor(Type transformBaseType)
at Microsoft.XLANGs.RuntimeTypes.TransformMetaData._creator(Type t)
at Microsoft.XLANGs.RuntimeTypes.MetadataCache._slowFor(Type t)
at Microsoft.XLANGs.RuntimeTypes.MetadataCache.For(Type t)
at Microsoft.XLANGs.RuntimeTypes.TransformMetaData.For(Type t)
at Microsoft.XLANGs.Core.Service.ApplyTransform(Type mapRef, Object[] outParams, Object[] inParams)
Additional error information:

'boolean(count(descendant::text()[string-length(normalize-space(.))>0]@*))' is an invalid XPath expression.

Exception type: XsltException
Source: System.Data.SqlXml
Target Site: Int32 AddQuery(System.String, Boolean, Boolean, Boolean)
The following is a stack trace that identifies the location where the exception occured

at System.Xml.Xsl.XsltOld.Compiler.AddQuery(String xpathQuery, Boolean allowVar, Boolean allowKey, Boolean isPattern)
at System.Xml.Xsl.XsltOld.Compiler.AddBooleanQuery(String xpathQuery)
at System.Xml.Xsl.XsltOld.IfAction.CompileAttribute(Compiler compiler)
at System.Xml.Xsl.XsltOld.CompiledAction.CompileAttributes(Compiler compiler)
at System.Xml.Xsl.XsltOld.IfAction.Compile(Compiler compiler)
at System.Xml.Xsl.XsltOld.Compiler.CreateIfAction(ConditionType type)
at System.Xml.Xsl.XsltOld.ContainerAction.CompileInstruction(Compiler compiler)
at System.Xml.Xsl.XsltOld.ContainerAction.CompileOnceTemplate(Compiler compiler)
at System.Xml.Xsl.XsltOld.ContainerAction.CompileTemplate(Compiler compiler)
at System.Xml.Xsl.XsltOld.TemplateAction.Compile(Compiler compiler)
at System.Xml.Xsl.XsltOld.Compiler.CreateTemplateAction()
at System.Xml.Xsl.XsltOld.ContainerAction.CompileTopLevelElements(Compiler compiler)
at System.Xml.Xsl.XsltOld.ContainerAction.CompileDocument(Compiler compiler, Boolean inInclude)
at System.Xml.Xsl.XsltOld.RootAction.Compile(Compiler compiler)
at System.Xml.Xsl.XsltOld.Compiler.CreateRootAction()
at System.Xml.Xsl.XsltOld.Compiler.Compile(NavigatorInput input, XmlResolver xmlResolver, Evidence evidence)
Additional error information:

'boolean(count(descendant::text()[string-length(normalize-space(.))>0]@*))' has an invalid token.

Exception type: XPathException
Source: System.Xml
Target Site: MS.Internal.Xml.XPath.AstNode ParseMethod(MS.Internal.Xml.XPath.AstNode)
The following is a stack trace that identifies the location where the exception occured

at MS.Internal.Xml.XPath.XPathParser.ParseMethod(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParsePrimaryExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseFilterExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParsePathExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseUnionExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseUnaryExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseMultiplicativeExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseAdditiveExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseRelationalExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseEqualityExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseAndExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseOrExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseMethod(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParsePrimaryExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseFilterExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParsePathExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseUnionExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseUnaryExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseMultiplicativeExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseAdditiveExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseRelationalExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseEqualityExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseAndExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseOrExpr(AstNode qyInput)
at MS.Internal.Xml.XPath.XPathParser.ParseXPathExpresion(String xpathExpresion)
at MS.Internal.Xml.XPath.QueryBuilder.Build(String query, Boolean allowVar, Boolean allowKey)
at System.Xml.Xsl.XsltOld.Compiler.AddQuery(String xpathQuery, Boolean allowVar, Boolean allowKey, Boolean isPattern)

regards

Mike

Lex Hegt said...

Mike, AK,

The original author has left the company and did not leave a sample project.
I haven't tried his trick myself, but I will try it as well. When I fixed it I'll post a comment.

Anonymous said...

I got this to work.
Looks like the xsl:stylesheet tag is malformed. Try changing thr xls:stylesheet line to something like this:

xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/TR/xhtml1/strict" xmlns:ns0="http://your.namespace.here"