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