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