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