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