001package org.hl7.fhir.r4.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 032import java.io.IOException; 033import java.io.InputStream; 034import java.io.OutputStream; 035import java.util.ArrayList; 036import java.util.Collections; 037import java.util.Comparator; 038import java.util.List; 039 040import javax.xml.parsers.DocumentBuilder; 041import javax.xml.parsers.DocumentBuilderFactory; 042import javax.xml.parsers.SAXParser; 043import javax.xml.parsers.SAXParserFactory; 044import javax.xml.transform.Transformer; 045import javax.xml.transform.TransformerFactory; 046import javax.xml.transform.dom.DOMResult; 047import javax.xml.transform.sax.SAXSource; 048 049import org.hl7.fhir.exceptions.DefinitionException; 050import org.hl7.fhir.exceptions.FHIRException; 051import org.hl7.fhir.exceptions.FHIRFormatError; 052import org.hl7.fhir.r4.conformance.ProfileUtilities; 053import org.hl7.fhir.r4.context.IWorkerContext; 054import org.hl7.fhir.r4.elementmodel.Element.SpecialElement; 055import org.hl7.fhir.r4.formats.FormatUtilities; 056import org.hl7.fhir.r4.formats.IParser.OutputStyle; 057import org.hl7.fhir.r4.model.DateTimeType; 058import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation; 059import org.hl7.fhir.r4.model.Enumeration; 060import org.hl7.fhir.r4.model.StructureDefinition; 061import org.hl7.fhir.r4.utils.ToolingExtensions; 062import org.hl7.fhir.r4.utils.formats.XmlLocationAnnotator; 063import org.hl7.fhir.r4.utils.formats.XmlLocationData; 064import org.hl7.fhir.utilities.ElementDecoration; 065import org.hl7.fhir.utilities.StringPair; 066import org.hl7.fhir.utilities.Utilities; 067import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 068import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 069import org.hl7.fhir.utilities.xhtml.CDANarrativeFormat; 070import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 071import org.hl7.fhir.utilities.xhtml.XhtmlNode; 072import org.hl7.fhir.utilities.xhtml.XhtmlParser; 073import org.hl7.fhir.utilities.xml.IXMLWriter; 074import org.hl7.fhir.utilities.xml.XMLUtil; 075import org.hl7.fhir.utilities.xml.XMLWriter; 076import org.w3c.dom.Document; 077import org.w3c.dom.Node; 078import org.xml.sax.InputSource; 079import org.xml.sax.XMLReader; 080 081public class XmlParser extends ParserBase { 082 private boolean allowXsiLocation; 083 084 public XmlParser(IWorkerContext context) { 085 super(context); 086 } 087 088 public boolean isAllowXsiLocation() { 089 return allowXsiLocation; 090 } 091 092 public void setAllowXsiLocation(boolean allowXsiLocation) { 093 this.allowXsiLocation = allowXsiLocation; 094 } 095 096 public Element parse(InputStream stream) throws FHIRFormatError, DefinitionException, FHIRException, IOException { 097 Document doc = null; 098 try { 099 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 100 // xxe protection 101 factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 102 factory.setFeature("http://xml.org/sax/features/external-general-entities", false); 103 factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); 104 factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); 105 factory.setXIncludeAware(false); 106 factory.setExpandEntityReferences(false); 107 108 factory.setNamespaceAware(true); 109 if (policy == ValidationPolicy.EVERYTHING) { 110 // use a slower parser that keeps location data 111 TransformerFactory transformerFactory = XMLUtil.newXXEProtectedTransformerFactory(); 112 Transformer nullTransformer = transformerFactory.newTransformer(); 113 DocumentBuilder docBuilder = factory.newDocumentBuilder(); 114 doc = docBuilder.newDocument(); 115 DOMResult domResult = new DOMResult(doc); 116 SAXParserFactory spf = SAXParserFactory.newInstance(); 117 spf.setNamespaceAware(true); 118 spf.setValidating(false); 119 // xxe protection 120 spf.setFeature("http://xml.org/sax/features/external-general-entities", false); 121 spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 122 SAXParser saxParser = spf.newSAXParser(); 123 XMLReader xmlReader = saxParser.getXMLReader(); 124 // xxe protection 125 xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false); 126 xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 127 128 XmlLocationAnnotator locationAnnotator = new XmlLocationAnnotator(xmlReader, doc); 129 InputSource inputSource = new InputSource(stream); 130 SAXSource saxSource = new SAXSource(locationAnnotator, inputSource); 131 nullTransformer.transform(saxSource, domResult); 132 } else { 133 DocumentBuilder builder = factory.newDocumentBuilder(); 134 doc = builder.parse(stream); 135 } 136 } catch (Exception e) { 137 logError(0, 0, "(syntax)", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL); 138 doc = null; 139 } 140 if (doc == null) 141 return null; 142 else 143 return parse(doc); 144 } 145 146 private void checkForProcessingInstruction(Document document) throws FHIRFormatError { 147 if (policy == ValidationPolicy.EVERYTHING 148 && FormatUtilities.FHIR_NS.equals(document.getDocumentElement().getNamespaceURI())) { 149 Node node = document.getFirstChild(); 150 while (node != null) { 151 if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) 152 logError(line(document), col(document), "(document)", IssueType.INVALID, 153 "No processing instructions allowed in resources", IssueSeverity.ERROR); 154 node = node.getNextSibling(); 155 } 156 } 157 } 158 159 private int line(Node node) { 160 XmlLocationData loc = (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY); 161 return loc == null ? 0 : loc.getStartLine(); 162 } 163 164 private int col(Node node) { 165 XmlLocationData loc = (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY); 166 return loc == null ? 0 : loc.getStartColumn(); 167 } 168 169 public Element parse(Document doc) throws FHIRFormatError, DefinitionException, FHIRException, IOException { 170 checkForProcessingInstruction(doc); 171 org.w3c.dom.Element element = doc.getDocumentElement(); 172 return parse(element); 173 } 174 175 public Element parse(org.w3c.dom.Element element) 176 throws FHIRFormatError, DefinitionException, FHIRException, IOException { 177 String ns = element.getNamespaceURI(); 178 String name = element.getLocalName(); 179 String path = "/" + pathPrefix(ns) + name; 180 181 StructureDefinition sd = getDefinition(line(element), col(element), ns, name); 182 if (sd == null) 183 return null; 184 185 Element result = new Element(element.getLocalName(), 186 new Property(context, sd.getSnapshot().getElement().get(0), sd)); 187 checkElement(element, path, result.getProperty()); 188 result.markLocation(line(element), col(element)); 189 result.setType(element.getLocalName()); 190 parseChildren(path, element, result); 191 result.numberChildren(); 192 return result; 193 } 194 195 private String pathPrefix(String ns) { 196 if (Utilities.noString(ns)) 197 return ""; 198 if (ns.equals(FormatUtilities.FHIR_NS)) 199 return "f:"; 200 if (ns.equals(FormatUtilities.XHTML_NS)) 201 return "h:"; 202 if (ns.equals("urn:hl7-org:v3")) 203 return "v3:"; 204 return "?:"; 205 } 206 207 private boolean empty(org.w3c.dom.Element element) { 208 for (int i = 0; i < element.getAttributes().getLength(); i++) { 209 String n = element.getAttributes().item(i).getNodeName(); 210 if (!n.equals("xmlns") && !n.startsWith("xmlns:")) 211 return false; 212 } 213 if (!Utilities.noString(element.getTextContent().trim())) 214 return false; 215 216 Node n = element.getFirstChild(); 217 while (n != null) { 218 if (n.getNodeType() == Node.ELEMENT_NODE) 219 return false; 220 n = n.getNextSibling(); 221 } 222 return true; 223 } 224 225 private void checkElement(org.w3c.dom.Element element, String path, Property prop) throws FHIRFormatError { 226 if (policy == ValidationPolicy.EVERYTHING) { 227 if (empty(element) && FormatUtilities.FHIR_NS.equals(element.getNamespaceURI())) // this rule only applies to FHIR 228 // Content 229 logError(line(element), col(element), path, IssueType.INVALID, "Element must have some content", 230 IssueSeverity.ERROR); 231 String ns = FormatUtilities.FHIR_NS; 232 if (ToolingExtensions.hasExtension(prop.getDefinition(), 233 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) 234 ns = ToolingExtensions.readStringExtension(prop.getDefinition(), 235 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 236 else if (ToolingExtensions.hasExtension(prop.getStructure(), 237 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) 238 ns = ToolingExtensions.readStringExtension(prop.getStructure(), 239 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 240 if (!element.getNamespaceURI().equals(ns)) 241 logError(line(element), col(element), path, IssueType.INVALID, "Wrong namespace - expected '" + ns + "'", 242 IssueSeverity.ERROR); 243 } 244 } 245 246 public Element parse(org.w3c.dom.Element base, String type) throws Exception { 247 StructureDefinition sd = getDefinition(0, 0, FormatUtilities.FHIR_NS, type); 248 Element result = new Element(base.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd)); 249 String path = "/" + pathPrefix(base.getNamespaceURI()) + base.getLocalName(); 250 checkElement(base, path, result.getProperty()); 251 result.setType(base.getLocalName()); 252 parseChildren(path, base, result); 253 result.numberChildren(); 254 return result; 255 } 256 257 private void parseChildren(String path, org.w3c.dom.Element node, Element context) 258 throws FHIRFormatError, FHIRException, IOException, DefinitionException { 259 // this parsing routine retains the original order in a the XML file, to support 260 // validation 261 reapComments(node, context); 262 List<Property> properties = context.getProperty().getChildProperties(context.getName(), XMLUtil.getXsiType(node)); 263 264 String text = XMLUtil.getDirectText(node).trim(); 265 if (!Utilities.noString(text)) { 266 Property property = getTextProp(properties); 267 if (property != null) { 268 context.getChildren().add( 269 new Element(property.getName(), property, property.getType(), text).markLocation(line(node), col(node))); 270 } else { 271 logError(line(node), col(node), path, IssueType.STRUCTURE, "Text should not be present", IssueSeverity.ERROR); 272 } 273 } 274 275 for (int i = 0; i < node.getAttributes().getLength(); i++) { 276 Node attr = node.getAttributes().item(i); 277 if (!(attr.getNodeName().equals("xmlns") || attr.getNodeName().startsWith("xmlns:"))) { 278 Property property = getAttrProp(properties, attr.getNodeName()); 279 if (property != null) { 280 String av = attr.getNodeValue(); 281 if (ToolingExtensions.hasExtension(property.getDefinition(), 282 "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat")) 283 av = convertForDateFormatFromExternal(ToolingExtensions.readStringExtension(property.getDefinition(), 284 "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat"), av); 285 if (property.getName().equals("value") && context.isPrimitive()) 286 context.setValue(av); 287 else 288 context.getChildren().add( 289 new Element(property.getName(), property, property.getType(), av).markLocation(line(node), col(node))); 290 } else { 291 boolean ok = false; 292 if (FormatUtilities.FHIR_NS.equals(node.getNamespaceURI())) { 293 if (attr.getLocalName().equals("schemaLocation") && FormatUtilities.NS_XSI.equals(attr.getNamespaceURI())) { 294 ok = ok || allowXsiLocation; 295 } 296 } else 297 ok = ok || (attr.getLocalName().equals("schemaLocation")); // xsi:schemalocation allowed for non FHIR 298 // content 299 ok = ok || (hasTypeAttr(context) && attr.getLocalName().equals("type") 300 && FormatUtilities.NS_XSI.equals(attr.getNamespaceURI())); // xsi:type allowed if element says so 301 if (!ok) 302 logError(line(node), col(node), path, IssueType.STRUCTURE, 303 "Undefined attribute '@" + attr.getNodeName() + "' on " + node.getNodeName() + " for type " 304 + context.fhirType() + " (properties = " + properties + ")", 305 IssueSeverity.ERROR); 306 } 307 } 308 } 309 310 Node child = node.getFirstChild(); 311 while (child != null) { 312 if (child.getNodeType() == Node.ELEMENT_NODE) { 313 Property property = getElementProp(properties, child.getLocalName()); 314 if (property != null) { 315 if (!property.isChoice() && "xhtml".equals(property.getType())) { 316 XhtmlNode xhtml; 317 if (property.getDefinition().hasRepresentation(PropertyRepresentation.CDATEXT)) 318 xhtml = new CDANarrativeFormat().convert((org.w3c.dom.Element) child); 319 else { 320 XhtmlParser xp = new XhtmlParser(); 321 xhtml = xp.parseHtmlNode((org.w3c.dom.Element) child); 322 if (policy == ValidationPolicy.EVERYTHING) { 323 for (StringPair s : xp.getValidationIssues()) { 324 logError(line(child), col(child), path, IssueType.INVALID, s.getName() + " " + s.getValue(), 325 IssueSeverity.ERROR); 326 } 327 } 328 } 329 context.getChildren() 330 .add(new Element(property.getName(), property, "xhtml", 331 new XhtmlComposer(XhtmlComposer.XML, false).compose(xhtml)).setXhtml(xhtml) 332 .markLocation(line(child), col(child))); 333 } else { 334 String npath = path + "/" + pathPrefix(child.getNamespaceURI()) + child.getLocalName(); 335 Element n = new Element(child.getLocalName(), property).markLocation(line(child), col(child)); 336 checkElement((org.w3c.dom.Element) child, npath, n.getProperty()); 337 boolean ok = true; 338 if (property.isChoice()) { 339 if (property.getDefinition().hasRepresentation(PropertyRepresentation.TYPEATTR)) { 340 String xsiType = ((org.w3c.dom.Element) child).getAttributeNS(FormatUtilities.NS_XSI, "type"); 341 if (Utilities.noString(xsiType)) { 342 logError(line(child), col(child), path, IssueType.STRUCTURE, 343 "No type found on '" + child.getLocalName() + '"', IssueSeverity.ERROR); 344 ok = false; 345 } else { 346 if (xsiType.contains(":")) 347 xsiType = xsiType.substring(xsiType.indexOf(":") + 1); 348 n.setType(xsiType); 349 n.setExplicitType(xsiType); 350 } 351 } else 352 n.setType(n.getType()); 353 } 354 context.getChildren().add(n); 355 if (ok) { 356 if (property.isResource()) 357 parseResource(npath, (org.w3c.dom.Element) child, n, property); 358 else 359 parseChildren(npath, (org.w3c.dom.Element) child, n); 360 } 361 } 362 } else 363 logError(line(child), col(child), path, IssueType.STRUCTURE, 364 "Undefined element '" + child.getLocalName() + "'", IssueSeverity.ERROR); 365 } else if (child.getNodeType() == Node.CDATA_SECTION_NODE) { 366 logError(line(child), col(child), path, IssueType.STRUCTURE, "CDATA is not allowed", IssueSeverity.ERROR); 367 } else if (!Utilities.existsInList(child.getNodeType(), 3, 8)) { 368 logError(line(child), col(child), path, IssueType.STRUCTURE, 369 "Node type " + Integer.toString(child.getNodeType()) + " is not allowed", IssueSeverity.ERROR); 370 } 371 child = child.getNextSibling(); 372 } 373 } 374 375 private Property getElementProp(List<Property> properties, String nodeName) { 376 List<Property> propsSortedByLongestFirst = new ArrayList<Property>(properties); 377 // sort properties according to their name longest first, so 378 // .requestOrganizationReference comes first before .request[x] 379 // and therefore the longer property names get evaluated first 380 Collections.sort(propsSortedByLongestFirst, new Comparator<Property>() { 381 @Override 382 public int compare(Property o1, Property o2) { 383 return o2.getName().length() - o1.getName().length(); 384 } 385 }); 386 for (Property p : propsSortedByLongestFirst) 387 if (!p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) 388 && !p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) { 389 if (p.getName().equals(nodeName)) 390 return p; 391 if (p.getName().endsWith("[x]") && nodeName.length() > p.getName().length() - 3 && p.getName() 392 .substring(0, p.getName().length() - 3).equals(nodeName.substring(0, p.getName().length() - 3))) 393 return p; 394 } 395 return null; 396 } 397 398 private Property getAttrProp(List<Property> properties, String nodeName) { 399 for (Property p : properties) 400 if (p.getName().equals(nodeName) && p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR)) 401 return p; 402 return null; 403 } 404 405 private Property getTextProp(List<Property> properties) { 406 for (Property p : properties) 407 if (p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) 408 return p; 409 return null; 410 } 411 412 private String convertForDateFormatFromExternal(String fmt, String av) throws FHIRException { 413 if ("v3".equals(fmt)) { 414 DateTimeType d = DateTimeType.parseV3(av); 415 return d.asStringValue(); 416 } else 417 throw new FHIRException("Unknown Data format '" + fmt + "'"); 418 } 419 420 private String convertForDateFormatToExternal(String fmt, String av) throws FHIRException { 421 if ("v3".equals(fmt)) { 422 DateTimeType d = new DateTimeType(av); 423 return d.getAsV3(); 424 } else 425 throw new FHIRException("Unknown Date format '" + fmt + "'"); 426 } 427 428 private void parseResource(String string, org.w3c.dom.Element container, Element parent, Property elementProperty) 429 throws FHIRFormatError, DefinitionException, FHIRException, IOException { 430 org.w3c.dom.Element res = XMLUtil.getFirstChild(container); 431 String name = res.getLocalName(); 432 StructureDefinition sd = context.fetchResource(StructureDefinition.class, 433 ProfileUtilities.sdNs(name, context.getOverrideVersionNs())); 434 if (sd == null) 435 throw new FHIRFormatError( 436 "Contained resource does not appear to be a FHIR resource (unknown name '" + res.getLocalName() + "')"); 437 parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd), 438 SpecialElement.fromProperty(parent.getProperty()), elementProperty); 439 parent.setType(name); 440 parseChildren(res.getLocalName(), res, parent); 441 } 442 443 private void reapComments(org.w3c.dom.Element element, Element context) { 444 Node node = element.getPreviousSibling(); 445 while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { 446 if (node.getNodeType() == Node.COMMENT_NODE) 447 context.getComments().add(0, node.getTextContent()); 448 node = node.getPreviousSibling(); 449 } 450 node = element.getLastChild(); 451 while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { 452 node = node.getPreviousSibling(); 453 } 454 while (node != null) { 455 if (node.getNodeType() == Node.COMMENT_NODE) 456 context.getComments().add(node.getTextContent()); 457 node = node.getNextSibling(); 458 } 459 } 460 461 private boolean isAttr(Property property) { 462 for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) { 463 if (r.getValue() == PropertyRepresentation.XMLATTR) { 464 return true; 465 } 466 } 467 return false; 468 } 469 470 private boolean isCdaText(Property property) { 471 for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) { 472 if (r.getValue() == PropertyRepresentation.CDATEXT) { 473 return true; 474 } 475 } 476 return false; 477 } 478 479 private boolean isTypeAttr(Property property) { 480 for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) { 481 if (r.getValue() == PropertyRepresentation.TYPEATTR) { 482 return true; 483 } 484 } 485 return false; 486 } 487 488 private boolean isText(Property property) { 489 for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) { 490 if (r.getValue() == PropertyRepresentation.XMLTEXT) { 491 return true; 492 } 493 } 494 return false; 495 } 496 497 @Override 498 public void compose(Element e, OutputStream stream, OutputStyle style, String base) 499 throws IOException, FHIRException { 500 XMLWriter xml = new XMLWriter(stream, "UTF-8"); 501 xml.setSortAttributes(false); 502 xml.setPretty(style == OutputStyle.PRETTY); 503 xml.start(); 504 xml.setDefaultNamespace(e.getProperty().getNamespace()); 505 if (hasTypeAttr(e)) 506 xml.namespace("http://www.w3.org/2001/XMLSchema-instance", "xsi"); 507 composeElement(xml, e, e.getType(), true); 508 xml.end(); 509 510 } 511 512 private boolean hasTypeAttr(Element e) { 513 if (isTypeAttr(e.getProperty())) 514 return true; 515 for (Element c : e.getChildren()) { 516 if (hasTypeAttr(c)) 517 return true; 518 } 519 return false; 520 } 521 522 public void compose(Element e, IXMLWriter xml) throws Exception { 523 xml.start(); 524 xml.setDefaultNamespace(e.getProperty().getNamespace()); 525 composeElement(xml, e, e.getType(), true); 526 xml.end(); 527 } 528 529 private void composeElement(IXMLWriter xml, Element element, String elementName, boolean root) 530 throws IOException, FHIRException { 531 if (showDecorations) { 532 @SuppressWarnings("unchecked") 533 List<ElementDecoration> decorations = (List<ElementDecoration>) element.getUserData("fhir.decorations"); 534 if (decorations != null) 535 for (ElementDecoration d : decorations) 536 xml.decorate(d); 537 } 538 for (String s : element.getComments()) { 539 xml.comment(s, true); 540 } 541 if (isText(element.getProperty())) { 542 if (linkResolver != null) 543 xml.link(linkResolver.resolveProperty(element.getProperty())); 544 xml.enter(elementName); 545 xml.text(element.getValue()); 546 xml.exit(elementName); 547 } else if (!element.hasChildren() && !element.hasValue()) { 548 if (element.getExplicitType() != null) 549 xml.attribute("xsi:type", element.getExplicitType()); 550 xml.element(elementName); 551 } else if (element.isPrimitive() || (element.hasType() && isPrimitive(element.getType()))) { 552 if (element.getType().equals("xhtml")) { 553 String rawXhtml = element.getValue(); 554 if (isCdaText(element.getProperty())) { 555 new CDANarrativeFormat().convert(xml, element.getXhtml()); 556 } else 557 xml.escapedText(rawXhtml); 558 } else if (isText(element.getProperty())) { 559 if (linkResolver != null) 560 xml.link(linkResolver.resolveProperty(element.getProperty())); 561 xml.text(element.getValue()); 562 } else { 563 if (isTypeAttr(element.getProperty()) && !Utilities.noString(element.getType())) { 564 xml.attribute("xsi:type", element.getType()); 565 } 566 if (element.hasValue()) { 567 if (linkResolver != null) 568 xml.link(linkResolver.resolveType(element.getType())); 569 xml.attribute("value", element.getValue()); 570 } 571 if (linkResolver != null) 572 xml.link(linkResolver.resolveProperty(element.getProperty())); 573 if (element.hasChildren()) { 574 xml.enter(elementName); 575 for (Element child : element.getChildren()) 576 composeElement(xml, child, child.getName(), false); 577 xml.exit(elementName); 578 } else 579 xml.element(elementName); 580 } 581 } else { 582 if (isTypeAttr(element.getProperty()) && !Utilities.noString(element.getType())) { 583 xml.attribute("xsi:type", element.getType()); 584 } 585 for (Element child : element.getChildren()) { 586 if (isAttr(child.getProperty())) { 587 if (linkResolver != null) 588 xml.link(linkResolver.resolveType(child.getType())); 589 String av = child.getValue(); 590 if (ToolingExtensions.hasExtension(child.getProperty().getDefinition(), 591 "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat")) 592 av = convertForDateFormatToExternal( 593 ToolingExtensions.readStringExtension(child.getProperty().getDefinition(), 594 "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat"), 595 av); 596 xml.attribute(child.getName(), av); 597 } 598 } 599 if (linkResolver != null) 600 xml.link(linkResolver.resolveProperty(element.getProperty())); 601 xml.enter(elementName); 602 if (!root && element.getSpecial() != null) { 603 if (linkResolver != null) 604 xml.link(linkResolver.resolveProperty(element.getProperty())); 605 xml.enter(element.getType()); 606 } 607 for (Element child : element.getChildren()) { 608 if (isText(child.getProperty())) { 609 if (linkResolver != null) 610 xml.link(linkResolver.resolveProperty(element.getProperty())); 611 xml.text(child.getValue()); 612 } else if (!isAttr(child.getProperty())) 613 composeElement(xml, child, child.getName(), false); 614 } 615 if (!root && element.getSpecial() != null) 616 xml.exit(element.getType()); 617 xml.exit(elementName); 618 } 619 } 620 621}