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