001package org.hl7.fhir.r5.fhirpath;
002
003import java.math.BigDecimal;
004import java.math.RoundingMode;
005import java.util.ArrayList;
006import java.util.Arrays;
007import java.util.Base64;
008import java.util.Calendar;
009import java.util.Date;
010import java.util.EnumSet;
011import java.util.HashMap;
012import java.util.HashSet;
013import java.util.List;
014import java.util.Map;
015import java.util.Set;
016import java.util.regex.Matcher;
017import java.util.regex.Pattern;
018
019import org.fhir.ucum.Decimal;
020import org.fhir.ucum.Pair;
021import org.fhir.ucum.UcumException;
022import org.hl7.fhir.exceptions.DefinitionException;
023import org.hl7.fhir.exceptions.FHIRException;
024import org.hl7.fhir.exceptions.PathEngineException;
025import org.hl7.fhir.instance.model.api.IIdType;
026import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
027import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions;
028import org.hl7.fhir.r5.context.ContextUtilities;
029import org.hl7.fhir.r5.context.IWorkerContext;
030import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus;
031import org.hl7.fhir.r5.fhirpath.ExpressionNode.Function;
032import org.hl7.fhir.r5.fhirpath.ExpressionNode.Kind;
033import org.hl7.fhir.r5.fhirpath.ExpressionNode.Operation;
034import org.hl7.fhir.r5.fhirpath.FHIRLexer.FHIRLexerException;
035import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.ClassTypeInfo;
036import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FHIRConstant;
037import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
038import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.TypedElementDefinition;
039import org.hl7.fhir.r5.fhirpath.TypeDetails.ProfiledType;
040import org.hl7.fhir.r5.model.Base;
041import org.hl7.fhir.r5.model.BaseDateTimeType;
042import org.hl7.fhir.r5.model.BooleanType;
043import org.hl7.fhir.r5.model.CanonicalType;
044import org.hl7.fhir.r5.model.CodeType;
045import org.hl7.fhir.r5.model.CodeableConcept;
046import org.hl7.fhir.r5.model.Constants;
047import org.hl7.fhir.r5.model.DateTimeType;
048import org.hl7.fhir.r5.model.DateType;
049import org.hl7.fhir.r5.model.DecimalType;
050import org.hl7.fhir.r5.model.Element;
051import org.hl7.fhir.r5.model.ElementDefinition;
052import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
053import org.hl7.fhir.r5.model.Identifier;
054import org.hl7.fhir.r5.model.IntegerType;
055import org.hl7.fhir.r5.model.Property;
056import org.hl7.fhir.r5.model.Property.PropertyMatcher;
057import org.hl7.fhir.r5.model.Quantity;
058import org.hl7.fhir.r5.model.Resource;
059import org.hl7.fhir.r5.model.StringType;
060import org.hl7.fhir.r5.model.StructureDefinition;
061import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
062import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
063import org.hl7.fhir.r5.model.TimeType;
064import org.hl7.fhir.r5.model.TypeConvertor;
065import org.hl7.fhir.r5.model.ValueSet;
066import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
067import org.hl7.fhir.utilities.*;
068import org.hl7.fhir.utilities.MergedList.MergeNode;
069import org.hl7.fhir.utilities.i18n.I18nConstants;
070import org.hl7.fhir.utilities.validation.ValidationOptions;
071import org.hl7.fhir.utilities.xhtml.NodeType;
072import org.hl7.fhir.utilities.xhtml.XhtmlNode;
073
074import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
075import ca.uhn.fhir.util.ElementUtil;
076
077/*
078  Copyright (c) 2011+, HL7, Inc.
079  All rights reserved.
080
081  Redistribution and use in source and binary forms, with or without modification, 
082  are permitted provided that the following conditions are met:
083
084 * Redistributions of source code must retain the above copyright notice, this 
085     list of conditions and the following disclaimer.
086 * Redistributions in binary form must reproduce the above copyright notice, 
087     this list of conditions and the following disclaimer in the documentation 
088     and/or other materials provided with the distribution.
089 * Neither the name of HL7 nor the names of its contributors may be used to 
090     endorse or promote products derived from this software without specific 
091     prior written permission.
092
093  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
094  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
095  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
096  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
097  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
098  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
099  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
100  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
101  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
102  POSSIBILITY OF SUCH DAMAGE.
103
104 */
105
106
107/**
108 * 
109 * @author Grahame Grieve
110 *
111 */
112public class FHIRPathEngine {
113
114  public class ExtensionDefinition {
115
116    private boolean root;
117    private StructureDefinition sd;
118    private ElementDefinition ed;
119
120    public ExtensionDefinition(boolean root, StructureDefinition sd, ElementDefinition ed) {
121      super();
122      this.root = root;
123      this.sd = sd;
124      this.ed = ed;
125    }
126
127    public boolean isRoot() {
128      return root;
129    }
130
131    public StructureDefinition getSd() {
132      return sd;
133    }
134
135    public ElementDefinition getEd() {
136      return ed;
137    }
138
139  }
140
141  public class IssueMessage {
142
143    private String message;
144    private String id;
145    
146    public IssueMessage(String message, String id) {
147      this.message = message;
148      this.id = id;
149    }
150
151    public String getMessage() {
152      return message;
153    }
154
155    public String getId() {
156      return id;
157    }
158
159  }
160
161  private enum Equality { Null, True, False }
162  
163  private IWorkerContext worker;
164  private IEvaluationContext hostServices;
165  private StringBuilder log = new StringBuilder();
166  private Set<String> primitiveTypes = new HashSet<String>();
167  private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>();
168  private boolean legacyMode; // some R2 and R3 constraints assume that != is valid for empty sets, so when running for R2/R3, this is set ot true  
169  private ValidationOptions terminologyServiceOptions = new ValidationOptions(FhirPublication.R5);
170  private ProfileUtilities profileUtilities;
171  private String location; // for error messages
172  private boolean allowPolymorphicNames;
173  private boolean doImplicitStringConversion;
174  private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host
175  private boolean doNotEnforceAsSingletonRule;
176  private boolean doNotEnforceAsCaseSensitive;
177  private boolean allowDoubleQuotes;
178  private List<IssueMessage> typeWarnings = new ArrayList<>();
179  private boolean emitSQLonFHIRWarning;
180
181  // if the fhir path expressions are allowed to use constants beyond those defined in the specification
182  // the application can implement them by providing a constant resolver 
183  public interface IEvaluationContext {
184
185    public abstract class FunctionDefinition {
186      public abstract String name();
187      public abstract FunctionDetails details();
188      public abstract TypeDetails check(FHIRPathEngine engine, Object appContext, TypeDetails focus, List<TypeDetails> parameters);
189      public abstract List<Base> execute(FHIRPathEngine engine, Object appContext, List<Base> focus, List<List<Base>> parameters);
190    }
191    
192    /**
193     * A constant reference - e.g. a reference to a name that must be resolved in context.
194     * The % will be removed from the constant name before this is invoked.
195     * Variables created with defineVariable will not be processed by resolveConstant (or resolveConstantType)
196     * 
197     * This will also be called if the host invokes the FluentPath engine with a context of null
198     *  
199     * @param appContext - content passed into the fluent path engine
200     * @param name - name reference to resolve
201     * @param beforeContext - whether this is being called before the name is resolved locally, or not
202     * @return the value of the reference (or null, if it's not valid, though can throw an exception if desired)
203     */
204    public List<Base> resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant)  throws PathEngineException;
205    public TypeDetails resolveConstantType(FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) throws PathEngineException;
206
207    /**
208     * when the .log() function is called
209     * 
210     * @param argument
211     * @param focus
212     * @return
213     */
214    public boolean log(String argument, List<Base> focus);
215
216    // extensibility for functions
217    /**
218     * 
219     * @param functionName
220     * @return null if the function is not known
221     */
222    public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName);
223
224    /**
225     * Check the function parameters, and throw an error if they are incorrect, or return the type for the function
226     * @param functionName
227     * @param parameters
228     * @return
229     */
230    public TypeDetails checkFunction(FHIRPathEngine engine, Object appContext, String functionName, TypeDetails focus, List<TypeDetails> parameters) throws PathEngineException;
231
232    /**
233     * @param appContext
234     * @param functionName
235     * @param parameters
236     * @return
237     */
238    public List<Base> executeFunction(FHIRPathEngine engine, Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters);
239
240    /**
241     * Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null
242     * @appContext - passed in by the host to the FHIRPathEngine
243     * @param url the reference (Reference.reference or the value of the canonical
244     * @return
245     * @throws FHIRException 
246     */
247    public Base resolveReference(FHIRPathEngine engine, Object appContext, String url, Base refContext) throws FHIRException;
248
249    public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) throws FHIRException;
250
251    /* 
252     * return the value set referenced by the url, which has been used in memberOf()
253     */
254    public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url);
255    
256    /**
257     * For the moment, there can only be one parameter if it's a type parameter 
258     * @param name
259     * @return true if it's a type parameter 
260     */
261    public boolean paramIsType(String name, int index);
262  }
263
264  /**
265   * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined)
266   */
267  public FHIRPathEngine(IWorkerContext worker) {
268    this(worker, new ProfileUtilities(worker, null, null));
269  }
270
271  public FHIRPathEngine(IWorkerContext worker, ProfileUtilities utilities) {
272    super();
273    this.worker = worker;
274    profileUtilities = utilities; 
275    for (StructureDefinition sd : worker.fetchResourcesByType(StructureDefinition.class)) {
276      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) {
277        allTypes.put(sd.getName(), sd);
278      }
279      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 
280        primitiveTypes.add(sd.getName());
281      }
282    }
283    initFlags();
284    cu = new ContextUtilities(worker);
285  }
286
287  private void initFlags() {
288    if (!VersionUtilities.isR5VerOrLater(worker.getVersion())) {
289      doNotEnforceAsCaseSensitive = true;
290      doNotEnforceAsSingletonRule = true;
291    }
292  }
293
294  // --- 3 methods to override in children -------------------------------------------------------
295  // if you don't override, it falls through to the using the base reference implementation 
296  // HAPI overrides to these to support extending the base model
297
298  public IEvaluationContext getHostServices() {
299    return hostServices;
300  }
301
302
303  public void setHostServices(IEvaluationContext constantResolver) {
304    this.hostServices = constantResolver;
305  }
306
307  public String getLocation() {
308    return location;
309  }
310
311
312  public void setLocation(String location) {
313    this.location = location;
314  }
315
316
317  /**
318   * Given an item, return all the children that conform to the pattern described in name
319   * 
320   * Possible patterns:
321   *  - a simple name (which may be the base of a name with [] e.g. value[x])
322   *  - a name with a type replacement e.g. valueCodeableConcept
323   *  - * which means all children
324   *  - ** which means all descendants
325   *  
326   * @param item
327   * @param name
328   * @param result
329   * @throws FHIRException 
330   */
331  protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException {
332    String tn = null;
333    if (isAllowPolymorphicNames()) {
334      // we'll look to see whether we have a polymorphic name 
335      for (Property p : item.children()) {
336        if (p.getName().endsWith("[x]")) {
337          String n = p.getName().substring(0, p.getName().length()-3);
338          if (name.startsWith(n)) {
339            tn = name.substring(n.length());
340            name = n;
341            break;            
342          }
343        }
344      }
345    }
346    Base[] list = item.listChildrenByName(name, false);
347    if (list != null) {
348      for (Base v : list) {
349        if (v != null && (tn == null || v.fhirType().equalsIgnoreCase(tn))) {
350          result.add(filterIdType(v));
351        }
352      }
353    }
354  }
355
356  private Base filterIdType(Base v) {
357    if (v instanceof IIdType) {
358      return (Base) ((IIdType) v).toUnqualifiedVersionless().withResourceType(null);
359    }
360    return v;
361  }
362  public boolean isLegacyMode() {
363    return legacyMode;
364  }
365
366
367  public void setLegacyMode(boolean legacyMode) {
368    this.legacyMode = legacyMode;
369  }
370
371
372  public boolean isDoImplicitStringConversion() {
373    return doImplicitStringConversion;
374  }
375
376  public void setDoImplicitStringConversion(boolean doImplicitStringConversion) {
377    this.doImplicitStringConversion = doImplicitStringConversion;
378  }
379
380  public boolean isDoNotEnforceAsSingletonRule() {
381    return doNotEnforceAsSingletonRule;
382  }
383
384  public void setDoNotEnforceAsSingletonRule(boolean doNotEnforceAsSingletonRule) {
385    this.doNotEnforceAsSingletonRule = doNotEnforceAsSingletonRule;
386  }
387
388  public boolean isDoNotEnforceAsCaseSensitive() {
389    return doNotEnforceAsCaseSensitive;
390  }
391
392  public void setDoNotEnforceAsCaseSensitive(boolean doNotEnforceAsCaseSensitive) {
393    this.doNotEnforceAsCaseSensitive = doNotEnforceAsCaseSensitive;
394  }
395
396  // --- public API -------------------------------------------------------
397  /**
398   * Parse a path for later use using execute
399   * 
400   * @param path
401   * @return
402   * @throws PathEngineException 
403   * @throws Exception
404   */
405  public ExpressionNode parse(String path) throws FHIRLexerException {
406    return parse(path, null);
407  }
408
409  public ExpressionNode parse(String path, String name) throws FHIRLexerException {
410    FHIRLexer lexer = new FHIRLexer(path, name, false, allowDoubleQuotes);
411    if (lexer.done()) {
412      throw lexer.error("Path cannot be empty");
413    }
414    ExpressionNode result = parseExpression(lexer, true);
415    if (!lexer.done()) {
416      throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\"");
417    }
418    result.check();
419    return result;    
420  }
421
422  public static class ExpressionNodeWithOffset {
423    private int offset;
424    private ExpressionNode node;
425    public ExpressionNodeWithOffset(int offset, ExpressionNode node) {
426      super();
427      this.offset = offset;
428      this.node = node;
429    }
430    public int getOffset() {
431      return offset;
432    }
433    public ExpressionNode getNode() {
434      return node;
435    }
436
437  }
438  /**
439   * Parse a path for later use using execute
440   * 
441   * @param path
442   * @return
443   * @throws PathEngineException 
444   * @throws Exception
445   */
446  public ExpressionNodeWithOffset parsePartial(String path, int i) throws FHIRLexerException {
447    FHIRLexer lexer = new FHIRLexer(path, i, allowDoubleQuotes);
448    if (lexer.done()) {
449      throw lexer.error("Path cannot be empty");
450    }
451    ExpressionNode result = parseExpression(lexer, true);
452    result.check();
453    return new ExpressionNodeWithOffset(lexer.getCurrentStart(), result);    
454  }
455
456  /**
457   * Parse a path that is part of some other syntax
458   *  
459   * @return
460   * @throws PathEngineException 
461   * @throws Exception
462   */
463  public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException {
464    ExpressionNode result = parseExpression(lexer, true);
465    result.check();
466    return result;    
467  }
468
469  /**
470   * check that paths referred to in the ExpressionNode are valid
471   * 
472   * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath
473   * 
474   * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context
475   * 
476   * @param context - the logical type against which this path is applied
477   * @throws DefinitionException
478   * @throws PathEngineException 
479   * @if the path is not valid
480   */
481  public TypeDetails check(Object appContext, String rootResourceType, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
482    return check(appContext, rootResourceType, resourceType, context, expr, null);
483  }
484
485  /**
486   * check that paths referred to in the ExpressionNode are valid
487   * 
488   * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath
489   * 
490   * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context
491   * 
492   * @param context - the logical type against which this path is applied
493   * @throws DefinitionException
494   * @throws PathEngineException 
495   * @if the path is not valid
496   */
497  public TypeDetails check(Object appContext, String rootResourceType, String resourceType, String context, ExpressionNode expr, Set<ElementDefinition> elementDependencies) throws FHIRLexerException, PathEngineException, DefinitionException {
498
499    // if context is a path that refers to a type, do that conversion now 
500    TypeDetails types; 
501    if (context == null) {
502      types = null; // this is a special case; the first path reference will have to resolve to something in the context
503    } else if (!context.contains(".")) {
504      StructureDefinition sd = worker.fetchTypeDefinition(context);
505      if (sd == null) {
506        throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, context);        
507      }
508      types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl());
509    } else {
510      String ctxt = context.substring(0, context.indexOf('.'));
511      if (Utilities.isAbsoluteUrl(resourceType)) {
512        ctxt = resourceType; //.substring(0, resourceType.lastIndexOf("/")+1)+ctxt;
513      }
514      StructureDefinition sd = cu.findType(ctxt);
515      if (sd == null) {
516        throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, context);
517      }
518      List<ElementDefinitionMatch> edl = getElementDefinition(sd, context, true, expr);
519      if (edl.size() == 0) {
520        throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, context);
521      }
522      if (edl.size() > 1) {
523        throw new Error("Not handled here yet");
524      }
525      ElementDefinitionMatch ed = edl.get(0);
526      if (ed.fixedType != null) { 
527        types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType);
528      } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { 
529        types = new TypeDetails(CollectionStatus.SINGLETON, ctxt+"#"+context);
530      } else {
531        types = new TypeDetails(CollectionStatus.SINGLETON);
532        for (TypeRefComponent t : ed.getDefinition().getType()) { 
533          types.addType(t.getCode());
534        }
535      }
536    }
537
538    return executeType(new ExecutionTypeContext(appContext, rootResourceType, resourceType, types, types), types, expr, elementDependencies, true, false, expr);
539  }
540  
541  /**
542   * check that paths referred to in the ExpressionNode are valid
543   * 
544   * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath
545   * 
546   * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context
547   * 
548   * @param context - the logical type against which this path is applied
549   * @throws DefinitionException
550   * @throws PathEngineException 
551   * @if the path is not valid
552   */
553  public TypeDetails checkOnTypes(Object appContext, String rootResourceType, String resourceType, List<String> typeList, ExpressionNode expr, List<IssueMessage> warnings) throws FHIRLexerException, PathEngineException, DefinitionException {
554    typeWarnings.clear();
555
556    // if context is a path that refers to a type, do that conversion now 
557    TypeDetails types = new TypeDetails(CollectionStatus.SINGLETON);
558    for (String t : typeList) {
559      if (!t.contains(".")) {
560        StructureDefinition sd = worker.fetchTypeDefinition(t);
561        if (sd == null) {
562          throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, t);        
563        }
564        types.addType(sd.getUrl());
565      } else {
566        boolean checkTypeName = false;
567        String ctxt = null;
568        if (t.contains("#")) {
569          ctxt = t.substring(0, t.indexOf('#'));
570          t = t.substring(t.indexOf('#')+1);
571        } else if (Utilities.isAbsoluteUrl(t)) {
572          ctxt = t;
573          t = ctxt.substring(ctxt.lastIndexOf("/")+1);
574          checkTypeName = true;
575        } else {
576          ctxt = t.substring(0, t.indexOf('.'));
577        }
578        StructureDefinition sd = cu.findType(ctxt);
579        if (sd == null) {
580          throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, t);
581        }
582        String tn = checkTypeName ? sd.getSnapshot().getElementFirstRep().getPath() : t;          
583
584        List<ElementDefinitionMatch> edl = getElementDefinition(sd, tn, true, expr);
585        if (edl.size() == 0) {
586          throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, t);
587        }
588        if (edl.size() > 1) {
589          throw new Error("not handled here either");
590        }
591        ElementDefinitionMatch ed = edl.get(0);
592        if (ed.fixedType != null) { 
593          types.addType(ed.fixedType);
594        } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { 
595          types.addType(sd.getType()+"#"+t);
596        } else {
597          for (TypeRefComponent tt : ed.getDefinition().getType()) { 
598            types.addType(tt.getCode());
599          }
600        }
601      }
602    }
603    TypeDetails res = executeType(new ExecutionTypeContext(appContext, rootResourceType, resourceType, types, types), types, expr, null, true, false, expr);
604    warnings.addAll(typeWarnings);
605    return res;
606  }
607
608  public TypeDetails checkOnTypes(Object appContext, String rootResourceType, String resourceType, TypeDetails types, ExpressionNode expr, List<IssueMessage> warnings) throws FHIRLexerException, PathEngineException, DefinitionException {
609    typeWarnings.clear();
610    TypeDetails res = executeType(new ExecutionTypeContext(appContext, rootResourceType, resourceType, types, types), types, expr, null, true, false, expr);
611    warnings.addAll(typeWarnings);
612    return res;
613  }
614  
615  public TypeDetails checkOnTypes(Object appContext, String rootResourceType, String resourceType, TypeDetails types, ExpressionNode expr, List<IssueMessage> warnings, boolean canBeNone) throws FHIRLexerException, PathEngineException, DefinitionException {
616    typeWarnings.clear();
617    TypeDetails res = executeType(new ExecutionTypeContext(appContext, rootResourceType, resourceType, types, types), types, expr, null, true, canBeNone, expr);
618    warnings.addAll(typeWarnings);
619    return res;
620  }
621  
622  /**
623   * check that paths referred to in the ExpressionNode are valid
624   * 
625   * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath
626   * 
627   * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context
628   * 
629   * @throws DefinitionException
630   * @throws PathEngineException 
631   * @if the path is not valid
632   */
633  public TypeDetails check(Object appContext, String rootResourceType, String resourceType, List<String> resourceTypes, ExpressionNode expr, Set<ElementDefinition> elementDependencies) throws FHIRLexerException, PathEngineException, DefinitionException {
634
635    // if context is a path that refers to a type, do that conversion now 
636    TypeDetails types = null;
637    for (String rt : resourceTypes) {
638      if (types == null) {
639        types = new TypeDetails(CollectionStatus.SINGLETON, rt);
640      } else {
641        types.addType(rt);
642      }
643    }
644
645    return executeType(new ExecutionTypeContext(appContext, rootResourceType, resourceType, types, types), types, expr, elementDependencies, true, false, expr);
646  }
647
648  private FHIRException makeExceptionPlural(Integer num, ExpressionNode holder, String constName, Object... args) {
649    String fmt = worker.formatMessagePlural(num, constName, args);
650    if (location != null) {
651      fmt = fmt + " "+worker.formatMessagePlural(num, I18nConstants.FHIRPATH_LOCATION, location);
652    }
653    if (holder != null) {      
654       return new PathEngineException(fmt, constName, holder.getStart(), holder.toString());
655    } else {
656      return new PathEngineException(fmt, constName);
657    }
658  }
659  
660  private FHIRException makeException(ExpressionNode holder, String constName, Object... args) {
661    String fmt = worker.formatMessage(constName, args);
662    if (location != null) {
663      fmt = fmt + " "+worker.formatMessage(I18nConstants.FHIRPATH_LOCATION, location);
664    }
665    if (holder != null) {      
666       return new PathEngineException(fmt, constName, holder.getStart(), holder.toString());
667    } else {
668      return new PathEngineException(fmt, constName);
669    }
670  }
671
672  public TypeDetails check(Object appContext, String rootResourceType, StructureDefinition sd, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
673    // if context is a path that refers to a type, do that conversion now 
674    TypeDetails types; 
675    if (!context.contains(".")) {
676      types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl());
677    } else {
678      List<ElementDefinitionMatch> edl = getElementDefinition(sd, context, true, expr);
679      if (edl.size() == 0) {
680        throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, context);
681      }
682      if (edl.size() > 1) {
683        throw new Error("Unhandled case?");
684      }
685      ElementDefinitionMatch ed = edl.get(0);
686      if (ed.fixedType != null) { 
687        types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType);
688      } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { 
689        types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()+"#"+context);
690      } else {
691        types = new TypeDetails(CollectionStatus.SINGLETON);
692        for (TypeRefComponent t : ed.getDefinition().getType()) { 
693          types.addType(t.getCode());
694        }
695      }
696    }
697
698    return executeType(new ExecutionTypeContext(appContext, rootResourceType, sd.getUrl(), types, types), types, expr, null, true, false, expr);
699  }
700
701  public TypeDetails check(Object appContext, String rootResourceType, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
702    // if context is a path that refers to a type, do that conversion now 
703    TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context
704    return executeType(new ExecutionTypeContext(appContext, rootResourceType, sd == null ? null : sd.getUrl(), null, types), types, expr, null, true, false, expr);
705  }
706
707  public TypeDetails check(Object appContext, String rootResourceType, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException {
708    return check(appContext, rootResourceType, resourceType, context, parse(expr));
709  }
710
711  private Integer compareDateTimeElements(Base theL, Base theR, boolean theEquivalenceTest) {
712    DateTimeType left = theL instanceof DateTimeType ? (DateTimeType) theL : new DateTimeType(theL.primitiveValue()); 
713    DateTimeType right = theR instanceof DateTimeType ? (DateTimeType) theR : new DateTimeType(theR.primitiveValue()); 
714
715    if (theEquivalenceTest) {
716      return left.equalsUsingFhirPathRules(right) == Boolean.TRUE ? 0 : 1;
717    }
718
719    if (left.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) {
720      left.setTimeZoneZulu(true);
721    }
722    if (right.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) {
723      right.setTimeZoneZulu(true);
724    }
725    return BaseDateTimeType.compareTimes(left, right, null);
726  }
727
728  private Integer compareTimeElements(Base theL, Base theR, boolean theEquivalenceTest) {
729    TimeType left = theL instanceof TimeType ? (TimeType) theL : new TimeType(theL.primitiveValue()); 
730    TimeType right = theR instanceof TimeType ? (TimeType) theR : new TimeType(theR.primitiveValue()); 
731
732    if (left.getHour() < right.getHour()) {
733      return -1;
734    } else if (left.getHour() > right.getHour()) {
735      return 1;
736      // hour is not a valid precision 
737      //    } else if (dateLeft.getPrecision() == TemporalPrecisionEnum.YEAR && dateRight.getPrecision() == TemporalPrecisionEnum.YEAR) {
738      //      return 0;
739      //    } else if (dateLeft.getPrecision() == TemporalPrecisionEnum.HOUR || dateRight.getPrecision() == TemporalPrecisionEnum.HOUR) {
740      //      return null;
741    }
742
743    if (left.getMinute() < right.getMinute()) {
744      return -1;
745    } else if (left.getMinute() > right.getMinute()) {
746      return 1;
747    } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE && right.getPrecision() == TemporalPrecisionEnum.MINUTE) {
748      return 0;
749    } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE || right.getPrecision() == TemporalPrecisionEnum.MINUTE) {
750      return null;
751    }
752
753    if (left.getSecond() < right.getSecond()) {
754      return -1;
755    } else if (left.getSecond() > right.getSecond()) {
756      return 1;
757    } else {
758      return 0;
759    }
760
761  }
762
763
764  /**
765   * evaluate a path and return the matching elements
766   * 
767   * @param base - the object against which the path is being evaluated
768   * @param ExpressionNode - the parsed ExpressionNode statement to use
769   * @return
770   * @throws FHIRException 
771   * @
772   */
773  public List<Base> evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException {
774    List<Base> list = new ArrayList<Base>();
775    if (base != null) {
776      list.add(base);
777    }
778    log = new StringBuilder();
779    return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true);
780  }
781
782
783  /**
784   * evaluate a path and return the matching elements
785   * 
786   * @param base - the object against which the path is being evaluated
787   * @param ExpressionNode - the parsed ExpressionNode statement to use
788   * @return
789   * @throws FHIRException 
790   * @
791   */
792  public List<Base> evaluate(Object appContext, Base base, ExpressionNode ExpressionNode) throws FHIRException {
793    List<Base> list = new ArrayList<Base>();
794    if (base != null) {
795      list.add(base);
796    }
797    log = new StringBuilder();
798    return execute(new ExecutionContext(appContext, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true);
799  }
800  
801  /**
802   * evaluate a path and return the matching elements
803   * 
804   * @param base - the object against which the path is being evaluated
805   * @param path - the FHIR Path statement to use
806   * @return
807   * @throws FHIRException 
808   * @
809   */
810  public List<Base> evaluate(Base base, String path) throws FHIRException {
811    ExpressionNode exp = parse(path);
812    List<Base> list = new ArrayList<Base>();
813    if (base != null) {
814      list.add(base);
815    }
816    log = new StringBuilder();
817    return execute(new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, base), list, exp, true);
818  }
819
820  /**
821   * evaluate a path and return the matching elements
822   * 
823   * @param base - the object against which the path is being evaluated
824   * @param ExpressionNode - the parsed ExpressionNode statement to use
825   * @return
826   * @throws FHIRException 
827   * @
828   */
829  public List<Base> evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, ExpressionNode ExpressionNode) throws FHIRException {
830    List<Base> list = new ArrayList<Base>();
831    if (base != null) {
832      list.add(base);
833    }
834    log = new StringBuilder();
835    return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, ExpressionNode, true);
836  }
837
838  /**
839   * evaluate a path and return the matching elements
840   * 
841   * @param base - the object against which the path is being evaluated
842   * @param expressionNode - the parsed ExpressionNode statement to use
843   * @return
844   * @throws FHIRException 
845   * @
846   */
847  public List<Base> evaluate(Object appContext, Base focusResource, Base rootResource, Base base, ExpressionNode expressionNode) throws FHIRException {
848    List<Base> list = new ArrayList<Base>();
849    if (base != null) {
850      list.add(base);
851    }
852    log = new StringBuilder();
853    return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, expressionNode, true);
854  }
855
856  /**
857   * evaluate a path and return the matching elements
858   * 
859   * @param base - the object against which the path is being evaluated
860   * @param path - the FHIR Path statement to use
861   * @return
862   * @throws FHIRException 
863   * @
864   */
865  public List<Base> evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, String path) throws FHIRException {
866    ExpressionNode exp = parse(path);
867    List<Base> list = new ArrayList<Base>();
868    if (base != null) {
869      list.add(base);
870    }
871    log = new StringBuilder();
872    return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, exp, true);
873  }
874
875  /**
876   * evaluate a path and return true or false (e.g. for an invariant)
877   * 
878   * @param base - the object against which the path is being evaluated
879   * @param path - the FHIR Path statement to use
880   * @return
881   * @throws FHIRException 
882   * @
883   */
884  public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, String path) throws FHIRException {
885    return convertToBoolean(evaluate(null, focusResource, rootResource, base, path));
886  }
887
888  /**
889   * evaluate a path and return true or false (e.g. for an invariant)
890   * 
891   * @param base - the object against which the path is being evaluated
892   * @return
893   * @throws FHIRException 
894   * @
895   */
896  public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, ExpressionNode node) throws FHIRException {
897    return convertToBoolean(evaluate(null, focusResource, rootResource, base, node));
898  }
899
900  /**
901   * evaluate a path and return true or false (e.g. for an invariant)
902   * 
903   * @param appInfo - application context
904   * @param base - the object against which the path is being evaluated
905   * @return
906   * @throws FHIRException 
907   * @
908   */
909  public boolean evaluateToBoolean(Object appInfo, Resource focusResource, Resource rootResource, Base base, ExpressionNode node) throws FHIRException {
910    return convertToBoolean(evaluate(appInfo, focusResource, rootResource, base, node));
911  }
912
913  /**
914   * evaluate a path and return true or false (e.g. for an invariant)
915   * 
916   * @param base - the object against which the path is being evaluated
917   * @return
918   * @throws FHIRException 
919   * @
920   */
921  public boolean evaluateToBoolean(Object appInfo, Base focusResource, Base rootResource, Base base, ExpressionNode node) throws FHIRException {
922    return convertToBoolean(evaluate(appInfo, focusResource, rootResource, base, node));
923  }
924
925  /**
926   * evaluate a path and a string containing the outcome (for display)
927   * 
928   * @param base - the object against which the path is being evaluated
929   * @param path - the FHIR Path statement to use
930   * @return
931   * @throws FHIRException 
932   * @
933   */
934  public String evaluateToString(Base base, String path) throws FHIRException {
935    return convertToString(evaluate(base, path));
936  }
937
938  public String evaluateToString(Object appInfo, Base focusResource, Base rootResource, Base base, ExpressionNode node) throws FHIRException {
939    return convertToString(evaluate(appInfo, focusResource, rootResource, base, node));
940  }
941
942  /**
943   * worker routine for converting a set of objects to a string representation
944   * 
945   * @param items - result from @evaluate
946   * @return
947   */
948  public String convertToString(List<Base> items) {
949    StringBuilder b = new StringBuilder();
950    boolean first = true;
951    for (Base item : items) {
952      if (first)  {
953        first = false;
954      } else {
955        b.append(',');
956      }
957
958      b.append(convertToString(item));
959    }
960    return b.toString();
961  }
962
963  public String convertToString(Base item) {
964    if (item instanceof IIdType) {
965      return ((IIdType)item).getIdPart();
966    } else if (item.isPrimitive()) {
967      return item.primitiveValue();
968    } else if (item instanceof Quantity) {
969      Quantity q = (Quantity) item;
970      if (q.hasUnit() && Utilities.existsInList(q.getUnit(), "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")
971          && (!q.hasSystem() || q.getSystem().equals("http://unitsofmeasure.org"))) {
972        return q.getValue().toPlainString()+" "+q.getUnit();
973      }
974      if (q.getSystem().equals("http://unitsofmeasure.org")) {
975        String u = "'"+q.getCode()+"'";
976        return q.getValue().toPlainString()+" "+u;
977      } else {
978        return item.toString();
979      }
980    } else
981      return item.toString();
982  }
983
984  /**
985   * worker routine for converting a set of objects to a boolean representation (for invariants)
986   * 
987   * @param items - result from @evaluate
988   * @return
989   */
990  public boolean convertToBoolean(List<Base> items) {
991    if (items == null) {
992      return false;
993    } else if (items.size() == 1 && items.get(0) instanceof BooleanType) {
994      return ((BooleanType) items.get(0)).getValue();
995    } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) { // element model
996      return Boolean.valueOf(items.get(0).primitiveValue());
997    } else { 
998      return items.size() > 0;
999    }
1000  }
1001
1002
1003  private void log(String name, List<Base> contents) {
1004    if (hostServices == null || !hostServices.log(name, contents)) {
1005      if (log.length() > 0) {
1006        log.append("; ");
1007      }
1008      log.append(name);
1009      log.append(": ");
1010      boolean first = true;
1011      for (Base b : contents) {
1012        if (first) {
1013          first = false;
1014        } else {
1015          log.append(",");
1016        }
1017        log.append(convertToString(b));
1018      }
1019    }
1020  }
1021
1022  public String forLog() {
1023    if (log.length() > 0) {
1024      return " ("+log.toString()+")";
1025    } else {
1026      return "";
1027    }
1028  }
1029
1030  private class ExecutionContext {
1031    private Object appInfo;
1032    private Base focusResource;
1033    private Base rootResource;
1034    private Base context;
1035    private Base thisItem;
1036    private List<Base> total;
1037    private int index;
1038    private Map<String, List<Base>> definedVariables;
1039
1040    public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Base thisItem) {
1041      this.appInfo = appInfo;
1042      this.context = context;
1043      this.focusResource = resource; 
1044      this.rootResource = rootResource; 
1045      this.thisItem = thisItem;
1046      this.index = 0;
1047    }
1048    public Base getFocusResource() {
1049      return focusResource;
1050    }
1051    public Base getRootResource() {
1052      return rootResource;
1053    }
1054    public Base getThisItem() {
1055      return thisItem;
1056    }
1057    public List<Base> getTotal() {
1058      return total;
1059    }
1060
1061    public void next() {
1062      index++;
1063    }
1064    public Base getIndex() {
1065      return new IntegerType(index);
1066    }
1067
1068    public ExecutionContext setIndex(int i) {
1069      index = i;
1070      return this;
1071    }
1072
1073    public boolean hasDefinedVariable(String name) {
1074      return definedVariables != null && definedVariables.containsKey(name);
1075    }
1076
1077    public List<Base> getDefinedVariable(String name) {
1078      return definedVariables == null ? makeNull() : definedVariables.get(name);
1079    }
1080
1081    public void setDefinedVariable(String name, List<Base> value) {
1082      if (isSystemVariable(name))
1083        throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_REDEFINE_VARIABLE, name), I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
1084
1085      if (definedVariables == null) {
1086        definedVariables = new HashMap<String, List<Base>>();
1087      } else {
1088        if (definedVariables.containsKey(name)) {
1089          // Can't do this, so throw an error
1090          throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_REDEFINE_VARIABLE, name), I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
1091        }
1092      }
1093
1094      definedVariables.put(name, value);
1095    }
1096  }
1097
1098  private static class ExecutionTypeContext {
1099    private Object appInfo; 
1100    private String resource;
1101    private TypeDetails context;
1102    private TypeDetails thisItem;
1103    private TypeDetails total;
1104    private Map<String, TypeDetails> definedVariables;
1105    private String rootResource;
1106
1107    public ExecutionTypeContext(Object appInfo, String rootResource, String resource, TypeDetails context, TypeDetails thisItem) {
1108      super();
1109      this.appInfo = appInfo;
1110      this.rootResource = rootResource;
1111      this.resource = resource;
1112      this.context = context;
1113      this.thisItem = thisItem;
1114
1115    }
1116    public String getResource() {
1117      return resource;
1118    }
1119    public TypeDetails getThisItem() {
1120      return thisItem;
1121    }
1122
1123    public boolean hasDefinedVariable(String name) {
1124      return definedVariables != null && definedVariables.containsKey(name);
1125    }
1126
1127    public TypeDetails getDefinedVariable(String name) {
1128      return definedVariables == null ? null : definedVariables.get(name);
1129    }
1130
1131    public void setDefinedVariable(String name, TypeDetails value) {
1132      if (isSystemVariable(name))
1133        throw new PathEngineException("Redefine of variable "+name, I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
1134
1135      if (definedVariables == null) {
1136        definedVariables = new HashMap<String, TypeDetails>();
1137      } else {
1138        if (definedVariables.containsKey(name)) {
1139          // Can't do this, so throw an error
1140          throw new PathEngineException("Redefine of variable "+name, I18nConstants.FHIRPATH_REDEFINE_VARIABLE);
1141        }
1142      }
1143
1144      definedVariables.put(name, value);
1145    }
1146  }
1147
1148  private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException {
1149    ExpressionNode result = new ExpressionNode(lexer.nextId());
1150    ExpressionNode wrapper = null;
1151    SourceLocation c = lexer.getCurrentStartLocation();
1152    result.setStart(lexer.getCurrentLocation());
1153    // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true.
1154    // so we back correct for both +/- and as part of a numeric constant below.
1155
1156    // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true.
1157    // so we back correct for both +/- and as part of a numeric constant below.
1158    if (Utilities.existsInList(lexer.getCurrent(), "-", "+")) {
1159      wrapper = new ExpressionNode(lexer.nextId());
1160      wrapper.setKind(Kind.Unary);
1161      wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.take()));
1162      wrapper.setStart(lexer.getCurrentLocation());
1163      wrapper.setProximal(proximal);
1164    }
1165
1166    if (lexer.getCurrent() == null) {
1167      throw lexer.error("Expression terminated unexpectedly");
1168    } else if (lexer.isConstant()) {
1169      boolean isString = lexer.isStringConstant();
1170      if (!isString && (lexer.getCurrent().startsWith("-") || lexer.getCurrent().startsWith("+"))) {
1171        // the grammar says that this is a unary operation; it affects the correct processing order of the inner operations
1172        wrapper = new ExpressionNode(lexer.nextId());
1173        wrapper.setKind(Kind.Unary);
1174        wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent().substring(0, 1)));
1175        wrapper.setProximal(proximal);
1176        wrapper.setStart(lexer.getCurrentLocation());
1177        lexer.setCurrent(lexer.getCurrent().substring(1));
1178      }
1179      result.setConstant(processConstant(lexer));
1180      result.setKind(Kind.Constant);
1181      if (!isString && !lexer.done() && (result.getConstant() instanceof IntegerType || result.getConstant() instanceof DecimalType) && (lexer.isStringConstant() || lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds"))) {
1182        // it's a quantity
1183        String ucum = null;
1184        String unit = null;
1185        if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) {
1186          String s = lexer.take();
1187          unit = s;
1188          if (s.equals("year") || s.equals("years")) {
1189            // this is not the UCUM year
1190          } else if (s.equals("month") || s.equals("months")) {
1191            // this is not the UCUM month
1192          } else if (s.equals("week") || s.equals("weeks")) {
1193            ucum = "wk";
1194          } else if (s.equals("day") || s.equals("days")) {
1195            ucum = "d";
1196          } else if (s.equals("hour") || s.equals("hours")) {
1197            ucum = "h";
1198          } else if (s.equals("minute") || s.equals("minutes")) {
1199            ucum = "min";
1200          } else if (s.equals("second") || s.equals("seconds")) {
1201            ucum = "s";
1202          } else { // (s.equals("millisecond") || s.equals("milliseconds"))
1203            ucum = "ms";
1204          } 
1205        } else {
1206          ucum = lexer.readConstant("units");
1207        }
1208        result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setUnit(unit).setSystem(ucum == null ? null : "http://unitsofmeasure.org").setCode(ucum));
1209      }
1210      result.setEnd(lexer.getCurrentLocation());
1211    } else if ("(".equals(lexer.getCurrent())) {
1212      lexer.next();
1213      result.setKind(Kind.Group);
1214      result.setGroup(parseExpression(lexer, true));
1215      if (!")".equals(lexer.getCurrent())) {
1216        throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\"");
1217      }
1218      result.setEnd(lexer.getCurrentLocation());
1219      lexer.next();
1220    } else {
1221      if (!lexer.isToken() && !lexer.getCurrent().startsWith("`")) {
1222        throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name");
1223      }
1224      if (lexer.isFixedName()) {
1225        result.setName(lexer.readFixedName("Path Name"));
1226      } else {
1227        result.setName(lexer.take());
1228      }
1229      result.setEnd(lexer.getCurrentLocation());
1230      if (!result.checkName()) {
1231        throw lexer.error("Found "+result.getName()+" expecting a valid token name");
1232      }
1233      if ("(".equals(lexer.getCurrent())) {
1234        Function f = Function.fromCode(result.getName());
1235        FunctionDetails details = null;
1236        if (f == null) {
1237          if (hostServices != null) {
1238            details = hostServices.resolveFunction(this, result.getName());
1239          }
1240          if (details == null) {
1241            throw lexer.error("The name "+result.getName()+" is not a valid function name");
1242          }
1243          f = Function.Custom;
1244        }
1245        result.setKind(Kind.Function);
1246        result.setFunction(f);
1247        lexer.next();
1248        while (!")".equals(lexer.getCurrent())) { 
1249          result.getParameters().add(parseExpression(lexer, true));
1250          if (",".equals(lexer.getCurrent())) {
1251            lexer.next();
1252          } else if (!")".equals(lexer.getCurrent())) {
1253            throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected");
1254          }
1255        }
1256        result.setEnd(lexer.getCurrentLocation());
1257        lexer.next();
1258        checkParameters(lexer, c, result, details);
1259      } else {
1260        result.setKind(Kind.Name);
1261      }
1262    }
1263    ExpressionNode focus = result;
1264    if ("[".equals(lexer.getCurrent())) {
1265      lexer.next();
1266      ExpressionNode item = new ExpressionNode(lexer.nextId());
1267      item.setKind(Kind.Function);
1268      item.setFunction(ExpressionNode.Function.Item);
1269      item.getParameters().add(parseExpression(lexer, true));
1270      if (!lexer.getCurrent().equals("]")) {
1271        throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected");
1272      }
1273      lexer.next();
1274      result.setInner(item);
1275      focus = item;
1276    }
1277    if (".".equals(lexer.getCurrent())) {
1278      lexer.next();
1279      focus.setInner(parseExpression(lexer, false));
1280    }
1281    result.setProximal(proximal);
1282    if (proximal) {
1283      while (lexer.isOp()) {
1284        focus.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent()));
1285        focus.setOpStart(lexer.getCurrentStartLocation());
1286        focus.setOpEnd(lexer.getCurrentLocation());
1287        lexer.next();
1288        focus.setOpNext(parseExpression(lexer, false));
1289        focus = focus.getOpNext();
1290      }
1291      result = organisePrecedence(lexer, result);
1292    }
1293    if (wrapper != null) {
1294      wrapper.setOpNext(result);
1295      result.setProximal(false);
1296      result = wrapper;
1297    }
1298    return result;
1299  }
1300
1301  private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) {
1302    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); 
1303    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); 
1304    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); 
1305    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThan, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual));
1306    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is));
1307    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent));
1308    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And));
1309    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or));
1310    // last: implies
1311    return node;
1312  }
1313
1314  private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) {
1315    //    work : boolean;
1316    //    focus, node, group : ExpressionNode;
1317
1318    assert(start.isProximal());
1319
1320    // is there anything to do?
1321    boolean work = false;
1322    ExpressionNode focus = start.getOpNext();
1323    if (ops.contains(start.getOperation())) {
1324      while (focus != null && focus.getOperation() != null) {
1325        work = work || !ops.contains(focus.getOperation());
1326        focus = focus.getOpNext();
1327      }
1328    } else {
1329      while (focus != null && focus.getOperation() != null) {
1330        work = work || ops.contains(focus.getOperation());
1331        focus = focus.getOpNext();
1332      }
1333    }  
1334    if (!work) {
1335      return start;
1336    }
1337
1338    // entry point: tricky
1339    ExpressionNode group;
1340    if (ops.contains(start.getOperation())) {
1341      group = newGroup(lexer, start);
1342      group.setProximal(true);
1343      focus = start;
1344      start = group;
1345    } else {
1346      ExpressionNode node = start;
1347
1348      focus = node.getOpNext();
1349      while (!ops.contains(focus.getOperation())) {
1350        node = focus;
1351        focus = focus.getOpNext();
1352      }
1353      group = newGroup(lexer, focus);
1354      node.setOpNext(group);
1355    }
1356
1357    // now, at this point:
1358    //   group is the group we are adding to, it already has a .group property filled out.
1359    //   focus points at the group.group
1360    do {
1361      // run until we find the end of the sequence
1362      while (ops.contains(focus.getOperation())) {
1363        focus = focus.getOpNext();
1364      }
1365      if (focus.getOperation() != null) {
1366        group.setOperation(focus.getOperation());
1367        group.setOpNext(focus.getOpNext());
1368        focus.setOperation(null);
1369        focus.setOpNext(null);
1370        // now look for another sequence, and start it
1371        ExpressionNode node = group;
1372        focus = group.getOpNext();
1373        if (focus != null) { 
1374          while (focus != null && !ops.contains(focus.getOperation())) {
1375            node = focus;
1376            focus = focus.getOpNext();
1377          }
1378          if (focus != null) { // && (focus.Operation in Ops) - must be true 
1379            group = newGroup(lexer, focus);
1380            node.setOpNext(group);
1381          }
1382        }
1383      }
1384    }
1385    while (focus != null && focus.getOperation() != null);
1386    return start;
1387  }
1388
1389
1390  private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) {
1391    ExpressionNode result = new ExpressionNode(lexer.nextId());
1392    result.setKind(Kind.Group);
1393    result.setGroup(next);
1394    result.getGroup().setProximal(true);
1395    return result;
1396  }
1397
1398  private Base processConstant(FHIRLexer lexer) throws FHIRLexerException {
1399    if (lexer.isStringConstant()) {
1400      return new StringType(processConstantString(lexer.take(), lexer)).noExtensions();
1401    } else if (Utilities.isInteger(lexer.getCurrent())) {
1402      return new IntegerType(lexer.take()).noExtensions();
1403    } else if (Utilities.isDecimal(lexer.getCurrent(), false)) {
1404      return new DecimalType(lexer.take()).noExtensions();
1405    } else if (Utilities.existsInList(lexer.getCurrent(), "true", "false")) {
1406      return new BooleanType(lexer.take()).noExtensions();
1407    } else if (lexer.getCurrent().equals("{}")) {
1408      lexer.take();
1409      return null;
1410    } else if (lexer.getCurrent().startsWith("%") || lexer.getCurrent().startsWith("@")) {
1411      return new FHIRConstant(lexer.take());
1412    } else {
1413      throw lexer.error("Invalid Constant "+lexer.getCurrent());
1414    }
1415  }
1416
1417  //  procedure CheckParamCount(c : integer);
1418  //  begin
1419  //    if exp.Parameters.Count <> c then
1420  //      raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset);
1421  //  end;
1422
1423  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException {
1424    if (exp.getParameters().size() != count) {
1425      throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString(), location);
1426    }
1427    return true;
1428  }
1429
1430  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException {
1431    if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) {
1432      throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString(), location);
1433    }
1434    return true;
1435  }
1436
1437  private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException {
1438    switch (exp.getFunction()) {
1439    case Empty: return checkParamCount(lexer, location, exp, 0);
1440    case Not: return checkParamCount(lexer, location, exp, 0);
1441    case Exists: return checkParamCount(lexer, location, exp, 0, 1);
1442    case SubsetOf: return checkParamCount(lexer, location, exp, 1);
1443    case SupersetOf: return checkParamCount(lexer, location, exp, 1);
1444    case IsDistinct: return checkParamCount(lexer, location, exp, 0);
1445    case Distinct: return checkParamCount(lexer, location, exp, 0);
1446    case Count: return checkParamCount(lexer, location, exp, 0);
1447    case Where: return checkParamCount(lexer, location, exp, 1);
1448    case Select: return checkParamCount(lexer, location, exp, 1);
1449    case All: return checkParamCount(lexer, location, exp, 0, 1);
1450    case Repeat: return checkParamCount(lexer, location, exp, 1);
1451    case Aggregate: return checkParamCount(lexer, location, exp, 1, 2);
1452    case Item: return checkParamCount(lexer, location, exp, 1);
1453    case As: return checkParamCount(lexer, location, exp, 1);
1454    case OfType: return checkParamCount(lexer, location, exp, 1);
1455    case Type: return checkParamCount(lexer, location, exp, 0);
1456    case Is: return checkParamCount(lexer, location, exp, 1);
1457    case Single: return checkParamCount(lexer, location, exp, 0);
1458    case First: return checkParamCount(lexer, location, exp, 0);
1459    case Last: return checkParamCount(lexer, location, exp, 0);
1460    case Tail: return checkParamCount(lexer, location, exp, 0);
1461    case Skip: return checkParamCount(lexer, location, exp, 1);
1462    case Take: return checkParamCount(lexer, location, exp, 1);
1463    case Union: return checkParamCount(lexer, location, exp, 1);
1464    case Combine: return checkParamCount(lexer, location, exp, 1);
1465    case Intersect: return checkParamCount(lexer, location, exp, 1);
1466    case Exclude: return checkParamCount(lexer, location, exp, 1);
1467    case Iif: return checkParamCount(lexer, location, exp, 2,3);
1468    case Lower: return checkParamCount(lexer, location, exp, 0);
1469    case Upper: return checkParamCount(lexer, location, exp, 0);
1470    case ToChars: return checkParamCount(lexer, location, exp, 0);
1471    case IndexOf : return checkParamCount(lexer, location, exp, 1);
1472    case Substring: return checkParamCount(lexer, location, exp, 1, 2);
1473    case StartsWith: return checkParamCount(lexer, location, exp, 1);
1474    case EndsWith: return checkParamCount(lexer, location, exp, 1);
1475    case Matches: return checkParamCount(lexer, location, exp, 1);
1476    case MatchesFull: return checkParamCount(lexer, location, exp, 1);
1477    case ReplaceMatches: return checkParamCount(lexer, location, exp, 2);
1478    case Contains: return checkParamCount(lexer, location, exp, 1);
1479    case Replace: return checkParamCount(lexer, location, exp, 2);
1480    case Length: return checkParamCount(lexer, location, exp, 0);
1481    case Children: return checkParamCount(lexer, location, exp, 0);
1482    case Descendants: return checkParamCount(lexer, location, exp, 0);
1483    case MemberOf: return checkParamCount(lexer, location, exp, 1);
1484    case Trace: return checkParamCount(lexer, location, exp, 1, 2);
1485    case DefineVariable: return checkParamCount(lexer, location, exp, 1, 2);
1486    case Check: return checkParamCount(lexer, location, exp, 2);
1487    case Today: return checkParamCount(lexer, location, exp, 0);
1488    case Now: return checkParamCount(lexer, location, exp, 0);
1489    case Resolve: return checkParamCount(lexer, location, exp, 0);
1490    case Extension: return checkParamCount(lexer, location, exp, 1);
1491    case AllFalse: return checkParamCount(lexer, location, exp, 0);
1492    case AnyFalse: return checkParamCount(lexer, location, exp, 0);
1493    case AllTrue: return checkParamCount(lexer, location, exp, 0);
1494    case AnyTrue: return checkParamCount(lexer, location, exp, 0);
1495    case HasValue: return checkParamCount(lexer, location, exp, 0);
1496    case Encode: return checkParamCount(lexer, location, exp, 1);
1497    case Decode: return checkParamCount(lexer, location, exp, 1);
1498    case Escape: return checkParamCount(lexer, location, exp, 1);
1499    case Unescape: return checkParamCount(lexer, location, exp, 1);
1500    case Trim: return checkParamCount(lexer, location, exp, 0);
1501    case Split: return checkParamCount(lexer, location, exp, 1);
1502    case Join: return checkParamCount(lexer, location, exp, 0, 1);    
1503    case HtmlChecks1: return checkParamCount(lexer, location, exp, 0);
1504    case HtmlChecks2: return checkParamCount(lexer, location, exp, 0);
1505    case Comparable: return checkParamCount(lexer, location, exp, 1);
1506    case ToInteger: return checkParamCount(lexer, location, exp, 0);
1507    case ToDecimal: return checkParamCount(lexer, location, exp, 0);
1508    case ToString: return checkParamCount(lexer, location, exp, 0);
1509    case ToQuantity: return checkParamCount(lexer, location, exp, 0);
1510    case ToBoolean: return checkParamCount(lexer, location, exp, 0);
1511    case ToDateTime: return checkParamCount(lexer, location, exp, 0);
1512    case ToTime: return checkParamCount(lexer, location, exp, 0);
1513    case ConvertsToInteger: return checkParamCount(lexer, location, exp, 0);
1514    case ConvertsToDecimal: return checkParamCount(lexer, location, exp, 0);
1515    case ConvertsToString: return checkParamCount(lexer, location, exp, 0);
1516    case ConvertsToQuantity: return checkParamCount(lexer, location, exp, 0);
1517    case ConvertsToBoolean: return checkParamCount(lexer, location, exp, 0);
1518    case ConvertsToDateTime: return checkParamCount(lexer, location, exp, 0);
1519    case ConvertsToDate: return checkParamCount(lexer, location, exp, 0);
1520    case ConvertsToTime: return checkParamCount(lexer, location, exp, 0);
1521    case ConformsTo: return checkParamCount(lexer, location, exp, 1);
1522    case Round: return checkParamCount(lexer, location, exp, 0, 1); 
1523    case Sqrt: return checkParamCount(lexer, location, exp, 0); 
1524    case Abs: return checkParamCount(lexer, location, exp, 0);
1525    case Ceiling:  return checkParamCount(lexer, location, exp, 0);
1526    case Exp:  return checkParamCount(lexer, location, exp, 0);
1527    case Floor:  return checkParamCount(lexer, location, exp, 0);
1528    case Ln:  return checkParamCount(lexer, location, exp, 0);
1529    case Log:  return checkParamCount(lexer, location, exp, 1);
1530    case Power:  return checkParamCount(lexer, location, exp, 1);
1531    case Truncate: return checkParamCount(lexer, location, exp, 0);
1532    case LowBoundary: return checkParamCount(lexer, location, exp, 0, 1);
1533    case HighBoundary: return checkParamCount(lexer, location, exp, 0, 1);
1534    case Precision: return checkParamCount(lexer, location, exp, 0);
1535    case hasTemplateIdOf: return checkParamCount(lexer, location, exp, 1);
1536    case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters());
1537    }
1538    return false;
1539  }
1540
1541  private List<Base> execute(ExecutionContext inContext, List<Base> focus, ExpressionNode exp, boolean atEntry) throws FHIRException {
1542    //    System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString());
1543    ExecutionContext context = contextForParameter(inContext);
1544    List<Base> work = new ArrayList<Base>();
1545    switch (exp.getKind()) {
1546    case Unary:
1547      work.add(new IntegerType(0));
1548      break;
1549    case Name:
1550      if (atEntry && exp.getName().equals("$this")) {
1551        work.add(context.getThisItem());
1552      } else if (atEntry && exp.getName().equals("$total")) {
1553        work.addAll(context.getTotal());
1554      } else if (atEntry && exp.getName().equals("$index")) {
1555        work.add(context.getIndex());
1556      } else {
1557        for (Base item : focus) {
1558          List<Base> outcome = execute(context, item, exp, atEntry);
1559          for (Base base : outcome) {
1560            if (base != null) {
1561              work.add(base);
1562            }
1563          }
1564        }     
1565      }
1566      break;
1567    case Function:
1568      List<Base> work2 = evaluateFunction(context, focus, exp);
1569      work.addAll(work2);
1570      break;
1571    case Constant:
1572      work.addAll(resolveConstant(context, exp.getConstant(), false, exp, true));
1573      break;
1574    case Group:
1575      work2 = execute(context, focus, exp.getGroup(), atEntry);
1576      work.addAll(work2);
1577    }
1578
1579    if (exp.getInner() != null) {
1580      work = execute(context, work, exp.getInner(), false);
1581    }
1582
1583    if (exp.isProximal() && exp.getOperation() != null) {
1584      ExpressionNode next = exp.getOpNext();
1585      ExpressionNode last = exp;
1586      while (next != null) {
1587        context = contextForParameter(inContext);
1588        List<Base> work2 = preOperate(work, last.getOperation(), exp);
1589        if (work2 != null) {
1590          work = work2;
1591        }
1592        else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
1593          work2 = executeTypeName(context, focus, next, false);
1594          work = operate(context, work, last.getOperation(), work2, last);
1595        } else {
1596          work2 = execute(context, focus, next, true);
1597          work = operate(context, work, last.getOperation(), work2, last);
1598          //          System.out.println("Result of {'"+last.toString()+" "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString());
1599        }
1600        last = next;
1601        next = next.getOpNext();
1602      }
1603    }
1604    //    System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString());
1605    return work;
1606  }
1607
1608  private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) {
1609    List<Base> result = new ArrayList<Base>();
1610    if (next.getInner() != null) {
1611      result.add(new StringType(next.getName()+"."+next.getInner().getName()));
1612    } else { 
1613      result.add(new StringType(next.getName()));
1614    }
1615    return result;
1616  }
1617
1618
1619  private List<Base> preOperate(List<Base> left, Operation operation, ExpressionNode expr) throws PathEngineException {
1620    if (left.size() == 0) {
1621      return null;
1622    }
1623    switch (operation) {
1624    case And:
1625      return isBoolean(left, false) ? makeBoolean(false) : null;
1626    case Or:
1627      return isBoolean(left, true) ? makeBoolean(true) : null;
1628    case Implies:
1629      Equality v = asBool(left, expr); 
1630      return v == Equality.False ? makeBoolean(true) : null;
1631    default: 
1632      return null;
1633    }
1634  }
1635
1636  private List<Base> makeBoolean(boolean b) {
1637    List<Base> res = new ArrayList<Base>();
1638    res.add(new BooleanType(b).noExtensions());
1639    return res;
1640  }
1641
1642  private List<Base> makeNull() {
1643    List<Base> res = new ArrayList<Base>();
1644    return res;
1645  }
1646
1647  private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
1648    return new TypeDetails(CollectionStatus.SINGLETON, exp.getName());
1649  }
1650
1651  private TypeDetails executeType(ExecutionTypeContext inContext, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies, boolean atEntry, boolean canBeNone, ExpressionNode container) throws PathEngineException, DefinitionException {
1652    ExecutionTypeContext context = contextForParameter(inContext);
1653    TypeDetails result = new TypeDetails(null);
1654    switch (exp.getKind()) {
1655    case Name:
1656      if (atEntry && exp.getName().equals("$this")) {
1657        result.update(context.getThisItem());
1658      } else if (atEntry && exp.getName().equals("$total")) {
1659        result.update(anything(CollectionStatus.UNORDERED));
1660      } else if (atEntry && exp.getName().equals("$index")) {
1661        result.addType(TypeDetails.FP_Integer);
1662      } else if (atEntry && focus == null) {
1663        result.update(executeContextType(context, exp.getName(), exp, false));
1664      } else {
1665        for (String s : focus.getTypes()) {
1666          result.update(executeType(s, exp, atEntry, focus, elementDependencies));
1667        }
1668        if (result.hasNoTypes()) {
1669          if (!canBeNone) { 
1670            throw makeException(exp, I18nConstants.FHIRPATH_UNKNOWN_NAME, exp.getName(), focus.describe());
1671          } else {
1672            // return result;
1673          }
1674        }
1675      }
1676      doSQLOnFHIRCheck(result, exp);
1677      break;
1678    case Function:
1679      result.update(evaluateFunctionType(context, focus, exp, elementDependencies, container));
1680      break;
1681    case Unary:
1682      result.addType(TypeDetails.FP_Integer);
1683      result.addType(TypeDetails.FP_Decimal);
1684      result.addType(TypeDetails.FP_Quantity);
1685      break;
1686    case Constant:
1687      result.update(resolveConstantType(context, exp.getConstant(), exp, true));
1688      break;
1689    case Group:
1690      result.update(executeType(context, focus, exp.getGroup(), elementDependencies, atEntry, canBeNone, exp));
1691    }
1692    exp.setTypes(result);
1693
1694    if (exp.getInner() != null) {
1695      result = executeType(context, result, exp.getInner(), elementDependencies, false, false, exp);
1696    }
1697
1698    if (exp.isProximal() && exp.getOperation() != null) {
1699      ExpressionNode next = exp.getOpNext();
1700      ExpressionNode last = exp;
1701      while (next != null) {
1702        context = contextForParameter(inContext);
1703        TypeDetails work;
1704        if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
1705          work = executeTypeName(context, focus, next, atEntry);
1706        } else {
1707          work = executeType(context, focus, next, elementDependencies, atEntry, canBeNone, exp);
1708        }
1709        result = operateTypes(result, last.getOperation(), work, last);
1710        last = next;
1711        next = next.getOpNext();
1712      }
1713      exp.setOpTypes(result);
1714    }
1715    return result;
1716  }
1717
1718  private void doSQLOnFHIRCheck(TypeDetails focus, ExpressionNode expr) {
1719    if (emitSQLonFHIRWarning) {
1720      // special Logic for SQL-on-FHIR:
1721      if (focus.isChoice()) {
1722        if (expr.getInner() == null || expr.getInner().getFunction() != Function.OfType) {
1723          typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_CHOICE_NO_TYPE_SPECIFIER, expr.toString()), I18nConstants.FHIRPATH_CHOICE_NO_TYPE_SPECIFIER));
1724        }
1725      } else if (expr.getInner() != null && expr.getInner().getFunction() == Function.OfType) {
1726        typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER, expr.toString()), I18nConstants.FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER));
1727      }
1728    }
1729  }
1730  private List<Base> resolveConstant(ExecutionContext context, Base constant, boolean beforeContext, ExpressionNode expr, boolean explicitConstant) throws PathEngineException {
1731    if (constant == null) {
1732      return new ArrayList<Base>();
1733    }
1734    if (!(constant instanceof FHIRConstant)) {
1735      return new ArrayList<Base>(Arrays.asList(constant));
1736    }
1737    FHIRConstant c = (FHIRConstant) constant;
1738    if (c.getValue().startsWith("%")) {
1739      String varName = c.getValue().substring(1);
1740      if (context.hasDefinedVariable(varName)) {
1741        return context.getDefinedVariable(varName);
1742      }
1743      return resolveConstant(context, c.getValue(), beforeContext, expr, explicitConstant);
1744    } else if (c.getValue().startsWith("@")) {
1745      return new ArrayList<Base>(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1), expr)));
1746    } else {
1747      throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, c.getValue());
1748    }
1749  }
1750
1751  private Base processDateConstant(Object appInfo, String value, ExpressionNode expr) throws PathEngineException {
1752    String date = null;
1753    String time = null;
1754    String tz = null;
1755
1756    TemporalPrecisionEnum temp = null;
1757
1758    if (value.startsWith("T")) {
1759      time = value.substring(1);
1760    } else if (!value.contains("T")) {
1761      date = value;
1762    } else {
1763      String[] p = value.split("T");
1764      date = p[0];
1765      if (p.length > 1) {
1766        time = p[1];
1767      }
1768    }
1769
1770    if (time != null) {
1771      int i = time.indexOf("-");
1772      if (i == -1) {
1773        i = time.indexOf("+");
1774      }
1775      if (i == -1) {
1776        i = time.indexOf("Z");
1777      }
1778      if (i > -1) {
1779        tz = time.substring(i);
1780        time = time.substring(0, i);
1781      }
1782
1783      if (time.length() == 2) {
1784        time = time+":00:00";
1785        temp = TemporalPrecisionEnum.MINUTE;
1786      } else if (time.length() == 5) {
1787        temp = TemporalPrecisionEnum.MINUTE;
1788        time = time+":00";
1789      } else if (time.contains(".")) {
1790        temp = TemporalPrecisionEnum.MILLI;
1791      } else {
1792        temp = TemporalPrecisionEnum.SECOND;
1793      }
1794    }
1795
1796    if (date == null) {
1797      if (tz != null) {
1798        throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, value);
1799      } else {
1800        TimeType tt = new TimeType(time);
1801        tt.setPrecision(temp);
1802        return tt.noExtensions();
1803      }
1804    } else if (time != null) {
1805      DateTimeType dt = new DateTimeType(date+"T"+time+(tz == null ? "" : tz));
1806      dt.setPrecision(temp);
1807      return dt.noExtensions();
1808    } else { 
1809      return new DateType(date).noExtensions();
1810    }
1811  }
1812
1813  static boolean isSystemVariable(String name){
1814    if (name.equals("sct"))
1815      return true;
1816    if (name.equals("loinc"))
1817      return true;
1818    if (name.equals("ucum"))
1819      return true;
1820    if (name.equals("resource"))
1821      return true;
1822    if (name.equals("rootResource"))
1823      return true;
1824    if (name.equals("context"))
1825      return true;
1826    return false;
1827  }
1828
1829  private List<Base> resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr, boolean explicitConstant) throws PathEngineException {
1830    if (s.equals("%sct")) {
1831      return new ArrayList<Base>(Arrays.asList(new StringType("http://snomed.info/sct").noExtensions()));
1832    } else if (s.equals("%loinc")) {
1833      return new ArrayList<Base>(Arrays.asList(new StringType("http://loinc.org").noExtensions()));
1834    } else if (s.equals("%ucum")) {
1835      return new ArrayList<Base>(Arrays.asList(new StringType("http://unitsofmeasure.org").noExtensions()));
1836    } else if (s.equals("%resource")) {
1837      if (context.focusResource == null) {
1838        throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%resource", "no focus resource");
1839      }
1840      return new ArrayList<Base>(Arrays.asList(context.focusResource));
1841    } else if (s.equals("%rootResource")) {
1842      if (context.rootResource == null) {
1843        throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus rootResource");
1844      }
1845      return new ArrayList<Base>(Arrays.asList(context.rootResource));
1846    } else if (s.equals("%context")) {
1847      return new ArrayList<Base>(Arrays.asList(context.context));
1848    } else if (s.equals("%us-zip")) {
1849      return new ArrayList<Base>(Arrays.asList(new StringType("[0-9]{5}(-[0-9]{4}){0,1}").noExtensions()));
1850    } else if (s.startsWith("%`vs-")) {
1851      return new ArrayList<Base>(Arrays.asList(new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"").noExtensions()));
1852    } else if (s.startsWith("%`cs-")) {
1853      return new ArrayList<Base>(Arrays.asList(new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"").noExtensions()));
1854    } else if (s.startsWith("%`ext-")) {
1855      return new ArrayList<Base>(Arrays.asList(new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)).noExtensions()));
1856    } else if (hostServices == null) {
1857      throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
1858    } else {
1859      return hostServices.resolveConstant(this, context.appInfo, s.substring(1), beforeContext, explicitConstant);
1860    }
1861  }
1862
1863
1864  private String processConstantString(String s, FHIRLexer lexer) throws FHIRLexerException {
1865    StringBuilder b = new StringBuilder();
1866    int i = 1;
1867    while (i < s.length()-1) {
1868      char ch = s.charAt(i);
1869      if (ch == '\\') {
1870        i++;
1871        switch (s.charAt(i)) {
1872        case 't': 
1873          b.append('\t');
1874          break;
1875        case 'r':
1876          b.append('\r');
1877          break;
1878        case 'n': 
1879          b.append('\n');
1880          break;
1881        case 'f': 
1882          b.append('\f');
1883          break;
1884        case '\'':
1885          b.append('\'');
1886          break;
1887        case '"':
1888          b.append('"');
1889          break;
1890        case '`':
1891          b.append('`');
1892          break;
1893        case '\\': 
1894          b.append('\\');
1895          break;
1896        case '/': 
1897          b.append('/');
1898          break;
1899        case 'u':
1900          i++;
1901          int uc = Integer.parseInt(s.substring(i, i+4), 16);
1902          b.append(Character.toString(uc));
1903          i = i + 3;
1904          break;
1905        default:
1906          throw lexer.error("Unknown FHIRPath character escape \\"+s.charAt(i));
1907        }
1908        i++;
1909      } else {
1910        b.append(ch);
1911        i++;
1912      }
1913    }
1914    return b.toString();
1915  }
1916
1917
1918  private List<Base> operate(ExecutionContext context, List<Base> left, Operation operation, List<Base> right, ExpressionNode holder) throws FHIRException {
1919    switch (operation) {
1920    case Equals: return opEquals(left, right, holder);
1921    case Equivalent: return opEquivalent(left, right, holder);
1922    case NotEquals: return opNotEquals(left, right, holder);
1923    case NotEquivalent: return opNotEquivalent(left, right, holder);
1924    case LessThan: return opLessThan(left, right, holder);
1925    case Greater: return opGreater(left, right, holder);
1926    case LessOrEqual: return opLessOrEqual(left, right, holder);
1927    case GreaterOrEqual: return opGreaterOrEqual(left, right, holder);
1928    case Union: return opUnion(left, right, holder);
1929    case In: return opIn(left, right, holder);
1930    case MemberOf: return opMemberOf(context, left, right, holder);
1931    case Contains: return opContains(left, right, holder);
1932    case Or:  return opOr(left, right, holder);
1933    case And:  return opAnd(left, right, holder);
1934    case Xor: return opXor(left, right, holder);
1935    case Implies: return opImplies(left, right, holder);
1936    case Plus: return opPlus(left, right, holder);
1937    case Times: return opTimes(left, right, holder);
1938    case Minus: return opMinus(left, right, holder);
1939    case Concatenate: return opConcatenate(left, right, holder);
1940    case DivideBy: return opDivideBy(left, right, holder);
1941    case Div: return opDiv(left, right, holder);
1942    case Mod: return opMod(left, right, holder);
1943    case Is: return opIs(left, right, holder);
1944    case As: return opAs(left, right, holder);
1945    default: 
1946      throw new Error("Not Done Yet: "+operation.toCode());
1947    }
1948  }
1949
1950  private List<Base> opAs(List<Base> left, List<Base> right, ExpressionNode expr) {
1951    List<Base> result = new ArrayList<>();
1952    if (right.size() != 1) {
1953      return result;
1954    } else {
1955      String tn = convertToString(right);
1956      if (!isKnownType(tn)) {
1957        throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE);
1958      }
1959      if (!doNotEnforceAsSingletonRule && left.size() > 1) {
1960        throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_AS_COLLECTION, left.size(), expr.toString()), I18nConstants.FHIRPATH_AS_COLLECTION); 
1961      }
1962      for (Base nextLeft : left) {
1963        if (compareTypeNames(tn, nextLeft.fhirType())) {
1964          result.add(nextLeft);
1965        }
1966      }
1967    }
1968    return result;
1969  }
1970
1971  private boolean compareTypeNames(String left, String right) {
1972    if (doNotEnforceAsCaseSensitive) {
1973      return left.equalsIgnoreCase(right);            
1974    } else {
1975      return left.equals(right);      
1976    }
1977  }
1978
1979  private boolean isKnownType(String tn) {
1980    if (!tn.contains(".")) {
1981      if (Utilities.existsInList(tn, "String", "Boolean", "Integer", "Decimal", "Quantity", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo")) {
1982        return true;
1983      }
1984      try {
1985        return worker.fetchTypeDefinition(tn) != null;
1986      } catch (Exception e) {
1987        return false;
1988      }
1989    }
1990    String[] t = tn.split("\\.");
1991    if (t.length != 2) {
1992      return false;
1993    }
1994    if ("System".equals(t[0])) {
1995      return Utilities.existsInList(t[1], "String", "Boolean", "Integer", "Decimal", "Quantity", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo");
1996    } else if ("FHIR".equals(t[0])) {      
1997      try {
1998        return worker.fetchTypeDefinition(t[1]) != null;
1999      } catch (Exception e) {
2000        return false;
2001      }
2002    } else if ("CDA".equals(t[0])) {      
2003      try {
2004        return worker.fetchTypeDefinition(Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", t[1])) != null;
2005      } catch (Exception e) {
2006        return false;
2007      }
2008    } else {
2009      return false;
2010    }
2011  }
2012
2013  private List<Base> opIs(List<Base> left, List<Base> right, ExpressionNode expr) {
2014    List<Base> result = new ArrayList<Base>();
2015    if (left.size() == 0 || right.size() == 0) {
2016    } else if (left.size() != 1 || right.size() != 1) 
2017      result.add(new BooleanType(false).noExtensions());
2018    else {
2019      String tn = convertToString(right);
2020      if (left.get(0) instanceof org.hl7.fhir.r5.elementmodel.Element) {
2021        result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions());
2022      } else if ((left.get(0) instanceof Element) && ((Element) left.get(0)).isDisallowExtensions()) {
2023        result.add(new BooleanType(Utilities.capitalize(left.get(0).fhirType()).equals(tn) || ("System."+Utilities.capitalize(left.get(0).fhirType())).equals(tn)).noExtensions());
2024      } else {
2025        if (left.get(0).fhirType().equals(tn)) {
2026          result.add(new BooleanType(true).noExtensions());
2027        } else {
2028          StructureDefinition sd = worker.fetchTypeDefinition(left.get(0).fhirType());
2029          while (sd != null) {
2030            if (tn.equals(sd.getType())) {
2031              return makeBoolean(true);
2032            }
2033            sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
2034          }
2035          return makeBoolean(false);
2036        }      
2037      }
2038    }
2039    return result;
2040  }
2041
2042
2043  private void checkCardinalityForComparabilitySame(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) {
2044    if (left.isList() && !right.isList()) {
2045      typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT));
2046    } else if (!left.isList() && right.isList()) {
2047      typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT));
2048    }
2049  }
2050
2051  private void checkCardinalityForSingle(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) {
2052    if (left.isList()) {
2053      typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT));
2054    } 
2055    if (right.isList()) {
2056      typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT));
2057    }
2058  }
2059  
2060  private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) {
2061    switch (operation) {
2062    case Equals: 
2063      checkCardinalityForComparabilitySame(left, operation, right, expr);
2064      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2065    case Equivalent: 
2066      checkCardinalityForComparabilitySame(left, operation, right, expr);
2067      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2068    case NotEquals: 
2069      checkCardinalityForComparabilitySame(left, operation, right, expr);
2070      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2071    case NotEquivalent: 
2072      checkCardinalityForComparabilitySame(left, operation, right, expr);
2073      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2074    case LessThan: 
2075      checkCardinalityForSingle(left, operation, right, expr);
2076      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2077    case Greater: 
2078      checkCardinalityForSingle(left, operation, right, expr);
2079      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2080    case LessOrEqual: 
2081      checkCardinalityForSingle(left, operation, right, expr);
2082      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2083    case GreaterOrEqual: 
2084      checkCardinalityForSingle(left, operation, right, expr);
2085      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2086    case Is: 
2087      checkCardinalityForSingle(left, operation, right, expr);
2088      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2089    case As: 
2090      checkCardinalityForSingle(left, operation, right, expr);
2091      TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, right.getTypes());
2092      if (td.typesHaveTargets()) {
2093        td.addTargets(left.getTargets());
2094      }
2095      return td;
2096    case Union: return left.union(right);
2097    case Or: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2098    case And: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2099    case Xor: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2100    case Implies : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2101    case Times: 
2102      checkCardinalityForSingle(left, operation, right, expr);
2103      TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
2104      if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
2105        result.addType(TypeDetails.FP_Integer);
2106      } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) {
2107        result.addType(TypeDetails.FP_Decimal);
2108      }
2109      return result;
2110    case DivideBy: 
2111      checkCardinalityForSingle(left, operation, right, expr);
2112      result = new TypeDetails(CollectionStatus.SINGLETON);
2113      if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
2114        result.addType(TypeDetails.FP_Decimal);
2115      } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) {
2116        result.addType(TypeDetails.FP_Decimal);
2117      }
2118      return result;
2119    case Concatenate:
2120      checkCardinalityForSingle(left, operation, right, expr);
2121      result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2122      return result;
2123    case Plus:
2124      checkCardinalityForSingle(left, operation, right, expr);
2125      result = new TypeDetails(CollectionStatus.SINGLETON);
2126      if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
2127        result.addType(TypeDetails.FP_Integer);
2128      } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) {
2129        result.addType(TypeDetails.FP_Decimal);
2130      } else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri")) {
2131        result.addType(TypeDetails.FP_String);
2132      } else if (left.hasType(worker, "date", "dateTime", "instant")) {
2133        if (right.hasType(worker, "Quantity")) {
2134          result.addType(left.getType());
2135        } else {
2136          throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_PLUS, right.getType(), left.getType()), I18nConstants.FHIRPATH_ARITHMETIC_PLUS, expr.getOpStart(), expr.toString()); 
2137        }
2138      }
2139      return result;
2140    case Minus:
2141      checkCardinalityForSingle(left, operation, right, expr);
2142      result = new TypeDetails(CollectionStatus.SINGLETON);
2143      if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
2144        result.addType(TypeDetails.FP_Integer);
2145      } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) {
2146        result.addType(TypeDetails.FP_Decimal);
2147      } else if (left.hasType(worker, "Quantity") && right.hasType(worker, "Quantity")) {
2148        result.addType(TypeDetails.FP_Quantity);
2149      } else if (left.hasType(worker, "date", "dateTime", "instant")) {
2150        if (right.hasType(worker, "Quantity")) {
2151          result.addType(left.getType());
2152        } else {
2153          throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_MINUS, right.getType(), left.getType()), I18nConstants.FHIRPATH_ARITHMETIC_MINUS, expr.getOpStart(), expr.toString());
2154        }
2155      }
2156      return result;
2157    case Div: 
2158    case Mod: 
2159      checkCardinalityForSingle(left, operation, right, expr);
2160      result = new TypeDetails(CollectionStatus.SINGLETON);
2161      if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
2162        result.addType(TypeDetails.FP_Integer);
2163      } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) {
2164        result.addType(TypeDetails.FP_Decimal);
2165      }
2166      return result;
2167    case In: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2168    case MemberOf: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2169    case Contains: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2170    default: 
2171      return null;
2172    }
2173  }
2174
2175
2176  private List<Base> opEquals(List<Base> left, List<Base> right, ExpressionNode expr) {
2177    if (left.size() == 0 || right.size() == 0) { 
2178      return new ArrayList<Base>();
2179    }
2180
2181    if (left.size() != right.size()) {
2182      return makeBoolean(false);
2183    }
2184
2185    boolean res = true;
2186    boolean nil = false;
2187    for (int i = 0; i < left.size(); i++) {
2188      Boolean eq = doEquals(left.get(i), right.get(i));
2189      if (eq == null) {
2190        nil = true;
2191      } else if (eq == false) { 
2192        res = false;
2193        break;
2194      }
2195    }
2196    if (!res) {
2197      return makeBoolean(res);
2198    } else if (nil) {
2199      return new ArrayList<Base>();
2200    } else {
2201      return makeBoolean(res);
2202    }
2203  }
2204
2205  private List<Base> opNotEquals(List<Base> left, List<Base> right, ExpressionNode expr) {
2206    if (!legacyMode && (left.size() == 0 || right.size() == 0)) {
2207      return new ArrayList<Base>();
2208    }
2209
2210    if (left.size() != right.size()) {
2211      return makeBoolean(true);
2212    }
2213
2214    boolean res = true;
2215    boolean nil = false;
2216    for (int i = 0; i < left.size(); i++) {
2217      Boolean eq = doEquals(left.get(i), right.get(i));
2218      if (eq == null) {
2219        nil = true;
2220      } else if (eq == true) { 
2221        res = false;
2222        break;
2223      }
2224    }
2225    if (!res) {
2226      return makeBoolean(res);
2227    } else if (nil) {
2228      return new ArrayList<Base>();
2229    } else {
2230      return makeBoolean(res);
2231    }
2232  }
2233
2234  private String removeTrailingZeros(String s) {
2235    if (Utilities.noString(s))
2236      return "";
2237    int i = s.length()-1;
2238    boolean done = false;
2239    boolean dot = false;
2240    while (i > 0 && !done) {
2241      if (s.charAt(i) == '.') {
2242        i--;
2243        dot = true;
2244      } else if (!dot && s.charAt(i) == '0') {
2245        i--;
2246      } else {
2247        done = true;
2248      }
2249    }
2250    return s.substring(0, i+1);
2251  }
2252
2253  private boolean decEqual(String left, String right) {
2254    left = removeTrailingZeros(left);
2255    right = removeTrailingZeros(right);
2256    return left.equals(right);
2257  }
2258
2259  private Boolean datesEqual(BaseDateTimeType left, BaseDateTimeType right) {
2260    return left.equalsUsingFhirPathRules(right);
2261  }
2262
2263  private Boolean doEquals(Base left, Base right) {
2264    if (left instanceof Quantity && right instanceof Quantity) {
2265      return qtyEqual((Quantity) left, (Quantity) right);
2266    } else if (left.isDateTime() && right.isDateTime()) { 
2267      return datesEqual(left.dateTimeValue(), right.dateTimeValue());
2268    } else if (left instanceof DecimalType || right instanceof DecimalType) { 
2269      return decEqual(left.primitiveValue(), right.primitiveValue());
2270    } else if (left.isPrimitive() && right.isPrimitive()) {
2271      return Base.equals(left.primitiveValue(), right.primitiveValue());
2272    } else {
2273      return Base.compareDeep(left, right, false);
2274    }
2275  }
2276
2277  private boolean doEquivalent(Base left, Base right) throws PathEngineException {
2278    if (left instanceof Quantity && right instanceof Quantity) {
2279      return qtyEquivalent((Quantity) left, (Quantity) right);
2280    }
2281    if (left.hasType("integer") && right.hasType("integer")) {
2282      return doEquals(left, right);
2283    }
2284    if (left.hasType("boolean") && right.hasType("boolean")) {
2285      return doEquals(left, right);
2286    }
2287    if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
2288      return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue());
2289    }
2290    if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant")) {
2291      Integer i = compareDateTimeElements(left, right, true);
2292      if (i == null) {
2293        i = 0;
2294      }
2295      return i == 0;
2296    }
2297    if (left.hasType(FHIR_TYPES_STRING) && right.hasType(FHIR_TYPES_STRING)) {
2298      return Utilities.equivalent(convertToString(left), convertToString(right));
2299    }
2300    if (left.isPrimitive() && right.isPrimitive()) {
2301      return Utilities.equivalent(left.primitiveValue(), right.primitiveValue());
2302    }
2303    if (!left.isPrimitive() && !right.isPrimitive()) {
2304      MergedList<Property> props = new MergedList<Property>(left.children(), right.children(), new PropertyMatcher());
2305      for (MergeNode<Property> t : props) {
2306        if (t.hasLeft() && t.hasRight()) {
2307          if (t.getLeft().hasValues() && t.getRight().hasValues()) {
2308            MergedList<Base> values = new MergedList<Base>(t.getLeft().getValues(), t.getRight().getValues());
2309            for (MergeNode<Base> v : values) {
2310              if (v.hasLeft() && v.hasRight()) {
2311                if (!doEquivalent(v.getLeft(), v.getRight())) {
2312                  return false;
2313                }
2314              } else if (v.hasLeft() || v.hasRight()) {
2315                return false;
2316              }            
2317            }
2318          } else if (t.getLeft().hasValues() || t.getRight().hasValues()) {
2319            return false;
2320          }
2321        } else {
2322          return false;
2323        }
2324      }
2325      return true;
2326    } else {
2327      return false;
2328    }      
2329  }
2330
2331  private Boolean qtyEqual(Quantity left, Quantity right) {
2332    if (!left.hasValue() && !right.hasValue()) {
2333      return true;
2334    }
2335    if (!left.hasValue() || !right.hasValue()) {
2336      return null;
2337    }
2338    if (worker.getUcumService() != null) {
2339      Pair dl = qtyToCanonicalPair(left);
2340      Pair dr = qtyToCanonicalPair(right);
2341      if (dl != null && dr != null) {
2342        if (dl.getCode().equals(dr.getCode())) {
2343          return doEquals(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal()));          
2344        } else {
2345          return false;
2346        }
2347      }
2348    }
2349    if (left.hasCode() || right.hasCode()) {
2350      if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) {
2351        return null;
2352      }
2353    } else if (!left.hasUnit() || right.hasUnit()) {
2354      if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) {
2355        return null;
2356      }
2357    }
2358    return doEquals(new DecimalType(left.getValue()), new DecimalType(right.getValue()));
2359  }
2360
2361  private Pair qtyToCanonicalPair(Quantity q) {
2362    if (!"http://unitsofmeasure.org".equals(q.getSystem())) {
2363      return null;
2364    }
2365    try {
2366      Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode() == null ? "1" : q.getCode());
2367      Pair c = worker.getUcumService().getCanonicalForm(p);
2368      return c;
2369    } catch (UcumException e) {
2370      return null;
2371    }
2372  }
2373
2374  private DecimalType qtyToCanonicalDecimal(Quantity q) {
2375    if (!"http://unitsofmeasure.org".equals(q.getSystem())) {
2376      return null;
2377    }
2378    try {
2379      Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode() == null ? "1" : q.getCode());
2380      Pair c = worker.getUcumService().getCanonicalForm(p);
2381      return new DecimalType(c.getValue().asDecimal());
2382    } catch (UcumException e) {
2383      return null;
2384    }
2385  }
2386
2387  private Base pairToQty(Pair p) {
2388    return new Quantity().setValue(new BigDecimal(p.getValue().toString())).setSystem("http://unitsofmeasure.org").setCode(p.getCode()).noExtensions();
2389  }
2390
2391
2392  private Pair qtyToPair(Quantity q) {
2393    if (!"http://unitsofmeasure.org".equals(q.getSystem())) {
2394      return null;
2395    }
2396    try {
2397      return new Pair(new Decimal(q.getValue().toPlainString()), q.getCode());
2398    } catch (UcumException e) {
2399      return null;
2400    }
2401  }
2402
2403
2404  private Boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException {
2405    if (!left.hasValue() && !right.hasValue()) {
2406      return true;
2407    }
2408    if (!left.hasValue() || !right.hasValue()) {
2409      return null;
2410    }
2411    if (worker.getUcumService() != null) {
2412      Pair dl = qtyToCanonicalPair(left);
2413      Pair dr = qtyToCanonicalPair(right);
2414      if (dl != null && dr != null) {
2415        if (dl.getCode().equals(dr.getCode())) {
2416          return doEquivalent(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal()));          
2417        } else {
2418          return false;
2419        }
2420      }
2421    }
2422    if (left.hasCode() || right.hasCode()) {
2423      if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) {
2424        return null;
2425      }
2426    } else if (!left.hasUnit() || right.hasUnit()) {
2427      if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) {
2428        return null;
2429      }
2430    }
2431    return doEquivalent(new DecimalType(left.getValue()), new DecimalType(right.getValue()));
2432  }
2433
2434
2435
2436  private List<Base> opEquivalent(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2437    if (left.size() != right.size()) {
2438      return makeBoolean(false);
2439    }
2440
2441    boolean res = true;
2442    for (int i = 0; i < left.size(); i++) {
2443      boolean found = false;
2444      for (int j = 0; j < right.size(); j++) {
2445        if (doEquivalent(left.get(i), right.get(j))) {
2446          found = true;
2447          break;
2448        }
2449      }
2450      if (!found) {
2451        res = false;
2452        break;
2453      }
2454    }
2455    return makeBoolean(res);
2456  }
2457
2458  private List<Base> opNotEquivalent(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2459    if (left.size() != right.size()) {
2460      return makeBoolean(true);
2461    }
2462
2463    boolean res = true;
2464    for (int i = 0; i < left.size(); i++) {
2465      boolean found = false;
2466      for (int j = 0; j < right.size(); j++) {
2467        if (doEquivalent(left.get(i), right.get(j))) {
2468          found = true;
2469          break;
2470        }
2471      }
2472      if (!found) {
2473        res = false;
2474        break;
2475      }
2476    }
2477    return makeBoolean(!res);
2478  }
2479
2480  private final static String[] FHIR_TYPES_STRING = new String[] {"string", "uri", "code", "oid", "id", "uuid", "sid", "markdown", "base64Binary", "canonical", "url", "xhtml"};
2481
2482  private List<Base> opLessThan(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2483    if (left.size() == 0 || right.size() == 0) 
2484      return new ArrayList<Base>();
2485
2486    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
2487      Base l = left.get(0);
2488      Base r = right.get(0);
2489      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 
2490        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
2491      } else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) { 
2492        return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue()));
2493      } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) {
2494        Integer i = compareDateTimeElements(l, r, false);
2495        if (i == null) {
2496          return makeNull();
2497        } else {
2498          return makeBoolean(i < 0);
2499        }
2500      } else if ((l.hasType("time")) && (r.hasType("time"))) { 
2501        Integer i = compareTimeElements(l, r, false);
2502        if (i == null) {
2503          return makeNull();
2504        } else {
2505          return makeBoolean(i < 0);
2506        }
2507      } else {
2508        throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
2509      }
2510    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
2511      List<Base> lUnit = left.get(0).listChildrenByName("code");
2512      List<Base> rUnit = right.get(0).listChildrenByName("code");
2513      if (Base.compareDeep(lUnit, rUnit, true)) {
2514        return opLessThan(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr);
2515      } else {
2516        if (worker.getUcumService() == null) {
2517          return makeBoolean(false);
2518        } else {
2519          List<Base> dl = new ArrayList<Base>();
2520          dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
2521          List<Base> dr = new ArrayList<Base>();
2522          dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
2523          return opLessThan(dl, dr, expr);
2524        }
2525      }
2526    }
2527    return new ArrayList<Base>();
2528  }
2529
2530  private List<Base> opGreater(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2531    if (left.size() == 0 || right.size() == 0) 
2532      return new ArrayList<Base>();
2533    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
2534      Base l = left.get(0);
2535      Base r = right.get(0);
2536      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) {
2537        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
2538      } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { 
2539        return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue()));
2540      } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) {
2541        Integer i = compareDateTimeElements(l, r, false);
2542        if (i == null) {
2543          return makeNull();
2544        } else {
2545          return makeBoolean(i > 0); 
2546        }
2547      } else if ((l.hasType("time")) && (r.hasType("time"))) { 
2548        Integer i = compareTimeElements(l, r, false);
2549        if (i == null) {
2550          return makeNull();
2551        } else {
2552          return makeBoolean(i > 0);
2553        }
2554      } else {
2555        throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
2556      }
2557    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
2558      List<Base> lUnit = left.get(0).listChildrenByName("unit");
2559      List<Base> rUnit = right.get(0).listChildrenByName("unit");
2560      if (Base.compareDeep(lUnit, rUnit, true)) {
2561        return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr);
2562      } else {
2563        if (worker.getUcumService() == null) {
2564          return makeBoolean(false);
2565        } else {
2566          List<Base> dl = new ArrayList<Base>();
2567          dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
2568          List<Base> dr = new ArrayList<Base>();
2569          dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
2570          return opGreater(dl, dr, expr);
2571        }
2572      }
2573    }
2574    return new ArrayList<Base>();
2575  }
2576
2577  private List<Base> opLessOrEqual(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2578    if (left.size() == 0 || right.size() == 0) { 
2579      return new ArrayList<Base>();
2580    }
2581    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
2582      Base l = left.get(0);
2583      Base r = right.get(0);
2584      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 
2585        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
2586      } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { 
2587        return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue()));
2588      } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) {
2589        Integer i = compareDateTimeElements(l, r, false);
2590        if (i == null) {
2591          return makeNull();
2592        } else {
2593          return makeBoolean(i <= 0);
2594        }
2595      } else if ((l.hasType("time")) && (r.hasType("time"))) {
2596        Integer i = compareTimeElements(l, r, false);
2597        if (i == null) {
2598          return makeNull();
2599        } else {
2600          return makeBoolean(i <= 0);
2601        }
2602      } else {
2603        throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
2604      }
2605    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
2606      List<Base> lUnits = left.get(0).listChildrenByName("unit");
2607      String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null;
2608      List<Base> rUnits = right.get(0).listChildrenByName("unit");
2609      String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null;
2610      if ((lunit == null && runit == null) || lunit.equals(runit)) {
2611        return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr);
2612      } else {
2613        if (worker.getUcumService() == null) {
2614          return makeBoolean(false);
2615        } else {
2616          List<Base> dl = new ArrayList<Base>();
2617          dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
2618          List<Base> dr = new ArrayList<Base>();
2619          dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
2620          return opLessOrEqual(dl, dr, expr);
2621        }
2622      }
2623    }
2624    return new ArrayList<Base>();
2625  }
2626
2627  private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2628    if (left.size() == 0 || right.size() == 0) { 
2629      return new ArrayList<Base>();
2630    }
2631    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
2632      Base l = left.get(0);
2633      Base r = right.get(0);
2634      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 
2635        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
2636      } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { 
2637        return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue()));
2638      } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) {
2639        Integer i = compareDateTimeElements(l, r, false);
2640        if (i == null) {
2641          return makeNull();
2642        } else {
2643          return makeBoolean(i >= 0);
2644        }
2645      } else if ((l.hasType("time")) && (r.hasType("time"))) {
2646        Integer i = compareTimeElements(l, r, false);
2647        if (i == null) {
2648          return makeNull();
2649        } else {
2650          return makeBoolean(i >= 0);
2651        }
2652      } else {
2653        throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
2654      }
2655    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
2656      List<Base> lUnit = left.get(0).listChildrenByName("unit");
2657      List<Base> rUnit = right.get(0).listChildrenByName("unit");
2658      if (Base.compareDeep(lUnit, rUnit, true)) {
2659        return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr);
2660      } else {
2661        if (worker.getUcumService() == null) {
2662          return makeBoolean(false);
2663        } else {
2664          List<Base> dl = new ArrayList<Base>();
2665          dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
2666          List<Base> dr = new ArrayList<Base>();
2667          dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
2668          return opGreaterOrEqual(dl, dr, expr);
2669        }
2670      }
2671    }
2672    return new ArrayList<Base>();
2673  }
2674
2675  private List<Base> opMemberOf(ExecutionContext context, List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2676    boolean ans = false;
2677    String url = right.get(0).primitiveValue();
2678    ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.findTxResource(ValueSet.class, url);
2679    if (vs != null) {
2680      for (Base l : left) {
2681        if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) {
2682          if (worker.validateCode(terminologyServiceOptions.withGuessSystem() , TypeConvertor.castToCoding(l), vs).isOk()) {
2683            ans = true;
2684          }
2685        } else if (l.fhirType().equals("Coding")) {
2686          if (worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCoding(l), vs).isOk()) {
2687            ans = true;
2688          }
2689        } else if (l.fhirType().equals("CodeableConcept")) {
2690          CodeableConcept cc = TypeConvertor.castToCodeableConcept(l);
2691          ValidationResult vr = worker.validateCode(terminologyServiceOptions, cc, vs);
2692          // System.out.println("~~~ "+DataRenderer.display(worker, cc)+ " memberOf "+url+": "+vr.toString());
2693          if (vr.isOk()) {
2694            ans = true;
2695          }
2696        } else {
2697          //            System.out.println("unknown type in opMemberOf: "+l.fhirType());
2698        }
2699      }
2700    }
2701    return makeBoolean(ans);
2702  }
2703
2704  private List<Base> opIn(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2705    if (left.size() == 0) { 
2706      return new ArrayList<Base>();
2707    }
2708    if (right.size() == 0) { 
2709      return makeBoolean(false);
2710    }
2711    boolean ans = true;
2712    for (Base l : left) {
2713      boolean f = false;
2714      for (Base r : right) {
2715        Boolean eq = doEquals(l, r);
2716        if (eq != null && eq == true) {
2717          f = true;
2718          break;
2719        }
2720      }
2721      if (!f) {
2722        ans = false;
2723        break;
2724      }
2725    }
2726    return makeBoolean(ans);
2727  }
2728
2729  private List<Base> opContains(List<Base> left, List<Base> right, ExpressionNode expr) {
2730    if (left.size() == 0 || right.size() == 0) { 
2731      return new ArrayList<Base>();
2732    }
2733    boolean ans = true;
2734    for (Base r : right) {
2735      boolean f = false;
2736      for (Base l : left) {
2737        Boolean eq = doEquals(l, r);
2738        if (eq != null && eq == true) {
2739          f = true;
2740          break;
2741        }
2742      }
2743      if (!f) {
2744        ans = false;
2745        break;
2746      }
2747    }
2748    return makeBoolean(ans);
2749  }
2750
2751  private List<Base> opPlus(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2752    if (left.size() == 0 || right.size() == 0) { 
2753      return new ArrayList<Base>();
2754    }
2755    if (left.size() > 1) {
2756      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "+");
2757    }
2758    if (!left.get(0).isPrimitive()) {
2759      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "+", left.get(0).fhirType());
2760    }
2761    if (right.size() > 1) {
2762      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "+");
2763    }
2764    if (!right.get(0).isPrimitive() &&  !((left.get(0).isDateTime() || left.get(0).hasType("date", "dateTime", "instant") || "0".equals(left.get(0).primitiveValue()) || left.get(0).hasType("Quantity")) && right.get(0).hasType("Quantity"))) {
2765      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "+", right.get(0).fhirType());
2766    }
2767
2768    List<Base> result = new ArrayList<Base>();
2769    Base l = left.get(0);
2770    Base r = right.get(0);
2771    if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 
2772      result.add(new StringType(l.primitiveValue() + r.primitiveValue()));
2773    } else if (l.hasType("integer") && r.hasType("integer")) { 
2774      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue())));
2775    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
2776      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue()))));
2777    } else if (l.hasType("date") && r.hasType("Quantity")) {
2778      DateType dl = l instanceof DateType ? (DateType) l : new DateType(l.primitiveValue()); 
2779      result.add(dateAdd(dl, (Quantity) r, false, expr));
2780    } else if ((l.isDateTime() || l.hasType("dateTime") || l.hasType("instant")) && r.hasType("Quantity")) {
2781      DateTimeType dl = l instanceof DateTimeType ? (DateTimeType) l : new DateTimeType(l.primitiveValue()); 
2782      result.add(dateAdd(dl, (Quantity) r, false, expr));
2783    } else {
2784      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "+", left.get(0).fhirType(), right.get(0).fhirType());
2785    }
2786    return result;
2787  }
2788
2789  private BaseDateTimeType dateAdd(BaseDateTimeType d, Quantity q, boolean negate, ExpressionNode holder) {
2790    BaseDateTimeType result = (BaseDateTimeType) d.copy();
2791
2792    int value = negate ? 0 - q.getValue().intValue() : q.getValue().intValue();
2793    switch (q.hasCode() ? q.getCode() : q.getUnit()) {
2794    case "years": 
2795    case "year": 
2796      result.add(Calendar.YEAR, value);
2797      break;
2798    case "a":
2799      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString());
2800    case "months": 
2801    case "month": 
2802      result.add(Calendar.MONTH, value);
2803      break;
2804    case "mo":
2805      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString());
2806    case "weeks": 
2807    case "week": 
2808    case "wk":
2809      result.add(Calendar.DAY_OF_MONTH, value * 7);
2810      break;
2811    case "days": 
2812    case "day": 
2813    case "d":
2814      result.add(Calendar.DAY_OF_MONTH, value);
2815      break;
2816    case "hours": 
2817    case "hour": 
2818    case "h":
2819      result.add(Calendar.HOUR, value);
2820      break;
2821    case "minutes": 
2822    case "minute": 
2823    case "min":
2824      result.add(Calendar.MINUTE, value);
2825      break;
2826    case "seconds": 
2827    case "second": 
2828    case "s":
2829      result.add(Calendar.SECOND, value);
2830      break;
2831    case "milliseconds": 
2832    case "millisecond": 
2833    case "ms": 
2834      result.add(Calendar.MILLISECOND, value);
2835      break;
2836    default:
2837      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_UNIT, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_UNIT, holder.getOpStart(), holder.toString());
2838    }
2839    return result;
2840  }
2841
2842  private List<Base> opTimes(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2843    if (left.size() == 0 || right.size() == 0) {
2844      return new ArrayList<Base>();
2845    }
2846    if (left.size() > 1) {
2847      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "*");
2848    }
2849    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) {
2850      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "*", left.get(0).fhirType());
2851    }
2852    if (right.size() > 1) {
2853      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "*");
2854    }
2855    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) {
2856      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "*", right.get(0).fhirType());
2857    }
2858
2859    List<Base> result = new ArrayList<Base>();
2860    Base l = left.get(0);
2861    Base r = right.get(0);
2862
2863    if (l.hasType("integer") && r.hasType("integer")) { 
2864      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue())));
2865    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
2866      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue()))));
2867    } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) {
2868      Pair pl = qtyToPair((Quantity) l);
2869      Pair pr = qtyToPair((Quantity) r);
2870      Pair p;
2871      try {
2872        p = worker.getUcumService().multiply(pl, pr);
2873        result.add(pairToQty(p));
2874      } catch (UcumException e) {
2875        throw new PathEngineException(e.getMessage(), null, expr.getOpStart(), expr.toString(), e); // #TODO: i18n
2876      }
2877    } else {
2878      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "*", left.get(0).fhirType(), right.get(0).fhirType());
2879    }
2880    return result;
2881  }
2882
2883
2884  private List<Base> opConcatenate(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2885    if (left.size() > 1) {
2886      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "&");
2887    }
2888    if (left.size() > 0 && !left.get(0).hasType(FHIR_TYPES_STRING)) {
2889      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "&", left.get(0).fhirType());
2890    }
2891    if (right.size() > 1) {
2892      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "&");
2893    }
2894    if (right.size() > 0 && !right.get(0).hasType(FHIR_TYPES_STRING)) {
2895      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "&", right.get(0).fhirType());
2896    }
2897
2898    List<Base> result = new ArrayList<Base>();
2899    String l = left.size() == 0 ? "" : left.get(0).primitiveValue();
2900    String r = right.size() == 0 ? "" : right.get(0).primitiveValue();
2901    result.add(new StringType(l + r));
2902    return result;
2903  }
2904
2905  private List<Base> opUnion(List<Base> left, List<Base> right, ExpressionNode expr) {
2906    List<Base> result = new ArrayList<Base>();
2907    for (Base item : left) {
2908      if (!doContains(result, item)) {
2909        result.add(item);
2910      }
2911    }
2912    for (Base item : right) {
2913      if (!doContains(result, item)) {
2914        result.add(item);
2915      }
2916    }
2917    return result;
2918  }
2919
2920  private boolean doContains(List<Base> list, Base item) {
2921    for (Base test : list) {
2922      Boolean eq = doEquals(test, item);
2923      if (eq != null && eq == true) {
2924        return true;
2925      }
2926    }
2927    return false;
2928  }
2929
2930
2931  private List<Base> opAnd(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2932    Equality l = asBool(left, expr);
2933    Equality r = asBool(right, expr);
2934    switch (l) {
2935    case False: return makeBoolean(false);
2936    case Null:
2937      if (r == Equality.False) {
2938        return makeBoolean(false);
2939      } else {
2940        return makeNull();
2941      }
2942    case True:
2943      switch (r) {
2944      case False: return makeBoolean(false);
2945      case Null: return makeNull();
2946      case True: return makeBoolean(true);
2947      }
2948    }
2949    return makeNull();
2950  }
2951
2952  private boolean isBoolean(List<Base> list, boolean b) {
2953    return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b;
2954  }
2955
2956  private List<Base> opOr(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2957    Equality l = asBool(left, expr);
2958    Equality r = asBool(right, expr);
2959    switch (l) {
2960    case True: return makeBoolean(true);
2961    case Null:
2962      if (r == Equality.True) {
2963        return makeBoolean(true);
2964      } else {
2965        return makeNull();
2966      }
2967    case False:
2968      switch (r) {
2969      case False: return makeBoolean(false);
2970      case Null: return makeNull();
2971      case True: return makeBoolean(true);
2972      }
2973    }
2974    return makeNull();
2975  }
2976
2977  private List<Base> opXor(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2978    Equality l = asBool(left, expr);
2979    Equality r = asBool(right, expr);
2980    switch (l) {
2981    case True: 
2982      switch (r) {
2983      case False: return makeBoolean(true);
2984      case True: return makeBoolean(false);
2985      case Null: return makeNull();
2986      }
2987    case Null:
2988      return makeNull();
2989    case False:
2990      switch (r) {
2991      case False: return makeBoolean(false);
2992      case True: return makeBoolean(true);
2993      case Null: return makeNull();
2994      }
2995    }
2996    return makeNull();
2997  }
2998
2999  private List<Base> opImplies(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
3000    Equality eq = asBool(left, expr);
3001    if (eq == Equality.False) { 
3002      return makeBoolean(true);
3003    } else if (right.size() == 0) {
3004      return makeNull();
3005    } else switch (asBool(right, expr)) {
3006    case False: return eq == Equality.Null ? makeNull() : makeBoolean(false);
3007    case Null: return makeNull();
3008    case True: return makeBoolean(true);
3009    }
3010    return makeNull();
3011  }
3012
3013
3014  private List<Base> opMinus(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
3015    if (left.size() == 0 || right.size() == 0) { 
3016      return new ArrayList<Base>();
3017    }
3018    if (left.size() > 1) {
3019      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "-");
3020    }
3021    if (!left.get(0).isPrimitive() && !left.get(0).hasType("Quantity")) {
3022      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "-", left.get(0).fhirType());
3023    }
3024    if (right.size() > 1) {
3025      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "-");
3026    }
3027    if (!right.get(0).isPrimitive() &&  !((left.get(0).isDateTime() || left.get(0).hasType("date", "dateTime", "instant") || "0".equals(left.get(0).primitiveValue()) || left.get(0).hasType("Quantity")) && right.get(0).hasType("Quantity"))) {
3028      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "-", right.get(0).fhirType());
3029    }
3030
3031    List<Base> result = new ArrayList<Base>();
3032    Base l = left.get(0);
3033    Base r = right.get(0);
3034
3035    if (l.hasType("integer") && r.hasType("integer")) { 
3036      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue())));
3037    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
3038      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue()))));
3039    } else if (l.hasType("decimal", "integer", "Quantity") && r.hasType("Quantity")) { 
3040      String s = l.primitiveValue();
3041      if ("0".equals(s)) {
3042        Quantity qty = (Quantity) r;
3043        result.add(qty.copy().setValue(qty.getValue().abs()));
3044      }
3045    } else if (l.hasType("date") && r.hasType("Quantity")) {
3046      DateType dl = l instanceof DateType ? (DateType) l : new DateType(l.primitiveValue()); 
3047      result.add(dateAdd(dl, (Quantity) r, true, expr));
3048    } else if ((l.isDateTime() || l.hasType("dateTime") || l.hasType("instant")) && r.hasType("Quantity")) {
3049      DateTimeType dl = l instanceof DateTimeType ? (DateTimeType) l : new DateTimeType(l.primitiveValue()); 
3050      result.add(dateAdd(dl, (Quantity) r, true, expr));
3051    } else {
3052      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "-", left.get(0).fhirType(), right.get(0).fhirType());
3053    }
3054    return result;
3055  }
3056
3057  private List<Base> opDivideBy(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
3058    if (left.size() == 0 || right.size() == 0) {
3059      return new ArrayList<Base>();
3060    }
3061    if (left.size() > 1) {
3062      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "/");
3063    }
3064    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) {
3065      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "/", left.get(0).fhirType());
3066    }
3067    if (right.size() > 1) {
3068      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "/");
3069    }
3070    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) {
3071      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "/", right.get(0).fhirType());
3072    }
3073
3074    List<Base> result = new ArrayList<Base>();
3075    Base l = left.get(0);
3076    Base r = right.get(0);
3077
3078    if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3079      Decimal d1;
3080      try {
3081        d1 = new Decimal(l.primitiveValue());
3082        Decimal d2 = new Decimal(r.primitiveValue());
3083        result.add(new DecimalType(d1.divide(d2).asDecimal()));
3084      } catch (UcumException e) {
3085        // just return nothing
3086      }
3087    } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) {
3088      Pair pl = qtyToPair((Quantity) l);
3089      Pair pr = qtyToPair((Quantity) r);
3090      Pair p;
3091      try {
3092        p = worker.getUcumService().divideBy(pl, pr);
3093        result.add(pairToQty(p));
3094      } catch (UcumException e) {
3095        // just return nothing
3096      }
3097    } else {
3098      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "/", left.get(0).fhirType(), right.get(0).fhirType());
3099    }
3100    return result;
3101  }
3102
3103  private List<Base> opDiv(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
3104    if (left.size() == 0 || right.size() == 0) { 
3105      return new ArrayList<Base>();
3106    }
3107    if (left.size() > 1) {
3108      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "div");
3109    }
3110    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) {
3111      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "div", left.get(0).fhirType());
3112    }
3113    if (right.size() > 1) {
3114      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "div");
3115    }
3116    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) {
3117      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "div", right.get(0).fhirType());
3118    }
3119
3120    List<Base> result = new ArrayList<Base>();
3121    Base l = left.get(0);
3122    Base r = right.get(0);
3123
3124    if (l.hasType("integer") && r.hasType("integer")) {
3125      int divisor = Integer.parseInt(r.primitiveValue());
3126      if (divisor != 0) { 
3127        result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / divisor));
3128      }
3129    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
3130      Decimal d1;
3131      try {
3132        d1 = new Decimal(l.primitiveValue());
3133        Decimal d2 = new Decimal(r.primitiveValue());
3134        result.add(new IntegerType(d1.divInt(d2).asDecimal()));
3135      } catch (UcumException e) {
3136        // just return nothing
3137      }
3138    } else {
3139      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "div", left.get(0).fhirType(), right.get(0).fhirType());
3140    }
3141    return result;
3142  }
3143
3144  private List<Base> opMod(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
3145    if (left.size() == 0 || right.size() == 0) {
3146      return new ArrayList<Base>();
3147    } if (left.size() > 1) {
3148      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "mod");
3149    }
3150    if (!left.get(0).isPrimitive()) {
3151      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "mod", left.get(0).fhirType());
3152    }
3153    if (right.size() > 1) {
3154      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "mod");
3155    }
3156    if (!right.get(0).isPrimitive()) {
3157      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "mod", right.get(0).fhirType());
3158    }
3159
3160    List<Base> result = new ArrayList<Base>();
3161    Base l = left.get(0);
3162    Base r = right.get(0);
3163
3164    if (l.hasType("integer") && r.hasType("integer")) { 
3165      int modulus = Integer.parseInt(r.primitiveValue());
3166      if (modulus != 0) {
3167        result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % modulus));
3168      }
3169    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) {
3170      Decimal d1;
3171      try {
3172        d1 = new Decimal(l.primitiveValue());
3173        Decimal d2 = new Decimal(r.primitiveValue());
3174        result.add(new DecimalType(d1.modulo(d2).asDecimal()));
3175      } catch (UcumException e) {
3176        throw new PathEngineException(e);
3177      }
3178    } else {
3179      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "mod", left.get(0).fhirType(), right.get(0).fhirType());
3180    }
3181    return result;
3182  }
3183
3184
3185  private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant, ExpressionNode expr, boolean explicitConstant) throws PathEngineException {
3186    if (constant instanceof BooleanType) { 
3187      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3188    } else if (constant instanceof IntegerType) {
3189      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
3190    } else if (constant instanceof DecimalType) {
3191      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
3192    } else if (constant instanceof Quantity) {
3193      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity);
3194    } else if (constant instanceof FHIRConstant) {
3195      return resolveConstantType(context, ((FHIRConstant) constant).getValue(), expr, explicitConstant);
3196    } else if (constant == null) {
3197      return new TypeDetails(CollectionStatus.SINGLETON);      
3198    } else {
3199      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3200    }
3201  }
3202
3203  private TypeDetails resolveConstantType(ExecutionTypeContext context, String s, ExpressionNode expr, boolean explicitConstant) throws PathEngineException {
3204    if (s.startsWith("@")) {
3205      if (s.startsWith("@T")) {
3206        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time);
3207      } else {
3208        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
3209      }
3210    } else if (s.equals("%sct")) {
3211      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3212    } else if (s.equals("%loinc")) {
3213      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3214    } else if (s.equals("%ucum")) {
3215      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3216    } else if (s.equals("%resource")) {
3217      if (context.resource == null) {
3218        throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%resource", "no focus resource");
3219      }
3220      return new TypeDetails(CollectionStatus.SINGLETON, context.resource);
3221    } else if (s.equals("%rootResource")) {
3222      if (context.rootResource == null) {
3223        throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus rootResource");
3224      }
3225      return new TypeDetails(CollectionStatus.SINGLETON, context.rootResource);
3226    } else if (s.equals("%context")) {
3227      return context.context;
3228    } else if (s.equals("%map-codes")) {
3229      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3230    } else if (s.equals("%us-zip")) {
3231      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3232    } else if (s.startsWith("%`vs-")) {
3233      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3234    } else if (s.startsWith("%`cs-")) {
3235      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3236    } else if (s.startsWith("%`ext-")) {
3237      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3238    } else if (hostServices == null) {
3239      String varName = s.substring(1);
3240      if (context.hasDefinedVariable(varName))
3241        return context.getDefinedVariable(varName);
3242      throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
3243    } else {
3244      String varName = s.substring(1);
3245      if (context.hasDefinedVariable(varName))
3246        return context.getDefinedVariable(varName);
3247      TypeDetails v = hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant);
3248      if (v == null) {
3249        throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); 
3250      } else {
3251        return v;
3252      }
3253    }
3254  }
3255
3256  private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException {
3257    List<Base> result = new ArrayList<Base>(); 
3258    if (atEntry && context.appInfo != null && hostServices != null) {
3259      // we'll see if the name matches a constant known by the context.
3260      List<Base> temp = hostServices.resolveConstant(this, context.appInfo, exp.getName(), true, false);
3261      if (!temp.isEmpty()) {
3262        result.addAll(temp);
3263        return result;
3264      }
3265    }
3266    if (atEntry && exp.getName() != null && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up
3267      StructureDefinition sd = worker.fetchTypeDefinition(item.fhirType());
3268      if (sd == null) {
3269        // logical model
3270        if (exp.getName().equals(item.fhirType())) {
3271          result.add(item);          
3272        }
3273      } else {
3274        while (sd != null) {
3275          if (sd.getType().equals(exp.getName()) || sd.getTypeTail().equals(exp.getName())) {  
3276            result.add(item);
3277            break;
3278          }
3279          sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
3280        }
3281      }
3282    } else {
3283      getChildrenByName(item, exp.getName(), result);
3284    }
3285    if (atEntry && context.appInfo != null && hostServices != null && result.isEmpty()) {
3286      // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context.
3287      // (if the name does match, and the user wants to get the constant value, they'll have to try harder...
3288      result.addAll(hostServices.resolveConstant(this, context.appInfo, exp.getName(), false, false));
3289    }
3290    return result;
3291  }     
3292
3293  private String getParent(String rn) {
3294    return null;
3295  }
3296
3297
3298  private TypeDetails executeContextType(ExecutionTypeContext context, String name, ExpressionNode expr, boolean explicitConstant) throws PathEngineException, DefinitionException {
3299    if (hostServices == null) {
3300      throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "Context Reference");
3301    }
3302    return hostServices.resolveConstantType(this, context.appInfo, name, explicitConstant);
3303  }
3304
3305  private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry, TypeDetails focus, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException {
3306    if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && (hashTail(type).equals(exp.getName()) || isAncestor(type, exp.getName()) )) { // special case for start up
3307      return new TypeDetails(CollectionStatus.SINGLETON, type);
3308    }
3309    TypeDetails result = new TypeDetails(focus.getCollectionStatus());
3310    getChildTypesByName(type, exp.getName(), result, exp, focus, elementDependencies);
3311    return result;
3312  }
3313
3314
3315  private boolean isAncestor(String wanted, String stated) {
3316    try {
3317      StructureDefinition sd = worker.fetchTypeDefinition(wanted);
3318      while (sd != null) {
3319        if (stated.equals(sd.getTypeName())) {
3320          return true;
3321        }
3322        sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
3323      }
3324      return false;
3325    } catch (Exception e) { 
3326      return false;
3327    }
3328  }
3329
3330  private String hashTail(String type) {
3331    return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1);
3332  }
3333
3334
3335  private void evaluateParameters(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies, List<TypeDetails> paramTypes, boolean canBeNone) {
3336    int i = 0;
3337    for (ExpressionNode expr : exp.getParameters()) {
3338      if (isExpressionParameter(exp, i)) {
3339        paramTypes.add(executeType(changeThis(context, focus), focus, expr, elementDependencies, true, canBeNone, expr));
3340      } else {
3341        paramTypes.add(executeType(context, context.thisItem, expr, elementDependencies, true, canBeNone, expr));
3342      }
3343      i++;
3344    }
3345  }
3346
3347  @SuppressWarnings("unchecked")
3348  private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies,  ExpressionNode container) throws PathEngineException, DefinitionException {
3349    List<TypeDetails> paramTypes = new ArrayList<TypeDetails>();
3350    if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType || (exp.getFunction() == Function.Custom && hostServices.paramIsType(exp.getName(), 0))) {
3351      paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
3352    } else if (exp.getFunction() == Function.Repeat && exp.getParameters().size() == 1) {
3353      TypeDetails base = TypeDetails.empty();
3354      TypeDetails lFocus = focus;
3355      boolean changed = false;
3356      do {
3357        evaluateParameters(context, lFocus, exp, elementDependencies, paramTypes, true);
3358        changed = false;
3359        if (!base.contains(paramTypes.get(0))) {
3360          changed = true;
3361          base.addTypes(paramTypes.get(0));
3362          lFocus = base;
3363        }
3364      } while (changed);
3365      paramTypes.clear();
3366      paramTypes.add(base);
3367    } else if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select || exp.getFunction() == Function.Exists || 
3368        exp.getFunction() == Function.All || exp.getFunction() == Function.AllTrue || exp.getFunction() == Function.AnyTrue 
3369        || exp.getFunction() == Function.AllFalse || exp.getFunction() == Function.AnyFalse) {
3370      evaluateParameters(context, focus.toSingleton(), exp, elementDependencies, paramTypes, false);
3371    } else {
3372      evaluateParameters(context, focus, exp, elementDependencies, paramTypes, false);
3373    }
3374    if (exp.getFunction() == Function.First || exp.getFunction() == Function.Last || exp.getFunction() == Function.Tail || exp.getFunction() == Function.Skip || exp.getFunction() == Function.Take) {
3375      if (focus.getCollectionStatus() == CollectionStatus.SINGLETON) {
3376        typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_NOT_A_COLLECTION, container.toString()), I18nConstants.FHIRPATH_NOT_A_COLLECTION));
3377
3378      }
3379    }
3380    switch (exp.getFunction()) {
3381    case Empty : 
3382      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3383    case Not : 
3384      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3385    case Exists : { 
3386      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 
3387      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3388    }
3389    case SubsetOf : {
3390      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus.toUnordered()); 
3391      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3392    }
3393    case SupersetOf : {
3394      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus); 
3395      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3396    }
3397    case IsDistinct : 
3398      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3399    case Distinct : 
3400      return focus;
3401    case Count : 
3402      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
3403    case Where : 
3404      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 
3405      // special case: where the focus is Reference, and the parameter to where is resolve() "is", we will suck up the target types
3406      if (focus.hasType("Reference")) {
3407        boolean canRestrictTargets = !exp.getParameters().isEmpty();
3408        List<String> targets = new ArrayList<>();
3409        if (canRestrictTargets) {
3410          ExpressionNode p = exp.getParameters().get(0);
3411          if (p.getKind() == Kind.Function && p.getName().equals("resolve") && p.getOperation() == Operation.Is) {
3412            targets.add(p.getOpNext().getName());
3413          } else {
3414            canRestrictTargets = false;
3415          }
3416        }
3417        if (canRestrictTargets) {
3418          TypeDetails td = focus.copy();
3419          td.getTargets().clear();
3420          td.getTargets().addAll(targets);
3421          return td;
3422        } else {
3423          return focus;
3424        }
3425      } else {
3426        return focus;
3427      }
3428    case Select : 
3429      return paramTypes.get(0);
3430    case All : 
3431      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 
3432      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3433    case Repeat : 
3434      return paramTypes.get(0); 
3435    case Aggregate : 
3436      return anything(focus.getCollectionStatus());
3437    case Item : {
3438      checkOrdered(focus, "item", exp);
3439      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3440      return focus.toSingleton(); 
3441    }
3442    case As : {
3443      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
3444      String tn = checkType(focus, exp);
3445      TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn);
3446      if (td.typesHaveTargets()) {
3447        td.addTargets(focus.getTargets());
3448      }
3449      return td;
3450    }
3451    case OfType : { 
3452      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
3453      String tn = checkType(focus, exp);
3454      TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn);
3455      if (td.typesHaveTargets()) {
3456        td.addTargets(focus.getTargets());
3457      }
3458      return td;
3459    }
3460    case Type : { 
3461      boolean s = false;
3462      boolean c = false;
3463      for (ProfiledType pt : focus.getProfiledTypes()) {
3464        s = s || pt.isSystemType();
3465        c = c || !pt.isSystemType();
3466      }
3467      if (s && c) {
3468        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo, TypeDetails.FP_ClassInfo);
3469      } else if (s) {
3470        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo);
3471      } else {
3472        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo);
3473      }
3474    }
3475    case Is : {
3476      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3477      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3478    }
3479    case Single :
3480      return focus.toSingleton();
3481    case First : {
3482      checkOrdered(focus, "first", exp);
3483      return focus.toSingleton();
3484    }
3485    case Last : {
3486      checkOrdered(focus, "last", exp);
3487      return focus.toSingleton();
3488    }
3489    case Tail : {
3490      checkOrdered(focus, "tail", exp);
3491      return focus;
3492    }
3493    case Skip : {
3494      checkOrdered(focus, "skip", exp);
3495      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3496      return focus;
3497    }
3498    case Take : {
3499      checkOrdered(focus, "take", exp);
3500      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3501      return focus;
3502    }
3503    case Union : {
3504      return focus.union(paramTypes.get(0));
3505    }
3506    case Combine : {
3507      return focus.union(paramTypes.get(0));
3508    }
3509    case Intersect : {
3510      return focus.intersect(paramTypes.get(0));
3511    }
3512    case Exclude : {
3513      return focus;
3514    }
3515    case Iif : {
3516      TypeDetails types = new TypeDetails(null);
3517      checkSingleton(focus, "iif", exp);       
3518      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean));       
3519      types.update(paramTypes.get(1));
3520      if (paramTypes.size() > 2) {
3521        types.update(paramTypes.get(2));
3522      }
3523      return types;
3524    }
3525    case Lower : {
3526      checkContextString(focus, "lower", exp, true);
3527      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3528    }
3529    case Upper : {
3530      checkContextString(focus, "upper", exp, true);
3531      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3532    }
3533    case ToChars : {
3534      checkContextString(focus, "toChars", exp, true);
3535      return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); 
3536    }
3537    case IndexOf : {
3538      checkContextString(focus, "indexOf", exp, true);
3539      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3540      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 
3541    }
3542    case Substring : {
3543      checkContextString(focus, "subString", exp, true);
3544      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3545      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3546    }
3547    case StartsWith : {
3548      checkContextString(focus, "startsWith", exp, true);
3549      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3550      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3551    }
3552    case EndsWith : {
3553      checkContextString(focus, "endsWith", exp, true);
3554      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3555      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3556    }
3557    case Matches : {
3558      checkContextString(focus, "matches", exp, true);
3559      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3560      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3561    }
3562    case MatchesFull : {
3563      checkContextString(focus, "matches", exp, true);
3564      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3565      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3566    }
3567    case ReplaceMatches : {
3568      checkContextString(focus, "replaceMatches", exp, true);
3569      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3570      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3571    }
3572    case Contains : {
3573      checkContextString(focus, "contains", exp, true);
3574      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3575      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3576    }
3577    case Replace : {
3578      checkContextString(focus, "replace", exp, true);
3579      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3580      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3581    }
3582    case Length : { 
3583      checkContextPrimitive(focus, "length", false, exp);
3584      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
3585    }
3586    case Children : 
3587      return childTypes(focus, "*", exp);
3588    case Descendants : 
3589      return childTypes(focus, "**", exp);
3590    case MemberOf : {
3591      checkContextCoded(focus, "memberOf", exp);
3592      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3593      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3594    }
3595    case Trace : {
3596      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3597      return focus; 
3598    }
3599    case DefineVariable : {
3600      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.UNORDERED, TypeDetails.FP_String)); 
3601      // set the type of the variable
3602      // Actually evaluate the value of the first parameter (to get the name of the variable if possible)
3603      // and if have that, set it into the context
3604      ExpressionNode p = exp.getParameters().get(0);
3605      if (p.getKind() == Kind.Constant && p.getConstant() != null) {
3606        String varName = exp.getParameters().get(0).getConstant().primitiveValue();
3607        if (varName != null) {
3608          if (paramTypes.size() > 1)
3609            context.setDefinedVariable(varName, paramTypes.get(1));
3610          else
3611            context.setDefinedVariable(varName, focus);
3612        }
3613      } else {
3614        // this variable is not a constant, so we can't analyze what name it could have
3615      }
3616      return focus; 
3617    }
3618    case Check : {
3619      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3620      return focus; 
3621    }
3622    case Today : 
3623      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
3624    case Now : 
3625      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
3626    case Resolve : {
3627      checkContextReference(focus, "resolve", exp);
3628      return new TypeDetails(focus.getCollectionStatus(), "Resource"); 
3629    }
3630    case Extension : {
3631      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
3632      ExpressionNode p = exp.getParameters().get(0);
3633      if (p.getKind() == Kind.Constant && p.getConstant() != null) {
3634        String url = exp.getParameters().get(0).getConstant().primitiveValue();
3635        if (!Utilities.isAbsoluteUrl(url) && focus.hasType("Extension")) {
3636          TypeDetails res = new TypeDetails(CollectionStatus.ORDERED);
3637          List<String> profiles = focus.getProfiles("Extension");
3638          if (profiles != null) {
3639            for (String pt : profiles) {
3640              String extn = pt.contains("#") ? pt.substring(0, pt.indexOf("#")) : pt;
3641              String subExtn = pt.contains("#") ? pt.substring(0, pt.indexOf("#")) : null;
3642              StructureDefinition sd = worker.fetchResource(StructureDefinition.class, extn);
3643              if (sd != null) {
3644                String id = subExtn == null ? "Extension.extension:"+url : subExtn+".extension:"+url;
3645                ElementDefinition ed = sd.getSnapshot().getElementById(id);
3646                if (ed != null) {
3647                  res.addType("Extension", sd.getUrl()+"#"+id);
3648                }
3649              }
3650            }
3651          }
3652          if (res.isEmpty()) {
3653            typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_UNKNOWN_EXTENSION, url), I18nConstants.FHIRPATH_UNKNOWN_EXTENSION));            
3654          } else {
3655            return res;
3656          }
3657        } else {
3658          ExtensionDefinition ed = findExtensionDefinition(focus, url);
3659          if (ed != null) {
3660            return new TypeDetails(CollectionStatus.ORDERED, new ProfiledType(ed.sd.getUrl()));
3661          } else {
3662            typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_UNKNOWN_EXTENSION, url), I18nConstants.FHIRPATH_UNKNOWN_EXTENSION));
3663          }
3664        }
3665        return new TypeDetails(CollectionStatus.SINGLETON, "Extension");
3666      }
3667    }
3668    case AnyTrue: 
3669      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3670    case AllTrue: 
3671      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3672    case AnyFalse: 
3673      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3674    case AllFalse: 
3675      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3676    case HasValue : 
3677      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3678    case HtmlChecks1 : 
3679      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3680    case HtmlChecks2 : 
3681      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3682    case Comparable : 
3683      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3684    case Encode:
3685      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3686      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3687    case Decode:
3688      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3689      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3690    case Escape:
3691      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3692      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3693    case Unescape:
3694      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3695      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3696    case Trim:
3697      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3698    case Split:
3699      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3700      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3701    case Join:
3702      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3703      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3704    case ToInteger : {
3705      checkContextPrimitive(focus, "toInteger", true, exp);
3706      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
3707    }
3708    case ToDecimal : {
3709      checkContextPrimitive(focus, "toDecimal", true, exp);
3710      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
3711    }
3712    case ToString : {
3713      checkContextPrimitive(focus, "toString", true, exp);
3714      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3715    }
3716    case ToQuantity : {
3717      checkContextPrimitive(focus, "toQuantity", true, exp);
3718      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity);
3719    }
3720    case ToBoolean : {
3721      checkContextPrimitive(focus, "toBoolean", false, exp);
3722      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3723    }
3724    case ToDateTime : {
3725      checkContextPrimitive(focus, "ToDateTime", false, exp);
3726      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
3727    }
3728    case ToTime : {
3729      checkContextPrimitive(focus, "ToTime", false, exp);
3730      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time);
3731    }
3732    case ConvertsToString : 
3733    case ConvertsToQuantity :{
3734      checkContextPrimitive(focus, exp.getFunction().toCode(), true, exp);
3735      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3736    } 
3737    case ConvertsToInteger : 
3738    case ConvertsToDecimal : 
3739    case ConvertsToDateTime : 
3740    case ConvertsToDate : 
3741    case ConvertsToTime : 
3742    case ConvertsToBoolean : {
3743      checkContextPrimitive(focus, exp.getFunction().toCode(), false, exp);
3744      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3745    }
3746    case ConformsTo: {
3747      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3748      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);       
3749    }
3750    case Abs : {
3751      checkContextNumerical(focus, "abs", exp);
3752      return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes());       
3753    }
3754    case Truncate :
3755    case Floor : 
3756    case Ceiling : {
3757      checkContextDecimal(focus, exp.getFunction().toCode(), exp);
3758      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);       
3759    }  
3760
3761    case Round :{
3762      checkContextDecimal(focus, "round", exp);
3763      if (paramTypes.size() > 0) {
3764        checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer));
3765      }
3766      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);       
3767    } 
3768
3769    case Exp : 
3770    case Ln : 
3771    case Sqrt : {
3772      checkContextNumerical(focus, exp.getFunction().toCode(), exp);      
3773      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);       
3774    }
3775    case Log :  {
3776      checkContextNumerical(focus, exp.getFunction().toCode(), exp);      
3777      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS));
3778      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);       
3779    }
3780    case Power : {
3781      checkContextNumerical(focus, exp.getFunction().toCode(), exp);      
3782      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS));
3783      return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes());       
3784    }
3785
3786    case LowBoundary:
3787    case HighBoundary: {
3788      checkContextContinuous(focus, exp.getFunction().toCode(), exp, true);      
3789      if (paramTypes.size() > 0) {
3790        checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer));
3791      }
3792      if ((focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) {
3793        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime);       
3794      } else if ((focus.hasType("time"))) {
3795          return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time, TypeDetails.FP_Time);       
3796      } else if (focus.hasType("decimal") || focus.hasType("integer")) {
3797        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);       
3798      } else {
3799        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);       
3800      }
3801    }
3802    case Precision: {
3803      checkContextContinuous(focus, exp.getFunction().toCode(), exp, false);      
3804      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);       
3805    }
3806    case hasTemplateIdOf: {
3807      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3808      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3809    }
3810    case Custom : {
3811      return hostServices.checkFunction(this, context.appInfo,exp.getName(), focus, paramTypes);
3812    }
3813    default:
3814      break;
3815    }
3816    throw new Error("not Implemented yet");
3817  }
3818
3819  private ExtensionDefinition findExtensionDefinition(TypeDetails focus, String url) {
3820    if (Utilities.isAbsoluteUrl(url)) {
3821      StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url);
3822      if (sd == null) {
3823        return null;
3824      } else {
3825        return new ExtensionDefinition(true, sd, sd.getSnapshot().getElementFirstRep());
3826      }
3827    }
3828    StructureDefinition sd = worker.fetchResource(StructureDefinition.class, focus.getType());
3829    if (sd != null) {
3830      for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3831        if (ed.hasFixed() && url.equals(ed.getFixed().primitiveValue())) {
3832          return new ExtensionDefinition(false, sd, ed);
3833        }
3834      }
3835    }
3836    return null;
3837  }
3838
3839  private String checkType(TypeDetails focus, ExpressionNode exp) {
3840    String tn;
3841    if (exp.getParameters().get(0).getInner() != null) {
3842      tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName();
3843    } else {
3844      tn = "FHIR."+exp.getParameters().get(0).getName();
3845    }
3846    if (tn.startsWith("System.")) {
3847      tn = tn.substring(7);
3848    } else if (tn.startsWith("FHIR.")) {
3849      tn = Utilities.pathURL(Constants.NS_FHIR_ROOT, "StructureDefinition", tn.substring(5));
3850    } else if (tn.startsWith("CDA.")) {
3851      tn = Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", tn.substring(4));
3852    }
3853    
3854    if (typeCastIsImpossible(focus, tn)) {
3855      typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE, focus.describeMin(), tn, exp.toString()), I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE));
3856    }
3857    return tn;
3858  }
3859
3860  private boolean typeCastIsImpossible(TypeDetails focus, String tn) {
3861    for (ProfiledType pt : focus.getProfiledTypes()) {
3862      if (!pt.hasProfiles()) { // get back to this later
3863        String purl = tn;
3864        while (purl != null && !purl.equals(pt.getUri())) {
3865          StructureDefinition sd = worker.fetchResource(StructureDefinition.class, purl);
3866          purl = sd == null ? null : sd.getBaseDefinition();
3867        }
3868        if (purl != null) {
3869          return false;
3870        }
3871      }
3872    }
3873    return !focus.hasType(tn);
3874  }
3875
3876  private boolean isExpressionParameter(ExpressionNode exp, int i) {
3877    switch (i) {
3878    case 0:
3879      return exp.getFunction() == Function.Where || exp.getFunction() == Function.Exists || exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate;
3880    case 1:
3881      return exp.getFunction() == Function.Trace || exp.getFunction() == Function.DefineVariable;
3882    default: 
3883      return false;
3884    }
3885  }
3886
3887
3888  private void checkParamTypes(ExpressionNode expr, String funcName,List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException {
3889    int i = 0;
3890    for (TypeDetails pt : typeSet) {
3891      if (i == paramTypes.size()) {
3892        return;
3893      }
3894      TypeDetails actual = paramTypes.get(i);
3895      i++;
3896      for (String a : actual.getTypes()) {
3897        if (!pt.hasType(worker, a)) {
3898          throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, funcName, i, a, pt.toString());
3899        }
3900      }
3901      if (actual.getCollectionStatus() != CollectionStatus.SINGLETON && pt.getCollectionStatus() == CollectionStatus.SINGLETON) {
3902        typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_PARAMETER, funcName, i, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_PARAMETER));
3903      }
3904    }
3905  }
3906
3907  private void checkSingleton(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3908    if (focus.getCollectionStatus() != CollectionStatus.SINGLETON) {
3909      typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT, name, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT));
3910    }
3911  }
3912
3913  private void checkOrdered(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3914    if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) {
3915      throw makeException(expr, I18nConstants.FHIRPATH_ORDERED_ONLY, name);
3916    }
3917  }
3918
3919  private void checkContextReference(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3920    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "url") && !focus.hasType(worker, "Reference") && !focus.hasType(worker, "canonical")) {
3921      throw makeException(expr, I18nConstants.FHIRPATH_REFERENCE_ONLY, name, focus.describe());
3922    }
3923  }
3924
3925
3926  private void checkContextCoded(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3927    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) {
3928      throw makeException(expr, I18nConstants.FHIRPATH_CODED_ONLY, name, focus.describe());
3929    }
3930  }
3931
3932
3933  private void checkContextString(TypeDetails focus, String name, ExpressionNode expr, boolean sing) throws PathEngineException {
3934    if (!focus.hasNoTypes() && !focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "url") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) {
3935      throw makeException(expr, sing ? I18nConstants.FHIRPATH_STRING_SING_ONLY : I18nConstants.FHIRPATH_STRING_ORD_ONLY, name, focus.describe());
3936    }
3937  }
3938
3939
3940  private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty, ExpressionNode expr) throws PathEngineException {
3941    if (!focus.hasNoTypes()) {
3942      if (canQty) {
3943        if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) {
3944          throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), "Quantity, "+primitiveTypes.toString());
3945        }
3946      } else if (!focus.hasType(primitiveTypes)) {
3947        throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), primitiveTypes.toString());
3948      }
3949    }
3950  }
3951
3952  private void checkContextNumerical(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3953    if (!focus.hasNoTypes() && !focus.hasType("integer")  && !focus.hasType("decimal") && !focus.hasType("Quantity")) {
3954      throw makeException(expr, I18nConstants.FHIRPATH_NUMERICAL_ONLY, name, focus.describe());
3955    }    
3956  }
3957
3958  private void checkContextDecimal(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3959    if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("integer")) {
3960      throw makeException(expr, I18nConstants.FHIRPATH_DECIMAL_ONLY, name, focus.describe());
3961    }    
3962  }
3963
3964  private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr, boolean allowInteger) throws PathEngineException {
3965    if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time") && !focus.hasType("Quantity") && !(allowInteger && focus.hasType("integer"))) {
3966      throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe());
3967    }    
3968  }
3969
3970  private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) throws PathEngineException, DefinitionException {
3971    TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
3972    for (String f : focus.getTypes()) {
3973      getChildTypesByName(f, mask, result, expr, null, null);
3974    }
3975    return result;
3976  }
3977
3978  private TypeDetails anything(CollectionStatus status) {
3979    return new TypeDetails(status, allTypes.keySet());
3980  }
3981
3982  //    private boolean isPrimitiveType(String s) {
3983  //            return s.equals("boolean") || s.equals("integer") || s.equals("decimal") || s.equals("base64Binary") || s.equals("instant") || s.equals("string") || s.equals("uri") || s.equals("date") || s.equals("dateTime") || s.equals("time") || s.equals("code") || s.equals("oid") || s.equals("id") || s.equals("unsignedInt") || s.equals("positiveInt") || s.equals("markdown");
3984  //    }
3985
3986  private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3987    switch (exp.getFunction()) {
3988    case Empty : return funcEmpty(context, focus, exp);
3989    case Not : return funcNot(context, focus, exp);
3990    case Exists : return funcExists(context, focus, exp);
3991    case SubsetOf : return funcSubsetOf(context, focus, exp);
3992    case SupersetOf : return funcSupersetOf(context, focus, exp);
3993    case IsDistinct : return funcIsDistinct(context, focus, exp);
3994    case Distinct : return funcDistinct(context, focus, exp);
3995    case Count : return funcCount(context, focus, exp);
3996    case Where : return funcWhere(context, focus, exp);
3997    case Select : return funcSelect(context, focus, exp);
3998    case All : return funcAll(context, focus, exp);
3999    case Repeat : return funcRepeat(context, focus, exp);
4000    case Aggregate : return funcAggregate(context, focus, exp);
4001    case Item : return funcItem(context, focus, exp);
4002    case As : return funcAs(context, focus, exp);
4003    case OfType : return funcOfType(context, focus, exp);
4004    case Type : return funcType(context, focus, exp);
4005    case Is : return funcIs(context, focus, exp);
4006    case Single : return funcSingle(context, focus, exp);
4007    case First : return funcFirst(context, focus, exp);
4008    case Last : return funcLast(context, focus, exp);
4009    case Tail : return funcTail(context, focus, exp);
4010    case Skip : return funcSkip(context, focus, exp);
4011    case Take : return funcTake(context, focus, exp);
4012    case Union : return funcUnion(context, focus, exp);
4013    case Combine : return funcCombine(context, focus, exp);
4014    case Intersect : return funcIntersect(context, focus, exp);
4015    case Exclude : return funcExclude(context, focus, exp);
4016    case Iif : return funcIif(context, focus, exp);
4017    case Lower : return funcLower(context, focus, exp);
4018    case Upper : return funcUpper(context, focus, exp);
4019    case ToChars : return funcToChars(context, focus, exp);
4020    case IndexOf : return funcIndexOf(context, focus, exp);
4021    case Substring : return funcSubstring(context, focus, exp);
4022    case StartsWith : return funcStartsWith(context, focus, exp);
4023    case EndsWith : return funcEndsWith(context, focus, exp);
4024    case Matches : return funcMatches(context, focus, exp);
4025    case MatchesFull : return funcMatchesFull(context, focus, exp);
4026    case ReplaceMatches : return funcReplaceMatches(context, focus, exp);
4027    case Contains : return funcContains(context, focus, exp);
4028    case Replace : return funcReplace(context, focus, exp);
4029    case Length : return funcLength(context, focus, exp);
4030    case Children : return funcChildren(context, focus, exp);
4031    case Descendants : return funcDescendants(context, focus, exp);
4032    case MemberOf : return funcMemberOf(context, focus, exp);
4033    case Trace : return funcTrace(context, focus, exp);
4034    case DefineVariable : return funcDefineVariable(context, focus, exp);
4035    case Check : return funcCheck(context, focus, exp);
4036    case Today : return funcToday(context, focus, exp);
4037    case Now : return funcNow(context, focus, exp);
4038    case Resolve : return funcResolve(context, focus, exp);
4039    case Extension : return funcExtension(context, focus, exp);
4040    case AnyFalse: return funcAnyFalse(context, focus, exp);
4041    case AllFalse: return funcAllFalse(context, focus, exp);
4042    case AnyTrue: return funcAnyTrue(context, focus, exp);
4043    case AllTrue: return funcAllTrue(context, focus, exp);
4044    case HasValue : return funcHasValue(context, focus, exp);
4045    case Encode : return funcEncode(context, focus, exp);
4046    case Decode : return funcDecode(context, focus, exp);
4047    case Escape : return funcEscape(context, focus, exp);
4048    case Unescape : return funcUnescape(context, focus, exp);
4049    case Trim : return funcTrim(context, focus, exp);
4050    case Split : return funcSplit(context, focus, exp);
4051    case Join : return funcJoin(context, focus, exp); 
4052    case HtmlChecks1 : return funcHtmlChecks1(context, focus, exp);
4053    case HtmlChecks2 : return funcHtmlChecks2(context, focus, exp);
4054    case Comparable : return funcComparable(context, focus, exp);
4055    case ToInteger : return funcToInteger(context, focus, exp);
4056    case ToDecimal : return funcToDecimal(context, focus, exp);
4057    case ToString : return funcToString(context, focus, exp);
4058    case ToBoolean : return funcToBoolean(context, focus, exp);
4059    case ToQuantity : return funcToQuantity(context, focus, exp);
4060    case ToDateTime : return funcToDateTime(context, focus, exp);
4061    case ToTime : return funcToTime(context, focus, exp);
4062    case ConvertsToInteger : return funcIsInteger(context, focus, exp);
4063    case ConvertsToDecimal : return funcIsDecimal(context, focus, exp);
4064    case ConvertsToString : return funcIsString(context, focus, exp);
4065    case ConvertsToBoolean : return funcIsBoolean(context, focus, exp);
4066    case ConvertsToQuantity : return funcIsQuantity(context, focus, exp);
4067    case ConvertsToDateTime : return funcIsDateTime(context, focus, exp);
4068    case ConvertsToDate : return funcIsDate(context, focus, exp);
4069    case ConvertsToTime : return funcIsTime(context, focus, exp);
4070    case ConformsTo : return funcConformsTo(context, focus, exp);
4071    case Round : return funcRound(context, focus, exp); 
4072    case Sqrt : return funcSqrt(context, focus, exp); 
4073    case Abs : return funcAbs(context, focus, exp); 
4074    case Ceiling : return funcCeiling(context, focus, exp); 
4075    case Exp : return funcExp(context, focus, exp); 
4076    case Floor : return funcFloor(context, focus, exp); 
4077    case Ln : return funcLn(context, focus, exp); 
4078    case Log : return funcLog(context, focus, exp); 
4079    case Power : return funcPower(context, focus, exp); 
4080    case Truncate : return funcTruncate(context, focus, exp);
4081    case LowBoundary : return funcLowBoundary(context, focus, exp);
4082    case HighBoundary : return funcHighBoundary(context, focus, exp);
4083    case Precision : return funcPrecision(context, focus, exp);
4084    case hasTemplateIdOf: return funcHasTemplateIdOf(context, focus, exp);
4085
4086
4087    case Custom: { 
4088      List<List<Base>> params = new ArrayList<List<Base>>();
4089      if (hostServices.paramIsType( exp.getName(), 0)) {
4090        if (exp.getParameters().size() > 0) {
4091          String tn;
4092          if (exp.getParameters().get(0).getInner() != null) {
4093            tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName();
4094          } else {
4095            tn = "FHIR."+exp.getParameters().get(0).getName();
4096          }
4097          List<Base> p = new ArrayList<>();
4098          p.add(new CodeType(tn));
4099          params.add(p);
4100        }
4101      } else {
4102        for (ExpressionNode p : exp.getParameters()) {
4103          params.add(execute(context, focus, p, true));
4104        }
4105      }
4106      return hostServices.executeFunction(this, context.appInfo, focus, exp.getName(), params);
4107    }
4108    default:
4109      throw new Error("not Implemented yet");
4110    }
4111  }
4112
4113  private List<Base> funcHasTemplateIdOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4114    List<Base> result = new ArrayList<Base>();
4115    List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
4116    String sw = convertToString(swb);
4117
4118    StructureDefinition sd = this.worker.fetchResource(StructureDefinition.class, sw);
4119    if (focus.size() == 1 && sd != null) {
4120      boolean found = false;
4121      for (Identifier id : sd.getIdentifier()) {
4122        if (id.getValue().startsWith("urn:hl7ii:")) {   
4123          String[] p = id.getValue().split("\\:");
4124          if (p.length == 4) {
4125            found = found || hasTemplateId(focus.get(0), p[2], p[3]);
4126          }
4127        } else if (id.getValue().startsWith("urn:oid:")) {
4128          found = found || hasTemplateId(focus.get(0), id.getValue().substring(8));          
4129        }
4130      }
4131      result.add(new BooleanType(found));
4132    }
4133    return result;
4134  }
4135
4136  private boolean hasTemplateId(Base base, String rv) {
4137    List<Base> templateIds = base.listChildrenByName("templateId");
4138    for (Base templateId : templateIds) {
4139      Base root = templateId.getChildValueByName("root");
4140      Base extension = templateId.getChildValueByName("extension");
4141      if (extension == null && root != null && rv.equals(root.primitiveValue())) {
4142        return true;
4143      }
4144    }    
4145    return false;
4146  }
4147
4148  private boolean hasTemplateId(Base base, String rv, String ev) {
4149    List<Base> templateIds = base.listChildrenByName("templateId");
4150    for (Base templateId : templateIds) {
4151      Base root = templateId.getChildValueByName("root");
4152      Base extension = templateId.getChildValueByName("extension");
4153      if (extension != null && ev.equals(extension.primitiveValue()) && root != null && rv.equals(root.primitiveValue())) {
4154        return true;
4155      }
4156    }    
4157    return false;
4158  }
4159  
4160  private List<Base> funcSqrt(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4161    if (focus.size() != 1) {
4162      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "sqrt", focus.size());
4163    }
4164    Base base = focus.get(0);
4165    List<Base> result = new ArrayList<Base>();
4166    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4167      Double d = Double.parseDouble(base.primitiveValue());
4168      try {
4169        result.add(new DecimalType(Math.sqrt(d)));
4170      } catch (Exception e) {
4171        // just return nothing
4172      }
4173    } else {
4174      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal");
4175    }
4176    return result;
4177  }
4178
4179
4180  private List<Base> funcAbs(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4181    if (focus.size() != 1) {
4182      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "abs", focus.size());
4183    }
4184    Base base = focus.get(0);
4185    List<Base> result = new ArrayList<Base>();
4186    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4187      Double d = Double.parseDouble(base.primitiveValue());
4188      try {
4189        result.add(new DecimalType(Math.abs(d)));
4190      } catch (Exception e) {
4191        // just return nothing
4192      }
4193    } else if (base.hasType("Quantity")) {
4194      Quantity qty = (Quantity) base;
4195      result.add(qty.copy().setValue(qty.getValue().abs()));
4196    } else {
4197      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "abs", "(focus)", base.fhirType(), "integer or decimal");
4198    }
4199    return result;
4200  }
4201
4202
4203  private List<Base> funcCeiling(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4204    if (focus.size() != 1) {
4205      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ceiling", focus.size());
4206    }
4207    Base base = focus.get(0);
4208    List<Base> result = new ArrayList<Base>();
4209    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4210      Double d = Double.parseDouble(base.primitiveValue());
4211      try {result.add(new IntegerType((int) Math.ceil(d)));
4212      } catch (Exception e) {
4213        // just return nothing
4214      }
4215    } else {
4216      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ceiling", "(focus)", base.fhirType(), "integer or decimal");
4217    }
4218    return result;
4219  }
4220
4221  private List<Base> funcFloor(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4222    if (focus.size() != 1) {
4223      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "floor", focus.size());
4224    }
4225    Base base = focus.get(0);
4226    List<Base> result = new ArrayList<Base>();
4227    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4228      Double d = Double.parseDouble(base.primitiveValue());
4229      try {
4230        result.add(new IntegerType((int) Math.floor(d)));
4231      } catch (Exception e) {
4232        // just return nothing
4233      }
4234    } else {
4235      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "floor", "(focus)", base.fhirType(), "integer or decimal");
4236    }
4237    return result;
4238  }
4239
4240
4241  private List<Base> funcExp(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4242    if (focus.size() == 0) {
4243      return new ArrayList<Base>();
4244    }
4245    if (focus.size() > 1) {
4246      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "exp", focus.size());
4247    }
4248    Base base = focus.get(0);
4249    List<Base> result = new ArrayList<Base>();
4250    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4251      Double d = Double.parseDouble(base.primitiveValue());
4252      try {
4253        result.add(new DecimalType(Math.exp(d)));
4254      } catch (Exception e) {
4255        // just return nothing
4256      }
4257
4258    } else {
4259      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "exp", "(focus)", base.fhirType(), "integer or decimal");
4260    }
4261    return result;  
4262  }
4263
4264
4265  private List<Base> funcLn(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4266    if (focus.size() != 1) {
4267      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ln", focus.size());
4268    }
4269    Base base = focus.get(0);
4270    List<Base> result = new ArrayList<Base>();
4271    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4272      Double d = Double.parseDouble(base.primitiveValue());
4273      try {
4274        result.add(new DecimalType(Math.log(d)));
4275      } catch (Exception e) {
4276        // just return nothing
4277      }        
4278    } else {
4279      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ln", "(focus)", base.fhirType(), "integer or decimal");
4280    }
4281    return result;
4282  }
4283
4284
4285  private List<Base> funcLog(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4286    if (focus.size() != 1) {
4287      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "log", focus.size());
4288    }
4289    Base base = focus.get(0);
4290    List<Base> result = new ArrayList<Base>();
4291    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4292      List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
4293      if (n1.size() != 1) {
4294        throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "0", "Multiple Values", "integer or decimal");
4295      }
4296      Double e = Double.parseDouble(n1.get(0).primitiveValue());
4297      Double d = Double.parseDouble(base.primitiveValue());
4298      try {
4299        result.add(new DecimalType(customLog(e, d)));
4300      } catch (Exception ex) {
4301        // just return nothing
4302      }
4303    } else {
4304      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "(focus)", base.fhirType(), "integer or decimal");
4305    }
4306    return result;
4307  }
4308
4309  private static double customLog(double base, double logNumber) {
4310    return Math.log(logNumber) / Math.log(base);
4311  }
4312
4313  private List<Base> funcPower(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4314    if (focus.size() != 1) {
4315      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "power", focus.size());
4316    }
4317    Base base = focus.get(0);
4318    List<Base> result = new ArrayList<Base>();
4319    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4320      List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
4321      if (n1.size() != 1) {
4322        throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer or decimal");
4323      }
4324      Double e = Double.parseDouble(n1.get(0).primitiveValue());
4325      Double d = Double.parseDouble(base.primitiveValue());
4326      try {
4327        result.add(new DecimalType(Math.pow(d, e)));
4328      } catch (Exception ex) {
4329        // just return nothing
4330      }
4331    } else {
4332      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "(focus)", base.fhirType(), "integer or decimal");
4333    }
4334    return result;
4335  }
4336
4337  private List<Base> funcTruncate(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4338    if (focus.size() != 1) {
4339      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "truncate", focus.size());
4340    }
4341    Base base = focus.get(0);
4342    List<Base> result = new ArrayList<Base>();
4343    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4344      String s = base.primitiveValue();
4345      if (s.contains(".")) {
4346        s = s.substring(0, s.indexOf("."));
4347      }
4348      result.add(new IntegerType(s));
4349    } else {
4350      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal");
4351    }
4352    return result;
4353  }
4354
4355  private List<Base> funcLowBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4356    if (focus.size() == 0) {
4357      return makeNull();
4358    }
4359    if (focus.size() > 1) {
4360      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "lowBoundary", focus.size());
4361    }
4362    Integer precision = null;
4363    if (expr.getParameters().size() > 0) {
4364      List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
4365      if (n1.size() != 1) {
4366        throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer");
4367      }
4368      precision = Integer.parseInt(n1.get(0).primitiveValue());
4369    }
4370    
4371    Base base = focus.get(0);
4372    List<Base> result = new ArrayList<Base>();
4373    
4374    if (base.hasType("decimal")) {
4375      if (precision == null || (precision >= 0 && precision < 17)) {
4376        result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision)));
4377      }
4378    } else if (base.hasType("integer")) {
4379      if (precision == null || (precision >= 0 && precision < 17)) {
4380        result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision)));
4381      }
4382    } else if (base.hasType("date")) {
4383      result.add(new DateTimeType(DateTimeUtil.lowBoundaryForDate(base.primitiveValue(), precision == null ? 10 : precision)));
4384    } else if (base.hasType("dateTime")) {
4385      result.add(new DateTimeType(DateTimeUtil.lowBoundaryForDate(base.primitiveValue(), precision == null ? 17 : precision)));
4386    } else if (base.hasType("time")) {
4387      result.add(new TimeType(DateTimeUtil.lowBoundaryForTime(base.primitiveValue(), precision == null ? 9 : precision)));
4388    } else if (base.hasType("Quantity")) {
4389      String value = getNamedValue(base, "value");
4390      Base v = base.copy();
4391      v.setProperty("value", new DecimalType(Utilities.lowBoundaryForDecimal(value, precision == null ? 8 : precision)));
4392      result.add(v);
4393    } else {
4394      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
4395    }
4396    return result;
4397  }
4398  
4399  private List<Base> funcHighBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4400    if (focus.size() == 0) {
4401      return makeNull();
4402    }
4403    if (focus.size() > 1) {
4404      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size());
4405    }
4406    Integer precision = null;
4407    if (expr.getParameters().size() > 0) {
4408      List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
4409      if (n1.size() != 1) {
4410        throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer");
4411      }
4412      precision = Integer.parseInt(n1.get(0).primitiveValue());
4413    }
4414    
4415    
4416    Base base = focus.get(0);
4417    List<Base> result = new ArrayList<Base>();
4418    if (base.hasType("decimal")) {
4419      if (precision == null || (precision >= 0 && precision < 17)) {
4420        result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision)));
4421      }
4422    } else if (base.hasType("integer")) {
4423      if (precision == null || (precision >= 0 && precision < 17)) {
4424        result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision)));
4425      }
4426    } else if (base.hasType("date")) {
4427      result.add(new DateTimeType(DateTimeUtil.highBoundaryForDate(base.primitiveValue(), precision == null ? 10 : precision)));
4428    } else if (base.hasType("dateTime")) {
4429      result.add(new DateTimeType(DateTimeUtil.highBoundaryForDate(base.primitiveValue(), precision == null ? 17 : precision)));
4430    } else if (base.hasType("time")) {
4431      result.add(new TimeType(DateTimeUtil.highBoundaryForTime(base.primitiveValue(), precision == null ? 9 : precision)));
4432    } else if (base.hasType("Quantity")) {
4433      String value = getNamedValue(base, "value");
4434      Base v = base.copy();
4435      v.setProperty("value", new DecimalType(Utilities.highBoundaryForDecimal(value, precision == null ? 8 : precision)));
4436      result.add(v);
4437    } else {
4438      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
4439    }
4440    return result;
4441  }
4442  
4443  private List<Base> funcPrecision(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4444    if (focus.size() != 1) {
4445      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size());
4446    }
4447    Base base = focus.get(0);
4448    List<Base> result = new ArrayList<Base>();
4449    if (base.hasType("decimal")) {
4450      result.add(new IntegerType(Utilities.getDecimalPrecision(base.primitiveValue())));
4451    } else if (base.hasType("date") || base.hasType("dateTime")) {
4452      result.add(new IntegerType(DateTimeUtil.getDatePrecision(base.primitiveValue())));
4453    } else if (base.hasType("time")) {
4454      result.add(new IntegerType(DateTimeUtil.getTimePrecision(base.primitiveValue())));
4455    } else {
4456      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
4457    }
4458    return result;
4459  }
4460
4461  private List<Base> funcRound(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4462    if (focus.size() != 1) {
4463      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "round", focus.size());
4464    }
4465    Base base = focus.get(0);
4466    List<Base> result = new ArrayList<Base>();
4467    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4468      int i = 0;
4469      if (expr.getParameters().size() == 1) {
4470        List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
4471        if (n1.size() != 1) {
4472          throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer");
4473        }
4474        i = Integer.parseInt(n1.get(0).primitiveValue());
4475      }
4476      BigDecimal  d = new BigDecimal (base.primitiveValue());
4477      result.add(new DecimalType(d.setScale(i, RoundingMode.HALF_UP)));
4478    } else {
4479      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "round", "(focus)", base.fhirType(), "integer or decimal");
4480    }
4481    return result;
4482  }
4483
4484  private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
4485  private ContextUtilities cu;
4486  public static String bytesToHex(byte[] bytes) {
4487    char[] hexChars = new char[bytes.length * 2];
4488    for (int j = 0; j < bytes.length; j++) {
4489      int v = bytes[j] & 0xFF;
4490      hexChars[j * 2] = HEX_ARRAY[v >>> 4];
4491      hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
4492    }
4493    return new String(hexChars);
4494  }
4495
4496  public static byte[] hexStringToByteArray(String s) {
4497    int len = s.length();
4498    byte[] data = new byte[len / 2];
4499    for (int i = 0; i < len; i += 2) {
4500      data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16));
4501    }
4502    return data;
4503  }
4504
4505  private List<Base> funcEncode(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4506    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4507    String param = nl.get(0).primitiveValue();
4508
4509    List<Base> result = new ArrayList<Base>();
4510
4511    if (focus.size() == 1) {
4512      String cnt = focus.get(0).primitiveValue();
4513      if ("hex".equals(param)) {
4514        result.add(new StringType(bytesToHex(cnt.getBytes())));        
4515      } else if ("base64".equals(param)) {
4516        Base64.Encoder enc = Base64.getEncoder();
4517        result.add(new StringType(enc.encodeToString(cnt.getBytes())));
4518      } else if ("urlbase64".equals(param)) {
4519        Base64.Encoder enc = Base64.getUrlEncoder();
4520        result.add(new StringType(enc.encodeToString(cnt.getBytes())));
4521      }
4522    }
4523    return result;      
4524  }
4525
4526  private List<Base> funcDecode(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4527    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4528    String param = nl.get(0).primitiveValue();
4529
4530    List<Base> result = new ArrayList<Base>();
4531    if (focus.size() == 1) {
4532      String cnt = focus.get(0).primitiveValue();
4533      if ("hex".equals(param)) {
4534        result.add(new StringType(new String(hexStringToByteArray(cnt))));        
4535      } else if ("base64".equals(param)) {
4536        Base64.Decoder enc = Base64.getDecoder();
4537        result.add(new StringType(new String(enc.decode(cnt))));
4538      } else if ("urlbase64".equals(param)) {
4539        Base64.Decoder enc = Base64.getUrlDecoder();
4540        result.add(new StringType(new String(enc.decode(cnt))));
4541      }
4542    }
4543    return result;  
4544  }
4545
4546  private List<Base> funcEscape(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4547    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4548    String param = nl.get(0).primitiveValue();
4549
4550    List<Base> result = new ArrayList<Base>();
4551    if (focus.size() == 1) {
4552      String cnt = focus.get(0).primitiveValue();
4553      if ("html".equals(param)) {
4554        result.add(new StringType(Utilities.escapeXml(cnt)));        
4555      } else if ("json".equals(param)) {
4556        result.add(new StringType(Utilities.escapeJson(cnt)));        
4557      } else if ("url".equals(param)) {
4558        result.add(new StringType(Utilities.URLEncode(cnt)));        
4559      } else if ("md".equals(param)) {
4560        result.add(new StringType(MarkDownProcessor.makeStringSafeAsMarkdown(cnt)));        
4561      }
4562    }
4563
4564    return result;  
4565  }
4566
4567  private List<Base> funcUnescape(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4568    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4569    String param = nl.get(0).primitiveValue();
4570
4571    List<Base> result = new ArrayList<Base>();
4572    if (focus.size() == 1) {
4573      String cnt = focus.get(0).primitiveValue();
4574      if ("html".equals(param)) {
4575        result.add(new StringType(Utilities.unescapeXml(cnt)));        
4576      } else if ("json".equals(param)) {
4577        result.add(new StringType(Utilities.unescapeJson(cnt)));        
4578      } else if ("url".equals(param)) {
4579        result.add(new StringType(Utilities.URLDecode(cnt)));        
4580      } else if ("md".equals(param)) {
4581        result.add(new StringType(MarkDownProcessor.makeMarkdownForString(cnt)));        
4582      }
4583    }
4584
4585    return result;  
4586  }
4587
4588  private List<Base> funcTrim(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4589    List<Base> result = new ArrayList<Base>();
4590    if (focus.size() == 1) {
4591      String cnt = focus.get(0).primitiveValue();
4592      result.add(new StringType(cnt.trim()));
4593    }
4594    return result;  
4595  }
4596
4597  private List<Base> funcSplit(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4598    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4599    String param = nl.get(0).primitiveValue();
4600
4601    List<Base> result = new ArrayList<Base>();
4602    if (focus.size() == 1) {
4603      String cnt = focus.get(0).primitiveValue();
4604      String[] sl = Utilities.simpleSplit(cnt, param);
4605      for (String s : sl) {
4606        result.add(new StringType(s));
4607      }
4608    }
4609    return result;  
4610  }
4611
4612  private List<Base> funcJoin(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4613    List<Base> nl = exp.getParameters().size() > 0 ? execute(context, focus, exp.getParameters().get(0), true) : new ArrayList<Base>();
4614    String param = "";
4615    String param2 = "";
4616    if (exp.getParameters().size() > 0) {
4617      param = nl.get(0).primitiveValue();
4618      param2 = param;
4619      if (exp.getParameters().size() == 2) {
4620        nl = execute(context, focus, exp.getParameters().get(1), true);
4621        param2 = nl.get(0).primitiveValue();
4622      }
4623    }
4624    
4625    List<Base> result = new ArrayList<Base>();
4626    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(param, param2);
4627    for (Base i : focus) {
4628      b.append(i.primitiveValue());    
4629    }
4630    result.add(new StringType(b.toString()));
4631    return result;  
4632  }
4633
4634  private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4635    // todo: actually check the HTML
4636    if (focus.size() != 1) {
4637      return makeBoolean(false);          
4638    }
4639    XhtmlNode x = focus.get(0).getXhtml();
4640    if (x == null) {
4641      return makeBoolean(false);                
4642    }
4643    boolean ok = checkHtmlNames(x, true);
4644    if (ok && VersionUtilities.isR6Plus(this.worker.getVersion())) {
4645      ok = checkForContent(x);
4646    }
4647    return makeBoolean(ok);    
4648  }
4649
4650  private List<Base> funcHtmlChecks2(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4651    // todo: actually check the HTML
4652    if (focus.size() != 1) {
4653      return makeBoolean(false);          
4654    }
4655    XhtmlNode x = focus.get(0).getXhtml();
4656    if (x == null) {
4657      return makeBoolean(false);                
4658    }
4659    return makeBoolean(checkForContent(x));    
4660  }
4661
4662  private boolean checkForContent(XhtmlNode x) {
4663    if ((x.getNodeType() == NodeType.Text && !Utilities.noString(x.getContent().trim())) || (x.getNodeType() == NodeType.Element && "img".equals(x.getName()))) {
4664      return true;
4665    }
4666    for (XhtmlNode c : x.getChildNodes()) {
4667      if (checkForContent(c)) {
4668        return true;
4669      }
4670    }
4671    return false;
4672  }
4673
4674  private List<Base> funcComparable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4675    if (focus.size() != 1 || !(focus.get(0).fhirType().equals("Quantity"))) {
4676      return makeBoolean(false);          
4677    }
4678    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4679    if (nl.size() != 1 || !(nl.get(0).fhirType().equals("Quantity"))) {
4680      return makeBoolean(false);          
4681    }
4682    String s1 = getNamedValue(focus.get(0), "system");
4683    String u1 = getNamedValue(focus.get(0), "code");
4684    String s2 = getNamedValue(nl.get(0), "system");
4685    String u2 = getNamedValue(nl.get(0), "code");
4686    
4687    if (s1 == null || s2 == null || !s1.equals(s2)) {
4688      return makeBoolean(false);                
4689    }
4690    if (u1 == null || u2 == null) {
4691      return makeBoolean(false);                
4692    }
4693    if (u1.equals(u2)) {
4694      return makeBoolean(true);
4695    }
4696    if (s1.equals("http://unitsofmeasure.org") && worker.getUcumService() != null) {
4697      try {
4698        return makeBoolean(worker.getUcumService().isComparable(u1, u2));
4699      } catch (UcumException e) {
4700        return makeBoolean(false);  
4701      }  
4702    } else {
4703      return makeBoolean(false);  
4704    }
4705  }
4706
4707
4708  private String getNamedValue(Base base, String name) {
4709    Property p = base.getChildByName(name);
4710    if (p.hasValues() && p.getValues().size() == 1) {
4711      return p.getValues().get(0).primitiveValue();
4712    }
4713    return null;
4714  }
4715
4716  private boolean checkHtmlNames(XhtmlNode node, boolean block) {
4717    if (node.getNodeType() == NodeType.Comment) {
4718      if (node.getContent().startsWith("DOCTYPE"))
4719        return false;
4720    }
4721    if (node.getNodeType() == NodeType.Element) {
4722      if (block) {
4723        if (!Utilities.existsInList(node.getName(),
4724            "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
4725            "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
4726            "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td",
4727            "code", "samp", "img", "map", "area")) {
4728          return false;
4729        }        
4730      } else {
4731        if (!Utilities.existsInList(node.getName(),
4732            "a", "span", "b", "em", "i", "strong", "small", "big", "small", "q", "var", "abbr", "acronym", "cite", "kbd", "q", "sub", "sup", "code", "samp", "img", "map", "area")) {
4733          return false;
4734        }
4735      }
4736      for (String an : node.getAttributes().keySet()) {
4737        boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an,
4738            "title", "style", "class", "id", "idref", "lang", "xml:lang", "xml:space", "dir", "accesskey", "tabindex",
4739            // tables
4740            "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") ||
4741
4742            Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite",
4743                "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src",
4744                "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
4745                "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
4746                "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap"
4747                );
4748        if (!ok) {
4749          return false;
4750        }
4751      }
4752      for (XhtmlNode c : node.getChildNodes()) {
4753        if (!checkHtmlNames(c, block && !"p".equals(c))) {
4754          return false;
4755        }
4756      }
4757    }
4758    return true;
4759  }
4760
4761  private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4762    List<Base> result = new ArrayList<Base>();
4763    if (exp.getParameters().size() == 1) {
4764      List<Base> pc = new ArrayList<Base>();
4765      boolean all = true;
4766      for (Base item : focus) {
4767        pc.clear();
4768        pc.add(item);
4769        Equality eq = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp);
4770        if (eq != Equality.True) {
4771          all = false;
4772          break;
4773        }
4774      }
4775      result.add(new BooleanType(all).noExtensions());
4776    } else {// (exp.getParameters().size() == 0) {
4777      boolean all = true;
4778      for (Base item : focus) {
4779        Equality eq = asBool(item, true);
4780        if (eq != Equality.True) {
4781          all = false;
4782          break;
4783        }
4784      }
4785      result.add(new BooleanType(all).noExtensions());
4786    }
4787    return result;
4788  }
4789
4790
4791  private ExecutionContext changeThis(ExecutionContext context, Base newThis) {
4792    ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, newThis);
4793    // append all of the defined variables from the context into the new context
4794    if (context.definedVariables != null) {
4795      for (String s : context.definedVariables.keySet()) {
4796        newContext.setDefinedVariable(s, context.definedVariables.get(s));
4797      }
4798    }
4799    return newContext;
4800  }
4801
4802  private ExecutionContext contextForParameter(ExecutionContext context) {
4803    ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.thisItem);
4804    newContext.total = context.total;
4805    newContext.index = context.index;
4806    // append all of the defined variables from the context into the new context
4807    if (context.definedVariables != null) {
4808      for (String s : context.definedVariables.keySet()) {
4809        newContext.setDefinedVariable(s, context.definedVariables.get(s));
4810      }
4811    }
4812    return newContext;
4813  }
4814
4815  private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) {
4816    ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.rootResource, context.resource, context.context, newThis);
4817    // append all of the defined variables from the context into the new context
4818    if (context.definedVariables != null) {
4819      for (String s : context.definedVariables.keySet()) {
4820        newContext.setDefinedVariable(s, context.definedVariables.get(s));
4821      }
4822    }
4823    return newContext;
4824  }
4825
4826  private ExecutionTypeContext contextForParameter(ExecutionTypeContext context) {
4827    ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.rootResource, context.resource, context.context, context.thisItem);
4828    // append all of the defined variables from the context into the new context
4829    if (context.definedVariables != null) {
4830      for (String s : context.definedVariables.keySet()) {
4831        newContext.setDefinedVariable(s, context.definedVariables.get(s));
4832      }
4833    }
4834    return newContext;
4835  }
4836
4837  private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4838    List<Base> result = new ArrayList<Base>();
4839    result.add(DateTimeType.now());
4840    return result;
4841  }
4842
4843
4844  private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4845    List<Base> result = new ArrayList<Base>();
4846    result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY));
4847    return result;
4848  }
4849
4850
4851  private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4852    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4853    if (nl.size() != 1 || focus.size() != 1) {
4854      return new ArrayList<Base>();
4855    }
4856
4857    String url = nl.get(0).primitiveValue();
4858    ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.findTxResource(ValueSet.class, url);
4859    if (vs == null) {
4860      return new ArrayList<Base>();
4861    }
4862    Base l = focus.get(0);
4863    if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) {
4864      return makeBoolean(worker.validateCode(terminologyServiceOptions.withGuessSystem(), TypeConvertor.castToCoding(l), vs).isOk());
4865    } else if (l.fhirType().equals("Coding")) {
4866      return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCoding(l), vs).isOk());
4867    } else if (l.fhirType().equals("CodeableConcept")) {
4868      return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCodeableConcept(l), vs).isOk());
4869    } else {
4870      //      System.out.println("unknown type in funcMemberOf: "+l.fhirType());
4871      return new ArrayList<Base>();
4872    }
4873  }
4874
4875
4876  private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4877    List<Base> result = new ArrayList<Base>();
4878    List<Base> current = new ArrayList<Base>();
4879    current.addAll(focus);
4880    List<Base> added = new ArrayList<Base>();
4881    boolean more = true;
4882    while (more) {
4883      added.clear();
4884      for (Base item : current) {
4885        getChildrenByName(item, "*", added);
4886      }
4887      more = !added.isEmpty();
4888      result.addAll(added);
4889      current.clear();
4890      current.addAll(added);
4891    }
4892    return result;
4893  }
4894
4895
4896  private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4897    List<Base> result = new ArrayList<Base>();
4898    for (Base b : focus) {
4899      getChildrenByName(b, "*", result);
4900    }
4901    return result;
4902  }
4903
4904
4905  private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException, PathEngineException {
4906    List<Base> result = new ArrayList<Base>();
4907    List<Base> tB = execute(context, focus, expr.getParameters().get(0), true);
4908    String t = convertToString(tB);
4909    List<Base> rB = execute(context, focus, expr.getParameters().get(1), true);
4910    String r = convertToString(rB);
4911
4912    if (focus.size() == 0 || tB.size() == 0 || rB.size() == 0) {
4913      //
4914    } else if (focus.size() == 1) {
4915      if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
4916        String f = convertToString(focus.get(0));
4917        if (Utilities.noString(f)) {
4918          result.add(new StringType(""));
4919        } else {
4920          String n = f.replace(t, r);
4921          result.add(new StringType(n));
4922        }
4923      }
4924    } else {
4925      throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "replace", focus.size());
4926    }
4927    return result;
4928  }
4929
4930
4931  private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4932    List<Base> result = new ArrayList<Base>();
4933    List<Base> regexB = execute(context, focus, exp.getParameters().get(0), true);
4934    String regex = convertToString(regexB);
4935    List<Base> replB = execute(context, focus, exp.getParameters().get(1), true);
4936    String repl = convertToString(replB);
4937
4938    if (focus.size() == 0 || regexB.size() == 0 || replB.size() == 0) {
4939      //
4940    } else if (focus.size() == 1 && !Utilities.noString(regex)) {
4941      if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
4942        result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions());
4943      }
4944    } else {
4945      result.add(new StringType(convertToString(focus.get(0))).noExtensions());
4946    }
4947    return result;
4948  }
4949
4950
4951  private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4952    List<Base> result = new ArrayList<Base>();
4953    List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
4954    String sw = convertToString(swb);
4955
4956    if (focus.size() == 0) {
4957      //
4958    } else if (swb.size() == 0) {
4959      //
4960    } else if (Utilities.noString(sw)) {
4961      result.add(new BooleanType(true).noExtensions());
4962    } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
4963      if (focus.size() == 1 && !Utilities.noString(sw)) {
4964        result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions());
4965      } else {
4966        result.add(new BooleanType(false).noExtensions());
4967      }
4968    }
4969    return result;
4970  }
4971
4972
4973  private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4974    List<Base> result = new ArrayList<Base>();
4975    for (Base item : focus) {
4976      String value = convertToString(item);
4977      if (value != null)
4978        result.add(new StringType(value).noExtensions());
4979    }
4980
4981    if (result.size() > 1) {
4982      throw makeException(exp, I18nConstants.FHIRPATH_NO_COLLECTION, "toString", result.size());
4983    }
4984    return result;
4985  }
4986
4987  private List<Base> funcToBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4988    List<Base> result = new ArrayList<Base>();
4989    if (focus.size() == 1) {
4990      if (focus.get(0) instanceof BooleanType) {
4991        result.add(focus.get(0));
4992      } else if (focus.get(0) instanceof IntegerType) {
4993        int i = Integer.parseInt(focus.get(0).primitiveValue());
4994        if (i == 0) {
4995          result.add(new BooleanType(false).noExtensions());
4996        } else if (i == 1) {
4997          result.add(new BooleanType(true).noExtensions());
4998        }
4999      } else if (focus.get(0) instanceof DecimalType) {
5000        if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0) {
5001          result.add(new BooleanType(false).noExtensions());
5002        } else if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0) {
5003          result.add(new BooleanType(true).noExtensions());
5004        }
5005      } else if (focus.get(0) instanceof StringType) {
5006        if ("true".equalsIgnoreCase(focus.get(0).primitiveValue())) {
5007          result.add(new BooleanType(true).noExtensions());
5008        } else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue())) {
5009          result.add(new BooleanType(false).noExtensions()); 
5010        }
5011      }
5012    }
5013    return result;
5014  }
5015
5016  private List<Base> funcToQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5017    List<Base> result = new ArrayList<Base>();
5018    if (focus.size() == 1) {
5019      if (focus.get(0) instanceof Quantity) {
5020        result.add(focus.get(0));
5021      } else if (focus.get(0) instanceof StringType) {
5022        Quantity q = parseQuantityString(focus.get(0).primitiveValue());
5023        if (q != null) {
5024          result.add(q.noExtensions());
5025        }
5026      } else if (focus.get(0) instanceof IntegerType) {
5027        result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions());
5028      } else if (focus.get(0) instanceof DecimalType) {
5029        result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions());
5030      }
5031    }
5032    return result;
5033  }
5034
5035  private List<Base> funcToDateTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
5036    //  List<Base> result = new ArrayList<Base>();
5037    //  result.add(new BooleanType(convertToBoolean(focus)));
5038    //  return result;
5039    throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toDateTime");
5040  }
5041
5042  private List<Base> funcToTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
5043    //  List<Base> result = new ArrayList<Base>();
5044    //  result.add(new BooleanType(convertToBoolean(focus)));
5045    //  return result;
5046    throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toTime");
5047  }
5048
5049
5050  private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
5051    String s = convertToString(focus);
5052    List<Base> result = new ArrayList<Base>();
5053    if (Utilities.isDecimal(s, true)) {
5054      result.add(new DecimalType(s).noExtensions());
5055    }
5056    if ("true".equals(s)) {
5057      result.add(new DecimalType(1).noExtensions());
5058    }
5059    if ("false".equals(s)) {
5060      result.add(new DecimalType(0).noExtensions());
5061    }
5062    return result;
5063  }
5064
5065
5066  private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5067    if (focus.size() > 1) {
5068      throw makeException(exp, I18nConstants.FHIRPATH_NO_COLLECTION, "iif", focus.size());    
5069    }
5070    
5071    List<Base> n1 = execute(focus.isEmpty() ? context : changeThis(context, focus.get(0)), focus, exp.getParameters().get(0), true);
5072    Equality v = asBool(n1, exp);
5073    if (v == Equality.True) {
5074      return execute(context, focus, exp.getParameters().get(1), true);
5075    } else if (exp.getParameters().size() < 3) {
5076      return new ArrayList<Base>();
5077    } else {
5078      return execute(context, focus, exp.getParameters().get(2), true);
5079    }
5080  }
5081
5082
5083  private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5084    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
5085    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
5086
5087    List<Base> result = new ArrayList<Base>();
5088    for (int i = 0; i < Math.min(focus.size(), i1); i++) {
5089      result.add(focus.get(i));
5090    }
5091    return result;
5092  }
5093
5094
5095  private List<Base> funcUnion(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5096    List<Base> result = new ArrayList<Base>();
5097    for (Base item : focus) {
5098      if (!doContains(result, item)) {
5099        result.add(item);
5100      }
5101    }
5102    for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) {
5103      if (!doContains(result, item)) {
5104        result.add(item);
5105      }
5106    }
5107    return result;
5108  }
5109
5110  private List<Base> funcCombine(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5111    List<Base> result = new ArrayList<Base>();
5112    for (Base item : focus) {
5113      result.add(item);
5114    }
5115    for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) {
5116      result.add(item);
5117    }
5118    return result;
5119  }
5120
5121  private List<Base> funcIntersect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5122    List<Base> result = new ArrayList<Base>();
5123    List<Base> other = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true);
5124
5125    for (Base item : focus) {
5126      if (!doContains(result, item) && doContains(other, item)) {
5127        result.add(item);
5128      }
5129    }
5130    return result;    
5131  }
5132
5133  private List<Base> funcExclude(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5134    List<Base> result = new ArrayList<Base>();
5135    List<Base> other = execute(context, focus, exp.getParameters().get(0), true);
5136
5137    for (Base item : focus) {
5138      if (!doContains(other, item)) {
5139        result.add(item);
5140      }
5141    }
5142    return result;
5143  }
5144
5145
5146  private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws PathEngineException {
5147    if (focus.size() == 1) {
5148      return focus;
5149    }
5150    throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "single", focus.size());
5151  }
5152
5153
5154  private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws PathEngineException {
5155    if (focus.size() == 0 || focus.size() > 1) {
5156      return makeNull();
5157    }
5158    String ns = null;
5159    String n = null;
5160
5161    ExpressionNode texp = expr.getParameters().get(0);
5162    if (texp.getKind() != Kind.Name) {
5163      throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "0", "is");
5164    }
5165    if (texp.getInner() != null) {
5166      if (texp.getInner().getKind() != Kind.Name) {
5167        throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "1", "is");
5168      }
5169      ns = texp.getName();
5170      n = texp.getInner().getName();
5171    } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Date", "Time", "SimpleTypeInfo", "ClassInfo")) {
5172      ns = "System";
5173      n = texp.getName();
5174    } else {
5175      ns = "FHIR";
5176      n = texp.getName();        
5177    }
5178    if (ns.equals("System")) {
5179      if (focus.get(0) instanceof Resource) {
5180        return makeBoolean(false);
5181      }
5182      if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) {
5183        String t = Utilities.capitalize(focus.get(0).fhirType());
5184        if (n.equals(t)) {
5185          return makeBoolean(true);
5186        }
5187        if ("Date".equals(t) && n.equals("DateTime")) {
5188          return makeBoolean(true);
5189        } else { 
5190          return makeBoolean(false);
5191        }
5192      } else {
5193        return makeBoolean(false);
5194      }
5195    } else if (ns.equals("FHIR")) {
5196      if (n.equals(focus.get(0).fhirType())) {
5197        return makeBoolean(true);
5198      } else {
5199        StructureDefinition sd = worker.fetchTypeDefinition(focus.get(0).fhirType());
5200        while (sd != null) {
5201          if (n.equals(sd.getType())) {
5202            return makeBoolean(true);
5203          }
5204          sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
5205        }
5206        return makeBoolean(false);
5207      }
5208    } else { 
5209      return makeBoolean(false);
5210    }
5211  }
5212
5213
5214  private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
5215    List<Base> result = new ArrayList<Base>();
5216    String tn;
5217    if (expr.getParameters().get(0).getInner() != null) {
5218      tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName();
5219    } else {
5220      tn = "FHIR."+expr.getParameters().get(0).getName();
5221    }
5222    if (!isKnownType(tn)) {
5223      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE);
5224    }
5225    if (!doNotEnforceAsSingletonRule && focus.size() > 1) {
5226      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_AS_COLLECTION, focus.size(), expr.toString()), I18nConstants.FHIRPATH_AS_COLLECTION); 
5227    }
5228    
5229    for (Base b : focus) {
5230      if (tn.startsWith("System.")) {
5231        if (b instanceof Element &&((Element) b).isDisallowExtensions()) { 
5232          if (b.hasType(tn.substring(7))) {
5233            result.add(b);
5234          }
5235        }
5236
5237      } else if (tn.startsWith("FHIR.")) {
5238        String tnp = tn.substring(5);
5239        if (b.fhirType().equals(tnp)) {
5240          result.add(b);          
5241        } else {
5242          StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType());
5243          while (sd != null) {
5244            if (compareTypeNames(tnp, sd.getType())) {
5245              result.add(b);
5246              break;
5247            }
5248            sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
5249          }
5250        }
5251      }
5252    }
5253    return result;
5254  }
5255  
5256
5257  private List<Base> funcOfType(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
5258    List<Base> result = new ArrayList<Base>();
5259    String tn;
5260    if (expr.getParameters().get(0).getInner() != null) {
5261      tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName();
5262    } else {
5263      tn = "FHIR."+expr.getParameters().get(0).getName();
5264    }
5265    if (!isKnownType(tn)) {
5266      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); 
5267    }
5268
5269    
5270    for (Base b : focus) {
5271      if (tn.startsWith("System.")) {
5272        if (b instanceof Element &&((Element) b).isDisallowExtensions()) { 
5273          if (b.hasType(tn.substring(7))) {
5274            result.add(b);
5275          }
5276        }
5277
5278      } else if (tn.startsWith("FHIR.")) {
5279        String tnp = tn.substring(5);
5280        if (b.fhirType().equals(tnp)) {
5281          result.add(b);          
5282        } else {
5283          StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType());
5284          while (sd != null) {
5285            if (tnp.equals(sd.getType())) {
5286              result.add(b);
5287              break;
5288            }
5289            sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
5290          }
5291        }
5292      } else if (tn.startsWith("CDA.")) {
5293        String tnp = Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", tn.substring(4));
5294        if (b.fhirType().equals(tnp)) {
5295          result.add(b);          
5296        } else {
5297          StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType());
5298          while (sd != null) {
5299            if (tnp.equals(sd.getType())) {
5300              result.add(b);
5301              break;
5302            }
5303            sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
5304          }
5305        }
5306      }
5307    }
5308    return result;
5309  }
5310
5311  private List<Base> funcType(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5312    List<Base> result = new ArrayList<Base>();
5313    for (Base item : focus) {
5314      result.add(new ClassTypeInfo(item));
5315    }
5316    return result;
5317  }
5318
5319
5320  private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5321    List<Base> result = new ArrayList<Base>();
5322    List<Base> current = new ArrayList<Base>();
5323    current.addAll(focus);
5324    List<Base> added = new ArrayList<Base>();
5325    boolean more = true;
5326    while (more) {
5327      added.clear();
5328      List<Base> pc = new ArrayList<Base>();
5329      for (Base item : current) {
5330        pc.clear();
5331        pc.add(item);
5332        added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false));
5333      }
5334      more = false;
5335      current.clear();
5336      for (Base b : added) {
5337        boolean isnew = true;
5338        for (Base t : result) {
5339          if (b.equalsDeep(t)) {
5340            isnew = false;
5341          }
5342        }
5343        if (isnew) {
5344          result.add(b);
5345          current.add(b);
5346          more = true;
5347        }
5348      }
5349    }
5350    return result;
5351  }
5352
5353
5354  private List<Base> funcAggregate(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5355    List<Base> total = new ArrayList<Base>();
5356    if (exp.parameterCount() > 1) {
5357      total = execute(context, focus, exp.getParameters().get(1), false);
5358    }
5359
5360    List<Base> pc = new ArrayList<Base>();
5361    for (Base item : focus) {
5362      ExecutionContext c = changeThis(context, item);
5363      c.total = total;
5364      c.next();
5365      total = execute(c, pc, exp.getParameters().get(0), true);
5366    }
5367    return total;
5368  }
5369
5370
5371
5372  private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5373    if (focus.size() < 1) {
5374      return makeBoolean(true);
5375    }
5376    if (focus.size() == 1) {
5377      return makeBoolean(true);
5378    }
5379
5380    boolean distinct = true;
5381    for (int i = 0; i < focus.size(); i++) {
5382      for (int j = i+1; j < focus.size(); j++) {
5383        Boolean eq = doEquals(focus.get(j), focus.get(i));
5384        if (eq == null) {
5385          return new ArrayList<Base>();
5386        } else if (eq == true) {
5387          distinct = false;
5388          break;
5389        }
5390      }
5391    }
5392    return makeBoolean(distinct);
5393  }
5394
5395
5396  private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5397    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
5398
5399    boolean valid = true;
5400    for (Base item : target) {
5401      boolean found = false;
5402      for (Base t : focus) {
5403        if (Base.compareDeep(item, t, false)) {
5404          found = true;
5405          break;
5406        }
5407      }
5408      if (!found) {
5409        valid = false;
5410        break;
5411      }
5412    }
5413    List<Base> result = new ArrayList<Base>();
5414    result.add(new BooleanType(valid).noExtensions());
5415    return result;
5416  }
5417
5418
5419  private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5420    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
5421
5422    boolean valid = true;
5423    for (Base item : focus) {
5424      boolean found = false;
5425      for (Base t : target) {
5426        if (Base.compareDeep(item, t, false)) {
5427          found = true;
5428          break;
5429        }
5430      }
5431      if (!found) {
5432        valid = false;
5433        break;
5434      }
5435    }
5436    List<Base> result = new ArrayList<Base>();
5437    result.add(new BooleanType(valid).noExtensions());
5438    return result;
5439  }
5440
5441
5442  private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5443    List<Base> result = new ArrayList<Base>();
5444    boolean empty = true;
5445    List<Base> pc = new ArrayList<Base>();
5446    for (Base f : focus) {
5447      if (exp.getParameters().size() == 1) {
5448        pc.clear();
5449        pc.add(f);
5450        Equality v = asBool(execute(changeThis(context, f), pc, exp.getParameters().get(0), true), exp);
5451        if (v == Equality.True) {
5452          empty = false;
5453        }
5454      } else if (!f.isEmpty()) {
5455        empty = false;
5456      }
5457    }
5458    result.add(new BooleanType(!empty).noExtensions());
5459    return result;
5460  }
5461
5462
5463  private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5464    List<Base> result = new ArrayList<Base>();
5465    Base refContext = null;
5466    for (Base item : focus) {
5467      String s = convertToString(item);
5468      if (item.fhirType().equals("Reference")) {
5469        refContext = item;
5470        Property p = item.getChildByName("reference");
5471        if (p != null && p.hasValues()) {
5472          s = convertToString(p.getValues().get(0));
5473        } else {
5474          s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it)
5475        }
5476      }
5477      if (item.fhirType().equals("canonical")) {
5478        s = item.primitiveValue();
5479        refContext = item;
5480      }
5481      if (s != null) {
5482        Base res = null;
5483        if (s.startsWith("#")) {
5484          String t = s.substring(1);
5485          Property p = context.rootResource.getChildByName("contained");
5486          if (p != null) {
5487            for (Base c : p.getValues()) {
5488              if (chompHash(s).equals(chompHash(c.getIdBase()))) {
5489                res = c;
5490                break;
5491              }
5492            }
5493          }
5494        } else if (hostServices != null) {
5495          try {
5496            res = hostServices.resolveReference(this, context.appInfo, s, refContext);
5497          } catch (Exception e) {
5498            res = null;
5499          }
5500        }
5501        if (res != null) {
5502          result.add(res);
5503        }
5504      }
5505    }
5506
5507    return result;
5508  }
5509
5510  private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5511    List<Base> result = new ArrayList<Base>();
5512    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
5513    String url = nl.get(0).primitiveValue();
5514
5515    for (Base item : focus) {
5516      List<Base> ext = new ArrayList<Base>();
5517      getChildrenByName(item, "extension", ext);
5518      getChildrenByName(item, "modifierExtension", ext);
5519      for (Base ex : ext) {
5520        List<Base> vl = new ArrayList<Base>();
5521        getChildrenByName(ex, "url", vl);
5522        if (convertToString(vl).equals(url)) {
5523          result.add(ex);
5524        }
5525      }
5526    }
5527    return result;
5528  }
5529
5530  private List<Base> funcAllFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5531    List<Base> result = new ArrayList<Base>();
5532    if (exp.getParameters().size() == 1) {
5533      boolean all = true;
5534      List<Base> pc = new ArrayList<Base>();
5535      for (Base item : focus) {
5536        pc.clear();
5537        pc.add(item);
5538        List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
5539        Equality v = asBool(res, exp);
5540        if (v != Equality.False) {
5541          all = false;
5542          break;
5543        }
5544      }
5545      result.add(new BooleanType(all).noExtensions());
5546    } else { 
5547      boolean all = true;
5548      for (Base item : focus) {
5549        if (!canConvertToBoolean(item)) {
5550          throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
5551        }
5552
5553        Equality v = asBool(item, true);
5554        if (v != Equality.False) {
5555          all = false;
5556          break;
5557        }
5558      }
5559      result.add(new BooleanType(all).noExtensions());
5560    }
5561    return result;
5562  }
5563
5564  private List<Base> funcAnyFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5565    List<Base> result = new ArrayList<Base>();
5566    if (exp.getParameters().size() == 1) {
5567      boolean any = false;
5568      List<Base> pc = new ArrayList<Base>();
5569      for (Base item : focus) {
5570        pc.clear();
5571        pc.add(item);
5572        List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
5573        Equality v = asBool(res, exp);
5574        if (v == Equality.False) {
5575          any = true;
5576          break;
5577        }
5578      }
5579      result.add(new BooleanType(any).noExtensions());
5580    } else {
5581      boolean any = false;
5582      for (Base item : focus) {
5583        if (!canConvertToBoolean(item)) {
5584          throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
5585        }
5586
5587        Equality v = asBool(item, true);
5588        if (v == Equality.False) {
5589          any = true;
5590          break;
5591        }
5592      }
5593      result.add(new BooleanType(any).noExtensions());
5594    }
5595    return result;
5596  }
5597
5598  private List<Base> funcAllTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5599    List<Base> result = new ArrayList<Base>();
5600    if (exp.getParameters().size() == 1) {
5601      boolean all = true;
5602      List<Base> pc = new ArrayList<Base>();
5603      for (Base item : focus) {
5604        pc.clear();
5605        pc.add(item);
5606        List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
5607        Equality v = asBool(res, exp);
5608        if (v != Equality.True) {
5609          all = false;
5610          break;
5611        }
5612      }
5613      result.add(new BooleanType(all).noExtensions());
5614    } else { 
5615      boolean all = true;
5616      for (Base item : focus) {
5617        if (!canConvertToBoolean(item)) {
5618          throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
5619        }
5620        Equality v = asBool(item, true);
5621        if (v != Equality.True) {
5622          all = false;
5623          break;
5624        }
5625      }
5626      result.add(new BooleanType(all).noExtensions());
5627    }
5628    return result;
5629  }
5630
5631  private List<Base> funcAnyTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5632    List<Base> result = new ArrayList<Base>();
5633    if (exp.getParameters().size() == 1) {
5634      boolean any = false;
5635      List<Base> pc = new ArrayList<Base>();
5636      for (Base item : focus) {
5637        pc.clear();
5638        pc.add(item);
5639        List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
5640        Equality v = asBool(res, exp);
5641        if (v == Equality.True) {
5642          any = true;
5643          break;
5644        }
5645      }
5646      result.add(new BooleanType(any).noExtensions());
5647    } else {
5648      boolean any = false;
5649      for (Base item : focus) {
5650        if (!canConvertToBoolean(item)) {
5651          throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
5652        }
5653
5654        Equality v = asBool(item, true);
5655        if (v == Equality.True) {
5656          any = true;
5657          break;
5658        }
5659      }
5660      result.add(new BooleanType(any).noExtensions());
5661    }
5662    return result;
5663  }
5664
5665  private boolean canConvertToBoolean(Base item) {
5666    return (item.isBooleanPrimitive());
5667  }
5668
5669  private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5670    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
5671    String name = nl.get(0).primitiveValue();
5672    if (exp.getParameters().size() == 2) {
5673      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
5674      log(name, n2);
5675    } else { 
5676      log(name, focus);
5677    }
5678    return focus;
5679  }
5680
5681  private List<Base> funcDefineVariable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5682    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
5683    String name = nl.get(0).primitiveValue();
5684    List<Base> value;
5685    if (exp.getParameters().size() == 2) {
5686      value = execute(context, focus, exp.getParameters().get(1), true);
5687    } else { 
5688      value = focus;
5689    }
5690    // stash the variable into the context
5691    context.setDefinedVariable(name, value);
5692    return focus;
5693  }
5694
5695  private List<Base> funcCheck(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException {
5696    List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
5697    if (!convertToBoolean(n1)) {
5698      List<Base> n2 = execute(context, focus, expr.getParameters().get(1), true);
5699      String name = n2.get(0).primitiveValue();
5700      throw makeException(expr, I18nConstants.FHIRPATH_CHECK_FAILED, name);
5701    }
5702    return focus;
5703  }
5704
5705  private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5706    if (focus.size() <= 1) {
5707      return focus;
5708    }
5709
5710    List<Base> result = new ArrayList<Base>();
5711    for (int i = 0; i < focus.size(); i++) {
5712      boolean found = false;
5713      for (int j = i+1; j < focus.size(); j++) {
5714        Boolean eq = doEquals(focus.get(j), focus.get(i));
5715        if (eq == null)
5716          return new ArrayList<Base>();
5717        else if (eq == true) {
5718          found = true;
5719          break;
5720        }
5721      }
5722      if (!found) {
5723        result.add(focus.get(i));
5724      }
5725    }
5726    return result;
5727  }
5728
5729  private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5730    List<Base> result = new ArrayList<Base>();
5731    List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
5732    String sw = convertToString(swb);
5733
5734    if (focus.size() == 0 || swb.size() == 0) {
5735      //
5736    } else if (focus.size() == 1 && !Utilities.noString(sw)) {
5737      if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
5738        String st = convertToString(focus.get(0));
5739        if (Utilities.noString(st)) {
5740          result.add(new BooleanType(false).noExtensions());
5741        } else {
5742          Pattern p = Pattern.compile("(?s)" + sw);
5743          Matcher m = p.matcher(st);
5744          boolean ok = m.find();
5745          result.add(new BooleanType(ok).noExtensions());
5746        }
5747      }
5748    } else {
5749      result.add(new BooleanType(false).noExtensions());
5750    }
5751    return result;
5752  }
5753
5754  private List<Base> funcMatchesFull(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5755    List<Base> result = new ArrayList<Base>();
5756    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
5757
5758    if (focus.size() == 1 && !Utilities.noString(sw)) {
5759      if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
5760        String st = convertToString(focus.get(0));
5761        if (Utilities.noString(st)) {
5762          result.add(new BooleanType(false).noExtensions());
5763        } else {
5764          Pattern p = Pattern.compile("(?s)" + sw);
5765          Matcher m = p.matcher(st);
5766          boolean ok = m.matches();
5767          result.add(new BooleanType(ok).noExtensions());
5768        }
5769      }
5770    } else {
5771      result.add(new BooleanType(false).noExtensions());
5772    }
5773    return result;
5774  }
5775
5776  private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5777    List<Base> result = new ArrayList<Base>();
5778    List<Base> swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true);
5779    String sw = convertToString(swb);
5780
5781    if (focus.size() != 1) {
5782      //
5783    } else if (swb.size() != 1) {
5784        //
5785    } else if (Utilities.noString(sw)) {
5786      result.add(new BooleanType(true).noExtensions());
5787    } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
5788      String st = convertToString(focus.get(0));
5789      if (Utilities.noString(st)) {
5790        result.add(new BooleanType(false).noExtensions());
5791      } else {
5792        result.add(new BooleanType(st.contains(sw)).noExtensions());
5793      }
5794    } 
5795    return result;
5796  }
5797
5798  private List<Base> baseToList(Base b) {
5799    List<Base> res = new ArrayList<>();
5800    res.add(b);
5801    return res;
5802  }
5803
5804  private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5805    List<Base> result = new ArrayList<Base>();
5806    if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
5807      String s = convertToString(focus.get(0));
5808      result.add(new IntegerType(s.length()).noExtensions());
5809    }
5810    return result;
5811  }
5812
5813  private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5814    List<Base> result = new ArrayList<Base>();
5815    if (focus.size() == 1) {
5816      String s = convertToString(focus.get(0));
5817      result.add(new BooleanType(!Utilities.noString(s)).noExtensions());
5818    } else {
5819      result.add(new BooleanType(false).noExtensions());
5820    }
5821    return result;
5822  }
5823
5824  private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5825    List<Base> result = new ArrayList<Base>();
5826    List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
5827    String sw = convertToString(swb);
5828
5829    if (focus.size() == 0) {
5830      // no result
5831    } else if (swb.size() == 0) {
5832      // no result
5833    } else if (Utilities.noString(sw)) {
5834      result.add(new BooleanType(true).noExtensions());
5835    } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
5836      String s = convertToString(focus.get(0));
5837      if (s == null) {
5838        result.add(new BooleanType(false).noExtensions());
5839      } else {
5840        result.add(new BooleanType(s.startsWith(sw)).noExtensions());
5841      }
5842    }
5843    return result;
5844  }
5845
5846  private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5847    List<Base> result = new ArrayList<Base>();
5848    if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
5849      String s = convertToString(focus.get(0));
5850      if (!Utilities.noString(s)) { 
5851        result.add(new StringType(s.toLowerCase()).noExtensions());
5852      }
5853    }
5854    return result;
5855  }
5856
5857  private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5858    List<Base> result = new ArrayList<Base>();
5859    if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
5860      String s = convertToString(focus.get(0));
5861      if (!Utilities.noString(s)) { 
5862        result.add(new StringType(s.toUpperCase()).noExtensions());
5863      }
5864    }
5865    return result;
5866  }
5867
5868  private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5869    List<Base> result = new ArrayList<Base>();
5870    if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
5871      String s = convertToString(focus.get(0));
5872      for (char c : s.toCharArray()) {  
5873        result.add(new StringType(String.valueOf(c)).noExtensions());
5874      }
5875    }
5876    return result;
5877  }
5878
5879  private List<Base> funcIndexOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5880    List<Base> result = new ArrayList<Base>();
5881
5882    List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
5883    String sw = convertToString(swb);
5884    if (focus.size() == 0) {
5885      // no result
5886    } else if (swb.size() == 0) {
5887      // no result
5888    } else if (Utilities.noString(sw)) {
5889      result.add(new IntegerType(0).noExtensions());
5890    } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
5891      String s = convertToString(focus.get(0));
5892      if (s == null) {
5893        result.add(new IntegerType(0).noExtensions());
5894      } else {
5895        result.add(new IntegerType(s.indexOf(sw)).noExtensions());
5896      }
5897    }
5898    return result;
5899  }
5900
5901  private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5902    List<Base> result = new ArrayList<Base>();
5903    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
5904    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
5905    int i2 = -1;
5906    if (exp.parameterCount() == 2) {
5907      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
5908      if (n2.isEmpty()|| !n2.get(0).isPrimitive() || !Utilities.isInteger(n2.get(0).primitiveValue())) {
5909        return new ArrayList<Base>();
5910      }
5911      i2 = Integer.parseInt(n2.get(0).primitiveValue());
5912    }
5913
5914    if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
5915      String sw = convertToString(focus.get(0));
5916      String s;
5917      if (i1 < 0 || i1 >= sw.length()) {
5918        return new ArrayList<Base>();
5919      }
5920      if (exp.parameterCount() == 2) {
5921        s = sw.substring(i1, Math.min(sw.length(), i1+i2));
5922      } else {
5923        s = sw.substring(i1);
5924      }
5925      if (!Utilities.noString(s)) { 
5926        result.add(new StringType(s).noExtensions());
5927      }
5928    }
5929    return result;
5930  }
5931
5932  private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5933    String s = convertToString(focus);
5934    List<Base> result = new ArrayList<Base>();
5935    if (Utilities.isInteger(s)) {
5936      result.add(new IntegerType(s).noExtensions());
5937    } else if ("true".equals(s)) {
5938      result.add(new IntegerType(1).noExtensions());
5939    } else if ("false".equals(s)) {
5940      result.add(new IntegerType(0).noExtensions());
5941    }
5942    return result;
5943  }
5944
5945  private List<Base> funcIsInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5946    List<Base> result = new ArrayList<Base>();
5947    if (focus.size() != 1) {
5948      result.add(new BooleanType(false).noExtensions());
5949    } else if (focus.get(0) instanceof IntegerType) {
5950      result.add(new BooleanType(true).noExtensions());
5951    } else if (focus.get(0) instanceof BooleanType) {
5952      result.add(new BooleanType(true).noExtensions());
5953    } else if (focus.get(0) instanceof StringType) {
5954      result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions());
5955    } else { 
5956      result.add(new BooleanType(false).noExtensions());
5957    }
5958    return result;
5959  }
5960
5961  private List<Base> funcIsBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5962    List<Base> result = new ArrayList<Base>();
5963    if (focus.size() != 1) {
5964      result.add(new BooleanType(false).noExtensions());
5965    } else if (focus.get(0) instanceof IntegerType) {
5966      result.add(new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1).noExtensions());
5967    } else if (focus.get(0) instanceof DecimalType) {
5968      result.add(new BooleanType(((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0 || ((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0).noExtensions());
5969    } else if (focus.get(0) instanceof BooleanType) {
5970      result.add(new BooleanType(true).noExtensions());
5971    } else if (focus.get(0) instanceof StringType) {
5972      result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")).noExtensions());
5973    } else { 
5974      result.add(new BooleanType(false).noExtensions());
5975    }
5976    return result;
5977  }
5978
5979  private List<Base> funcIsDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5980    List<Base> result = new ArrayList<Base>();
5981    if (focus.size() != 1) {
5982      result.add(new BooleanType(false).noExtensions());
5983    } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) {
5984      result.add(new BooleanType(true).noExtensions());
5985    } else if (focus.get(0) instanceof StringType) {
5986      result.add(new BooleanType((convertToString(focus.get(0)).matches
5987          ("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3])(:[0-5][0-9](:([0-5][0-9]|60))?)?(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"))).noExtensions());
5988    } else { 
5989      result.add(new BooleanType(false).noExtensions());
5990    }
5991    return result;
5992  }
5993
5994  private List<Base> funcIsDate(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5995    List<Base> result = new ArrayList<Base>();
5996    if (focus.size() != 1) {
5997      result.add(new BooleanType(false).noExtensions());
5998    } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) {
5999      result.add(new BooleanType(true).noExtensions());
6000    } else if (focus.get(0) instanceof StringType) {
6001      result.add(new BooleanType((convertToString(focus.get(0)).matches
6002          ("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3])(:[0-5][0-9](:([0-5][0-9]|60))?)?(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"))).noExtensions());
6003    } else { 
6004      result.add(new BooleanType(false).noExtensions());
6005    }
6006    return result;
6007  }
6008
6009  private List<Base> funcConformsTo(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException {
6010    if (hostServices == null) {
6011      throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "conformsTo");
6012    }
6013    List<Base> result = new ArrayList<Base>();
6014    if (focus.size() != 1) {
6015      result.add(new BooleanType(false).noExtensions());
6016    } else {
6017      String url = convertToString(execute(context, focus, expr.getParameters().get(0), true));
6018      result.add(new BooleanType(hostServices.conformsToProfile(this, context.appInfo,  focus.get(0), url)).noExtensions());
6019    }
6020    return result;
6021  }
6022
6023  private List<Base> funcIsTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6024    List<Base> result = new ArrayList<Base>();
6025    if (focus.size() != 1) {
6026      result.add(new BooleanType(false).noExtensions());
6027    } else if (focus.get(0) instanceof TimeType) {
6028      result.add(new BooleanType(true).noExtensions());
6029    } else if (focus.get(0) instanceof StringType) {
6030      result.add(new BooleanType((convertToString(focus.get(0)).matches
6031          ("(T)?([01][0-9]|2[0-3])(:[0-5][0-9](:([0-5][0-9]|60))?)?(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?"))).noExtensions());
6032    } else {
6033      result.add(new BooleanType(false).noExtensions());
6034    }
6035    return result;
6036  }
6037
6038  private List<Base> funcIsString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6039    List<Base> result = new ArrayList<Base>();
6040    if (focus.size() != 1) {
6041      result.add(new BooleanType(false).noExtensions());
6042    } else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType)) {
6043      result.add(new BooleanType(true).noExtensions());
6044    } else { 
6045      result.add(new BooleanType(false).noExtensions());
6046    }
6047    return result;
6048  }
6049
6050  private List<Base> funcIsQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6051    List<Base> result = new ArrayList<Base>();
6052    if (focus.size() != 1) {
6053      result.add(new BooleanType(false).noExtensions());
6054    } else if (focus.get(0) instanceof IntegerType) {
6055      result.add(new BooleanType(true).noExtensions());
6056    } else if (focus.get(0) instanceof DecimalType) {
6057      result.add(new BooleanType(true).noExtensions());
6058    } else if (focus.get(0) instanceof Quantity) {
6059      result.add(new BooleanType(true).noExtensions());
6060    } else if (focus.get(0) instanceof BooleanType) {
6061      result.add(new BooleanType(true).noExtensions());
6062    } else  if (focus.get(0) instanceof StringType) {
6063      Quantity q = parseQuantityString(focus.get(0).primitiveValue());
6064      result.add(new BooleanType(q != null).noExtensions());
6065    } else {
6066      result.add(new BooleanType(false).noExtensions());
6067    }
6068    return result;
6069  }
6070
6071  public Quantity parseQuantityString(String s) {
6072    if (s == null) {
6073      return null;
6074    }
6075    s = s.trim();
6076    if (s.contains(" ")) {
6077      String v = s.substring(0, s.indexOf(" ")).trim();
6078      s = s.substring(s.indexOf(" ")).trim();
6079      if (!Utilities.isDecimal(v, false)) {
6080        return null;
6081      }
6082      if (s.startsWith("'") && s.endsWith("'")) {
6083        return Quantity.fromUcum(v, s.substring(1, s.length()-1));
6084      }
6085      if (s.equals("year") || s.equals("years")) {
6086        return Quantity.fromUcum(v, "a");
6087      } else if (s.equals("month") || s.equals("months")) {
6088        return Quantity.fromUcum(v, "mo_s");
6089      } else if (s.equals("week") || s.equals("weeks")) {
6090        return Quantity.fromUcum(v, "wk");
6091      } else if (s.equals("day") || s.equals("days")) {
6092        return Quantity.fromUcum(v, "d");
6093      } else if (s.equals("hour") || s.equals("hours")) {
6094        return Quantity.fromUcum(v, "h");
6095      } else if (s.equals("minute") || s.equals("minutes")) {
6096        return Quantity.fromUcum(v, "min");
6097      } else if (s.equals("second") || s.equals("seconds")) {
6098        return Quantity.fromUcum(v, "s");
6099      } else if (s.equals("millisecond") || s.equals("milliseconds")) {
6100        return Quantity.fromUcum(v, "ms");
6101      } else {
6102        return null;
6103      } 
6104    } else {
6105      if (Utilities.isDecimal(s, true)) {
6106        return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1");
6107      } else {
6108        return null;
6109      } 
6110    }
6111  }
6112
6113
6114  private List<Base> funcIsDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6115    List<Base> result = new ArrayList<Base>();
6116    if (focus.size() != 1) {
6117      result.add(new BooleanType(false).noExtensions());
6118    } else if (focus.get(0) instanceof IntegerType) {
6119      result.add(new BooleanType(true).noExtensions());
6120    } else if (focus.get(0) instanceof BooleanType) {
6121      result.add(new BooleanType(true).noExtensions());
6122    } else if (focus.get(0) instanceof DecimalType) {
6123      result.add(new BooleanType(true).noExtensions());
6124    } else if (focus.get(0) instanceof StringType) {
6125      result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)), true)).noExtensions());
6126    } else {
6127      result.add(new BooleanType(false).noExtensions());
6128    } 
6129    return result;
6130  }
6131
6132  private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6133    List<Base> result = new ArrayList<Base>();
6134    result.add(new IntegerType(focus.size()).noExtensions());
6135    return result;
6136  }
6137
6138  private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
6139    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
6140    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
6141
6142    List<Base> result = new ArrayList<Base>();
6143    for (int i = i1; i < focus.size(); i++) {
6144      result.add(focus.get(i));
6145    } 
6146    return result;
6147  }
6148
6149  private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6150    List<Base> result = new ArrayList<Base>();
6151    for (int i = 1; i < focus.size(); i++) {
6152      result.add(focus.get(i));
6153    } 
6154    return result;
6155  }
6156
6157  private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6158    List<Base> result = new ArrayList<Base>();
6159    if (focus.size() > 0) {
6160      result.add(focus.get(focus.size()-1));
6161    } 
6162    return result;
6163  }
6164
6165  private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6166    List<Base> result = new ArrayList<Base>();
6167    if (focus.size() > 0) {
6168      result.add(focus.get(0));
6169    } 
6170    return result;
6171  }
6172
6173
6174  private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
6175    List<Base> result = new ArrayList<Base>();
6176    List<Base> pc = new ArrayList<Base>();
6177    for (Base item : focus) {
6178      pc.clear();
6179      pc.add(item);
6180      Equality v = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp);
6181      if (v == Equality.True) {
6182        result.add(item);
6183      } 
6184    }
6185    return result;
6186  }
6187
6188  private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
6189    List<Base> result = new ArrayList<Base>();
6190    List<Base> pc = new ArrayList<Base>();
6191    int i = 0;
6192    for (Base item : focus) {
6193      pc.clear();
6194      pc.add(item);
6195      result.addAll(execute(changeThis(context, item).setIndex(i), pc, exp.getParameters().get(0), true));
6196      i++;
6197    }
6198    return result;
6199  }
6200
6201
6202  private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
6203    List<Base> result = new ArrayList<Base>();
6204    String s = convertToString(execute(context, focus, exp.getParameters().get(0), true));
6205    if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) {
6206      result.add(focus.get(Integer.parseInt(s)));
6207    } 
6208    return result;
6209  }
6210
6211  private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6212    List<Base> result = new ArrayList<Base>();
6213    result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions());
6214    return result;
6215  }
6216
6217  private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
6218    List<Base> result = new ArrayList<Base>();  
6219    Equality v = asBool(focus, exp);
6220    if (v != Equality.Null) {
6221      result.add(new BooleanType(v != Equality.True));
6222    } 
6223    return result;
6224  }
6225
6226  private class ElementDefinitionMatch {
6227    private ElementDefinition definition;
6228    private ElementDefinition sourceDefinition; // if there was a content reference
6229    private String fixedType;
6230    public ElementDefinitionMatch(ElementDefinition definition, String fixedType) {
6231      super();
6232      this.definition = definition;
6233      this.fixedType = fixedType;
6234    }
6235    public ElementDefinition getDefinition() {
6236      return definition;
6237    }
6238    public ElementDefinition getSourceDefinition() {
6239      return sourceDefinition;
6240    }
6241    public String getFixedType() {
6242      return fixedType;
6243    }
6244
6245  }
6246
6247  private void getChildTypesByName(String type, String name, TypeDetails result, ExpressionNode expr, TypeDetails focus, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException {
6248    if (Utilities.noString(type)) {
6249      throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, "", "getChildTypesByName");
6250    } 
6251    if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) {
6252      return;
6253    }     
6254
6255    if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { 
6256      getSimpleTypeChildTypesByName(name, result);
6257    } else if (type.equals(TypeDetails.FP_ClassInfo)) { 
6258      getClassInfoChildTypesByName(name, result);
6259    } else {
6260      if (type.startsWith(Constants.NS_SYSTEM_TYPE)) {
6261        return;
6262      } 
6263      
6264      String url = null;
6265      if (type.contains("#")) {
6266        url = type.substring(0, type.indexOf("#"));
6267      } else {
6268        url = type;
6269      }
6270      String tail = "";
6271      StructureDefinition sd = worker.fetchTypeDefinition(url);
6272      if (sd == null) {
6273        sd = worker.fetchResource(StructureDefinition.class, url);
6274      }
6275      if (sd == null) {
6276        if (url.startsWith(TypeDetails.FP_NS)) {
6277          return;
6278        } else {
6279          throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, url, "getChildTypesByName#1");          
6280        }
6281      }
6282      List<StructureDefinition> sdl = new ArrayList<StructureDefinition>();
6283      ElementDefinitionMatch m = null;
6284      if (type.contains("#")) {
6285        List<ElementDefinitionMatch> list = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false, expr);
6286        m = list.size() == 1 ? list.get(0) : null;
6287      }
6288      if (m != null && hasDataType(m.definition)) {
6289        if (m.fixedType != null)  {
6290          StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, null), sd);
6291          if (dt == null) {
6292            throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(m.fixedType, null), "getChildTypesByName#2");
6293          }
6294          sdl.add(dt);
6295        } else
6296          for (TypeRefComponent t : m.definition.getType()) {
6297            StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), null));
6298            if (dt == null) {
6299              throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(t.getCode(), null), "getChildTypesByName#3");
6300            }
6301            addTypeAndDescendents(sdl, dt, cu.allStructures());
6302            // also add any descendant types
6303          }
6304      } else {
6305        addTypeAndDescendents(sdl, sd, cu.allStructures());
6306        if (type.contains("#")) {
6307          tail = type.substring(type.indexOf("#")+1);
6308          if (tail.contains(".")) {
6309            tail = tail.substring(tail.indexOf("."));
6310          } else {
6311            tail = "";
6312          }
6313        }
6314      }
6315
6316      for (StructureDefinition sdi : sdl) {
6317        String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+".";
6318        if (name.equals("**")) {
6319          assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
6320          for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
6321            if (ed.getPath().startsWith(path)) {
6322              if (ed.hasContentReference()) {
6323                String cpath = ed.getContentReference();
6324                if (!cpath.startsWith("#")) {
6325                  if (!cpath.contains("#")) {
6326                    throw new Error("ContentReference doesn't contain a #: "+cpath);
6327                  }
6328                  cpath = cpath.substring(cpath.indexOf("#"));
6329                }
6330                String tn = sdi.getType()+cpath;
6331                if (!result.hasType(worker, tn)) {
6332                  if (elementDependencies != null) {
6333                    elementDependencies.add(ed);
6334                  }
6335                  getChildTypesByName(result.addType(tn), "**", result, expr, null, elementDependencies);
6336                }
6337              } else {
6338                for (TypeRefComponent t : ed.getType()) {
6339                  if (t.hasCode() && t.getCodeElement().hasValue()) {
6340                    String tn = null;
6341                    if (Utilities.existsInList(t.getCode(), "Element", "BackboneElement", "Base") || cu.isAbstractType(t.getCode())) {
6342                      tn = sdi.getType()+"#"+ed.getPath();
6343                    } else {
6344                      tn = t.getCode();
6345                    }
6346                    if (t.getCode().equals("Resource")) {
6347                      for (String rn : worker.getResourceNames()) {
6348                        if (!result.hasType(worker, rn)) {
6349                          if (elementDependencies != null) {
6350                            elementDependencies.add(ed);
6351                          }
6352                          getChildTypesByName(result.addType(rn), "**", result, expr, null, elementDependencies);
6353                        }                  
6354                      }
6355                    } else if (!result.hasType(worker, tn)) {
6356                      if (elementDependencies != null) {
6357                        elementDependencies.add(ed);
6358                      }
6359                      getChildTypesByName(result.addType(tn), "**", result, expr, null, elementDependencies);
6360                    }
6361                  }
6362                }
6363              }
6364            }
6365          }      
6366        } else if (name.equals("*")) {
6367          assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
6368          for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
6369            if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains("."))
6370              for (TypeRefComponent t : ed.getType()) {
6371                if (Utilities.noString(t.getCode())) { // Element.id or Extension.url
6372                  if (elementDependencies != null) {
6373                    elementDependencies.add(ed);
6374                  }
6375                  result.addType("System.string");
6376                } else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) {
6377                  if (elementDependencies != null) {
6378                    elementDependencies.add(ed);
6379                  }
6380                  result.addType(sdi.getType()+"#"+ed.getPath());
6381                } else if (t.getCode().equals("Resource")) {
6382                  if (elementDependencies != null) {
6383                    elementDependencies.add(ed);
6384                  }
6385                  result.addTypes(worker.getResourceNames());
6386                } else {
6387                  if (elementDependencies != null) {
6388                    elementDependencies.add(ed);
6389                  }
6390                  result.addType(t.getCode());
6391                  copyTargetProfiles(ed, t, focus, result);
6392                }
6393              }
6394          }
6395        } else {
6396          path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name;
6397
6398          List<ElementDefinitionMatch> edl = getElementDefinition(sdi, path, isAllowPolymorphicNames(), expr);
6399          for (ElementDefinitionMatch ed : edl) {
6400            if (ed.getDefinition().isChoice()) {
6401              result.setChoice(true);
6402            }
6403            if (!Utilities.noString(ed.getFixedType())) {
6404              if (elementDependencies != null) {
6405                elementDependencies.add(ed.definition);
6406              }
6407              result.addType(ed.getFixedType());
6408            } else if (ed.getSourceDefinition() != null) {
6409              ProfiledType pt = new ProfiledType(sdi.getType()+"#"+ed.definition.getPath());
6410              result.addType(ed.getSourceDefinition().unbounded() ? CollectionStatus.ORDERED : CollectionStatus.SINGLETON, pt);
6411            } else {
6412              for (TypeRefComponent t : ed.getDefinition().getType()) {
6413                if (Utilities.noString(t.getCode())) {
6414                  if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", "Extension.url")) { 
6415                    if (elementDependencies != null) {
6416                      elementDependencies.add(ed.definition);
6417                    }
6418                    result.addType(TypeDetails.FP_NS, "System.String");
6419                  }
6420                  break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path);
6421                }
6422
6423                ProfiledType pt = null;
6424                if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement") || isAbstractType(t.getCode())) {
6425                  pt = new ProfiledType(sdi.getUrl()+"#"+path);
6426                } else if (t.getCode().equals("Resource")) {
6427                  if (elementDependencies != null) {
6428                    elementDependencies.add(ed.definition);
6429                  }
6430                  result.addTypes(worker.getResourceNames());
6431                } else {
6432                  pt = new ProfiledType(t.getWorkingCode());
6433                }
6434                if (pt != null) {
6435                  if (t.hasProfile()) {
6436                    pt.addProfiles(t.getProfile());
6437                  }
6438                  if (ed.getDefinition().hasBinding()) {
6439                    pt.addBinding(ed.getDefinition().getBinding());
6440                  }
6441                  if (elementDependencies != null) {
6442                    elementDependencies.add(ed.definition);
6443                  }
6444                  result.addType(ed.definition.unbounded() ? CollectionStatus.ORDERED : CollectionStatus.SINGLETON, pt);
6445                  copyTargetProfiles(ed.getDefinition(), t, focus, result);
6446                }
6447              }
6448            }
6449          }
6450        }
6451      }
6452    }
6453  }
6454
6455  private void copyTargetProfiles(ElementDefinition ed, TypeRefComponent t, TypeDetails focus, TypeDetails result) {
6456    if (t.hasTargetProfile()) {
6457      for (CanonicalType u : t.getTargetProfile()) {
6458        result.addTarget(u.primitiveValue());
6459      }
6460    } else if (focus != null && focus.hasType("CodeableReference") && ed.getPath().endsWith(".reference") && focus.getTargets() != null) { // special case, targets are on parent
6461      for (String s : focus.getTargets()) {
6462        result.addTarget(s);
6463      }
6464    }
6465  }
6466
6467  private void addTypeAndDescendents(List<StructureDefinition> sdl, StructureDefinition dt, List<StructureDefinition> types) {
6468    sdl.add(dt);
6469    for (StructureDefinition sd : types) {
6470      if (sd.hasBaseDefinition() && sd.getBaseDefinition().equals(dt.getUrl()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
6471        addTypeAndDescendents(sdl, sd, types);
6472      }
6473    }  
6474  }
6475
6476  private void getClassInfoChildTypesByName(String name, TypeDetails result) {
6477    if (name.equals("namespace")) {
6478      result.addType(TypeDetails.FP_String);
6479    }
6480    if (name.equals("name")) {
6481      result.addType(TypeDetails.FP_String);
6482    }
6483  }
6484
6485
6486  private void getSimpleTypeChildTypesByName(String name, TypeDetails result) {
6487    if (name.equals("namespace")) {
6488      result.addType(TypeDetails.FP_String);
6489    }
6490    if (name.equals("name")) {
6491      result.addType(TypeDetails.FP_String);
6492    }
6493  }
6494
6495
6496  public List<ElementDefinitionMatch> getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, ExpressionNode expr) throws PathEngineException {
6497    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
6498      if (ed.getPath().equals(path)) {
6499        if (ed.hasContentReference()) {
6500          ElementDefinitionMatch res = getElementDefinitionById(sd, ed.getContentReference());
6501          if (res == null) {
6502            throw new Error("Unable to find "+ed.getContentReference());
6503          } else {
6504            res.sourceDefinition = ed;
6505          }
6506          return ml(res);
6507        } else {
6508          return ml(new ElementDefinitionMatch(ed, null));
6509        }
6510      }
6511      if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3) {
6512        return ml(new ElementDefinitionMatch(ed, null));
6513      }
6514      if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) {
6515        String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3));
6516        if (primitiveTypes.contains(s)) {
6517          return ml(new ElementDefinitionMatch(ed, s));
6518        } else {
6519          return ml(new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3)));
6520        }
6521      }
6522      if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 
6523        // now we walk into the type.
6524        if (ed.getType().size() > 1) { // if there's more than one type, the test above would fail this, but we can get here with CDA
6525          List<ElementDefinitionMatch> list = new ArrayList<>();
6526          // for each type, does it have the next node in the path? 
6527          for (TypeRefComponent tr : ed.getType()) {
6528            StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tr.getCode(), null), sd);
6529            if (nsd == null) { 
6530              throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), "getElementDefinition");
6531            }
6532            List<ElementDefinitionMatch> edl = getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr);
6533            list.addAll(edl);
6534          }
6535          return list;
6536        }
6537        StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), null), sd);
6538        if (nsd == null) { 
6539          throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), "getElementDefinition");
6540        }
6541        return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr);
6542      }
6543      if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) {
6544        ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference());
6545        if (m == null) {
6546          throw new Error("Unable to find path "+path+" with a content reference of "+ed.getContentReference());          
6547        } else {
6548          List<ElementDefinitionMatch> res = getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName, expr);
6549          for (ElementDefinitionMatch item : res) {
6550            item.sourceDefinition = ed;
6551          }
6552          return res;
6553        }
6554      }
6555    }
6556    return ml(null);
6557  }
6558
6559  private List<ElementDefinitionMatch> ml(ElementDefinitionMatch item) {
6560    List<ElementDefinitionMatch> list = new ArrayList<>();
6561    if (item != null) {
6562      list.add(item);
6563    }
6564    return list;
6565  }
6566
6567  private boolean isAbstractType(List<TypeRefComponent> list) {
6568    if (list.size() != 1) {
6569      return false;
6570    } else {
6571      return isAbstractType(list.get(0).getCode());
6572    }
6573  }
6574
6575  private boolean isAbstractType(String code) {
6576    StructureDefinition sd = worker.fetchTypeDefinition(code);
6577    return sd != null && sd.getAbstract() && sd.getKind() != StructureDefinitionKind.RESOURCE;
6578  }
6579
6580  
6581  private boolean hasType(ElementDefinition ed, String s) {
6582    for (TypeRefComponent t : ed.getType()) {
6583      if (s.equalsIgnoreCase(t.getCode())) {
6584        return true;
6585      }
6586    }
6587    return false;
6588  }
6589
6590  private boolean hasDataType(ElementDefinition ed) {
6591    return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement") || isAbstractType(ed.getType().get(0).getCode()));
6592  }
6593
6594  private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) {
6595    StructureDefinition sdt = sd; 
6596    while (sdt != null) {
6597      if (ref.startsWith(sdt.getUrl()+"#")) {
6598        ref = ref.replace(sdt.getUrl()+"#", "#");
6599        break;
6600      }
6601      sdt = worker.fetchResource(StructureDefinition.class, sdt.getBaseDefinition());
6602    }
6603    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
6604      if (ref.equals("#"+ed.getId())) {
6605        return new ElementDefinitionMatch(ed, null);
6606      }
6607    }
6608    return null;
6609  }
6610
6611
6612  public boolean hasLog() {
6613    return log != null && log.length() > 0;
6614  }
6615
6616
6617  public String takeLog() {
6618    if (!hasLog()) {
6619      return "";
6620    }
6621    String s = log.toString();
6622    log = new StringBuilder();
6623    return s;
6624  }
6625
6626
6627  /** given an element definition in a profile, what element contains the differentiating fixed 
6628   * for the element, given the differentiating expression. The expression is only allowed to 
6629   * use a subset of FHIRPath
6630   * 
6631   * @param profile
6632   * @param element
6633   * @return
6634   * @throws PathEngineException 
6635   * @throws DefinitionException 
6636   */
6637  public TypedElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, TypedElementDefinition element, StructureDefinition source, boolean dontWalkIntoReferences) throws DefinitionException {
6638    StructureDefinition sd = profile;
6639    TypedElementDefinition focus = null;
6640    boolean okToNotResolve = false;
6641
6642    if (expr.getKind() == Kind.Name) {
6643      if (element.getElement().hasSlicing()) {
6644        ElementDefinition slice = pickMandatorySlice(sd, element.getElement());
6645        if (slice == null) {
6646          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED, element.getElement().getId());
6647        }
6648        element = new TypedElementDefinition(slice);
6649      }
6650
6651      if (expr.getName().equals("$this")) {
6652        focus = element;
6653      } else { 
6654        SourcedChildDefinitions childDefinitions;
6655        childDefinitions = profileUtilities.getChildMap(sd, element.getElement(), false);
6656        // if that's empty, get the children of the type
6657        if (childDefinitions.getList().isEmpty()) {
6658
6659          sd = fetchStructureByType(element, expr);
6660          if (sd == null) {
6661            throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getElement().getType().get(0).getProfile(), element.getElement().getId());
6662          }
6663          childDefinitions = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep(), false);
6664        }
6665        for (ElementDefinition t : childDefinitions.getList()) {
6666          if (tailMatches(t, expr.getName()) && !t.hasSlicing()) { // GG: slicing is a problem here. This is for an exetnsion with a fixed value (type slicing) 
6667            focus = new TypedElementDefinition(t);
6668            break;
6669          }
6670        }
6671      }
6672    } else if (expr.getKind() == Kind.Function) {
6673      if ("resolve".equals(expr.getName())) {
6674        if (element.getTypes().size() == 0) {
6675          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NO_TYPE, element.getElement().getId());
6676        }
6677        if (element.getTypes().size() > 1) {
6678          throw makeExceptionPlural(element.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_MULTIPLE_TYPES, element.getElement().getId());
6679        }
6680        if (!element.getTypes().get(0).hasTarget()) {
6681          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NOT_REFERENCE, element.getElement().getId(), element.getElement().getType().get(0).getCode()+")");
6682        }
6683        if (element.getTypes().get(0).getTargetProfile().size() > 1) {
6684          throw makeExceptionPlural(element.getTypes().get(0).getTargetProfile().size(), expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_NO_TARGET, element.getElement().getId());
6685        }
6686        sd = worker.fetchResource(StructureDefinition.class, element.getTypes().get(0).getTargetProfile().get(0).getValue(), profile);
6687        if (sd == null) {
6688          throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getTypes().get(0).getTargetProfile(), element.getElement().getId());
6689        }
6690        focus = new TypedElementDefinition(sd.getSnapshot().getElementFirstRep());
6691      } else if ("extension".equals(expr.getName())) {
6692        String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue();
6693        SourcedChildDefinitions childDefinitions = profileUtilities.getChildMap(sd, element.getElement(), true);
6694        for (ElementDefinition t : childDefinitions.getList()) {
6695          if (t.getPath().endsWith(".extension") && t.hasSliceName()) {
6696            StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() || t.getType().get(0).getProfile().isEmpty()) ?
6697                null : worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue(), profile);
6698            while (exsd != null && !exsd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) {
6699              exsd = worker.fetchResource(StructureDefinition.class, exsd.getBaseDefinition(), exsd);
6700            }
6701            if (exsd != null && exsd.getUrl().equals(targetUrl)) {
6702              if (profileUtilities.getChildMap(sd, t, false).getList().isEmpty()) {
6703                sd = exsd;
6704              }
6705              focus = new TypedElementDefinition(t);
6706              break;
6707            }
6708          }
6709        }
6710        if (focus == null) { 
6711          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION, expr.toString(), targetUrl, element.getElement().getId(), sd.getUrl());
6712        }
6713      } else if ("ofType".equals(expr.getName())) {
6714        if (!element.getElement().hasType()) {
6715          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_TYPE_NONE, element.getElement().getId());
6716        }
6717        List<String> atn = new ArrayList<>();
6718        for (TypeRefComponent tr : element.getTypes()) {
6719          if (!tr.hasCode()) {
6720            throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NO_CODE, element.getElement().getId());
6721          }
6722          atn.add(tr.getCode());
6723        }
6724        String stn = expr.getParameters().get(0).getName();  
6725        okToNotResolve = true;
6726        if ((atn.contains(stn))) {
6727          if (element.getTypes().size() > 1) {
6728            focus = new TypedElementDefinition( element.getSrc(), element.getElement(), stn);
6729          } else {
6730            focus = element;
6731          }
6732        }
6733      } else {
6734        throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_NAME, expr.getName());
6735      }
6736    } else if (expr.getKind() == Kind.Group) {
6737      throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP, expr.toString());
6738    } else if (expr.getKind() == Kind.Constant) {
6739      throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST);
6740    }
6741
6742    if (focus == null) { 
6743      if (okToNotResolve) {
6744        return null;
6745      } else {
6746        throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString(), source.getUrl(), element.getElement().getId(), profile.getUrl());
6747      }
6748    } else {
6749      // gdg 26-02-2022. If we're walking towards a resolve() and we're on a reference, and  we try to walk into the reference
6750      // then we don't do that. .resolve() is allowed on the Reference.reference, but the target of the reference will be defined
6751      // on the Reference, not the reference.reference.
6752      ExpressionNode next = expr.getInner();
6753      if (dontWalkIntoReferences && focus.hasType("Reference") && next != null && next.getKind() == Kind.Name && next.getName().equals("reference")) {
6754        next = next.getInner();
6755      }
6756      if (next == null) {
6757        return focus;
6758      } else {
6759        return evaluateDefinition(next, sd, focus, profile, dontWalkIntoReferences);
6760      }
6761    }
6762  }
6763
6764  private ElementDefinition pickMandatorySlice(StructureDefinition sd, ElementDefinition element) throws DefinitionException {
6765    List<ElementDefinition> list = profileUtilities.getSliceList(sd, element);
6766    for (ElementDefinition ed : list) {
6767      if (ed.getMin() > 0) {
6768        return ed;
6769      }
6770    }
6771    return null;
6772  }
6773
6774
6775  private StructureDefinition fetchStructureByType(TypedElementDefinition ed, ExpressionNode expr) throws DefinitionException {
6776    if (ed.getTypes().size() == 0) {
6777      throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NOTYPE, ed.getElement().getId());
6778    }
6779    if (ed.getTypes().size() > 1) {
6780      throw makeExceptionPlural(ed.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES, ed.getElement().getId());
6781    }
6782    if (ed.getTypes().get(0).getProfile().size() > 1) {
6783      throw makeExceptionPlural(ed.getTypes().get(0).getProfile().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES, ed.getElement().getId());
6784    }
6785    if (ed.getTypes().get(0).hasProfile()) { 
6786      return worker.fetchResource(StructureDefinition.class, ed.getTypes().get(0).getProfile().get(0).getValue(), ed.getSrc());
6787    } else {
6788      return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getTypes().get(0).getCode(), null), ed.getSrc());
6789    }
6790  }
6791
6792
6793  private boolean tailMatches(ElementDefinition t, String d) {
6794    String tail = tailDot(t.getPath());
6795    if (d.contains("[")) {
6796      return tail.startsWith(d.substring(0, d.indexOf('[')));
6797    } else if (tail.equals(d)) {
6798      return true;
6799    } else if (t.getType().size() == 1 && t.getType().get(0).getCode() != null && t.getPath() != null && t.getPath().toUpperCase().endsWith(t.getType().get(0).getCode().toUpperCase())) {
6800      return tail.startsWith(d);
6801    } else if (t.getPath().endsWith("[x]") && tail.startsWith(d)) {
6802      return true;
6803    }
6804    return false;
6805  }
6806
6807  private String tailDot(String path) {
6808    return path.substring(path.lastIndexOf(".") + 1);
6809  }
6810
6811  private Equality asBool(List<Base> items, ExpressionNode expr) throws PathEngineException {
6812    if (items.size() == 0) {
6813      return Equality.Null;
6814    } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) {
6815      return asBool(items.get(0), true);
6816    } else if (items.size() == 1) {
6817      return Equality.True; 
6818    } else {
6819      throw makeException(expr, I18nConstants.FHIRPATH_UNABLE_BOOLEAN, convertToString(items));
6820    }
6821  }
6822
6823  private Equality asBoolFromInt(String s) {
6824    try {
6825      int i = Integer.parseInt(s);
6826      switch (i) {
6827      case 0: return Equality.False;
6828      case 1: return Equality.True;
6829      default: return Equality.Null;
6830      }
6831    } catch (Exception e) {
6832      return Equality.Null;
6833    }
6834  }
6835
6836  private Equality asBoolFromDec(String s) {
6837    try {
6838      BigDecimal d = new BigDecimal(s);
6839      if (d.compareTo(BigDecimal.ZERO) == 0) { 
6840        return Equality.False;
6841      } else if (d.compareTo(BigDecimal.ONE) == 0) { 
6842        return Equality.True;
6843      } else {
6844        return Equality.Null;
6845      }
6846    } catch (Exception e) {
6847      return Equality.Null;
6848    }
6849  }
6850
6851  private Equality asBool(Base item, boolean narrow) {
6852    if (item instanceof BooleanType) { 
6853      return boolToTriState(((BooleanType) item).booleanValue());
6854    } else if (item.isBooleanPrimitive()) {
6855      if (Utilities.existsInList(item.primitiveValue(), "true")) {
6856        return Equality.True;
6857      } else if (Utilities.existsInList(item.primitiveValue(), "false")) {
6858        return Equality.False;
6859      } else { 
6860        return Equality.Null;
6861      }
6862    } else if (narrow) {
6863      return Equality.False;
6864    } else if (item instanceof IntegerType || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) {
6865      return asBoolFromInt(item.primitiveValue());
6866    } else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal")) {
6867      return asBoolFromDec(item.primitiveValue());
6868    } else if (Utilities.existsInList(item.fhirType(), FHIR_TYPES_STRING)) {
6869      if (Utilities.existsInList(item.primitiveValue(), "true", "t", "yes", "y")) {
6870        return Equality.True;
6871      } else if (Utilities.existsInList(item.primitiveValue(), "false", "f", "no", "n")) {
6872        return Equality.False;
6873      } else if (Utilities.isInteger(item.primitiveValue())) {
6874        return asBoolFromInt(item.primitiveValue());
6875      } else if (Utilities.isDecimal(item.primitiveValue(), true)) {
6876        return asBoolFromDec(item.primitiveValue());
6877      } else {
6878        return Equality.Null;
6879      }
6880    } 
6881    return Equality.Null;
6882  }
6883
6884  private Equality boolToTriState(boolean b) {
6885    return b ? Equality.True : Equality.False;
6886  }
6887
6888
6889  public ValidationOptions getTerminologyServiceOptions() {
6890    return terminologyServiceOptions;
6891  }
6892
6893
6894  public IWorkerContext getWorker() {
6895    return worker;
6896  }
6897
6898  public boolean isAllowPolymorphicNames() {
6899    return allowPolymorphicNames;
6900  }
6901
6902  public void setAllowPolymorphicNames(boolean allowPolymorphicNames) {
6903    this.allowPolymorphicNames = allowPolymorphicNames;
6904  }
6905
6906  public boolean isLiquidMode() {
6907    return liquidMode;
6908  }
6909
6910  public void setLiquidMode(boolean liquidMode) {
6911    this.liquidMode = liquidMode;
6912  }
6913
6914  public ProfileUtilities getProfileUtilities() {
6915    return profileUtilities;
6916  }
6917
6918  public boolean isAllowDoubleQuotes() {
6919    return allowDoubleQuotes;
6920  }
6921  public void setAllowDoubleQuotes(boolean allowDoubleQuotes) {
6922    this.allowDoubleQuotes = allowDoubleQuotes;    
6923  }
6924
6925  public boolean isEmitSQLonFHIRWarning() {
6926    return emitSQLonFHIRWarning;
6927  }
6928
6929  public void setEmitSQLonFHIRWarning(boolean emitSQLonFHIRWarning) {
6930    this.emitSQLonFHIRWarning = emitSQLonFHIRWarning;
6931  }
6932
6933  /**
6934   * Strips a leading hashmark (#) if present at the start of a string
6935   */
6936  private String chompHash(String theId) {
6937    String retVal = theId;
6938    while (retVal.startsWith("#")) {
6939      retVal = retVal.substring(1);
6940    }
6941    return retVal;
6942  }
6943
6944}