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