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