
001package org.hl7.fhir.r5.formats; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033/* 034Copyright (c) 2011+, HL7, Inc 035All rights reserved. 036 037Redistribution and use in source and binary forms, with or without modification, 038are permitted provided that the following conditions are met: 039 040 * Redistributions of source code must retain the above copyright notice, this 041 list of conditions and the following disclaimer. 042 * Redistributions in binary form must reproduce the above copyright notice, 043 this list of conditions and the following disclaimer in the documentation 044 and/or other materials provided with the distribution. 045 * Neither the name of HL7 nor the names of its contributors may be used to 046 endorse or promote products derived from this software without specific 047 prior written permission. 048 049THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 050ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 051WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 052IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 053INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 054NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 055PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 056WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 057ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 058POSSIBILITY OF SUCH DAMAGE. 059 060 */ 061 062import java.io.BufferedInputStream; 063import java.io.ByteArrayInputStream; 064import java.io.IOException; 065import java.io.InputStream; 066import java.io.OutputStream; 067import java.io.UnsupportedEncodingException; 068import java.util.ArrayList; 069import java.util.List; 070 071import org.hl7.fhir.exceptions.FHIRFormatError; 072import org.hl7.fhir.instance.model.api.IIdType; 073import org.hl7.fhir.r5.model.Base; 074import org.hl7.fhir.r5.model.DataType; 075import org.hl7.fhir.r5.model.DomainResource; 076import org.hl7.fhir.r5.model.Element; 077import org.hl7.fhir.r5.model.Resource; 078import org.hl7.fhir.r5.model.StringType; 079import org.hl7.fhir.r5.test.utils.ClassesLoadedFlags; 080import org.hl7.fhir.utilities.Utilities; 081import org.hl7.fhir.utilities.xhtml.NodeType; 082import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 083import org.hl7.fhir.utilities.xhtml.XhtmlNode; 084import org.hl7.fhir.utilities.xhtml.XhtmlParser; 085import org.hl7.fhir.utilities.xml.IXMLWriter; 086import org.hl7.fhir.utilities.xml.XMLWriter; 087import org.slf4j.LoggerFactory; 088import org.xmlpull.v1.XmlPullParser; 089import org.xmlpull.v1.XmlPullParserException; 090import org.xmlpull.v1.XmlPullParserFactory; 091 092/** 093 * General parser for XML content. You instantiate an XmlParser of these, but you 094 * actually use parse or parseGeneral defined on this class 095 * 096 * The two classes are separated to keep generated and manually maintained code apart. 097 */ 098public abstract class XmlParserBase extends ParserBase implements IParser { 099 100 static { 101 LoggerFactory.getLogger("org.hl7.fhir.r5.formats.XmlParserBase").debug("XML Parser is being loaded"); 102 ClassesLoadedFlags.ourXmlParserBaseLoaded = true; 103 } 104 105 @Override 106 public ParserType getType() { 107 return ParserType.XML; 108 } 109 110 111 // -- in descendent generated code -------------------------------------- 112 113 abstract protected Resource parseResource(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError ; 114 abstract protected DataType parseType(XmlPullParser xml, String type) throws XmlPullParserException, IOException, FHIRFormatError ; 115 abstract protected DataType parseAnyType(XmlPullParser xml, String type) throws XmlPullParserException, IOException, FHIRFormatError ; 116 abstract protected void composeType(String prefix, DataType type) throws IOException ; 117 118 /* -- entry points --------------------------------------------------- */ 119 120 /** 121 * Parse content that is known to be a resource 122 * @ 123 */ 124 @Override 125 public Resource parse(InputStream input) throws IOException, FHIRFormatError { 126 try { 127 XmlPullParser xpp = loadXml(input); 128 return parse(xpp); 129 } catch (XmlPullParserException e) { 130 throw new FHIRFormatError(e.getMessage(), e); 131 } 132 } 133 134 /** 135 * parse xml that is known to be a resource, and that is already being read by an XML Pull Parser 136 * This is if a resource is in a bigger piece of XML. 137 * @ 138 */ 139 public Resource parse(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException { 140 if (xpp.getNamespace() == null) 141 throw new FHIRFormatError("This does not appear to be a FHIR resource (no namespace '"+xpp.getNamespace()+"') (@ /) "+Integer.toString(xpp.getEventType())); 142 if (!xpp.getNamespace().equals(FHIR_NS)) 143 throw new FHIRFormatError("This does not appear to be a FHIR resource (wrong namespace '"+xpp.getNamespace()+"') (@ /)"); 144 return parseResource(xpp); 145 } 146 147 @Override 148 public DataType parseType(InputStream input, String knownType) throws IOException, FHIRFormatError { 149 try { 150 XmlPullParser xml = loadXml(input); 151 return parseType(xml, knownType); 152 } catch (XmlPullParserException e) { 153 throw new FHIRFormatError(e.getMessage(), e); 154 } 155 } 156 157 @Override 158 public DataType parseAnyType(InputStream input, String knownType) throws IOException, FHIRFormatError { 159 try { 160 XmlPullParser xml = loadXml(input); 161 return parseAnyType(xml, knownType); 162 } catch (XmlPullParserException e) { 163 throw new FHIRFormatError(e.getMessage(), e); 164 } 165 } 166 167 /** 168 * Compose a resource to a stream, possibly using pretty presentation for a human reader (used in the spec, for example, but not normally in production) 169 * @ 170 */ 171 @Override 172 public void compose(OutputStream stream, Resource resource) throws IOException { 173 XMLWriter writer = new XMLWriter(stream, "UTF-8"); 174 writer.setPretty(style == OutputStyle.PRETTY); 175 writer.start(); 176 compose(writer, resource, writer.isPretty()); 177 writer.end(); 178 } 179 180 /** 181 * Compose a resource to a stream, possibly using pretty presentation for a human reader, and maybe a different choice in the xhtml narrative (used in the spec in one place, but should not be used in production) 182 * @ 183 */ 184 public void compose(OutputStream stream, Resource resource, boolean htmlPretty) throws IOException { 185 XMLWriter writer = new XMLWriter(stream, "UTF-8"); 186 writer.setPretty(style == OutputStyle.PRETTY); 187 writer.start(); 188 compose(writer, resource, htmlPretty); 189 writer.end(); 190 } 191 192 /** 193 * Compose a type to a stream (used in the spec, for example, but not normally in production) 194 * @ 195 */ 196 public void compose(OutputStream stream, String rootName, DataType type) throws IOException { 197 xml = new XMLWriter(stream, "UTF-8"); 198 xml.setPretty(style == OutputStyle.PRETTY); 199 xml.start(); 200 xml.setDefaultNamespace(FHIR_NS); 201 composeType(Utilities.noString(rootName) ? "value" : rootName, type); 202 xml.end(); 203 } 204 205 @Override 206 public void compose(OutputStream stream, DataType type, String rootName) throws IOException { 207 xml = new XMLWriter(stream, "UTF-8"); 208 xml.setPretty(style == OutputStyle.PRETTY); 209 xml.start(); 210 xml.setDefaultNamespace(FHIR_NS); 211 composeType(Utilities.noString(rootName) ? "value" : rootName, type); 212 xml.end(); 213 } 214 215 216 protected boolean composeCustomResource(Resource resource) throws IOException { 217 if (customResourceHandlers.containsKey(resource.fhirType())) { 218 customResourceHandlers.get(resource.fhirType()).composerXml(xml).composeResource(resource); 219 return true; 220 } else { 221 return false; 222 } 223 } 224 225 protected Resource parseCustomResource(XmlPullParser xpp) throws FHIRFormatError, IOException, XmlPullParserException { 226 if (customResourceHandlers.containsKey(xpp.getName())) { 227 return customResourceHandlers.get(xpp.getName()).parserXml(allowUnknownContent).parse(xpp); 228 } else { 229 return null; 230 } 231 } 232 233 234 /* -- xml routines --------------------------------------------------- */ 235 236 protected XmlPullParser loadXml(String source) throws UnsupportedEncodingException, XmlPullParserException, IOException { 237 return loadXml(new ByteArrayInputStream(source.getBytes("UTF-8"))); 238 } 239 240 protected XmlPullParser loadXml(InputStream stream) throws XmlPullParserException, IOException { 241 BufferedInputStream input = new BufferedInputStream(stream); 242 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); 243 factory.setNamespaceAware(true); 244 factory.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, false); 245 XmlPullParser xpp = factory.newPullParser(); 246 xpp.setInput(input, "UTF-8"); 247 next(xpp); 248 nextNoWhitespace(xpp); 249 250 return xpp; 251 } 252 253 protected int next(XmlPullParser xpp) throws XmlPullParserException, IOException { 254 if (handleComments) 255 return xpp.nextToken(); 256 else 257 return xpp.next(); 258 } 259 260 protected List<String> comments = new ArrayList<String>(); 261 262 protected int nextNoWhitespace(XmlPullParser xpp) throws XmlPullParserException, IOException { 263 int eventType = xpp.getEventType(); 264 while ((eventType == XmlPullParser.TEXT && xpp.isWhitespace()) || (eventType == XmlPullParser.COMMENT) 265 || (eventType == XmlPullParser.CDSECT) || (eventType == XmlPullParser.IGNORABLE_WHITESPACE) 266 || (eventType == XmlPullParser.PROCESSING_INSTRUCTION) || (eventType == XmlPullParser.DOCDECL)) { 267 if (eventType == XmlPullParser.COMMENT) { 268 comments.add(xpp.getText()); 269 } else if (eventType == XmlPullParser.DOCDECL) { 270 throw new XmlPullParserException("DTD declarations are not allowed"); 271 } 272 eventType = next(xpp); 273 } 274 return eventType; 275 } 276 277 278 protected void skipElementWithContent(XmlPullParser xpp) throws XmlPullParserException, IOException { 279 // when this is called, we are pointing an element that may have content 280 while (xpp.getEventType() != XmlPullParser.END_TAG) { 281 next(xpp); 282 if (xpp.getEventType() == XmlPullParser.START_TAG) 283 skipElementWithContent(xpp); 284 } 285 next(xpp); 286 } 287 288 protected void skipEmptyElement(XmlPullParser xpp) throws XmlPullParserException, IOException { 289 while (xpp.getEventType() != XmlPullParser.END_TAG) 290 next(xpp); 291 next(xpp); 292 } 293 294 protected IXMLWriter xml; 295 protected boolean htmlPretty; 296 private String schemaPath; 297 298 public String getSchemaPath() { 299 return schemaPath; 300 } 301 public void setSchemaPath(String schemaPath) { 302 this.schemaPath = schemaPath; 303 } 304 305 306 307 /* -- worker routines --------------------------------------------------- */ 308 309 protected void parseTypeAttributes(XmlPullParser xpp, DataType t) { 310 parseElementAttributes(xpp, t); 311 } 312 313 protected void parseElementAttributes(XmlPullParser xpp, Element e) { 314 if (xpp.getAttributeValue(null, "id") != null) { 315 e.setId(xpp.getAttributeValue(null, "id")); 316 idMap.put(e.getId(), e); 317 } 318 if (!comments.isEmpty()) { 319 e.getFormatCommentsPre().addAll(comments); 320 comments.clear(); 321 } 322 } 323 324 protected void parseElementClose(Base e) { 325 if (!comments.isEmpty()) { 326 e.getFormatCommentsPost().addAll(comments); 327 comments.clear(); 328 } 329 } 330 331 protected void parseBackboneAttributes(XmlPullParser xpp, Element e) { 332 parseElementAttributes(xpp, e); 333 } 334 335 protected void parseResourceAttributes(XmlPullParser xpp, Resource r) { 336 } 337 338 private String pathForLocation(XmlPullParser xpp) { 339 return xpp.getPositionDescription(); 340 } 341 342 343 protected void unknownContent(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException { 344 if (!isAllowUnknownContent()) 345 throw new FHIRFormatError("Unknown Content "+xpp.getName()+" @ "+pathForLocation(xpp)); 346 // otherwise, read over whatever element this is 347 int count = 1; 348 do { 349 xpp.next(); 350 if (xpp.getEventType() == XmlPullParser.END_TAG) 351 count--; 352 if (xpp.getEventType() == XmlPullParser.START_TAG) 353 count++; 354 } while (count > 0); 355 xpp.next(); 356 } 357 358 protected XhtmlNode parseXhtml(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError { 359 XhtmlParser prsr = new XhtmlParser(); 360 try { 361 return prsr.parseHtmlNode(xpp); 362 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 363 throw new FHIRFormatError(e.getMessage(), e); 364 } 365 } 366 367 private String parseString(XmlPullParser xpp) throws XmlPullParserException, FHIRFormatError, IOException { 368 StringBuilder res = new StringBuilder(); 369 next(xpp); 370 while (xpp.getEventType() == XmlPullParser.TEXT || xpp.getEventType() == XmlPullParser.IGNORABLE_WHITESPACE || xpp.getEventType() == XmlPullParser.ENTITY_REF) { 371 res.append(xpp.getText()); 372 next(xpp); 373 } 374 if (xpp.getEventType() != XmlPullParser.END_TAG) 375 throw new FHIRFormatError("Bad String Structure - parsed "+res.toString()+" now found "+Integer.toString(xpp.getEventType())); 376 next(xpp); 377 return res.length() == 0 ? null : res.toString(); 378 } 379 380 private int parseInt(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException { 381 int res = -1; 382 String textNode = parseString(xpp); 383 res = java.lang.Integer.parseInt(textNode); 384 return res; 385 } 386 387 protected DomainResource parseDomainResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException { 388 next(xpp); 389 int eventType = nextNoWhitespace(xpp); 390 if (eventType == XmlPullParser.START_TAG) { 391 DomainResource dr = (DomainResource) parseResource(xpp); 392 nextNoWhitespace(xpp); 393 next(xpp); 394 return dr; 395 } else { 396 unknownContent(xpp); 397 return null; 398 } 399 } 400 protected Resource parseResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException { 401 next(xpp); 402 int eventType = nextNoWhitespace(xpp); 403 if (eventType == XmlPullParser.START_TAG) { 404 Resource r = (Resource) parseResource(xpp); 405 nextNoWhitespace(xpp); 406 next(xpp); 407 return r; 408 } else { 409 unknownContent(xpp); 410 return null; 411 } 412 } 413 414 public void compose(IXMLWriter writer, Resource resource, boolean htmlPretty) throws IOException { 415 this.htmlPretty = htmlPretty; 416 xml = writer; 417 xml.setDefaultNamespace(FHIR_NS); 418 if (schemaPath != null) { 419 xml.setSchemaLocation(FHIR_NS, Utilities.pathURL(schemaPath, resource.fhirType()+".xsd")); 420 } 421 composeResource(resource); 422 } 423 424 protected abstract void composeResource(Resource resource) throws IOException ; 425 426 protected void composeElementAttributes(Element element) throws IOException { 427 if (style != OutputStyle.CANONICAL) 428 for (String comment : element.getFormatCommentsPre()) 429 xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY); 430 if (element.getId() != null) 431 xml.attribute("id", element.getId()); 432 } 433 434 protected void composeElementClose(Base base) throws IOException { 435 if (style != OutputStyle.CANONICAL) 436 for (String comment : base.getFormatCommentsPost()) 437 xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY); 438 } 439 protected void composeResourceAttributes(Resource element) throws IOException { 440 if (style != OutputStyle.CANONICAL) 441 for (String comment : element.getFormatCommentsPre()) 442 xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY); 443 } 444 445 protected void composeTypeAttributes(DataType type) throws IOException { 446 composeElementAttributes(type); 447 } 448 449 protected void composeXhtml(String name, XhtmlNode html) throws IOException { 450 if (!Utilities.noString(xhtmlMessage)) { 451 xml.enter(XhtmlComposer.XHTML_NS, name); 452 xml.comment(xhtmlMessage, false); 453 xml.exit(XhtmlComposer.XHTML_NS, name); 454 } else { 455 XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty); 456 // name is also found in the html and should the same 457 // ? check that 458 boolean oldPretty = xml.isPretty(); 459 xml.setPretty(htmlPretty); 460 if (html.getNodeType() != NodeType.Text && html.getNsDecl() == null) 461 xml.namespace(XhtmlComposer.XHTML_NS, null); 462 comp.compose(xml, html); 463 xml.setPretty(oldPretty); 464 } 465 } 466 467 protected void composeBaseElements(Base element) throws IOException { 468 // nothing 469 } 470 471 abstract protected void composeString(String name, StringType value) throws IOException ; 472 473 protected void composeString(String name, IIdType value) throws IOException { 474 composeString(name, new StringType(value.getValue())); 475 } 476 477 478 protected void composeDomainResource(String name, DomainResource res) throws IOException { 479 xml.enter(FHIR_NS, name); 480 composeResource(res.getResourceType().toString(), res); 481 xml.exit(FHIR_NS, name); 482 } 483 484 485 486 protected abstract void composeResource(String name, Resource res) throws IOException ; 487 488}