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