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