Hl7 Accelerator and xslt.

Actual Problem :

HL7 file coming out of HL7 Accelerator is having empty new line character in-between segments.

Segment looked like

ORC|RE|
^XXX|123454^^12234^L||CM||^^^^20120823120000||20120823120000|||1104869304^Doctor^hello^J^^^^^^^^^

 Cause for this problem.

The XML file that was fed into HL7 Accelerator had as new line character in those elements

<ORC_2_PlacerOrderNumber><EI_0_EntityIdentifier> {cr}{lf}

</EI_0_EntityIdentifier>

</ORC_2_PlacerOrderNumber>

Solution :

Pass the XML through xslt transformation before sending it to HL7 accelerator

Here is the code that does the transformation.

 public static String  xslRemoveEmptyNodes(String xdoc)
        {
            string strXmlOutput = xdoc;
            try
            {
                XmlDocument xDoc = new XmlDocument();
                xDoc.LoadXml(xdoc);
                string xslstring = @"<xsl:stylesheet version=’1.0′ xmlns:xsl=’http://www.w3.org/1999/XSL/Transform’&gt;
                        <xsl:output method=’xml’ omit-xml-declaration=’yes’ indent=’yes’/>                         
                        <xsl:template match=""node()|@*"">
                          <xsl:copy>
                           <xsl:apply-templates select=""node()|@*""/>
                          </xsl:copy>
                         </xsl:template>
                         <xsl:template match=""*[not(@*) and not(*) and not(text()) ]""/>
                        </xsl:stylesheet>";

                Stream xslOutStream = new MemoryStream();
                StringWriter writer = new StringWriter();
                using (XmlReader reader = XmlReader.Create(new StringReader(xslstring)))
                {
                    XslCompiledTransform myXslTransform;
                    myXslTransform = new XslCompiledTransform();
                    myXslTransform.Load(reader);
                    myXslTransform.Transform(xDoc, null, writer);

}
                strXmlOutput = writer.ToString();
                
                int index = strXmlOutput.IndexOf("<ns1:") < 0 ? 0 : strXmlOutput.IndexOf("<ns1:");
                strXmlOutput = strXmlOutput.Substring(index);

}


}

Errors encountered during the process

Alternate Error Number: 301

Alternate Error Description: XmlReader not positioned at root elment of ‘ORU_R01_24_GLO_DEF’

Alternate Encoding System: HL7-BTA

Fix for this Error

Add a line <xsl:output method=’xml’ omit-xml-declaration=’yes’ indent=’yes’/>  in your xslt transformation.

 

Advertisements

LINQ to XML Challenge 1

Recently I came across with the unique requirement to do the extravagant changes on the incoming Hl7 data in XML document. At first, I handed it over to my offshore subordinate and explained the logic. After couple of days, when I started reviewing the code. I could clearly realize, it was not meant to be released to production. For that matter, not even to test. Lots of XML document, XMLNodes..etc. Overall the code looked old school lacking performance and scalability.

So I decided to write it myself.

Requirement : We get the ORU HL7 message in XML format with only one ORC and OBR and had to split it into multiple ORC and OBR. Also associate the corresponding OBX segments with the correct ORC and OBR, These associations would be taken form the SQL table.

