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