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