package ca.uhn.fhir.parser;

/*
 * #%L
 * HAPI FHIR - Core Library
 * %%
 * Copyright (C) 2014 - 2017 University Health Network
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.Comment;
import javax.xml.stream.events.EntityReference;
import javax.xml.stream.events.Namespace;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IBaseXhtml;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.instance.model.api.IPrimitiveType;

import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeChildContainedResources;
import ca.uhn.fhir.context.RuntimeChildExtension;
import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.BaseBundle;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.XhtmlDt;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.ElementUtil;
import ca.uhn.fhir.util.NonPrettyPrintWriterWrapper;
import ca.uhn.fhir.util.PrettyPrintWriterWrapper;
import ca.uhn.fhir.util.XmlUtil;

/**
 * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use
 * {@link FhirContext#newXmlParser()} to get an instance.
 */
public class XmlParser extends BaseParser /*implements IParser */{

	static final String ATOM_NS = "http://www.w3.org/2005/Atom";
	static final String FHIR_NS = "http://hl7.org/fhir";
	static final String OPENSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1/";
	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class);
	static final String RESREF_DISPLAY = "display";
	static final String RESREF_REFERENCE = "reference";
	static final String TOMBSTONES_NS = "http://purl.org/atompub/tombstones/1.0";
	static final String XHTML_NS = "http://www.w3.org/1999/xhtml";

	// private static final Set<String> RESOURCE_NAMESPACES;

	private FhirContext myContext;
	private boolean myPrettyPrint;

	/**
	 * Do not use this constructor, the recommended way to obtain a new instance of the XML parser is to invoke
	 * {@link FhirContext#newXmlParser()}.
	 * 
	 * @param theParserErrorHandler
	 */
	public XmlParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
		super(theContext, theParserErrorHandler);
		myContext = theContext;
	}

	private XMLEventReader createStreamReader(Reader theReader) {
		try {
			return XmlUtil.createXmlReader(theReader);
		} catch (FactoryConfigurationError e1) {
			throw new ConfigurationException("Failed to initialize STaX event factory", e1);
		} catch (XMLStreamException e1) {
			throw new DataFormatException(e1);
		}
	}

	private XMLStreamWriter createXmlWriter(Writer theWriter) throws XMLStreamException {
		XMLStreamWriter eventWriter;
		eventWriter = XmlUtil.createXmlStreamWriter(theWriter);
		eventWriter = decorateStreamWriter(eventWriter);
		return eventWriter;
	}

	private XMLStreamWriter decorateStreamWriter(XMLStreamWriter eventWriter) {
		if (myPrettyPrint) {
			PrettyPrintWriterWrapper retVal = new PrettyPrintWriterWrapper(eventWriter);
			return retVal;
		}
		NonPrettyPrintWriterWrapper retVal = new NonPrettyPrintWriterWrapper(eventWriter);
		return retVal;
	}

	@Override
	public void doEncodeBundleToWriter(Bundle theBundle, Writer theWriter) throws DataFormatException {
		try {
			XMLStreamWriter eventWriter = createXmlWriter(theWriter);
			if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
				encodeBundleToWriterDstu2(theBundle, eventWriter);
			} else {
				encodeBundleToWriterDstu1(theBundle, eventWriter);
			}
		} catch (XMLStreamException e) {
			throw new ConfigurationException("Failed to initialize STaX event factory", e);
		}
	}

	@Override
	public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws DataFormatException {
		XMLStreamWriter eventWriter;
		try {
			eventWriter = createXmlWriter(theWriter);

			encodeResourceToXmlStreamWriter(theResource, eventWriter, false, false);
			eventWriter.flush();
		} catch (XMLStreamException e) {
			throw new ConfigurationException("Failed to initialize STaX event factory", e);
		}
	}

	@Override
	public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) {
		XMLEventReader streamReader = createStreamReader(theReader);
		return parseResource(theResourceType, streamReader);
	}

	private <T> T doXmlLoop(XMLEventReader streamReader, ParserState<T> parserState) {
		ourLog.trace("Entering XML parsing loop with state: {}", parserState);

		try {
			List<String> heldComments = new ArrayList<String>(1);

			while (streamReader.hasNext()) {
				XMLEvent nextEvent = streamReader.nextEvent();
				try {

					switch (nextEvent.getEventType()) {
					case XMLStreamConstants.START_ELEMENT: {
						StartElement elem = nextEvent.asStartElement();

						String namespaceURI = elem.getName().getNamespaceURI();

						if ("extension".equals(elem.getName().getLocalPart())) {
							Attribute urlAttr = elem.getAttributeByName(new QName("url"));
							String url;
							if (urlAttr == null || isBlank(urlAttr.getValue())) {
								getErrorHandler().missingRequiredElement(new ParseLocation("extension"), "url");
								url = null;
							} else {
								url = urlAttr.getValue();
							}
							parserState.enteringNewElementExtension(elem, url, false);
						} else if ("modifierExtension".equals(elem.getName().getLocalPart())) {
							Attribute urlAttr = elem.getAttributeByName(new QName("url"));
							String url;
							if (urlAttr == null || isBlank(urlAttr.getValue())) {
								getErrorHandler().missingRequiredElement(new ParseLocation("modifierExtension"), "url");
								url = null;
							} else {
								url = urlAttr.getValue();
							}
							parserState.enteringNewElementExtension(elem, url, true);
						} else {
							String elementName = elem.getName().getLocalPart();
							parserState.enteringNewElement(namespaceURI, elementName);
						}

						if (!heldComments.isEmpty()) {
							for (String next : heldComments) {
								parserState.commentPre(next);
							}
							heldComments.clear();
						}

						@SuppressWarnings("unchecked")
						Iterator<Attribute> attributes = elem.getAttributes();
						for (Iterator<Attribute> iter = attributes; iter.hasNext();) {
							Attribute next = iter.next();
							parserState.attributeValue(next.getName().getLocalPart(), next.getValue());
						}

						break;
					}
					case XMLStreamConstants.END_DOCUMENT:
					case XMLStreamConstants.END_ELEMENT: {
						if (!heldComments.isEmpty()) {
							for (String next : heldComments) {
								parserState.commentPost(next);
							}
							heldComments.clear();
						}
						parserState.endingElement();
//						if (parserState.isComplete()) {
//							return parserState.getObject();
//						}
						break;
					}
					case XMLStreamConstants.CHARACTERS: {
						parserState.string(nextEvent.asCharacters().getData());
						break;
					}
					case XMLStreamConstants.COMMENT: {
						Comment comment = (Comment) nextEvent;
						String commentText = comment.getText();
						heldComments.add(commentText);
						break;
					}
					}

					parserState.xmlEvent(nextEvent);

				} catch (DataFormatException e) {
					throw new DataFormatException("DataFormatException at [" + nextEvent.getLocation().toString() + "]: " + e.getMessage(), e);
				}
			}
			return parserState.getObject();
		} catch (XMLStreamException e) {
			throw new DataFormatException(e);
		}
	}

	@Override
	public String encodeBundleToString(Bundle theBundle) throws DataFormatException {
		StringWriter stringWriter = new StringWriter();
		try {
			encodeBundleToWriter(theBundle, stringWriter);
		} catch (IOException e) {
			throw new InternalErrorException("IOException writing to StringWriter - Should not happen", e);
		}

		return stringWriter.toString();
	}

	private void encodeBundleToWriterDstu1(Bundle theBundle, XMLStreamWriter eventWriter) throws XMLStreamException {
		eventWriter.writeStartElement("feed");
		eventWriter.writeDefaultNamespace(ATOM_NS);

		writeTagWithTextNode(eventWriter, "title", theBundle.getTitle());
		writeTagWithTextNode(eventWriter, "id", theBundle.getBundleId());

		writeAtomLink(eventWriter, Constants.LINK_SELF, theBundle.getLinkSelf());
		writeAtomLink(eventWriter, Constants.LINK_FIRST, theBundle.getLinkFirst());
		writeAtomLink(eventWriter, Constants.LINK_PREVIOUS, theBundle.getLinkPrevious());
		writeAtomLink(eventWriter, Constants.LINK_NEXT, theBundle.getLinkNext());
		writeAtomLink(eventWriter, Constants.LINK_LAST, theBundle.getLinkLast());
		writeAtomLink(eventWriter, Constants.LINK_FHIR_BASE, theBundle.getLinkBase());

		if (theBundle.getTotalResults().getValue() != null) {
			eventWriter.writeStartElement("os", "totalResults", OPENSEARCH_NS);
			eventWriter.writeNamespace("os", OPENSEARCH_NS);
			eventWriter.writeCharacters(theBundle.getTotalResults().getValue().toString());
			eventWriter.writeEndElement();
		}

		writeOptionalTagWithTextNode(eventWriter, "updated", theBundle.getUpdated());

		writeAuthor(eventWriter, theBundle);

		writeCategories(eventWriter, theBundle.getCategories());

		for (BundleEntry nextEntry : theBundle.getEntries()) {
			boolean deleted = false;
			if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false) {
				deleted = true;
				eventWriter.writeStartElement("at", "deleted-entry", TOMBSTONES_NS);
				eventWriter.writeNamespace("at", TOMBSTONES_NS);

				if (nextEntry.getDeletedResourceId().isEmpty()) {
					//TODO: Use of a deprecated method should be resolved.
					writeOptionalAttribute(eventWriter, "ref", nextEntry.getId().getValueAsString());
				} else {
					writeOptionalAttribute(eventWriter, "ref", nextEntry.getDeletedResourceId().getValueAsString());
				}

				writeOptionalAttribute(eventWriter, "when", nextEntry.getDeletedAt().getValueAsString());
				if (nextEntry.getDeletedByEmail().isEmpty() == false || nextEntry.getDeletedByName().isEmpty() == false) {
					eventWriter.writeStartElement(TOMBSTONES_NS, "by");
					if (nextEntry.getDeletedByName().isEmpty() == false) {
						eventWriter.writeStartElement(TOMBSTONES_NS, "name");
						eventWriter.writeCharacters(nextEntry.getDeletedByName().getValue());
						eventWriter.writeEndElement();
					}
					if (nextEntry.getDeletedByEmail().isEmpty() == false) {
						eventWriter.writeStartElement(TOMBSTONES_NS, "email");
						eventWriter.writeCharacters(nextEntry.getDeletedByEmail().getValue());
						eventWriter.writeEndElement();
					}
					eventWriter.writeEndElement();
				}
				if (nextEntry.getDeletedComment().isEmpty() == false) {
					eventWriter.writeStartElement(TOMBSTONES_NS, "comment");
					eventWriter.writeCharacters(nextEntry.getDeletedComment().getValue());
					eventWriter.writeEndElement();
				}
			} else {
				eventWriter.writeStartElement("entry");
			}

			writeOptionalTagWithTextNode(eventWriter, "title", nextEntry.getTitle());
			if (!deleted) {
				//TODO: Use of a deprecated method should be resolved.
				if (nextEntry.getId().isEmpty() == false) {
					//TODO: Use of a deprecated method should be resolved.
					writeTagWithTextNode(eventWriter, "id", nextEntry.getId());
				} else {
					writeTagWithTextNode(eventWriter, "id", nextEntry.getResource().getId());
				}
			}
			//TODO: Use of a deprecated method should be resolved.
			writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated());
			writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished());

			writeAuthor(eventWriter, nextEntry);

			writeCategories(eventWriter, nextEntry.getCategories());

			if (!nextEntry.getLinkSelf().isEmpty()) {
				writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf());
			}

			if (!nextEntry.getLinkAlternate().isEmpty()) {
				writeAtomLink(eventWriter, "alternate", nextEntry.getLinkAlternate());
			}

			if (!nextEntry.getLinkSearch().isEmpty()) {
				writeAtomLink(eventWriter, "search", nextEntry.getLinkSearch());
			}

			IResource resource = nextEntry.getResource();
			if (resource != null && !resource.isEmpty() && !deleted) {
				eventWriter.writeStartElement("content");
				eventWriter.writeAttribute("type", "text/xml");
				encodeResourceToXmlStreamWriter(resource, eventWriter, false, true);
				eventWriter.writeEndElement(); // content
			} else {
				ourLog.debug("Bundle entry contains null resource");
			}

			if (!nextEntry.getSummary().isEmpty()) {
				eventWriter.writeStartElement("summary");
				eventWriter.writeAttribute("type", "xhtml");
				encodeXhtml(nextEntry.getSummary(), eventWriter);
				eventWriter.writeEndElement();
			}

			eventWriter.writeEndElement(); // entry
		}

		eventWriter.writeEndElement();
		eventWriter.close();
	}

	private void encodeBundleToWriterDstu2(Bundle theBundle, XMLStreamWriter theEventWriter) throws XMLStreamException {
		theEventWriter.writeStartElement("Bundle");
		theEventWriter.writeDefaultNamespace(FHIR_NS);

		writeOptionalTagWithValue(theEventWriter, "id", theBundle.getId().getIdPart());

		InstantDt updated = (InstantDt) theBundle.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
		IdDt bundleId = theBundle.getId();
		if (bundleId != null && isNotBlank(bundleId.getVersionIdPart()) || (updated != null && !updated.isEmpty())) {
			theEventWriter.writeStartElement("meta");
			//FIXME potential null acces bundleId may be null at this time due to the OR clause
			writeOptionalTagWithValue(theEventWriter, "versionId", bundleId.getVersionIdPart());
			if (updated != null) {
				writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString());
			}
			theEventWriter.writeEndElement();
		}

		writeOptionalTagWithValue(theEventWriter, "type", theBundle.getType().getValue());
		writeOptionalTagWithValue(theEventWriter, "total", theBundle.getTotalResults().getValueAsString());

		writeBundleResourceLink(theEventWriter, "first", theBundle.getLinkFirst());
		writeBundleResourceLink(theEventWriter, "previous", theBundle.getLinkPrevious());
		writeBundleResourceLink(theEventWriter, "next", theBundle.getLinkNext());
		writeBundleResourceLink(theEventWriter, "last", theBundle.getLinkLast());
		writeBundleResourceLink(theEventWriter, "self", theBundle.getLinkSelf());

		for (BundleEntry nextEntry : theBundle.getEntries()) {
			theEventWriter.writeStartElement("entry");

			boolean deleted = false;
			if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false) {
				deleted = true;
			}

			writeBundleResourceLink(theEventWriter, "alternate", nextEntry.getLinkAlternate());

			if (nextEntry.getResource() != null && isNotBlank(nextEntry.getResource().getIdElement().getValue()) && (nextEntry.getResource().getId().getBaseUrl() != null || nextEntry.getResource().getId().getValueAsString().startsWith("urn:"))) {
				writeOptionalTagWithValue(theEventWriter, "fullUrl", nextEntry.getResource().getId().getValue());
			}

			IResource resource = nextEntry.getResource();
			if (resource != null && !resource.isEmpty() && !deleted) {
				theEventWriter.writeStartElement("resource");
				encodeResourceToXmlStreamWriter(resource, theEventWriter, false, true);
				theEventWriter.writeEndElement(); // content
			} else {
				ourLog.debug("Bundle entry contains null resource");
			}

			if (nextEntry.getSearchMode().isEmpty() == false || nextEntry.getScore().isEmpty() == false) {
				theEventWriter.writeStartElement("search");
				writeOptionalTagWithValue(theEventWriter, "mode", nextEntry.getSearchMode().getValueAsString());
				writeOptionalTagWithValue(theEventWriter, "score", nextEntry.getScore().getValueAsString());
				theEventWriter.writeEndElement();
				// IResource nextResource = nextEntry.getResource();
			}

			if (nextEntry.getTransactionMethod().isEmpty() == false || nextEntry.getLinkSearch().isEmpty() == false) {
				theEventWriter.writeStartElement("request");
				writeOptionalTagWithValue(theEventWriter, "method", nextEntry.getTransactionMethod().getValue());
				writeOptionalTagWithValue(theEventWriter, "url", nextEntry.getLinkSearch().getValue());
				theEventWriter.writeEndElement();
			}

			if (deleted) {
				theEventWriter.writeStartElement("deleted");
				//TODO: Use of a deprecated method should be resolved.
				writeOptionalTagWithValue(theEventWriter, "type", nextEntry.getId().getResourceType());
				//TODO: Use of a deprecated method should be resolved.
				writeOptionalTagWithValue(theEventWriter, "id", nextEntry.getId().getIdPart());
				//TODO: Use of a deprecated method should be resolved.
				writeOptionalTagWithValue(theEventWriter, "versionId", nextEntry.getId().getVersionIdPart());
				writeOptionalTagWithValue(theEventWriter, "instant", nextEntry.getDeletedAt().getValueAsString());
				theEventWriter.writeEndElement();
			}

			theEventWriter.writeEndElement(); // entry
		}

		theEventWriter.writeEndElement();
		theEventWriter.close();
	}

	private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase theElement, String childName, BaseRuntimeElementDefinition<?> childDef,
			String theExtensionUrl, boolean theIncludedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException {
		if (theElement == null || theElement.isEmpty()) {
			if (isChildContained(childDef, theIncludedResource)) {
				// We still want to go in..
			} else {
				return;
			}
		}

		writeCommentsPre(theEventWriter, theElement);

		switch (childDef.getChildType()) {
		case ID_DATATYPE: {
			IIdType value = IIdType.class.cast(theElement);
			String encodedValue = "id".equals(childName) ? value.getIdPart() : value.getValue();
			theEventWriter.writeStartElement(childName);
			theEventWriter.writeAttribute("value", encodedValue);
			encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource);
			theEventWriter.writeEndElement();
			break;
		}
		case PRIMITIVE_DATATYPE: {
			IPrimitiveType<?> pd = IPrimitiveType.class.cast(theElement);
			String value = pd.getValueAsString();
			if (value != null || super.hasExtensions(pd)) {
				theEventWriter.writeStartElement(childName);
				String elementId = getCompositeElementId(theElement);
				if (isNotBlank(elementId)) {
					theEventWriter.writeAttribute("id", elementId);
				}
				if (value != null) {
					theEventWriter.writeAttribute("value", value);
				}
				encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource);
				theEventWriter.writeEndElement();
			}
			break;
		}
		case RESOURCE_BLOCK:
		case COMPOSITE_DATATYPE: {
			theEventWriter.writeStartElement(childName);
			String elementId = getCompositeElementId(theElement);
			if (isNotBlank(elementId)) {
				theEventWriter.writeAttribute("id", elementId);
			}
			if (isNotBlank(theExtensionUrl)) {
				theEventWriter.writeAttribute("url", theExtensionUrl);
			}
			encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theParent);
			theEventWriter.writeEndElement();
			break;
		}
		case CONTAINED_RESOURCE_LIST:
		case CONTAINED_RESOURCES: {
			/*
			 * Disable per #103 for (IResource next : value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; }
			 * theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue()));
			 * theEventWriter.writeEndElement(); }
			 */
			for (IBaseResource next : getContainedResources().getContainedResources()) {
				IIdType resourceId = getContainedResources().getResourceId(next);
				theEventWriter.writeStartElement("contained");
				encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()));
				theEventWriter.writeEndElement();
			}
			break;
		}
		case RESOURCE: {
			theEventWriter.writeStartElement(childName);
			IBaseResource resource = (IBaseResource) theElement;
			encodeResourceToXmlStreamWriter(resource, theEventWriter, false, true);
			theEventWriter.writeEndElement();
			break;
		}
		case PRIMITIVE_XHTML: {
			XhtmlDt dt = XhtmlDt.class.cast(theElement);
			if (dt.hasContent()) {
				encodeXhtml(dt, theEventWriter);
			}
			break;
		}
		case PRIMITIVE_XHTML_HL7ORG: {
			IBaseXhtml dt = IBaseXhtml.class.cast(theElement);
			if (!dt.isEmpty()) {
				// TODO: this is probably not as efficient as it could be
				XhtmlDt hdt = new XhtmlDt();
				hdt.setValueAsString(dt.getValueAsString());
				encodeXhtml(hdt, theEventWriter);
			}
			break;
		}
		case EXTENSION_DECLARED:
		case UNDECL_EXT: {
			throw new IllegalStateException("state should not happen: " + childDef.getName());
		}
		}

		writeCommentsPost(theEventWriter, theElement);

	}

	private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent)
			throws XMLStreamException, DataFormatException {
		
		for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent)) {

			BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
			
			if (nextChild.getElementName().equals("url") && theElement instanceof IBaseExtension) {
				/* 
				 * XML encoding is a one-off for extensions. The URL element goes in an attribute
				 * instead of being encoded as a normal element, only for XML encoding
				 */
				continue;
			}

			if (nextChild instanceof RuntimeChildNarrativeDefinition) {
				INarrativeGenerator gen = myContext.getNarrativeGenerator();
				INarrative narr;
				if (theResource instanceof IResource) {
					narr = ((IResource) theResource).getText();
				} else if (theResource instanceof IDomainResource) {
					narr = ((IDomainResource) theResource).getText();
				} else {
					narr = null;
				}
				//FIXME potential null access on narr see line 623
				if (gen != null && narr.isEmpty()) {
					gen.generateNarrative(myContext, theResource, narr);
				}
				if (narr != null && narr.isEmpty() == false) {
					RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
					String childName = nextChild.getChildNameByDatatype(child.getDatatype());
					BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
					encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, nextChildElem);
					continue;
				}
			}

			if (nextChild instanceof RuntimeChildContainedResources) {
				encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem);
			} else {

				List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
				values = super.preProcessValues(nextChild, theResource, values, nextChildElem);

				if (values == null || values.isEmpty()) {
					continue;
				}
				for (IBase nextValue : values) {
					if ((nextValue == null || nextValue.isEmpty())) {
						continue;
					}
					
					BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue);
					if (childNameAndDef == null) {
						continue;
					}
					
					String childName = childNameAndDef.getChildName();
					BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
					String extensionUrl = nextChild.getExtensionUrl();
					
					if (nextValue instanceof IBaseExtension && myContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
						// This is called for the Query resource in DSTU1 only
						extensionUrl = ((IBaseExtension<?, ?>) nextValue).getUrl();
						encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem);

					} else if (extensionUrl != null && childName.equals("extension") == false) {
						encodeExtension(theResource, theEventWriter, theContainedResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef);
					} else if (nextChild instanceof RuntimeChildExtension) {
						IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
						if ((extension.getValue() == null || extension.getValue().isEmpty())) {
							if (extension.getExtension().isEmpty()) {
								continue;
							}
						}
						encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extension.getUrl(), theContainedResource, nextChildElem);
					} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
						// suppress narratives from contained resources
					} else {
						encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem);
					}
				}
			}
		}
	}

	private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition<?> childDef)
			throws XMLStreamException {
		BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
		if (extDef.isModifier()) {
			theEventWriter.writeStartElement("modifierExtension");
		} else {
			theEventWriter.writeStartElement("extension");
		}

		String elementId = getCompositeElementId(nextValue);
		if (isNotBlank(elementId)) {
			theEventWriter.writeAttribute("id", elementId);
		}
		
		theEventWriter.writeAttribute("url", extensionUrl);
		encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, nextChildElem);
		theEventWriter.writeEndElement();
	}

	private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
		if (theElement instanceof ISupportsUndeclaredExtensions) {
			ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
			encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource);
			encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource);
		}
		if (theElement instanceof IBaseHasExtensions) {
			IBaseHasExtensions res = (IBaseHasExtensions) theElement;
			encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource);
		}
		if (theElement instanceof IBaseHasModifierExtensions) {
			IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
			encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource);
		}
	}

	private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource, boolean theSubResource) throws XMLStreamException, DataFormatException {
		IIdType resourceId = null;

		if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
			resourceId = theResource.getIdElement();
			if (theResource.getIdElement().getValue().startsWith("urn:")) {
				resourceId = null;
			}
			if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) {
				resourceId = null;
			}
		}

		if (!theIncludedResource) {
			if (super.shouldEncodeResourceId(theResource) == false) {
				resourceId = null;
			} else if (theSubResource == false && getEncodeForceResourceId() != null) {
				resourceId = getEncodeForceResourceId();
			}
		}

		encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId);
	}


	private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, IIdType theResourceId) throws XMLStreamException {
		if (!theContainedResource) {
			super.containResourcesForEncoding(theResource);
		}

		RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
		if (resDef == null) {
			throw new ConfigurationException("Unknown resource type: " + theResource.getClass());
		}

		theEventWriter.writeStartElement(resDef.getName());
		theEventWriter.writeDefaultNamespace(FHIR_NS);

		if (theResource instanceof IAnyResource) {

			// HL7.org Structures
			if (theResourceId != null) {
				writeCommentsPre(theEventWriter, theResourceId);
				writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart());
				writeCommentsPost(theEventWriter, theResourceId);
			}

			encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));

		} else {

			if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {

				// DSTU2+

				IResource resource = (IResource) theResource;
				if (theResourceId != null) {
					writeCommentsPre(theEventWriter, theResourceId);
					writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart());
					writeCommentsPost(theEventWriter, theResourceId);
				}

				InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
				IdDt resourceId = resource.getId();
				String versionIdPart = resourceId.getVersionIdPart();
				if (isBlank(versionIdPart)) {
					versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
				}
				List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
				List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
				profiles = super.getProfileTagsForEncoding(resource, profiles);
				
				TagList tags = getMetaTagsForEncoding((resource));

				if (super.shouldEncodeResourceMeta(resource) && ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) {
					theEventWriter.writeStartElement("meta");
					writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart);
					if (updated != null) {
						writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString());
					}

					for (IIdType profile : profiles) {
						theEventWriter.writeStartElement("profile");
						theEventWriter.writeAttribute("value", profile.getValue());
						theEventWriter.writeEndElement();
					}
					for (BaseCodingDt securityLabel : securityLabels) {
						theEventWriter.writeStartElement("security");
						encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, null);
						theEventWriter.writeEndElement();
					}
					if (tags != null) {
						for (Tag tag : tags) {
							if (tag.isEmpty()) {
								continue;
							}
							theEventWriter.writeStartElement("tag");
							writeOptionalTagWithValue(theEventWriter, "system", tag.getScheme());
							writeOptionalTagWithValue(theEventWriter, "code", tag.getTerm());
							writeOptionalTagWithValue(theEventWriter, "display", tag.getLabel());
							theEventWriter.writeEndElement();
						}
					}
					theEventWriter.writeEndElement();
				}

				if (theResource instanceof IBaseBinary) {
					IBaseBinary bin = (IBaseBinary) theResource;
					writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
					writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
				} else {
					encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
				}

			} else {

				// DSTU1
				if (theResourceId != null && theContainedResource && theResourceId.hasIdPart()) {
					theEventWriter.writeAttribute("id", theResourceId.getIdPart());
				}

				if (theResource instanceof IBaseBinary) {
					IBaseBinary bin = (IBaseBinary) theResource;
					if (bin.getContentType() != null) {
						theEventWriter.writeAttribute("contentType", bin.getContentType());
					}
					theEventWriter.writeCharacters(bin.getContentAsBase64());
				} else {
					encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
				}

			}

		}

		theEventWriter.writeEndElement();
	}


	@Override
	public void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException {
		try {
			XMLStreamWriter eventWriter = createXmlWriter(theWriter);

			eventWriter.writeStartElement(TagList.ELEMENT_NAME_LC);
			eventWriter.writeDefaultNamespace(FHIR_NS);

			for (Tag next : theTagList) {
				eventWriter.writeStartElement(TagList.ATTR_CATEGORY);

				if (isNotBlank(next.getTerm())) {
					eventWriter.writeAttribute(Tag.ATTR_TERM, next.getTerm());
				}
				if (isNotBlank(next.getLabel())) {
					eventWriter.writeAttribute(Tag.ATTR_LABEL, next.getLabel());
				}
				if (isNotBlank(next.getScheme())) {
					eventWriter.writeAttribute(Tag.ATTR_SCHEME, next.getScheme());
				}

				eventWriter.writeEndElement();
			}

			eventWriter.writeEndElement();
			eventWriter.close();
		} catch (XMLStreamException e) {
			throw new ConfigurationException("Failed to initialize STaX event factory", e);
		}
	}

	private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List<? extends IBaseExtension<?, ?>> theExtensions, String tagName, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
		for (IBaseExtension<?, ?> next : theExtensions) {
			if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
				continue;
			}

			writeCommentsPre(theEventWriter, next);

			theEventWriter.writeStartElement(tagName);

			String elementId = getCompositeElementId(next);
			if (isNotBlank(elementId)) {
				theEventWriter.writeAttribute("id", elementId);
			}

			String url = next.getUrl();
			theEventWriter.writeAttribute("url", url);

			if (next.getValue() != null) {
				IBaseDatatype value = next.getValue();
				RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
				String childName = extDef.getChildNameByDatatype(value.getClass());
				BaseRuntimeElementDefinition<?> childDef;
				if (childName == null) {
					childDef = myContext.getElementDefinition(value.getClass());
					if (childDef == null) {
						throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
					} 
					childName = RuntimeChildUndeclaredExtensionDefinition.createExtensionChildName(childDef);
				} else {
					childDef = extDef.getChildElementDefinitionByDatatype(value.getClass());
					if (childDef == null) {
						throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
					}
				}
				encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, null);
			}

			// child extensions
			encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource);

			theEventWriter.writeEndElement();

			writeCommentsPost(theEventWriter, next);

		}
	}

	private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws XMLStreamException {
		if (theDt == null || theDt.getValue() == null) {
			return;
		}

		boolean firstElement = true;
		for (XMLEvent event : theDt.getValue()) {
			switch (event.getEventType()) {
			case XMLStreamConstants.ATTRIBUTE:
				Attribute attr = (Attribute) event;
				if (isBlank(attr.getName().getPrefix())) {
					if (isBlank(attr.getName().getNamespaceURI())) {
						theEventWriter.writeAttribute(attr.getName().getLocalPart(), attr.getValue());
					} else {
						theEventWriter.writeAttribute(attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue());
					}
				} else {
					theEventWriter.writeAttribute(attr.getName().getPrefix(), attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue());
				}

				break;
			case XMLStreamConstants.CDATA:
				theEventWriter.writeCData(((Characters) event).getData());
				break;
			case XMLStreamConstants.CHARACTERS:
			case XMLStreamConstants.SPACE:
				String data = ((Characters) event).getData();
				theEventWriter.writeCharacters(data);
				break;
			case XMLStreamConstants.COMMENT:
				theEventWriter.writeComment(((Comment) event).getText());
				break;
			case XMLStreamConstants.END_ELEMENT:
				theEventWriter.writeEndElement();
				break;
			case XMLStreamConstants.ENTITY_REFERENCE:
				EntityReference er = (EntityReference) event;
				theEventWriter.writeEntityRef(er.getName());
				break;
			case XMLStreamConstants.NAMESPACE:
				Namespace ns = (Namespace) event;
				theEventWriter.writeNamespace(ns.getPrefix(), ns.getNamespaceURI());
				break;
			case XMLStreamConstants.START_ELEMENT:
				StartElement se = event.asStartElement();
				if (firstElement) {
					if (StringUtils.isBlank(se.getName().getPrefix())) {
						String namespaceURI = se.getName().getNamespaceURI();
						if (StringUtils.isBlank(namespaceURI)) {
							namespaceURI = "http://www.w3.org/1999/xhtml";
						}
						theEventWriter.writeStartElement(se.getName().getLocalPart());
						theEventWriter.writeDefaultNamespace(namespaceURI);
					} else {
						String prefix = se.getName().getPrefix();
						String namespaceURI = se.getName().getNamespaceURI();
						theEventWriter.writeStartElement(prefix, se.getName().getLocalPart(), namespaceURI);
						theEventWriter.writeNamespace(prefix, namespaceURI);
					}
					firstElement = false;
				} else {
					if (isBlank(se.getName().getPrefix())) {
						if (isBlank(se.getName().getNamespaceURI())) {
							theEventWriter.writeStartElement(se.getName().getLocalPart());
						} else {
							if (StringUtils.isBlank(se.getName().getPrefix())) {
								theEventWriter.writeStartElement(se.getName().getLocalPart());
								// theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI());
							} else {
								theEventWriter.writeStartElement(se.getName().getNamespaceURI(), se.getName().getLocalPart());
							}
						}
					} else {
						theEventWriter.writeStartElement(se.getName().getPrefix(), se.getName().getLocalPart(), se.getName().getNamespaceURI());
					}
					for (Iterator<?> attrIter = se.getAttributes(); attrIter.hasNext();) {
						Attribute next = (Attribute) attrIter.next();
						theEventWriter.writeAttribute(next.getName().getLocalPart(), next.getValue());
					}
				}
				break;
			case XMLStreamConstants.DTD:
			case XMLStreamConstants.END_DOCUMENT:
			case XMLStreamConstants.ENTITY_DECLARATION:
			case XMLStreamConstants.NOTATION_DECLARATION:
			case XMLStreamConstants.PROCESSING_INSTRUCTION:
			case XMLStreamConstants.START_DOCUMENT:
				break;
			}

		}
	}

	@Override
	public EncodingEnum getEncoding() {
		return EncodingEnum.XML;
	}

	@Override
	public <T extends IBaseResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader) {
		XMLEventReader streamReader = createStreamReader(theReader);

		return parseBundle(streamReader, theResourceType);
	}

	private Bundle parseBundle(XMLEventReader theStreamReader, Class<? extends IBaseResource> theResourceType) {
		ParserState<Bundle> parserState = ParserState.getPreAtomInstance(this, myContext, theResourceType, false, getErrorHandler());
		return doXmlLoop(theStreamReader, parserState);
	}

	private <T extends IBaseResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) {
		ParserState<T> parserState = ParserState.getPreResourceInstance(this, theResourceType, myContext, false, getErrorHandler());
		return doXmlLoop(theStreamReader, parserState);
	}

	@Override
	public TagList parseTagList(Reader theReader) {
		XMLEventReader streamReader = createStreamReader(theReader);

		ParserState<TagList> parserState = ParserState.getPreTagListInstance(this, myContext, false, getErrorHandler());
		return doXmlLoop(streamReader, parserState);
	}

	@Override
	public IParser setPrettyPrint(boolean thePrettyPrint) {
		myPrettyPrint = thePrettyPrint;
		return this;
	}

	/**
	 * This is just to work around the fact that casting java.util.List<ca.uhn.fhir.model.api.ExtensionDt> to
	 * java.util.List<? extends org.hl7.fhir.instance.model.api.IBaseExtension<?, ?>> seems to be
	 * rejected by the compiler some of the time.
	 */
	private <Q extends IBaseExtension<?, ?>> List<IBaseExtension<?, ?>> toBaseExtensionList(final List<Q> theList) {
		List<IBaseExtension<?, ?>> retVal = new ArrayList<IBaseExtension<?, ?>>(theList.size());
		retVal.addAll(theList);
		return retVal;
	}

	private void writeAuthor(XMLStreamWriter theEventWriter, BaseBundle theBundle) throws XMLStreamException {
		if (StringUtils.isNotBlank(theBundle.getAuthorName().getValue())) {
			theEventWriter.writeStartElement("author");
			writeTagWithTextNode(theEventWriter, "name", theBundle.getAuthorName());
			writeOptionalTagWithTextNode(theEventWriter, "uri", theBundle.getAuthorUri());
			theEventWriter.writeEndElement();
		}
	}

	private void writeAtomLink(XMLStreamWriter theEventWriter, String theRel, StringDt theStringDt) throws XMLStreamException {
		if (StringUtils.isNotBlank(theStringDt.getValue())) {
			theEventWriter.writeStartElement("link");
			theEventWriter.writeAttribute("rel", theRel);
			theEventWriter.writeAttribute("href", theStringDt.getValue());
			theEventWriter.writeEndElement();
		}
	}

	private void writeBundleResourceLink(XMLStreamWriter theEventWriter, String theRel, StringDt theUrl) throws XMLStreamException {
		if (theUrl.isEmpty() == false) {
			theEventWriter.writeStartElement("link");
			theEventWriter.writeStartElement("relation");
			theEventWriter.writeAttribute("value", theRel);
			theEventWriter.writeEndElement();
			theEventWriter.writeStartElement("url");
			theEventWriter.writeAttribute("value", theUrl.getValue());
			theEventWriter.writeEndElement();
			theEventWriter.writeEndElement();
		}
	}

	private void writeCategories(XMLStreamWriter eventWriter, TagList categories) throws XMLStreamException {
		if (categories != null) {
			for (Tag next : categories) {
				eventWriter.writeStartElement("category");
				eventWriter.writeAttribute("term", defaultString(next.getTerm()));
				eventWriter.writeAttribute("label", defaultString(next.getLabel()));
				eventWriter.writeAttribute("scheme", defaultString(next.getScheme()));
				eventWriter.writeEndElement();
			}
		}
	}

	private void writeCommentsPost(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
		if (theElement != null && theElement.hasFormatComment()) {
			for (String next : theElement.getFormatCommentsPost()) {
				if (isNotBlank(next)) {
					theEventWriter.writeComment(next);
				}
			}
		}
	}

	private void writeCommentsPre(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
		if (theElement != null && theElement.hasFormatComment()) {
			for (String next : theElement.getFormatCommentsPre()) {
				if (isNotBlank(next)) {
					theEventWriter.writeComment(next);
				}
			}
		}
	}

	private void writeOptionalAttribute(XMLStreamWriter theEventWriter, String theName, String theValue) throws XMLStreamException {
		if (StringUtils.isNotBlank(theValue)) {
			theEventWriter.writeAttribute(theName, theValue);
		}
	}

	private void writeOptionalTagWithTextNode(XMLStreamWriter theEventWriter, String theTagName, InstantDt theInstantDt) throws XMLStreamException {
		if (theInstantDt.getValue() != null) {
			theEventWriter.writeStartElement(theTagName);
			theEventWriter.writeCharacters(theInstantDt.getValueAsString());
			theEventWriter.writeEndElement();
		}
	}

	private void writeOptionalTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, StringDt theTextValue) throws XMLStreamException {
		if (StringUtils.isNotBlank(theTextValue.getValue())) {
			theEventWriter.writeStartElement(theElementName);
			theEventWriter.writeCharacters(theTextValue.getValue());
			theEventWriter.writeEndElement();
		}
	}

	private void writeOptionalTagWithValue(XMLStreamWriter theEventWriter, String theName, String theValue) throws XMLStreamException {
		if (StringUtils.isNotBlank(theValue)) {
			theEventWriter.writeStartElement(theName);
			theEventWriter.writeAttribute("value", theValue);
			theEventWriter.writeEndElement();
		}
	}

	private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, IdDt theIdDt) throws XMLStreamException {
		theEventWriter.writeStartElement(theElementName);
		if (StringUtils.isNotBlank(theIdDt.getValue())) {
			theEventWriter.writeCharacters(theIdDt.getValue());
		}
		theEventWriter.writeEndElement();
	}

	private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, StringDt theStringDt) throws XMLStreamException {
		theEventWriter.writeStartElement(theElementName);
		if (StringUtils.isNotBlank(theStringDt.getValue())) {
			theEventWriter.writeCharacters(theStringDt.getValue());
		}
		theEventWriter.writeEndElement();
	}

}
