
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.apache.commons.lang3.NotImplementedException; 072import org.hl7.fhir.exceptions.FHIRFormatError; 073import org.hl7.fhir.instance.model.api.IIdType; 074import org.hl7.fhir.r5.model.Base; 075import org.hl7.fhir.r5.model.DataType; 076import org.hl7.fhir.r5.model.DomainResource; 077import org.hl7.fhir.r5.model.Element; 078import org.hl7.fhir.r5.model.Resource; 079import org.hl7.fhir.r5.model.StringType; 080import org.hl7.fhir.r5.test.utils.ClassesLoadedFlags; 081import org.hl7.fhir.utilities.Utilities; 082import org.hl7.fhir.utilities.xhtml.NodeType; 083import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 084import org.hl7.fhir.utilities.xhtml.XhtmlNode; 085import org.hl7.fhir.utilities.xhtml.XhtmlParser; 086import org.hl7.fhir.utilities.xml.IXMLWriter; 087import org.hl7.fhir.utilities.xml.XMLWriter; 088import org.slf4j.LoggerFactory; 089import org.xmlpull.v1.XmlPullParser; 090import org.xmlpull.v1.XmlPullParserException; 091import org.xmlpull.v1.XmlPullParserFactory; 092 093/** 094 * General parser for XML content. You instantiate an XmlParser of these, but you 095 * actually use parse or parseGeneral defined on this class 096 * 097 * The two classes are separated to keep generated and manually maintained code apart. 098 */ 099public abstract class XmlParserBase extends ParserBase implements IParser { 100 101 static { 102 LoggerFactory.getLogger("org.hl7.fhir.r5.formats.XmlParserBase").debug("XML Parser is being loaded"); 103 ClassesLoadedFlags.ourXmlParserBaseLoaded = true; 104 } 105 106 @Override 107 public ParserType getType() { 108 return ParserType.XML; 109 } 110 111 112 // -- in descendent generated code -------------------------------------- 113 114 abstract protected Resource parseResource(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError ; 115 abstract protected DataType parseType(XmlPullParser xml, String type) throws XmlPullParserException, IOException, FHIRFormatError ; 116 abstract protected DataType parseAnyType(XmlPullParser xml, String type) throws XmlPullParserException, IOException, FHIRFormatError ; 117 abstract protected void composeType(String prefix, DataType type) throws IOException ; 118 119 /* -- entry points --------------------------------------------------- */ 120 121 protected Base parseBase(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException { 122 throw new NotImplementedException("Still to do (for openEHR)"); 123 // return parseType(xpp, null); 124 } 125 126 127 /** 128 * Parse content that is known to be a resource 129 * @ 130 */ 131 @Override 132 public Resource parse(InputStream input) throws IOException, FHIRFormatError { 133 try { 134 XmlPullParser xpp = loadXml(input); 135 return parse(xpp); 136 } catch (XmlPullParserException e) { 137 throw new FHIRFormatError(e.getMessage(), e); 138 } 139 } 140 141 /** 142 * parse xml that is known to be a resource, and that is already being read by an XML Pull Parser 143 * This is if a resource is in a bigger piece of XML. 144 * @ 145 */ 146 public Resource parse(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException { 147 if (xpp.getNamespace() == null) 148 throw new FHIRFormatError("This does not appear to be a FHIR resource (no namespace '"+xpp.getNamespace()+"') (@ /) "+Integer.toString(xpp.getEventType())); 149 if (!xpp.getNamespace().equals(FHIR_NS)) 150 throw new FHIRFormatError("This does not appear to be a FHIR resource (wrong namespace '"+xpp.getNamespace()+"') (@ /)"); 151 return parseResource(xpp); 152 } 153 154 @Override 155 public DataType parseType(InputStream input, String knownType) throws IOException, FHIRFormatError { 156 try { 157 XmlPullParser xml = loadXml(input); 158 return parseType(xml, knownType); 159 } catch (XmlPullParserException e) { 160 throw new FHIRFormatError(e.getMessage(), e); 161 } 162 } 163 164 @Override 165 public DataType parseAnyType(InputStream input, String knownType) throws IOException, FHIRFormatError { 166 try { 167 XmlPullParser xml = loadXml(input); 168 return parseAnyType(xml, knownType); 169 } catch (XmlPullParserException e) { 170 throw new FHIRFormatError(e.getMessage(), e); 171 } 172 } 173 174 /** 175 * 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) 176 * @ 177 */ 178 @Override 179 public void compose(OutputStream stream, Resource resource) throws IOException { 180 XMLWriter writer = new XMLWriter(stream, "UTF-8"); 181 writer.setPretty(style == OutputStyle.PRETTY); 182 writer.start(); 183 compose(writer, resource, writer.isPretty()); 184 writer.end(); 185 } 186 187 /** 188 * 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) 189 * @ 190 */ 191 public void compose(OutputStream stream, Resource resource, boolean htmlPretty) throws IOException { 192 XMLWriter writer = new XMLWriter(stream, "UTF-8"); 193 writer.setPretty(style == OutputStyle.PRETTY); 194 writer.start(); 195 compose(writer, resource, htmlPretty); 196 writer.end(); 197 } 198 199 /** 200 * Compose a type to a stream (used in the spec, for example, but not normally in production) 201 * @ 202 */ 203 public void compose(OutputStream stream, String rootName, DataType type) throws IOException { 204 xml = new XMLWriter(stream, "UTF-8"); 205 xml.setPretty(style == OutputStyle.PRETTY); 206 xml.start(); 207 xml.setDefaultNamespace(FHIR_NS); 208 composeType(Utilities.noString(rootName) ? "value" : rootName, type); 209 xml.end(); 210 } 211 212 @Override 213 public void compose(OutputStream stream, DataType type, String rootName) throws IOException { 214 xml = new XMLWriter(stream, "UTF-8"); 215 xml.setPretty(style == OutputStyle.PRETTY); 216 xml.start(); 217 xml.setDefaultNamespace(FHIR_NS); 218 composeType(Utilities.noString(rootName) ? "value" : rootName, type); 219 xml.end(); 220 } 221 222 223 protected boolean composeCustomResource(Resource resource) throws IOException { 224 if (customResourceHandlers.containsKey(resource.fhirType())) { 225 customResourceHandlers.get(resource.fhirType()).composerXml(xml).composeResource(resource); 226 return true; 227 } else { 228 return false; 229 } 230 } 231 232 protected Resource parseCustomResource(XmlPullParser xpp) throws FHIRFormatError, IOException, XmlPullParserException { 233 if (customResourceHandlers.containsKey(xpp.getName())) { 234 return customResourceHandlers.get(xpp.getName()).parserXml(allowUnknownContent).parse(xpp); 235 } else { 236 return null; 237 } 238 } 239 240 241 /* -- xml routines --------------------------------------------------- */ 242 243 protected XmlPullParser loadXml(String source) throws UnsupportedEncodingException, XmlPullParserException, IOException { 244 return loadXml(new ByteArrayInputStream(source.getBytes("UTF-8"))); 245 } 246 247 protected XmlPullParser loadXml(InputStream stream) throws XmlPullParserException, IOException { 248 BufferedInputStream input = new BufferedInputStream(stream); 249 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); 250 factory.setNamespaceAware(true); 251 factory.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, false); 252 XmlPullParser xpp = factory.newPullParser(); 253 xpp.setInput(input, "UTF-8"); 254 next(xpp); 255 nextNoWhitespace(xpp); 256 257 return xpp; 258 } 259 260 protected int next(XmlPullParser xpp) throws XmlPullParserException, IOException { 261 if (handleComments) 262 return xpp.nextToken(); 263 else 264 return xpp.next(); 265 } 266 267 protected List<String> comments = new ArrayList<String>(); 268 269 protected int nextNoWhitespace(XmlPullParser xpp) throws XmlPullParserException, IOException { 270 int eventType = xpp.getEventType(); 271 while ((eventType == XmlPullParser.TEXT && xpp.isWhitespace()) || (eventType == XmlPullParser.COMMENT) 272 || (eventType == XmlPullParser.CDSECT) || (eventType == XmlPullParser.IGNORABLE_WHITESPACE) 273 || (eventType == XmlPullParser.PROCESSING_INSTRUCTION) || (eventType == XmlPullParser.DOCDECL)) { 274 if (eventType == XmlPullParser.COMMENT) { 275 comments.add(xpp.getText()); 276 } else if (eventType == XmlPullParser.DOCDECL) { 277 throw new XmlPullParserException("DTD declarations are not allowed"); 278 } 279 eventType = next(xpp); 280 } 281 return eventType; 282 } 283 284 285 protected void skipElementWithContent(XmlPullParser xpp) throws XmlPullParserException, IOException { 286 // when this is called, we are pointing an element that may have content 287 while (xpp.getEventType() != XmlPullParser.END_TAG) { 288 next(xpp); 289 if (xpp.getEventType() == XmlPullParser.START_TAG) 290 skipElementWithContent(xpp); 291 } 292 next(xpp); 293 } 294 295 protected void skipEmptyElement(XmlPullParser xpp) throws XmlPullParserException, IOException { 296 while (xpp.getEventType() != XmlPullParser.END_TAG) 297 next(xpp); 298 next(xpp); 299 } 300 301 protected IXMLWriter xml; 302 protected boolean htmlPretty; 303 private String schemaPath; 304 305 public String getSchemaPath() { 306 return schemaPath; 307 } 308 public void setSchemaPath(String schemaPath) { 309 this.schemaPath = schemaPath; 310 } 311 312 313 314 /* -- worker routines --------------------------------------------------- */ 315 316 protected void parseTypeAttributes(XmlPullParser xpp, DataType t) { 317 parseElementAttributes(xpp, t); 318 } 319 320 protected void parseElementAttributes(XmlPullParser xpp, Element e) { 321 if (xpp.getAttributeValue(null, "id") != null) { 322 e.setId(xpp.getAttributeValue(null, "id")); 323 idMap.put(e.getId(), e); 324 } 325 if (!comments.isEmpty()) { 326 e.getFormatCommentsPre().addAll(comments); 327 comments.clear(); 328 } 329 } 330 331 protected void parseElementClose(Base e) { 332 if (!comments.isEmpty()) { 333 e.getFormatCommentsPost().addAll(comments); 334 comments.clear(); 335 } 336 } 337 338 protected void parseBackboneAttributes(XmlPullParser xpp, Element e) { 339 parseElementAttributes(xpp, e); 340 } 341 342 protected void parseResourceAttributes(XmlPullParser xpp, Resource r) { 343 } 344 345 private String pathForLocation(XmlPullParser xpp) { 346 return xpp.getPositionDescription(); 347 } 348 349 350 protected void unknownContent(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException { 351 if (!isAllowUnknownContent()) 352 throw new FHIRFormatError("Unknown Content "+xpp.getName()+" @ "+pathForLocation(xpp)); 353 // otherwise, read over whatever element this is 354 int count = 1; 355 do { 356 xpp.next(); 357 if (xpp.getEventType() == XmlPullParser.END_TAG) 358 count--; 359 if (xpp.getEventType() == XmlPullParser.START_TAG) 360 count++; 361 } while (count > 0); 362 xpp.next(); 363 } 364 365 protected XhtmlNode parseXhtml(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError { 366 XhtmlParser prsr = new XhtmlParser(); 367 try { 368 return prsr.parseHtmlNode(xpp); 369 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 370 throw new FHIRFormatError(e.getMessage(), e); 371 } 372 } 373 374 private String parseString(XmlPullParser xpp) throws XmlPullParserException, FHIRFormatError, IOException { 375 StringBuilder res = new StringBuilder(); 376 next(xpp); 377 while (xpp.getEventType() == XmlPullParser.TEXT || xpp.getEventType() == XmlPullParser.IGNORABLE_WHITESPACE || xpp.getEventType() == XmlPullParser.ENTITY_REF) { 378 res.append(xpp.getText()); 379 next(xpp); 380 } 381 if (xpp.getEventType() != XmlPullParser.END_TAG) 382 throw new FHIRFormatError("Bad String Structure - parsed "+res.toString()+" now found "+Integer.toString(xpp.getEventType())); 383 next(xpp); 384 return res.length() == 0 ? null : res.toString(); 385 } 386 387 private int parseInt(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException { 388 int res = -1; 389 String textNode = parseString(xpp); 390 res = java.lang.Integer.parseInt(textNode); 391 return res; 392 } 393 394 protected DomainResource parseDomainResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException { 395 next(xpp); 396 int eventType = nextNoWhitespace(xpp); 397 if (eventType == XmlPullParser.START_TAG) { 398 DomainResource dr = (DomainResource) parseResource(xpp); 399 nextNoWhitespace(xpp); 400 next(xpp); 401 return dr; 402 } else { 403 unknownContent(xpp); 404 return null; 405 } 406 } 407 protected Resource parseResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException { 408 next(xpp); 409 int eventType = nextNoWhitespace(xpp); 410 if (eventType == XmlPullParser.START_TAG) { 411 Resource r = (Resource) parseResource(xpp); 412 nextNoWhitespace(xpp); 413 next(xpp); 414 return r; 415 } else { 416 unknownContent(xpp); 417 return null; 418 } 419 } 420 421 public void compose(IXMLWriter writer, Resource resource, boolean htmlPretty) throws IOException { 422 this.htmlPretty = htmlPretty; 423 xml = writer; 424 xml.setDefaultNamespace(FHIR_NS); 425 if (schemaPath != null) { 426 xml.setSchemaLocation(FHIR_NS, Utilities.pathURL(schemaPath, resource.fhirType()+".xsd")); 427 } 428 composeResource(resource); 429 } 430 431 protected abstract void composeResource(Resource resource) throws IOException ; 432 433 protected void composeElementAttributes(Element element) throws IOException { 434 if (style != OutputStyle.CANONICAL) 435 for (String comment : element.getFormatCommentsPre()) 436 xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY); 437 if (element.getId() != null) 438 xml.attribute("id", element.getId()); 439 } 440 441 protected void composeElementClose(Base base) throws IOException { 442 if (style != OutputStyle.CANONICAL) 443 for (String comment : base.getFormatCommentsPost()) 444 xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY); 445 } 446 protected void composeResourceAttributes(Resource element) throws IOException { 447 if (style != OutputStyle.CANONICAL) 448 for (String comment : element.getFormatCommentsPre()) 449 xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY); 450 } 451 452 protected void composeTypeAttributes(DataType type) throws IOException { 453 composeElementAttributes(type); 454 } 455 456 protected void composeXhtml(String name, XhtmlNode html) throws IOException { 457 if (!Utilities.noString(xhtmlMessage)) { 458 xml.enter(XhtmlComposer.XHTML_NS, name); 459 xml.comment(xhtmlMessage, false); 460 xml.exit(XhtmlComposer.XHTML_NS, name); 461 } else { 462 XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty); 463 // name is also found in the html and should the same 464 // ? check that 465 boolean oldPretty = xml.isPretty(); 466 xml.setPretty(htmlPretty); 467 if (html.getNodeType() != NodeType.Text && html.getNsDecl() == null) 468 xml.namespace(XhtmlComposer.XHTML_NS, null); 469 comp.compose(xml, html); 470 xml.setPretty(oldPretty); 471 } 472 } 473 474 protected void composeBaseElements(Base element) throws IOException { 475 // nothing 476 } 477 478 abstract protected void composeString(String name, StringType value) throws IOException ; 479 480 protected void composeString(String name, IIdType value) throws IOException { 481 composeString(name, new StringType(value.getValue())); 482 } 483 484 485 protected void composeDomainResource(String name, DomainResource res) throws IOException { 486 xml.enter(FHIR_NS, name); 487 composeResource(res.getResourceType().toString(), res); 488 xml.exit(FHIR_NS, name); 489 } 490 491 492 493 protected abstract void composeResource(String name, Resource res) throws IOException ; 494 495 496 protected DataType parseNativePrimitive(XmlPullParser xpp) { 497 throw new NotImplementedException("Still to do (for openEHR)"); 498 } 499 500 501 protected void composeNativePrimitive(String string, DataType defaultValue) { 502 throw new NotImplementedException("Still to do (for openEHR)"); 503 } 504 505 protected void composeBase(String string, Base base) throws IOException { 506 throw new NotImplementedException("Still to do (for openEHR)"); 507// composeType(string, (DataType) base); 508 } 509 510}