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