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