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