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