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