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