001package org.hl7.fhir.r5.elementmodel;
002
003import java.io.ByteArrayInputStream;
004
005/*
006  Copyright (c) 2011+, HL7, Inc.
007  All rights reserved.
008
009  Redistribution and use in source and binary forms, with or without modification, 
010  are permitted provided that the following conditions are met:
011
012 * Redistributions of source code must retain the above copyright notice, this 
013     list of conditions and the following disclaimer.
014 * Redistributions in binary form must reproduce the above copyright notice, 
015     this list of conditions and the following disclaimer in the documentation 
016     and/or other materials provided with the distribution.
017 * Neither the name of HL7 nor the names of its contributors may be used to 
018     endorse or promote products derived from this software without specific 
019     prior written permission.
020
021  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
022  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
023  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
024  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
025  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
026  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
027  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
028  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
029  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
030  POSSIBILITY OF SUCH DAMAGE.
031
032 */
033
034
035import java.io.IOException;
036import java.io.InputStream;
037import java.io.OutputStream;
038import java.util.ArrayList;
039import java.util.Collections;
040import java.util.Comparator;
041import java.util.HashSet;
042import java.util.List;
043import java.util.Set;
044
045import javax.xml.parsers.DocumentBuilder;
046import javax.xml.parsers.DocumentBuilderFactory;
047import javax.xml.parsers.SAXParserFactory;
048import javax.xml.transform.Transformer;
049import javax.xml.transform.TransformerFactory;
050import javax.xml.transform.dom.DOMResult;
051import javax.xml.transform.sax.SAXSource;
052
053import org.hl7.fhir.exceptions.DefinitionException;
054import org.hl7.fhir.exceptions.FHIRException;
055import org.hl7.fhir.exceptions.FHIRFormatError;
056import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
057import org.hl7.fhir.r5.context.IWorkerContext;
058import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
059import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
060import org.hl7.fhir.r5.formats.FormatUtilities;
061import org.hl7.fhir.r5.formats.IParser.OutputStyle;
062import org.hl7.fhir.r5.model.Constants;
063import org.hl7.fhir.r5.model.DateTimeType;
064import org.hl7.fhir.r5.model.ElementDefinition;
065import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation;
066import org.hl7.fhir.r5.model.Enumeration;
067import org.hl7.fhir.r5.model.StructureDefinition;
068import org.hl7.fhir.r5.utils.ToolingExtensions;
069import org.hl7.fhir.r5.utils.UserDataNames;
070import org.hl7.fhir.r5.utils.formats.XmlLocationAnnotator;
071import org.hl7.fhir.r5.utils.formats.XmlLocationData;
072import org.hl7.fhir.utilities.ElementDecoration;
073import org.hl7.fhir.utilities.StringPair;
074import org.hl7.fhir.utilities.FileUtilities;
075import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
076import org.hl7.fhir.utilities.Utilities;
077import org.hl7.fhir.utilities.i18n.I18nConstants;
078import org.hl7.fhir.utilities.validation.ValidationMessage;
079import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
080import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
081import org.hl7.fhir.utilities.xhtml.CDANarrativeFormat;
082import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
083import org.hl7.fhir.utilities.xhtml.XhtmlNode;
084import org.hl7.fhir.utilities.xhtml.XhtmlParser;
085import org.hl7.fhir.utilities.xml.IXMLWriter;
086import org.hl7.fhir.utilities.xml.XMLUtil;
087import org.hl7.fhir.utilities.xml.XMLWriter;
088import org.w3c.dom.Document;
089import org.w3c.dom.Node;
090import org.xml.sax.ErrorHandler;
091import org.xml.sax.InputSource;
092import org.xml.sax.SAXParseException;
093import org.xml.sax.XMLReader;
094
095@MarkedToMoveToAdjunctPackage
096public class XmlParser extends ParserBase {
097  private boolean allowXsiLocation;
098  private String version;
099  private boolean elideElements;
100
101  public XmlParser(IWorkerContext context) {
102    super(context);
103  }
104
105  private String schemaPath;
106  private boolean markedXhtml;
107
108  public String getSchemaPath() {
109    return schemaPath;
110  }
111  public void setSchemaPath(String schemaPath) {
112    this.schemaPath = schemaPath;
113  }
114
115  public boolean isAllowXsiLocation() {
116    return allowXsiLocation;
117  }
118
119  public void setAllowXsiLocation(boolean allowXsiLocation) {
120    this.allowXsiLocation = allowXsiLocation;
121  }
122
123  public List<ValidatedFragment> parse(InputStream inStream) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
124
125    byte[] content = FileUtilities.streamToBytes(inStream);
126    ValidatedFragment focusFragment = new ValidatedFragment(ValidatedFragment.FOCUS_NAME, "xml", content, false);
127
128    ByteArrayInputStream stream = new ByteArrayInputStream(content);
129    Document doc = null;
130    try {
131      DocumentBuilderFactory factory = XMLUtil.newXXEProtectedDocumentBuilderFactory();
132      // xxe protection
133      factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
134      factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
135      factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
136      factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
137      factory.setXIncludeAware(false);
138      factory.setExpandEntityReferences(false);
139
140      factory.setNamespaceAware(true);
141      if (policy == ValidationPolicy.EVERYTHING) {
142        // The SAX interface appears to not work when reporting the correct version/encoding.
143        // if we can, we'll inspect the header/encoding ourselves 
144
145        stream.mark(1024);
146        version = checkHeader(focusFragment.getErrors(), stream);
147        stream.reset();
148
149        // use a slower parser that keeps location data
150        TransformerFactory transformerFactory = XMLUtil.newXXEProtectedTransformerFactory();
151        Transformer nullTransformer = transformerFactory.newTransformer();
152        DocumentBuilder docBuilder = factory.newDocumentBuilder();
153        doc = docBuilder.newDocument();
154        DOMResult domResult = new DOMResult(doc);
155        SAXParserFactory spf = XMLUtil.newXXEProtectedSaxParserFactory();
156        spf.setNamespaceAware(true);
157        spf.setValidating(false);
158
159        XMLReader xmlReader = XMLUtil.getXXEProtectedXMLReader(spf);
160
161        XmlLocationAnnotator locationAnnotator = new XmlLocationAnnotator(xmlReader, doc);
162        InputSource inputSource = new InputSource(stream);
163        SAXSource saxSource = new SAXSource(locationAnnotator, inputSource);
164        nullTransformer.transform(saxSource, domResult);
165      } else {
166        DocumentBuilder builder = factory.newDocumentBuilder();
167        builder.setErrorHandler(new NullErrorHandler());
168        doc = builder.parse(stream);
169      }
170    } catch (Exception e) {
171      if (e.getMessage().contains("lineNumber:") && e.getMessage().contains("columnNumber:")) {
172        int line = Utilities.parseInt(extractVal(e.getMessage(), "lineNumber"), 0); 
173        int col = Utilities.parseInt(extractVal(e.getMessage(), "columnNumber"), 0); 
174        logError(focusFragment.getErrors(), ValidationMessage.NO_RULE_DATE, line, col, "(xml)", IssueType.INVALID, e.getMessage().substring(e.getMessage().lastIndexOf(";")+1).trim(), IssueSeverity.FATAL);
175      } else {
176        logError(focusFragment.getErrors(), ValidationMessage.NO_RULE_DATE, 0, 0, "(xml)", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL);
177      }
178      doc = null;
179    }
180    if (doc != null) {
181      focusFragment.setElement(parse(focusFragment.getErrors(), doc));
182    }
183    List<ValidatedFragment> res = new ArrayList<>();
184    res.add(focusFragment);
185    return res;
186  }
187
188
189  private String extractVal(String src, String name) {
190    src = src.substring(src.indexOf(name)+name.length()+1);
191    src = src.substring(0, src.indexOf(";")).trim();
192    return src;
193  }
194  private void checkForProcessingInstruction(List<ValidationMessage> errors, Document document) throws FHIRFormatError {
195    if (policy == ValidationPolicy.EVERYTHING && FormatUtilities.FHIR_NS.equals(document.getDocumentElement().getNamespaceURI())) {
196      Node node = document.getFirstChild();
197      while (node != null) {
198        if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE)
199          logError(errors, ValidationMessage.NO_RULE_DATE, line(document, false), col(document, false), "(document)", IssueType.INVALID, context.formatMessage(
200              I18nConstants.NO_PROCESSING_INSTRUCTIONS_ALLOWED_IN_RESOURCES), IssueSeverity.ERROR);
201        node = node.getNextSibling();
202      }
203    }
204  }
205
206
207  private int line(Node node, boolean end) {
208    XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY);
209    return loc == null ? 0 : end ? loc.getEndLine() : loc.getStartLine();
210  }
211
212  private int col(Node node, boolean end) {
213    XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY);
214    return loc == null ? 0 : end ? loc.getEndColumn() : loc.getStartColumn();
215  }
216
217  public Element parse(List<ValidationMessage> errors, Document doc) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
218    checkForProcessingInstruction(errors, doc);
219    org.w3c.dom.Element element = doc.getDocumentElement();
220    return parse(errors, element);
221  }
222
223  public Element parse(List<ValidationMessage> errors, org.w3c.dom.Element element) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
224    String ns = element.getNamespaceURI();
225    String name = element.getLocalName();
226    String path = "/"+pathPrefix(ns)+name;
227
228    StructureDefinition sd = getDefinition(errors, line(element, false), col(element, false), (ns == null ? "noNamespace" : ns), name);
229    if (sd == null)
230      return null;
231
232    Element result = new Element(element.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd, getProfileUtilities(), getContextUtilities())).setFormat(FhirFormat.XML);
233    result.setPath(element.getLocalName());
234    checkElement(errors, element, result, path, result.getProperty(), false);
235    result.markLocation(line(element, false), col(element, false));
236    result.setType(element.getLocalName());
237    parseChildren(errors, path, element, result);
238    result.numberChildren();
239    return result;
240  }
241
242  private String pathPrefix(String ns) {
243    if (Utilities.noString(ns))
244      return "";
245    if (ns.equals(FormatUtilities.FHIR_NS))
246      return "f:";
247    if (ns.equals(FormatUtilities.XHTML_NS))
248      return "h:";
249    if (ns.equals("urn:hl7-org:v3"))
250      return "v3:";
251    if (ns.equals("urn:hl7-org:sdtc")) 
252      return "sdtc:";
253    if (ns.equals("urn:ihe:pharm"))
254      return "pharm:";
255    if (ns.equals("http://ns.electronichealth.net.au/Ci/Cda/Extensions/3.0"))
256      return "ext:";
257    return "?:";
258  }
259
260  private boolean empty(org.w3c.dom.Element element) {
261    for (int i = 0; i < element.getAttributes().getLength(); i++) {
262      String n = element.getAttributes().item(i).getNodeName();
263      if (!n.equals("xmlns") && !n.startsWith("xmlns:"))
264        return false;
265    }
266    if (!Utilities.noString(element.getTextContent().trim()))
267      return false;
268
269    Node n = element.getFirstChild();
270    while (n != null) {
271      if (n.getNodeType() == Node.ELEMENT_NODE)
272        return false;
273      n = n.getNextSibling();
274    }
275    return true;
276  }
277
278  private void checkElement(List<ValidationMessage> errors, org.w3c.dom.Element element, Element e, String path, Property prop, boolean xsiTypeChecked) throws FHIRFormatError {
279    if (policy == ValidationPolicy.EVERYTHING) {
280      if (empty(element) && FormatUtilities.FHIR_NS.equals(element.getNamespaceURI())) // this rule only applies to FHIR Content
281        logError(errors, ValidationMessage.NO_RULE_DATE, line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.ELEMENT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR);
282      String ns = prop.getXmlNamespace();
283      String elementNs = element.getNamespaceURI();
284      if (elementNs == null) {
285        elementNs = "noNamespace";
286      }
287      if (!elementNs.equals(ns)) {
288        logError(errors, ValidationMessage.NO_RULE_DATE, line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.WRONG_NAMESPACE__EXPECTED_, ns), IssueSeverity.ERROR);
289      }
290      if (!xsiTypeChecked) {
291        String xsiType = element.getAttributeNS(FormatUtilities.NS_XSI, "type");
292        if (!Utilities.noString(xsiType)) {
293          String actualType = prop.getXmlTypeName();
294          if (xsiType.equals(actualType)) {
295            logError(errors, "2023-10-12", line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.XSI_TYPE_UNNECESSARY), IssueSeverity.INFORMATION);            
296          } else {
297            StructureDefinition sd = findLegalConstraint(xsiType, actualType);
298            if (sd != null) {
299              e.setType(sd.getType());
300              e.setExplicitType(xsiType);
301            } else {
302              logError(errors, "2023-10-12", line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.XSI_TYPE_WRONG, xsiType, actualType), IssueSeverity.ERROR);           
303            }  
304          }
305        }
306      }
307    }
308  }
309
310  private StructureDefinition findLegalConstraint(String xsiType, String actualType) {
311    StructureDefinition sdA = context.fetchTypeDefinition(actualType);
312    StructureDefinition sd = context.fetchTypeDefinition(xsiType);
313    while (sd != null) {
314      if (sd == sdA) {
315        return sd;
316      }
317      sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
318    }
319    return null;
320  }
321
322  public Element parse(List<ValidationMessage> errors, org.w3c.dom.Element base, String type) throws Exception {
323    StructureDefinition sd = getDefinition(errors, 0, 0, FormatUtilities.FHIR_NS, type);
324    Element result = new Element(base.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd, getProfileUtilities(), getContextUtilities())).setFormat(FhirFormat.XML).setNativeObject(base);
325    result.setPath(base.getLocalName());
326    String path = "/"+pathPrefix(base.getNamespaceURI())+base.getLocalName();
327    checkElement(errors, base, result, path, result.getProperty(), false);
328    result.setType(base.getLocalName());
329    parseChildren(errors, path, base, result);
330    result.numberChildren();
331    return result;
332  }
333
334  private void parseChildren(List<ValidationMessage> errors, String path, org.w3c.dom.Element node, Element element) throws FHIRFormatError, FHIRException, IOException, DefinitionException {
335    // this parsing routine retains the original order in a the XML file, to support validation
336    reapComments(node, element);
337    List<Property> properties = element.getProperty().getChildProperties(element.getName(), XMLUtil.getXsiType(node));
338    Property cgProp = getChoiceGroupProp(properties);
339    Property mtProp = cgProp == null ? null : getTextProp(cgProp.getChildProperties(null, null));
340
341    String text = mtProp == null ? XMLUtil.getDirectText(node).trim() : null;
342    int line = line(node, false);
343    int col = col(node, false);
344    if (!Utilities.noString(text)) {
345      Property property = getTextProp(properties);
346      if (property != null) {
347        if ("ED.data[x]".equals(property.getDefinition().getId()) || (property.getDefinition()!=null && property.getDefinition().getBase()!=null && "ED.data[x]".equals(property.getDefinition().getBase().getPath()))) {
348          if ("B64".equals(node.getAttribute("representation"))) {
349            Element n = new Element("dataBase64Binary", property, "base64Binary", text).markLocation(line, col).setFormat(FhirFormat.XML);
350            n.setPath(element.getPath()+"."+property.getName());
351            element.getChildren().add(n);
352          } else {
353            Element n = new Element("dataString", property, "string", text).markLocation(line, col).setFormat(FhirFormat.XML);
354            n.setPath(element.getPath()+"."+property.getName());
355            element.getChildren().add(n);
356          }
357        } else {
358          Element n = new Element(property.getName(), property, property.getType(), text).markLocation(line, col).setFormat(FhirFormat.XML);
359          n.setPath(element.getPath()+"."+property.getName());
360          element.getChildren().add(n);
361        }
362      } else {
363        Node n = node.getFirstChild();
364        while (n != null) {
365          if (n.getNodeType() == Node.TEXT_NODE && !Utilities.noString(n.getTextContent().trim())) {
366            Node nt = n; // try to find the nearest element for a line/col location
367            boolean end = false;
368            while (nt.getPreviousSibling() != null && nt.getNodeType() != Node.ELEMENT_NODE) {
369              nt = nt.getPreviousSibling();
370              end = true;
371            }
372            while (nt.getNextSibling() != null && nt.getNodeType() != Node.ELEMENT_NODE) {
373              nt = nt.getNextSibling();
374              end = false;
375            }
376            line = line(nt, end);
377            col = col(nt, end);
378            logError(errors, ValidationMessage.NO_RULE_DATE, line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.TEXT_SHOULD_NOT_BE_PRESENT, Utilities.makeSingleLine(n.getTextContent().trim())), IssueSeverity.ERROR);
379          }
380          n = n.getNextSibling();
381        }
382      }                 
383    }
384
385    for (int i = 0; i < node.getAttributes().getLength(); i++) {
386      Node attr = node.getAttributes().item(i);
387      String value = attr.getNodeValue();
388      if (!validAttrValue(value)) {
389        logError(errors, ValidationMessage.NO_RULE_DATE, line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.XML_ATTR_VALUE_INVALID, attr.getNodeName()), IssueSeverity.ERROR);
390      }
391      if (!(attr.getNodeName().equals("xmlns") || attr.getNodeName().startsWith("xmlns:"))) {
392        Property property = getAttrProp(properties, attr.getLocalName(), attr.getNamespaceURI());
393        if (property != null) {
394          String av = attr.getNodeValue();
395          if (ToolingExtensions.hasExtension(property.getDefinition(), ToolingExtensions.EXT_DATE_FORMAT))
396            av = convertForDateFormatFromExternal(ToolingExtensions.readStringExtension(property.getDefinition(), ToolingExtensions.EXT_DATE_FORMAT), av);          
397          if (property.getName().equals("value") && element.isPrimitive())
398            element.setValue(av);
399          else {
400            String[] vl = {av};
401            if (property.isList() && av.contains(" ")) {
402              vl = av.split(" ");
403            }
404            for (String v : vl) {
405              Element n = new Element(property.getName(), property, property.getType(), v).markLocation(line, col).setFormat(FhirFormat.XML);
406              n.setPath(element.getPath()+"."+property.getName());
407              element.getChildren().add(n);
408            }
409          }
410        } else {
411          boolean ok = false;
412          if (FormatUtilities.FHIR_NS.equals(node.getNamespaceURI())) {
413            if (attr.getLocalName().equals("schemaLocation") && FormatUtilities.NS_XSI.equals(attr.getNamespaceURI())) {
414              ok = ok || allowXsiLocation; 
415            }
416          } else
417            ok = ok || (attr.getLocalName().equals("schemaLocation")); // xsi:schemalocation allowed for non FHIR content
418          ok = ok || (hasTypeAttr(element) && attr.getLocalName().equals("type") && FormatUtilities.NS_XSI.equals(attr.getNamespaceURI())); // xsi:type allowed if element says so
419          if (!ok) { 
420            logError(errors, ValidationMessage.NO_RULE_DATE, line(node, false), col(node, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ATTRIBUTE__ON__FOR_TYPE__PROPERTIES__, attr.getNodeName(), node.getNodeName(), element.fhirType(), properties), IssueSeverity.ERROR);
421          }
422        }
423      }
424    }
425
426    String lastName = null;
427    int repeatCount = 0;
428    Node child = node.getFirstChild();
429    while (child != null) {
430      if (child.getNodeType() == Node.ELEMENT_NODE) {
431        Property property = getElementProp(properties, child.getLocalName(), child.getNamespaceURI());
432
433        if (property != null) {
434          if (property.getName().equals(lastName)) {
435            repeatCount++;
436          } else {
437            lastName = property.getName();
438            repeatCount = 0;
439          }
440          if (!property.isChoice() && "xhtml".equals(property.getType())) {
441            XhtmlNode xhtml;
442            if (property.getDefinition().hasRepresentation(PropertyRepresentation.CDATEXT))
443              xhtml = new CDANarrativeFormat().convert((org.w3c.dom.Element) child);
444            else {
445              XhtmlParser xp = new XhtmlParser();
446              xhtml = xp.parseHtmlNode((org.w3c.dom.Element) child);
447              if (policy == ValidationPolicy.EVERYTHING) {
448                for (StringPair s : xp.getValidationIssues()) {
449                  logError(errors, "2022-11-17", line(child, false), col(child, false), path, IssueType.INVALID, context.formatMessage(s.getName(), s.getValue()), IssueSeverity.ERROR);                
450                }
451              }
452            }
453            Element n = new Element(property.getName(), property, "xhtml", new XhtmlComposer(XhtmlComposer.XML, false).compose(xhtml)).setXhtml(xhtml).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML).setNativeObject(child);
454            n.setPath(element.getPath()+"."+property.getName());
455            element.getChildren().add(n);
456          } else {
457            String npath = path+"/"+pathPrefix(child.getNamespaceURI())+child.getLocalName();
458            String name = child.getLocalName();
459            if (!property.isChoice() && !name.equals(property.getName())) {
460              name = property.getName();
461            }
462            Element n = new Element(name, property).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML).setNativeObject(child);
463            if (property.isList()) {
464              n.setPath(element.getPath()+"."+property.getName()+"["+repeatCount+"]");                                    
465            } else {
466              n.setPath(element.getPath()+"."+property.getName());
467            }
468            boolean xsiTypeChecked = false;
469            boolean ok = true;
470            if (property.isChoice()) {
471              if (property.getDefinition().hasRepresentation(PropertyRepresentation.TYPEATTR)) {
472                String xsiType = ((org.w3c.dom.Element) child).getAttributeNS(FormatUtilities.NS_XSI, "type");
473                if (Utilities.noString(xsiType)) {
474                  if (ToolingExtensions.hasExtension(property.getDefinition(), "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) {
475                    xsiType = ToolingExtensions.readStringExtension(property.getDefinition(), "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype");
476                    n.setType(xsiType);
477                  } else {
478                    logError(errors, ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NO_TYPE_FOUND_ON_, child.getLocalName()), IssueSeverity.ERROR);
479                    ok = false;
480                  }
481                } else {
482                  if (xsiType.contains(":"))
483                    xsiType = xsiType.substring(xsiType.indexOf(":")+1);
484                  n.setType(xsiType);
485                  n.setExplicitType(xsiType);
486                }
487                xsiTypeChecked = true;
488              } else
489                n.setType(n.getType());
490            }
491            checkElement(errors, (org.w3c.dom.Element) child, n, npath, n.getProperty(), xsiTypeChecked);
492            element.getChildren().add(n);
493            if (ok) {
494              if (property.isResource())
495                parseResource(errors, npath, (org.w3c.dom.Element) child, n, property);
496              else
497                parseChildren(errors, npath, (org.w3c.dom.Element) child, n);
498            }
499          }
500        } else {
501          if (cgProp != null) {
502            property = getElementProp(cgProp.getChildProperties(null, null), child.getLocalName(), child.getNamespaceURI());
503            if (property != null) {
504              if (cgProp.getName().equals(lastName)) {
505                repeatCount++;
506              } else {
507                lastName = cgProp.getName();
508                repeatCount = 0;
509              }
510
511              String npath = path+"/"+pathPrefix(cgProp.getXmlNamespace())+cgProp.getName();
512              String name = cgProp.getName();
513              Element cgn = new Element(cgProp.getName(), cgProp).setFormat(FhirFormat.XML);
514              cgn.setPath(element.getPath()+"."+cgProp.getName()+"["+repeatCount+"]"); 
515              element.getChildren().add(cgn);
516
517              npath = npath+"/"+pathPrefix(child.getNamespaceURI())+child.getLocalName();
518              name = child.getLocalName();
519              Element n = new Element(name, property).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML).setNativeObject(child);
520              cgn.getChildren().add(n);
521              n.setPath(element.getPath()+"."+property.getName());
522              checkElement(errors, (org.w3c.dom.Element) child, n, npath, n.getProperty(), false);
523              parseChildren(errors, npath, (org.w3c.dom.Element) child, n);
524            }
525          }
526          if (property == null) {
527            logError(errors, ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ELEMENT_, child.getLocalName(), path), IssueSeverity.ERROR);
528          }
529        }
530      } else if (child.getNodeType() == Node.TEXT_NODE && !Utilities.noString(child.getTextContent().trim()) && mtProp != null) {
531        if (cgProp.getName().equals(lastName)) {
532          repeatCount++;
533        } else {
534          lastName = cgProp.getName();
535          repeatCount = 0;
536        }
537
538        String npath = path+"/"+pathPrefix(cgProp.getXmlNamespace())+cgProp.getName();
539        String name = cgProp.getName();
540        Element cgn = new Element(cgProp.getName(), cgProp).setFormat(FhirFormat.XML);
541        cgn.setPath(element.getPath()+"."+cgProp.getName()+"["+repeatCount+"]"); 
542        element.getChildren().add(cgn);
543
544        npath = npath+"/text()";
545        name = mtProp.getName();
546        Element n = new Element(name, mtProp, mtProp.getType(), child.getTextContent().trim()).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML).setNativeObject(child);
547        cgn.getChildren().add(n);
548        n.setPath(element.getPath()+"."+mtProp.getName());
549
550
551      } else if (child.getNodeType() == Node.CDATA_SECTION_NODE) {
552        logError(errors, ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.CDATA_IS_NOT_ALLOWED), IssueSeverity.ERROR);
553      } else if (!Utilities.existsInList(child.getNodeType(), 3, 8)) {
554        logError(errors, ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NODE_TYPE__IS_NOT_ALLOWED, Integer.toString(child.getNodeType())), IssueSeverity.ERROR);
555      }
556      child = child.getNextSibling();
557    }
558  }
559
560  private Property getChoiceGroupProp(List<Property> properties) {
561    for (Property p : properties) {
562      if (p.getDefinition().hasExtension(ToolingExtensions.EXT_ID_CHOICE_GROUP)) {
563        return p;
564      }
565    }
566    return null;
567  }
568
569  private boolean validAttrValue(String value) {
570    if (version == null) {
571      return true;
572    }
573    if (version.equals("1.0")) {
574      boolean ok = true;
575      for (char ch : value.toCharArray()) {
576        if (ch <= 0x1F && !Utilities.existsInList(ch, '\r', '\n', '\t')) {
577          ok = false;
578        }
579      }
580      return ok;
581    } else
582      return true;
583  }
584
585
586  private Property getElementProp(List<Property> properties, String nodeName, String namespace) {
587    List<Property> propsSortedByLongestFirst = new ArrayList<Property>(properties);
588    // sort properties according to their name longest first, so .requestOrganizationReference comes first before .request[x]
589    // and therefore the longer property names get evaluated first
590    Collections.sort(propsSortedByLongestFirst, new Comparator<Property>() {
591      @Override
592      public int compare(Property o1, Property o2) {
593        return o2.getName().length() - o1.getName().length();
594      }
595    });
596    // first scan, by namespace
597    for (Property p : propsSortedByLongestFirst) {
598      if (!p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) && !p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) {
599        if (p.getXmlName().equals(nodeName) && p.getXmlNamespace().equals(namespace)) 
600          return p;
601      }
602    }
603    for (Property p : propsSortedByLongestFirst) {
604      if (!p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) && !p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) {
605        if (p.getXmlName().equals(nodeName)) 
606          return p;
607        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))) 
608          return p;
609      }
610    }
611
612
613    return null;
614  }
615
616  private Property getAttrProp(List<Property> properties, String nodeName, String namespace) {
617    for (Property p : properties) {
618      if (p.getXmlName().equals(nodeName) && p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) && p.getXmlNamespace().equals(namespace)) {
619        return p;
620      }
621    }
622    if (namespace == null) {
623      for (Property p : properties) {
624        if (p.getXmlName().equals(nodeName) && p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR)) {
625          return p;
626        }    
627      }
628    }
629    return null;
630  }
631
632  private Property getTextProp(List<Property> properties) {
633    for (Property p : properties)
634      if (p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) 
635        return p;
636    return null;
637  }
638
639  private String convertForDateFormatFromExternal(String fmt, String av) throws FHIRException {
640    if ("v3".equals(fmt) || "YYYYMMDDHHMMSS.UUUU[+|-ZZzz]".equals(fmt)) {
641      try {
642        DateTimeType d = DateTimeType.parseV3(av);
643        return d.asStringValue();
644      } catch (Exception e) {
645        return av; // not at all clear what to do in this case.
646      }
647    }
648    throw new FHIRException(context.formatMessage(I18nConstants.UNKNOWN_DATA_FORMAT_, fmt));
649  }
650
651  private String convertForDateFormatToExternal(String fmt, String av) throws FHIRException {
652    if ("v3".equals(fmt) || "YYYYMMDDHHMMSS.UUUU[+|-ZZzz]".equals(fmt)) {
653      DateTimeType d = new DateTimeType(av);
654      return d.getAsV3();
655    } else
656      throw new FHIRException(context.formatMessage(I18nConstants.UNKNOWN_DATE_FORMAT_, fmt));
657  }
658
659  private void parseResource(List<ValidationMessage> errors, String string, org.w3c.dom.Element container, Element parent, Property elementProperty) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
660    org.w3c.dom.Element res = XMLUtil.getFirstChild(container);
661    String name = res.getLocalName();
662    StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, null));
663    if (sd == null)
664      throw new FHIRFormatError(context.formatMessage(I18nConstants.CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, res.getLocalName()));
665    parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd, getProfileUtilities(), getContextUtilities()), SpecialElement.fromProperty(parent.getProperty()), elementProperty);
666    parent.setType(name);
667    parseChildren(errors, res.getLocalName(), res, parent);
668  }
669
670  private void reapComments(org.w3c.dom.Element element, Element context) {
671    Node node = element.getPreviousSibling();
672    while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
673      if (node.getNodeType() == Node.COMMENT_NODE)
674        context.getComments().add(0, node.getTextContent());
675      node = node.getPreviousSibling();
676    }
677    node = element.getLastChild();
678    while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
679      node = node.getPreviousSibling();
680    }
681    while (node != null) {
682      if (node.getNodeType() == Node.COMMENT_NODE)
683        context.getComments().add(node.getTextContent());
684      node = node.getNextSibling();
685    }
686  }
687
688  private boolean isAttr(Property property) {
689    for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) {
690      if (r.getValue() == PropertyRepresentation.XMLATTR) {
691        return true;
692      }
693    }
694    return false;
695  }
696
697  private boolean isCdaText(Property property) {
698    for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) {
699      if (r.getValue() == PropertyRepresentation.CDATEXT) {
700        return true;
701      }
702    }
703    return false;
704  }
705
706  private boolean isTypeAttr(Property property) {
707    for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) {
708      if (r.getValue() == PropertyRepresentation.TYPEATTR) {
709        return true;
710      }
711    }
712    return false;
713  }
714
715  private boolean isText(Property property) {
716    for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) {
717      if (r.getValue() == PropertyRepresentation.XMLTEXT) {
718        return true;
719      }
720    }
721    return false;
722  }
723
724  @Override
725  public void compose(Element e, OutputStream stream, OutputStyle style, String base) throws IOException, FHIRException {
726    markedXhtml = false;
727    XMLWriter xml = new XMLWriter(stream, "UTF-8");
728    xml.setSortAttributes(false);
729    xml.setPretty(style == OutputStyle.PRETTY);
730    xml.start();
731    if (e.getPath() == null) {
732      e.populatePaths(null);
733    }
734    String ns = e.getProperty().getXmlNamespace();
735    if (ns!=null && !"noNamespace".equals(ns)) {
736      xml.setDefaultNamespace(ns);
737    }
738    if (hasTypeAttr(e))
739      xml.namespace("http://www.w3.org/2001/XMLSchema-instance", "xsi");
740    if (Utilities.isAbsoluteUrl(e.getType())) {
741      xml.namespace(urlRoot(e.getType()), "et");
742    }
743    addNamespaces(xml, e);
744    composeElement(xml, e, e.getType(), true);
745    xml.end();
746  }
747
748  private void addNamespaces(IXMLWriter xml, Element e) throws IOException {
749    String ns = e.getProperty().getXmlNamespace();
750    if (ns!=null && xml.getDefaultNamespace()!=null && !xml.getDefaultNamespace().equals(ns)){
751      if (!xml.namespaceDefined(ns)) {
752        String prefix = pathPrefix(ns);
753        if (prefix.endsWith(":")) {
754          prefix = prefix.substring(0, prefix.length()-1);
755        }
756        if ("?".equals(prefix)) {
757          xml.namespace(ns);
758        } else {
759          xml.namespace(ns, prefix);
760        }
761      }
762    }
763    for (Element c : e.getChildren()) {
764      addNamespaces(xml, c);
765    }
766  }
767
768  private boolean hasTypeAttr(Element e) {
769    if (isTypeAttr(e.getProperty()))
770      return true;
771    for (Element c : e.getChildren()) {
772      if (hasTypeAttr(c))
773        return true;
774    }
775    // xsi_type is always allowed on CDA elements. right now, I'm not sure where to indicate this in the model, 
776    // so it's just hardcoded here 
777    if (e.getType() != null && e.getType().startsWith(Constants.NS_CDA_ROOT)) {
778      return true;
779    }
780    return false;
781  }
782
783  private void setXsiTypeIfIsTypeAttr(IXMLWriter xml, Element element) throws IOException, FHIRException {
784    if (isTypeAttr(element.getProperty()) && !Utilities.noString(element.getType())) {
785      String type = element.getType();
786      if (Utilities.isAbsoluteUrl(type)) {
787        type = type.substring(type.lastIndexOf("/")+1);
788      }
789      xml.attribute("xsi:type",type);    
790    }
791  }
792
793  public void compose(Element e, IXMLWriter xml) throws Exception {
794    if (e.getPath() == null) {
795      e.populatePaths(null);
796    }
797    markedXhtml = false;
798    xml.start();
799    xml.setDefaultNamespace(e.getProperty().getXmlNamespace());    
800    if (Utilities.isAbsoluteUrl(e.getType())) {
801      xml.namespace(urlRoot(e.getType()), "et");
802    }
803
804    if (schemaPath != null) {
805      xml.setSchemaLocation(FormatUtilities.FHIR_NS, Utilities.pathURL(schemaPath, e.fhirType()+".xsd"));
806    }
807    composeElement(xml, e, e.getType(), true);
808    xml.end();
809  }
810
811  private void composeElement(IXMLWriter xml, Element element, String elementName, boolean root) throws IOException, FHIRException {
812    if (!(isElideElements() && element.isElided())) {
813      if (showDecorations) {
814        @SuppressWarnings("unchecked")
815        List<ElementDecoration> decorations = (List<ElementDecoration>) element.getUserData(UserDataNames.rendering_xml_decorations);
816        if (decorations != null)
817          for (ElementDecoration d : decorations)
818            xml.decorate(d);
819      }
820      for (String s : element.getComments()) {
821        xml.comment(s, true);
822      }
823    }
824    if (isText(element.getProperty())) {
825      if (isElideElements() && element.isElided() && xml.canElide())
826        xml.elide();
827      else {
828        if (linkResolver != null)
829          xml.link(linkResolver.resolveProperty(element.getProperty()));
830        xml.enter(element.getProperty().getXmlNamespace(),elementName);
831        if (linkResolver != null && element.getProperty().isReference()) {
832          String ref = linkResolver.resolveReference(getReferenceForElement(element));
833          if (ref != null) {
834            xml.externalLink(ref);
835          }
836        }
837        xml.text(element.getValue());
838        xml.exit(element.getProperty().getXmlNamespace(),elementName);
839      }
840    } else if (!element.hasChildren() && !element.hasValue()) {
841      if (isElideElements() && element.isElided() && xml.canElide())
842        xml.elide();
843      else {
844        if (element.getExplicitType() != null)
845          xml.attribute("xsi:type", element.getExplicitType());
846        xml.element(elementName);
847      }
848    } else if (element.isPrimitive() || (element.hasType() && isPrimitive(element.getType()))) {
849      if (element.getType().equals("xhtml")) {
850        if (isElideElements() && element.isElided() && xml.canElide())
851          xml.elide();
852        else {
853          String rawXhtml = element.getValue();
854          if (isCdaText(element.getProperty())) {
855            new CDANarrativeFormat().convert(xml, new XhtmlParser().parseFragment(rawXhtml));
856          } else {
857            xml.escapedText(rawXhtml);
858            if (!markedXhtml) {
859              xml.anchor("end-xhtml");
860              markedXhtml = true;
861            }
862          }
863        }
864      } else if (isText(element.getProperty())) {
865        if (isElideElements() && element.isElided() && xml.canElide())
866          xml.elide();
867        else {
868          if (linkResolver != null)
869            xml.link(linkResolver.resolveProperty(element.getProperty()));
870          xml.text(element.getValue());
871        }
872      } else {
873        if (isElideElements() && element.isElided())
874          xml.attributeElide();
875        else {
876          setXsiTypeIfIsTypeAttr(xml, element);
877          if (element.hasValue()) {
878            if (linkResolver != null)
879              xml.link(linkResolver.resolveType(element.getType()));
880            xml.attribute("value", element.getValue());
881          }
882          if (linkResolver != null)
883            xml.link(linkResolver.resolveProperty(element.getProperty()));
884          if (element.hasChildren()) {
885            xml.enter(element.getProperty().getXmlNamespace(), elementName);
886            if (linkResolver != null && element.getProperty().isReference()) {
887              String ref = linkResolver.resolveReference(getReferenceForElement(element));
888              if (ref != null) {
889                xml.externalLink(ref);
890              }
891            }
892            for (Element child : element.getChildren())
893              composeElement(xml, child, child.getName(), false);
894            xml.exit(element.getProperty().getXmlNamespace(),elementName);
895          } else
896            xml.element(elementName);
897        }
898      }
899    } else {
900      if (isElideElements() && element.isElided() && xml.canElide())
901        xml.elide();
902      else {
903        setXsiTypeIfIsTypeAttr(xml, element);
904        Set<String> handled = new HashSet<>();
905      for (Element child : element.getChildren()) {
906        if (!handled.contains(child.getName()) && isAttr(child.getProperty()) && wantCompose(element.getPath(), child)) {
907          handled.add(child.getName());
908          if (isElideElements() && child.isElided())
909            xml.attributeElide();
910          else {
911            String av = child.getValue();
912            if (child.getProperty().isList()) {
913              for (Element c2 : element.getChildren()) {
914                if (c2 != child && c2.getName().equals(child.getName())) {
915                  if (c2.isElided())
916                    av = av + " ...";
917                  else
918                    av = av + " " + c2.getValue();
919                }
920              }
921            }
922            if (linkResolver != null)
923              xml.link(linkResolver.resolveType(child.getType()));
924            if (ToolingExtensions.hasExtension(child.getProperty().getDefinition(), ToolingExtensions.EXT_DATE_FORMAT))
925              av = convertForDateFormatToExternal(ToolingExtensions.readStringExtension(child.getProperty().getDefinition(), ToolingExtensions.EXT_DATE_FORMAT), av);
926            xml.attribute(child.getProperty().getXmlNamespace(), child.getProperty().getXmlName(), av);
927          }
928        }
929      }
930      }
931      if (!element.getProperty().getDefinition().hasExtension(ToolingExtensions.EXT_ID_CHOICE_GROUP)) {
932        if (linkResolver != null)
933          xml.link(linkResolver.resolveProperty(element.getProperty()));
934        if (!xml.namespaceDefined(element.getProperty().getXmlNamespace())) {
935          String abbrev = makeNamespaceAbbrev(element.getProperty(), xml);
936          xml.namespace(element.getProperty().getXmlNamespace(), abbrev);
937        }
938        if (Utilities.isAbsoluteUrl(elementName)) {
939          xml.enter(urlRoot(elementName), urlTail(elementName));
940        } else {
941          xml.enter(element.getProperty().getXmlNamespace(), elementName);
942        }
943      }
944
945      if (!root && element.getSpecial() != null) {
946        if (linkResolver != null)
947          xml.link(linkResolver.resolveProperty(element.getProperty()));
948        xml.enter(element.getProperty().getXmlNamespace(),element.getType());
949      }
950      if (linkResolver != null && element.getProperty().isReference()) {
951        String ref = linkResolver.resolveReference(getReferenceForElement(element));
952        if (ref != null) {
953          xml.externalLink(ref);
954        }
955      }
956      for (Element child : element.getChildren()) {
957        if (wantCompose(element.getPath(), child)) {
958          if (isElideElements() && child.isElided() && xml.canElide())
959            xml.elide();
960          else {
961            if (isText(child.getProperty())) {
962              if (linkResolver != null)
963                xml.link(linkResolver.resolveProperty(element.getProperty()));
964              xml.text(child.getValue());
965            } else if (!isAttr(child.getProperty())) {
966              composeElement(xml, child, child.getName(), false);
967            }
968          }
969        }
970      }
971      if (!element.getProperty().getDefinition().hasExtension(ToolingExtensions.EXT_ID_CHOICE_GROUP)) {
972        if (!root && element.getSpecial() != null)
973          xml.exit(element.getProperty().getXmlNamespace(),element.getType());
974        if (Utilities.isAbsoluteUrl(elementName)) {
975          xml.exit(urlRoot(elementName), urlTail(elementName));
976        } else {
977          xml.exit(element.getProperty().getXmlNamespace(),elementName);
978        }
979      }
980    }
981  }
982
983  private String urlRoot(String elementName) {
984    return elementName.substring(0, elementName.lastIndexOf("/"));
985  }
986  
987  private String makeNamespaceAbbrev(Property property, IXMLWriter xml) {
988    // it's a cosmetic thing, but we're going to try to come up with a nice namespace
989
990    ElementDefinition ed = property.getDefinition();
991    String ns = property.getXmlNamespace();
992    String n = property.getXmlName();
993
994    String diff = property.getName().toLowerCase().replace(n.toLowerCase(), "");
995    if (!Utilities.noString(diff) && diff.length() <= 5 && Utilities.isToken(diff) && !xml.abbreviationDefined(diff)) {
996      return diff;
997    }
998
999    int i = ns.length()-1;
1000    while (i > 0) {
1001      if (Character.isAlphabetic(ns.charAt(i)) || Character.isDigit(ns.charAt(i))) {
1002        i--;
1003      } else {
1004        break;
1005      }
1006    }
1007    String tail = ns.substring(i+1);
1008    if (!Utilities.noString(tail) && tail.length() <= 5 && Utilities.isToken(tail) && !xml.abbreviationDefined(tail)) {
1009      return tail;
1010    }
1011
1012    i = 0;
1013    while (xml.abbreviationDefined("ns"+i)) {
1014      i++;
1015    }
1016    return "ns"+i;
1017  }
1018  private String checkHeader(List<ValidationMessage> errors, InputStream stream) throws IOException {
1019    try {
1020      // the stream will either start with the UTF-8 BOF or with <xml
1021      int i0 = stream.read();
1022      int i1 = stream.read();
1023      int i2 = stream.read();
1024
1025      StringBuilder b = new StringBuilder();
1026      if (i0 == 0xEF && i1 == 0xBB && i2 == 0xBF) {
1027        // ok, it's UTF-8
1028      } else if (i0 == 0x3C && i1 == 0x3F && i2 == 0x78) { // <xm
1029        b.append((char) i0);
1030        b.append((char) i1);
1031        b.append((char) i2);
1032      } else if (i0 == 60) { // just plain old XML with no header
1033        return "1.0";        
1034      } else {
1035        throw new Exception(context.formatMessage(I18nConstants.XML_ENCODING_INVALID));
1036      }
1037      int i = stream.read();
1038      do {
1039        b.append((char) i);
1040        i = stream.read();
1041      } while (i != 0x3E);
1042      String header = b.toString();
1043      String e = null;
1044      i = header.indexOf("encoding=\"");
1045      if (i > -1) {
1046        e = header.substring(i+10, i+15);
1047      } else {
1048        i = header.indexOf("encoding='");
1049        if (i > -1) {
1050          e = header.substring(i+10, i+15);
1051        } 
1052      }
1053      if (e != null && !"UTF-8".equalsIgnoreCase(e)) {
1054        logError(errors, ValidationMessage.NO_RULE_DATE, 0, 0, "XML", IssueType.INVALID, context.formatMessage(I18nConstants.XML_ENCODING_INVALID), IssueSeverity.ERROR);
1055      }
1056
1057      i = header.indexOf("version=\"");
1058      if (i > -1) {
1059        return header.substring(i+9, i+12);
1060      } else {
1061        i = header.indexOf("version='");
1062        if (i > -1) {
1063          return header.substring(i+9, i+12);          
1064        } 
1065      }
1066      return "?xml-p1?";
1067    } catch (Exception e) {
1068      // suppress this error 
1069      logError(errors, ValidationMessage.NO_RULE_DATE, 0, 0, "XML", IssueType.INVALID, e.getMessage(), IssueSeverity.ERROR);
1070    }
1071    return "?xml-p2?";
1072  }
1073
1074  class NullErrorHandler implements ErrorHandler {
1075    @Override
1076    public void fatalError(SAXParseException e) {
1077      // do nothing
1078    }
1079
1080    @Override
1081    public void error(SAXParseException e) {
1082      // do nothing
1083    }
1084
1085    @Override
1086    public void warning(SAXParseException e) {
1087      // do nothing
1088    }
1089  }
1090
1091  public boolean isElideElements() {
1092    return elideElements;
1093  }
1094
1095  public void setElideElements(boolean elideElements) {
1096    this.elideElements = elideElements;
1097  }
1098
1099}