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