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