Click here to Skip to main content
12,445,004 members (52,703 online)
Click here to Skip to main content
Add your own
alternative version

Stats

88.9K views
32 bookmarked
Posted

Converting HL7 to XML

, 25 Sep 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
An initial stab at a class to convert HL7 to XML. Feel free to contribute.

Introduction

For those of us unfortunate enough to have to deal with it, HL7 is a commonly encountered language used by medical systems to communicate with each other. It is what someone dreamed up way back when before XML was invented. Indeed, the most recent version of HL7 is XML. However, for those of us that still have to use older systems, it's an unwieldy and unfriendly language to deal with; message components are delimited using carriage returns, pipe symbols, tildes, and ampersands.

Since most applications use XML for data exchange, and XML is much nicer to deal with anyway, it would be helpful if there were an HL7 to XML conversion library that was freely available. Sadly, despite scouring the web, I have not found a (free) class or utility that can easily be integrated into applications. There are a few Java libraries, and one well known (excellent) commercial application, but nothing free and easy to use.

This article is v1 of my attempt at creating such a library. Please feel free to use and extend it - and most importantly, fix any bugs I've missed.

Using the code

A very simple HL7 message looks something like this:

MSH|^~\&|||||20080925161613||ADT^A05||P|2.6

This class and method simply produces an XML representation of the same message. Note that this class isn't nearly clever enough to know what type of HL7 message it is converting - it merely creates an XML version of it. The point is that you can then use XPath to retrieve the segment you want to use since you know its location. The HL7 above returned by this method would look like this:

<HL7Message>
    <MSH>
        <MSH.0>MSH</MSH.0>
        <MSH.1>^~\&amp;</MSH.1>
        <MSH.2/>
        <MSH.3/>
        <MSH.4/>
        <MSH.5/>
        <MSH.6>20080925161613</MSH.6>
        <MSH.7/>
        <MSH.8>
            <MSH.8.0>ADT</MSH.8.0>
            <MSH.8.1>A05</MSH.8.1>
        </MSH.8>
        <MSH.9/>
        <MSH.10>P</MSH.10>
        <MSH.11>2.6</MSH.11>
    </MSH>
</HL7Message>

It's a static class which returns an XML string (as a string; it could easily be modified to return an XmlDocument instead).

Using the class

string sHL7asXml = HL7ToXmlConverter.ConvertToXml(myHL7string);

The full class looks like this:

/// This class takes an HL7 message
/// and transforms it into an XML representation.
public static class HL7ToXmlConverter
{
    // This is the XML document we'll be creating
    private static XmlDocument _xmlDoc;

