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