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