View Javadoc
1   package ca.uhn.fhir.parser;
2   
3   /*-
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2019 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import ca.uhn.fhir.context.*;
24  import ca.uhn.fhir.model.api.*;
25  import ca.uhn.fhir.model.base.composite.BaseCodingDt;
26  import ca.uhn.fhir.model.primitive.IdDt;
27  import ca.uhn.fhir.model.primitive.InstantDt;
28  import ca.uhn.fhir.model.primitive.XhtmlDt;
29  import ca.uhn.fhir.narrative.INarrativeGenerator;
30  import ca.uhn.fhir.rest.api.EncodingEnum;
31  import ca.uhn.fhir.util.ElementUtil;
32  import ca.uhn.fhir.util.NonPrettyPrintWriterWrapper;
33  import ca.uhn.fhir.util.PrettyPrintWriterWrapper;
34  import ca.uhn.fhir.util.XmlUtil;
35  import org.apache.commons.lang3.StringUtils;
36  import org.hl7.fhir.instance.model.api.*;
37  
38  import javax.xml.namespace.QName;
39  import javax.xml.stream.*;
40  import javax.xml.stream.events.*;
41  import java.io.Reader;
42  import java.io.Writer;
43  import java.util.ArrayList;
44  import java.util.Iterator;
45  import java.util.List;
46  
47  import static org.apache.commons.lang3.StringUtils.isBlank;
48  import static org.apache.commons.lang3.StringUtils.isNotBlank;
49  
50  /**
51   * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use
52   * {@link FhirContext#newXmlParser()} to get an instance.
53   */
54  public class XmlParser extends BaseParser /* implements IParser */ {
55  
56  	static final String ATOM_NS = "http://www.w3.org/2005/Atom";
57  	static final String FHIR_NS = "http://hl7.org/fhir";
58  	static final String OPENSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1/";
59  	static final String RESREF_DISPLAY = "display";
60  	static final String RESREF_REFERENCE = "reference";
61  	static final String TOMBSTONES_NS = "http://purl.org/atompub/tombstones/1.0";
62  	static final String XHTML_NS = "http://www.w3.org/1999/xhtml";
63  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class);
64  
65  	// private static final Set<String> RESOURCE_NAMESPACES;
66  	private FhirContext myContext;
67  	private boolean myPrettyPrint;
68  
69  	/**
70  	 * Do not use this constructor, the recommended way to obtain a new instance of the XML parser is to invoke
71  	 * {@link FhirContext#newXmlParser()}.
72  	 *
73  	 * @param theParserErrorHandler
74  	 */
75  	public XmlParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
76  		super(theContext, theParserErrorHandler);
77  		myContext = theContext;
78  	}
79  
80  	private XMLEventReader createStreamReader(Reader theReader) {
81  		try {
82  			return XmlUtil.createXmlReader(theReader);
83  		} catch (FactoryConfigurationError e1) {
84  			throw new ConfigurationException("Failed to initialize STaX event factory", e1);
85  		} catch (XMLStreamException e1) {
86  			throw new DataFormatException(e1);
87  		}
88  	}
89  
90  	private XMLStreamWriter createXmlWriter(Writer theWriter) throws XMLStreamException {
91  		XMLStreamWriter eventWriter;
92  		eventWriter = XmlUtil.createXmlStreamWriter(theWriter);
93  		eventWriter = decorateStreamWriter(eventWriter);
94  		return eventWriter;
95  	}
96  
97  	private XMLStreamWriter decorateStreamWriter(XMLStreamWriter eventWriter) {
98  		if (myPrettyPrint) {
99  			PrettyPrintWriterWrapper retVal = new PrettyPrintWriterWrapper(eventWriter);
100 			return retVal;
101 		}
102 		NonPrettyPrintWriterWrapper retVal = new NonPrettyPrintWriterWrapper(eventWriter);
103 		return retVal;
104 	}
105 
106 	@Override
107 	public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws DataFormatException {
108 		XMLStreamWriter eventWriter;
109 		try {
110 			eventWriter = createXmlWriter(theWriter);
111 
112 			encodeResourceToXmlStreamWriter(theResource, eventWriter, false, theEncodeContext);
113 			eventWriter.flush();
114 		} catch (XMLStreamException e) {
115 			throw new ConfigurationException("Failed to initialize STaX event factory", e);
116 		}
117 	}
118 
119 	@Override
120 	public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) {
121 		XMLEventReader streamReader = createStreamReader(theReader);
122 		return parseResource(theResourceType, streamReader);
123 	}
124 
125 	private <T> T doXmlLoop(XMLEventReader streamReader, ParserState<T> parserState) {
126 		ourLog.trace("Entering XML parsing loop with state: {}", parserState);
127 
128 		try {
129 			List<String> heldComments = new ArrayList<>(1);
130 
131 			while (streamReader.hasNext()) {
132 				XMLEvent nextEvent = streamReader.nextEvent();
133 				try {
134 
135 					switch (nextEvent.getEventType()) {
136 						case XMLStreamConstants.START_ELEMENT: {
137 							StartElement elem = nextEvent.asStartElement();
138 
139 							String namespaceURI = elem.getName().getNamespaceURI();
140 
141 							if ("extension".equals(elem.getName().getLocalPart())) {
142 								Attribute urlAttr = elem.getAttributeByName(new QName("url"));
143 								String url;
144 								if (urlAttr == null || isBlank(urlAttr.getValue())) {
145 									getErrorHandler().missingRequiredElement(new ParseLocation().setParentElementName("extension"), "url");
146 									url = null;
147 								} else {
148 									url = urlAttr.getValue();
149 								}
150 								parserState.enteringNewElementExtension(elem, url, false, getServerBaseUrl());
151 							} else if ("modifierExtension".equals(elem.getName().getLocalPart())) {
152 								Attribute urlAttr = elem.getAttributeByName(new QName("url"));
153 								String url;
154 								if (urlAttr == null || isBlank(urlAttr.getValue())) {
155 									getErrorHandler().missingRequiredElement(new ParseLocation().setParentElementName("modifierExtension"), "url");
156 									url = null;
157 								} else {
158 									url = urlAttr.getValue();
159 								}
160 								parserState.enteringNewElementExtension(elem, url, true, getServerBaseUrl());
161 							} else {
162 								String elementName = elem.getName().getLocalPart();
163 								parserState.enteringNewElement(namespaceURI, elementName);
164 							}
165 
166 							if (!heldComments.isEmpty()) {
167 								for (String next : heldComments) {
168 									parserState.commentPre(next);
169 								}
170 								heldComments.clear();
171 							}
172 
173 							for (Iterator<Attribute> attributes = elem.getAttributes(); attributes.hasNext(); ) {
174 								Attribute next = attributes.next();
175 								parserState.attributeValue(next.getName().getLocalPart(), next.getValue());
176 							}
177 
178 							break;
179 						}
180 						case XMLStreamConstants.END_DOCUMENT:
181 						case XMLStreamConstants.END_ELEMENT: {
182 							if (!heldComments.isEmpty()) {
183 								for (String next : heldComments) {
184 									parserState.commentPost(next);
185 								}
186 								heldComments.clear();
187 							}
188 							parserState.endingElement();
189 //						if (parserState.isComplete()) {
190 //							return parserState.getObject();
191 //						}
192 							break;
193 						}
194 						case XMLStreamConstants.CHARACTERS: {
195 							parserState.string(nextEvent.asCharacters().getData());
196 							break;
197 						}
198 						case XMLStreamConstants.COMMENT: {
199 							Comment comment = (Comment) nextEvent;
200 							String commentText = comment.getText();
201 							heldComments.add(commentText);
202 							break;
203 						}
204 					}
205 
206 					parserState.xmlEvent(nextEvent);
207 
208 				} catch (DataFormatException e) {
209 					throw new DataFormatException("DataFormatException at [" + nextEvent.getLocation().toString() + "]: " + e.getMessage(), e);
210 				}
211 			}
212 			return parserState.getObject();
213 		} catch (XMLStreamException e) {
214 			throw new DataFormatException(e);
215 		}
216 	}
217 
218 	private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase theElement, String childName, BaseRuntimeElementDefinition<?> childDef,
219 																 String theExtensionUrl, boolean theIncludedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws XMLStreamException, DataFormatException {
220 		theEncodeContext.pushPath(childName, false);
221 		try {
222 
223 			if (theElement == null || theElement.isEmpty()) {
224 				if (isChildContained(childDef, theIncludedResource)) {
225 					// We still want to go in..
226 				} else {
227 					return;
228 				}
229 			}
230 
231 			writeCommentsPre(theEventWriter, theElement);
232 
233 			switch (childDef.getChildType()) {
234 				case ID_DATATYPE: {
235 					IIdType value = IIdType.class.cast(theElement);
236 					String encodedValue = "id".equals(childName) ? value.getIdPart() : value.getValue();
237 					if (StringUtils.isNotBlank(encodedValue) || !super.hasNoExtensions(value)) {
238 						theEventWriter.writeStartElement(childName);
239 						if (StringUtils.isNotBlank(encodedValue)) {
240 							theEventWriter.writeAttribute("value", encodedValue);
241 						}
242 						encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theEncodeContext);
243 						theEventWriter.writeEndElement();
244 					}
245 					break;
246 				}
247 				case PRIMITIVE_DATATYPE: {
248 					IPrimitiveType<?> pd = IPrimitiveType.class.cast(theElement);
249 					String value = pd.getValueAsString();
250 					if (value != null || !super.hasNoExtensions(pd)) {
251 						theEventWriter.writeStartElement(childName);
252 						String elementId = getCompositeElementId(theElement);
253 						if (isNotBlank(elementId)) {
254 							theEventWriter.writeAttribute("id", elementId);
255 						}
256 						if (value != null) {
257 							theEventWriter.writeAttribute("value", value);
258 						}
259 						encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theEncodeContext);
260 						theEventWriter.writeEndElement();
261 					}
262 					break;
263 				}
264 				case RESOURCE_BLOCK:
265 				case COMPOSITE_DATATYPE: {
266 					theEventWriter.writeStartElement(childName);
267 					String elementId = getCompositeElementId(theElement);
268 					if (isNotBlank(elementId)) {
269 						theEventWriter.writeAttribute("id", elementId);
270 					}
271 					if (isNotBlank(theExtensionUrl)) {
272 						theEventWriter.writeAttribute("url", theExtensionUrl);
273 					}
274 					encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theParent, theEncodeContext);
275 					theEventWriter.writeEndElement();
276 					break;
277 				}
278 				case CONTAINED_RESOURCE_LIST:
279 				case CONTAINED_RESOURCES: {
280 					/*
281 					 * Disable per #103 for (IResource next : value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; }
282 					 * theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue()));
283 					 * theEventWriter.writeEndElement(); }
284 					 */
285 					for (IBaseResource next : getContainedResources().getContainedResources()) {
286 						IIdType resourceId = getContainedResources().getResourceId(next);
287 						theEventWriter.writeStartElement("contained");
288 						encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()), theEncodeContext);
289 						theEventWriter.writeEndElement();
290 					}
291 					break;
292 				}
293 				case RESOURCE: {
294 					theEventWriter.writeStartElement(childName);
295 					IBaseResource resource = (IBaseResource) theElement;
296 					String resourceName = myContext.getResourceDefinition(resource).getName();
297 					theEncodeContext.pushPath(resourceName, true);
298 					encodeResourceToXmlStreamWriter(resource, theEventWriter, false, theEncodeContext);
299 					theEncodeContext.popPath();
300 					theEventWriter.writeEndElement();
301 					break;
302 				}
303 				case PRIMITIVE_XHTML: {
304 					XhtmlDt dt = XhtmlDt.class.cast(theElement);
305 					if (dt.hasContent()) {
306 						encodeXhtml(dt, theEventWriter);
307 					}
308 					break;
309 				}
310 				case PRIMITIVE_XHTML_HL7ORG: {
311 					IBaseXhtml dt = IBaseXhtml.class.cast(theElement);
312 					if (!dt.isEmpty()) {
313 						// TODO: this is probably not as efficient as it could be
314 						XhtmlDt hdt = new XhtmlDt();
315 						hdt.setValueAsString(dt.getValueAsString());
316 						encodeXhtml(hdt, theEventWriter);
317 					}
318 					break;
319 				}
320 				case EXTENSION_DECLARED:
321 				case UNDECL_EXT: {
322 					throw new IllegalStateException("state should not happen: " + childDef.getName());
323 				}
324 			}
325 
326 			writeCommentsPost(theEventWriter, theElement);
327 
328 		} finally {
329 			theEncodeContext.popPath();
330 		}
331 
332 	}
333 
334 	private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext)
335 		throws XMLStreamException, DataFormatException {
336 
337 		for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext)) {
338 
339 			BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
340 
341 			if (nextChild.getElementName().equals("url") && theElement instanceof IBaseExtension) {
342 				/*
343 				 * XML encoding is a one-off for extensions. The URL element goes in an attribute
344 				 * instead of being encoded as a normal element, only for XML encoding
345 				 */
346 				continue;
347 			}
348 
349 			if (nextChild instanceof RuntimeChildNarrativeDefinition) {
350 				INarrativeGenerator gen = myContext.getNarrativeGenerator();
351 				INarrative narr;
352 				if (theResource instanceof IResource) {
353 					narr = ((IResource) theResource).getText();
354 				} else if (theResource instanceof IDomainResource) {
355 					narr = ((IDomainResource) theResource).getText();
356 				} else {
357 					narr = null;
358 				}
359 				// FIXME potential null access on narr see line 623
360 				if (gen != null && narr.isEmpty()) {
361 					gen.generateNarrative(myContext, theResource, narr);
362 				}
363 				if (narr != null && narr.isEmpty() == false) {
364 					RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
365 					String childName = nextChild.getChildNameByDatatype(child.getDatatype());
366 					BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
367 					encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, nextChildElem, theEncodeContext);
368 					continue;
369 				}
370 			}
371 
372 			if (nextChild instanceof RuntimeChildContainedResources) {
373 				encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem, theEncodeContext);
374 			} else {
375 
376 				List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
377 				values = super.preProcessValues(nextChild, theResource, values, nextChildElem, theEncodeContext);
378 
379 				if (values == null || values.isEmpty()) {
380 					continue;
381 				}
382 				for (IBase nextValue : values) {
383 					if ((nextValue == null || nextValue.isEmpty())) {
384 						continue;
385 					}
386 
387 					BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue);
388 					if (childNameAndDef == null) {
389 						continue;
390 					}
391 
392 					String childName = childNameAndDef.getChildName();
393 					BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
394 					String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl());
395 
396 					if (extensionUrl != null && childName.equals("extension") == false) {
397 						encodeExtension(theResource, theEventWriter, theContainedResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef, theEncodeContext);
398 					} else if (nextChild instanceof RuntimeChildExtension) {
399 						IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
400 						if ((extension.getValue() == null || extension.getValue().isEmpty())) {
401 							if (extension.getExtension().isEmpty()) {
402 								continue;
403 							}
404 						}
405 						encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, nextChildElem, theEncodeContext);
406 					} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
407 						// suppress narratives from contained resources
408 					} else {
409 						encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem, theEncodeContext);
410 					}
411 
412 				}
413 			}
414 		}
415 	}
416 
417 	private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition<?> childDef, EncodeContext theEncodeContext)
418 		throws XMLStreamException {
419 		BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
420 		if (extDef.isModifier()) {
421 			theEventWriter.writeStartElement("modifierExtension");
422 		} else {
423 			theEventWriter.writeStartElement("extension");
424 		}
425 
426 		String elementId = getCompositeElementId(nextValue);
427 		if (isNotBlank(elementId)) {
428 			theEventWriter.writeAttribute("id", elementId);
429 		}
430 
431 		theEventWriter.writeAttribute("url", extensionUrl);
432 		encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, nextChildElem, theEncodeContext);
433 		theEventWriter.writeEndElement();
434 	}
435 
436 	private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource, EncodeContext theEncodeContext) throws XMLStreamException, DataFormatException {
437 		if (theElement instanceof ISupportsUndeclaredExtensions) {
438 			ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
439 			encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource, theEncodeContext);
440 			encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource, theEncodeContext);
441 		}
442 		if (theElement instanceof IBaseHasExtensions) {
443 			IBaseHasExtensions res = (IBaseHasExtensions) theElement;
444 			encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource, theEncodeContext);
445 		}
446 		if (theElement instanceof IBaseHasModifierExtensions) {
447 			IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
448 			encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource, theEncodeContext);
449 		}
450 	}
451 
452 	private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource, EncodeContext theEncodeContext) throws XMLStreamException, DataFormatException {
453 		IIdType resourceId = null;
454 
455 		if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
456 			resourceId = theResource.getIdElement();
457 			if (theResource.getIdElement().getValue().startsWith("urn:")) {
458 				resourceId = null;
459 			}
460 		}
461 
462 		if (!theIncludedResource) {
463 			if (super.shouldEncodeResourceId(theResource, theEncodeContext) == false) {
464 				resourceId = null;
465 			} else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) {
466 				resourceId = getEncodeForceResourceId();
467 			}
468 		}
469 
470 		encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId, theEncodeContext);
471 	}
472 
473 	private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, IIdType theResourceId, EncodeContext theEncodeContext) throws XMLStreamException {
474 		if (!theContainedResource) {
475 			super.containResourcesForEncoding(theResource);
476 		}
477 
478 		RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
479 		if (resDef == null) {
480 			throw new ConfigurationException("Unknown resource type: " + theResource.getClass());
481 		}
482 
483 		theEventWriter.writeStartElement(resDef.getName());
484 		theEventWriter.writeDefaultNamespace(FHIR_NS);
485 
486 		if (theResource instanceof IAnyResource) {
487 			// HL7.org Structures
488 			if (theResourceId != null) {
489 				writeCommentsPre(theEventWriter, theResourceId);
490 				theEventWriter.writeStartElement("id");
491 				theEventWriter.writeAttribute("value", theResourceId.getIdPart());
492 				encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext);
493 				theEventWriter.writeEndElement();
494 				writeCommentsPost(theEventWriter, theResourceId);
495 			}
496 
497 			encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
498 
499 		} else {
500 
501 			// DSTU2+
502 
503 			IResource resource = (IResource) theResource;
504 			if (theResourceId != null) {
505           /*	writeCommentsPre(theEventWriter, theResourceId);
506               writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart());
507 					    writeCommentsPost(theEventWriter, theResourceId);*/
508 				theEventWriter.writeStartElement("id");
509 				theEventWriter.writeAttribute("value", theResourceId.getIdPart());
510 				encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext);
511 				theEventWriter.writeEndElement();
512 				writeCommentsPost(theEventWriter, theResourceId);
513 			}
514 
515 			InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
516 			IdDt resourceId = resource.getId();
517 			String versionIdPart = resourceId.getVersionIdPart();
518 			if (isBlank(versionIdPart)) {
519 				versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
520 			}
521 			List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
522 			List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
523 			profiles = super.getProfileTagsForEncoding(resource, profiles);
524 
525 			TagList tags = getMetaTagsForEncoding((resource), theEncodeContext);
526 
527 			if (super.shouldEncodeResourceMeta(resource) && ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) {
528 				theEventWriter.writeStartElement("meta");
529 				if (shouldEncodePath(resource, "meta.versionId")) {
530 					writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart);
531 				}
532 				if (updated != null) {
533 					if (shouldEncodePath(resource, "meta.lastUpdated")) {
534 						writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString());
535 					}
536 				}
537 
538 				for (IIdType profile : profiles) {
539 					theEventWriter.writeStartElement("profile");
540 					theEventWriter.writeAttribute("value", profile.getValue());
541 					theEventWriter.writeEndElement();
542 				}
543 				for (BaseCodingDt securityLabel : securityLabels) {
544 					theEventWriter.writeStartElement("security");
545 					encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext);
546 					theEventWriter.writeEndElement();
547 				}
548 				if (tags != null) {
549 					for (Tag tag : tags) {
550 						if (tag.isEmpty()) {
551 							continue;
552 						}
553 						theEventWriter.writeStartElement("tag");
554 						writeOptionalTagWithValue(theEventWriter, "system", tag.getScheme());
555 						writeOptionalTagWithValue(theEventWriter, "code", tag.getTerm());
556 						writeOptionalTagWithValue(theEventWriter, "display", tag.getLabel());
557 						theEventWriter.writeEndElement();
558 					}
559 				}
560 				theEventWriter.writeEndElement();
561 			}
562 
563 			if (theResource instanceof IBaseBinary) {
564 				IBaseBinary bin = (IBaseBinary) theResource;
565 				writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
566 				writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
567 			} else {
568 				encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
569 			}
570 
571 		}
572 
573 		theEventWriter.writeEndElement();
574 	}
575 
576 	private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List<? extends IBaseExtension<?, ?>> theExtensions, String tagName, boolean theIncludedResource, EncodeContext theEncodeContext)
577 		throws XMLStreamException, DataFormatException {
578 		for (IBaseExtension<?, ?> next : theExtensions) {
579 			if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
580 				continue;
581 			}
582 
583 			writeCommentsPre(theEventWriter, next);
584 
585 			theEventWriter.writeStartElement(tagName);
586 
587 			String elementId = getCompositeElementId(next);
588 			if (isNotBlank(elementId)) {
589 				theEventWriter.writeAttribute("id", elementId);
590 			}
591 
592 			String url = getExtensionUrl(next.getUrl());
593 			theEventWriter.writeAttribute("url", url);
594 
595 			if (next.getValue() != null) {
596 				IBaseDatatype value = next.getValue();
597 				RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
598 				String childName = extDef.getChildNameByDatatype(value.getClass());
599 				BaseRuntimeElementDefinition<?> childDef;
600 				if (childName == null) {
601 					childDef = myContext.getElementDefinition(value.getClass());
602 					if (childDef == null) {
603 						throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
604 					}
605 					childName = RuntimeChildUndeclaredExtensionDefinition.createExtensionChildName(childDef);
606 				} else {
607 					childDef = extDef.getChildElementDefinitionByDatatype(value.getClass());
608 					if (childDef == null) {
609 						throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
610 					}
611 				}
612 				encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, null, theEncodeContext);
613 			}
614 
615 			// child extensions
616 			encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource, theEncodeContext);
617 
618 			theEventWriter.writeEndElement();
619 
620 			writeCommentsPost(theEventWriter, next);
621 
622 		}
623 	}
624 
625 
626 	private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws XMLStreamException {
627 		if (theDt == null || theDt.getValue() == null) {
628 			return;
629 		}
630 
631 		List<XMLEvent> events = XmlUtil.parse(theDt.getValue());
632 		boolean firstElement = true;
633 
634 		for (XMLEvent event : events) {
635 			switch (event.getEventType()) {
636 				case XMLStreamConstants.ATTRIBUTE:
637 					Attribute attr = (Attribute) event;
638 					if (isBlank(attr.getName().getPrefix())) {
639 						if (isBlank(attr.getName().getNamespaceURI())) {
640 							theEventWriter.writeAttribute(attr.getName().getLocalPart(), attr.getValue());
641 						} else {
642 							theEventWriter.writeAttribute(attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue());
643 						}
644 					} else {
645 						theEventWriter.writeAttribute(attr.getName().getPrefix(), attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue());
646 					}
647 
648 					break;
649 				case XMLStreamConstants.CDATA:
650 					theEventWriter.writeCData(((Characters) event).getData());
651 					break;
652 				case XMLStreamConstants.CHARACTERS:
653 				case XMLStreamConstants.SPACE:
654 					String data = ((Characters) event).getData();
655 					theEventWriter.writeCharacters(data);
656 					break;
657 				case XMLStreamConstants.COMMENT:
658 					theEventWriter.writeComment(((Comment) event).getText());
659 					break;
660 				case XMLStreamConstants.END_ELEMENT:
661 					theEventWriter.writeEndElement();
662 					break;
663 				case XMLStreamConstants.ENTITY_REFERENCE:
664 					EntityReference er = (EntityReference) event;
665 					theEventWriter.writeEntityRef(er.getName());
666 					break;
667 				case XMLStreamConstants.NAMESPACE:
668 					Namespace ns = (Namespace) event;
669 					theEventWriter.writeNamespace(ns.getPrefix(), ns.getNamespaceURI());
670 					break;
671 				case XMLStreamConstants.START_ELEMENT:
672 					StartElement se = event.asStartElement();
673 					if (firstElement) {
674 						if (StringUtils.isBlank(se.getName().getPrefix())) {
675 							String namespaceURI = se.getName().getNamespaceURI();
676 							if (StringUtils.isBlank(namespaceURI)) {
677 								namespaceURI = "http://www.w3.org/1999/xhtml";
678 							}
679 							theEventWriter.writeStartElement(se.getName().getLocalPart());
680 							theEventWriter.writeDefaultNamespace(namespaceURI);
681 						} else {
682 							String prefix = se.getName().getPrefix();
683 							String namespaceURI = se.getName().getNamespaceURI();
684 							theEventWriter.writeStartElement(prefix, se.getName().getLocalPart(), namespaceURI);
685 							theEventWriter.writeNamespace(prefix, namespaceURI);
686 						}
687 						firstElement = false;
688 					} else {
689 						if (isBlank(se.getName().getPrefix())) {
690 							if (isBlank(se.getName().getNamespaceURI())) {
691 								theEventWriter.writeStartElement(se.getName().getLocalPart());
692 							} else {
693 								if (StringUtils.isBlank(se.getName().getPrefix())) {
694 									theEventWriter.writeStartElement(se.getName().getLocalPart());
695 									// theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI());
696 								} else {
697 									theEventWriter.writeStartElement(se.getName().getNamespaceURI(), se.getName().getLocalPart());
698 								}
699 							}
700 						} else {
701 							theEventWriter.writeStartElement(se.getName().getPrefix(), se.getName().getLocalPart(), se.getName().getNamespaceURI());
702 						}
703 						for (Iterator<?> attrIter = se.getAttributes(); attrIter.hasNext(); ) {
704 							Attribute next = (Attribute) attrIter.next();
705 							theEventWriter.writeAttribute(next.getName().getLocalPart(), next.getValue());
706 						}
707 					}
708 					break;
709 				case XMLStreamConstants.DTD:
710 				case XMLStreamConstants.END_DOCUMENT:
711 				case XMLStreamConstants.ENTITY_DECLARATION:
712 				case XMLStreamConstants.NOTATION_DECLARATION:
713 				case XMLStreamConstants.PROCESSING_INSTRUCTION:
714 				case XMLStreamConstants.START_DOCUMENT:
715 					break;
716 			}
717 
718 		}
719 	}
720 
721 	@Override
722 	public EncodingEnum getEncoding() {
723 		return EncodingEnum.XML;
724 	}
725 
726 	private <T extends IBaseResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) {
727 		ParserState<T> parserState = ParserState.getPreResourceInstance(this, theResourceType, myContext, false, getErrorHandler());
728 		return doXmlLoop(theStreamReader, parserState);
729 	}
730 
731 	@Override
732 	public IParser setPrettyPrint(boolean thePrettyPrint) {
733 		myPrettyPrint = thePrettyPrint;
734 		return this;
735 	}
736 
737 	/**
738 	 * This is just to work around the fact that casting java.util.List<ca.uhn.fhir.model.api.ExtensionDt> to
739 	 * java.util.List<? extends org.hl7.fhir.instance.model.api.IBaseExtension<?, ?>> seems to be
740 	 * rejected by the compiler some of the time.
741 	 */
742 	private <Q extends IBaseExtension<?, ?>> List<IBaseExtension<?, ?>> toBaseExtensionList(final List<Q> theList) {
743 		List<IBaseExtension<?, ?>> retVal = new ArrayList<IBaseExtension<?, ?>>(theList.size());
744 		retVal.addAll(theList);
745 		return retVal;
746 	}
747 
748 	private void writeCommentsPost(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
749 		if (theElement != null && theElement.hasFormatComment()) {
750 			for (String next : theElement.getFormatCommentsPost()) {
751 				if (isNotBlank(next)) {
752 					theEventWriter.writeComment(next);
753 				}
754 			}
755 		}
756 	}
757 
758 	private void writeCommentsPre(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
759 		if (theElement != null && theElement.hasFormatComment()) {
760 			for (String next : theElement.getFormatCommentsPre()) {
761 				if (isNotBlank(next)) {
762 					theEventWriter.writeComment(next);
763 				}
764 			}
765 		}
766 	}
767 
768 	private void writeOptionalTagWithValue(XMLStreamWriter theEventWriter, String theName, String theValue) throws XMLStreamException {
769 		if (StringUtils.isNotBlank(theValue)) {
770 			theEventWriter.writeStartElement(theName);
771 			theEventWriter.writeAttribute("value", theValue);
772 			theEventWriter.writeEndElement();
773 		}
774 	}
775 
776 }