
001package org.hl7.fhir.r5.elementmodel; 002 003import java.io.PrintStream; 004 005/* 006 Copyright (c) 2011+, HL7, Inc. 007 All rights reserved. 008 009 Redistribution and use in source and binary forms, with or without modification, 010 are permitted provided that the following conditions are met: 011 012 * Redistributions of source code must retain the above copyright notice, this 013 list of conditions and the following disclaimer. 014 * Redistributions in binary form must reproduce the above copyright notice, 015 this list of conditions and the following disclaimer in the documentation 016 and/or other materials provided with the distribution. 017 * Neither the name of HL7 nor the names of its contributors may be used to 018 endorse or promote products derived from this software without specific 019 prior written permission. 020 021 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 022 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 023 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 024 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 025 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 026 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 027 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 028 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 029 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 030 POSSIBILITY OF SUCH DAMAGE. 031 032 */ 033import java.util.ArrayList; 034import java.util.Arrays; 035import java.util.Comparator; 036import java.util.HashMap; 037import java.util.HashSet; 038import java.util.List; 039import java.util.Map; 040import java.util.Set; 041 042import lombok.Getter; 043import lombok.Setter; 044import org.apache.commons.lang3.Validate; 045import org.hl7.fhir.exceptions.FHIRException; 046import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 047import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; 048import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 049import org.hl7.fhir.r5.extensions.ExtensionUtilities; 050import org.hl7.fhir.r5.model.Base; 051import org.hl7.fhir.r5.model.DataType; 052import org.hl7.fhir.r5.model.ElementDefinition; 053import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 054import org.hl7.fhir.r5.model.Enumerations.BindingStrength; 055import org.hl7.fhir.r5.model.ICoding; 056import org.hl7.fhir.r5.model.StringType; 057import org.hl7.fhir.r5.model.StructureDefinition; 058import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 059import org.hl7.fhir.r5.model.TypeConvertor; 060import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 061import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 062 063import org.hl7.fhir.r5.utils.UserDataNames; 064import org.hl7.fhir.utilities.ElementDecoration; 065import org.hl7.fhir.utilities.ElementDecoration.DecorationType; 066import org.hl7.fhir.utilities.FhirPublication; 067import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 068import org.hl7.fhir.utilities.NamedItemList; 069import org.hl7.fhir.utilities.NamedItemList.NamedItem; 070import org.hl7.fhir.utilities.SourceLocation; 071import org.hl7.fhir.utilities.Utilities; 072import org.hl7.fhir.utilities.validation.ValidationMessage; 073import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 074import org.hl7.fhir.utilities.xhtml.XhtmlNode; 075 076/** 077 * This class represents the underlying reference model of FHIR 078 * 079 * A resource is nothing but a set of elements, where every element has a 080 * name, maybe a stated type, maybe an id, and either a value or child elements 081 * (one or the other, but not both or neither) 082 * 083 * @author Grahame Grieve 084 * 085 */ 086@MarkedToMoveToAdjunctPackage 087public class Element extends Base implements NamedItem { 088 089 public class SliceDefinition { 090 091 private StructureDefinition profile; 092 private ElementDefinition definition; 093 private ElementDefinition slice; 094 095 public SliceDefinition(StructureDefinition profile, ElementDefinition definition, ElementDefinition slice) { 096 this.profile = profile; 097 this.definition = definition; 098 this.slice = slice; 099 } 100 101 public StructureDefinition getProfile() { 102 return profile; 103 } 104 105 public ElementDefinition getDefinition() { 106 return definition; 107 } 108 109 public ElementDefinition getSlice() { 110 return slice; 111 } 112 113 } 114 115 private static final HashSet<String> extensionList = new HashSet<>(Arrays.asList("extension", "modifierExtension")); 116 117 public enum SpecialElement { 118 CONTAINED, BUNDLE_ENTRY, BUNDLE_OUTCOME, BUNDLE_ISSUES, PARAMETER, LOGICAL; 119 120 public static SpecialElement fromProperty(Property property) { 121 if (property.getStructure().getType().equals("Parameters")) 122 return PARAMETER; 123 if (property.getStructure().getType().equals("Bundle") && property.getName().equals("resource")) 124 return BUNDLE_ENTRY; 125 if (property.getStructure().getType().equals("Bundle") && property.getName().equals("outcome")) 126 return BUNDLE_OUTCOME; 127 if (property.getStructure().getType().equals("Bundle") && property.getName().equals("issues")) 128 return BUNDLE_ISSUES; 129 if (property.getName().equals("contained")) 130 return CONTAINED; 131 if (property.getStructure().getKind() == StructureDefinitionKind.LOGICAL) 132 return LOGICAL; 133 throw new FHIRException("Unknown resource containing a native resource: "+property.getDefinition().getId()); 134 } 135 136 public String toHuman() { 137 switch (this) { 138 case BUNDLE_ENTRY: return "entry"; 139 case BUNDLE_OUTCOME: return "outcome"; 140 case BUNDLE_ISSUES: return "issues"; 141 case CONTAINED: return "contained"; 142 case PARAMETER: return "parameter"; 143 case LOGICAL: return "logical"; 144 default: return "??"; 145 } 146 } 147 } 148 149 private List<String> comments;// not relevant for production, but useful in documentation 150 private String name; 151 private String type; 152 private String value; 153 private int index = -1; 154 private NamedItemList<Element> children; 155 private Property property; 156 private Property elementProperty; // this is used when special is set to true - it tracks the underlying element property which is used in a few places 157 private int line; 158 private int col; 159 private SpecialElement special; 160 private XhtmlNode xhtml; // if this is populated, then value will also hold the string representation 161 private String explicitType; // for xsi:type attribute 162 private Element parentForValidator; 163 private boolean hasParentForValidator; 164 private String path; 165 private List<ValidationMessage> messages; 166 private boolean prohibited; 167 private boolean required; 168 private int descendentCount; 169 private int instanceId; 170 private boolean isNull; 171 private Base source; 172 private boolean ignorePropertyOrder; 173 private FhirFormat format; 174 private Object nativeObject; 175 private List<SliceDefinition> sliceDefinitions; 176 private boolean elided; 177 @Getter @Setter private String resourceDefinition; 178 179 public Element(String name) { 180 super(); 181 this.name = name; 182 } 183 184 public Element(Element other) { 185 super(); 186 name = other.name; 187 type = other.type; 188 property = other.property; 189 elementProperty = other.elementProperty; 190 special = other.special; 191 } 192 193 public Element(String name, Property property) { 194 super(); 195 this.name = name; 196 this.property = property; 197 if (property.isResource()) { 198 children = new NamedItemList<>(); 199 } 200 } 201 202 public Element(String name, Property property, String type, String value) { 203 super(); 204 this.name = name; 205 this.property = property; 206 this.type = type; 207 this.value = value; 208 } 209 210 public void updateProperty(Property property, SpecialElement special, Property elementProperty) { 211 this.property = property; 212 this.elementProperty = elementProperty; 213 this.special = special; 214 } 215 216 public SpecialElement getSpecial() { 217 return special; 218 } 219 220 public String getName() { 221 return name; 222 } 223 224 public String getJsonName() { 225 return property.getJsonName(); 226 } 227 228 public String getType() { 229 if (type == null) 230 return property.getType(name); 231 else 232 return type; 233 } 234 235 public String getValue() { 236 if (xhtml != null) { 237 throw new Error("Don't use getValue() for xhtml"); 238 } 239 return value; 240 } 241 242 public boolean hasChildren() { 243 return !(children == null || children.isEmpty()); 244 } 245 246 public NamedItemList<Element> getChildren() { 247 if (children == null) 248 children = new NamedItemList<Element>(); 249 return children; 250 } 251 252 public boolean hasComments() { 253 return !(comments == null || comments.isEmpty()); 254 } 255 256 public List<String> getComments() { 257 if (comments == null) 258 comments = new ArrayList<String>(); 259 return comments; 260 } 261 262 public Property getProperty() { 263 return property; 264 } 265 266 public void setValue(String value) { 267 this.value = value; 268 } 269 270 public Element setType(String type) { 271 this.type = type; 272 return this; 273 274 } 275 276 public boolean isNull() { 277 return isNull; 278 } 279 280 public void setNull(boolean isNull) { 281 this.isNull = isNull; 282 } 283 284 public boolean hasValue() { 285 return value != null; 286 } 287 288 public List<Element> getChildrenByName(String name) { 289 return children.getByName(name); 290 } 291 292 public void numberChildren() { 293 if (children == null) 294 return; 295 296 String last = ""; 297 int index = 0; 298 for (Element child : children) { 299 if (child.getProperty().isList()) { 300 if (last.equals(child.getName())) { 301 index++; 302 } else { 303 last = child.getName(); 304 index = 0; 305 } 306 child.index = index; 307 } else { 308 child.index = -1; 309 } 310 child.numberChildren(); 311 } 312 } 313 314 public int getIndex() { 315 return index; 316 } 317 318 public boolean hasIndex() { 319 return index > -1; 320 } 321 322 public void setIndex(int index) { 323 this.index = index; 324 } 325 326 public String getChildValue(String name) { 327 if (children == null) 328 return null; 329 for (Element child : children) { 330 if (name.equals(child.getName())) 331 return child.getValue(); 332 } 333 for (Element child : children) { 334 if (name.equals(child.getNameBase())) 335 return child.getValue(); 336 } 337 return null; 338 } 339 340 private String getNameBase() { 341 if (property.isChoice()) { 342 return property.getName().replace("[x]", ""); 343 } else { 344 return getName(); 345 } 346 } 347 348 public void setChildValue(String name, String value) { 349 if (children == null) 350 children = new NamedItemList<Element>(); 351 for (Element child : children) { 352 if (name.equals(child.getName())) { 353 if (!child.isPrimitive()) 354 throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")"); 355 child.setValue(value); 356 } 357 } 358 359 try { 360 setProperty(name.hashCode(), name, new StringType(value)); 361 } catch (FHIRException e) { 362 throw new Error(e); 363 } 364 } 365 366 public void setChildValue(String name, Base value) { 367 if (children == null) 368 children = new NamedItemList<Element>(); 369 for (Element child : children) { 370 if (nameMatches(child.getName(), name)) { 371 if (!child.isPrimitive()) 372 throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")"); 373 child.setValue(value.primitiveValue()); 374 return; 375 } 376 } 377 378 try { 379 setProperty(name.hashCode(), name, value); 380 } catch (FHIRException e) { 381 throw new Error(e); 382 } 383 } 384 385 public List<Element> getChildren(String name) { 386 List<Element> res = new ArrayList<Element>(); 387 if (children != null) { 388 if (children.size() > 20) { 389 List<Element> l = children.getByName(name); 390 if (l != null) { 391 res.addAll(l); 392 } 393 } else { 394 for (Element child : children) { 395 if (name.equals(child.getName())) 396 res.add(child); 397 } 398 } 399 } 400 return res; 401 } 402 403 public boolean hasType() { 404 if (type == null) 405 return property.hasType(name); 406 else 407 return true; 408 } 409 410 @Override 411 public String fhirType() { 412 return getType(); 413 } 414 415 @Override 416 public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { 417 if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) { 418// String tn = getType(); 419// throw new Error(tn+" not done yet"); 420 Base[] b = new Base[1]; 421 b[0] = new StringType(value); 422 return b; 423 } 424 425 List<Base> result = new ArrayList<Base>(); 426 if (children != null) { 427 if (children.size() > 20) { 428 List<Element> l = children.getByName(name); 429 if (l != null) { 430 result.addAll(l); 431 } 432 } else { 433 for (Element child : children) { 434 if (child.getName().equals(name)) { 435 result.add(child); 436 } 437 if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]")) { 438 result.add(child); 439 } 440 } 441 } 442 } 443 if (result.isEmpty() && checkValid) { 444// throw new FHIRException("not determined yet"); 445 } 446 return result.toArray(new Base[result.size()]); 447 } 448 449 @Override 450 protected void listChildren(List<org.hl7.fhir.r5.model.Property> childProps) { 451 if (children != null) { 452 Map<String, org.hl7.fhir.r5.model.Property> map = new HashMap<String, org.hl7.fhir.r5.model.Property>(); 453 for (Element c : children) { 454 org.hl7.fhir.r5.model.Property p = map.get(c.getName()); 455 if (p == null) { 456 p = new org.hl7.fhir.r5.model.Property(c.getName(), c.fhirType(), c.getProperty().getDefinition().getDefinition(), c.getProperty().getDefinition().getMin(), maxToInt(c.getProperty().getDefinition().getMax()), c); 457 childProps.add(p); 458 map.put(c.getName(), p); 459 460 } else 461 p.getValues().add(c); 462 } 463 } 464 } 465 466 @Override 467 public Base setProperty(int hash, String name, Base value) throws FHIRException { 468 if ("xhtml".equals(getType()) && (hash == "value".hashCode())) { 469 this.xhtml = TypeConvertor.castToXhtml(value); 470 this.value = TypeConvertor.castToXhtmlString(value); 471 return this; 472 } 473 if (isPrimitive() && (hash == "value".hashCode())) { 474 this.value = TypeConvertor.castToString(value).asStringValue(); 475 return this; 476 } 477 478 if (!value.isPrimitive() && !(value instanceof Element)) { 479 if (isDataType(value)) 480 value = convertToElement(property.getChild(name), value); 481 else 482 throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type"); 483 } 484 485 if (children == null) 486 children = new NamedItemList<Element>(); 487 Element childForValue = null; 488 489 // look through existing children 490 for (Element child : children) { 491 if (child.getName().equals(name)) { 492 if (!child.isList()) { 493 childForValue = child; 494 break; 495 } else { 496 Element ne = new Element(child).setFormat(format); 497 children.add(ne); 498 ne.index = children.getSizeByName(ne.getListName()) - 1; 499 childForValue = ne; 500 break; 501 } 502 } 503 } 504 505 int i = 0; 506 if (childForValue == null) { 507 for (Property p : property.getChildProperties(this.name, type)) { 508 int t = -1; 509 for (int c =0; c < children.size(); c++) { 510 Element e = children.get(c); 511 if (p.getName().equals(e.getName())) 512 t = c; 513 } 514 if (t >= i) 515 i = t+1; 516 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 517 Element ne = new Element(name, p).setFormat(format); 518 children.add(i, ne); 519 childForValue = ne; 520 break; 521 } else if (p.getName().endsWith("[x]") && name.startsWith(p.getName().replace("[x]", ""))) { 522 Element ne = new Element(p.getName(), p).setFormat(format); 523 children.add(i, ne); 524 childForValue = ne; 525 break; 526 } 527 } 528 } 529 530 if (childForValue == null) 531 throw new Error("Cannot set property "+name+" on "+this.name); 532 else if (value.isPrimitive()) { 533 if (childForValue.property.getName().endsWith("[x]")) 534 childForValue.name = childForValue.name.replace("[x]", "")+Utilities.capitalize(value.fhirType()); 535 childForValue.setValue(value.primitiveValue()); 536 } else { 537 Element ve = (Element) value; 538 childForValue.type = ve.getType(); 539 if (childForValue.property.getName().endsWith("[x]")) 540 childForValue.name = name+Utilities.capitalize(childForValue.type); 541 else if (value.isResource()) { 542 if (childForValue.elementProperty == null) 543 childForValue.elementProperty = childForValue.property; 544 childForValue.property = ve.property; 545 childForValue.special = SpecialElement.BUNDLE_ENTRY; 546 } 547 if (ve.children != null) { 548 if (childForValue.children == null) 549 childForValue.children = new NamedItemList<Element>(); 550 else 551 childForValue.children.clear(); 552 childForValue.children.addAll(ve.children); 553 } 554 } 555 return childForValue; 556 } 557 558 private Base convertToElement(Property prop, Base v) throws FHIRException { 559 return new ObjectConverter(property.getContext()).convert(prop, (DataType) v); 560 } 561 562 private boolean isDataType(Base v) { 563 return v instanceof DataType && property.getContextUtils().getTypeNames().contains(v.fhirType()); 564 } 565 566 @Override 567 public Base makeProperty(int hash, String name) throws FHIRException { 568 if (isPrimitive() && (hash == "value".hashCode())) { 569 return new StringType(value); 570 } else { 571 return makeElement(name); 572 } 573 } 574 575 public Element makeElement(String name) throws FHIRException { 576 if (children == null) 577 children = new NamedItemList<Element>(); 578 579 // look through existing children 580 for (Element child : children) { 581 if (child.getName().equals(name)) { 582 if (!child.isList()) { 583 return child; 584 } else { 585 Element ne = new Element(child).setFormat(format); 586 children.add(ne); 587 ne.index = children.getSizeByName(ne.getListName()) - 1; 588 return ne; 589 } 590 } 591 } 592 593 for (Property p : property.getChildProperties(this.name, type)) { 594 if (p.getName().equals(name)) { 595 Element ne = new Element(name, p).setFormat(format); 596 children.add(ne); 597 ne.index = children.getSizeByName(ne.getListName()) - 1; 598 return ne; 599 } else if (p.getDefinition().isChoice() && name.startsWith(p.getName().replace("[x]", ""))) { 600 String type = name.substring(p.getName().length()-3); 601 if (property.getContext().isPrimitiveType(Utilities.uncapitalize(type))) { 602 type = Utilities.uncapitalize(type); 603 } 604 Element ne = new Element(name, p).setFormat(format); 605 ne.setType(type); 606 children.add(ne); 607 ne.index = children.getSizeByName(ne.getListName()) - 1; 608 return ne; 609 610 } 611 } 612 613 throw new Error("Unrecognised name "+name+" on "+this.name); 614 } 615 616 public Element forceElement(String name) throws FHIRException { 617 if (children == null) 618 children = new NamedItemList<Element>(); 619 620 // look through existing children 621 for (Element child : children) { 622 if (child.getName().equals(name)) { 623 return child; 624 } 625 } 626 627 int index = -1; 628 629 for (Property p : property.getChildProperties(this.name, type)) { 630 if (p.getName().equals(name)) { 631 Element ne = new Element(name, p).setFormat(format); 632 children.add(index+1, ne); 633 for (int i = 0; i < children.size(); i++) { 634 children.get(i).index = i; 635 } 636 return ne; 637 } else { 638 for (int i = 0; i < children.size(); i++) { 639 if (children.get(i).getName().equals(p.getName())) { 640 index = i; 641 } 642 } 643 } 644 } 645 646 throw new Error("Unrecognised name "+name+" on "+this.name); 647 } 648 649 650 private int maxToInt(String max) { 651 if (max.equals("*")) 652 return Integer.MAX_VALUE; 653 else 654 return Integer.parseInt(max); 655 } 656 657 @Override 658 public boolean isPrimitive() { 659 return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name)); 660 } 661 662 @Override 663 public boolean isBooleanPrimitive() { 664 return isPrimitive() && ("boolean".equals(type) || "boolean".equals(property.getType(name))); 665 } 666 667 @Override 668 public boolean isResource() { 669 return property.isResource(); 670 } 671 672 673 @Override 674 public boolean hasPrimitiveValue() { 675 //return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name); 676 return super.hasPrimitiveValue(); 677 } 678 679 @Override 680 public boolean canHavePrimitiveValue() { 681 return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name); 682 } 683 684 685 @Override 686 public String primitiveValue() { 687 if (isPrimitive() || value != null) 688 return value; 689 else if (hasXhtml()) { 690 return new XhtmlComposer(XhtmlComposer.XML, false).setCanonical(false).compose(xhtml); 691 } else { 692 if (canHavePrimitiveValue() && children != null) { 693 for (Element c : children) { 694 if (c.getName().equals("value")) 695 return c.primitiveValue(); 696 } 697 } 698 return null; 699 } 700 } 701 702 // for the validator 703 public int line() { 704 return line; 705 } 706 707 public int col() { 708 return col; 709 } 710 711 public Element markLocation(int line, int col) { 712 this.line = line; 713 this.col = col; 714 return this; 715 } 716 717 public Element markLocation(SourceLocation loc) { 718 this.line = loc.getLine(); 719 this.col = loc.getColumn(); 720 return this; 721 } 722 723 public Element markLocation(Element src) { 724 this.line = src.line(); 725 this.col = src.col(); 726 return this; 727 } 728 729 public void clearDecorations() { 730 clearUserData(UserDataNames.rendering_xml_decorations); 731 for (Element e : children) { 732 e.clearDecorations(); 733 } 734 } 735 736 public void markValidation(StructureDefinition profile, ElementDefinition definition) { 737 @SuppressWarnings("unchecked") 738 List<ElementDecoration> decorations = (List<ElementDecoration>) getUserData(UserDataNames.rendering_xml_decorations); 739 if (decorations == null) { 740 decorations = new ArrayList<>(); 741 setUserData(UserDataNames.rendering_xml_decorations, decorations); 742 } 743 decorations.add(new ElementDecoration(DecorationType.TYPE, profile.getWebPath(), definition.getPath())); 744 if (definition.getId() != null && tail(definition.getId()).contains(":")) { 745 String[] details = tail(definition.getId()).split(":"); 746 decorations.add(new ElementDecoration(DecorationType.SLICE, null, details[1])); 747 } 748 } 749 750 private String tail(String id) { 751 return id.contains(".") ? id.substring(id.lastIndexOf(".")+1) : id; 752 } 753 754 public Element getNamedChild(String name) { 755 return getNamedChild(name, true); 756 } 757 758 public Element getNamedChild(String name, boolean exception) { 759 if (children == null) 760 return null; 761 if (children.size() > 20) { 762 List<Element> l = children.getByName(name); 763 if (l == null || l.size() == 0) { 764 // try the other way (in case of complicated naming rules) 765 } else if (l.size() > 1 && exception) { 766 throw new Error("Attempt to read a single element when there is more than one present ("+name+")"); 767 } else { 768 return l.get(0); 769 } 770 } else { 771 772 } 773 Element result = null; 774 775 for (Element child : children) { 776 if (child.getName() != null && name != null && child.getProperty() != null && child.getProperty().getDefinition() != null && child.fhirType() != null) { 777 if (child.getName().equals(name) || (child.getName().length() > child.fhirType().length() && child.getName().substring(0, child.getName().length() - child.fhirType().length()).equals(name) && child.getProperty().getDefinition().isChoice())) { 778 if (result == null) 779 result = child; 780 else 781 throw new Error("Attempt to read a single element when there is more than one present ("+name+")"); 782 } 783 } 784 } 785 return result; 786 } 787 788 public Element getNamedChildSingle(String name, boolean exception) { 789 if (children == null) 790 return null; 791 if (children.size() > 20) { 792 List<Element> l = children.getByName(name); 793 if (l == null || l.size() == 0) { 794 // try the other way (in case of complicated naming rules) 795 } else if (l.size() > 1 && exception) { 796 throw new Error("Attempt to read a single element when there is more than one present ("+name+")"); 797 } else { 798 return l.get(0); 799 } 800 } 801 for (Element child : children) { 802 if (child.getName() != null && name != null && child.getProperty() != null && child.getProperty().getDefinition() != null && child.fhirType() != null) { 803 if (child.getName().equals(name) || (child.getName().length() > child.fhirType().length() && child.getName().substring(0, child.getName().length() - child.fhirType().length()).equals(name) && child.getProperty().getDefinition().isChoice())) { 804 return child; 805 } 806 } 807 } 808 return null; 809 } 810 811 public void getNamedChildren(String name, List<Element> list) { 812 if (children != null) 813 if (children.size() > 20) { 814 List<Element> l = children.getByName(name); 815 if (l != null) { 816 list.addAll(l); 817 } 818 } else { 819 for (Element child : children) 820 if (child.getName().equals(name)) 821 list.add(child); 822 } 823 } 824 825 public String getNamedChildValue(String... names) { 826 for (String name : names) { 827 String result = getNamedChildValue(name, true); 828 if (result != null) { 829 return result; 830 } 831 } 832 return null; 833 } 834 835 public String getNamedChildValue(String name, boolean exception) { 836 Element child = getNamedChild(name, exception); 837 return child == null ? null : child.value; 838 } 839 840 public String getNamedChildValueSingle(String... names) { 841 for (String name : names) { 842 String result = getNamedChildValueSingle(name, false); 843 if (result != null) { 844 return result; 845 } 846 } 847 return null; 848 } 849 850 public String getNamedChildValueSingle(String name, boolean exception) { 851 Element child = getNamedChildSingle(name, exception); 852 return child == null ? null : child.value; 853 } 854 855 public void getNamedChildrenWithWildcard(String string, List<Element> values) { 856 Validate.isTrue(string.endsWith("[x]")); 857 858 String start = string.substring(0, string.length() - 3); 859 if (children != null) { 860 for (Element child : children) { 861 if (child.getName().startsWith(start)) { 862 values.add(child); 863 } 864 } 865 } 866 } 867 868 869 public XhtmlNode getXhtml() { 870 return xhtml; 871 } 872 873 public Element setXhtml(XhtmlNode xhtml) { 874 this.xhtml = xhtml; 875 value = new XhtmlComposer(true, false).compose(xhtml); 876 return this; 877 } 878 879 @Override 880 public boolean isEmpty() { 881 // GG: this used to also test !"".equals(value). 882 // the condition where "" is empty and there are no children is an error, and so this really only manifested as an issue in corner cases technical testing of the validator / FHIRPath. 883 // it should not cause any problems in real life. 884 if (value != null) { 885 return false; 886 } 887 for (Element next : getChildren()) { 888 if (!next.isEmpty()) { 889 return false; 890 } 891 } 892 return true; 893 } 894 895 public Property getElementProperty() { 896 return elementProperty; 897 } 898 899 public boolean hasElementProperty() { 900 return elementProperty != null; 901 } 902 903 public boolean hasChild(String name) { 904 return getNamedChild(name, true) != null; 905 } 906 907 public boolean hasChild(String name, boolean exception) { 908 return getNamedChild(name, exception) != null; 909 } 910 911 public boolean canHaveChild(String name) { 912 for (Property p : property.getChildProperties(this.name, type)) { 913 if (name.equals(p.getName())) { 914 return true; 915 } 916 } 917 return false; 918 } 919 920 921 922 public boolean hasChildren(String name) { 923 if (children != null) 924 for (Element child : children) 925 if (child.getName().equals(name)) 926 return true; 927 return false; 928 } 929 930 @Override 931 public String toString() { 932 if (name.equals(fhirType()) && isResource()) { 933 return fhirType()+"/"+getIdBase() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 934 935 } else if (isResource()) { 936 return name+"="+fhirType()+"/"+getIdBase()+ "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 937 } else { 938 return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]"; 939 } 940 } 941 942 @Override 943 public String getIdBase() { 944 return getChildValue("id"); 945 } 946 947 @Override 948 public void setIdBase(String value) { 949 setChildValue("id", value); 950 } 951 952 953 @Override 954 public boolean equalsDeep(Base other) { 955 if (!super.equalsDeep(other)) 956 return false; 957 if (isPrimitive() && primitiveValue() != null && other.isPrimitive()) 958 return primitiveValue().equals(other.primitiveValue()); 959 if (isPrimitive() || other.isPrimitive()) 960 return false; 961 Set<String> processed = new HashSet<String>(); 962 for (org.hl7.fhir.r5.model.Property p : children()) { 963 String name = p.getName(); 964 processed.add(name); 965 org.hl7.fhir.r5.model.Property o = other.getChildByName(name); 966 if (!equalsDeep(p, o)) 967 return false; 968 } 969 for (org.hl7.fhir.r5.model.Property p : children()) { 970 String name = p.getName(); 971 if (!processed.contains(name)) { 972 org.hl7.fhir.r5.model.Property o = other.getChildByName(name); 973 if (!equalsDeep(p, o)) 974 return false; 975 } 976 } 977 return true; 978 } 979 980 private boolean equalsDeep(org.hl7.fhir.r5.model.Property p, org.hl7.fhir.r5.model.Property o) { 981 if (o == null || p == null) 982 return false; 983 if (p.getValues().size() != o.getValues().size()) 984 return false; 985 for (int i = 0; i < p.getValues().size(); i++) 986 if (!Base.compareDeep(p.getValues().get(i), o.getValues().get(i), true)) 987 return false; 988 return true; 989 } 990 991 @Override 992 public boolean equalsShallow(Base other) { 993 if (!super.equalsShallow(other)) 994 return false; 995 if (isPrimitive() && other.isPrimitive()) 996 return primitiveValue().equals(other.primitiveValue()); 997 if (isPrimitive() || other.isPrimitive()) 998 return false; 999 return true; //? 1000 } 1001 1002 public DataType asType() throws FHIRException { 1003 return new ObjectConverter(property.getContext()).convertToType(this); 1004 } 1005 1006 @Override 1007 public boolean isMetadataBased() { 1008 return true; 1009 } 1010 1011 public boolean isList() { 1012 if (elementProperty != null) 1013 return elementProperty.isList(); 1014 else 1015 return property.isList(); 1016 } 1017 1018 public boolean isBaseList() { 1019 if (elementProperty != null) 1020 return elementProperty.isBaseList(); 1021 else 1022 return property.isBaseList(); 1023 } 1024 1025 @Override 1026 public String[] getTypesForProperty(int hash, String name) throws FHIRException { 1027 Property p = property.getChildSimpleName(this.name, name); 1028 if (p != null) { 1029 Set<String> types = new HashSet<String>(); 1030 for (TypeRefComponent tr : p.getDefinition().getType()) { 1031 types.add(tr.getCode()); 1032 } 1033 return types.toArray(new String[]{}); 1034 } 1035 return super.getTypesForProperty(hash, name); 1036 1037 } 1038 1039 public void sort() { 1040 if (children != null) { 1041 List<Element> remove = new ArrayList<Element>(); 1042 for (Element child : children) { 1043 child.sort(); 1044 if (child.isEmpty()) 1045 remove.add(child); 1046 } 1047 children.removeAll(remove); 1048 children.sort(new ElementSortComparator(this, this.property)); 1049 } 1050 } 1051 1052 public class ElementSortComparator implements Comparator<Element> { 1053 private List<ElementDefinition> children; 1054 public ElementSortComparator(Element e, Property property) { 1055 String tn = e.getType(); 1056 StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tn, null)); 1057 if (sd != null && !sd.getAbstract()) 1058 children = sd.getSnapshot().getElement(); 1059 else 1060 children = property.getStructure().getSnapshot().getElement(); 1061 } 1062 1063 @Override 1064 public int compare(Element e0, Element e1) { 1065 int i0 = find(e0); 1066 int i1 = find(e1); 1067 return Integer.compare(i0, i1); 1068 } 1069 private int find(Element e0) { 1070 int i = e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) : children.indexOf(e0.property.getDefinition()); 1071 return i; 1072 } 1073 1074 } 1075 1076 public class ICodingImpl implements ICoding { 1077 private String system; 1078 private String version; 1079 private String code; 1080 private String display; 1081 private boolean doesSystem; 1082 private boolean doesVersion; 1083 private boolean doesCode; 1084 private boolean doesDisplay; 1085 public ICodingImpl(boolean doesCode, boolean doesSystem, boolean doesVersion, boolean doesDisplay) { 1086 super(); 1087 this.doesCode = doesCode; 1088 this.doesSystem = doesSystem; 1089 this.doesVersion = doesVersion; 1090 this.doesDisplay = doesDisplay; 1091 } 1092 public String getSystem() { 1093 return system; 1094 } 1095 public String getVersion() { 1096 return version; 1097 } 1098 public String getCode() { 1099 return code; 1100 } 1101 public String getDisplay() { 1102 return display; 1103 } 1104 public boolean hasSystem() { 1105 return !Utilities.noString(system); 1106 } 1107 public boolean hasVersion() { 1108 return !Utilities.noString(version); 1109 } 1110 public boolean hasCode() { 1111 return !Utilities.noString(code); 1112 } 1113 public boolean hasDisplay() { 1114 return !Utilities.noString(display); 1115 } 1116 public boolean supportsSystem() { 1117 return doesSystem; 1118 } 1119 public boolean supportsVersion() { 1120 return doesVersion; 1121 } 1122 public boolean supportsCode() { 1123 return doesCode; 1124 } 1125 public boolean supportsDisplay() { 1126 return doesDisplay; 1127 } 1128 } 1129 1130 public ICoding getAsICoding() throws FHIRException { 1131 if ("code".equals(fhirType())) { 1132 if (property.getDefinition().getBinding().getStrength() != BindingStrength.REQUIRED) 1133 return null; 1134 ICodingImpl c = new ICodingImpl(true, true, false, false); 1135 c.code = primitiveValue(); 1136 ValueSetExpansionOutcome vse = property.getContext().expandVS(property.getStructure(), property.getDefinition().getBinding(), true, false); 1137 if (vse.getValueset() == null) 1138 return null; 1139 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 1140 if (cc.getCode().equals(c.code)) { 1141 c.system = cc.getSystem(); 1142 if (cc.hasVersion()) { 1143 c.doesVersion = true; 1144 c.version = cc.getVersion(); 1145 } 1146 if (cc.hasDisplay()) { 1147 c.doesDisplay = true; 1148 c.display = cc.getDisplay(); 1149 } 1150 } 1151 } 1152 if (c.system == null) 1153 return null; 1154 return c; 1155 } else if ("Coding".equals(fhirType())) { 1156 ICodingImpl c = new ICodingImpl(true, true, true, true); 1157 c.system = getNamedChildValue("system", false); 1158 c.code = getNamedChildValue("code", false); 1159 c.display = getNamedChildValue("display", false); 1160 c.version = getNamedChildValue("version", false); 1161 return c; 1162 } else if ("Quantity".equals(fhirType())) { 1163 ICodingImpl c = new ICodingImpl(true, true, false, false); 1164 c.system = getNamedChildValue("system", false); 1165 c.code = getNamedChildValue("code", false); 1166 return c; 1167 } else 1168 return null; 1169 } 1170 1171 public String getExplicitType() { 1172 return explicitType; 1173 } 1174 1175 public void setExplicitType(String explicitType) { 1176 this.explicitType = explicitType; 1177 } 1178 1179 public boolean hasDescendant(Element element) { 1180 if (children != null) { 1181 for (Element child : children) { 1182 if (element == child || child.hasDescendant(element)) { 1183 return true; 1184 } 1185 } 1186 } 1187 return false; 1188 } 1189 1190 public Element getExtension(String url) { 1191 if (children != null) { 1192 for (Element child : children) { 1193 if (extensionList.contains(child.getName())) { 1194 String u = child.getChildValue("url"); 1195 if (url.equals(u)) { 1196 return child; 1197 } 1198 } 1199 } 1200 } 1201 return null; 1202 } 1203 1204 public Element getExtension(String... urls) { 1205 for (String url : urls) { 1206 Element e = getExtension(url); 1207 if (e != null) { 1208 return e; 1209 } 1210 } 1211 return null; 1212 } 1213 1214 public String getExtensionString(String url) { 1215 if (children != null) { 1216 for (Element child : children) { 1217 if (extensionList.contains(child.getName())) { 1218 String u = child.getChildValue("url"); 1219 if (url.equals(u)) { 1220 return child.getNamedChildValue("value"); 1221 } 1222 } 1223 } 1224 } 1225 return null; 1226 } 1227 1228 public List<Element> getExtensions(String url) { 1229 List<Element> list = new ArrayList<>(); 1230 if (children != null) { 1231 for (Element child : children) { 1232 if (extensionList.contains(child.getName())) { 1233 String u = child.getChildValue("url"); 1234 if (url.equals(u)) { 1235 list.add(child); 1236 } 1237 } 1238 } 1239 } 1240 return list; 1241 } 1242 1243 public Base getExtensionValue(String... url) { 1244 if (children != null) { 1245 for (Element child : children) { 1246 if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) { 1247 String u = child.getChildValue("url"); 1248 if (Utilities.existsInList(u, url)) { 1249 return child.getNamedChild("value", false); 1250 } 1251 } 1252 } 1253 } 1254 return null; 1255 } 1256 1257 public boolean hasExtension(String... url) { 1258 if (children != null) { 1259 for (Element child : children) { 1260 if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) { 1261 String u = child.getChildValue("url"); 1262 if (Utilities.existsInList(u, url)) { 1263 return true; 1264 } 1265 } 1266 } 1267 } 1268 return false; 1269 } 1270 1271 /** 1272 * this is set by the instance validator. There's no reason to maintain this when working with an element tree, and so it should be ignored outside the validator 1273 */ 1274 public Element getParentForValidator() { 1275 if (!hasParentForValidator) { 1276 throw new Error("Parent not set"); 1277 } 1278 return parentForValidator; 1279 } 1280 1281 public void setParentForValidator(Element parentForValidator) { 1282 this.parentForValidator = parentForValidator; 1283 this.hasParentForValidator = true; 1284 } 1285 1286 public boolean hasParentForValidator() { 1287 return hasParentForValidator; 1288 } 1289 1290 public void clear() { 1291 comments = null; 1292 children.clear(); 1293 property = null; 1294 elementProperty = null; 1295 xhtml = null; 1296 path = null; 1297 } 1298 1299 public String getPath() { 1300 return path; 1301 } 1302 1303 public void setPath(String path) { 1304 this.path = path; 1305 } 1306 1307 public void addMessage(ValidationMessage vm) { 1308 if (messages == null) { 1309 messages = new ArrayList<>(); 1310 } 1311 messages.add(vm); 1312 } 1313 1314 public boolean hasMessages() { 1315 return messages != null && !messages.isEmpty(); 1316 } 1317 1318 public List<ValidationMessage> getMessages() { 1319 return messages; 1320 } 1321 1322 public void removeChild(String name) { 1323 if (children.removeIf(n -> name.equals(n.getName()))) { 1324 children.clearMap(); 1325 } 1326 } 1327 1328 public void removeChild(Element child) { 1329 if (children.removeIf(n -> n == child)) { 1330 children.clearMap(); 1331 } 1332 } 1333 1334 public boolean isProhibited() { 1335 return prohibited; 1336 } 1337 1338 public void setProhibited(boolean prohibited) { 1339 this.prohibited = prohibited; 1340 } 1341 1342 public boolean isRequired() { 1343 return required; 1344 } 1345 1346 public void setRequired(boolean required) { 1347 this.required = required; 1348 } 1349 1350 public int getDescendentCount() { 1351 return descendentCount; 1352 } 1353 1354 public void setDescendentCount(int descendentCount) { 1355 this.descendentCount = descendentCount; 1356 } 1357 1358 public int countDescendents() { 1359 if (descendentCount > 0) { 1360 return descendentCount; 1361 } else if (children != null) { 1362 descendentCount = children.size(); 1363 for (Element e : children) { 1364 descendentCount = descendentCount + e.countDescendents(); 1365 } 1366 } else { 1367 descendentCount = 0; 1368 } 1369 return descendentCount; 1370 } 1371 1372 public int getInstanceId() { 1373 return instanceId; 1374 } 1375 1376 public void setInstanceId(int instanceId) { 1377 this.instanceId = instanceId; 1378 } 1379 1380 1381 @Override 1382 public boolean hasValidationInfo() { 1383 return hasSource() ? source.hasValidationInfo() : super.hasValidationInfo(); 1384 } 1385 1386 @Override 1387 public List<ValidationInfo> getValidationInfo() { 1388 return hasSource() ? source.getValidationInfo() : super.getValidationInfo(); 1389 } 1390 1391 @Override 1392 public ValidationInfo addDefinition(StructureDefinition source, ElementDefinition defn, ValidationMode mode) { 1393 if (this.source != null) { 1394 return this.source.addDefinition(source, defn, mode); 1395 } else { 1396 return super.addDefinition(source, defn, mode); 1397 } 1398 } 1399 1400 public boolean hasSource() { 1401 return source != null; 1402 } 1403 1404 1405 public Base getSource() { 1406 return source; 1407 } 1408 1409 public void setSource(Base source) { 1410 this.source = source; 1411 } 1412 1413 @SuppressWarnings("checkstyle:systemout") 1414 public void printToOutput() { 1415 printToOutput(System.out, ""); 1416 } 1417 1418 public void printToOutput(PrintStream stream) { 1419 printToOutput(stream, ""); 1420 1421 } 1422 1423 private void printToOutput(PrintStream out, String indent) { 1424 String s = indent+name +(index == -1 ? "" : "["+index+"]") +(special != null ? "$"+special.toHuman(): "")+ (type!= null || explicitType != null ? " : "+type+(explicitType != null ? "/'"+explicitType+"'" : "") : ""); 1425 if (isNull) { 1426 s = s + " = (null)"; 1427 } else if (value != null) { 1428 s = s + " = '"+value+"'"; 1429 } else if (xhtml != null) { 1430 s = s + " = (xhtml)"; 1431 } 1432 if (property != null) { 1433 s = s +" {"+property.summary(); 1434 if (elementProperty != null) { 1435 s = s +" -> "+elementProperty.summary(); 1436 } 1437 s = s + "}"; 1438 } 1439 if (line > 0) { 1440 s = s + " (l"+line+":c"+col+")"; 1441 } 1442 out.println(s); 1443 if (children != null) { 1444 for (Element child : children) { 1445 child.printToOutput(out, indent+" "); 1446 } 1447 } 1448 1449 } 1450 1451 private String msgCounts() { 1452 int e = 0; 1453 int w = 0; 1454 int h = 0; 1455 for (ValidationMessage msg : messages) { 1456 switch (msg.getLevel()) { 1457 case ERROR: 1458 e++; 1459 break; 1460 case FATAL: 1461 e++; 1462 break; 1463 case INFORMATION: 1464 h++; 1465 break; 1466 case NULL: 1467 break; 1468 case WARNING: 1469 w++; 1470 break; 1471 default: 1472 break; 1473 } 1474 } 1475 return "e:"+e+",w:"+w+",h:"+h; 1476 } 1477 1478 public void populatePaths(String path) { 1479 if (path == null) { 1480 path = fhirType(); 1481 } 1482 setPath(path); 1483 if (children != null) { 1484 for (Element n : children) { 1485 n.populatePaths(path+"."+n.getName()); 1486 } 1487 } 1488 1489 } 1490 1491 public String fhirTypeRoot() { 1492 if (fhirType().contains("/")) { 1493 return fhirType().substring(fhirType().lastIndexOf("/")+1); 1494 } else { 1495 return fhirType(); 1496 } 1497 } 1498 1499 public void setElement(String string, Element map) { 1500 throw new Error("Not done yet"); 1501 } 1502 1503 public Element addElement(String name) { 1504 if (children == null) 1505 children = new NamedItemList<Element>(); 1506 int insertionPoint = 0; 1507 1508 for (Property p : property.getChildProperties(this.name, type)) { 1509 while (insertionPoint < children.size() && nameMatches(children.get(insertionPoint).getName(), p.getName())) { 1510 insertionPoint++; 1511 } 1512 if (p.getName().equals(name)) { 1513 if (!p.isList() && hasChild(name, false)) { 1514 throw new Error(name+" on "+this.name+" is not a list, so can't add an element"); 1515 } 1516 Element ne = new Element(name, p).setFormat(format); 1517 children.add(insertionPoint, ne); 1518 return ne; 1519 } 1520 // polymorphic support 1521 if (p.getName().endsWith("[x]")) { 1522 String base = p.getName().substring(0, p.getName().length()-3); 1523 1524 if (name.startsWith(base)) { 1525 String type = name.substring(base.length()); 1526 if (p.getContextUtils().isPrimitiveType(Utilities.uncapitalize(type))) { 1527 type = Utilities.uncapitalize(type); 1528 } 1529 if (p.canBeType(type)) { 1530 Element ne = new Element(name, p).setFormat(format); 1531 ne.setType(type); 1532 children.add(insertionPoint, ne); 1533 return ne; 1534 } 1535 } 1536 } 1537 } 1538 1539 throw new Error("Unrecognised property '"+name+"' on "+this.name); 1540 } 1541 1542 private boolean nameMatches(String elementName, String propertyName) { 1543 if (propertyName.endsWith("[x]")) { 1544 String base = propertyName.replace("[x]", ""); 1545 return elementName.startsWith(base); 1546 } else { 1547 return elementName.equals(propertyName); 1548 } 1549 } 1550 1551 @Override 1552 public Base copy() { 1553 Element element = new Element(this); 1554 this.copyValues(element); 1555 if (this.isElided()) 1556 element.setElided(true); 1557 return element; 1558 } 1559 1560 @Override 1561 public void copyValues(Base dst) { 1562 super.copyValues(dst); 1563 1564 Element dest = (Element) dst; 1565 if (comments != null) { 1566 dest.comments = new ArrayList<>(); 1567 dest.comments.addAll(comments); 1568 } else { 1569 dest.comments = null; 1570 } 1571 dest.value = value; 1572 if (children != null) { 1573 dest.children = new NamedItemList<>(); 1574 for (Element child : children) { 1575 dest.children.add((Element) child.copy()); 1576 } 1577 } else { 1578 dest.children = null; 1579 } 1580 dest.line = line; 1581 dest.col = col; 1582 dest.xhtml = xhtml; 1583 dest.explicitType = explicitType; 1584 dest.hasParentForValidator = false; 1585 dest.path = path; 1586 dest.messages = null; 1587 dest.prohibited = prohibited; 1588 dest.required = required; 1589 dest.descendentCount = descendentCount; 1590 dest.instanceId = instanceId; 1591 dest.isNull = isNull; 1592 dest.source = source; 1593 dest.format = format; 1594 } 1595 1596 public Base setProperty(String name, Base value) throws FHIRException { 1597 setChildValue(name, value.primitiveValue()); 1598 return this; 1599 } 1600 1601 public boolean isIgnorePropertyOrder() { 1602 return ignorePropertyOrder; 1603 } 1604 1605 public void setIgnorePropertyOrder(boolean ignorePropertyOrder) { 1606 this.ignorePropertyOrder = ignorePropertyOrder; 1607 if (children != null) { 1608 for (Element e : children) { 1609 e.setIgnorePropertyOrder(ignorePropertyOrder); 1610 } 1611 } 1612 } 1613 1614 1615 private String webPath; 1616 public boolean hasWebPath() { 1617 return webPath != null; 1618 } 1619 public String getWebPath() { 1620 return webPath; 1621 } 1622 public void setWebPath(String webPath) { 1623 this.webPath = webPath; 1624 } 1625 1626 public String getTranslation(String lang) { 1627 for (Element e : getChildren()) { 1628 if (e.fhirType().equals("Extension")) { 1629 String url = e.getNamedChildValue("url", false); 1630 if (ExtensionDefinitions.EXT_TRANSLATION.equals(url)) { 1631 String l = null; 1632 String v = null; 1633 for (Element g : e.getChildren()) { 1634 if (g.fhirType().equals("Extension")) { 1635 String u = g.getNamedChildValue("url", false); 1636 if ("lang".equals(u)) { 1637 l = g.getNamedChildValue("value", false); 1638 } else if ("value".equals(u)) { 1639 v = g.getNamedChildValue("value", false); 1640 } 1641 } 1642 } 1643 if (LanguageUtils.langsMatch(lang, l)) { 1644 return v; 1645 } 1646 } 1647 } 1648 } 1649 return null; 1650 } 1651 1652 public String getBasePath() { 1653 if (property.getStructure().hasExtension(ExtensionDefinitions.EXT_RESOURCE_IMPLEMENTS)) { 1654 StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ExtensionUtilities.readStringExtension(property.getStructure(), ExtensionDefinitions.EXT_RESOURCE_IMPLEMENTS)); 1655 if (sd != null) { 1656 ElementDefinition ed = sd.getSnapshot().getElementByPath(property.getDefinition().getPath().replace(property.getStructure().getType(), sd.getType())); 1657 if (ed != null) { 1658 return ed.getBase().getPath(); 1659 } 1660 } 1661 } 1662 return property.getDefinition().getBase().getPath(); 1663 } 1664 1665 public void setTranslation(String lang, String translation) { 1666 for (Element e : getChildren()) { 1667 if (e.fhirType().equals("Extension")) { 1668 String url = e.getNamedChildValue("url", false); 1669 if (ExtensionDefinitions.EXT_TRANSLATION.equals(url)) { 1670 String l = null; 1671 Element v = null; 1672 for (Element g : e.getChildren()) { 1673 if (g.fhirType().equals("Extension")) { 1674 String u = g.getNamedChildValue("url", false); 1675 if ("lang".equals(u)) { 1676 l = g.getNamedChildValue("value", false); 1677 } else if ("value".equals(u)) { 1678 v = g.getNamedChild("value", false); 1679 } 1680 } 1681 } 1682 if (LanguageUtils.langsMatch(lang, l)) { 1683 if (v == null) { 1684 Element ext = e.addElement("extension"); 1685 ext.addElement("url").setValue("value"); 1686 ext.addElement("valueString").setValue(translation); 1687 } else { 1688 v.setValue(translation); 1689 } 1690 } 1691 } 1692 } 1693 } 1694 Element t = addElement("extension"); 1695 t.addElement("url").setValue(ExtensionDefinitions.EXT_TRANSLATION); 1696 1697 Element ext = t.addElement("extension"); 1698 ext.addElement("url").setValue("lang"); 1699 ext.addElement("valueCode").setValue(lang); 1700 1701 ext = t.addElement("extension"); 1702 ext.addElement("url").setValue("content"); 1703 ext.addElement("valueString").setValue(translation); 1704 } 1705 1706 @Override 1707 public String getListName() { 1708 if (getProperty().getName().endsWith("[x]")) { 1709 String n = getProperty().getName(); 1710 return n.substring(0, n.length()-3); 1711 } else { 1712 return getName(); 1713 } 1714 } 1715 1716 public FhirFormat getFormat() { 1717 return format; 1718 } 1719 1720 public Element setFormat(FhirFormat format) { 1721 this.format = format; 1722 return this; 1723 } 1724 1725 public Object getNativeObject() { 1726 return nativeObject; 1727 } 1728 1729 public Element setNativeObject(Object nativeObject) { 1730 this.nativeObject = nativeObject; 1731 return this; 1732 } 1733 1734 public void removeExtension(String url) { 1735 if (children != null) { 1736 List<Element> rem = new ArrayList<>(); 1737 for (Element e : children) { 1738 if ("extension".equals(e.getName()) && url.equals(e.getChildValue("url"))) { 1739 rem.add(e); 1740 } 1741 } 1742 children.removeAll(rem); 1743 } 1744 } 1745 1746 public void addSliceDefinition(StructureDefinition profile, ElementDefinition definition, ElementDefinition slice) { 1747 if (sliceDefinitions == null) { 1748 sliceDefinitions = new ArrayList<>(); 1749 } 1750 sliceDefinitions.add(new SliceDefinition(profile, definition, slice)); 1751 } 1752 1753 public boolean hasSlice(StructureDefinition sd, String sliceName) { 1754 if (sliceDefinitions != null) { 1755 for (SliceDefinition def : sliceDefinitions) { 1756 if (def.profile == sd && sliceName.equals(def.definition.getSliceName())) { 1757 return true; 1758 } 1759 } 1760 } 1761 return false; 1762 } 1763 1764 public FhirPublication getFHIRPublicationVersion() { 1765 return FhirPublication.fromCode(property.getStructure().getVersion()); 1766 } 1767 1768 public void setElided(boolean elided) { 1769 this.elided = elided; 1770 } 1771 1772 public boolean isElided() { 1773 return this.elided; 1774 } 1775 1776 public void stripLocations() { 1777 line = -1; 1778 col = -1; 1779 if (children != null) { 1780 for (Element child : children) { 1781 child.stripLocations(); 1782 } 1783 } 1784 } 1785 1786 public void sortChildren(Comparator<Element> sorter) { 1787 children.sort(sorter); 1788 } 1789 1790 public String getStatedResourceId() { 1791 for (Property p : getProperty().getChildProperties(null)) { 1792 if (ExtensionUtilities.readBoolExtension(p.getDefinition(), ExtensionDefinitions.EXT_USE_AS_RESOURCE_ID)) { 1793 return getNamedChildValue(p.getName()); 1794 } 1795 } 1796 return null; 1797 } 1798 1799 public boolean hasXhtml() { 1800 return xhtml != null; 1801 } 1802 1803 public boolean isElementForPath(String... paths) { 1804 for (String s : paths) { 1805 if (s.equals(property.getDefinition().getBase().getPath())) { 1806 return true; 1807 } 1808 } 1809 return false; 1810 } 1811 1812}