001package org.hl7.fhir.r5.elementmodel; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033import java.io.IOException; 034import java.io.InputStream; 035import java.io.OutputStream; 036import java.util.ArrayList; 037import java.util.Collections; 038import java.util.Comparator; 039import java.util.List; 040 041import javax.xml.parsers.DocumentBuilder; 042import javax.xml.parsers.DocumentBuilderFactory; 043import javax.xml.parsers.SAXParser; 044import javax.xml.parsers.SAXParserFactory; 045import javax.xml.transform.Transformer; 046import javax.xml.transform.TransformerFactory; 047import javax.xml.transform.dom.DOMResult; 048import javax.xml.transform.sax.SAXSource; 049 050import org.hl7.fhir.exceptions.DefinitionException; 051import org.hl7.fhir.exceptions.FHIRException; 052import org.hl7.fhir.exceptions.FHIRFormatError; 053import org.hl7.fhir.r5.conformance.ProfileUtilities; 054import org.hl7.fhir.r5.context.IWorkerContext; 055import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; 056import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement; 057import org.hl7.fhir.r5.formats.FormatUtilities; 058import org.hl7.fhir.r5.formats.IParser.OutputStyle; 059import org.hl7.fhir.r5.model.DateTimeType; 060import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation; 061import org.hl7.fhir.r5.model.Enumeration; 062import org.hl7.fhir.r5.model.StructureDefinition; 063import org.hl7.fhir.r5.utils.ToolingExtensions; 064import org.hl7.fhir.r5.utils.formats.XmlLocationAnnotator; 065import org.hl7.fhir.r5.utils.formats.XmlLocationData; 066import org.hl7.fhir.utilities.ElementDecoration; 067import org.hl7.fhir.utilities.Utilities; 068import org.hl7.fhir.utilities.i18n.I18nConstants; 069import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 070import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 071import org.hl7.fhir.utilities.xhtml.CDANarrativeFormat; 072import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 073import org.hl7.fhir.utilities.xhtml.XhtmlNode; 074import org.hl7.fhir.utilities.xhtml.XhtmlParser; 075import org.hl7.fhir.utilities.xml.IXMLWriter; 076import org.hl7.fhir.utilities.xml.XMLUtil; 077import org.hl7.fhir.utilities.xml.XMLWriter; 078import org.w3c.dom.Document; 079import org.w3c.dom.Node; 080import org.xml.sax.InputSource; 081import org.xml.sax.XMLReader; 082 083public class XmlParser extends ParserBase { 084 private boolean allowXsiLocation; 085 private String version; 086 087 public XmlParser(IWorkerContext context) { 088 super(context); 089 } 090 091 private String schemaPath; 092 093 public String getSchemaPath() { 094 return schemaPath; 095 } 096 public void setSchemaPath(String schemaPath) { 097 this.schemaPath = schemaPath; 098 } 099 100 101 102 public boolean isAllowXsiLocation() { 103 return allowXsiLocation; 104 } 105 106 public void setAllowXsiLocation(boolean allowXsiLocation) { 107 this.allowXsiLocation = allowXsiLocation; 108 } 109 110 public List<NamedElement> parse(InputStream stream) throws FHIRFormatError, DefinitionException, FHIRException, IOException { 111 List<NamedElement> res = new ArrayList<>(); 112 Document doc = null; 113 try { 114 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 115 // xxe protection 116 factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 117 factory.setFeature("http://xml.org/sax/features/external-general-entities", false); 118 factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); 119 factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); 120 factory.setXIncludeAware(false); 121 factory.setExpandEntityReferences(false); 122 123 factory.setNamespaceAware(true); 124 if (policy == ValidationPolicy.EVERYTHING) { 125 // The SAX interface appears to not work when reporting the correct version/encoding. 126 // if we can, we'll inspect the header/encoding ourselves 127 if (stream.markSupported()) { 128 stream.mark(1024); 129 version = checkHeader(stream); 130 stream.reset(); 131 } 132 // use a slower parser that keeps location data 133 TransformerFactory transformerFactory = TransformerFactory.newInstance(); 134 Transformer nullTransformer = transformerFactory.newTransformer(); 135 DocumentBuilder docBuilder = factory.newDocumentBuilder(); 136 doc = docBuilder.newDocument(); 137 DOMResult domResult = new DOMResult(doc); 138 SAXParserFactory spf = SAXParserFactory.newInstance(); 139 spf.setNamespaceAware(true); 140 spf.setValidating(false); 141 // xxe protection 142 spf.setFeature("http://xml.org/sax/features/external-general-entities", false); 143 spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 144 SAXParser saxParser = spf.newSAXParser(); 145 XMLReader xmlReader = saxParser.getXMLReader(); 146 // xxe protection 147 xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false); 148 xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 149 150 XmlLocationAnnotator locationAnnotator = new XmlLocationAnnotator(xmlReader, doc); 151 InputSource inputSource = new InputSource(stream); 152 SAXSource saxSource = new SAXSource(locationAnnotator, inputSource); 153 nullTransformer.transform(saxSource, domResult); 154 } else { 155 DocumentBuilder builder = factory.newDocumentBuilder(); 156 doc = builder.parse(stream); 157 } 158 } catch (Exception e) { 159 logError(0, 0, "(syntax)", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL); 160 doc = null; 161 } 162 if (doc != null) { 163 Element e = parse(doc); 164 if (e != null) { 165 res.add(new NamedElement(null, e)); 166 } 167 } 168 return res; 169 } 170 171 172 private void checkForProcessingInstruction(Document document) throws FHIRFormatError { 173 if (policy == ValidationPolicy.EVERYTHING && FormatUtilities.FHIR_NS.equals(document.getDocumentElement().getNamespaceURI())) { 174 Node node = document.getFirstChild(); 175 while (node != null) { 176 if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) 177 logError(line(document), col(document), "(document)", IssueType.INVALID, context.formatMessage( 178 I18nConstants.NO_PROCESSING_INSTRUCTIONS_ALLOWED_IN_RESOURCES), IssueSeverity.ERROR); 179 node = node.getNextSibling(); 180 } 181 } 182 } 183 184 185 private int line(Node node) { 186 XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY); 187 return loc == null ? 0 : loc.getStartLine(); 188 } 189 190 private int col(Node node) { 191 XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY); 192 return loc == null ? 0 : loc.getStartColumn(); 193 } 194 195 public Element parse(Document doc) throws FHIRFormatError, DefinitionException, FHIRException, IOException { 196 checkForProcessingInstruction(doc); 197 org.w3c.dom.Element element = doc.getDocumentElement(); 198 return parse(element); 199 } 200 201 public Element parse(org.w3c.dom.Element element) throws FHIRFormatError, DefinitionException, FHIRException, IOException { 202 String ns = element.getNamespaceURI(); 203 String name = element.getLocalName(); 204 String path = "/"+pathPrefix(ns)+name; 205 206 StructureDefinition sd = getDefinition(line(element), col(element), (ns == null ? "noNamespace" : ns), name); 207 if (sd == null) 208 return null; 209 210 Element result = new Element(element.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd)); 211 result.setPath(element.getLocalName()); 212 checkElement(element, path, result.getProperty()); 213 result.markLocation(line(element), col(element)); 214 result.setType(element.getLocalName()); 215 parseChildren(path, element, result); 216 result.numberChildren(); 217 return result; 218 } 219 220 private String pathPrefix(String ns) { 221 if (Utilities.noString(ns)) 222 return ""; 223 if (ns.equals(FormatUtilities.FHIR_NS)) 224 return "f:"; 225 if (ns.equals(FormatUtilities.XHTML_NS)) 226 return "h:"; 227 if (ns.equals("urn:hl7-org:v3")) 228 return "v3:"; 229 if (ns.equals("urn:hl7-org:sdtc")) 230 return "sdtc:"; 231 if (ns.equals("urn:ihe:pharm")) 232 return "pharm:"; 233 return "?:"; 234 } 235 236 private boolean empty(org.w3c.dom.Element element) { 237 for (int i = 0; i < element.getAttributes().getLength(); i++) { 238 String n = element.getAttributes().item(i).getNodeName(); 239 if (!n.equals("xmlns") && !n.startsWith("xmlns:")) 240 return false; 241 } 242 if (!Utilities.noString(element.getTextContent().trim())) 243 return false; 244 245 Node n = element.getFirstChild(); 246 while (n != null) { 247 if (n.getNodeType() == Node.ELEMENT_NODE) 248 return false; 249 n = n.getNextSibling(); 250 } 251 return true; 252 } 253 254 private void checkElement(org.w3c.dom.Element element, String path, Property prop) throws FHIRFormatError { 255 if (policy == ValidationPolicy.EVERYTHING) { 256 if (empty(element) && FormatUtilities.FHIR_NS.equals(element.getNamespaceURI())) // this rule only applies to FHIR Content 257 logError(line(element), col(element), path, IssueType.INVALID, context.formatMessage(I18nConstants.ELEMENT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR); 258 String ns = prop.getXmlNamespace(); 259 String elementNs = element.getNamespaceURI(); 260 if (elementNs == null) { 261 elementNs = "noNamespace"; 262 } 263 if (!elementNs.equals(ns)) 264 logError(line(element), col(element), path, IssueType.INVALID, context.formatMessage(I18nConstants.WRONG_NAMESPACE__EXPECTED_, ns), IssueSeverity.ERROR); 265 } 266 } 267 268 public Element parse(org.w3c.dom.Element base, String type) throws Exception { 269 StructureDefinition sd = getDefinition(0, 0, FormatUtilities.FHIR_NS, type); 270 Element result = new Element(base.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd)); 271 result.setPath(base.getLocalName()); 272 String path = "/"+pathPrefix(base.getNamespaceURI())+base.getLocalName(); 273 checkElement(base, path, result.getProperty()); 274 result.setType(base.getLocalName()); 275 parseChildren(path, base, result); 276 result.numberChildren(); 277 return result; 278 } 279 280 private void parseChildren(String path, org.w3c.dom.Element node, Element element) throws FHIRFormatError, FHIRException, IOException, DefinitionException { 281 // this parsing routine retains the original order in a the XML file, to support validation 282 reapComments(node, element); 283 List<Property> properties = element.getProperty().getChildProperties(element.getName(), XMLUtil.getXsiType(node)); 284 285 String text = XMLUtil.getDirectText(node).trim(); 286 int line = line(node); 287 int col = col(node); 288 if (!Utilities.noString(text)) { 289 Property property = getTextProp(properties); 290 if (property != null) { 291 if ("ED.data[x]".equals(property.getDefinition().getId()) || (property.getDefinition()!=null && property.getDefinition().getBase()!=null && "ED.data[x]".equals(property.getDefinition().getBase().getPath()))) { 292 if ("B64".equals(node.getAttribute("representation"))) { 293 Element n = new Element("dataBase64Binary", property, "base64Binary", text).markLocation(line, col); 294 n.setPath(element.getPath()+"."+property.getName()); 295 element.getChildren().add(n); 296 } else { 297 Element n = new Element("dataString", property, "string", text).markLocation(line, col); 298 n.setPath(element.getPath()+"."+property.getName()); 299 element.getChildren().add(n); 300 } 301 } else { 302 Element n = new Element(property.getName(), property, property.getType(), text).markLocation(line, col); 303 n.setPath(element.getPath()+"."+property.getName()); 304 element.getChildren().add(n); 305 } 306 } 307 else { 308 Node n = node.getFirstChild(); 309 while (n != null) { 310 if (n.getNodeType() == Node.TEXT_NODE && !Utilities.noString(n.getTextContent().trim())) { 311 while (n.getNextSibling() != null && n.getNodeType() != Node.ELEMENT_NODE) { 312 n = n.getNextSibling(); 313 } 314 while (n.getPreviousSibling() != null && n.getNodeType() != Node.ELEMENT_NODE) { 315 n = n.getPreviousSibling(); 316 } 317 line = line(n); 318 col = col(n); 319 logError(line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.TEXT_SHOULD_NOT_BE_PRESENT, text), IssueSeverity.ERROR); 320 } 321 n = n.getNextSibling(); 322 } 323 } 324 } 325 326 for (int i = 0; i < node.getAttributes().getLength(); i++) { 327 Node attr = node.getAttributes().item(i); 328 String value = attr.getNodeValue(); 329 if (!validAttrValue(value)) { 330 logError(line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.XML_ATTR_VALUE_INVALID, attr.getNodeName()), IssueSeverity.ERROR); 331 } 332 if (!(attr.getNodeName().equals("xmlns") || attr.getNodeName().startsWith("xmlns:"))) { 333 Property property = getAttrProp(properties, attr.getLocalName(), attr.getNamespaceURI()); 334 if (property != null) { 335 String av = attr.getNodeValue(); 336 if (ToolingExtensions.hasExtension(property.getDefinition(), "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat")) 337 av = convertForDateFormatFromExternal(ToolingExtensions.readStringExtension(property.getDefinition(), "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat"), av); 338 if (property.getName().equals("value") && element.isPrimitive()) 339 element.setValue(av); 340 else { 341 Element n = new Element(property.getName(), property, property.getType(), av).markLocation(line, col); 342 n.setPath(element.getPath()+"."+property.getName()); 343 element.getChildren().add(n); 344 } 345 } else { 346 boolean ok = false; 347 if (FormatUtilities.FHIR_NS.equals(node.getNamespaceURI())) { 348 if (attr.getLocalName().equals("schemaLocation") && FormatUtilities.NS_XSI.equals(attr.getNamespaceURI())) { 349 ok = ok || allowXsiLocation; 350 } 351 } else 352 ok = ok || (attr.getLocalName().equals("schemaLocation")); // xsi:schemalocation allowed for non FHIR content 353 ok = ok || (hasTypeAttr(element) && attr.getLocalName().equals("type") && FormatUtilities.NS_XSI.equals(attr.getNamespaceURI())); // xsi:type allowed if element says so 354 if (!ok) 355 logError(line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ATTRIBUTE__ON__FOR_TYPE__PROPERTIES__, attr.getNodeName(), node.getNodeName(), element.fhirType(), properties), IssueSeverity.ERROR); 356 } 357 } 358 } 359 360 String lastName = null; 361 int repeatCount = 0; 362 Node child = node.getFirstChild(); 363 while (child != null) { 364 if (child.getNodeType() == Node.ELEMENT_NODE) { 365 Property property = getElementProp(properties, child.getLocalName(), child.getNamespaceURI()); 366 if (property != null) { 367 if (property.getName().equals(lastName)) { 368 repeatCount++; 369 } else { 370 lastName = property.getName(); 371 repeatCount = 0; 372 } 373 if (!property.isChoice() && "xhtml".equals(property.getType())) { 374 XhtmlNode xhtml; 375 if (property.getDefinition().hasRepresentation(PropertyRepresentation.CDATEXT)) 376 xhtml = new CDANarrativeFormat().convert((org.w3c.dom.Element) child); 377 else 378 xhtml = new XhtmlParser().setValidatorMode(true).parseHtmlNode((org.w3c.dom.Element) child); 379 Element n = new Element(property.getName(), property, "xhtml", new XhtmlComposer(XhtmlComposer.XML, false).compose(xhtml)).setXhtml(xhtml).markLocation(line(child), col(child)); 380 n.setPath(element.getPath()+"."+property.getName()); 381 element.getChildren().add(n); 382 } else { 383 String npath = path+"/"+pathPrefix(child.getNamespaceURI())+child.getLocalName(); 384 Element n = new Element(child.getLocalName(), property).markLocation(line(child), col(child)); 385 if (property.isList()) { 386 n.setPath(element.getPath()+"."+property.getName()+"["+repeatCount+"]"); 387 } else { 388 n.setPath(element.getPath()+"."+property.getName()); 389 } 390 checkElement((org.w3c.dom.Element) child, npath, n.getProperty()); 391 boolean ok = true; 392 if (property.isChoice()) { 393 if (property.getDefinition().hasRepresentation(PropertyRepresentation.TYPEATTR)) { 394 String xsiType = ((org.w3c.dom.Element) child).getAttributeNS(FormatUtilities.NS_XSI, "type"); 395 if (Utilities.noString(xsiType)) { 396 if (ToolingExtensions.hasExtension(property.getDefinition(), "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) { 397 xsiType = ToolingExtensions.readStringExtension(property.getDefinition(), "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"); 398 n.setType(xsiType); 399 } else { 400 logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NO_TYPE_FOUND_ON_, child.getLocalName()), IssueSeverity.ERROR); 401 ok = false; 402 } 403 } else { 404 if (xsiType.contains(":")) 405 xsiType = xsiType.substring(xsiType.indexOf(":")+1); 406 n.setType(xsiType); 407 n.setExplicitType(xsiType); 408 } 409 } else 410 n.setType(n.getType()); 411 } 412 element.getChildren().add(n); 413 if (ok) { 414 if (property.isResource()) 415 parseResource(npath, (org.w3c.dom.Element) child, n, property); 416 else 417 parseChildren(npath, (org.w3c.dom.Element) child, n); 418 } 419 } 420 } else 421 logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ELEMENT_, child.getLocalName()), IssueSeverity.ERROR); 422 } else if (child.getNodeType() == Node.CDATA_SECTION_NODE){ 423 logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.CDATA_IS_NOT_ALLOWED), IssueSeverity.ERROR); 424 } else if (!Utilities.existsInList(child.getNodeType(), 3, 8)) { 425 logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NODE_TYPE__IS_NOT_ALLOWED, Integer.toString(child.getNodeType())), IssueSeverity.ERROR); 426 } 427 child = child.getNextSibling(); 428 } 429 } 430 431 private boolean validAttrValue(String value) { 432 if (version == null) { 433 return true; 434 } 435 if (version.equals("1.0")) { 436 boolean ok = true; 437 for (char ch : value.toCharArray()) { 438 if (ch <= 0x1F && !Utilities.existsInList(ch, '\r', '\n', '\t')) { 439 ok = false; 440 } 441 } 442 return ok; 443 } else 444 return true; 445 } 446 447 448 private Property getElementProp(List<Property> properties, String nodeName, String namespace) { 449 List<Property> propsSortedByLongestFirst = new ArrayList<Property>(properties); 450 // sort properties according to their name longest first, so .requestOrganizationReference comes first before .request[x] 451 // and therefore the longer property names get evaluated first 452 Collections.sort(propsSortedByLongestFirst, new Comparator<Property>() { 453 @Override 454 public int compare(Property o1, Property o2) { 455 return o2.getName().length() - o1.getName().length(); 456 } 457 }); 458 // first scan, by namespace 459 for (Property p : propsSortedByLongestFirst) { 460 if (!p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) && !p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) { 461 if (p.getXmlName().equals(nodeName) && p.getXmlNamespace().equals(namespace)) 462 return p; 463 } 464 } 465 for (Property p : propsSortedByLongestFirst) { 466 if (!p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) && !p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) { 467 if (p.getXmlName().equals(nodeName)) 468 return p; 469 if (p.getName().endsWith("[x]") && nodeName.length() > p.getName().length()-3 && p.getName().substring(0, p.getName().length()-3).equals(nodeName.substring(0, p.getName().length()-3))) 470 return p; 471 } 472 } 473 return null; 474 } 475 476 private Property getAttrProp(List<Property> properties, String nodeName, String namespace) { 477 for (Property p : properties) { 478 if (p.getXmlName().equals(nodeName) && p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) && p.getXmlNamespace().equals(namespace)) { 479 return p; 480 } 481 } 482 if (namespace == null) { 483 for (Property p : properties) { 484 if (p.getXmlName().equals(nodeName) && p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR)) { 485 return p; 486 } 487 } 488 } 489 return null; 490 } 491 492 private Property getTextProp(List<Property> properties) { 493 for (Property p : properties) 494 if (p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) 495 return p; 496 return null; 497 } 498 499 private String convertForDateFormatFromExternal(String fmt, String av) throws FHIRException { 500 if ("v3".equals(fmt)) { 501 try { 502 DateTimeType d = DateTimeType.parseV3(av); 503 return d.asStringValue(); 504 } catch (Exception e) { 505 return av; // not at all clear what to do in this case. 506 } 507 } else 508 throw new FHIRException(context.formatMessage(I18nConstants.UNKNOWN_DATA_FORMAT_, fmt)); 509 } 510 511 private String convertForDateFormatToExternal(String fmt, String av) throws FHIRException { 512 if ("v3".equals(fmt)) { 513 DateTimeType d = new DateTimeType(av); 514 return d.getAsV3(); 515 } else 516 throw new FHIRException(context.formatMessage(I18nConstants.UNKNOWN_DATE_FORMAT_, fmt)); 517 } 518 519 private void parseResource(String string, org.w3c.dom.Element container, Element parent, Property elementProperty) throws FHIRFormatError, DefinitionException, FHIRException, IOException { 520 org.w3c.dom.Element res = XMLUtil.getFirstChild(container); 521 String name = res.getLocalName(); 522 StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, context.getOverrideVersionNs())); 523 if (sd == null) 524 throw new FHIRFormatError(context.formatMessage(I18nConstants.CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, res.getLocalName())); 525 parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd), SpecialElement.fromProperty(parent.getProperty()), elementProperty); 526 parent.setType(name); 527 parseChildren(res.getLocalName(), res, parent); 528 } 529 530 private void reapComments(org.w3c.dom.Element element, Element context) { 531 Node node = element.getPreviousSibling(); 532 while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { 533 if (node.getNodeType() == Node.COMMENT_NODE) 534 context.getComments().add(0, node.getTextContent()); 535 node = node.getPreviousSibling(); 536 } 537 node = element.getLastChild(); 538 while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { 539 node = node.getPreviousSibling(); 540 } 541 while (node != null) { 542 if (node.getNodeType() == Node.COMMENT_NODE) 543 context.getComments().add(node.getTextContent()); 544 node = node.getNextSibling(); 545 } 546 } 547 548 private boolean isAttr(Property property) { 549 for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) { 550 if (r.getValue() == PropertyRepresentation.XMLATTR) { 551 return true; 552 } 553 } 554 return false; 555 } 556 557 private boolean isCdaText(Property property) { 558 for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) { 559 if (r.getValue() == PropertyRepresentation.CDATEXT) { 560 return true; 561 } 562 } 563 return false; 564 } 565 566 private boolean isTypeAttr(Property property) { 567 for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) { 568 if (r.getValue() == PropertyRepresentation.TYPEATTR) { 569 return true; 570 } 571 } 572 return false; 573 } 574 575 private boolean isText(Property property) { 576 for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) { 577 if (r.getValue() == PropertyRepresentation.XMLTEXT) { 578 return true; 579 } 580 } 581 return false; 582 } 583 584 @Override 585 public void compose(Element e, OutputStream stream, OutputStyle style, String base) throws IOException, FHIRException { 586 XMLWriter xml = new XMLWriter(stream, "UTF-8"); 587 xml.setSortAttributes(false); 588 xml.setPretty(style == OutputStyle.PRETTY); 589 xml.start(); 590 String ns = e.getProperty().getXmlNamespace(); 591 if (ns!=null && !"noNamespace".equals(ns)) { 592 xml.setDefaultNamespace(ns); 593 } 594 if (hasTypeAttr(e)) 595 xml.namespace("http://www.w3.org/2001/XMLSchema-instance", "xsi"); 596 addNamespaces(xml, e); 597 composeElement(xml, e, e.getType(), true); 598 xml.end(); 599 } 600 601 private void addNamespaces(IXMLWriter xml, Element e) throws IOException { 602 String ns = e.getProperty().getXmlNamespace(); 603 if (ns!=null && xml.getDefaultNamespace()!=null && !xml.getDefaultNamespace().equals(ns)){ 604 if (!xml.namespaceDefined(ns)) { 605 String prefix = pathPrefix(ns); 606 if (prefix.endsWith(":")) { 607 prefix = prefix.substring(0, prefix.length()-1); 608 } 609 if ("?".equals(prefix)) { 610 xml.namespace(ns); 611 } else { 612 xml.namespace(ns, prefix); 613 } 614 } 615 } 616 for (Element c : e.getChildren()) { 617 addNamespaces(xml, c); 618 } 619 } 620 621 private boolean hasTypeAttr(Element e) { 622 if (isTypeAttr(e.getProperty())) 623 return true; 624 for (Element c : e.getChildren()) { 625 if (hasTypeAttr(c)) 626 return true; 627 } 628 return false; 629 } 630 631 private void setXsiTypeIfIsTypeAttr(IXMLWriter xml, Element element) throws IOException, FHIRException { 632 if (isTypeAttr(element.getProperty()) && !Utilities.noString(element.getType())) { 633 String type = element.getType(); 634 if (Utilities.isAbsoluteUrl(type)) { 635 type = type.substring(type.lastIndexOf("/")+1); 636 } 637 xml.attribute("xsi:type",type); 638 } 639 } 640 641 public void compose(Element e, IXMLWriter xml) throws Exception { 642 xml.start(); 643 xml.setDefaultNamespace(e.getProperty().getXmlNamespace()); 644 if (schemaPath != null) { 645 xml.setSchemaLocation(FormatUtilities.FHIR_NS, Utilities.pathURL(schemaPath, e.fhirType()+".xsd")); 646 } 647 composeElement(xml, e, e.getType(), true); 648 xml.end(); 649 } 650 651 private void composeElement(IXMLWriter xml, Element element, String elementName, boolean root) throws IOException, FHIRException { 652 if (showDecorations) { 653 @SuppressWarnings("unchecked") 654 List<ElementDecoration> decorations = (List<ElementDecoration>) element.getUserData("fhir.decorations"); 655 if (decorations != null) 656 for (ElementDecoration d : decorations) 657 xml.decorate(d); 658 } 659 for (String s : element.getComments()) { 660 xml.comment(s, true); 661 } 662 if (isText(element.getProperty())) { 663 if (linkResolver != null) 664 xml.link(linkResolver.resolveProperty(element.getProperty())); 665 xml.enter(element.getProperty().getXmlNamespace(),elementName); 666 xml.text(element.getValue()); 667 xml.exit(element.getProperty().getXmlNamespace(),elementName); 668 } else if (!element.hasChildren() && !element.hasValue()) { 669 if (element.getExplicitType() != null) 670 xml.attribute("xsi:type", element.getExplicitType()); 671 xml.element(elementName); 672 } else if (element.isPrimitive() || (element.hasType() && isPrimitive(element.getType()))) { 673 if (element.getType().equals("xhtml")) { 674 String rawXhtml = element.getValue(); 675 if (isCdaText(element.getProperty())) { 676 new CDANarrativeFormat().convert(xml, new XhtmlParser().parseFragment(rawXhtml)); 677 } else { 678 xml.escapedText(rawXhtml); 679 xml.anchor("end-xhtml"); 680 } 681 } else if (isText(element.getProperty())) { 682 if (linkResolver != null) 683 xml.link(linkResolver.resolveProperty(element.getProperty())); 684 xml.text(element.getValue()); 685 } else { 686 setXsiTypeIfIsTypeAttr(xml, element); 687 if (element.hasValue()) { 688 if (linkResolver != null) 689 xml.link(linkResolver.resolveType(element.getType())); 690 xml.attribute("value", element.getValue()); 691 } 692 if (linkResolver != null) 693 xml.link(linkResolver.resolveProperty(element.getProperty())); 694 if (element.hasChildren()) { 695 xml.enter(element.getProperty().getXmlNamespace(), elementName); 696 for (Element child : element.getChildren()) 697 composeElement(xml, child, child.getName(), false); 698 xml.exit(element.getProperty().getXmlNamespace(),elementName); 699 } else 700 xml.element(elementName); 701 } 702 } else { 703 setXsiTypeIfIsTypeAttr(xml, element); 704 for (Element child : element.getChildren()) { 705 if (isAttr(child.getProperty())) { 706 if (linkResolver != null) 707 xml.link(linkResolver.resolveType(child.getType())); 708 String av = child.getValue(); 709 if (ToolingExtensions.hasExtension(child.getProperty().getDefinition(), "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat")) 710 av = convertForDateFormatToExternal(ToolingExtensions.readStringExtension(child.getProperty().getDefinition(), "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat"), av); 711 xml.attribute(child.getProperty().getXmlNamespace(),child.getProperty().getXmlName(), av); 712 } 713 } 714 if (linkResolver != null) 715 xml.link(linkResolver.resolveProperty(element.getProperty())); 716 xml.enter(element.getProperty().getXmlNamespace(),elementName); 717 if (!root && element.getSpecial() != null) { 718 if (linkResolver != null) 719 xml.link(linkResolver.resolveProperty(element.getProperty())); 720 xml.enter(element.getProperty().getXmlNamespace(),element.getType()); 721 } 722 for (Element child : element.getChildren()) { 723 if (isText(child.getProperty())) { 724 if (linkResolver != null) 725 xml.link(linkResolver.resolveProperty(element.getProperty())); 726 xml.text(child.getValue()); 727 } else if (!isAttr(child.getProperty())) 728 composeElement(xml, child, child.getName(), false); 729 } 730 if (!root && element.getSpecial() != null) 731 xml.exit(element.getProperty().getXmlNamespace(),element.getType()); 732 xml.exit(element.getProperty().getXmlNamespace(),elementName); 733 } 734 } 735 736 private String checkHeader(InputStream stream) throws IOException { 737 try { 738 // the stream will either start with the UTF-8 BOF or with <xml 739 int i0 = stream.read(); 740 int i1 = stream.read(); 741 int i2 = stream.read(); 742 743 StringBuilder b = new StringBuilder(); 744 if (i0 == 0xEF && i1 == 0xBB && i2 == 0xBF) { 745 // ok, it's UTF-8 746 } else if (i0 == 0x3C && i1 == 0x3F && i2 == 0x78) { // <xm 747 b.append((char) i0); 748 b.append((char) i1); 749 b.append((char) i2); 750 } else if (i0 == 60) { // just plain old XML with no header 751 return "1.0"; 752 } else { 753 throw new Exception(context.formatMessage(I18nConstants.XML_ENCODING_INVALID)); 754 } 755 int i = stream.read(); 756 do { 757 b.append((char) i); 758 i = stream.read(); 759 } while (i != 0x3E); 760 String header = b.toString(); 761 String e = null; 762 i = header.indexOf("encoding=\""); 763 if (i > -1) { 764 e = header.substring(i+10, i+15); 765 } else { 766 i = header.indexOf("encoding='"); 767 if (i > -1) { 768 e = header.substring(i+10, i+15); 769 } 770 } 771 if (e != null && !"UTF-8".equalsIgnoreCase(e)) { 772 logError(0, 0, "XML", IssueType.INVALID, context.formatMessage(I18nConstants.XML_ENCODING_INVALID), IssueSeverity.ERROR); 773 } 774 775 i = header.indexOf("version=\""); 776 if (i > -1) { 777 return header.substring(i+9, i+12); 778 } else { 779 i = header.indexOf("version='"); 780 if (i > -1) { 781 return header.substring(i+9, i+12); 782 } 783 } 784 return "?xml-p1?"; 785 } catch (Exception e) { 786 // suppress this error 787 logError(0, 0, "XML", IssueType.INVALID, e.getMessage(), IssueSeverity.ERROR); 788 } 789 return "?xml-p2?"; 790 } 791 792}