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