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