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