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