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}