001package org.hl7.fhir.r5.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.apache.commons.lang3.NotImplementedException;
072import org.hl7.fhir.exceptions.FHIRFormatError;
073import org.hl7.fhir.instance.model.api.IIdType;
074import org.hl7.fhir.r5.model.Base;
075import org.hl7.fhir.r5.model.DataType;
076import org.hl7.fhir.r5.model.DomainResource;
077import org.hl7.fhir.r5.model.Element;
078import org.hl7.fhir.r5.model.Resource;
079import org.hl7.fhir.r5.model.StringType;
080import org.hl7.fhir.r5.test.utils.ClassesLoadedFlags;
081import org.hl7.fhir.utilities.Utilities;
082import org.hl7.fhir.utilities.xhtml.NodeType;
083import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
084import org.hl7.fhir.utilities.xhtml.XhtmlNode;
085import org.hl7.fhir.utilities.xhtml.XhtmlParser;
086import org.hl7.fhir.utilities.xml.IXMLWriter;
087import org.hl7.fhir.utilities.xml.XMLWriter;
088import org.slf4j.LoggerFactory;
089import org.xmlpull.v1.XmlPullParser;
090import org.xmlpull.v1.XmlPullParserException;
091import org.xmlpull.v1.XmlPullParserFactory;
092
093/**
094 * General parser for XML content. You instantiate an XmlParser of these, but you 
095 * actually use parse or parseGeneral defined on this class
096 * 
097 * The two classes are separated to keep generated and manually maintained code apart.
098 */
099public abstract class XmlParserBase extends ParserBase implements IParser {
100
101  static {
102    LoggerFactory.getLogger("org.hl7.fhir.r5.formats.XmlParserBase").debug("XML Parser is being loaded");
103    ClassesLoadedFlags.ourXmlParserBaseLoaded = true;
104  }
105
106        @Override
107        public ParserType getType() {
108                return ParserType.XML;
109        }
110        
111        
112        // -- in descendent generated code --------------------------------------
113
114  abstract protected Resource parseResource(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError ;
115  abstract protected DataType parseType(XmlPullParser xml, String type) throws XmlPullParserException, IOException, FHIRFormatError ;
116  abstract protected DataType parseAnyType(XmlPullParser xml, String type) throws XmlPullParserException, IOException, FHIRFormatError ;
117        abstract protected void composeType(String prefix, DataType type) throws IOException ;
118
119        /* -- entry points --------------------------------------------------- */
120
121        protected Base parseBase(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException {
122    throw new NotImplementedException("Still to do (for openEHR)");
123    // return parseType(xpp, null);
124  }
125
126  
127        /**
128         * Parse content that is known to be a resource
129         * @ 
130         */
131        @Override
132        public Resource parse(InputStream input) throws IOException, FHIRFormatError {
133                try {
134                        XmlPullParser xpp = loadXml(input);
135                        return parse(xpp);
136                } catch (XmlPullParserException e) {
137                        throw new FHIRFormatError(e.getMessage(), e);
138                }
139        }
140
141        /**
142         * parse xml that is known to be a resource, and that is already being read by an XML Pull Parser
143         * This is if a resource is in a bigger piece of XML.   
144         * @ 
145         */
146        public Resource parse(XmlPullParser xpp)  throws IOException, FHIRFormatError, XmlPullParserException {
147                if (xpp.getNamespace() == null)
148                        throw new FHIRFormatError("This does not appear to be a FHIR resource (no namespace '"+xpp.getNamespace()+"') (@ /) "+Integer.toString(xpp.getEventType()));
149                if (!xpp.getNamespace().equals(FHIR_NS))
150                        throw new FHIRFormatError("This does not appear to be a FHIR resource (wrong namespace '"+xpp.getNamespace()+"') (@ /)");
151                return parseResource(xpp);
152        }
153
154        @Override
155        public DataType parseType(InputStream input, String knownType) throws IOException, FHIRFormatError  {
156                try {
157                        XmlPullParser xml = loadXml(input);
158                        return parseType(xml, knownType);
159                } catch (XmlPullParserException e) {
160                        throw new FHIRFormatError(e.getMessage(), e);
161                }
162        }
163
164  @Override
165  public DataType parseAnyType(InputStream input, String knownType) throws IOException, FHIRFormatError  {
166    try {
167      XmlPullParser xml = loadXml(input);
168      return parseAnyType(xml, knownType);
169    } catch (XmlPullParserException e) {
170      throw new FHIRFormatError(e.getMessage(), e);
171    }
172  }
173
174        /**
175         * 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)
176         * @ 
177         */
178        @Override
179        public void compose(OutputStream stream, Resource resource)  throws IOException {
180                XMLWriter writer = new XMLWriter(stream, "UTF-8");
181                writer.setPretty(style == OutputStyle.PRETTY);
182                writer.start();
183                compose(writer, resource, writer.isPretty());
184                writer.end();
185        }
186
187        /**
188         * 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)
189         * @ 
190         */
191        public void compose(OutputStream stream, Resource resource, boolean htmlPretty)  throws IOException {
192                XMLWriter writer = new XMLWriter(stream, "UTF-8");
193                writer.setPretty(style == OutputStyle.PRETTY);
194                writer.start();
195                compose(writer, resource, htmlPretty);
196                writer.end();
197        }
198
199        /**
200         * Compose a type to a stream (used in the spec, for example, but not normally in production)
201         * @ 
202         */
203        public void compose(OutputStream stream, String rootName, DataType type)  throws IOException {
204                xml = new XMLWriter(stream, "UTF-8");
205                xml.setPretty(style == OutputStyle.PRETTY);
206                xml.start();
207                xml.setDefaultNamespace(FHIR_NS);
208                composeType(Utilities.noString(rootName) ? "value" : rootName, type);
209                xml.end();
210        }
211
212        @Override
213        public void compose(OutputStream stream, DataType type, String rootName)  throws IOException {
214                xml = new XMLWriter(stream, "UTF-8");
215                xml.setPretty(style == OutputStyle.PRETTY);
216                xml.start();
217                xml.setDefaultNamespace(FHIR_NS);
218                composeType(Utilities.noString(rootName) ? "value" : rootName, type);
219                xml.end();
220        }
221
222
223  protected boolean composeCustomResource(Resource resource) throws IOException {
224    if (customResourceHandlers.containsKey(resource.fhirType())) {
225      customResourceHandlers.get(resource.fhirType()).composerXml(xml).composeResource(resource);
226      return true;
227    } else {
228      return false;
229    }
230  }
231
232  protected Resource parseCustomResource(XmlPullParser xpp) throws FHIRFormatError, IOException, XmlPullParserException {
233    if (customResourceHandlers.containsKey(xpp.getName())) {
234    return customResourceHandlers.get(xpp.getName()).parserXml(allowUnknownContent).parse(xpp);
235  } else {
236    return null;
237  }
238  }
239
240  
241        /* -- xml routines --------------------------------------------------- */
242
243        protected XmlPullParser loadXml(String source) throws UnsupportedEncodingException, XmlPullParserException, IOException {
244                return loadXml(new ByteArrayInputStream(source.getBytes("UTF-8")));
245        }
246
247        protected XmlPullParser loadXml(InputStream stream) throws XmlPullParserException, IOException {
248                BufferedInputStream input = new BufferedInputStream(stream);
249                XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
250                factory.setNamespaceAware(true);
251                factory.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, false);
252                XmlPullParser xpp = factory.newPullParser();
253                xpp.setInput(input, "UTF-8");
254                next(xpp);
255                nextNoWhitespace(xpp);
256
257                return xpp;
258        }
259
260        protected int next(XmlPullParser xpp) throws XmlPullParserException, IOException {
261                if (handleComments)
262                        return xpp.nextToken();
263                else
264                        return xpp.next();    
265        }
266
267        protected List<String> comments = new ArrayList<String>();
268
269        protected int nextNoWhitespace(XmlPullParser xpp) throws XmlPullParserException, IOException {
270                int eventType = xpp.getEventType();
271                while ((eventType == XmlPullParser.TEXT && xpp.isWhitespace()) || (eventType == XmlPullParser.COMMENT) 
272                                || (eventType == XmlPullParser.CDSECT) || (eventType == XmlPullParser.IGNORABLE_WHITESPACE)
273                                || (eventType == XmlPullParser.PROCESSING_INSTRUCTION) || (eventType == XmlPullParser.DOCDECL)) {
274                        if (eventType == XmlPullParser.COMMENT) {
275                                comments.add(xpp.getText());
276                        } else if (eventType == XmlPullParser.DOCDECL) {
277              throw new XmlPullParserException("DTD declarations are not allowed"); 
278      }  
279                        eventType = next(xpp);
280                }
281                return eventType;
282        }
283
284
285        protected void skipElementWithContent(XmlPullParser xpp) throws XmlPullParserException, IOException  {
286                // when this is called, we are pointing an element that may have content
287                while (xpp.getEventType() != XmlPullParser.END_TAG) {
288                        next(xpp);
289                        if (xpp.getEventType() == XmlPullParser.START_TAG) 
290                                skipElementWithContent(xpp);
291                }
292                next(xpp);
293        }
294
295        protected void skipEmptyElement(XmlPullParser xpp) throws XmlPullParserException, IOException {
296                while (xpp.getEventType() != XmlPullParser.END_TAG) 
297                        next(xpp);
298                next(xpp);
299        }
300
301        protected IXMLWriter xml;
302        protected boolean htmlPretty;
303  private String schemaPath;
304  
305  public String getSchemaPath() {
306    return schemaPath;
307  }
308  public void setSchemaPath(String schemaPath) {
309    this.schemaPath = schemaPath;
310  }
311
312
313
314        /* -- worker routines --------------------------------------------------- */
315
316        protected void parseTypeAttributes(XmlPullParser xpp, DataType t) {
317                parseElementAttributes(xpp, t);
318        }
319
320        protected void parseElementAttributes(XmlPullParser xpp, Element e) {
321                if (xpp.getAttributeValue(null, "id") != null) {
322                        e.setId(xpp.getAttributeValue(null, "id"));
323                        idMap.put(e.getId(), e);
324                }
325                if (!comments.isEmpty()) {
326                        e.getFormatCommentsPre().addAll(comments);
327                        comments.clear();
328                }
329        }
330
331        protected void parseElementClose(Base e) {
332                if (!comments.isEmpty()) {
333                        e.getFormatCommentsPost().addAll(comments);
334                        comments.clear();
335                }
336        }
337
338        protected void parseBackboneAttributes(XmlPullParser xpp, Element e) {
339                parseElementAttributes(xpp, e);
340        }
341
342  protected void parseResourceAttributes(XmlPullParser xpp, Resource r) {
343  }
344
345        private String pathForLocation(XmlPullParser xpp) {
346                return xpp.getPositionDescription();
347        }
348
349
350        protected void unknownContent(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException {
351                if (!isAllowUnknownContent())
352                        throw new FHIRFormatError("Unknown Content "+xpp.getName()+" @ "+pathForLocation(xpp));
353                // otherwise, read over whatever element this is 
354                int count = 1;
355                do {
356            xpp.next();
357                  if (xpp.getEventType() == XmlPullParser.END_TAG)
358                    count--;
359      if (xpp.getEventType() == XmlPullParser.START_TAG)
360        count++;                  
361                } while (count > 0);
362    xpp.next();
363        }
364
365        protected XhtmlNode parseXhtml(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError {
366                XhtmlParser prsr = new XhtmlParser();
367                try {
368                        return prsr.parseHtmlNode(xpp);
369                } catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
370                        throw new FHIRFormatError(e.getMessage(), e);
371                }
372        }
373
374        private String parseString(XmlPullParser xpp) throws XmlPullParserException, FHIRFormatError, IOException {
375                StringBuilder res = new StringBuilder();
376                next(xpp);
377                while (xpp.getEventType() == XmlPullParser.TEXT || xpp.getEventType() == XmlPullParser.IGNORABLE_WHITESPACE || xpp.getEventType() == XmlPullParser.ENTITY_REF) {
378                        res.append(xpp.getText());
379                        next(xpp);
380                }
381                if (xpp.getEventType() != XmlPullParser.END_TAG)
382                        throw new FHIRFormatError("Bad String Structure - parsed "+res.toString()+" now found "+Integer.toString(xpp.getEventType()));
383                next(xpp);
384                return res.length() == 0 ? null : res.toString();
385        }
386
387        private int parseInt(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException {
388                int res = -1;
389                String textNode = parseString(xpp);
390                res = java.lang.Integer.parseInt(textNode);
391                return res;
392        }
393
394        protected DomainResource parseDomainResourceContained(XmlPullParser xpp)  throws IOException, FHIRFormatError, XmlPullParserException {
395                next(xpp);
396                int eventType = nextNoWhitespace(xpp);
397                if (eventType == XmlPullParser.START_TAG) { 
398                        DomainResource dr = (DomainResource) parseResource(xpp);
399                        nextNoWhitespace(xpp);
400                        next(xpp);
401                        return dr;
402                } else {
403                        unknownContent(xpp);
404                        return null;
405                }
406        } 
407        protected Resource parseResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException  {
408                next(xpp);
409                int eventType = nextNoWhitespace(xpp);
410                if (eventType == XmlPullParser.START_TAG) { 
411                        Resource r = (Resource) parseResource(xpp);
412                        nextNoWhitespace(xpp);
413                        next(xpp);
414                        return r;
415                } else {
416                        unknownContent(xpp);
417                        return null;
418                }
419        }
420
421        public void compose(IXMLWriter writer, Resource resource, boolean htmlPretty)  throws IOException   {
422                this.htmlPretty = htmlPretty;
423                xml = writer;
424                xml.setDefaultNamespace(FHIR_NS);
425                if (schemaPath != null) {
426                  xml.setSchemaLocation(FHIR_NS, Utilities.pathURL(schemaPath, resource.fhirType()+".xsd"));
427                }
428                composeResource(resource);
429        }
430
431        protected abstract void composeResource(Resource resource) throws IOException ;
432
433        protected void composeElementAttributes(Element element) throws IOException {
434                if (style != OutputStyle.CANONICAL)
435                        for (String comment : element.getFormatCommentsPre())
436                                xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY);
437                if (element.getId() != null) 
438                        xml.attribute("id", element.getId());
439        }
440
441        protected void composeElementClose(Base base) throws IOException {
442                if (style != OutputStyle.CANONICAL)
443                        for (String comment : base.getFormatCommentsPost())
444                                xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY);
445        }
446  protected void composeResourceAttributes(Resource element) throws IOException {
447    if (style != OutputStyle.CANONICAL)
448      for (String comment : element.getFormatCommentsPre())
449        xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY);
450  }
451
452        protected void composeTypeAttributes(DataType type) throws IOException {
453                composeElementAttributes(type);
454        }
455
456        protected void composeXhtml(String name, XhtmlNode html) throws IOException {
457                if (!Utilities.noString(xhtmlMessage)) {
458                        xml.enter(XhtmlComposer.XHTML_NS, name);
459                        xml.comment(xhtmlMessage, false);
460                        xml.exit(XhtmlComposer.XHTML_NS, name);
461                } else {
462                        XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty);
463                        // name is also found in the html and should the same
464                        // ? check that
465                        boolean oldPretty = xml.isPretty();
466                        xml.setPretty(htmlPretty);
467                        if (html.getNodeType() != NodeType.Text && html.getNsDecl() == null)
468                                xml.namespace(XhtmlComposer.XHTML_NS, null);
469                        comp.compose(xml, html);
470                        xml.setPretty(oldPretty);
471                }
472        }
473
474  protected void composeBaseElements(Base element) throws IOException {
475    // nothing
476  }
477
478        abstract protected void composeString(String name, StringType value) throws IOException ;
479
480        protected void composeString(String name, IIdType value) throws IOException  {
481                composeString(name, new StringType(value.getValue()));
482        }    
483
484
485        protected void composeDomainResource(String name, DomainResource res) throws IOException  {
486                xml.enter(FHIR_NS, name);
487                composeResource(res.getResourceType().toString(), res);
488                xml.exit(FHIR_NS, name);
489        }
490
491        
492
493        protected abstract void composeResource(String name, Resource res) throws IOException ;
494
495
496        protected DataType parseNativePrimitive(XmlPullParser xpp) {
497    throw new NotImplementedException("Still to do (for openEHR)");
498  }
499
500
501        protected void composeNativePrimitive(String string, DataType defaultValue) {
502    throw new NotImplementedException("Still to do (for openEHR)");
503  }
504
505        protected void composeBase(String string, Base base) throws IOException {
506    throw new NotImplementedException("Still to do (for openEHR)");
507//     composeType(string, (DataType) base);
508  }
509
510}