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}