1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package ca.uhn.hl7v2.parser;
29
30 import java.util.HashSet;
31 import java.util.Set;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34
35 import ca.uhn.hl7v2.Version;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38 import org.w3c.dom.DOMException;
39 import org.w3c.dom.Document;
40 import org.w3c.dom.Element;
41 import org.w3c.dom.Node;
42 import org.w3c.dom.NodeList;
43
44 import ca.uhn.hl7v2.ErrorCode;
45 import ca.uhn.hl7v2.HL7Exception;
46 import ca.uhn.hl7v2.HapiContext;
47 import ca.uhn.hl7v2.model.Composite;
48 import ca.uhn.hl7v2.model.DataTypeException;
49 import ca.uhn.hl7v2.model.GenericComposite;
50 import ca.uhn.hl7v2.model.GenericMessage;
51 import ca.uhn.hl7v2.model.GenericPrimitive;
52 import ca.uhn.hl7v2.model.Message;
53 import ca.uhn.hl7v2.model.Primitive;
54 import ca.uhn.hl7v2.model.Segment;
55 import ca.uhn.hl7v2.model.Type;
56 import ca.uhn.hl7v2.model.Varies;
57 import ca.uhn.hl7v2.util.Terser;
58 import ca.uhn.hl7v2.util.XMLUtils;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 public abstract class XMLParser extends Parser {
75
76 private static final String ESCAPE_ATTRNAME = "V";
77 private static final String ESCAPE_NODENAME = "escape";
78 private static final Logger log = LoggerFactory.getLogger(XMLParser.class);
79 protected static final String NS = "urn:hl7-org:v2xml";
80 private static final Pattern NS_PATTERN = Pattern.compile("xmlns(.*)=\"" + NS + "\"");
81
82 private String textEncoding;
83
84
85
86
87
88 public XMLParser() {
89 super();
90 }
91
92
93
94
95
96 public XMLParser(HapiContext context) {
97 super(context);
98 }
99
100
101
102
103
104
105 public XMLParser(ModelClassFactory theFactory) {
106 super(theFactory);
107
108 }
109
110
111
112
113
114
115
116
117
118 public String getEncoding(String message) {
119 return EncodingDetector.isXmlEncoded(message) ? getDefaultEncoding() : null;
120 }
121
122
123
124
125 public String getDefaultEncoding() {
126 return "XML";
127 }
128
129
130
131
132
133
134
135
136
137
138 @Deprecated()
139 public void setKeepAsOriginalNodes(String[] keepAsOriginalNodes) {
140 getParserConfiguration().setXmlDisableWhitespaceTrimmingOnNodeNames(keepAsOriginalNodes);
141 }
142
143
144
145
146
147
148 @Deprecated
149 public String[] getKeepAsOriginalNodes() {
150 return getParserConfiguration().getXmlDisableWhitespaceTrimmingOnNodeNames().toArray(new String[0]);
151 }
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178 public abstract Message parseDocument(Document xmlMessage, String version) throws HL7Exception;
179
180
181
182
183
184
185
186
187 protected Message doParse(String message, String version) throws HL7Exception {
188 Message m;
189
190
191 Document doc;
192 doc = parseStringIntoDocument(message);
193 m = parseDocument(doc, version);
194
195 return m;
196 }
197
198
199
200
201
202
203
204
205 protected synchronized Document parseStringIntoDocument(String message) throws HL7Exception {
206 try {
207 return XMLUtils.parse(message);
208 } catch (Exception e) {
209 throw new HL7Exception("Exception parsing XML", e);
210 }
211 }
212
213
214
215
216
217
218
219
220
221 protected String doEncode(Message source, String encoding) throws HL7Exception {
222 if (!encoding.equals("XML"))
223 throw new EncodingNotSupportedException("XMLParser supports only XML encoding");
224 return encode(source);
225 }
226
227
228
229
230
231
232
233
234
235 protected String doEncode(Message source) throws HL7Exception {
236
237
238
239
240
241
242 log.info("XML-Encoding a GenericMessage is not covered by the specification. ");
243
244 Document doc = encodeDocument(source);
245 try {
246 return XMLUtils.serialize(doc, getParserConfiguration().isPrettyPrintWhenEncodingXml());
247 } catch (Exception e) {
248 throw new HL7Exception("Exception serializing XML document to string", e);
249 }
250 }
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 public abstract Document encodeDocument(Message source) throws HL7Exception;
268
269
270 protected void assertNamespaceURI(String ns) throws HL7Exception {
271 if (!NS.equals(ns)) {
272 throw new HL7Exception("Namespace URI must be " + NS);
273 }
274 }
275
276
277
278
279
280
281
282
283
284 public void parse(Segment segmentObject, Element segmentElement) throws HL7Exception {
285 Set<String> done = new HashSet<>();
286
287 NodeList all = segmentElement.getChildNodes();
288 for (int i = 0; i < all.getLength(); i++) {
289 String elementName = all.item(i).getNodeName();
290
291 if (all.item(i).getNodeType() == Node.ELEMENT_NODE && !done.contains(elementName)) {
292 assertNamespaceURI(all.item(i).getNamespaceURI());
293 done.add(elementName);
294
295 int index = elementName.indexOf('.');
296 if (index >= 0 && elementName.length() > index) {
297 String fieldNumString = elementName.substring(index + 1);
298 int fieldNum = Integer.parseInt(fieldNumString);
299 parseReps(segmentObject, segmentElement, elementName, fieldNum);
300 } else {
301 log.debug("Child of segment {} doesn't look like a field {}",
302 segmentObject.getName(), elementName);
303 }
304 }
305 }
306
307
308 if (segmentObject.getClass().getName().contains("OBX")) {
309 FixFieldDataType.fixOBX5(segmentObject, getFactory(), getHapiContext().getParserConfiguration());
310 }
311
312 if (segmentObject.getClass().getName().contains("MFE") &&
313 Version.versionOf(segmentObject.getMessage().getVersion()).isGreaterThan(Version.V23)) {
314 FixFieldDataType.fixMFE4(segmentObject, getFactory(), getHapiContext().getParserConfiguration());
315 }
316 }
317
318 private void parseReps(Segment segmentObject, Element segmentElement, String fieldName,
319 int fieldNum) throws HL7Exception {
320
321 NodeList reps = segmentElement.getElementsByTagName(fieldName);
322 for (int i = 0; i < reps.getLength(); i++) {
323 parse(segmentObject.getField(fieldNum, i), (Element) reps.item(i));
324 }
325 }
326
327
328
329
330
331
332
333
334
335
336
337 public boolean encode(Segment segmentObject, Element segmentElement) throws HL7Exception {
338 boolean hasValue = false;
339 int n = segmentObject.numFields();
340 for (int i = 1; i <= n; i++) {
341 String name = makeElementName(segmentObject, i);
342 Type[] reps = segmentObject.getField(i);
343 for (Type rep : reps) {
344 Element newNode = segmentElement.getOwnerDocument().createElementNS(NS, name);
345 boolean componentHasValue = encode(rep, newNode);
346 if (componentHasValue) {
347 try {
348 segmentElement.appendChild(newNode);
349 } catch (DOMException e) {
350 throw new HL7Exception("DOMException encoding Segment: ", e);
351 }
352 hasValue = true;
353 }
354 }
355 }
356 return hasValue;
357 }
358
359
360
361
362
363
364
365
366 public void parse(Type datatypeObject, Element datatypeElement) throws HL7Exception {
367 if (datatypeObject instanceof Varies) {
368 parseVaries((Varies) datatypeObject, datatypeElement);
369 } else if (datatypeObject instanceof Primitive) {
370 parsePrimitive((Primitive) datatypeObject, datatypeElement);
371 } else if (datatypeObject instanceof Composite) {
372 parseComposite((Composite) datatypeObject, datatypeElement);
373 }
374 }
375
376
377
378
379
380
381 private void parseVaries(Varies datatypeObject, Element datatypeElement)
382 throws HL7Exception {
383
384
385 if (!hasChildElement(datatypeElement)) {
386
387 datatypeObject.setData(new GenericPrimitive(datatypeObject.getMessage()));
388 } else {
389
390
391 datatypeObject.setData(new GenericComposite(datatypeObject.getMessage()));
392 }
393 parse(datatypeObject.getData(), datatypeElement);
394 }
395
396
397 private boolean hasChildElement(Element e) {
398 NodeList children = e.getChildNodes();
399 boolean hasElement = false;
400 int c = 0;
401 while (c < children.getLength() && !hasElement) {
402 if (children.item(c).getNodeType() == Node.ELEMENT_NODE
403 && !ESCAPE_NODENAME.equals(children.item(c).getNodeName())) {
404 hasElement = true;
405 }
406 c++;
407 }
408 return hasElement;
409 }
410
411
412
413
414
415 private void parsePrimitive(Primitive datatypeObject, Element datatypeElement)
416 throws HL7Exception {
417 NodeList children = datatypeElement.getChildNodes();
418 StringBuilder builder = new StringBuilder();
419 for (int c = 0; c < children.getLength(); c++) {
420 Node child = children.item(c);
421 try {
422 if (child.getNodeType() == Node.TEXT_NODE) {
423 String value = child.getNodeValue();
424 if (value != null && value.length() > 0) {
425 if (keepAsOriginal(child.getParentNode())) {
426 builder.append(value);
427 } else {
428 builder.append(removeWhitespace(value));
429 }
430 }
431
432 } else if (child.getNodeType() == Node.ELEMENT_NODE
433 && ESCAPE_NODENAME.equals(child.getLocalName())) {
434 assertNamespaceURI(child.getNamespaceURI());
435 EncodingCharacters ec = EncodingCharacters.getInstance(datatypeObject
436 .getMessage());
437 Element elem = (Element) child;
438 String attr = elem.getAttribute(ESCAPE_ATTRNAME).trim();
439 if (attr.length() > 0) {
440 builder.append(ec.getEscapeCharacter()).append(attr)
441 .append(ec.getEscapeCharacter());
442 }
443 }
444 } catch (Exception e) {
445 log.error("Error parsing primitive value from TEXT_NODE", e);
446 }
447
448 }
449 datatypeObject.setValue(builder.toString());
450 }
451
452
453
454
455
456
457
458
459
460 protected boolean keepAsOriginal(Node node) {
461 if (getParserConfiguration().isXmlDisableWhitespaceTrimmingOnAllNodes()) {
462 return true;
463 }
464 return (node.getNodeName() != null) && getParserConfiguration().getXmlDisableWhitespaceTrimmingOnNodeNames().contains(node.getNodeName());
465 }
466
467
468
469
470
471
472 protected String removeWhitespace(String s) {
473
474 s = s.replace('\r', ' ');
475 s = s.replace('\n', ' ');
476 s = s.replace('\t', ' ');
477
478 boolean repeatedSpacesExist = true;
479 while (repeatedSpacesExist) {
480 int loc = s.indexOf(" ");
481 if (loc < 0) {
482 repeatedSpacesExist = false;
483 } else {
484 s = s.substring(0, loc) +
485 " " +
486 s.substring(loc + 2);
487 }
488 }
489 return s.trim();
490 }
491
492
493
494
495
496 private void parseComposite(Composite datatypeObject, Element datatypeElement)
497 throws HL7Exception {
498 if (datatypeObject instanceof GenericComposite) {
499
500 NodeList children = datatypeElement.getChildNodes();
501 int compNum = 0;
502 for (int i = 0; i < children.getLength(); i++) {
503 if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
504 Element nextElement = (Element) children.item(i);
505 assertNamespaceURI(nextElement.getNamespaceURI());
506 String localName = nextElement.getLocalName();
507 int dotIndex = localName.indexOf(".");
508 if (dotIndex > -1) {
509 compNum = Integer.parseInt(localName.substring(dotIndex + 1)) - 1;
510 } else {
511 log.debug(
512 "Datatype element {} doesn't have a valid numbered name, using default index of {}",
513 datatypeElement.getLocalName(), compNum);
514 }
515 Type nextComponent = datatypeObject.getComponent(compNum);
516 parse(nextComponent, nextElement);
517 compNum++;
518 }
519 }
520 } else {
521 Type[] children = datatypeObject.getComponents();
522 for (int i = 0; i < children.length; i++) {
523 NodeList matchingElements = datatypeElement.getElementsByTagNameNS(NS, makeElementName(
524 datatypeObject, i + 1));
525 if (matchingElements.getLength() > 0) {
526 parse(children[i], (Element) matchingElements.item(0));
527 }
528 }
529
530 int nextExtraCmpIndex = 0;
531 boolean foundExtraComponent;
532 do {
533 foundExtraComponent = false;
534 NodeList matchingElements = datatypeElement.getElementsByTagNameNS(NS, makeElementName(datatypeObject, children.length + nextExtraCmpIndex + 1));
535 if (matchingElements.getLength() > 0) {
536 parse(datatypeObject.getExtraComponents().getComponent(nextExtraCmpIndex), (Element) matchingElements.item(0));
537 foundExtraComponent = true;
538 }
539 nextExtraCmpIndex++;
540 } while (foundExtraComponent);
541
542
543 }
544 }
545
546
547 private String makeElementName(Segment s, int child) {
548 return s.getName() + "." + child;
549 }
550
551
552 private String makeElementName(Composite composite, int child) {
553 return composite.getName() + "." + child;
554 }
555
556
557
558
559
560
561
562 private boolean encode(Type datatypeObject, Element datatypeElement) throws DataTypeException {
563 boolean hasData = false;
564 if (datatypeObject instanceof Varies) {
565 hasData = encodeVaries((Varies) datatypeObject, datatypeElement);
566 } else if (datatypeObject instanceof Primitive) {
567 hasData = encodePrimitive((Primitive) datatypeObject, datatypeElement);
568 } else if (datatypeObject instanceof Composite) {
569 hasData = encodeComposite((Composite) datatypeObject, datatypeElement);
570 }
571 return hasData;
572 }
573
574
575
576
577
578 private boolean encodeVaries(Varies datatypeObject, Element datatypeElement)
579 throws DataTypeException {
580 boolean hasData = false;
581 if (datatypeObject.getData() != null) {
582 hasData = encode(datatypeObject.getData(), datatypeElement);
583 }
584 return hasData;
585 }
586
587
588
589
590
591
592 private boolean encodePrimitive(Primitive datatypeObject, Element datatypeElement)
593 throws DataTypeException {
594 String value = datatypeObject.getValue();
595 boolean hasValue = (value != null && value.length() > 0);
596 if (hasValue) {
597 try {
598 EncodingCharacters ec = EncodingCharacters.getInstance(datatypeObject.getMessage());
599 char esc = ec.getEscapeCharacter();
600 int pos;
601 int oldpos = 0;
602 boolean escaping = false;
603
604
605 while ((pos = value.indexOf(esc, oldpos)) >= 0) {
606
607
608 String v = value.substring(oldpos, pos);
609 if (!escaping) {
610
611 if (v.length() > 0)
612 datatypeElement.appendChild(datatypeElement.getOwnerDocument()
613 .createTextNode(v));
614 escaping = true;
615 } else {
616 if (v.startsWith(".") || "H".equals(v) || "N".equals(v)) {
617
618 Element escape = datatypeElement.getOwnerDocument().createElementNS(NS,
619 ESCAPE_NODENAME);
620 escape.setAttribute(ESCAPE_ATTRNAME, v);
621 datatypeElement.appendChild(escape);
622 escaping = false;
623 } else {
624
625 datatypeElement.appendChild(datatypeElement.getOwnerDocument()
626 .createTextNode(esc + v));
627 }
628 }
629 oldpos = pos + 1;
630 }
631
632 if (oldpos <= value.length()) {
633
634 StringBuilder sb = new StringBuilder();
635
636
637 if (escaping)
638 sb.append(esc);
639
640 sb.append(value.substring(oldpos));
641 datatypeElement.appendChild(datatypeElement.getOwnerDocument().createTextNode(
642 sb.toString()));
643 }
644
645 } catch (Exception e) {
646 throw new DataTypeException("Exception encoding Primitive: ", e);
647 }
648
649 }
650 return hasValue;
651 }
652
653
654
655
656
657
658 private boolean encodeComposite(Composite datatypeObject, Element datatypeElement)
659 throws DataTypeException {
660 Type[] components = datatypeObject.getComponents();
661 boolean hasValue = false;
662 for (int i = 0; i < components.length; i++) {
663 String name = makeElementName(datatypeObject, i + 1);
664 Element newNode = datatypeElement.getOwnerDocument().createElementNS(NS, name);
665 boolean componentHasValue = encode(components[i], newNode);
666 if (componentHasValue) {
667 try {
668 datatypeElement.appendChild(newNode);
669 } catch (DOMException e) {
670 throw new DataTypeException("DOMException encoding Composite: ", e);
671 }
672 hasValue = true;
673 }
674 }
675 return hasValue;
676 }
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694 public Segment getCriticalResponseData(String message) throws HL7Exception {
695 String version = getVersion(message);
696 Segment criticalData = Parser.makeControlMSH(version, getFactory());
697
698 Terser.set(criticalData, 1, 0, 1, 1, parseLeaf(message, "MSH.1", 0));
699 Terser.set(criticalData, 2, 0, 1, 1, parseLeaf(message, "MSH.2", 0));
700 Terser.set(criticalData, 10, 0, 1, 1, parseLeaf(message, "MSH.10", 0));
701 String procID = parseLeaf(message, "MSH.11", 0);
702 if (procID.length() == 0) {
703 procID = parseLeaf(message, "PT.1", message.indexOf("MSH.11"));
704
705 }
706 Terser.set(criticalData, 11, 0, 1, 1, procID);
707
708 return criticalData;
709 }
710
711
712
713
714
715
716
717
718
719
720 public String getAckID(String message) {
721 String ackID = null;
722 try {
723 ackID = parseLeaf(message, "msa.2", 0).trim();
724 } catch (HL7Exception e) {
725 }
726 return ackID;
727 }
728
729 public String getVersion(String message) throws HL7Exception {
730 String version = parseLeaf(message, "MSH.12", 0);
731 if (version.trim().length() == 0) {
732 version = parseLeaf(message, "VID.1", message.indexOf("MSH.12"));
733 }
734 return version;
735 }
736
737
738
739
740
741
742
743
744
745
746
747
748
749 protected static String parseLeaf(String message, String tagName, int startAt) throws HL7Exception {
750
751
752
753 String prefix = "";
754 Matcher m = NS_PATTERN.matcher(message);
755 if (m.find()) {
756 String ns = m.group(1);
757 if (ns != null && ns.length() > 0) {
758 prefix = ns.substring(1) + ":";
759 }
760 }
761
762 int tagStart = message.indexOf("<" + prefix + tagName, startAt);
763 if (tagStart < 0)
764 tagStart = message.indexOf("<" + prefix + tagName.toUpperCase(), startAt);
765 int valStart = message.indexOf(">", tagStart) + 1;
766 int valEnd = message.indexOf("<", valStart);
767
768 String value;
769 if (tagStart >= 0 && valEnd >= valStart) {
770 value = message.substring(valStart, valEnd);
771 } else {
772 throw new HL7Exception("Couldn't find " + tagName + " in message beginning: "
773 + message.substring(0, Math.min(150, message.length())),
774 ErrorCode.REQUIRED_FIELD_MISSING);
775 }
776
777
778 value = value.replaceAll(""", "\"");
779 value = value.replaceAll("'", "'");
780 value = value.replaceAll("&", "&");
781 value = value.replaceAll("<", "<");
782 value = value.replaceAll(">", ">");
783
784 return value;
785 }
786
787
788
789
790
791
792 @Override
793 public String doEncode(Segment structure, EncodingCharacters encodingCharacters) {
794 throw new UnsupportedOperationException("Not supported yet.");
795 }
796
797
798
799
800
801
802 @Override
803 protected Message doParseForSpecificPackage(String theMessage, String theVersion,
804 String thePackageName) {
805 throw new UnsupportedOperationException("Not supported yet.");
806 }
807
808
809
810
811
812
813 @Override
814 public String doEncode(Type type, EncodingCharacters encodingCharacters) {
815 throw new UnsupportedOperationException("Not supported yet.");
816 }
817
818
819
820
821
822
823 @Override
824 public void parse(Type type, String string, EncodingCharacters encodingCharacters) {
825 throw new UnsupportedOperationException("Not supported yet.");
826 }
827
828
829
830
831
832
833 @Override
834 public void parse(Segment segment, String string, EncodingCharacters encodingCharacters) {
835 throw new UnsupportedOperationException("Not supported yet.");
836 }
837
838
839
840
841
842
843
844 public String getTextEncoding() {
845 return textEncoding;
846 }
847
848
849
850
851
852
853
854 public void setTextEncoding(String textEncoding) {
855 this.textEncoding = textEncoding;
856 }
857
858 }