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