public static void FormatSeperateORCOBR(ref XmlDocument doc, string trackingNo, string hl7Id)
        {
            try
            {
                XElement xe;
                xe = XElement.Parse(doc.OuterXml);

                //Get the data from table
                DataSet dsResultSet = new DataSet();
                dsResultSet = DAL.DataAccess.GetTestResultAssociation();
                DataTable dtTestResults = dsResultSet.Tables[0];

                Dictionary<string, List<int>> testResultDictionary = new Dictionary<string, List<int>>();

                //Get the unique test codes from the datatable
                var testcodes = from t in dtTestResults.AsEnumerable()
                                group t by t.Field<int>("iTestCode") into testcode
                                select testcode;

                //populate dictionary with Test and results association
                foreach (var testcode in testcodes)
                {
                    string testId = testcode.Key.ToString();

                    var results = from r in dtTestResults.AsEnumerable()
                                  where r.Field<int>("iTestCode") == testcode.Key
                                  select r.Field<int>("iResultCode");
                    testResultDictionary.Add(testId, results.ToList());         //List of result codes in the dictionary.
                }

                StringBuilder strObservation = new System.Text.StringBuilder();

                // Create ORC and OBR Templates
                string strORCTemplate = xe.XPathSelectElement("//Observation/ORC_CommonOrder").ToString();
                string strOBRTemplate = xe.XPathSelectElement("//Observation/OBR_ObservationRequest").ToString();

                //create OBX with PDF and OBX without PDF.
                string strOBXwithPDF = xe.XPathSelectElements("//ObservationResults").
                                        Where(item => item.XPathSelectElement("OBX_ObservationResult/OBX_2_ValueType").Value == "ED")
                                        .FirstOrDefault().ToString();

                //Take the OBX with PDF and replace the elements using below statment to get OBXwithoutPDF
                XElement xeOBXwithPDF = XElement.Parse(strOBXwithPDF);
                xeOBXwithPDF.XPathSelectElements("//OBX_ObservationResult").ToList().
                                    ForEach(item =>
                                    {
                                        item.XPathSelectElement("OBX_5_ObservationValue").Value = "$FileName$";
                                        item.XPathSelectElement("OBX_2_ValueType").Value = "ST";
                                        item.XPathSelectElement("OBX_3_ObservationIdentifier/CE_0_Identifier").Value = "IR";
                                        item.XPathSelectElement("OBX_3_ObservationIdentifier/CE_1_Text").Value = "IMAGE";
                                    }
                                            );
                string strOBXwithoutPDF = xeOBXwithPDF.ToString();

                XElement xeOBR;
                string strtestName;

                var last = testResultDictionary.Last();
                foreach (var item in testResultDictionary)
                {

                    //1. Get the Templates and modify them accordingly
                    xeOBR = XElement.Parse(strOBRTemplate);
                    xeOBR.XPathSelectElement("OBR_4_UniversalServiceIdentifier/CE_0_Identifier").SetValue(item.Key);  //item.Key holds the test code.

                    strtestName = dtTestResults.Select("iTestCode=" + item.Key).FirstOrDefault().Field<string>("vchTestName");
                    xeOBR.XPathSelectElement("OBR_4_UniversalServiceIdentifier/CE_1_Text").SetValue(strtestName);

                    //2. Append the template to sb.
                    strObservation.Append("<Observation xmlns=\"\">" + strORCTemplate);
                    strObservation.Append(xeOBR.ToString());

                    //3. Get the Results from the XML belonging to Test code into the List.
                    string testcode = item.Key;
                    var query = from o in xe.XPathSelectElements("//ObservationResults")
                                from resultcode in item.Value  //item.Value is List of ResultCodes
                                where o.XPathSelectElement("OBX_ObservationResult/OBX_3_ObservationIdentifier/CE_0_Identifier").Value == resultcode.ToString()
                                select o;
                    //4. Join the list to get the bunch of ObservationResults beloging to Test code in context
                    strObservation.Append(String.Join("", query));

                    //5. Append the OBX without out PDF to all of them expect the last one.
                    if (item.Equals(last))
                        strObservation.Append(strOBXwithPDF);
                    else
                        strObservation.Append(strOBXwithoutPDF);

                    strObservation.Append("</Observation>");
                }

                //6. Replace the old Observation with new Observations.
                XElement xeObservations = XElement.Parse("<Root>" + strObservation.ToString() + "</Root>");     //Adding root to make the XElement parse the string.

                //7. Update OBR Index Number
                int iOBRCounter = 1;
                foreach (var observation in xeObservations.XPathSelectElements("//Observation"))
                {

                    observation.XPathSelectElement("OBR_ObservationRequest/OBR_1_SetIdObr").SetValue(iOBRCounter);
                    iOBRCounter++;
                }

                //8. Replace the Observations
                xe.XPathSelectElement("//Observation").ReplaceWith(xeObservations);

                // 9. Remove the dummy root element
                string strFinalXml = xe.ToString().Replace(@"<Root xmlns="""">", "").Replace("</Root>", "");

                //Convert to xmlDocument
                //XmlDocument xDoc = new System.Xml.XmlDocument();
                //xDoc.LoadXml(xe.ToString().Replace(@"<Root xmlns="""">", "").Replace("</Root>", ""));

                doc = new System.Xml.XmlDocument();
                doc.LoadXml(strFinalXml);
                //11. Update OBX index number
                UpdateOBXIndexNumber(doc, 1);
            }   
            catch (Exception ex)
            {
                Ameritox.BTIS.Core.Components.Log.Info(trackingNo, hl7Id, "HL7Transform.cs :: Error occured in FormatSeperateORCOBR method\r\n" + ex.ToString(), "General", null);
                throw ex;
            }

        }

