001/*- 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2023 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.parser; 021 022import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 023import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition; 024import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 025import ca.uhn.fhir.context.ConfigurationException; 026import ca.uhn.fhir.context.FhirContext; 027import ca.uhn.fhir.context.RuntimeChildContainedResources; 028import ca.uhn.fhir.context.RuntimeChildExtension; 029import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition; 030import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition; 031import ca.uhn.fhir.context.RuntimeResourceDefinition; 032import ca.uhn.fhir.i18n.Msg; 033import ca.uhn.fhir.model.api.IResource; 034import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; 035import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; 036import ca.uhn.fhir.model.api.Tag; 037import ca.uhn.fhir.model.api.TagList; 038import ca.uhn.fhir.model.base.composite.BaseCodingDt; 039import ca.uhn.fhir.model.primitive.IdDt; 040import ca.uhn.fhir.model.primitive.InstantDt; 041import ca.uhn.fhir.model.primitive.XhtmlDt; 042import ca.uhn.fhir.narrative.INarrativeGenerator; 043import ca.uhn.fhir.rest.api.EncodingEnum; 044import ca.uhn.fhir.util.ElementUtil; 045import ca.uhn.fhir.util.NonPrettyPrintWriterWrapper; 046import ca.uhn.fhir.util.PrettyPrintWriterWrapper; 047import ca.uhn.fhir.util.XmlUtil; 048import org.apache.commons.lang3.StringUtils; 049import org.hl7.fhir.instance.model.api.IAnyResource; 050import org.hl7.fhir.instance.model.api.IBase; 051import org.hl7.fhir.instance.model.api.IBaseBinary; 052import org.hl7.fhir.instance.model.api.IBaseDatatype; 053import org.hl7.fhir.instance.model.api.IBaseExtension; 054import org.hl7.fhir.instance.model.api.IBaseHasExtensions; 055import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions; 056import org.hl7.fhir.instance.model.api.IBaseResource; 057import org.hl7.fhir.instance.model.api.IBaseXhtml; 058import org.hl7.fhir.instance.model.api.IIdType; 059import org.hl7.fhir.instance.model.api.IPrimitiveType; 060 061import javax.xml.namespace.QName; 062import javax.xml.stream.FactoryConfigurationError; 063import javax.xml.stream.XMLEventReader; 064import javax.xml.stream.XMLStreamConstants; 065import javax.xml.stream.XMLStreamException; 066import javax.xml.stream.XMLStreamWriter; 067import javax.xml.stream.events.Attribute; 068import javax.xml.stream.events.Characters; 069import javax.xml.stream.events.Comment; 070import javax.xml.stream.events.EntityReference; 071import javax.xml.stream.events.Namespace; 072import javax.xml.stream.events.StartElement; 073import javax.xml.stream.events.XMLEvent; 074import java.io.Reader; 075import java.io.Writer; 076import java.util.ArrayList; 077import java.util.Iterator; 078import java.util.List; 079import java.util.Optional; 080 081import static org.apache.commons.lang3.StringUtils.isBlank; 082import static org.apache.commons.lang3.StringUtils.isNotBlank; 083 084/** 085 * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use 086 * {@link FhirContext#newXmlParser()} to get an instance. 087 */ 088public class XmlParser extends BaseParser { 089 090 static final String FHIR_NS = "http://hl7.org/fhir"; 091 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class); 092 private boolean myPrettyPrint; 093 094 /** 095 * Do not use this constructor, the recommended way to obtain a new instance of the XML parser is to invoke 096 * {@link FhirContext#newXmlParser()}. 097 * 098 * @param theParserErrorHandler 099 */ 100 public XmlParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) { 101 super(theContext, theParserErrorHandler); 102 } 103 104 private XMLEventReader createStreamReader(Reader theReader) { 105 try { 106 return XmlUtil.createXmlReader(theReader); 107 } catch (FactoryConfigurationError e1) { 108 throw new ConfigurationException(Msg.code(1848) + "Failed to initialize STaX event factory", e1); 109 } catch (XMLStreamException e1) { 110 throw new DataFormatException(Msg.code(1849) + e1); 111 } 112 } 113 114 private XMLStreamWriter createXmlWriter(Writer theWriter) throws XMLStreamException { 115 XMLStreamWriter eventWriter; 116 eventWriter = XmlUtil.createXmlStreamWriter(theWriter); 117 eventWriter = decorateStreamWriter(eventWriter); 118 return eventWriter; 119 } 120 121 private XMLStreamWriter decorateStreamWriter(XMLStreamWriter eventWriter) { 122 if (myPrettyPrint) { 123 PrettyPrintWriterWrapper retVal = new PrettyPrintWriterWrapper(eventWriter); 124 return retVal; 125 } 126 NonPrettyPrintWriterWrapper retVal = new NonPrettyPrintWriterWrapper(eventWriter); 127 return retVal; 128 } 129 130 @Override 131 public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws DataFormatException { 132 XMLStreamWriter eventWriter; 133 try { 134 eventWriter = createXmlWriter(theWriter); 135 136 encodeResourceToXmlStreamWriter(theResource, eventWriter, false, theEncodeContext); 137 eventWriter.flush(); 138 } catch (XMLStreamException e) { 139 throw new ConfigurationException(Msg.code(1850) + "Failed to initialize STaX event factory", e); 140 } 141 } 142 143 @Override 144 public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) { 145 XMLEventReader streamReader = createStreamReader(theReader); 146 return parseResource(theResourceType, streamReader); 147 } 148 149 private <T> T doXmlLoop(XMLEventReader streamReader, ParserState<T> parserState) { 150 ourLog.trace("Entering XML parsing loop with state: {}", parserState); 151 152 try { 153 List<String> heldComments = new ArrayList<>(1); 154 155 while (streamReader.hasNext()) { 156 XMLEvent nextEvent = streamReader.nextEvent(); 157 try { 158 159 switch (nextEvent.getEventType()) { 160 case XMLStreamConstants.START_ELEMENT: { 161 StartElement elem = nextEvent.asStartElement(); 162 163 String namespaceURI = elem.getName().getNamespaceURI(); 164 165 if ("extension".equals(elem.getName().getLocalPart())) { 166 Attribute urlAttr = elem.getAttributeByName(new QName("url")); 167 String url; 168 if (urlAttr == null || isBlank(urlAttr.getValue())) { 169 getErrorHandler().missingRequiredElement(new ParseLocation().setParentElementName("extension"), "url"); 170 url = null; 171 } else { 172 url = urlAttr.getValue(); 173 } 174 parserState.enteringNewElementExtension(elem, url, false, getServerBaseUrl()); 175 } else if ("modifierExtension".equals(elem.getName().getLocalPart())) { 176 Attribute urlAttr = elem.getAttributeByName(new QName("url")); 177 String url; 178 if (urlAttr == null || isBlank(urlAttr.getValue())) { 179 getErrorHandler().missingRequiredElement(new ParseLocation().setParentElementName("modifierExtension"), "url"); 180 url = null; 181 } else { 182 url = urlAttr.getValue(); 183 } 184 parserState.enteringNewElementExtension(elem, url, true, getServerBaseUrl()); 185 } else { 186 String elementName = elem.getName().getLocalPart(); 187 parserState.enteringNewElement(namespaceURI, elementName); 188 } 189 190 if (!heldComments.isEmpty()) { 191 for (String next : heldComments) { 192 parserState.commentPre(next); 193 } 194 heldComments.clear(); 195 } 196 197 for (Iterator<Attribute> attributes = elem.getAttributes(); attributes.hasNext(); ) { 198 Attribute next = attributes.next(); 199 parserState.attributeValue(next.getName().getLocalPart(), next.getValue()); 200 } 201 202 break; 203 } 204 case XMLStreamConstants.END_DOCUMENT: 205 case XMLStreamConstants.END_ELEMENT: { 206 if (!heldComments.isEmpty()) { 207 for (String next : heldComments) { 208 parserState.commentPost(next); 209 } 210 heldComments.clear(); 211 } 212 parserState.endingElement(); 213 break; 214 } 215 case XMLStreamConstants.CHARACTERS: { 216 parserState.string(nextEvent.asCharacters().getData()); 217 break; 218 } 219 case XMLStreamConstants.COMMENT: { 220 Comment comment = (Comment) nextEvent; 221 String commentText = comment.getText(); 222 heldComments.add(commentText); 223 break; 224 } 225 } 226 227 parserState.xmlEvent(nextEvent); 228 229 } catch (DataFormatException e) { 230 throw new DataFormatException(Msg.code(1851) + "DataFormatException at [" + nextEvent.getLocation().toString() + "]: " + e.getMessage(), e); 231 } 232 } 233 return parserState.getObject(); 234 } catch (XMLStreamException e) { 235 throw new DataFormatException(Msg.code(1852) + e); 236 } 237 } 238 239 private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, BaseRuntimeChildDefinition theChildDefinition, IBase theElement, String theChildName, BaseRuntimeElementDefinition<?> childDef, 240 String theExtensionUrl, boolean theIncludedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws XMLStreamException, DataFormatException { 241 242 /* 243 * Often the two values below will be the same thing. There are cases though 244 * where they will not be. An example would be Observation.value, which is 245 * a choice type. If the value contains a Quantity, then: 246 * childGenericName = "value" 247 * theChildName = "valueQuantity" 248 */ 249 String childGenericName = theChildDefinition.getElementName(); 250 251 theEncodeContext.pushPath(childGenericName, false); 252 try { 253 254 if (theElement == null || theElement.isEmpty()) { 255 if (isChildContained(childDef, theIncludedResource)) { 256 // We still want to go in.. 257 } else { 258 return; 259 } 260 } 261 262 writeCommentsPre(theEventWriter, theElement); 263 264 switch (childDef.getChildType()) { 265 case ID_DATATYPE: { 266 IIdType value = IIdType.class.cast(theElement); 267 String encodedValue = "id".equals(theChildName) ? value.getIdPart() : value.getValue(); 268 if (StringUtils.isNotBlank(encodedValue) || !super.hasNoExtensions(value)) { 269 theEventWriter.writeStartElement(theChildName); 270 if (StringUtils.isNotBlank(encodedValue)) { 271 theEventWriter.writeAttribute("value", encodedValue); 272 } 273 encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theEncodeContext); 274 theEventWriter.writeEndElement(); 275 } 276 break; 277 } 278 case PRIMITIVE_DATATYPE: { 279 IPrimitiveType<?> pd = IPrimitiveType.class.cast(theElement); 280 String value = pd.getValueAsString(); 281 if (value != null || !super.hasNoExtensions(pd)) { 282 theEventWriter.writeStartElement(theChildName); 283 String elementId = getCompositeElementId(theElement); 284 if (isNotBlank(elementId)) { 285 theEventWriter.writeAttribute("id", elementId); 286 } 287 if (value != null) { 288 theEventWriter.writeAttribute("value", value); 289 } 290 encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theEncodeContext); 291 theEventWriter.writeEndElement(); 292 } 293 break; 294 } 295 case RESOURCE_BLOCK: 296 case COMPOSITE_DATATYPE: { 297 theEventWriter.writeStartElement(theChildName); 298 String elementId = getCompositeElementId(theElement); 299 if (isNotBlank(elementId)) { 300 theEventWriter.writeAttribute("id", elementId); 301 } 302 if (isNotBlank(theExtensionUrl)) { 303 theEventWriter.writeAttribute("url", theExtensionUrl); 304 } 305 encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theParent, theEncodeContext); 306 theEventWriter.writeEndElement(); 307 break; 308 } 309 case CONTAINED_RESOURCE_LIST: 310 case CONTAINED_RESOURCES: { 311 /* 312 * Disable per #103 for (IResource next : value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; } 313 * theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue())); 314 * theEventWriter.writeEndElement(); } 315 */ 316 for (IBaseResource next : getContainedResources().getContainedResources()) { 317 IIdType resourceId = getContainedResources().getResourceId(next); 318 theEventWriter.writeStartElement("contained"); 319 String value = resourceId.getValue(); 320 encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(value), theEncodeContext); 321 theEventWriter.writeEndElement(); 322 } 323 break; 324 } 325 case RESOURCE: { 326 IBaseResource resource = (IBaseResource) theElement; 327 String resourceName = getContext().getResourceType(resource); 328 if (!super.shouldEncodeResource(resourceName)) { 329 break; 330 } 331 theEventWriter.writeStartElement(theChildName); 332 theEncodeContext.pushPath(resourceName, true); 333 encodeResourceToXmlStreamWriter(resource, theEventWriter, theIncludedResource, theEncodeContext); 334 theEncodeContext.popPath(); 335 theEventWriter.writeEndElement(); 336 break; 337 } 338 case PRIMITIVE_XHTML: { 339 XhtmlDt dt = XhtmlDt.class.cast(theElement); 340 if (dt.hasContent()) { 341 encodeXhtml(dt, theEventWriter); 342 } 343 break; 344 } 345 case PRIMITIVE_XHTML_HL7ORG: { 346 IBaseXhtml dt = IBaseXhtml.class.cast(theElement); 347 if (!dt.isEmpty()) { 348 // TODO: this is probably not as efficient as it could be 349 XhtmlDt hdt = new XhtmlDt(); 350 hdt.setValueAsString(dt.getValueAsString()); 351 encodeXhtml(hdt, theEventWriter); 352 } 353 break; 354 } 355 case EXTENSION_DECLARED: 356 case UNDECL_EXT: { 357 throw new IllegalStateException(Msg.code(1853) + "state should not happen: " + childDef.getName()); 358 } 359 } 360 361 writeCommentsPost(theEventWriter, theElement); 362 363 } finally { 364 theEncodeContext.popPath(); 365 } 366 367 } 368 369 private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) 370 throws XMLStreamException, DataFormatException { 371 372 for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext)) { 373 374 BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); 375 376 if (nextChild.getElementName().equals("url") && theElement instanceof IBaseExtension) { 377 /* 378 * XML encoding is a one-off for extensions. The URL element goes in an attribute 379 * instead of being encoded as a normal element, only for XML encoding 380 */ 381 continue; 382 } 383 384 if (nextChild instanceof RuntimeChildNarrativeDefinition) { 385 Optional<IBase> narr = nextChild.getAccessor().getFirstValueOrNull(theElement); 386 INarrativeGenerator gen = getContext().getNarrativeGenerator(); 387 if (gen != null && narr.isPresent() == false) { 388 gen.populateResourceNarrative(getContext(), theResource); 389 } 390 391 narr = nextChild.getAccessor().getFirstValueOrNull(theElement); 392 if (narr.isPresent()) { 393 RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild; 394 String childName = nextChild.getChildNameByDatatype(child.getDatatype()); 395 BaseRuntimeElementDefinition<?> type = child.getChildByName(childName); 396 encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, narr.get(), childName, type, null, theContainedResource, nextChildElem, theEncodeContext); 397 continue; 398 } 399 } 400 401 if (nextChild instanceof RuntimeChildContainedResources) { 402 encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem, theEncodeContext); 403 } else { 404 405 List<? extends IBase> values = nextChild.getAccessor().getValues(theElement); 406 values = preProcessValues(nextChild, theResource, values, nextChildElem, theEncodeContext); 407 408 if (values == null || values.isEmpty()) { 409 continue; 410 } 411 for (IBase nextValue : values) { 412 if ((nextValue == null || nextValue.isEmpty())) { 413 continue; 414 } 415 416 BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue); 417 if (childNameAndDef == null) { 418 continue; 419 } 420 421 String childName = childNameAndDef.getChildName(); 422 BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef(); 423 String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl()); 424 425 boolean isExtension = childName.equals("extension") || childName.equals("modifierExtension"); 426 if (isExtension && nextValue instanceof IBaseExtension) { 427 IBaseExtension<?, ?> ext = (IBaseExtension<?, ?>) nextValue; 428 if (isBlank(ext.getUrl())) { 429 ParseLocation loc = new ParseLocation(theEncodeContext.toString() + "." + childName); 430 getErrorHandler().missingRequiredElement(loc, "url"); 431 } 432 if (ext.getValue() != null && ext.getExtension().size() > 0) { 433 ParseLocation loc = new ParseLocation(theEncodeContext.toString() + "." + childName); 434 getErrorHandler().extensionContainsValueAndNestedExtensions(loc); 435 } 436 } 437 438 if (extensionUrl != null && isExtension == false) { 439 encodeExtension(theResource, theEventWriter, theContainedResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef, theEncodeContext); 440 } else if (nextChild instanceof RuntimeChildExtension) { 441 IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue; 442 if ((extension.getValue() == null || extension.getValue().isEmpty())) { 443 if (extension.getExtension().isEmpty()) { 444 continue; 445 } 446 } 447 encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, nextChildElem, theEncodeContext); 448 } else { 449 encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem, theEncodeContext); 450 } 451 452 } 453 } 454 } 455 } 456 457 private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition<?> childDef, EncodeContext theEncodeContext) 458 throws XMLStreamException { 459 BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild; 460 if (extDef.isModifier()) { 461 theEventWriter.writeStartElement("modifierExtension"); 462 } else { 463 theEventWriter.writeStartElement("extension"); 464 } 465 466 String elementId = getCompositeElementId(nextValue); 467 if (isNotBlank(elementId)) { 468 theEventWriter.writeAttribute("id", elementId); 469 } 470 471 if (isBlank(extensionUrl)) { 472 ParseLocation loc = new ParseLocation(theEncodeContext.toString()); 473 getErrorHandler().missingRequiredElement(loc, "url"); 474 } else { 475 theEventWriter.writeAttribute("url", extensionUrl); 476 } 477 478 encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, nextValue, childName, childDef, null, theContainedResource, nextChildElem, theEncodeContext); 479 theEventWriter.writeEndElement(); 480 } 481 482 private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource, EncodeContext theEncodeContext) throws XMLStreamException, DataFormatException { 483 if (theElement instanceof ISupportsUndeclaredExtensions) { 484 ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement; 485 encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource, theEncodeContext); 486 encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource, theEncodeContext); 487 } 488 if (theElement instanceof IBaseHasExtensions) { 489 IBaseHasExtensions res = (IBaseHasExtensions) theElement; 490 encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource, theEncodeContext); 491 } 492 if (theElement instanceof IBaseHasModifierExtensions) { 493 IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement; 494 encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource, theEncodeContext); 495 } 496 } 497 498 private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource, EncodeContext theEncodeContext) throws XMLStreamException, DataFormatException { 499 IIdType resourceId = null; 500 501 if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) { 502 resourceId = theResource.getIdElement(); 503 if (theResource.getIdElement().getValue().startsWith("urn:")) { 504 resourceId = null; 505 } 506 } 507 508 if (!theIncludedResource) { 509 if (super.shouldEncodeResourceId(theResource, theEncodeContext) == false) { 510 resourceId = null; 511 } else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) { 512 resourceId = getEncodeForceResourceId(); 513 } 514 } 515 516 encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId, theEncodeContext); 517 } 518 519 private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, IIdType theResourceId, EncodeContext theEncodeContext) throws XMLStreamException { 520 RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource); 521 if (resDef == null) { 522 throw new ConfigurationException(Msg.code(1854) + "Unknown resource type: " + theResource.getClass()); 523 } 524 525 if (!theContainedResource) { 526 setContainedResources(getContext().newTerser().containResources(theResource)); 527 } 528 529 theEventWriter.writeStartElement(resDef.getName()); 530 theEventWriter.writeDefaultNamespace(FHIR_NS); 531 532 if (theResource instanceof IAnyResource) { 533 // HL7.org Structures 534 if (theResourceId != null) { 535 writeCommentsPre(theEventWriter, theResourceId); 536 theEventWriter.writeStartElement("id"); 537 theEventWriter.writeAttribute("value", theResourceId.getIdPart()); 538 encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext); 539 theEventWriter.writeEndElement(); 540 writeCommentsPost(theEventWriter, theResourceId); 541 } 542 543 encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext); 544 545 } else { 546 547 // DSTU2+ 548 549 IResource resource = (IResource) theResource; 550 if (theResourceId != null) { 551 /* writeCommentsPre(theEventWriter, theResourceId); 552 writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart()); 553 writeCommentsPost(theEventWriter, theResourceId);*/ 554 theEventWriter.writeStartElement("id"); 555 theEventWriter.writeAttribute("value", theResourceId.getIdPart()); 556 encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext); 557 theEventWriter.writeEndElement(); 558 writeCommentsPost(theEventWriter, theResourceId); 559 } 560 561 InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); 562 IdDt resourceId = resource.getId(); 563 String versionIdPart = resourceId.getVersionIdPart(); 564 if (isBlank(versionIdPart)) { 565 versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource); 566 } 567 List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS); 568 List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES); 569 profiles = super.getProfileTagsForEncoding(resource, profiles); 570 571 TagList tags = getMetaTagsForEncoding((resource), theEncodeContext); 572 573 if (super.shouldEncodeResourceMeta(resource) && ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) { 574 theEventWriter.writeStartElement("meta"); 575 if (shouldEncodePath(resource, "meta.versionId")) { 576 writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart); 577 } 578 if (updated != null) { 579 if (shouldEncodePath(resource, "meta.lastUpdated")) { 580 writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString()); 581 } 582 } 583 584 for (IIdType profile : profiles) { 585 theEventWriter.writeStartElement("profile"); 586 theEventWriter.writeAttribute("value", profile.getValue()); 587 theEventWriter.writeEndElement(); 588 } 589 for (BaseCodingDt securityLabel : securityLabels) { 590 theEventWriter.writeStartElement("security"); 591 encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext); 592 theEventWriter.writeEndElement(); 593 } 594 if (tags != null) { 595 for (Tag tag : tags) { 596 if (tag.isEmpty()) { 597 continue; 598 } 599 theEventWriter.writeStartElement("tag"); 600 writeOptionalTagWithValue(theEventWriter, "system", tag.getScheme()); 601 writeOptionalTagWithValue(theEventWriter, "code", tag.getTerm()); 602 writeOptionalTagWithValue(theEventWriter, "display", tag.getLabel()); 603 theEventWriter.writeEndElement(); 604 } 605 } 606 theEventWriter.writeEndElement(); 607 } 608 609 if (theResource instanceof IBaseBinary) { 610 IBaseBinary bin = (IBaseBinary) theResource; 611 writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType()); 612 writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64()); 613 } else { 614 encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext); 615 } 616 617 } 618 619 theEventWriter.writeEndElement(); 620 } 621 622 private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List<? extends IBaseExtension<?, ?>> theExtensions, String tagName, boolean theIncludedResource, EncodeContext theEncodeContext) 623 throws XMLStreamException, DataFormatException { 624 for (IBaseExtension<?, ?> next : theExtensions) { 625 if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) { 626 continue; 627 } 628 629 writeCommentsPre(theEventWriter, next); 630 631 theEventWriter.writeStartElement(tagName); 632 633 String elementId = getCompositeElementId(next); 634 if (isNotBlank(elementId)) { 635 theEventWriter.writeAttribute("id", elementId); 636 } 637 638 String url = getExtensionUrl(next.getUrl()); 639 if (isNotBlank(url)) { 640 theEventWriter.writeAttribute("url", url); 641 } 642 643 if (next.getValue() != null) { 644 IBaseDatatype value = next.getValue(); 645 RuntimeChildUndeclaredExtensionDefinition extDef = getContext().getRuntimeChildUndeclaredExtensionDefinition(); 646 String childName = extDef.getChildNameByDatatype(value.getClass()); 647 BaseRuntimeElementDefinition<?> childDef; 648 if (childName == null) { 649 childDef = getContext().getElementDefinition(value.getClass()); 650 if (childDef == null) { 651 throw new ConfigurationException(Msg.code(1855) + "Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName()); 652 } 653 childName = RuntimeChildUndeclaredExtensionDefinition.createExtensionChildName(childDef); 654 } else { 655 childDef = extDef.getChildElementDefinitionByDatatype(value.getClass()); 656 if (childDef == null) { 657 throw new ConfigurationException(Msg.code(1856) + "Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName()); 658 } 659 } 660 encodeChildElementToStreamWriter(theResource, theEventWriter, extDef, value, childName, childDef, null, theIncludedResource, null, theEncodeContext); 661 } 662 663 // child extensions 664 encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource, theEncodeContext); 665 666 theEventWriter.writeEndElement(); 667 668 writeCommentsPost(theEventWriter, next); 669 670 } 671 } 672 673 674 private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws XMLStreamException { 675 if (theDt == null || theDt.getValue() == null) { 676 return; 677 } 678 679 List<XMLEvent> events = XmlUtil.parse(theDt.getValue()); 680 boolean firstElement = true; 681 682 for (XMLEvent event : events) { 683 switch (event.getEventType()) { 684 case XMLStreamConstants.ATTRIBUTE: 685 Attribute attr = (Attribute) event; 686 if (isBlank(attr.getName().getPrefix())) { 687 if (isBlank(attr.getName().getNamespaceURI())) { 688 theEventWriter.writeAttribute(attr.getName().getLocalPart(), attr.getValue()); 689 } else { 690 theEventWriter.writeAttribute(attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue()); 691 } 692 } else { 693 theEventWriter.writeAttribute(attr.getName().getPrefix(), attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue()); 694 } 695 696 break; 697 case XMLStreamConstants.CDATA: 698 theEventWriter.writeCData(((Characters) event).getData()); 699 break; 700 case XMLStreamConstants.CHARACTERS: 701 case XMLStreamConstants.SPACE: 702 String data = ((Characters) event).getData(); 703 theEventWriter.writeCharacters(data); 704 break; 705 case XMLStreamConstants.COMMENT: 706 theEventWriter.writeComment(((Comment) event).getText()); 707 break; 708 case XMLStreamConstants.END_ELEMENT: 709 theEventWriter.writeEndElement(); 710 break; 711 case XMLStreamConstants.ENTITY_REFERENCE: 712 EntityReference er = (EntityReference) event; 713 theEventWriter.writeEntityRef(er.getName()); 714 break; 715 case XMLStreamConstants.NAMESPACE: 716 Namespace ns = (Namespace) event; 717 theEventWriter.writeNamespace(ns.getPrefix(), ns.getNamespaceURI()); 718 break; 719 case XMLStreamConstants.START_ELEMENT: 720 StartElement se = event.asStartElement(); 721 if (firstElement) { 722 if (StringUtils.isBlank(se.getName().getPrefix())) { 723 String namespaceURI = se.getName().getNamespaceURI(); 724 if (StringUtils.isBlank(namespaceURI)) { 725 namespaceURI = "http://www.w3.org/1999/xhtml"; 726 } 727 theEventWriter.writeStartElement(se.getName().getLocalPart()); 728 theEventWriter.writeDefaultNamespace(namespaceURI); 729 } else { 730 String prefix = se.getName().getPrefix(); 731 String namespaceURI = se.getName().getNamespaceURI(); 732 theEventWriter.writeStartElement(prefix, se.getName().getLocalPart(), namespaceURI); 733 theEventWriter.writeNamespace(prefix, namespaceURI); 734 } 735 firstElement = false; 736 } else { 737 if (isBlank(se.getName().getPrefix())) { 738 if (isBlank(se.getName().getNamespaceURI())) { 739 theEventWriter.writeStartElement(se.getName().getLocalPart()); 740 } else { 741 if (StringUtils.isBlank(se.getName().getPrefix())) { 742 theEventWriter.writeStartElement(se.getName().getLocalPart()); 743 // theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI()); 744 } else { 745 theEventWriter.writeStartElement(se.getName().getNamespaceURI(), se.getName().getLocalPart()); 746 } 747 } 748 } else { 749 theEventWriter.writeStartElement(se.getName().getPrefix(), se.getName().getLocalPart(), se.getName().getNamespaceURI()); 750 } 751 } 752 for (Iterator<?> attrIter = se.getAttributes(); attrIter.hasNext(); ) { 753 Attribute next = (Attribute) attrIter.next(); 754 if (isBlank(next.getName().getNamespaceURI())) { 755 theEventWriter.writeAttribute(next.getName().getLocalPart(), next.getValue()); 756 } else { 757 theEventWriter.writeAttribute(next.getName().getPrefix(), next.getName().getNamespaceURI(), next.getName().getLocalPart(), next.getValue()); 758 } 759 } 760 break; 761 case XMLStreamConstants.DTD: 762 case XMLStreamConstants.END_DOCUMENT: 763 case XMLStreamConstants.ENTITY_DECLARATION: 764 case XMLStreamConstants.NOTATION_DECLARATION: 765 case XMLStreamConstants.PROCESSING_INSTRUCTION: 766 case XMLStreamConstants.START_DOCUMENT: 767 break; 768 } 769 770 } 771 } 772 773 @Override 774 public EncodingEnum getEncoding() { 775 return EncodingEnum.XML; 776 } 777 778 private <T extends IBaseResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) { 779 ParserState<T> parserState = ParserState.getPreResourceInstance(this, theResourceType, getContext(), false, getErrorHandler()); 780 return doXmlLoop(theStreamReader, parserState); 781 } 782 783 @Override 784 public IParser setPrettyPrint(boolean thePrettyPrint) { 785 myPrettyPrint = thePrettyPrint; 786 return this; 787 } 788 789 /** 790 * This is just to work around the fact that casting java.util.List<ca.uhn.fhir.model.api.ExtensionDt> to 791 * java.util.List<? extends org.hl7.fhir.instance.model.api.IBaseExtension<?, ?>> seems to be 792 * rejected by the compiler some of the time. 793 */ 794 private <Q extends IBaseExtension<?, ?>> List<IBaseExtension<?, ?>> toBaseExtensionList(final List<Q> theList) { 795 List<IBaseExtension<?, ?>> retVal = new ArrayList<IBaseExtension<?, ?>>(theList.size()); 796 retVal.addAll(theList); 797 return retVal; 798 } 799 800 private void writeCommentsPost(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException { 801 if (theElement != null && theElement.hasFormatComment()) { 802 for (String next : theElement.getFormatCommentsPost()) { 803 if (isNotBlank(next)) { 804 theEventWriter.writeComment(next); 805 } 806 } 807 } 808 } 809 810 private void writeCommentsPre(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException { 811 if (theElement != null && theElement.hasFormatComment()) { 812 for (String next : theElement.getFormatCommentsPre()) { 813 if (isNotBlank(next)) { 814 theEventWriter.writeComment(next); 815 } 816 } 817 } 818 } 819 820 private void writeOptionalTagWithValue(XMLStreamWriter theEventWriter, String theName, String theValue) throws XMLStreamException { 821 if (StringUtils.isNotBlank(theValue)) { 822 theEventWriter.writeStartElement(theName); 823 theEventWriter.writeAttribute("value", theValue); 824 theEventWriter.writeEndElement(); 825 } 826 } 827 828}