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