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