Coverage Report - ca.uhn.hl7v2.preparser.ER7
 
Classes in this File Line Coverage Branch Coverage Complexity
ER7
85%
80/94
77%
28/36
4.167
ER7$ER7SegmentHandler
88%
22/25
85%
17/20
4.167
ER7$Handler
N/A
N/A
4.167
 
 1  
 package ca.uhn.hl7v2.preparser;
 2  
 
 3  
 import java.util.ArrayList;
 4  
 import java.util.Iterator;
 5  
 import java.util.List;
 6  
 import java.util.Map;
 7  
 import java.util.Properties;
 8  
 import java.util.SortedMap;
 9  
 import java.util.StringTokenizer;
 10  
 import java.util.TreeMap;
 11  
 
 12  
 import ca.uhn.hl7v2.parser.EncodingCharacters;
 13  
 
 14  
 /*
 15  
 The point of this class (all static members, not instantiatable) is to take a
 16  
 traditionally-encoded HL7 message and add all it's contents to a Properties
 17  
 object, via the parseMessage() method.
 18  
 
 19  
 The key-value pairs added to the Properties argument have keys that represent a
 20  
 datum's location in the message.  (in the ZYX-1-2[0] style.  TODO: define
 21  
 exactly.)  See Datum, particularly the toString() of that class.
 22  
 Anyway, the Properties keys are those and the values are the tokens found.
 23  
 
 24  
 Note: we accept useless field repetition separators at the end of a 
 25  
 field repetition sequence.  i.e. |855-4545~555-3792~~~| , and interpret this
 26  
 as definining repetitions 0 and 1.  This might not be allowed.  (HL7 2.3.1
 27  
 section 2.10 explicitly allows this behaviour for fields / components /
 28  
 subcomponents, but the allowance is notably absent for repetitions.  TODO:
 29  
 nail down.)  We allow it anyway.
 30  
 
 31  
 Also, we accept things like |855-4545~~555-3792|, and interpret it as defining
 32  
 repetitions 0 and 2.  The spec would seem to disallow this too, but there's no
 33  
 harm.  :D  
 34  
 */
 35  
 public class ER7 {
 36  
         
 37  0
         private ER7() {}
 38  
 
 39  
         /** characters that delimit segments.  for use with StringTokenizer.
 40  
         We are forgiving: HL7 2.3.1 section 2.7 says that carriage return ('\r') is
 41  
         the only segment delimiter.  TODO: check other versions. */ 
 42  
         static final String segmentSeparators = "\r\n\f";
 43  
 
 44  
         /** Parses message and dumps contents to props, with keys in the 
 45  
         ZYX[a]-b[c]-d-e style.
 46  
         */
 47  
         public static boolean parseMessage(/*out*/ Properties props, 
 48  
                 /*in*/ List<DatumPath> msgMask, /*in*/ String message)
 49  
         {
 50  385
                 boolean ok = false;
 51  385
                 if(message != null) {
 52  385
                         if(props == null)
 53  0
                                 props = new Properties();
 54  
 
 55  385
                         StringTokenizer messageTokenizer 
 56  
                                 = new StringTokenizer(message, segmentSeparators);
 57  385
                         if(messageTokenizer.hasMoreTokens()) {
 58  385
                                 String firstSegment = messageTokenizer.nextToken();
 59  385
                                 EncodingCharacters encodingChars = new EncodingCharacters('0', "0000");
 60  385
                                 if(parseMSHSegmentWhole(props, msgMask, encodingChars, firstSegment)) {
 61  380
                                         ok = true;
 62  380
                                         SortedMap<String, Integer> segmentId2nextRepIdx = new TreeMap<String, Integer>();
 63  380
                                         segmentId2nextRepIdx.put(new String("MSH"), 1); 
 64  
                                                 // in case we find another MSH segment, heh.
 65  810
                                         while(messageTokenizer.hasMoreTokens()) {
 66  860
                                                 parseSegmentWhole(props, segmentId2nextRepIdx, 
 67  430
                                                         msgMask, encodingChars, messageTokenizer.nextToken());
 68  
                                         }
 69  
                                 }
 70  
                         }
 71  
                 }
 72  385
                 return ok;
 73  
         }
 74  
         
 75  
         /** given segment, starting with "MSH", then encoding characters, etc...
 76  
         put MSH[0]-1[0]-1-1 (== MSH-1) and MSH[0]-2[0]-1-1 (== MSH-2) into props, if found,
 77  
         plus everything else found in 'segment' */
 78  
         protected static boolean parseMSHSegmentWhole(/*out*/ Properties props, 
 79  
                 /*in*/ List<DatumPath> msgMask, /*in*/ EncodingCharacters encodingChars, 
 80  
                 /*in*/ String segment) 
 81  
         {
 82  385
                 boolean ret = false;
 83  
                 try {
 84  385
                         ER7SegmentHandler handler = new ER7SegmentHandler();
 85  385
                         handler.m_props = props;
 86  385
                         handler.m_encodingChars = encodingChars;
 87  385
                         handler.m_segmentId = "MSH";
 88  385
                         handler.m_segmentRepIdx = 0;
 89  385
                         if(msgMask != null)
 90  385
                                 handler.m_msgMask = msgMask;
 91  
                         else {
 92  0
                                 handler.m_msgMask = new ArrayList<DatumPath>();
 93  0
                                 handler.m_msgMask.add(new DatumPath()); // everything will pass this
 94  
                                         // (every DatumPath startsWith the zero-length DatumPath)
 95  
                         }
 96  
 
 97  385
                         encodingChars.setFieldSeparator(segment.charAt(3));
 98  385
                         List<Integer> nodeKey = new ArrayList<Integer>();
 99  385
                         nodeKey.add(new Integer(0));
 100  385
                         handler.putDatum(nodeKey, String.valueOf(encodingChars.getFieldSeparator()));
 101  385
                         encodingChars.setComponentSeparator(segment.charAt(4));
 102  385
                         encodingChars.setRepetitionSeparator(segment.charAt(5));
 103  385
                         encodingChars.setEscapeCharacter(segment.charAt(6));
 104  385
                         encodingChars.setSubcomponentSeparator(segment.charAt(7));
 105  385
                         nodeKey.set(0, new Integer(1));
 106  385
                         handler.putDatum(nodeKey, encodingChars.toString());
 107  
 
 108  385
                         if(segment.charAt(8) == encodingChars.getFieldSeparator()) {        
 109  380
                                 ret = true; 
 110  
                                 // now -- we recurse 
 111  
                                 // through fields / field-repetitions / components / subcomponents.
 112  380
                                 nodeKey.clear();
 113  380
                                 nodeKey.add(new Integer(2));
 114  380
                                 parseSegmentGuts(handler, segment.substring(9), nodeKey);
 115  
                         }
 116  
                 }
 117  0
                 catch(IndexOutOfBoundsException e) {}
 118  385
                 catch(NullPointerException e) {}
 119  
 
 120  385
                 return ret;
 121  
         }
 122  
 
 123  
         /** pass in a whole segment (of type other than MSH), including message type
 124  
         at the start, according to encodingChars, and we'll parse the contents and
 125  
         put them in props. */
 126  
         protected static void parseSegmentWhole(/*out*/ Properties props, 
 127  
                 /*in/out*/ Map<String, Integer> segmentId2nextRepIdx, 
 128  
                 /*in*/ List<DatumPath> msgMask, /*in*/ EncodingCharacters encodingChars, 
 129  
                 /*in*/ String segment)
 130  
         {
 131  
                 try {
 132  430
                         String segmentId = segment.substring(0, 3);
 133  
 
 134  430
                         int currentSegmentRepIdx = 0;
 135  430
                         if(segmentId2nextRepIdx.containsKey(segmentId))
 136  40
                                 currentSegmentRepIdx = ((Integer)segmentId2nextRepIdx.get(segmentId)).intValue();
 137  
                         else
 138  390
                                 currentSegmentRepIdx = 0;
 139  430
                         segmentId2nextRepIdx.put(segmentId, new Integer(currentSegmentRepIdx+1));
 140  
 
 141  
                         // will only bother to parse this segment if any of it's contents will 
 142  
                         // be dumped to props.
 143  430
                         boolean parseThisSegment = false;
 144  430
                         DatumPath segmentIdAsDatumPath = new DatumPath().add(segmentId);
 145  430
                         for(Iterator<DatumPath> maskIt = msgMask.iterator(); !parseThisSegment && maskIt.hasNext(); ) 
 146  685
                                 parseThisSegment = segmentIdAsDatumPath.startsWith(maskIt.next());
 147  430
                         for(Iterator<DatumPath> maskIt = msgMask.iterator(); !parseThisSegment && maskIt.hasNext(); ) 
 148  645
                                 parseThisSegment = maskIt.next().startsWith(segmentIdAsDatumPath);
 149  
 
 150  430
                         if(parseThisSegment && (segment.charAt(3) == encodingChars.getFieldSeparator())) {
 151  160
                                 ER7SegmentHandler handler = new ER7SegmentHandler();
 152  160
                                 handler.m_props = props;
 153  160
                                 handler.m_encodingChars = encodingChars;
 154  160
                                 handler.m_segmentId = segmentId;
 155  160
                                 handler.m_msgMask = msgMask;
 156  160
                                 handler.m_segmentRepIdx = currentSegmentRepIdx;
 157  
 
 158  160
                                 List<Integer> nodeKey = new ArrayList<Integer>();
 159  160
                                 nodeKey.add(new Integer(0));
 160  160
                                 parseSegmentGuts(handler, segment.substring(4), nodeKey);
 161  
                         }
 162  
                 }
 163  0
                 catch(NullPointerException e) {}
 164  430
                 catch(IndexOutOfBoundsException e) {}
 165  430
         }
 166  
 
 167  
         static protected interface Handler
 168  
         {
 169  
                 public int specDepth();
 170  
                 public char delim(int level);
 171  
 
 172  
                 public void putDatum(List<Integer> nodeKey, String value);
 173  
         }
 174  
 
 175  545
         static protected class ER7SegmentHandler implements Handler
 176  
         {
 177  
                 Properties m_props;
 178  
 
 179  
                 EncodingCharacters m_encodingChars;
 180  
 
 181  
                 String m_segmentId;
 182  
                 int m_segmentRepIdx;
 183  
 
 184  
                 List<DatumPath> m_msgMask;
 185  
 
 186  15240
                 public int specDepth() {return 4;}
 187  
 
 188  
                 public char delim(int level)
 189  
                 {
 190  11400
                         if(level == 0)
 191  540
                                 return m_encodingChars.getFieldSeparator();
 192  10860
                         else if(level == 1)
 193  3230
                                 return m_encodingChars.getRepetitionSeparator();
 194  7630
                         else if(level == 2)
 195  3280
                                 return m_encodingChars.getComponentSeparator();
 196  4350
                         else if(level == 3)
 197  4350
                                 return m_encodingChars.getSubcomponentSeparator();
 198  0
             else if(level == 4)
 199  0
                 return m_encodingChars.getTruncationCharacter();
 200  
                         else
 201  0
                                 throw new java.lang.Error();
 202  
                 }
 203  
 
 204  
                 public void putDatum(List<Integer> valNodeKey, String value)
 205  
                 {
 206  
                         // make a DatumPath from valNodeKey and info in this: 
 207  5150
                         DatumPath valDatumPath = new DatumPath();
 208  5150
                         valDatumPath.add(m_segmentId).add(m_segmentRepIdx);
 209  23440
                         for(int i=0; i<valNodeKey.size(); ++i) {
 210  
                                 // valNodeKey: everything counts from 0 -- not so with DatumPath ... sigh. 
 211  18290
                                 int itval = ((Integer)valNodeKey.get(i)).intValue();
 212  18290
                                 valDatumPath.add(new Integer(i == 1 ? itval : itval+1));
 213  
                         }
 214  
 
 215  
                         // see if valDatumPath passes m_msgMask: 
 216  5150
                         boolean valDatumPathPassesMask = false;
 217  5150
                         for(Iterator<DatumPath> maskIt = m_msgMask.iterator(); 
 218  13250
                                 !valDatumPathPassesMask && maskIt.hasNext(); )
 219  
                         {
 220  8100
                                 valDatumPathPassesMask = valDatumPath.startsWith(maskIt.next());
 221  
                         }
 222  
 
 223  5150
                         if(valDatumPathPassesMask)
 224  330
                                 m_props.setProperty(valDatumPath.toString(), value);
 225  5150
                 }
 226  
         }
 227  
 
 228  
         /** recursively tokenize "guts" (a segment, or part of one) into tokens, 
 229  
         according to separators (aka delimiters) which are different at each level
 230  
         of recursion, and to a recursive depth which is discovered through "handler"
 231  
         via handler.delim(int) and handler.specDepth()  As tokens are found, they
 232  
         are reported to handler via handler.putDatum(), which presumably stashes them
 233  
         away somewhere.  We tell the handler about the location in the message via
 234  
         putDatum()'s key argument, which is a List of Integers representing the 
 235  
         position in the parse tree (size() == depth of recursion).
 236  
 
 237  
         TODO: say more.
 238  
         */
 239  
         protected static void parseSegmentGuts(/*in/out*/ Handler handler,  
 240  
                 /*in*/ String guts, /*in*/List<Integer> nodeKey)
 241  
         {
 242  11400
                 char thisDepthsDelim = handler.delim(nodeKey.size()-1);
 243  
                 //nodeKey.add(new Integer(0)); // will change nodeKey back before function exits
 244  
 
 245  11400
                 StringTokenizer gutsTokenizer 
 246  11400
                         = new StringTokenizer(guts, String.valueOf(thisDepthsDelim), true);
 247  34560
                 while(gutsTokenizer.hasMoreTokens()) {
 248  23160
                         String gutsToken = gutsTokenizer.nextToken();
 249  
 
 250  23160
                         if(gutsToken.charAt(0) == thisDepthsDelim) {
 251  
                                 // gutsToken is all delims -- skipping over as many fields or
 252  
                                 // components or whatevers as there are characters in the token: 
 253  7920
                                 int oldvalue = ((Integer)nodeKey.get(nodeKey.size()-1)).intValue();
 254  7920
                                 nodeKey.set(nodeKey.size()-1, new Integer(oldvalue + gutsToken.length()));
 255  7920
                         }
 256  
                         else {
 257  15240
                                 if(nodeKey.size() < handler.specDepth()) {
 258  10860
                                         nodeKey.add(new Integer(0));
 259  10860
                                         parseSegmentGuts(handler, gutsToken, nodeKey);
 260  10860
                                         nodeKey.remove(nodeKey.size()-1);
 261  
                                 }
 262  
                                 else 
 263  4380
                                         handler.putDatum(nodeKey, gutsToken);
 264  
                         }
 265  23160
                 }
 266  
                 //nodeKey.setSize(nodeKey.size()-1); // undoing add done at top of this func
 267  11400
         }
 268  
 
 269  
         public static void main(String args[])
 270  
         {
 271  0
                 if(args.length >= 1) {
 272  
                         //String message = "MSH|^~\\&||||foo|foo|foo";
 273  0
                         System.out.println(args[0]);
 274  
 
 275  0
                         Properties props = new Properties();
 276  
 
 277  0
                         List<DatumPath> msgMask = new ArrayList<DatumPath>();
 278  0
                         msgMask.add(new DatumPath());
 279  
 
 280  0
                         System.err.println("ER7.parseMessage returned " + parseMessage(props, msgMask, args[0]));
 281  0
                         props.list(System.out);
 282  
                 }
 283  0
         }
 284  
         
 285  
 }
 286