Encoding.GetEncoding(0)

One of the partners we had integrated with wanted us to send the extended characters in the output file.

Those characters would look something like this  »    «

It was a easy change for us in code, so we made, deployed to production as well.. Later we realized that a different character shows along with the ones intended to show.

The file had an output as   «   instead of just «.

To add more to the perplexity, This character «  would not show up when the file is opened in text editors like notepad or notepad++, but shows up in other editors like “7-Edit”. So at first we ignored considering that problem is with the editor.

Later we heard back from the client with the same issue and they wanted us to fix it.

On deep diving, we realized that, in-fact it was an issue in one of our pipeline component, where we converted the output message to UTF8 encoding using the code below

byteArray = Encoding.UTF8.GetBytes(strMessage);

pInMsg.BodyPart.Data = new MemoryStream(byteArray);

But instead it should be left to default encoding.

byteArray = Encoding.GetEncoding(0).GetBytes(strMessage);

pInMsg.BodyPart.Data = new MemoryStream(byteArray);

So once the code was changed, the issue got fixed and made our client happySmile

Articles referred :-

http://www.joelonsoftware.com/articles/Unicode.html

http://msdn.microsoft.com/en-us/library/wzsz3bk3(v=vs.80).aspx

Schedule a Task to pull files from sFTP Server

Files from sFTP server can be downloaded to the local system easily using the free tool such as winscp.

1. Download WinSCP From here

WinSCP :: Download

2. Install WinScp to the client machine

3. Copy the below script and paste in a text file called “sFTP_Script.txt” and place it in C:\sFTPPull folder you wish

# Automatically abort script on errors
option batch abort
# Disable overwrite confirmations that conflict with the previous
option confirm off
# Connect using a password
# open sftp://user:password@example.com -hostkey="ssh-rsa 1024 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"
# Connect
open sftp://sFTPTest:Fh2EsZwi@sFTPServer.MyCompany.com -hostkey="ssh-rsa 1024 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"
# Change remote directory
cd /
# Force binary mode transfer
option transfer binary
# Download file to the local directory d:\
get *.txt c:\
# Disconnect
close

4. Either you can download file using command line or by scheduling a task in Windows Task Scheduler.

5. Command line is :-

C:\ProgramFiles\WinSCP\WinSCP.exe /console  /script="C:\sFTPPull \sFTP_ Script.txt"

6. The File would be written to C:\ drive, but it can be changed to the destination folder you wish by editing the “get *.txt C:\ “ line.

Articles Referred :-

http://winscp.net/eng/docs/scripting#hostkey

 

Serializing & Deserializing a object

Convert Object to String

Class c1 = new Class();
c1.Name = "Chemistry";
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Class));
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, c1);
string serializedXML = stringWriter.ToString();

Convert Object to XMLDocument

            Class c1 = new Class();
            c1.Name = "Chemistry";
 
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(Class));
            MemoryStream memStream = new MemoryStream();
            StreamWriter streamWriter = new StreamWriter(memStream);
            xmlSerializer.Serialize(streamWriter, c1);
            memStream.Position = 0;
            StreamReader streamReader = new StreamReader(memStream);
            XmlDocument serializedXML = new XmlDocument();
            serializedXML.Load(streamReader);

Convert String to Object

Class c1 = new Class();
XmlSerializer xmlSerializer = new XmlSerializer(c1.GetType());
StringReader stringReader = new StringReader(strValue);
c1= xmlSerializer.Deserialize(stringReader);

Making an Asyncronous call to WebService

Server Side Code

[WebMethod]

public String HelloWorld(string name)

{ return "Hello " + name; }

Client Side Code

public void TestHelloWorld_Async()

{

MyWebService.Service client = new MyWebService.Service(); client.HelloWorldCompleted += new HelloWorldCompletedEventHandler(this.ProcessAsyncCompleted);

client.HelloWorldAsync(“James Bond”);

}

private void ProcessAsyncCompleted(object sender, AuthenticateSenderCompletedEventArgs args)

{ object i = args.Result; }

The Control would come to ProcessAsyncCompleted after the execution of the webmethod.

 

Refer :-

http://weblogs.asp.net/gunnarpeipman/archive/2010/09/07/making-asynchronous-calls-to-web-services-during-asp-net-page-processing.aspx