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