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