    /// <span class="code-SummaryComment"><summary></span>
    /// Converts an HL7 message into an XML representation of the same message.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="sHL7">The HL7 to convert</param></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    public static string ConvertToXml(string sHL7)
    {
        // Go and create the base XML
        _xmlDoc = CreateXmlDoc();

        // HL7 message segments are terminated by carriage returns,
        // so to get an array of the message segments, split on carriage return
        string[] sHL7Lines = sHL7.Split('\r');

        // Now we want to replace any other unprintable control
        // characters with whitespace otherwise they'll break the XML
        for (int i = 0; i < sHL7Lines.Length; i++)
        {
            sHL7Lines[i] = Regex.Replace(sHL7Lines[i], @"[^ -~]", "");
        }

        /// Go through each segment in the message
        /// and first get the fields, separated by pipe (|),
        /// then for each of those, get the field components,
        /// separated by carat (^), and check for
        /// repetition (~) and also check each component
        /// for subcomponents, and repetition within them too.
        for (int i = 0; i < sHL7Lines.Length; i++)
        {
            // Don't care about empty lines
            if (sHL7Lines[i] != string.Empty)
            {
                // Get the line and get the line's segments
                string sHL7Line = sHL7Lines[i];
                string[] sFields = HL7ToXmlConverter.GetMessgeFields(sHL7Line);

                // Create a new element in the XML for the line
                XmlElement el = _xmlDoc.CreateElement(sFields[0]);
                _xmlDoc.DocumentElement.AppendChild(el);

                // For each field in the line of HL7
                for (int a = 0; a < sFields.Length; a++)
                {
                    // Create a new element
                    XmlElement fieldEl = _xmlDoc.CreateElement(sFields[0] + 
                                         "." + a.ToString());

                    /// Part of the HL7 specification is that part
                    /// of the message header defines which characters
                    /// are going to be used to delimit the message
                    /// and since we want to capture the field that
                    /// contains those characters we need
                    /// to just capture them and stick them in an element.
                    if (sFields[a] != @"^~\&")
                    {
                        /// Get the components within this field, separated by carats (^)
                        /// If there are more than one, go through and create an element for
                        /// each, then check for subcomponents, and repetition in both.
                        string[] sComponents = HL7ToXmlConverter.GetComponents(sFields[a]);
                        if (sComponents.Length > 1)
                        {
                            for (int b = 0; b < sComponents.Length; b++)
                            {
                                XmlElement componentEl = _xmlDoc.CreateElement(sFields[0] + 
                                           "." + a.ToString() + 
                                           "." + b.ToString());

                                string[] subComponents = GetSubComponents(sComponents[b]);
                                if (subComponents.Length > 1)
                                // There were subcomponents
                                {
                                    for (int c = 0; c < subComponents.Length; c++)
                                    {
                                        // Check for repetition
                                        string[] subComponentRepetitions = 
                                                 GetRepetitions(subComponents[c]);
                                        if (subComponentRepetitions.Length > 1)
                                        {
                                            for (int d = 0; 
                                                 d < subComponentRepetitions.Length; 
                                                 d++)
                                            {
                                                XmlElement subComponentRepEl = 
                                                  _xmlDoc.CreateElement(sFields[0] + 
                                                  "." + a.ToString() + 
                                                  "." + b.ToString() + 
                                                  "." + c.ToString() + 
                                                  "." + d.ToString());
                                                subComponentRepEl.InnerText = 
                                                     subComponentRepetitions[d];
                                                componentEl.AppendChild(subComponentRepEl);
                                            }
                                        }
                                        else
                                        {
                                            XmlElement subComponentEl = 
                                              _xmlDoc.CreateElement(sFields[0] + 
                                              "." + a.ToString() + "." + 
                                              b.ToString() + "." + c.ToString());
                                            subComponentEl.InnerText = subComponents[c];
                                            componentEl.AppendChild(subComponentEl);

                                        }
                                    }
                                    fieldEl.AppendChild(componentEl);
                                }
                                else // There were no subcomponents
                                {
                                    string[] sRepetitions = 
                                       HL7ToXmlConverter.GetRepetitions(sComponents[b]);
                                    if (sRepetitions.Length > 1)
                                    {
                                        XmlElement repetitionEl = null;
                                        for (int c = 0; c < sRepetitions.Length; c++)
                                        {
                                            repetitionEl = 
                                              _xmlDoc.CreateElement(sFields[0] + "." + 
                                              a.ToString() + "." + b.ToString() + 
                                              "." + c.ToString());
                                            repetitionEl.InnerText = sRepetitions[c];
                                            componentEl.AppendChild(repetitionEl);
                                        }
                                        fieldEl.AppendChild(componentEl);
                                        el.AppendChild(fieldEl);
                                    }
                                    else
                                    {
                                        componentEl.InnerText = sComponents[b];
                                        fieldEl.AppendChild(componentEl);
                                        el.AppendChild(fieldEl);
                                    }
                                }
                            }
                            el.AppendChild(fieldEl);
                        }
                        else
                        {
                            fieldEl.InnerText = sFields[a];
                            el.AppendChild(fieldEl);
                        }
                    }
                    else
                    {
                        fieldEl.InnerText = sFields[a];
                        el.AppendChild(fieldEl);
                    }
                }
            }
        }

        return _xmlDoc.OuterXml;
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Split a line into its component parts based on pipe.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="s"></param></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    private static string[] GetMessgeFields(string s)
    {
        return s.Split('|');
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Get the components of a string by splitting based on carat.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="s"></param></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    private static string[] GetComponents(string s)
    {
        return s.Split('^');
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Get the subcomponents of a string by splitting on ampersand.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="s"></param></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    private static string[] GetSubComponents(string s)
    {
        return s.Split('&');
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Get the repetitions within a string based on tilde.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="s"></param></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    private static string[] GetRepetitions(string s)
    {
        return s.Split('~');
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Create the basic XML document that represents the HL7 message
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    private static XmlDocument CreateXmlDoc()
    {
        XmlDocument output = new XmlDocument();
        XmlElement rootNode = output.CreateElement("HL7Message");
        output.AppendChild(rootNode);
        return output;
    }
}

Points of Interest

This is my first article on The Code Project. The new version of HL7 (which always should've been written in XML) will make this class redundant, but I hope this is useful to people in the meantime.

I hate HL7! Smile | :)

History

No history yet.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

T Clark
United Kingdom United Kingdom
No Biography provided

You may also be interested in...

Pro
Pro

Comments and Discussions

 
SuggestionModified this code to consider field repetitions. Pin
Shru3728-Jul-15 3:40
memberShru3728-Jul-15 3:40 
BugThis validator does not recognize repetition separator (~) Pin
Mengchao Yue18-Nov-14 10:23
memberMengchao Yue18-Nov-14 10:23 
QuestionGreat work! Pin
dexterbrock8-Jun-12 5:13
memberdexterbrock8-Jun-12 5:13 
NewsA more dynamic approach Pin
Member 315449723-Aug-10 3:54
memberMember 315449723-Aug-10 3:54 
QuestionHL7 Development Pin
ashoksudani23-Mar-10 2:22
memberashoksudani23-Mar-10 2:22 
Questionwhat is Regex.Replace Pin
Member 390784314-Mar-10 22:10
memberMember 390784314-Mar-10 22:10 
AnswerRe: what is Regex.Replace Pin
BryEllis27-Jun-13 17:53
memberBryEllis27-Jun-13 17:53 
General[My vote of 2] HL7 to XML Pin
bodywise25-Jul-09 5:55
memberbodywise25-Jul-09 5:55 
GeneralRe: [My vote of 2] HL7 to XML Pin
fizban646-Jun-11 7:03
memberfizban646-Jun-11 7:03 
GeneralGood first article Pin
Donsw26-May-09 16:22
memberDonsw26-May-09 16:22 
Generalplease keep these articles comming Pin
roa_f26-Nov-08 6:25
memberroa_f26-Nov-08 6:25 
GeneralHL7 progress Pin
David Rush29-Sep-08 8:51
memberDavid Rush29-Sep-08 8:51 
GeneralnHAPI Pin
ByteGhost25-Sep-08 17:34
memberByteGhost25-Sep-08 17:34 
GeneralRe: nHAPI Pin
T Clark25-Sep-08 22:21
memberT Clark25-Sep-08 22:21 
GeneralGood attempt with constructive criticism Pin
Mike Doyon25-Sep-08 10:21
memberMike Doyon25-Sep-08 10:21 
GeneralRe: Good attempt with constructive criticism Pin
T Clark25-Sep-08 11:30
memberT Clark25-Sep-08 11:30 
GeneralSimilar to EDI Pin
PIEBALDconsult25-Sep-08 7:07
memberPIEBALDconsult25-Sep-08 7:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160811.3 | Last Updated 25 Sep 2008
Article Copyright 2008 by T Clark
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid