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