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