
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 809 if (Utilities.isAbsoluteUrl(e.getType())) { 810 xml.namespace(urlRoot(e.getType()), "et"); 811 } 812 813 if (schemaPath != null) { 814 xml.setSchemaLocation(FormatUtilities.FHIR_NS, Utilities.pathURL(schemaPath, e.fhirType()+".xsd")); 815 } 816 composeElement(xml, e, e.getType(), true); 817 xml.end(); 818 } 819 820 private void composeElement(IXMLWriter xml, Element element, String elementName, boolean root) throws IOException, FHIRException { 821 if (canonicalFilter.contains(element.getPath())) { 822 return; 823 } 824 if (!(isElideElements() && element.isElided())) { 825 if (showDecorations) { 826 @SuppressWarnings("unchecked") 827 List<ElementDecoration> decorations = (List<ElementDecoration>) element.getUserData(UserDataNames.rendering_xml_decorations); 828 if (decorations != null) 829 for (ElementDecoration d : decorations) 830 xml.decorate(d); 831 } 832 for (String s : element.getComments()) { 833 xml.comment(s, true); 834 } 835 } 836 if (isText(element.getProperty())) { 837 if (isElideElements() && element.isElided() && xml.canElide()) 838 xml.elide(); 839 else { 840 if (linkResolver != null) { 841 xml.link(linkResolver.resolveProperty(element.getProperty())); 842 } 843 xml.enter(element.getProperty().getXmlNamespace(),elementName); 844 if (linkResolver != null && element.getProperty().isReference()) { 845 String ref = linkResolver.resolveReference(getReferenceForElement(element)); 846 if (ref != null) { 847 xml.externalLink(ref); 848 } 849 } 850 xml.text(element.getValue()); 851 if (linkResolver != null) { 852 xml.link(linkResolver.resolveProperty(element.getProperty())); 853 } 854 xml.exit(element.getProperty().getXmlNamespace(),elementName); 855 } 856 } else if (!element.hasChildren() && !element.hasValue() && !element.hasXhtml()) { 857 if (isElideElements() && element.isElided() && xml.canElide()) 858 xml.elide(); 859 else { 860 if (element.getExplicitType() != null) 861 xml.attribute("xsi:type", element.getExplicitType()); 862 xml.element(elementName); 863 } 864 } else if (element.isPrimitive() || (element.hasType() && isPrimitive(element.getType()))) { 865 if (element.getType().equals("xhtml")) { 866 if (isElideElements() && element.isElided() && xml.canElide()) 867 xml.elide(); 868 else { 869 if ((element.getXhtml()==null) && (element.getValue() != null)) { 870 XhtmlParser xhtml = new XhtmlParser(); 871 element.setXhtml(xhtml.setXmlMode(true).parse(element.getValue(), null).getDocumentElement()); 872 } 873 if (isCdaText(element.getProperty())) { 874 new CDANarrativeFormat().convert(xml, element.getXhtml()); 875 } else { 876 String xhtml = new XhtmlComposer(XhtmlComposer.XML, false).setCanonical(xml.isCanonical()).compose(element.getXhtml()); 877 xml.escapedText(xhtml); 878 if (!markedXhtml) { 879 xml.anchor("end-xhtml"); 880 markedXhtml = true; 881 } 882 } 883 } 884 } else if (isText(element.getProperty())) { 885 if (isElideElements() && element.isElided() && xml.canElide()) 886 xml.elide(); 887 else { 888 if (linkResolver != null) 889 xml.link(linkResolver.resolveProperty(element.getProperty())); 890 xml.text(element.getValue()); 891 } 892 } else { 893 if (isElideElements() && element.isElided()) 894 xml.attributeElide(); 895 else { 896 setXsiTypeIfIsTypeAttr(xml, element); 897 if (element.hasValue()) { 898 if (linkResolver != null) 899 xml.link(linkResolver.resolveType(element.getType())); 900 xml.attribute("value", element.getValue()); 901 } 902 if (linkResolver != null) { 903 xml.link(linkResolver.resolveProperty(element.getProperty())); 904 } 905 if (element.hasChildren()) { 906 xml.enter(element.getProperty().getXmlNamespace(), elementName); 907 if (linkResolver != null && element.getProperty().isReference()) { 908 String ref = linkResolver.resolveReference(getReferenceForElement(element)); 909 if (ref != null) { 910 xml.externalLink(ref); 911 } 912 } 913 for (Element child : element.getChildren()) 914 composeElement(xml, child, child.getName(), false); 915 if (linkResolver != null) { 916 xml.link(linkResolver.resolveProperty(element.getProperty())); 917 } 918 xml.exit(element.getProperty().getXmlNamespace(),elementName); 919 } else 920 xml.element(elementName); 921 } 922 } 923 } else { 924 if (isElideElements() && element.isElided() && xml.canElide()) 925 xml.elide(); 926 else { 927 setXsiTypeIfIsTypeAttr(xml, element); 928 Set<String> handled = new HashSet<>(); 929 for (Element child : element.getChildren()) { 930 if (!handled.contains(child.getName()) && isAttr(child.getProperty()) && wantCompose(element.getPath(), child)) { 931 handled.add(child.getName()); 932 if (isElideElements() && child.isElided()) 933 xml.attributeElide(); 934 else { 935 String av = child.getValue(); 936 if (child.getProperty().isList()) { 937 for (Element c2 : element.getChildren()) { 938 if (c2 != child && c2.getName().equals(child.getName())) { 939 if (c2.isElided()) 940 av = av + " ..."; 941 else 942 av = av + " " + c2.getValue(); 943 } 944 } 945 } 946 if (linkResolver != null) 947 xml.link(linkResolver.resolveType(child.getType())); 948 if (ExtensionUtilities.hasExtension(child.getProperty().getDefinition(), ExtensionDefinitions.EXT_DATE_FORMAT)) 949 av = convertForDateFormatToExternal(ExtensionUtilities.readStringExtension(child.getProperty().getDefinition(), ExtensionDefinitions.EXT_DATE_FORMAT), av); 950 xml.attribute(child.getProperty().getXmlNamespace(), child.getProperty().getXmlName(), av); 951 } 952 } 953 } 954 } 955 if (!element.getProperty().getDefinition().hasExtension(ExtensionDefinitions.EXT_ID_CHOICE_GROUP)) { 956 if (linkResolver != null) 957 xml.link(linkResolver.resolveProperty(element.getProperty())); 958 if (!xml.namespaceDefined(element.getProperty().getXmlNamespace())) { 959 String abbrev = makeNamespaceAbbrev(element.getProperty(), xml); 960 xml.namespace(element.getProperty().getXmlNamespace(), abbrev); 961 } 962 if (Utilities.isAbsoluteUrl(elementName)) { 963 xml.enter(urlRoot(elementName), urlTail(elementName)); 964 } else { 965 xml.enter(element.getProperty().getXmlNamespace(), elementName); 966 } 967 } 968 969 if (!root && element.getSpecial() != null) { 970 if (linkResolver != null) 971 xml.link(linkResolver.resolveProperty(element.getProperty())); 972 xml.enter(element.getProperty().getXmlNamespace(),element.getType()); 973 } 974 if (linkResolver != null && element.getProperty().isReference()) { 975 String ref = linkResolver.resolveReference(getReferenceForElement(element)); 976 if (ref != null) { 977 xml.externalLink(ref); 978 } 979 } 980 for (Element child : element.getChildren()) { 981 if (wantCompose(element.getPath(), child)) { 982 if (isElideElements() && child.isElided() && xml.canElide()) 983 xml.elide(); 984 else { 985 if (isText(child.getProperty())) { 986 if (linkResolver != null) 987 xml.link(linkResolver.resolveProperty(element.getProperty())); 988 xml.text(child.getValue()); 989 } else if (!isAttr(child.getProperty())) { 990 composeElement(xml, child, child.getName(), false); 991 } 992 } 993 } 994 } 995 if (!element.getProperty().getDefinition().hasExtension(ExtensionDefinitions.EXT_ID_CHOICE_GROUP)) { 996 997 if (linkResolver != null) { 998 xml.link(linkResolver.resolveProperty(element.getProperty())); 999 } 1000 if (!root && element.getSpecial() != null) { 1001 xml.exit(element.getProperty().getXmlNamespace(), element.getType()); 1002 } 1003 1004 if (Utilities.isAbsoluteUrl(elementName)) { 1005 xml.exit(urlRoot(elementName), urlTail(elementName)); 1006 } else { 1007 xml.exit(element.getProperty().getXmlNamespace(),elementName); 1008 } 1009 } 1010 } 1011 } 1012 1013 private String urlRoot(String elementName) { 1014 return elementName.substring(0, elementName.lastIndexOf("/")); 1015 } 1016 1017 private String makeNamespaceAbbrev(Property property, IXMLWriter xml) { 1018 // it's a cosmetic thing, but we're going to try to come up with a nice namespace 1019 1020 ElementDefinition ed = property.getDefinition(); 1021 String ns = property.getXmlNamespace(); 1022 String n = property.getXmlName(); 1023 1024 String diff = property.getName().toLowerCase().replace(n.toLowerCase(), ""); 1025 if (!Utilities.noString(diff) && diff.length() <= 5 && Utilities.isToken(diff) && !xml.abbreviationDefined(diff)) { 1026 return diff; 1027 } 1028 1029 int i = ns.length()-1; 1030 while (i > 0) { 1031 if (Character.isAlphabetic(ns.charAt(i)) || Character.isDigit(ns.charAt(i))) { 1032 i--; 1033 } else { 1034 break; 1035 } 1036 } 1037 String tail = ns.substring(i+1); 1038 if (!Utilities.noString(tail) && tail.length() <= 5 && Utilities.isToken(tail) && !xml.abbreviationDefined(tail)) { 1039 return tail; 1040 } 1041 1042 i = 0; 1043 while (xml.abbreviationDefined("ns"+i)) { 1044 i++; 1045 } 1046 return "ns"+i; 1047 } 1048 private String checkHeader(List<ValidationMessage> errors, InputStream stream) throws IOException { 1049 try { 1050 // the stream will either start with the UTF-8 BOF or with <xml 1051 int i0 = stream.read(); 1052 int i1 = stream.read(); 1053 int i2 = stream.read(); 1054 1055 StringBuilder b = new StringBuilder(); 1056 if (i0 == 0xEF && i1 == 0xBB && i2 == 0xBF) { 1057 // ok, it's UTF-8 1058 } else if (i0 == 0x3C && i1 == 0x3F && i2 == 0x78) { // <xm 1059 b.append((char) i0); 1060 b.append((char) i1); 1061 b.append((char) i2); 1062 } else if (i0 == 60) { // just plain old XML with no header 1063 return "1.0"; 1064 } else { 1065 throw new Exception(context.formatMessage(I18nConstants.XML_ENCODING_INVALID)); 1066 } 1067 int i = stream.read(); 1068 do { 1069 b.append((char) i); 1070 i = stream.read(); 1071 } while (i != 0x3E); 1072 String header = b.toString(); 1073 String e = null; 1074 i = header.indexOf("encoding=\""); 1075 if (i > -1) { 1076 e = header.substring(i+10, i+15); 1077 } else { 1078 i = header.indexOf("encoding='"); 1079 if (i > -1) { 1080 e = header.substring(i+10, i+15); 1081 } 1082 } 1083 if (e != null && !"UTF-8".equalsIgnoreCase(e)) { 1084 logError(errors, ValidationMessage.NO_RULE_DATE, 0, 0, "XML", IssueType.INVALID, context.formatMessage(I18nConstants.XML_ENCODING_INVALID, e), IssueSeverity.ERROR); 1085 } 1086 1087 i = header.indexOf("version=\""); 1088 if (i > -1) { 1089 return header.substring(i+9, i+12); 1090 } else { 1091 i = header.indexOf("version='"); 1092 if (i > -1) { 1093 return header.substring(i+9, i+12); 1094 } 1095 } 1096 return "?xml-p1?"; 1097 } catch (Exception e) { 1098 // suppress this error 1099 logError(errors, ValidationMessage.NO_RULE_DATE, 0, 0, "XML", IssueType.INVALID, e.getMessage(), IssueSeverity.ERROR); 1100 } 1101 return "?xml-p2?"; 1102 } 1103 1104 class NullErrorHandler implements ErrorHandler { 1105 @Override 1106 public void fatalError(SAXParseException e) { 1107 // do nothing 1108 } 1109 1110 @Override 1111 public void error(SAXParseException e) { 1112 // do nothing 1113 } 1114 1115 @Override 1116 public void warning(SAXParseException e) { 1117 // do nothing 1118 } 1119 } 1120 1121 public boolean isElideElements() { 1122 return elideElements; 1123 } 1124 1125 public void setElideElements(boolean elideElements) { 1126 this.elideElements = elideElements; 1127 } 1128 1129}