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