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 217 /* -- xml routines --------------------------------------------------- */ 218 219 protected XmlPullParser loadXml(String source) throws UnsupportedEncodingException, XmlPullParserException, IOException { 220 return loadXml(new ByteArrayInputStream(source.getBytes("UTF-8"))); 221 } 222 223 protected XmlPullParser loadXml(InputStream stream) throws XmlPullParserException, IOException { 224 BufferedInputStream input = new BufferedInputStream(stream); 225 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); 226 factory.setNamespaceAware(true); 227 factory.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, false); 228 XmlPullParser xpp = factory.newPullParser(); 229 xpp.setInput(input, "UTF-8"); 230 next(xpp); 231 nextNoWhitespace(xpp); 232 233 return xpp; 234 } 235 236 protected int next(XmlPullParser xpp) throws XmlPullParserException, IOException { 237 if (handleComments) 238 return xpp.nextToken(); 239 else 240 return xpp.next(); 241 } 242 243 protected List<String> comments = new ArrayList<String>(); 244 245 protected int nextNoWhitespace(XmlPullParser xpp) throws XmlPullParserException, IOException { 246 int eventType = xpp.getEventType(); 247 while ((eventType == XmlPullParser.TEXT && xpp.isWhitespace()) || (eventType == XmlPullParser.COMMENT) 248 || (eventType == XmlPullParser.CDSECT) || (eventType == XmlPullParser.IGNORABLE_WHITESPACE) 249 || (eventType == XmlPullParser.PROCESSING_INSTRUCTION) || (eventType == XmlPullParser.DOCDECL)) { 250 if (eventType == XmlPullParser.COMMENT) { 251 comments.add(xpp.getText()); 252 } else if (eventType == XmlPullParser.DOCDECL) { 253 throw new XmlPullParserException("DTD declarations are not allowed"); 254 } 255 eventType = next(xpp); 256 } 257 return eventType; 258 } 259 260 261 protected void skipElementWithContent(XmlPullParser xpp) throws XmlPullParserException, IOException { 262 // when this is called, we are pointing an element that may have content 263 while (xpp.getEventType() != XmlPullParser.END_TAG) { 264 next(xpp); 265 if (xpp.getEventType() == XmlPullParser.START_TAG) 266 skipElementWithContent(xpp); 267 } 268 next(xpp); 269 } 270 271 protected void skipEmptyElement(XmlPullParser xpp) throws XmlPullParserException, IOException { 272 while (xpp.getEventType() != XmlPullParser.END_TAG) 273 next(xpp); 274 next(xpp); 275 } 276 277 protected IXMLWriter xml; 278 protected boolean htmlPretty; 279 private String schemaPath; 280 281 public String getSchemaPath() { 282 return schemaPath; 283 } 284 public void setSchemaPath(String schemaPath) { 285 this.schemaPath = schemaPath; 286 } 287 288 289 290 /* -- worker routines --------------------------------------------------- */ 291 292 protected void parseTypeAttributes(XmlPullParser xpp, DataType t) { 293 parseElementAttributes(xpp, t); 294 } 295 296 protected void parseElementAttributes(XmlPullParser xpp, Element e) { 297 if (xpp.getAttributeValue(null, "id") != null) { 298 e.setId(xpp.getAttributeValue(null, "id")); 299 idMap.put(e.getId(), e); 300 } 301 if (!comments.isEmpty()) { 302 e.getFormatCommentsPre().addAll(comments); 303 comments.clear(); 304 } 305 } 306 307 protected void parseElementClose(Base e) { 308 if (!comments.isEmpty()) { 309 e.getFormatCommentsPost().addAll(comments); 310 comments.clear(); 311 } 312 } 313 314 protected void parseBackboneAttributes(XmlPullParser xpp, Element e) { 315 parseElementAttributes(xpp, e); 316 } 317 318 protected void parseResourceAttributes(XmlPullParser xpp, Resource r) { 319 } 320 321 private String pathForLocation(XmlPullParser xpp) { 322 return xpp.getPositionDescription(); 323 } 324 325 326 protected void unknownContent(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException { 327 if (!isAllowUnknownContent()) 328 throw new FHIRFormatError("Unknown Content "+xpp.getName()+" @ "+pathForLocation(xpp)); 329 // otherwise, read over whatever element this is 330 int count = 1; 331 do { 332 xpp.next(); 333 if (xpp.getEventType() == XmlPullParser.END_TAG) 334 count--; 335 if (xpp.getEventType() == XmlPullParser.START_TAG) 336 count++; 337 } while (count > 0); 338 xpp.next(); 339 } 340 341 protected XhtmlNode parseXhtml(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError { 342 XhtmlParser prsr = new XhtmlParser(); 343 try { 344 return prsr.parseHtmlNode(xpp); 345 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 346 throw new FHIRFormatError(e.getMessage(), e); 347 } 348 } 349 350 private String parseString(XmlPullParser xpp) throws XmlPullParserException, FHIRFormatError, IOException { 351 StringBuilder res = new StringBuilder(); 352 next(xpp); 353 while (xpp.getEventType() == XmlPullParser.TEXT || xpp.getEventType() == XmlPullParser.IGNORABLE_WHITESPACE || xpp.getEventType() == XmlPullParser.ENTITY_REF) { 354 res.append(xpp.getText()); 355 next(xpp); 356 } 357 if (xpp.getEventType() != XmlPullParser.END_TAG) 358 throw new FHIRFormatError("Bad String Structure - parsed "+res.toString()+" now found "+Integer.toString(xpp.getEventType())); 359 next(xpp); 360 return res.length() == 0 ? null : res.toString(); 361 } 362 363 private int parseInt(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException { 364 int res = -1; 365 String textNode = parseString(xpp); 366 res = java.lang.Integer.parseInt(textNode); 367 return res; 368 } 369 370 protected DomainResource parseDomainResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException { 371 next(xpp); 372 int eventType = nextNoWhitespace(xpp); 373 if (eventType == XmlPullParser.START_TAG) { 374 DomainResource dr = (DomainResource) parseResource(xpp); 375 nextNoWhitespace(xpp); 376 next(xpp); 377 return dr; 378 } else { 379 unknownContent(xpp); 380 return null; 381 } 382 } 383 protected Resource parseResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException { 384 next(xpp); 385 int eventType = nextNoWhitespace(xpp); 386 if (eventType == XmlPullParser.START_TAG) { 387 Resource r = (Resource) parseResource(xpp); 388 nextNoWhitespace(xpp); 389 next(xpp); 390 return r; 391 } else { 392 unknownContent(xpp); 393 return null; 394 } 395 } 396 397 public void compose(IXMLWriter writer, Resource resource, boolean htmlPretty) throws IOException { 398 this.htmlPretty = htmlPretty; 399 xml = writer; 400 xml.setDefaultNamespace(FHIR_NS); 401 if (schemaPath != null) { 402 xml.setSchemaLocation(FHIR_NS, Utilities.pathURL(schemaPath, resource.fhirType()+".xsd")); 403 } 404 composeResource(resource); 405 } 406 407 protected abstract void composeResource(Resource resource) throws IOException ; 408 409 protected void composeElementAttributes(Element element) throws IOException { 410 if (style != OutputStyle.CANONICAL) 411 for (String comment : element.getFormatCommentsPre()) 412 xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY); 413 if (element.getId() != null) 414 xml.attribute("id", element.getId()); 415 } 416 417 protected void composeElementClose(Base base) throws IOException { 418 if (style != OutputStyle.CANONICAL) 419 for (String comment : base.getFormatCommentsPost()) 420 xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY); 421 } 422 protected void composeResourceAttributes(Resource element) throws IOException { 423 if (style != OutputStyle.CANONICAL) 424 for (String comment : element.getFormatCommentsPre()) 425 xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY); 426 } 427 428 protected void composeTypeAttributes(DataType type) throws IOException { 429 composeElementAttributes(type); 430 } 431 432 protected void composeXhtml(String name, XhtmlNode html) throws IOException { 433 if (!Utilities.noString(xhtmlMessage)) { 434 xml.enter(XhtmlComposer.XHTML_NS, name); 435 xml.comment(xhtmlMessage, false); 436 xml.exit(XhtmlComposer.XHTML_NS, name); 437 } else { 438 XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty); 439 // name is also found in the html and should the same 440 // ? check that 441 boolean oldPretty = xml.isPretty(); 442 xml.setPretty(htmlPretty); 443 if (html.getNodeType() != NodeType.Text && html.getNsDecl() == null) 444 xml.namespace(XhtmlComposer.XHTML_NS, null); 445 comp.compose(xml, html); 446 xml.setPretty(oldPretty); 447 } 448 } 449 450 protected void composeBaseElements(Base element) throws IOException { 451 // nothing 452 } 453 454 abstract protected void composeString(String name, StringType value) throws IOException ; 455 456 protected void composeString(String name, IIdType value) throws IOException { 457 composeString(name, new StringType(value.getValue())); 458 } 459 460 461 protected void composeDomainResource(String name, DomainResource res) throws IOException { 462 xml.enter(FHIR_NS, name); 463 composeResource(res.getResourceType().toString(), res); 464 xml.exit(FHIR_NS, name); 465 } 466 467 468 469 protected abstract void composeResource(String name, Resource res) throws IOException ; 470 471}