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