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