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