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