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