Coverage Report - ca.uhn.hl7v2.parser.DefaultXMLParser
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultXMLParser
69%
102/147
82%
48/58
3.714
 
 1  
 /**
 2  
 The contents of this file are subject to the Mozilla Public License Version 1.1
 3  
 (the "License"); you may not use this file except in compliance with the License.
 4  
 You may obtain a copy of the License at http://www.mozilla.org/MPL/
 5  
 Software distributed under the License is distributed on an "AS IS" basis,
 6  
 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
 7  
 specific language governing rights and limitations under the License.
 8  
 
 9  
 The Initial Developer of the Original Code is University Health Network. Copyright (C)
 10  
 2001.  All Rights Reserved.
 11  
 
 12  
 Contributor(s): ______________________________________.
 13  
 
 14  
 Alternatively, the contents of this file may be used under the terms of the
 15  
 GNU General Public License (the  �GPL�), in which case the provisions of the GPL are
 16  
 applicable instead of those above.  If you wish to allow use of your version of this
 17  
 file only under the terms of the GPL and not to allow others to use your version
 18  
 of this file under the MPL, indicate your decision by deleting  the provisions above
 19  
 and replace  them with the notice and other provisions required by the GPL License.
 20  
 If you do not delete the provisions above, a recipient may use your version of
 21  
 this file under either the MPL or the GPL.
 22  
 
 23  
 */
 24  
 package ca.uhn.hl7v2.parser;
 25  
 
 26  
 import java.io.File;
 27  
 import java.io.FileReader;
 28  
 import java.util.ArrayList;
 29  
 import java.util.HashSet;
 30  
 import java.util.List;
 31  
 import java.util.Set;
 32  
 
 33  
 import ca.uhn.hl7v2.DefaultHapiContext;
 34  
 import org.slf4j.Logger;
 35  
 import org.slf4j.LoggerFactory;
 36  
 import org.w3c.dom.DOMException;
 37  
 import org.w3c.dom.Document;
 38  
 import org.w3c.dom.Element;
 39  
 import org.w3c.dom.Node;
 40  
 import org.w3c.dom.NodeList;
 41  
 
 42  
 import ca.uhn.hl7v2.HL7Exception;
 43  
 import ca.uhn.hl7v2.HapiContext;
 44  
 import ca.uhn.hl7v2.model.Group;
 45  
 import ca.uhn.hl7v2.model.Message;
 46  
 import ca.uhn.hl7v2.model.Segment;
 47  
 import ca.uhn.hl7v2.model.Structure;
 48  
 import ca.uhn.hl7v2.util.XMLUtils;
 49  
 import ca.uhn.hl7v2.validation.impl.NoValidation;
 50  
 import ca.uhn.hl7v2.validation.impl.ValidationContextFactory;
 51  
 
 52  
 /**
 53  
  * <p>A default XMLParser.  This class assigns segment elements (in an XML-encoded message) 
 54  
  * to Segment objects (in a Message object) using the name of a segment and the names 
 55  
  * of any groups in which the segment is nested.  The names of group classes must correspond
 56  
  * to the names of group elements (they must be identical except that a dot in the element 
 57  
  * name, following the message name, is replaced with an underscore, in order to consitute a 
 58  
  * valid class name). </p>
 59  
  * <p>At the time of writing, the group names in the XML spec are changing.  Many of the group 
 60  
  * names have been automatically generated based on the group contents.  However, these automatic 
 61  
  * names are gradually being replaced with manually assigned names.  This process is expected to 
 62  
  * be complete by November 2002.  As a result, mismatches are likely.  Messages could be  
 63  
  * transformed prior to parsing (using XSLT) as a work-around.  Alternatively the group class names 
 64  
  * could be changed to reflect updates in the XML spec.  Ultimately, HAPI group classes will be 
 65  
  * changed to correspond with the official group names, once these are all assigned.  </p>
 66  
  * 
 67  
  * @see ParserConfiguration for configuration options which may affect parser encoding and decoding behaviour
 68  
  * @author Bryan Tripp
 69  
  */
 70  
 public class DefaultXMLParser extends XMLParser {
 71  
 
 72  5
     private static final Logger log = LoggerFactory.getLogger(DefaultXMLParser.class);
 73  
 
 74  
     private static final Set<String> ourForceGroupNames;
 75  
     
 76  
     static {
 77  5
             ourForceGroupNames = new HashSet<String>();
 78  5
             ourForceGroupNames.add("DIET");
 79  5
     }
 80  
     
 81  
     public DefaultXMLParser() {
 82  50
             super();
 83  50
     }
 84  
     
 85  
     public DefaultXMLParser(HapiContext context) {
 86  435
                 super(context);
 87  435
         }
 88  
 
 89  
         /** 
 90  
      * Creates a new instance of DefaultXMLParser 
 91  
      *  
 92  
      * @param theFactory custom factory to use for model class lookup 
 93  
      */
 94  
     public DefaultXMLParser(ModelClassFactory theFactory) {
 95  0
             super(theFactory);
 96  0
     }
 97  
     
 98  
     /**
 99  
      * <p>Creates an XML Document that corresponds to the given Message object. </p>
 100  
      * <p>If you are implementing this method, you should create an XML Document, and insert XML Elements
 101  
      * into it that correspond to the groups and segments that belong to the message type that your subclass
 102  
      * of XMLParser supports.  Then, for each segment in the message, call the method
 103  
      * <code>encode(Segment segmentObject, Element segmentElement)</code> using the Element for
 104  
      * that segment and the corresponding Segment object from the given Message.</p>
 105  
      */
 106  
     public Document encodeDocument(Message source) throws HL7Exception {
 107  125
         String messageClassName = source.getClass().getName();
 108  125
         String messageName = messageClassName.substring(messageClassName.lastIndexOf('.') + 1);
 109  
         try {
 110  125
             Document doc = XMLUtils.emptyDocument(messageName);
 111  
             //Element root = doc.createElement(messageName);
 112  
             //doc.appendChild(root);
 113  125
             encode(source, doc.getDocumentElement());
 114  125
             return doc;
 115  0
         } catch (Exception e) {
 116  0
             throw new HL7Exception(
 117  0
                 "Can't create XML document - " + e.getClass().getName(), e);
 118  
         }
 119  
     }
 120  
 
 121  
     /**
 122  
      * Copies data from a group object into the corresponding group element, creating any 
 123  
      * necessary child nodes.  
 124  
      */
 125  
     private void encode(Group groupObject, Element groupElement) throws HL7Exception {
 126  265
         String[] childNames = groupObject.getNames();
 127  265
         String messageName = groupObject.getMessage().getName();
 128  
         
 129  
         try {
 130  2725
                 for (String name : childNames) {
 131  2460
                 Structure[] reps = groupObject.getAll(name);
 132  2995
                 for (Structure rep : reps) {
 133  535
                     String elementName = makeGroupElementName(messageName, name);
 134  
                                         Element childElement;
 135  
                                         try {
 136  535
                                                 childElement = groupElement.getOwnerDocument().createElement(elementName);
 137  0
                                 } catch (DOMException e) {
 138  0
                                     throw new HL7Exception(
 139  0
                                         "Can't encode element " + elementName + " in group " + groupObject.getClass().getName(), e);
 140  535
                                 }
 141  535
                     groupElement.appendChild(childElement);
 142  535
                     if (rep instanceof Group) {
 143  140
                         encode((Group) rep, childElement);
 144  
                     }
 145  395
                     else if (rep instanceof Segment) {
 146  395
                         encode((Segment) rep, childElement);
 147  
                     }
 148  
                                 }
 149  
             }
 150  0
         } catch (DOMException e) {
 151  0
             throw new HL7Exception(
 152  0
                 "Can't encode group " + groupObject.getClass().getName(), e);
 153  265
         }
 154  265
     }
 155  
 
 156  
 
 157  
     /**
 158  
      * <p>Creates and populates a Message object from an XML Document that contains an XML-encoded HL7 message.</p>
 159  
      * <p>The easiest way to implement this method for a particular message structure is as follows:
 160  
      * <ol><li>Create an instance of the Message type you are going to handle with your subclass
 161  
      * of XMLParser</li>
 162  
      * <li>Go through the given Document and find the Elements that represent the top level of
 163  
      * each message segment. </li>
 164  
      * <li>For each of these segments, call <code>parse(Segment segmentObject, Element segmentElement)</code>,
 165  
      * providing the appropriate Segment from your Message object, and the corresponding Element.</li></ol>
 166  
      * At the end of this process, your Message object should be populated with data from the XML
 167  
      * Document.</p>
 168  
      * @throws HL7Exception if the message is not correctly formatted.
 169  
      * @throws EncodingNotSupportedException if the message encoded
 170  
      *     is not supported by this parser.
 171  
      */
 172  
     public Message parseDocument(Document xmlMessage, String version) throws HL7Exception {
 173  
 
 174  55
         assertNamespaceURI(xmlMessage.getDocumentElement().getNamespaceURI());
 175  
 
 176  55
         Message message = instantiateMessage(xmlMessage.getDocumentElement().getLocalName(), version, true);
 177  
             // Note: this will change in future to reuse the Parser's/HapiContext's
 178  
             // ValidationContext.
 179  
         // message.setValidationContext(getValidationContext());
 180  55
         parse(message, xmlMessage.getDocumentElement());
 181  55
         return message;
 182  
     }
 183  
 
 184  
     /**
 185  
      * Populates the given group object with data from the given group element, ignoring 
 186  
      * any unrecognized nodes.  
 187  
      */
 188  
     private void parse(Group groupObject, Element groupElement) throws HL7Exception {
 189  150
         String[] childNames = groupObject.getNames();
 190  150
         String messageName = groupObject.getMessage().getName();
 191  
         
 192  150
         NodeList allChildNodes = groupElement.getChildNodes();
 193  150
         List<String> unparsedElementList = new ArrayList<String>();
 194  920
         for (int i = 0; i < allChildNodes.getLength(); i++) {
 195  770
             Node node = allChildNodes.item(i);
 196  770
             String name = node.getLocalName();
 197  770
             if (node.getNodeType() == Node.ELEMENT_NODE && !unparsedElementList.contains(name)) {
 198  280
                 assertNamespaceURI(node.getNamespaceURI());
 199  280
                 unparsedElementList.add(name);                
 200  
             }
 201  
         }
 202  
         
 203  
         //we're not too fussy about order here (all occurrences get parsed as repetitions) ... 
 204  1290
         for (String nextChildName : childNames) {
 205  1140
             String childName = nextChildName;
 206  1140
             if(groupObject.isGroup(nextChildName)) {
 207  205
                     childName = makeGroupElementName(groupObject.getMessage().getName(), nextChildName);
 208  
             }
 209  1140
                         unparsedElementList.remove(childName);
 210  
             
 211  
             // 4 char segment names are second occurrences of a segment within a single message
 212  
             // structure. e.g. the second PID segment in an A17 patient swap message is known
 213  
             // to hapi's code represenation as PID2
 214  1140
             if (nextChildName.length() == 4 && Character.isDigit(nextChildName.charAt(3))) {
 215  115
                     log.trace("Skipping rep segment: {}", nextChildName);
 216  
             } else {   
 217  1025
                     parseReps(groupElement, groupObject, messageName, nextChildName, nextChildName);
 218  
             }
 219  
         }
 220  
         
 221  150
         for (String segName : unparsedElementList) {
 222  5
             String segIndexName = groupObject.addNonstandardSegment(segName);
 223  5
             parseReps(groupElement, groupObject, messageName, segName, segIndexName);
 224  5
         }
 225  150
     }
 226  
     
 227  
     //param childIndexName may have an integer on the end if >1 sibling with same name (e.g. NTE2) 
 228  
     private void parseReps(Element groupElement, Group groupObject, 
 229  
             String messageName, String childName, String childIndexName) throws HL7Exception {
 230  
         
 231  1030
             String groupName = makeGroupElementName(messageName, childName);
 232  1030
         List<Element> reps = getChildElementsByTagName(groupElement, groupName);
 233  1030
         log.trace("# of elements matching {}: {}", groupName, reps.size());
 234  
 
 235  1030
                 if (groupObject.isRepeating(childIndexName)) {
 236  590
                         for (int i = 0; i < reps.size(); i++) {
 237  95
                                 parseRep(reps.get(i), groupObject.get(childIndexName, i));
 238  
                         }                                        
 239  
                 } else {
 240  535
                         if (reps.size() > 0) {
 241  205
                                 parseRep(reps.get(0), groupObject.get(childIndexName, 0));                                
 242  
                         }
 243  
 
 244  
 //                        if (reps.size() > 1) {                        
 245  
 //                                String newIndexName = groupObject.addNonstandardSegment(childName);                        
 246  
 //                                for (int i = 1; i < reps.size(); i++) {
 247  
 //                                        parseRep((Element) reps.get(i), groupObject.get(newIndexName, i-1));
 248  
 //                                }                                                                
 249  
 //                        }
 250  535
                         if (reps.size() > 1) {
 251  
                                 String newIndexName;
 252  10
                                 int i=1;
 253  
                                 try        {
 254  20
                                         for (i = 1; i < reps.size(); i++) {
 255  10
                                                 newIndexName = childName+(i+1);
 256  10
                                                 Structure st = groupObject.get(newIndexName);
 257  10
                                                 parseRep(reps.get(i), st);
 258  
                                         }
 259  0
                                 } catch(Throwable t) {
 260  0
                                         log.info("Issue Parsing: " + t);
 261  0
                                         newIndexName = groupObject.addNonstandardSegment(childName);
 262  0
                                         for (int j = i; j < reps.size(); j++) {
 263  0
                                                 parseRep(reps.get(j), groupObject.get(newIndexName, j-i));
 264  
                                         }
 265  10
                                 }
 266  
                         }
 267  
                         
 268  
                 }
 269  1030
     }
 270  
     
 271  
     private void parseRep(Element theElem, Structure theObj) throws HL7Exception {
 272  310
                 if (theObj instanceof Group) {
 273  80
                         parse((Group) theObj, theElem);
 274  
                 }
 275  230
                 else if (theObj instanceof Segment) {
 276  230
                         parse((Segment) theObj, theElem);
 277  
                 }                
 278  310
                 log.trace("Parsed element: {}", theElem.getNodeName());            
 279  310
     }
 280  
     
 281  
     //includes direct children only
 282  
     private List<Element> getChildElementsByTagName(Element theElement, String theName) throws HL7Exception {
 283  1030
             List<Element> result = new ArrayList<Element>(10);
 284  1030
             NodeList children = theElement.getChildNodes();
 285  
             
 286  8390
             for (int i = 0; i < children.getLength(); i++) {
 287  7360
                     Node child = children.item(i);
 288  7360
                     if (child.getNodeType() == Node.ELEMENT_NODE && child.getLocalName().equals(theName)) {
 289  310
                 assertNamespaceURI(child.getNamespaceURI());
 290  310
                             result.add((Element)child);
 291  
                     }
 292  
             }
 293  
             
 294  1030
             return result; 
 295  
     }
 296  
     
 297  
     /** 
 298  
      * Given the name of a group element in an XML message, returns the corresponding 
 299  
      * group class name.  This name is identical except in order to be a valid class 
 300  
      * name, the dot character immediately following the message name is replaced with 
 301  
      * an underscore.  For example, there is a group element called ADT_A01.INSURANCE and the 
 302  
      * corresponding group Class is called ADT_A01_INSURANCE. 
 303  
      */
 304  
 //    protected static String makeGroupClassName(String elementName) {
 305  
 //        return elementName.replace('.', '_');
 306  
 //    }
 307  
 
 308  
     /** 
 309  
      * Given the name of a message and a Group class, returns the corresponding group element name in an 
 310  
      * XML-encoded message.  This is the message name and group name separated by a dot. For example, 
 311  
      * ADT_A01.INSURANCE.
 312  
      * 
 313  
      * If it looks like a segment name (i.e. has 3 characters), no change is made. 
 314  
      */
 315  
     protected static String makeGroupElementName(String messageName, String className) {
 316  
         String ret;
 317  
         
 318  1770
         if (className.length() > 4 || ourForceGroupNames.contains(className)) {
 319  550
             StringBuilder elementName = new StringBuilder();
 320  550
             elementName.append(messageName);
 321  550
             elementName.append('.');
 322  550
             elementName.append(className);
 323  550
             ret = elementName.toString();
 324  550
         } else if (className.length() == 4) {
 325  
             // It is not clear why this case is needed.. We should figure out
 326  
                 // why it was added, since removing it or optimizing its use would
 327  
                 // prevent the need for "ourForGroupNames" above
 328  5
                 ret = className.substring(0,3);
 329  
         } else {
 330  1215
             ret = className;
 331  
         }
 332  
         
 333  1770
         return ret;
 334  
     }
 335  
 
 336  
     /** Test harness */
 337  
     public static void main(String args[]) {
 338  0
         if (args.length != 1) {
 339  0
             System.out.println("Usage: DefaultXMLParser pipe_encoded_file");
 340  0
             System.exit(1);
 341  
         }
 342  
 
 343  
         //read and parse message from file 
 344  
         try {
 345  0
             File messageFile = new File(args[0]);
 346  0
             long fileLength = messageFile.length();
 347  0
             FileReader r = new FileReader(messageFile);
 348  0
             char[] cbuf = new char[(int) fileLength];
 349  0
             System.out.println("Reading message file ... " + r.read(cbuf) + " of " + fileLength + " chars");
 350  0
             r.close();
 351  0
             String messString = String.valueOf(cbuf);
 352  
 
 353  0
             Parser inParser = null;
 354  0
             Parser outParser = null;
 355  0
             PipeParser pp = new PipeParser();
 356  0
             ca.uhn.hl7v2.parser.XMLParser xp = new DefaultXMLParser();
 357  0
             System.out.println("Encoding: " + pp.getEncoding(messString));
 358  0
             if (pp.getEncoding(messString) != null) {
 359  0
                 inParser = pp;
 360  0
                 outParser = xp;
 361  
             }
 362  0
             else if (xp.getEncoding(messString) != null) {
 363  0
                 inParser = xp;
 364  0
                 outParser = pp;
 365  
             }
 366  
 
 367  0
             Message mess = inParser.parse(messString);
 368  0
             System.out.println("Got message of type " + mess.getClass().getName());
 369  
 
 370  0
             String otherEncoding = outParser.encode(mess);
 371  0
             System.out.println(otherEncoding);
 372  
         }
 373  0
         catch (Exception e) {
 374  0
             e.printStackTrace();
 375  0
         }
 376  0
     }
 377  
 
 378  
     /**
 379  
      * {@inheritDoc}
 380  
      */
 381  
         @Override
 382  
         public void parse(Message theMessage, String theString) throws HL7Exception {
 383  15
                 Document doc = parseStringIntoDocument(theString);
 384  15
         parse(theMessage, doc.getDocumentElement());
 385  
 
 386  15
         applySuperStructureName(theMessage);
 387  15
         }
 388  
 
 389  
     /**
 390  
      * Convenience factory method which returns an instance that has a 
 391  
      * {@link NoValidation NoValidation validation context}. 
 392  
      */
 393  
     public static XMLParser getInstanceWithNoValidation() {
 394  15
         HapiContext context = new DefaultHapiContext(ValidationContextFactory.noValidation());
 395  15
         XMLParser retVal = context.getXMLParser();
 396  15
         return retVal;
 397  
     }
 398  
 
 399  
 
 400  
 }