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