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