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