This utility class found in the Microsoft.BizTalk.Streaming namespace has saved me on many occasions where I had to modify namespaces, elements, attributes and the XML declarations inside a pipeline and from a referenced custom assemble inside an orchestration. This class uses a stream reader and writer with virtual methods representing the components of a xml structure.
More information can be found here: http://msdn.microsoft.com/en-us/library/microsoft.biztalk.streaming.xmltranslatorstream.aspx
Using the XMLtranslatorStream avoids having to load the xml message into a xmlDocument object which will impact performance for large messages.
The list below shows all the available methods that may be overridden within this class
protected override int ProcessXmlNodes(int count);
protected virtual void TranslateAttribute();
protected virtual void TranslateAttributes();
protected virtual void TranslateAttributeValue(string prefix, string localName, string nsURI, string val);
protected virtual void TranslateCData(string data);
protected virtual void TranslateComment(string comment);
protected virtual void TranslateDocType(string name, string pubAttr, string systemAttr, string subset);
protected virtual void TranslateElement();
protected virtual void TranslateEndElement(bool full);
protected virtual void TranslateEntityRef(string name);
protected virtual void TranslateProcessingInstruction(string target, string val);
protected virtual void TranslateStartAttribute(string prefix, string localName, string nsURI);
protected virtual void TranslateStartElement(string prefix, string localName, string nsURI);
protected virtual void TranslateText(string s);
protected virtual void TranslateWhitespace(string space);
protected virtual void TranslateXmlDeclaration(string target, string val);
protected virtual bool TranslateXmlNode();
To use the XmlTranslatorStream inside a custom pipeline, add a subclass that inherits the XmlTranslatorStream. An example is shown below where I have overridden some of the methods.
Extra information maybe passed to the subclass by adding parameters to the constructor.
#region Subclass XmlModifierStream
public class XmlModifierStream : XmlTranslatorStream
{
public XmlModifierStream(Stream input): base(new XmlTextReader(input), Encoding.Default)
{
Debug.WriteLine("[BTS.Utilities.CustomPipelines.XmlNamespaceModifierStream]Entered method");
}
protected override void TranslateXmlDeclaration(string target, string val)
{
base.TranslateXmlDeclaration(target, val);
}
protected override void TranslateStartElement(string prefix, string localName, string nsURI)
{
base.TranslateStartElement(prefix, localName, nsURI);
}
protected override void TranslateStartAttribute(string prefix, string localName, string nsURI)
{
base.TranslateStartAttribute(prefix, localName, nsURI);
}
}
#endregion
Then inside the Execute method of the pipeline call the constructor of the sub class passing the data stream as shown below.
public Microsoft.BizTalk.Message.Interop.IBaseMessage Execute(
Microsoft.BizTalk.Component.Interop.IPipelineContext pc, Microsoft.BizTalk.Message.Interop.IBaseMessage inmsg)
{
Debug.WriteLine("[BTS.Utilities.CustomPipelines.Execute]Entered method");
if (inmsg == null || inmsg.BodyPart == null || inmsg.BodyPart.Data == null)
{
throw new ArgumentNullException("inmsg");
}
//call the xml translator subclass
inmsg.BodyPart.Data = new XmlModifierStream(inmsg.BodyPart.GetOriginalDataStream());
Debug.WriteLine("[BTS.Utilities.CustomPipelines.Execute]Exit method");
return inmsg;
}
Below are some examples where I have used the XMLTranslatorStream class.
In this scenario the incoming message contained elements which specified the document version and document type. The values TypeVersion and Type were read from the message below and used to identify the message type and finally rename the root element, remove all the element prefixes and update the namespace.
Inside the Execute function of the pipeline, I first opened a stream to obtain the required values from the message. These values are then passed as parameters to the subclass XmlNamespaceModifierStream together with a reference to the original stream.
The subclass overrides the TranslateStartElement method and checks if the current element is the root element of the document. If it is the root element I rename the element from StandardBusinessDocument to either CatalogueItemNotification or PriceSynchronisationDocument and set the namespace to a new value.
The overiden TranslateAttribute removes any attributtes that are prefixed with “xmlns”
//xpath the the document version elelment
private const string XPATHQUERY_VERSION = "/*[local-name()='StandardBusinessDocument']/*[local-name()='StandardBusinessDocumentHeader']/*[local-name()='DocumentIdentification']/*[local-name()='TypeVersion']";
private const string DOCUMENTTYPE_ELEMENT = "Type";
#region IComponent members
public Microsoft.BizTalk.Message.Interop.IBaseMessage Execute(Microsoft.BizTalk.Component.Interop.IPipelineContext pc, Microsoft.BizTalk.Message.Interop.IBaseMessage inmsg)
{
Debug.WriteLine("[BTS.Utilities.CustomPipelines.NamespaceModifier.Execute]Entered method");
if (inmsg == null || inmsg.BodyPart == null || inmsg.BodyPart.Data == null)
{
throw new ArgumentNullException("inmsg");
}
string propertyVersionValue = string.Empty;
string propertyTypeValue = string.Empty;
if (!string.IsNullOrEmpty(XPATHQUERY_VERSION))
{
Debug.WriteLine("[BTS.Utilities.CustomPipelines.NamespaceModifier.Execute]Obtain the xpath value from within the document");
IBaseMessagePart bodyPart = inmsg.BodyPart;
Stream inboundStream = bodyPart.GetOriginalDataStream();
//note that the path would be "C:\Documents and Settings\<BTSHostInstanceName>\Local Settings\Temp" for the virtual stream
VirtualStream virtualStream = new VirtualStream(VirtualStream.MemoryFlag.AutoOverFlowToDisk);
ReadOnlySeekableStream readOnlySeekableStream = new ReadOnlySeekableStream(inboundStream, virtualStream);
XmlTextReader xmlTextReader = new XmlTextReader(readOnlySeekableStream);
XPathCollection xPathCollection = new XPathCollection();
XPathReader xPathReader = new XPathReader(xmlTextReader, xPathCollection);
xPathCollection.Add(XPATHQUERY_VERSION);
bool isFirstMatch = false;
while (xPathReader.ReadUntilMatch())
{
//only interested in the first match
if (xPathReader.Match(0) && !isFirstMatch)
{
propertyVersionValue = xPathReader.ReadString();
isFirstMatch = true;
//get the type next which is 2nd element down
while (xPathReader.Read())
{
if (xPathReader.LocalName.Equals(DOCUMENTTYPE_ELEMENT))
{
propertyTypeValue = xPathReader.ReadString();
break;
}
}
}
}
if (isFirstMatch)
{
Debug.WriteLine(string.Format("[BTS.Utilities.CustomPipelines.NetNamespaceModifier.Execute]Match found for xpath query. Value equals:{0}", propertyVersionValue));
}
else
{
Trace.WriteLine(string.Format("[BTS.Utilities.CustomPipelines.NetNamespaceModifier.Execute]No match found for xpath query '{0}'", XPATHQUERY_VERSION));
}
//rewind back to start
readOnlySeekableStream.Position = 0;
bodyPart.Data = readOnlySeekableStream;
}
inmsg.BodyPart.Data = new XmlNamespaceModifierStream(inmsg.BodyPart.GetOriginalDataStream(), propertyVersionValue, propertyTypeValue);
Debug.WriteLine("[BTS.Utilities.CustomPipelines.NamespaceModifier.Execute]Exit method");
return inmsg;
}
#endregion
#region Subclass XmlNamespaceModifierStream
public class XmlNamespaceModifierStream : XmlTranslatorStream
{
private const string CIN_DOCTYPE = "catalogueItemNotification";
private const string CPN_DOCTYPE = "priceSynchronisationDocument";
private const string ROOT_GS1_ELEMENT = "StandardBusinessDocument";
private const string NS_PREFIX = "urn:ean.ucc:";
private string _newNamespaceVersion;
private string _documentType;
protected override void TranslateStartElement(string prefix, string localName, string nsURI)
{
string newNSUri = string.Empty;
bool isElementFoundWithNamespace = false;
bool isFirstElement = false;
if (!string.IsNullOrEmpty(prefix) && !isFirstElement)
{
//element found with prefix. Modify namespace with new value and append passed namespace version
newNSUri = NS_PREFIX + _newNamespaceVersion;
isElementFoundWithNamespace = true;
if (localName.Equals(ROOT_GS1_ELEMENT))
isFirstElement = true;
}
if (isElementFoundWithNamespace & isFirstElement)
{
//replace with new namespace
Debug.WriteLine(string.Format("[BTS.Utilities.CustomPipelines.NamespaceModifier.XmlNamespaceModifierStream]Replace namespace with {0}", nsURI + newNSUri));
if (_documentType.Equals(CIN_DOCTYPE))
localName = localName + "Catalogue";
if (_documentType.Equals(CPN_DOCTYPE))
localName = localName + "Price";
base.TranslateStartElement(null, localName, newNSUri);
}
else
{
base.TranslateStartElement(null, localName, null);
}
}
protected override void TranslateAttribute()
{
if (this.m_reader.Prefix != "xmlns" && this.m_reader.Name != "xmlns")
base.TranslateAttribute();
}
public XmlNamespaceModifierStream(Stream input, string namespaceVersion, string documentType)
: base(new XmlTextReader(input), Encoding.Default)
{
Debug.WriteLine("[BTS.Utilities.CustomPipelines.NamespaceModifier.XmlNamespaceModifierStream]Entered method");
_newNamespaceVersion = namespaceVersion.Trim();
_documentType = documentType.Trim();
Debug.WriteLine("[BTS.Utilities.CustomPipelines.NamespaceModifier.XmlNamespaceModifierStream]Exit method");
}
}
#endregion
In this custom send pipeline I used the XMLTranslator to add the following XML declaration “version=”1.0″ encoding=”utf-8″” and to modify the namespaces and prefixes of some elements and attributes.
The Execute function in the custom pipeline component simply calls the subclass XmlExtensionModifierStream passing only the original message stream as a parameter value.
The subclass overrides the TranslateXmlDeclaration, TranslateStartElement and TranslateStartAttribute methods to modify the values.
#region IComponent members
public Microsoft.BizTalk.Message.Interop.IBaseMessage Execute(Microsoft.BizTalk.Component.Interop.IPipelineContext pc,
Microsoft.BizTalk.Message.Interop.IBaseMessage inmsg)
{
Debug.WriteLine("[BTS.Utilities.CustomPipelines.ExtensionModifier.Execute]Entered method");
if (inmsg == null || inmsg.BodyPart == null || inmsg.BodyPart.Data == null)
{
throw new ArgumentNullException("inmsg");
}
inmsg.BodyPart.Data = new XmlExtensionModifierStream(inmsg.BodyPart.GetOriginalDataStream());
Debug.WriteLine("[BTS.Utilities.CustomPipelines.ExtensionModifier.Execute]Exit method");
return inmsg;
}
#endregion
#region Subclass XmlExtensionModifierStream
public class XmlExtensionModifierStream : XmlTranslatorStream
{
public XmlExtensionModifierStream(Stream input)
: base(new XmlTextReader(input), Encoding.Default)
{
Debug.WriteLine("[BTS.Utilities.CustomPipelines.ExtensionModifier.XmlNamespaceModifierStream]Entered method");
}
protected override void TranslateXmlDeclaration(string target, string val)
{
base.TranslateXmlDeclaration(target, val);
this.m_writer.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\"");
}
protected override void TranslateStartElement(string prefix, string localName, string nsURI)
{
switch (localName)
{
case "fMCGTradeItemExtension":
base.TranslateStartElement("fmcg", localName, "urn:ean.ucc:align:fmcg:2");
break;
case "attributeValuePairExtension":
base.TranslateStartElement("gdsn", localName, "urn:ean.ucc:gdsn:2");
break;
default:
base.TranslateStartElement(prefix, localName, nsURI);
break;
}
}
protected override void TranslateStartAttribute(string prefix, string localName, string nsURI)
{
switch (localName)
{
case "schemaLocation":
base.TranslateStartAttribute("xsi", localName, "http://www.w3.org/2001/XMLSchema-instance");
break;
default:
base.TranslateStartAttribute(prefix, localName, nsURI);
break;
}
}
}
#endregion
Enjoy.
Like this:
Like Loading...