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.*; 032import java.util.ArrayList; 033import java.util.List; 034 035import javax.xml.stream.XMLEventReader; 036import javax.xml.stream.XMLStreamException; 037 038import org.hl7.fhir.exceptions.FHIRFormatError; 039import org.hl7.fhir.dstu3.model.*; 040import org.hl7.fhir.instance.model.api.IIdType; 041import org.hl7.fhir.utilities.Utilities; 042import org.hl7.fhir.utilities.xhtml.NodeType; 043import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 044import org.hl7.fhir.utilities.xhtml.XhtmlNode; 045import org.hl7.fhir.utilities.xhtml.XhtmlParser; 046import org.hl7.fhir.utilities.xml.IXMLWriter; 047import org.hl7.fhir.utilities.xml.XMLWriter; 048import org.xmlpull.v1.XmlPullParser; 049import org.xmlpull.v1.XmlPullParserException; 050 051/** 052 * General parser for XML content. You instantiate an XmlParser of these, but you 053 * actually use parse or parseGeneral defined on this class 054 * 055 * The two classes are separated to keep generated and manually maintained code apart. 056 */ 057public abstract class XmlParserBase extends ParserBase implements IParser { 058 059 @Override 060 public ParserType getType() { 061 return ParserType.XML; 062 } 063 064 // -- in descendent generated code -------------------------------------- 065 066 abstract protected Resource parseResource(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError ; 067 abstract protected Type parseType(XmlPullParser xml, String type) throws XmlPullParserException, IOException, FHIRFormatError ; 068 abstract protected void composeType(String prefix, Type type) throws IOException ; 069 070 /* -- entry points --------------------------------------------------- */ 071 072 /** 073 * Parse content that is known to be a resource 074 * @ 075 */ 076 @Override 077 public Resource parse(InputStream input) throws IOException, FHIRFormatError { 078 try { 079 XmlPullParser xpp = loadXml(input); 080 return parse(xpp); 081 } catch (XmlPullParserException e) { 082 throw new FHIRFormatError(e.getMessage(), e); 083 } 084 } 085 086 /** 087 * parse xml that is known to be a resource, and that is already being read by an XML Pull Parser 088 * This is if a resource is in a bigger piece of XML. 089 * @ 090 */ 091 public Resource parse(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException { 092 if (xpp.getNamespace() == null) 093 throw new FHIRFormatError("This does not appear to be a FHIR resource (no namespace '"+xpp.getNamespace()+"') (@ /) "+Integer.toString(xpp.getEventType())); 094 if (!xpp.getNamespace().equals(FHIR_NS)) 095 throw new FHIRFormatError("This does not appear to be a FHIR resource (wrong namespace '"+xpp.getNamespace()+"') (@ /)"); 096 return parseResource(xpp); 097 } 098 099 @Override 100 public Type parseType(InputStream input, String knownType) throws IOException, FHIRFormatError { 101 try { 102 XmlPullParser xml = loadXml(input); 103 return parseType(xml, knownType); 104 } catch (XmlPullParserException e) { 105 throw new FHIRFormatError(e.getMessage(), e); 106 } 107 } 108 109 110 /** 111 * 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) 112 * @ 113 */ 114 @Override 115 public void compose(OutputStream stream, Resource resource) throws IOException { 116 XMLWriter writer = new XMLWriter(stream, "UTF-8"); 117 writer.setPretty(style == OutputStyle.PRETTY); 118 writer.start(); 119 compose(writer, resource, writer.isPretty()); 120 writer.end(); 121 } 122 123 /** 124 * 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) 125 * @ 126 */ 127 public void compose(OutputStream stream, Resource resource, boolean htmlPretty) throws IOException { 128 XMLWriter writer = new XMLWriter(stream, "UTF-8"); 129 writer.setPretty(style == OutputStyle.PRETTY); 130 writer.start(); 131 compose(writer, resource, htmlPretty); 132 writer.end(); 133 } 134 135 136 /** 137 * Compose a type to a stream (used in the spec, for example, but not normally in production) 138 * @ 139 */ 140 public void compose(OutputStream stream, String rootName, Type type) throws IOException { 141 xml = new XMLWriter(stream, "UTF-8"); 142 xml.setPretty(style == OutputStyle.PRETTY); 143 xml.start(); 144 xml.setDefaultNamespace(FHIR_NS); 145 composeType(Utilities.noString(rootName) ? "value" : rootName, type); 146 xml.end(); 147 } 148 149 @Override 150 public void compose(OutputStream stream, Type type, String rootName) throws IOException { 151 xml = new XMLWriter(stream, "UTF-8"); 152 xml.setPretty(style == OutputStyle.PRETTY); 153 xml.start(); 154 xml.setDefaultNamespace(FHIR_NS); 155 composeType(Utilities.noString(rootName) ? "value" : rootName, type); 156 xml.end(); 157 } 158 159 160 161 /* -- xml routines --------------------------------------------------- */ 162 163 protected XmlPullParser loadXml(String source) throws UnsupportedEncodingException, XmlPullParserException, IOException { 164 return loadXml(new ByteArrayInputStream(source.getBytes("UTF-8"))); 165 } 166 167 protected XmlPullParser loadXml(InputStream stream) throws XmlPullParserException, IOException { 168 BufferedInputStream input = new BufferedInputStream(stream); 169// XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); 170// factory.setNamespaceAware(true); 171// factory.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, false); 172// XmlPullParser xpp = factory.newPullParser(); 173// xpp.setInput(input, "UTF-8"); 174// next(xpp); 175// nextNoWhitespace(xpp); 176// 177// return xpp; 178 throw new UnsupportedOperationException(); 179 } 180 181 protected int next(XmlPullParser xpp) throws XmlPullParserException, IOException { 182 if (handleComments) 183 return xpp.nextToken(); 184 else 185 return xpp.next(); 186 } 187 188 protected List<String> comments = new ArrayList<String>(); 189 190 protected int nextNoWhitespace(XmlPullParser xpp) throws XmlPullParserException, IOException { 191 int eventType = xpp.getEventType(); 192 while ((eventType == XmlPullParser.TEXT && xpp.isWhitespace()) || (eventType == XmlPullParser.COMMENT) 193 || (eventType == XmlPullParser.CDSECT) || (eventType == XmlPullParser.IGNORABLE_WHITESPACE) 194 || (eventType == XmlPullParser.PROCESSING_INSTRUCTION) || (eventType == XmlPullParser.DOCDECL)) { 195 if (eventType == XmlPullParser.COMMENT) { 196 comments.add(xpp.getText()); 197 } else if (eventType == XmlPullParser.DOCDECL) { 198 throw new XmlPullParserException("DTD declarations are not allowed"); 199 } 200 eventType = next(xpp); 201 } 202 return eventType; 203 } 204 205 206 protected void skipElementWithContent(XmlPullParser xpp) throws XmlPullParserException, IOException { 207 // when this is called, we are pointing an element that may have content 208 while (xpp.getEventType() != XmlPullParser.END_TAG) { 209 next(xpp); 210 if (xpp.getEventType() == XmlPullParser.START_TAG) 211 skipElementWithContent(xpp); 212 } 213 next(xpp); 214 } 215 216 protected void skipEmptyElement(XmlPullParser xpp) throws XmlPullParserException, IOException { 217 while (xpp.getEventType() != XmlPullParser.END_TAG) 218 next(xpp); 219 next(xpp); 220 } 221 222 protected IXMLWriter xml; 223 protected boolean htmlPretty; 224 225 226 227 /* -- worker routines --------------------------------------------------- */ 228 229 protected void parseTypeAttributes(XmlPullParser xpp, Type t) { 230 parseElementAttributes(xpp, t); 231 } 232 233 protected void parseElementAttributes(XmlPullParser xpp, Element e) { 234 if (xpp.getAttributeValue(null, "id") != null) { 235 e.setId(xpp.getAttributeValue(null, "id")); 236 idMap.put(e.getId(), e); 237 } 238 if (!comments.isEmpty()) { 239 e.getFormatCommentsPre().addAll(comments); 240 comments.clear(); 241 } 242 } 243 244 protected void parseElementClose(Base e) { 245 if (!comments.isEmpty()) { 246 e.getFormatCommentsPost().addAll(comments); 247 comments.clear(); 248 } 249 } 250 251 protected void parseBackboneAttributes(XmlPullParser xpp, Element e) { 252 parseElementAttributes(xpp, e); 253 } 254 255 private String pathForLocation(XmlPullParser xpp) { 256 return xpp.getPositionDescription(); 257 } 258 259 260 protected void unknownContent(XmlPullParser xpp) throws FHIRFormatError { 261 if (!isAllowUnknownContent()) 262 throw new FHIRFormatError("Unknown Content "+xpp.getName()+" @ "+pathForLocation(xpp)); 263 } 264 265 protected XhtmlNode parseXhtml(XMLEventReader xpp) throws IOException, FHIRFormatError, XMLStreamException { 266 XhtmlParser prsr = new XhtmlParser(); 267 try { 268 return prsr.parseHtmlNode(xpp); 269 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 270 throw new FHIRFormatError(e.getMessage(), e); 271 } 272 } 273 274 private String parseString(XmlPullParser xpp) throws XmlPullParserException, FHIRFormatError, IOException { 275 StringBuilder res = new StringBuilder(); 276 next(xpp); 277 while (xpp.getEventType() == XmlPullParser.TEXT || xpp.getEventType() == XmlPullParser.IGNORABLE_WHITESPACE || xpp.getEventType() == XmlPullParser.ENTITY_REF) { 278 res.append(xpp.getText()); 279 next(xpp); 280 } 281 if (xpp.getEventType() != XmlPullParser.END_TAG) 282 throw new FHIRFormatError("Bad String Structure - parsed "+res.toString()+" now found "+Integer.toString(xpp.getEventType())); 283 next(xpp); 284 return res.length() == 0 ? null : res.toString(); 285 } 286 287 private int parseInt(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException { 288 int res = -1; 289 String textNode = parseString(xpp); 290 res = java.lang.Integer.parseInt(textNode); 291 return res; 292 } 293 294 protected DomainResource parseDomainResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException { 295 next(xpp); 296 int eventType = nextNoWhitespace(xpp); 297 if (eventType == XmlPullParser.START_TAG) { 298 DomainResource dr = (DomainResource) parseResource(xpp); 299 nextNoWhitespace(xpp); 300 next(xpp); 301 return dr; 302 } else { 303 unknownContent(xpp); 304 return null; 305 } 306 } 307 protected Resource parseResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException { 308 next(xpp); 309 int eventType = nextNoWhitespace(xpp); 310 if (eventType == XmlPullParser.START_TAG) { 311 Resource r = (Resource) parseResource(xpp); 312 nextNoWhitespace(xpp); 313 next(xpp); 314 return r; 315 } else { 316 unknownContent(xpp); 317 return null; 318 } 319 } 320 321 public void compose(IXMLWriter writer, Resource resource, boolean htmlPretty) throws IOException { 322 this.htmlPretty = htmlPretty; 323 xml = writer; 324 xml.setDefaultNamespace(FHIR_NS); 325 composeResource(resource); 326 } 327 328 protected abstract void composeResource(Resource resource) throws IOException ; 329 330 protected void composeElementAttributes(Element element) throws IOException { 331 if (style != OutputStyle.CANONICAL) 332 for (String comment : element.getFormatCommentsPre()) 333 xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY); 334 if (element.getId() != null) 335 xml.attribute("id", element.getId()); 336 } 337 338 protected void composeElementClose(Base base) throws IOException { 339 if (style != OutputStyle.CANONICAL) 340 for (String comment : base.getFormatCommentsPost()) 341 xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY); 342 } 343 protected void composeTypeAttributes(Type type) throws IOException { 344 composeElementAttributes(type); 345 } 346 347 protected void composeXhtml(String name, XhtmlNode html) throws IOException { 348 if (!Utilities.noString(xhtmlMessage)) { 349 xml.enter(XhtmlComposer.XHTML_NS, name); 350 xml.comment(xhtmlMessage, false); 351 xml.exit(XhtmlComposer.XHTML_NS, name); 352 } else { 353 XhtmlComposer comp = new XhtmlComposer(); 354 // name is also found in the html and should the same 355 // ? check that 356 boolean oldPretty = xml.isPretty(); 357 xml.setPretty(htmlPretty); 358 comp.setXmlOnly(true); 359 if (html.getNodeType() != NodeType.Text && html.getNsDecl() == null) 360 xml.namespace(XhtmlComposer.XHTML_NS, null); 361 comp.compose(xml, html); 362 xml.setPretty(oldPretty); 363 } 364 } 365 366 367 abstract protected void composeString(String name, StringType value) throws IOException ; 368 369 protected void composeString(String name, IIdType value) throws IOException { 370 composeString(name, new StringType(value.getValue())); 371 } 372 373 374 protected void composeDomainResource(String name, DomainResource res) throws IOException { 375 xml.enter(FHIR_NS, name); 376 composeResource(res.getResourceType().toString(), res); 377 xml.exit(FHIR_NS, name); 378 } 379 380 protected abstract void composeResource(String name, Resource res) throws IOException ; 381 382}