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}