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