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