001package org.hl7.fhir.dstu3.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.dstu3.model.Base;
072import org.hl7.fhir.dstu3.model.DomainResource;
073import org.hl7.fhir.dstu3.model.Element;
074import org.hl7.fhir.dstu3.model.Resource;
075import org.hl7.fhir.dstu3.model.StringType;
076import org.hl7.fhir.dstu3.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 {
299                if (!isAllowUnknownContent())
300                        throw new FHIRFormatError("Unknown Content "+xpp.getName()+" @ "+pathForLocation(xpp));
301        }
302
303        protected XhtmlNode parseXhtml(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError {
304                XhtmlParser prsr = new XhtmlParser();
305                try {
306                        return prsr.parseHtmlNode(xpp);
307                } catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
308                        throw new FHIRFormatError(e.getMessage(), e);
309                }
310        }
311
312        private String parseString(XmlPullParser xpp) throws XmlPullParserException, FHIRFormatError, IOException {
313                StringBuilder res = new StringBuilder();
314                next(xpp);
315                while (xpp.getEventType() == XmlPullParser.TEXT || xpp.getEventType() == XmlPullParser.IGNORABLE_WHITESPACE || xpp.getEventType() == XmlPullParser.ENTITY_REF) {
316                        res.append(xpp.getText());
317                        next(xpp);
318                }
319                if (xpp.getEventType() != XmlPullParser.END_TAG)
320                        throw new FHIRFormatError("Bad String Structure - parsed "+res.toString()+" now found "+Integer.toString(xpp.getEventType()));
321                next(xpp);
322                return res.length() == 0 ? null : res.toString();
323        }
324
325        private int parseInt(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException {
326                int res = -1;
327                String textNode = parseString(xpp);
328                res = java.lang.Integer.parseInt(textNode);
329                return res;
330        }
331
332        protected DomainResource parseDomainResourceContained(XmlPullParser xpp)  throws IOException, FHIRFormatError, XmlPullParserException {
333                next(xpp);
334                int eventType = nextNoWhitespace(xpp);
335                if (eventType == XmlPullParser.START_TAG) { 
336                        DomainResource dr = (DomainResource) parseResource(xpp);
337                        nextNoWhitespace(xpp);
338                        next(xpp);
339                        return dr;
340                } else {
341                        unknownContent(xpp);
342                        return null;
343                }
344        } 
345        protected Resource parseResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException  {
346                next(xpp);
347                int eventType = nextNoWhitespace(xpp);
348                if (eventType == XmlPullParser.START_TAG) { 
349                        Resource r = (Resource) parseResource(xpp);
350                        nextNoWhitespace(xpp);
351                        next(xpp);
352                        return r;
353                } else {
354                        unknownContent(xpp);
355                        return null;
356                }
357        }
358
359        public void compose(IXMLWriter writer, Resource resource, boolean htmlPretty)  throws IOException   {
360                this.htmlPretty = htmlPretty;
361                xml = writer;
362                xml.setDefaultNamespace(FHIR_NS);
363                composeResource(resource);
364        }
365
366        protected abstract void composeResource(Resource resource) throws IOException ;
367
368        protected void composeElementAttributes(Element element) throws IOException {
369                if (style != OutputStyle.CANONICAL)
370                        for (String comment : element.getFormatCommentsPre())
371                                xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY);
372                if (element.getId() != null) 
373                        xml.attribute("id", element.getId());
374        }
375
376        protected void composeElementClose(Base base) throws IOException {
377                if (style != OutputStyle.CANONICAL)
378                        for (String comment : base.getFormatCommentsPost())
379                                xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY);
380        }
381        protected void composeTypeAttributes(Type type) throws IOException {
382                composeElementAttributes(type);
383        }
384
385        protected void composeXhtml(String name, XhtmlNode html) throws IOException {
386                if (!Utilities.noString(xhtmlMessage)) {
387                        xml.enter(XhtmlComposer.XHTML_NS, name);
388                        xml.comment(xhtmlMessage, false);
389                        xml.exit(XhtmlComposer.XHTML_NS, name);
390                } else {
391                        XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty);
392                        // name is also found in the html and should the same
393                        // ? check that
394                        boolean oldPretty = xml.isPretty();
395                        xml.setPretty(htmlPretty);
396                        if (html.getNodeType() != NodeType.Text && html.getNsDecl() == null)
397                                xml.namespace(XhtmlComposer.XHTML_NS, null);
398                        comp.compose(xml, html);
399                        xml.setPretty(oldPretty);
400                }
401        }
402
403
404        abstract protected void composeString(String name, StringType value) throws IOException ;
405
406        protected void composeString(String name, IIdType value) throws IOException  {
407                composeString(name, new StringType(value.getValue()));
408        }    
409
410
411        protected void composeDomainResource(String name, DomainResource res) throws IOException  {
412                xml.enter(FHIR_NS, name);
413                composeResource(res.getResourceType().toString(), res);
414                xml.exit(FHIR_NS, name);
415        }
416
417        
418
419        protected abstract void composeResource(String name, Resource res) throws IOException ;
420
421}