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