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