001 /** 002 The contents of this file are subject to the Mozilla Public License Version 1.1 003 (the "License"); you may not use this file except in compliance with the License. 004 You may obtain a copy of the License at http://www.mozilla.org/MPL/ 005 Software distributed under the License is distributed on an "AS IS" basis, 006 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 007 specific language governing rights and limitations under the License. 008 009 The Initial Developer of the Original Code is University Health Network. Copyright (C) 010 2001. All Rights Reserved. 011 012 Contributor(s): ______________________________________. 013 014 Alternatively, the contents of this file may be used under the terms of the 015 GNU General Public License (the ???GPL???), in which case the provisions of the GPL are 016 applicable instead of those above. If you wish to allow use of your version of this 017 file only under the terms of the GPL and not to allow others to use your version 018 of this file under the MPL, indicate your decision by deleting the provisions above 019 and replace them with the notice and other provisions required by the GPL License. 020 If you do not delete the provisions above, a recipient may use your version of 021 this file under either the MPL or the GPL. 022 023 */ 024 package ca.uhn.hl7v2.parser; 025 026 import java.io.File; 027 import java.io.FileReader; 028 import java.util.ArrayList; 029 import java.util.List; 030 031 import javax.xml.parsers.DocumentBuilderFactory; 032 033 import org.w3c.dom.DOMException; 034 import org.w3c.dom.Document; 035 import org.w3c.dom.Element; 036 import org.w3c.dom.Node; 037 import org.w3c.dom.NodeList; 038 039 import ca.uhn.hl7v2.HL7Exception; 040 import ca.uhn.hl7v2.model.Group; 041 import ca.uhn.hl7v2.model.Message; 042 import ca.uhn.hl7v2.model.Segment; 043 import ca.uhn.hl7v2.model.Structure; 044 import ca.uhn.log.HapiLog; 045 import ca.uhn.log.HapiLogFactory; 046 047 /** 048 * <p>A default XMLParser. This class assigns segment elements (in an XML-encoded message) 049 * to Segment objects (in a Message object) using the name of a segment and the names 050 * of any groups in which the segment is nested. The names of group classes must correspond 051 * to the names of group elements (they must be identical except that a dot in the element 052 * name, following the message name, is replaced with an underscore, in order to consitute a 053 * valid class name). </p> 054 * <p>At the time of writing, the group names in the XML spec are changing. Many of the group 055 * names have been automatically generated based on the group contents. However, these automatic 056 * names are gradually being replaced with manually assigned names. This process is expected to 057 * be complete by November 2002. As a result, mismatches are likely. Messages could be 058 * transformed prior to parsing (using XSLT) as a work-around. Alternatively the group class names 059 * could be changed to reflect updates in the XML spec. Ultimately, HAPI group classes will be 060 * changed to correspond with the official group names, once these are all assigned. </p> 061 * @author Bryan Tripp 062 */ 063 public class DefaultXMLParser extends XMLParser { 064 065 private static final HapiLog log = HapiLogFactory.getHapiLog(DefaultXMLParser.class); 066 067 /** Creates a new instance of DefaultXMLParser */ 068 public DefaultXMLParser() { 069 } 070 071 /** 072 * Creates a new instance of DefaultXMLParser 073 * 074 * @param theFactory custom factory to use for model class lookup 075 */ 076 public DefaultXMLParser(ModelClassFactory theFactory) { 077 super(theFactory); 078 } 079 080 /** 081 * <p>Creates an XML Document that corresponds to the given Message object. </p> 082 * <p>If you are implementing this method, you should create an XML Document, and insert XML Elements 083 * into it that correspond to the groups and segments that belong to the message type that your subclass 084 * of XMLParser supports. Then, for each segment in the message, call the method 085 * <code>encode(Segment segmentObject, Element segmentElement)</code> using the Element for 086 * that segment and the corresponding Segment object from the given Message.</p> 087 */ 088 public Document encodeDocument(Message source) throws HL7Exception { 089 String messageClassName = source.getClass().getName(); 090 String messageName = messageClassName.substring(messageClassName.lastIndexOf('.') + 1); 091 org.w3c.dom.Document doc = null; 092 try { 093 doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 094 Element root = doc.createElement(messageName); 095 doc.appendChild(root); 096 } 097 catch (Exception e) { 098 throw new HL7Exception( 099 "Can't create XML document - " + e.getClass().getName(), 100 HL7Exception.APPLICATION_INTERNAL_ERROR, 101 e); 102 } 103 encode(source, doc.getDocumentElement()); 104 return doc; 105 } 106 107 /** 108 * Copies data from a group object into the corresponding group element, creating any 109 * necessary child nodes. 110 */ 111 private void encode(ca.uhn.hl7v2.model.Group groupObject, org.w3c.dom.Element groupElement) throws HL7Exception { 112 String[] childNames = groupObject.getNames(); 113 String messageName = groupObject.getMessage().getName(); 114 115 try { 116 for (int i = 0; i < childNames.length; i++) { 117 Structure[] reps = groupObject.getAll(childNames[i]); 118 for (int j = 0; j < reps.length; j++) { 119 Element childElement = 120 groupElement.getOwnerDocument().createElement(makeGroupElementName(messageName, childNames[i])); 121 groupElement.appendChild(childElement); 122 if (reps[j] instanceof Group) { 123 encode((Group) reps[j], childElement); 124 } 125 else if (reps[j] instanceof Segment) { 126 encode((Segment) reps[j], childElement); 127 } 128 } 129 } 130 } 131 catch (DOMException e) { 132 throw new HL7Exception( 133 "Can't encode group " + groupObject.getClass().getName(), 134 HL7Exception.APPLICATION_INTERNAL_ERROR, 135 e); 136 } 137 } 138 139 /** 140 * <p>Creates and populates a Message object from an XML Document that contains an XML-encoded HL7 message.</p> 141 * <p>The easiest way to implement this method for a particular message structure is as follows: 142 * <ol><li>Create an instance of the Message type you are going to handle with your subclass 143 * of XMLParser</li> 144 * <li>Go through the given Document and find the Elements that represent the top level of 145 * each message segment. </li> 146 * <li>For each of these segments, call <code>parse(Segment segmentObject, Element segmentElement)</code>, 147 * providing the appropriate Segment from your Message object, and the corresponding Element.</li></ol> 148 * At the end of this process, your Message object should be populated with data from the XML 149 * Document.</p> 150 * @throws HL7Exception if the message is not correctly formatted. 151 * @throws EncodingNotSupportedException if the message encoded 152 * is not supported by this parser. 153 */ 154 public Message parseDocument(org.w3c.dom.Document XMLMessage, String version) throws HL7Exception { 155 String messageName = XMLMessage.getDocumentElement().getTagName(); 156 Message message = instantiateMessage(messageName, version, true); 157 parse(message, XMLMessage.getDocumentElement()); 158 return message; 159 } 160 161 /** 162 * Populates the given group object with data from the given group element, ignoring 163 * any unrecognized nodes. 164 */ 165 private void parse(ca.uhn.hl7v2.model.Group groupObject, org.w3c.dom.Element groupElement) throws HL7Exception { 166 String[] childNames = groupObject.getNames(); 167 String messageName = groupObject.getMessage().getName(); 168 169 NodeList allChildNodes = groupElement.getChildNodes(); 170 ArrayList unparsedElementList = new ArrayList(); 171 for (int i = 0; i < allChildNodes.getLength(); i++) { 172 Node node = allChildNodes.item(i); 173 String name = node.getNodeName(); 174 if (node.getNodeType() == Node.ELEMENT_NODE && !unparsedElementList.contains(name)) { 175 unparsedElementList.add(name); 176 } 177 } 178 179 //we're not too fussy about order here (all occurrences get parsed as repetitions) ... 180 for (int i = 0; i < childNames.length; i++) { 181 unparsedElementList.remove(childNames[i]); 182 183 // 4 char segment names are second occurrences of a segment within a single message 184 // structure. e.g. the second PID segment in an A17 patient swap message is known 185 // to hapi's code represenation as PID2 186 if (childNames[i].length() != 4) { 187 parseReps(groupElement, groupObject, messageName, childNames[i], childNames[i]); 188 } else { 189 log.debug("Skipping rep segment: " + childNames[i]); 190 } 191 } 192 193 for (int i = 0; i < unparsedElementList.size(); i++) { 194 String segName = (String) unparsedElementList.get(i); 195 String segIndexName = groupObject.addNonstandardSegment(segName); 196 parseReps(groupElement, groupObject, messageName, segName, segIndexName); 197 } 198 } 199 200 //param childIndexName may have an integer on the end if >1 sibling with same name (e.g. NTE2) 201 private void parseReps(Element groupElement, Group groupObject, 202 String messageName, String childName, String childIndexName) throws HL7Exception { 203 204 List reps = getChildElementsByTagName(groupElement, makeGroupElementName(messageName, childName)); 205 log.debug("# of elements matching " 206 + makeGroupElementName(messageName, childName) + ": " + reps.size()); 207 208 if (groupObject.isRepeating(childIndexName)) { 209 for (int i = 0; i < reps.size(); i++) { 210 parseRep((Element) reps.get(i), groupObject.get(childIndexName, i)); 211 } 212 } else { 213 if (reps.size() > 0) { 214 parseRep((Element) reps.get(0), groupObject.get(childIndexName, 0)); 215 } 216 217 // if (reps.size() > 1) { 218 // String newIndexName = groupObject.addNonstandardSegment(childName); 219 // for (int i = 1; i < reps.size(); i++) { 220 // parseRep((Element) reps.get(i), groupObject.get(newIndexName, i-1)); 221 // } 222 // } 223 if (reps.size() > 1) { 224 String newIndexName = ""; 225 int i=1; 226 try { 227 for (i = 1; i < reps.size(); i++) { 228 newIndexName = childName+(i+1); 229 Structure st = groupObject.get(newIndexName); 230 parseRep((Element) reps.get(i), st); 231 } 232 } catch(Throwable t) { 233 log.info("Issue Parsing: " + t); 234 newIndexName = groupObject.addNonstandardSegment(childName); 235 for (int j = i; j < reps.size(); j++) { 236 parseRep((Element) reps.get(j), groupObject.get(newIndexName, j-i)); 237 } 238 } 239 } 240 241 } 242 } 243 244 private void parseRep(Element theElem, Structure theObj) throws HL7Exception { 245 if (theObj instanceof Group) { 246 parse((Group) theObj, theElem); 247 } 248 else if (theObj instanceof Segment) { 249 parse((Segment) theObj, theElem); 250 } 251 log.debug("Parsed element: " + theElem.getNodeName()); 252 } 253 254 //includes direct children only 255 private List getChildElementsByTagName(Element theElement, String theName) { 256 List result = new ArrayList(10); 257 NodeList children = theElement.getChildNodes(); 258 259 for (int i = 0; i < children.getLength(); i++) { 260 Node child = children.item(i); 261 if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(theName)) { 262 result.add(child); 263 } 264 } 265 266 return result; 267 } 268 269 /** 270 * Given the name of a group element in an XML message, returns the corresponding 271 * group class name. This name is identical except in order to be a valid class 272 * name, the dot character immediately following the message name is replaced with 273 * an underscore. For example, there is a group element called ADT_A01.INSURANCE and the 274 * corresponding group Class is called ADT_A01_INSURANCE. 275 */ 276 // protected static String makeGroupClassName(String elementName) { 277 // return elementName.replace('.', '_'); 278 // } 279 280 /** 281 * Given the name of a message and a Group class, returns the corresponding group element name in an 282 * XML-encoded message. This is the message name and group name separated by a dot. For example, 283 * ADT_A01.INSURANCE. 284 * 285 * If it looks like a segment name (i.e. has 3 characters), no change is made. 286 */ 287 protected static String makeGroupElementName(String messageName, String className) { 288 String ret = null; 289 290 if (className.length() > 4) { 291 StringBuffer elementName = new StringBuffer(); 292 elementName.append(messageName); 293 elementName.append('.'); 294 elementName.append(className); 295 ret = elementName.toString(); 296 } else if (className.length() == 4) { 297 ret = className.substring(0,3); 298 } else { 299 ret = className; 300 } 301 302 return ret; 303 } 304 305 /** Test harness */ 306 public static void main(String args[]) { 307 if (args.length != 1) { 308 System.out.println("Usage: DefaultXMLParser pipe_encoded_file"); 309 System.exit(1); 310 } 311 312 //read and parse message from file 313 try { 314 File messageFile = new File(args[0]); 315 long fileLength = messageFile.length(); 316 FileReader r = new FileReader(messageFile); 317 char[] cbuf = new char[(int) fileLength]; 318 System.out.println("Reading message file ... " + r.read(cbuf) + " of " + fileLength + " chars"); 319 r.close(); 320 String messString = String.valueOf(cbuf); 321 322 Parser inParser = null; 323 Parser outParser = null; 324 PipeParser pp = new PipeParser(); 325 ca.uhn.hl7v2.parser.XMLParser xp = new DefaultXMLParser(); 326 System.out.println("Encoding: " + pp.getEncoding(messString)); 327 if (pp.getEncoding(messString) != null) { 328 inParser = pp; 329 outParser = xp; 330 } 331 else if (xp.getEncoding(messString) != null) { 332 inParser = xp; 333 outParser = pp; 334 } 335 336 Message mess = inParser.parse(messString); 337 System.out.println("Got message of type " + mess.getClass().getName()); 338 339 String otherEncoding = outParser.encode(mess); 340 System.out.println(otherEncoding); 341 } 342 catch (Exception e) { 343 e.printStackTrace(); 344 } 345 } 346 347 348 }