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