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