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() || left.get(0).hasType("date", "dateTime", "instant") || "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.hasType("date") && r.hasType("Quantity")) { 2752 DateType dl = l instanceof DateType ? (DateType) l : new DateType(l.primitiveValue()); 2753 result.add(dateAdd(dl, (Quantity) r, false, expr)); 2754 } else if ((l.isDateTime() || l.hasType("dateTime") || l.hasType("instant")) && r.hasType("Quantity")) { 2755 DateTimeType dl = l instanceof DateTimeType ? (DateTimeType) l : new DateTimeType(l.primitiveValue()); 2756 result.add(dateAdd(dl, (Quantity) r, false, expr)); 2757 } else { 2758 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "+", left.get(0).fhirType(), right.get(0).fhirType()); 2759 } 2760 return result; 2761 } 2762 2763 private BaseDateTimeType dateAdd(BaseDateTimeType d, Quantity q, boolean negate, ExpressionNode holder) { 2764 BaseDateTimeType result = (BaseDateTimeType) d.copy(); 2765 2766 int value = negate ? 0 - q.getValue().intValue() : q.getValue().intValue(); 2767 switch (q.hasCode() ? q.getCode() : q.getUnit()) { 2768 case "years": 2769 case "year": 2770 result.add(Calendar.YEAR, value); 2771 break; 2772 case "a": 2773 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString()); 2774 case "months": 2775 case "month": 2776 result.add(Calendar.MONTH, value); 2777 break; 2778 case "mo": 2779 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString()); 2780 case "weeks": 2781 case "week": 2782 case "wk": 2783 result.add(Calendar.DAY_OF_MONTH, value * 7); 2784 break; 2785 case "days": 2786 case "day": 2787 case "d": 2788 result.add(Calendar.DAY_OF_MONTH, value); 2789 break; 2790 case "hours": 2791 case "hour": 2792 case "h": 2793 result.add(Calendar.HOUR, value); 2794 break; 2795 case "minutes": 2796 case "minute": 2797 case "min": 2798 result.add(Calendar.MINUTE, value); 2799 break; 2800 case "seconds": 2801 case "second": 2802 case "s": 2803 result.add(Calendar.SECOND, value); 2804 break; 2805 case "milliseconds": 2806 case "millisecond": 2807 case "ms": 2808 result.add(Calendar.MILLISECOND, value); 2809 break; 2810 default: 2811 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_UNIT, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_UNIT, holder.getOpStart(), holder.toString()); 2812 } 2813 return result; 2814 } 2815 2816 private List<Base> opTimes(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2817 if (left.size() == 0 || right.size() == 0) { 2818 return new ArrayList<Base>(); 2819 } 2820 if (left.size() > 1) { 2821 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "*"); 2822 } 2823 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) { 2824 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "*", left.get(0).fhirType()); 2825 } 2826 if (right.size() > 1) { 2827 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "*"); 2828 } 2829 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) { 2830 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "*", right.get(0).fhirType()); 2831 } 2832 2833 List<Base> result = new ArrayList<Base>(); 2834 Base l = left.get(0); 2835 Base r = right.get(0); 2836 2837 if (l.hasType("integer") && r.hasType("integer")) { 2838 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue()))); 2839 } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 2840 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue())))); 2841 } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) { 2842 Pair pl = qtyToPair((Quantity) l); 2843 Pair pr = qtyToPair((Quantity) r); 2844 Pair p; 2845 try { 2846 p = worker.getUcumService().multiply(pl, pr); 2847 result.add(pairToQty(p)); 2848 } catch (UcumException e) { 2849 throw new PathEngineException(e.getMessage(), null, expr.getOpStart(), expr.toString(), e); // #TODO: i18n 2850 } 2851 } else { 2852 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "*", left.get(0).fhirType(), right.get(0).fhirType()); 2853 } 2854 return result; 2855 } 2856 2857 2858 private List<Base> opConcatenate(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2859 if (left.size() > 1) { 2860 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "&"); 2861 } 2862 if (left.size() > 0 && !left.get(0).hasType(FHIR_TYPES_STRING)) { 2863 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "&", left.get(0).fhirType()); 2864 } 2865 if (right.size() > 1) { 2866 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "&"); 2867 } 2868 if (right.size() > 0 && !right.get(0).hasType(FHIR_TYPES_STRING)) { 2869 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "&", right.get(0).fhirType()); 2870 } 2871 2872 List<Base> result = new ArrayList<Base>(); 2873 String l = left.size() == 0 ? "" : left.get(0).primitiveValue(); 2874 String r = right.size() == 0 ? "" : right.get(0).primitiveValue(); 2875 result.add(new StringType(l + r)); 2876 return result; 2877 } 2878 2879 private List<Base> opUnion(List<Base> left, List<Base> right, ExpressionNode expr) { 2880 List<Base> result = new ArrayList<Base>(); 2881 for (Base item : left) { 2882 if (!doContains(result, item)) { 2883 result.add(item); 2884 } 2885 } 2886 for (Base item : right) { 2887 if (!doContains(result, item)) { 2888 result.add(item); 2889 } 2890 } 2891 return result; 2892 } 2893 2894 private boolean doContains(List<Base> list, Base item) { 2895 for (Base test : list) { 2896 Boolean eq = doEquals(test, item); 2897 if (eq != null && eq == true) { 2898 return true; 2899 } 2900 } 2901 return false; 2902 } 2903 2904 2905 private List<Base> opAnd(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2906 Equality l = asBool(left, expr); 2907 Equality r = asBool(right, expr); 2908 switch (l) { 2909 case False: return makeBoolean(false); 2910 case Null: 2911 if (r == Equality.False) { 2912 return makeBoolean(false); 2913 } else { 2914 return makeNull(); 2915 } 2916 case True: 2917 switch (r) { 2918 case False: return makeBoolean(false); 2919 case Null: return makeNull(); 2920 case True: return makeBoolean(true); 2921 } 2922 } 2923 return makeNull(); 2924 } 2925 2926 private boolean isBoolean(List<Base> list, boolean b) { 2927 return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b; 2928 } 2929 2930 private List<Base> opOr(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2931 Equality l = asBool(left, expr); 2932 Equality r = asBool(right, expr); 2933 switch (l) { 2934 case True: return makeBoolean(true); 2935 case Null: 2936 if (r == Equality.True) { 2937 return makeBoolean(true); 2938 } else { 2939 return makeNull(); 2940 } 2941 case False: 2942 switch (r) { 2943 case False: return makeBoolean(false); 2944 case Null: return makeNull(); 2945 case True: return makeBoolean(true); 2946 } 2947 } 2948 return makeNull(); 2949 } 2950 2951 private List<Base> opXor(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2952 Equality l = asBool(left, expr); 2953 Equality r = asBool(right, expr); 2954 switch (l) { 2955 case True: 2956 switch (r) { 2957 case False: return makeBoolean(true); 2958 case True: return makeBoolean(false); 2959 case Null: return makeNull(); 2960 } 2961 case Null: 2962 return makeNull(); 2963 case False: 2964 switch (r) { 2965 case False: return makeBoolean(false); 2966 case True: return makeBoolean(true); 2967 case Null: return makeNull(); 2968 } 2969 } 2970 return makeNull(); 2971 } 2972 2973 private List<Base> opImplies(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2974 Equality eq = asBool(left, expr); 2975 if (eq == Equality.False) { 2976 return makeBoolean(true); 2977 } else if (right.size() == 0) { 2978 return makeNull(); 2979 } else switch (asBool(right, expr)) { 2980 case False: return eq == Equality.Null ? makeNull() : makeBoolean(false); 2981 case Null: return makeNull(); 2982 case True: return makeBoolean(true); 2983 } 2984 return makeNull(); 2985 } 2986 2987 2988 private List<Base> opMinus(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2989 if (left.size() == 0 || right.size() == 0) { 2990 return new ArrayList<Base>(); 2991 } 2992 if (left.size() > 1) { 2993 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "-"); 2994 } 2995 if (!left.get(0).isPrimitive() && !left.get(0).hasType("Quantity")) { 2996 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "-", left.get(0).fhirType()); 2997 } 2998 if (right.size() > 1) { 2999 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "-"); 3000 } 3001 if (!right.get(0).isPrimitive() && !((left.get(0).isDateTime() || left.get(0).hasType("date", "dateTime", "instant") || "0".equals(left.get(0).primitiveValue()) || left.get(0).hasType("Quantity")) && right.get(0).hasType("Quantity"))) { 3002 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "-", right.get(0).fhirType()); 3003 } 3004 3005 List<Base> result = new ArrayList<Base>(); 3006 Base l = left.get(0); 3007 Base r = right.get(0); 3008 3009 if (l.hasType("integer") && r.hasType("integer")) { 3010 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue()))); 3011 } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 3012 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue())))); 3013 } else if (l.hasType("decimal", "integer", "Quantity") && r.hasType("Quantity")) { 3014 String s = l.primitiveValue(); 3015 if ("0".equals(s)) { 3016 Quantity qty = (Quantity) r; 3017 result.add(qty.copy().setValue(qty.getValue().abs())); 3018 } 3019 } else if (l.hasType("date") && r.hasType("Quantity")) { 3020 DateType dl = l instanceof DateType ? (DateType) l : new DateType(l.primitiveValue()); 3021 result.add(dateAdd(dl, (Quantity) r, true, expr)); 3022 } else if ((l.isDateTime() || l.hasType("dateTime") || l.hasType("instant")) && r.hasType("Quantity")) { 3023 DateTimeType dl = l instanceof DateTimeType ? (DateTimeType) l : new DateTimeType(l.primitiveValue()); 3024 result.add(dateAdd(dl, (Quantity) r, true, expr)); 3025 } else { 3026 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "-", left.get(0).fhirType(), right.get(0).fhirType()); 3027 } 3028 return result; 3029 } 3030 3031 private List<Base> opDivideBy(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 3032 if (left.size() == 0 || right.size() == 0) { 3033 return new ArrayList<Base>(); 3034 } 3035 if (left.size() > 1) { 3036 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "/"); 3037 } 3038 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) { 3039 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "/", left.get(0).fhirType()); 3040 } 3041 if (right.size() > 1) { 3042 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "/"); 3043 } 3044 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) { 3045 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "/", right.get(0).fhirType()); 3046 } 3047 3048 List<Base> result = new ArrayList<Base>(); 3049 Base l = left.get(0); 3050 Base r = right.get(0); 3051 3052 if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 3053 Decimal d1; 3054 try { 3055 d1 = new Decimal(l.primitiveValue()); 3056 Decimal d2 = new Decimal(r.primitiveValue()); 3057 result.add(new DecimalType(d1.divide(d2).asDecimal())); 3058 } catch (UcumException e) { 3059 // just return nothing 3060 } 3061 } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) { 3062 Pair pl = qtyToPair((Quantity) l); 3063 Pair pr = qtyToPair((Quantity) r); 3064 Pair p; 3065 try { 3066 p = worker.getUcumService().divideBy(pl, pr); 3067 result.add(pairToQty(p)); 3068 } catch (UcumException e) { 3069 // just return nothing 3070 } 3071 } else { 3072 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "/", left.get(0).fhirType(), right.get(0).fhirType()); 3073 } 3074 return result; 3075 } 3076 3077 private List<Base> opDiv(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 3078 if (left.size() == 0 || right.size() == 0) { 3079 return new ArrayList<Base>(); 3080 } 3081 if (left.size() > 1) { 3082 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "div"); 3083 } 3084 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) { 3085 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "div", left.get(0).fhirType()); 3086 } 3087 if (right.size() > 1) { 3088 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "div"); 3089 } 3090 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) { 3091 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "div", right.get(0).fhirType()); 3092 } 3093 3094 List<Base> result = new ArrayList<Base>(); 3095 Base l = left.get(0); 3096 Base r = right.get(0); 3097 3098 if (l.hasType("integer") && r.hasType("integer")) { 3099 int divisor = Integer.parseInt(r.primitiveValue()); 3100 if (divisor != 0) { 3101 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / divisor)); 3102 } 3103 } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 3104 Decimal d1; 3105 try { 3106 d1 = new Decimal(l.primitiveValue()); 3107 Decimal d2 = new Decimal(r.primitiveValue()); 3108 result.add(new IntegerType(d1.divInt(d2).asDecimal())); 3109 } catch (UcumException e) { 3110 // just return nothing 3111 } 3112 } else { 3113 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "div", left.get(0).fhirType(), right.get(0).fhirType()); 3114 } 3115 return result; 3116 } 3117 3118 private List<Base> opMod(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 3119 if (left.size() == 0 || right.size() == 0) { 3120 return new ArrayList<Base>(); 3121 } if (left.size() > 1) { 3122 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "mod"); 3123 } 3124 if (!left.get(0).isPrimitive()) { 3125 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "mod", left.get(0).fhirType()); 3126 } 3127 if (right.size() > 1) { 3128 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "mod"); 3129 } 3130 if (!right.get(0).isPrimitive()) { 3131 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "mod", right.get(0).fhirType()); 3132 } 3133 3134 List<Base> result = new ArrayList<Base>(); 3135 Base l = left.get(0); 3136 Base r = right.get(0); 3137 3138 if (l.hasType("integer") && r.hasType("integer")) { 3139 int modulus = Integer.parseInt(r.primitiveValue()); 3140 if (modulus != 0) { 3141 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % modulus)); 3142 } 3143 } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 3144 Decimal d1; 3145 try { 3146 d1 = new Decimal(l.primitiveValue()); 3147 Decimal d2 = new Decimal(r.primitiveValue()); 3148 result.add(new DecimalType(d1.modulo(d2).asDecimal())); 3149 } catch (UcumException e) { 3150 throw new PathEngineException(e); 3151 } 3152 } else { 3153 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "mod", left.get(0).fhirType(), right.get(0).fhirType()); 3154 } 3155 return result; 3156 } 3157 3158 3159 private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant, ExpressionNode expr, boolean explicitConstant) throws PathEngineException { 3160 if (constant instanceof BooleanType) { 3161 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3162 } else if (constant instanceof IntegerType) { 3163 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3164 } else if (constant instanceof DecimalType) { 3165 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3166 } else if (constant instanceof Quantity) { 3167 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); 3168 } else if (constant instanceof FHIRConstant) { 3169 return resolveConstantType(context, ((FHIRConstant) constant).getValue(), expr, explicitConstant); 3170 } else if (constant == null) { 3171 return new TypeDetails(CollectionStatus.SINGLETON); 3172 } else { 3173 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3174 } 3175 } 3176 3177 private TypeDetails resolveConstantType(ExecutionTypeContext context, String s, ExpressionNode expr, boolean explicitConstant) throws PathEngineException { 3178 if (s.startsWith("@")) { 3179 if (s.startsWith("@T")) { 3180 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); 3181 } else { 3182 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3183 } 3184 } else if (s.equals("%sct")) { 3185 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3186 } else if (s.equals("%loinc")) { 3187 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3188 } else if (s.equals("%ucum")) { 3189 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3190 } else if (s.equals("%resource")) { 3191 if (context.resource == null) { 3192 throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%resource", "no focus resource"); 3193 } 3194 return new TypeDetails(CollectionStatus.SINGLETON, context.resource); 3195 } else if (s.equals("%rootResource")) { 3196 if (context.resource == null) { 3197 throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus rootResource"); 3198 } 3199 return new TypeDetails(CollectionStatus.SINGLETON, context.resource); 3200 } else if (s.equals("%context")) { 3201 return context.context; 3202 } else if (s.equals("%map-codes")) { 3203 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3204 } else if (s.equals("%us-zip")) { 3205 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3206 } else if (s.startsWith("%`vs-")) { 3207 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3208 } else if (s.startsWith("%`cs-")) { 3209 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3210 } else if (s.startsWith("%`ext-")) { 3211 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3212 } else if (hostServices == null) { 3213 String varName = s.substring(1); 3214 if (context.hasDefinedVariable(varName)) 3215 return context.getDefinedVariable(varName); 3216 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); 3217 } else { 3218 String varName = s.substring(1); 3219 if (context.hasDefinedVariable(varName)) 3220 return context.getDefinedVariable(varName); 3221 TypeDetails v = hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant); 3222 if (v == null) { 3223 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); 3224 } else { 3225 return v; 3226 } 3227 } 3228 } 3229 3230 private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException { 3231 List<Base> result = new ArrayList<Base>(); 3232 if (atEntry && context.appInfo != null && hostServices != null) { 3233 // we'll see if the name matches a constant known by the context. 3234 List<Base> temp = hostServices.resolveConstant(this, context.appInfo, exp.getName(), true, false); 3235 if (!temp.isEmpty()) { 3236 result.addAll(temp); 3237 return result; 3238 } 3239 } 3240 if (atEntry && exp.getName() != null && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up 3241 StructureDefinition sd = worker.fetchTypeDefinition(item.fhirType()); 3242 if (sd == null) { 3243 // logical model 3244 if (exp.getName().equals(item.fhirType())) { 3245 result.add(item); 3246 } 3247 } else { 3248 while (sd != null) { 3249 if (sd.getType().equals(exp.getName()) || sd.getTypeTail().equals(exp.getName())) { 3250 result.add(item); 3251 break; 3252 } 3253 sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 3254 } 3255 } 3256 } else { 3257 getChildrenByName(item, exp.getName(), result); 3258 } 3259 if (atEntry && context.appInfo != null && hostServices != null && result.isEmpty()) { 3260 // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context. 3261 // (if the name does match, and the user wants to get the constant value, they'll have to try harder... 3262 result.addAll(hostServices.resolveConstant(this, context.appInfo, exp.getName(), false, false)); 3263 } 3264 return result; 3265 } 3266 3267 private String getParent(String rn) { 3268 return null; 3269 } 3270 3271 3272 private TypeDetails executeContextType(ExecutionTypeContext context, String name, ExpressionNode expr, boolean explicitConstant) throws PathEngineException, DefinitionException { 3273 if (hostServices == null) { 3274 throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "Context Reference"); 3275 } 3276 return hostServices.resolveConstantType(this, context.appInfo, name, explicitConstant); 3277 } 3278 3279 private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry, TypeDetails focus, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException { 3280 if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && (hashTail(type).equals(exp.getName()) || isAncestor(type, exp.getName()) )) { // special case for start up 3281 return new TypeDetails(CollectionStatus.SINGLETON, type); 3282 } 3283 TypeDetails result = new TypeDetails(focus.getCollectionStatus()); 3284 getChildTypesByName(type, exp.getName(), result, exp, focus, elementDependencies); 3285 return result; 3286 } 3287 3288 3289 private boolean isAncestor(String wanted, String stated) { 3290 try { 3291 StructureDefinition sd = worker.fetchTypeDefinition(wanted); 3292 while (sd != null) { 3293 if (stated.equals(sd.getTypeName())) { 3294 return true; 3295 } 3296 sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 3297 } 3298 return false; 3299 } catch (Exception e) { 3300 return false; 3301 } 3302 } 3303 3304 private String hashTail(String type) { 3305 return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1); 3306 } 3307 3308 3309 private void evaluateParameters(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies, List<TypeDetails> paramTypes, boolean canBeNone) { 3310 int i = 0; 3311 for (ExpressionNode expr : exp.getParameters()) { 3312 if (isExpressionParameter(exp, i)) { 3313 paramTypes.add(executeType(changeThis(context, focus), focus, expr, elementDependencies, true, canBeNone, expr)); 3314 } else { 3315 paramTypes.add(executeType(context, context.thisItem, expr, elementDependencies, true, canBeNone, expr)); 3316 } 3317 i++; 3318 } 3319 } 3320 3321 @SuppressWarnings("unchecked") 3322 private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies, ExpressionNode container) throws PathEngineException, DefinitionException { 3323 List<TypeDetails> paramTypes = new ArrayList<TypeDetails>(); 3324 if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType || (exp.getFunction() == Function.Custom && hostServices.paramIsType(exp.getName(), 0))) { 3325 paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3326 } else if (exp.getFunction() == Function.Repeat && exp.getParameters().size() == 1) { 3327 TypeDetails base = TypeDetails.empty(); 3328 TypeDetails lFocus = focus; 3329 boolean changed = false; 3330 do { 3331 evaluateParameters(context, lFocus, exp, elementDependencies, paramTypes, true); 3332 changed = false; 3333 if (!base.contains(paramTypes.get(0))) { 3334 changed = true; 3335 base.addTypes(paramTypes.get(0)); 3336 lFocus = base; 3337 } 3338 } while (changed); 3339 paramTypes.clear(); 3340 paramTypes.add(base); 3341 } else if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select || exp.getFunction() == Function.Exists || 3342 exp.getFunction() == Function.All || exp.getFunction() == Function.AllTrue || exp.getFunction() == Function.AnyTrue 3343 || exp.getFunction() == Function.AllFalse || exp.getFunction() == Function.AnyFalse) { 3344 evaluateParameters(context, focus.toSingleton(), exp, elementDependencies, paramTypes, false); 3345 } else { 3346 evaluateParameters(context, focus, exp, elementDependencies, paramTypes, false); 3347 } 3348 if (exp.getFunction() == Function.First || exp.getFunction() == Function.Last || exp.getFunction() == Function.Tail || exp.getFunction() == Function.Skip || exp.getFunction() == Function.Take) { 3349 if (focus.getCollectionStatus() == CollectionStatus.SINGLETON) { 3350 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_NOT_A_COLLECTION, container.toString()), I18nConstants.FHIRPATH_NOT_A_COLLECTION)); 3351 3352 } 3353 } 3354 switch (exp.getFunction()) { 3355 case Empty : 3356 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3357 case Not : 3358 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3359 case Exists : { 3360 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 3361 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3362 } 3363 case SubsetOf : { 3364 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus.toUnordered()); 3365 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3366 } 3367 case SupersetOf : { 3368 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus); 3369 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3370 } 3371 case IsDistinct : 3372 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3373 case Distinct : 3374 return focus; 3375 case Count : 3376 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3377 case Where : 3378 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 3379 // special case: where the focus is Reference, and the parameter to where is resolve() "is", we will suck up the target types 3380 if (focus.hasType("Reference")) { 3381 boolean canRestrictTargets = !exp.getParameters().isEmpty(); 3382 List<String> targets = new ArrayList<>(); 3383 if (canRestrictTargets) { 3384 ExpressionNode p = exp.getParameters().get(0); 3385 if (p.getKind() == Kind.Function && p.getName().equals("resolve") && p.getOperation() == Operation.Is) { 3386 targets.add(p.getOpNext().getName()); 3387 } else { 3388 canRestrictTargets = false; 3389 } 3390 } 3391 if (canRestrictTargets) { 3392 TypeDetails td = focus.copy(); 3393 td.getTargets().clear(); 3394 td.getTargets().addAll(targets); 3395 return td; 3396 } else { 3397 return focus; 3398 } 3399 } else { 3400 return focus; 3401 } 3402 case Select : 3403 return paramTypes.get(0); 3404 case All : 3405 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 3406 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3407 case Repeat : 3408 return paramTypes.get(0); 3409 case Aggregate : 3410 return anything(focus.getCollectionStatus()); 3411 case Item : { 3412 checkOrdered(focus, "item", exp); 3413 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3414 return focus; 3415 } 3416 case As : { 3417 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3418 String tn = checkType(focus, exp); 3419 TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn); 3420 if (td.typesHaveTargets()) { 3421 td.addTargets(focus.getTargets()); 3422 } 3423 return td; 3424 } 3425 case OfType : { 3426 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3427 String tn = checkType(focus, exp); 3428 TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn); 3429 if (td.typesHaveTargets()) { 3430 td.addTargets(focus.getTargets()); 3431 } 3432 return td; 3433 } 3434 case Type : { 3435 boolean s = false; 3436 boolean c = false; 3437 for (ProfiledType pt : focus.getProfiledTypes()) { 3438 s = s || pt.isSystemType(); 3439 c = c || !pt.isSystemType(); 3440 } 3441 if (s && c) { 3442 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo, TypeDetails.FP_ClassInfo); 3443 } else if (s) { 3444 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo); 3445 } else { 3446 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo); 3447 } 3448 } 3449 case Is : { 3450 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3451 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3452 } 3453 case Single : 3454 return focus.toSingleton(); 3455 case First : { 3456 checkOrdered(focus, "first", exp); 3457 return focus.toSingleton(); 3458 } 3459 case Last : { 3460 checkOrdered(focus, "last", exp); 3461 return focus.toSingleton(); 3462 } 3463 case Tail : { 3464 checkOrdered(focus, "tail", exp); 3465 return focus; 3466 } 3467 case Skip : { 3468 checkOrdered(focus, "skip", exp); 3469 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3470 return focus; 3471 } 3472 case Take : { 3473 checkOrdered(focus, "take", exp); 3474 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3475 return focus; 3476 } 3477 case Union : { 3478 return focus.union(paramTypes.get(0)); 3479 } 3480 case Combine : { 3481 return focus.union(paramTypes.get(0)); 3482 } 3483 case Intersect : { 3484 return focus.intersect(paramTypes.get(0)); 3485 } 3486 case Exclude : { 3487 return focus; 3488 } 3489 case Iif : { 3490 TypeDetails types = new TypeDetails(null); 3491 checkSingleton(focus, "iif", exp); 3492 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 3493 types.update(paramTypes.get(1)); 3494 if (paramTypes.size() > 2) { 3495 types.update(paramTypes.get(2)); 3496 } 3497 return types; 3498 } 3499 case Lower : { 3500 checkContextString(focus, "lower", exp, true); 3501 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3502 } 3503 case Upper : { 3504 checkContextString(focus, "upper", exp, true); 3505 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3506 } 3507 case ToChars : { 3508 checkContextString(focus, "toChars", exp, true); 3509 return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); 3510 } 3511 case IndexOf : { 3512 checkContextString(focus, "indexOf", exp, true); 3513 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3514 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3515 } 3516 case Substring : { 3517 checkContextString(focus, "subString", exp, true); 3518 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3519 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3520 } 3521 case StartsWith : { 3522 checkContextString(focus, "startsWith", exp, true); 3523 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3524 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3525 } 3526 case EndsWith : { 3527 checkContextString(focus, "endsWith", exp, true); 3528 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3529 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3530 } 3531 case Matches : { 3532 checkContextString(focus, "matches", exp, true); 3533 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3534 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3535 } 3536 case MatchesFull : { 3537 checkContextString(focus, "matches", exp, true); 3538 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3539 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3540 } 3541 case ReplaceMatches : { 3542 checkContextString(focus, "replaceMatches", exp, true); 3543 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3544 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3545 } 3546 case Contains : { 3547 checkContextString(focus, "contains", exp, true); 3548 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3549 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3550 } 3551 case Replace : { 3552 checkContextString(focus, "replace", exp, true); 3553 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3554 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3555 } 3556 case Length : { 3557 checkContextPrimitive(focus, "length", false, exp); 3558 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3559 } 3560 case Children : 3561 return childTypes(focus, "*", exp); 3562 case Descendants : 3563 return childTypes(focus, "**", exp); 3564 case MemberOf : { 3565 checkContextCoded(focus, "memberOf", exp); 3566 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3567 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3568 } 3569 case Trace : { 3570 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3571 return focus; 3572 } 3573 case DefineVariable : { 3574 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.UNORDERED, TypeDetails.FP_String)); 3575 // set the type of the variable 3576 // Actually evaluate the value of the first parameter (to get the name of the variable if possible) 3577 // and if have that, set it into the context 3578 ExpressionNode p = exp.getParameters().get(0); 3579 if (p.getKind() == Kind.Constant && p.getConstant() != null) { 3580 String varName = exp.getParameters().get(0).getConstant().primitiveValue(); 3581 if (varName != null) { 3582 if (paramTypes.size() > 1) 3583 context.setDefinedVariable(varName, paramTypes.get(1)); 3584 else 3585 context.setDefinedVariable(varName, focus); 3586 } 3587 } else { 3588 // this variable is not a constant, so we can't analyze what name it could have 3589 } 3590 return focus; 3591 } 3592 case Check : { 3593 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3594 return focus; 3595 } 3596 case Today : 3597 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3598 case Now : 3599 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3600 case Resolve : { 3601 checkContextReference(focus, "resolve", exp); 3602 return new TypeDetails(focus.getCollectionStatus(), "Resource"); 3603 } 3604 case Extension : { 3605 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3606 ExpressionNode p = exp.getParameters().get(0); 3607 if (p.getKind() == Kind.Constant && p.getConstant() != null) { 3608 String url = exp.getParameters().get(0).getConstant().primitiveValue(); 3609 ExtensionDefinition ed = findExtensionDefinition(focus, url); 3610 if (ed != null) { 3611 return new TypeDetails(CollectionStatus.ORDERED, new ProfiledType(ed.sd.getUrl())); 3612 } else { 3613 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_UNKNOWN_EXTENSION, url), I18nConstants.FHIRPATH_UNKNOWN_EXTENSION)); 3614 } 3615 return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); 3616 } 3617 } 3618 case AnyTrue: 3619 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3620 case AllTrue: 3621 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3622 case AnyFalse: 3623 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3624 case AllFalse: 3625 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3626 case HasValue : 3627 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3628 case HtmlChecks1 : 3629 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3630 case HtmlChecks2 : 3631 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3632 case Comparable : 3633 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3634 case Encode: 3635 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3636 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3637 case Decode: 3638 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3639 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3640 case Escape: 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 Unescape: 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 Trim: 3647 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3648 case Split: 3649 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3650 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3651 case Join: 3652 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3653 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3654 case ToInteger : { 3655 checkContextPrimitive(focus, "toInteger", true, exp); 3656 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3657 } 3658 case ToDecimal : { 3659 checkContextPrimitive(focus, "toDecimal", true, exp); 3660 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3661 } 3662 case ToString : { 3663 checkContextPrimitive(focus, "toString", true, exp); 3664 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3665 } 3666 case ToQuantity : { 3667 checkContextPrimitive(focus, "toQuantity", true, exp); 3668 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); 3669 } 3670 case ToBoolean : { 3671 checkContextPrimitive(focus, "toBoolean", false, exp); 3672 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3673 } 3674 case ToDateTime : { 3675 checkContextPrimitive(focus, "ToDateTime", false, exp); 3676 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3677 } 3678 case ToTime : { 3679 checkContextPrimitive(focus, "ToTime", false, exp); 3680 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); 3681 } 3682 case ConvertsToString : 3683 case ConvertsToQuantity :{ 3684 checkContextPrimitive(focus, exp.getFunction().toCode(), true, exp); 3685 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3686 } 3687 case ConvertsToInteger : 3688 case ConvertsToDecimal : 3689 case ConvertsToDateTime : 3690 case ConvertsToDate : 3691 case ConvertsToTime : 3692 case ConvertsToBoolean : { 3693 checkContextPrimitive(focus, exp.getFunction().toCode(), false, exp); 3694 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3695 } 3696 case ConformsTo: { 3697 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3698 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3699 } 3700 case Abs : { 3701 checkContextNumerical(focus, "abs", exp); 3702 return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes()); 3703 } 3704 case Truncate : 3705 case Floor : 3706 case Ceiling : { 3707 checkContextDecimal(focus, exp.getFunction().toCode(), exp); 3708 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3709 } 3710 3711 case Round :{ 3712 checkContextDecimal(focus, "round", exp); 3713 if (paramTypes.size() > 0) { 3714 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3715 } 3716 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3717 } 3718 3719 case Exp : 3720 case Ln : 3721 case Sqrt : { 3722 checkContextNumerical(focus, exp.getFunction().toCode(), exp); 3723 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3724 } 3725 case Log : { 3726 checkContextNumerical(focus, exp.getFunction().toCode(), exp); 3727 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS)); 3728 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3729 } 3730 case Power : { 3731 checkContextNumerical(focus, exp.getFunction().toCode(), exp); 3732 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS)); 3733 return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes()); 3734 } 3735 3736 case LowBoundary: 3737 case HighBoundary: { 3738 checkContextContinuous(focus, exp.getFunction().toCode(), exp, true); 3739 if (paramTypes.size() > 0) { 3740 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3741 } 3742 if ((focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) { 3743 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime); 3744 } else if (focus.hasType("decimal") || focus.hasType("integer")) { 3745 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3746 } else { 3747 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3748 } 3749 } 3750 case Precision: { 3751 checkContextContinuous(focus, exp.getFunction().toCode(), exp, false); 3752 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3753 } 3754 case hasTemplateIdOf: { 3755 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3756 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3757 } 3758 case Custom : { 3759 return hostServices.checkFunction(this, context.appInfo,exp.getName(), focus, paramTypes); 3760 } 3761 default: 3762 break; 3763 } 3764 throw new Error("not Implemented yet"); 3765 } 3766 3767 private ExtensionDefinition findExtensionDefinition(TypeDetails focus, String url) { 3768 if (Utilities.isAbsoluteUrl(url)) { 3769 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); 3770 if (sd == null) { 3771 return null; 3772 } else { 3773 return new ExtensionDefinition(true, sd, sd.getSnapshot().getElementFirstRep()); 3774 } 3775 } 3776 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, focus.getType()); 3777 if (sd != null) { 3778 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 3779 if (ed.hasFixed() && url.equals(ed.getFixed().primitiveValue())) { 3780 return new ExtensionDefinition(false, sd, ed); 3781 } 3782 } 3783 } 3784 return null; 3785 } 3786 3787 private String checkType(TypeDetails focus, ExpressionNode exp) { 3788 String tn; 3789 if (exp.getParameters().get(0).getInner() != null) { 3790 tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName(); 3791 } else { 3792 tn = "FHIR."+exp.getParameters().get(0).getName(); 3793 } 3794 if (tn.startsWith("System.")) { 3795 tn = tn.substring(7); 3796 } else if (tn.startsWith("FHIR.")) { 3797 tn = Utilities.pathURL(Constants.NS_FHIR_ROOT, "StructureDefinition", tn.substring(5)); 3798 } else if (tn.startsWith("CDA.")) { 3799 tn = Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", tn.substring(4)); 3800 } 3801 3802 if (typeCastIsImpossible(focus, tn)) { 3803 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE, focus.describeMin(), tn, exp.toString()), I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE)); 3804 } 3805 return tn; 3806 } 3807 3808 private boolean typeCastIsImpossible(TypeDetails focus, String tn) { 3809 return !focus.hasType(tn); 3810 } 3811 3812 private boolean isExpressionParameter(ExpressionNode exp, int i) { 3813 switch (i) { 3814 case 0: 3815 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; 3816 case 1: 3817 return exp.getFunction() == Function.Trace || exp.getFunction() == Function.DefineVariable; 3818 default: 3819 return false; 3820 } 3821 } 3822 3823 3824 private void checkParamTypes(ExpressionNode expr, String funcName,List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException { 3825 int i = 0; 3826 for (TypeDetails pt : typeSet) { 3827 if (i == paramTypes.size()) { 3828 return; 3829 } 3830 TypeDetails actual = paramTypes.get(i); 3831 i++; 3832 for (String a : actual.getTypes()) { 3833 if (!pt.hasType(worker, a)) { 3834 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, funcName, i, a, pt.toString()); 3835 } 3836 } 3837 if (actual.getCollectionStatus() != CollectionStatus.SINGLETON && pt.getCollectionStatus() == CollectionStatus.SINGLETON) { 3838 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_PARAMETER, funcName, i, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_PARAMETER)); 3839 } 3840 } 3841 } 3842 3843 private void checkSingleton(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3844 if (focus.getCollectionStatus() != CollectionStatus.SINGLETON) { 3845 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT, name, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT)); 3846 } 3847 } 3848 3849 private void checkOrdered(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3850 if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) { 3851 throw makeException(expr, I18nConstants.FHIRPATH_ORDERED_ONLY, name); 3852 } 3853 } 3854 3855 private void checkContextReference(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3856 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "url") && !focus.hasType(worker, "Reference") && !focus.hasType(worker, "canonical")) { 3857 throw makeException(expr, I18nConstants.FHIRPATH_REFERENCE_ONLY, name, focus.describe()); 3858 } 3859 } 3860 3861 3862 private void checkContextCoded(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3863 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) { 3864 throw makeException(expr, I18nConstants.FHIRPATH_CODED_ONLY, name, focus.describe()); 3865 } 3866 } 3867 3868 3869 private void checkContextString(TypeDetails focus, String name, ExpressionNode expr, boolean sing) throws PathEngineException { 3870 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")) { 3871 throw makeException(expr, sing ? I18nConstants.FHIRPATH_STRING_SING_ONLY : I18nConstants.FHIRPATH_STRING_ORD_ONLY, name, focus.describe()); 3872 } 3873 } 3874 3875 3876 private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty, ExpressionNode expr) throws PathEngineException { 3877 if (!focus.hasNoTypes()) { 3878 if (canQty) { 3879 if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) { 3880 throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), "Quantity, "+primitiveTypes.toString()); 3881 } 3882 } else if (!focus.hasType(primitiveTypes)) { 3883 throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), primitiveTypes.toString()); 3884 } 3885 } 3886 } 3887 3888 private void checkContextNumerical(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3889 if (!focus.hasNoTypes() && !focus.hasType("integer") && !focus.hasType("decimal") && !focus.hasType("Quantity")) { 3890 throw makeException(expr, I18nConstants.FHIRPATH_NUMERICAL_ONLY, name, focus.describe()); 3891 } 3892 } 3893 3894 private void checkContextDecimal(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3895 if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("integer")) { 3896 throw makeException(expr, I18nConstants.FHIRPATH_DECIMAL_ONLY, name, focus.describe()); 3897 } 3898 } 3899 3900 private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr, boolean allowInteger) throws PathEngineException { 3901 if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time") && !focus.hasType("Quantity") && !(allowInteger && focus.hasType("integer"))) { 3902 throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe()); 3903 } 3904 } 3905 3906 private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) throws PathEngineException, DefinitionException { 3907 TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); 3908 for (String f : focus.getTypes()) { 3909 getChildTypesByName(f, mask, result, expr, null, null); 3910 } 3911 return result; 3912 } 3913 3914 private TypeDetails anything(CollectionStatus status) { 3915 return new TypeDetails(status, allTypes.keySet()); 3916 } 3917 3918 // private boolean isPrimitiveType(String s) { 3919 // 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"); 3920 // } 3921 3922 private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3923 switch (exp.getFunction()) { 3924 case Empty : return funcEmpty(context, focus, exp); 3925 case Not : return funcNot(context, focus, exp); 3926 case Exists : return funcExists(context, focus, exp); 3927 case SubsetOf : return funcSubsetOf(context, focus, exp); 3928 case SupersetOf : return funcSupersetOf(context, focus, exp); 3929 case IsDistinct : return funcIsDistinct(context, focus, exp); 3930 case Distinct : return funcDistinct(context, focus, exp); 3931 case Count : return funcCount(context, focus, exp); 3932 case Where : return funcWhere(context, focus, exp); 3933 case Select : return funcSelect(context, focus, exp); 3934 case All : return funcAll(context, focus, exp); 3935 case Repeat : return funcRepeat(context, focus, exp); 3936 case Aggregate : return funcAggregate(context, focus, exp); 3937 case Item : return funcItem(context, focus, exp); 3938 case As : return funcAs(context, focus, exp); 3939 case OfType : return funcOfType(context, focus, exp); 3940 case Type : return funcType(context, focus, exp); 3941 case Is : return funcIs(context, focus, exp); 3942 case Single : return funcSingle(context, focus, exp); 3943 case First : return funcFirst(context, focus, exp); 3944 case Last : return funcLast(context, focus, exp); 3945 case Tail : return funcTail(context, focus, exp); 3946 case Skip : return funcSkip(context, focus, exp); 3947 case Take : return funcTake(context, focus, exp); 3948 case Union : return funcUnion(context, focus, exp); 3949 case Combine : return funcCombine(context, focus, exp); 3950 case Intersect : return funcIntersect(context, focus, exp); 3951 case Exclude : return funcExclude(context, focus, exp); 3952 case Iif : return funcIif(context, focus, exp); 3953 case Lower : return funcLower(context, focus, exp); 3954 case Upper : return funcUpper(context, focus, exp); 3955 case ToChars : return funcToChars(context, focus, exp); 3956 case IndexOf : return funcIndexOf(context, focus, exp); 3957 case Substring : return funcSubstring(context, focus, exp); 3958 case StartsWith : return funcStartsWith(context, focus, exp); 3959 case EndsWith : return funcEndsWith(context, focus, exp); 3960 case Matches : return funcMatches(context, focus, exp); 3961 case MatchesFull : return funcMatchesFull(context, focus, exp); 3962 case ReplaceMatches : return funcReplaceMatches(context, focus, exp); 3963 case Contains : return funcContains(context, focus, exp); 3964 case Replace : return funcReplace(context, focus, exp); 3965 case Length : return funcLength(context, focus, exp); 3966 case Children : return funcChildren(context, focus, exp); 3967 case Descendants : return funcDescendants(context, focus, exp); 3968 case MemberOf : return funcMemberOf(context, focus, exp); 3969 case Trace : return funcTrace(context, focus, exp); 3970 case DefineVariable : return funcDefineVariable(context, focus, exp); 3971 case Check : return funcCheck(context, focus, exp); 3972 case Today : return funcToday(context, focus, exp); 3973 case Now : return funcNow(context, focus, exp); 3974 case Resolve : return funcResolve(context, focus, exp); 3975 case Extension : return funcExtension(context, focus, exp); 3976 case AnyFalse: return funcAnyFalse(context, focus, exp); 3977 case AllFalse: return funcAllFalse(context, focus, exp); 3978 case AnyTrue: return funcAnyTrue(context, focus, exp); 3979 case AllTrue: return funcAllTrue(context, focus, exp); 3980 case HasValue : return funcHasValue(context, focus, exp); 3981 case Encode : return funcEncode(context, focus, exp); 3982 case Decode : return funcDecode(context, focus, exp); 3983 case Escape : return funcEscape(context, focus, exp); 3984 case Unescape : return funcUnescape(context, focus, exp); 3985 case Trim : return funcTrim(context, focus, exp); 3986 case Split : return funcSplit(context, focus, exp); 3987 case Join : return funcJoin(context, focus, exp); 3988 case HtmlChecks1 : return funcHtmlChecks1(context, focus, exp); 3989 case HtmlChecks2 : return funcHtmlChecks2(context, focus, exp); 3990 case Comparable : return funcComparable(context, focus, exp); 3991 case ToInteger : return funcToInteger(context, focus, exp); 3992 case ToDecimal : return funcToDecimal(context, focus, exp); 3993 case ToString : return funcToString(context, focus, exp); 3994 case ToBoolean : return funcToBoolean(context, focus, exp); 3995 case ToQuantity : return funcToQuantity(context, focus, exp); 3996 case ToDateTime : return funcToDateTime(context, focus, exp); 3997 case ToTime : return funcToTime(context, focus, exp); 3998 case ConvertsToInteger : return funcIsInteger(context, focus, exp); 3999 case ConvertsToDecimal : return funcIsDecimal(context, focus, exp); 4000 case ConvertsToString : return funcIsString(context, focus, exp); 4001 case ConvertsToBoolean : return funcIsBoolean(context, focus, exp); 4002 case ConvertsToQuantity : return funcIsQuantity(context, focus, exp); 4003 case ConvertsToDateTime : return funcIsDateTime(context, focus, exp); 4004 case ConvertsToDate : return funcIsDate(context, focus, exp); 4005 case ConvertsToTime : return funcIsTime(context, focus, exp); 4006 case ConformsTo : return funcConformsTo(context, focus, exp); 4007 case Round : return funcRound(context, focus, exp); 4008 case Sqrt : return funcSqrt(context, focus, exp); 4009 case Abs : return funcAbs(context, focus, exp); 4010 case Ceiling : return funcCeiling(context, focus, exp); 4011 case Exp : return funcExp(context, focus, exp); 4012 case Floor : return funcFloor(context, focus, exp); 4013 case Ln : return funcLn(context, focus, exp); 4014 case Log : return funcLog(context, focus, exp); 4015 case Power : return funcPower(context, focus, exp); 4016 case Truncate : return funcTruncate(context, focus, exp); 4017 case LowBoundary : return funcLowBoundary(context, focus, exp); 4018 case HighBoundary : return funcHighBoundary(context, focus, exp); 4019 case Precision : return funcPrecision(context, focus, exp); 4020 case hasTemplateIdOf: return funcHasTemplateIdOf(context, focus, exp); 4021 4022 4023 case Custom: { 4024 List<List<Base>> params = new ArrayList<List<Base>>(); 4025 if (hostServices.paramIsType( exp.getName(), 0)) { 4026 if (exp.getParameters().size() > 0) { 4027 String tn; 4028 if (exp.getParameters().get(0).getInner() != null) { 4029 tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName(); 4030 } else { 4031 tn = "FHIR."+exp.getParameters().get(0).getName(); 4032 } 4033 List<Base> p = new ArrayList<>(); 4034 p.add(new CodeType(tn)); 4035 params.add(p); 4036 } 4037 } else { 4038 for (ExpressionNode p : exp.getParameters()) { 4039 params.add(execute(context, focus, p, true)); 4040 } 4041 } 4042 return hostServices.executeFunction(this, context.appInfo, focus, exp.getName(), params); 4043 } 4044 default: 4045 throw new Error("not Implemented yet"); 4046 } 4047 } 4048 4049 private List<Base> funcHasTemplateIdOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4050 List<Base> result = new ArrayList<Base>(); 4051 List<Base> swb = execute(context, focus, exp.getParameters().get(0), true); 4052 String sw = convertToString(swb); 4053 4054 StructureDefinition sd = this.worker.fetchResource(StructureDefinition.class, sw); 4055 if (focus.size() == 1 && sd != null) { 4056 boolean found = false; 4057 for (Identifier id : sd.getIdentifier()) { 4058 if (id.getValue().startsWith("urn:hl7ii:")) { 4059 String[] p = id.getValue().split("\\:"); 4060 if (p.length == 4) { 4061 found = found || hasTemplateId(focus.get(0), p[2], p[3]); 4062 } 4063 } else if (id.getValue().startsWith("urn:oid:")) { 4064 found = found || hasTemplateId(focus.get(0), id.getValue().substring(8)); 4065 } 4066 } 4067 result.add(new BooleanType(found)); 4068 } 4069 return result; 4070 } 4071 4072 private boolean hasTemplateId(Base base, String rv) { 4073 List<Base> templateIds = base.listChildrenByName("templateId"); 4074 for (Base templateId : templateIds) { 4075 Base root = templateId.getChildValueByName("root"); 4076 Base extension = templateId.getChildValueByName("extension"); 4077 if (extension == null && root != null && rv.equals(root.primitiveValue())) { 4078 return true; 4079 } 4080 } 4081 return false; 4082 } 4083 4084 private boolean hasTemplateId(Base base, String rv, String ev) { 4085 List<Base> templateIds = base.listChildrenByName("templateId"); 4086 for (Base templateId : templateIds) { 4087 Base root = templateId.getChildValueByName("root"); 4088 Base extension = templateId.getChildValueByName("extension"); 4089 if (extension != null && ev.equals(extension.primitiveValue()) && root != null && rv.equals(root.primitiveValue())) { 4090 return true; 4091 } 4092 } 4093 return false; 4094 } 4095 4096 private List<Base> funcSqrt(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4097 if (focus.size() != 1) { 4098 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "sqrt", focus.size()); 4099 } 4100 Base base = focus.get(0); 4101 List<Base> result = new ArrayList<Base>(); 4102 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4103 Double d = Double.parseDouble(base.primitiveValue()); 4104 try { 4105 result.add(new DecimalType(Math.sqrt(d))); 4106 } catch (Exception e) { 4107 // just return nothing 4108 } 4109 } else { 4110 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal"); 4111 } 4112 return result; 4113 } 4114 4115 4116 private List<Base> funcAbs(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4117 if (focus.size() != 1) { 4118 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "abs", focus.size()); 4119 } 4120 Base base = focus.get(0); 4121 List<Base> result = new ArrayList<Base>(); 4122 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4123 Double d = Double.parseDouble(base.primitiveValue()); 4124 try { 4125 result.add(new DecimalType(Math.abs(d))); 4126 } catch (Exception e) { 4127 // just return nothing 4128 } 4129 } else if (base.hasType("Quantity")) { 4130 Quantity qty = (Quantity) base; 4131 result.add(qty.copy().setValue(qty.getValue().abs())); 4132 } else { 4133 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "abs", "(focus)", base.fhirType(), "integer or decimal"); 4134 } 4135 return result; 4136 } 4137 4138 4139 private List<Base> funcCeiling(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4140 if (focus.size() != 1) { 4141 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ceiling", focus.size()); 4142 } 4143 Base base = focus.get(0); 4144 List<Base> result = new ArrayList<Base>(); 4145 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4146 Double d = Double.parseDouble(base.primitiveValue()); 4147 try {result.add(new IntegerType((int) Math.ceil(d))); 4148 } catch (Exception e) { 4149 // just return nothing 4150 } 4151 } else { 4152 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ceiling", "(focus)", base.fhirType(), "integer or decimal"); 4153 } 4154 return result; 4155 } 4156 4157 private List<Base> funcFloor(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4158 if (focus.size() != 1) { 4159 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "floor", focus.size()); 4160 } 4161 Base base = focus.get(0); 4162 List<Base> result = new ArrayList<Base>(); 4163 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4164 Double d = Double.parseDouble(base.primitiveValue()); 4165 try { 4166 result.add(new IntegerType((int) Math.floor(d))); 4167 } catch (Exception e) { 4168 // just return nothing 4169 } 4170 } else { 4171 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "floor", "(focus)", base.fhirType(), "integer or decimal"); 4172 } 4173 return result; 4174 } 4175 4176 4177 private List<Base> funcExp(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4178 if (focus.size() == 0) { 4179 return new ArrayList<Base>(); 4180 } 4181 if (focus.size() > 1) { 4182 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "exp", focus.size()); 4183 } 4184 Base base = focus.get(0); 4185 List<Base> result = new ArrayList<Base>(); 4186 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4187 Double d = Double.parseDouble(base.primitiveValue()); 4188 try { 4189 result.add(new DecimalType(Math.exp(d))); 4190 } catch (Exception e) { 4191 // just return nothing 4192 } 4193 4194 } else { 4195 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "exp", "(focus)", base.fhirType(), "integer or decimal"); 4196 } 4197 return result; 4198 } 4199 4200 4201 private List<Base> funcLn(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4202 if (focus.size() != 1) { 4203 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ln", focus.size()); 4204 } 4205 Base base = focus.get(0); 4206 List<Base> result = new ArrayList<Base>(); 4207 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4208 Double d = Double.parseDouble(base.primitiveValue()); 4209 try { 4210 result.add(new DecimalType(Math.log(d))); 4211 } catch (Exception e) { 4212 // just return nothing 4213 } 4214 } else { 4215 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ln", "(focus)", base.fhirType(), "integer or decimal"); 4216 } 4217 return result; 4218 } 4219 4220 4221 private List<Base> funcLog(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4222 if (focus.size() != 1) { 4223 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "log", focus.size()); 4224 } 4225 Base base = focus.get(0); 4226 List<Base> result = new ArrayList<Base>(); 4227 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4228 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 4229 if (n1.size() != 1) { 4230 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "0", "Multiple Values", "integer or decimal"); 4231 } 4232 Double e = Double.parseDouble(n1.get(0).primitiveValue()); 4233 Double d = Double.parseDouble(base.primitiveValue()); 4234 try { 4235 result.add(new DecimalType(customLog(e, d))); 4236 } catch (Exception ex) { 4237 // just return nothing 4238 } 4239 } else { 4240 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "(focus)", base.fhirType(), "integer or decimal"); 4241 } 4242 return result; 4243 } 4244 4245 private static double customLog(double base, double logNumber) { 4246 return Math.log(logNumber) / Math.log(base); 4247 } 4248 4249 private List<Base> funcPower(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4250 if (focus.size() != 1) { 4251 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "power", focus.size()); 4252 } 4253 Base base = focus.get(0); 4254 List<Base> result = new ArrayList<Base>(); 4255 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4256 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 4257 if (n1.size() != 1) { 4258 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer or decimal"); 4259 } 4260 Double e = Double.parseDouble(n1.get(0).primitiveValue()); 4261 Double d = Double.parseDouble(base.primitiveValue()); 4262 try { 4263 result.add(new DecimalType(Math.pow(d, e))); 4264 } catch (Exception ex) { 4265 // just return nothing 4266 } 4267 } else { 4268 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "(focus)", base.fhirType(), "integer or decimal"); 4269 } 4270 return result; 4271 } 4272 4273 private List<Base> funcTruncate(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4274 if (focus.size() != 1) { 4275 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "truncate", focus.size()); 4276 } 4277 Base base = focus.get(0); 4278 List<Base> result = new ArrayList<Base>(); 4279 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4280 String s = base.primitiveValue(); 4281 if (s.contains(".")) { 4282 s = s.substring(0, s.indexOf(".")); 4283 } 4284 result.add(new IntegerType(s)); 4285 } else { 4286 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal"); 4287 } 4288 return result; 4289 } 4290 4291 private List<Base> funcLowBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4292 if (focus.size() == 0) { 4293 return makeNull(); 4294 } 4295 if (focus.size() > 1) { 4296 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "lowBoundary", focus.size()); 4297 } 4298 Integer precision = null; 4299 if (expr.getParameters().size() > 0) { 4300 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 4301 if (n1.size() != 1) { 4302 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer"); 4303 } 4304 precision = Integer.parseInt(n1.get(0).primitiveValue()); 4305 } 4306 4307 Base base = focus.get(0); 4308 List<Base> result = new ArrayList<Base>(); 4309 4310 if (base.hasType("decimal")) { 4311 if (precision == null || (precision >= 0 && precision < 17)) { 4312 result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); 4313 } 4314 } else if (base.hasType("integer")) { 4315 if (precision == null || (precision >= 0 && precision < 17)) { 4316 result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); 4317 } 4318 } else if (base.hasType("date")) { 4319 result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == null ? 10 : precision))); 4320 } else if (base.hasType("dateTime")) { 4321 result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == null ? 17 : precision))); 4322 } else if (base.hasType("time")) { 4323 result.add(new TimeType(Utilities.lowBoundaryForTime(base.primitiveValue(), precision == null ? 9 : precision))); 4324 } else if (base.hasType("Quantity")) { 4325 String value = getNamedValue(base, "value"); 4326 Base v = base.copy(); 4327 v.setProperty("value", new DecimalType(Utilities.lowBoundaryForDecimal(value, precision == null ? 8 : precision))); 4328 result.add(v); 4329 } else { 4330 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); 4331 } 4332 return result; 4333 } 4334 4335 private List<Base> funcHighBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4336 if (focus.size() == 0) { 4337 return makeNull(); 4338 } 4339 if (focus.size() > 1) { 4340 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size()); 4341 } 4342 Integer precision = null; 4343 if (expr.getParameters().size() > 0) { 4344 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 4345 if (n1.size() != 1) { 4346 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer"); 4347 } 4348 precision = Integer.parseInt(n1.get(0).primitiveValue()); 4349 } 4350 4351 4352 Base base = focus.get(0); 4353 List<Base> result = new ArrayList<Base>(); 4354 if (base.hasType("decimal")) { 4355 if (precision == null || (precision >= 0 && precision < 17)) { 4356 result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); 4357 } 4358 } else if (base.hasType("integer")) { 4359 if (precision == null || (precision >= 0 && precision < 17)) { 4360 result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); 4361 } 4362 } else if (base.hasType("date")) { 4363 result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == null ? 10 : precision))); 4364 } else if (base.hasType("dateTime")) { 4365 result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == null ? 17 : precision))); 4366 } else if (base.hasType("time")) { 4367 result.add(new TimeType(Utilities.highBoundaryForTime(base.primitiveValue(), precision == null ? 9 : precision))); 4368 } else if (base.hasType("Quantity")) { 4369 String value = getNamedValue(base, "value"); 4370 Base v = base.copy(); 4371 v.setProperty("value", new DecimalType(Utilities.highBoundaryForDecimal(value, precision == null ? 8 : precision))); 4372 result.add(v); 4373 } else { 4374 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); 4375 } 4376 return result; 4377 } 4378 4379 private List<Base> funcPrecision(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4380 if (focus.size() != 1) { 4381 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size()); 4382 } 4383 Base base = focus.get(0); 4384 List<Base> result = new ArrayList<Base>(); 4385 if (base.hasType("decimal")) { 4386 result.add(new IntegerType(Utilities.getDecimalPrecision(base.primitiveValue()))); 4387 } else if (base.hasType("date") || base.hasType("dateTime")) { 4388 result.add(new IntegerType(Utilities.getDatePrecision(base.primitiveValue()))); 4389 } else if (base.hasType("time")) { 4390 result.add(new IntegerType(Utilities.getTimePrecision(base.primitiveValue()))); 4391 } else { 4392 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); 4393 } 4394 return result; 4395 } 4396 4397 private List<Base> funcRound(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4398 if (focus.size() != 1) { 4399 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "round", focus.size()); 4400 } 4401 Base base = focus.get(0); 4402 List<Base> result = new ArrayList<Base>(); 4403 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4404 int i = 0; 4405 if (expr.getParameters().size() == 1) { 4406 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 4407 if (n1.size() != 1) { 4408 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer"); 4409 } 4410 i = Integer.parseInt(n1.get(0).primitiveValue()); 4411 } 4412 BigDecimal d = new BigDecimal (base.primitiveValue()); 4413 result.add(new DecimalType(d.setScale(i, RoundingMode.HALF_UP))); 4414 } else { 4415 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "round", "(focus)", base.fhirType(), "integer or decimal"); 4416 } 4417 return result; 4418 } 4419 4420 private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); 4421 private ContextUtilities cu; 4422 public static String bytesToHex(byte[] bytes) { 4423 char[] hexChars = new char[bytes.length * 2]; 4424 for (int j = 0; j < bytes.length; j++) { 4425 int v = bytes[j] & 0xFF; 4426 hexChars[j * 2] = HEX_ARRAY[v >>> 4]; 4427 hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; 4428 } 4429 return new String(hexChars); 4430 } 4431 4432 public static byte[] hexStringToByteArray(String s) { 4433 int len = s.length(); 4434 byte[] data = new byte[len / 2]; 4435 for (int i = 0; i < len; i += 2) { 4436 data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); 4437 } 4438 return data; 4439 } 4440 4441 private List<Base> funcEncode(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4442 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4443 String param = nl.get(0).primitiveValue(); 4444 4445 List<Base> result = new ArrayList<Base>(); 4446 4447 if (focus.size() == 1) { 4448 String cnt = focus.get(0).primitiveValue(); 4449 if ("hex".equals(param)) { 4450 result.add(new StringType(bytesToHex(cnt.getBytes()))); 4451 } else if ("base64".equals(param)) { 4452 Base64.Encoder enc = Base64.getEncoder(); 4453 result.add(new StringType(enc.encodeToString(cnt.getBytes()))); 4454 } else if ("urlbase64".equals(param)) { 4455 Base64.Encoder enc = Base64.getUrlEncoder(); 4456 result.add(new StringType(enc.encodeToString(cnt.getBytes()))); 4457 } 4458 } 4459 return result; 4460 } 4461 4462 private List<Base> funcDecode(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 ("hex".equals(param)) { 4470 result.add(new StringType(new String(hexStringToByteArray(cnt)))); 4471 } else if ("base64".equals(param)) { 4472 Base64.Decoder enc = Base64.getDecoder(); 4473 result.add(new StringType(new String(enc.decode(cnt)))); 4474 } else if ("urlbase64".equals(param)) { 4475 Base64.Decoder enc = Base64.getUrlDecoder(); 4476 result.add(new StringType(new String(enc.decode(cnt)))); 4477 } 4478 } 4479 return result; 4480 } 4481 4482 private List<Base> funcEscape(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4483 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4484 String param = nl.get(0).primitiveValue(); 4485 4486 List<Base> result = new ArrayList<Base>(); 4487 if (focus.size() == 1) { 4488 String cnt = focus.get(0).primitiveValue(); 4489 if ("html".equals(param)) { 4490 result.add(new StringType(Utilities.escapeXml(cnt))); 4491 } else if ("json".equals(param)) { 4492 result.add(new StringType(Utilities.escapeJson(cnt))); 4493 } else if ("url".equals(param)) { 4494 result.add(new StringType(Utilities.URLEncode(cnt))); 4495 } else if ("md".equals(param)) { 4496 result.add(new StringType(MarkDownProcessor.makeStringSafeAsMarkdown(cnt))); 4497 } 4498 } 4499 4500 return result; 4501 } 4502 4503 private List<Base> funcUnescape(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4504 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4505 String param = nl.get(0).primitiveValue(); 4506 4507 List<Base> result = new ArrayList<Base>(); 4508 if (focus.size() == 1) { 4509 String cnt = focus.get(0).primitiveValue(); 4510 if ("html".equals(param)) { 4511 result.add(new StringType(Utilities.unescapeXml(cnt))); 4512 } else if ("json".equals(param)) { 4513 result.add(new StringType(Utilities.unescapeJson(cnt))); 4514 } else if ("url".equals(param)) { 4515 result.add(new StringType(Utilities.URLDecode(cnt))); 4516 } else if ("md".equals(param)) { 4517 result.add(new StringType(MarkDownProcessor.makeMarkdownForString(cnt))); 4518 } 4519 } 4520 4521 return result; 4522 } 4523 4524 private List<Base> funcTrim(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4525 List<Base> result = new ArrayList<Base>(); 4526 if (focus.size() == 1) { 4527 String cnt = focus.get(0).primitiveValue(); 4528 result.add(new StringType(cnt.trim())); 4529 } 4530 return result; 4531 } 4532 4533 private List<Base> funcSplit(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4534 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4535 String param = nl.get(0).primitiveValue(); 4536 4537 List<Base> result = new ArrayList<Base>(); 4538 if (focus.size() == 1) { 4539 String cnt = focus.get(0).primitiveValue(); 4540 String[] sl = Utilities.simpleSplit(cnt, param); 4541 for (String s : sl) { 4542 result.add(new StringType(s)); 4543 } 4544 } 4545 return result; 4546 } 4547 4548 private List<Base> funcJoin(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4549 List<Base> nl = exp.getParameters().size() > 0 ? execute(context, focus, exp.getParameters().get(0), true) : new ArrayList<Base>(); 4550 String param = ""; 4551 String param2 = ""; 4552 if (exp.getParameters().size() > 0) { 4553 param = nl.get(0).primitiveValue(); 4554 param2 = param; 4555 if (exp.getParameters().size() == 2) { 4556 nl = execute(context, focus, exp.getParameters().get(1), true); 4557 param2 = nl.get(0).primitiveValue(); 4558 } 4559 } 4560 4561 List<Base> result = new ArrayList<Base>(); 4562 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(param, param2); 4563 for (Base i : focus) { 4564 b.append(i.primitiveValue()); 4565 } 4566 result.add(new StringType(b.toString())); 4567 return result; 4568 } 4569 4570 private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4571 // todo: actually check the HTML 4572 if (focus.size() != 1) { 4573 return makeBoolean(false); 4574 } 4575 XhtmlNode x = focus.get(0).getXhtml(); 4576 if (x == null) { 4577 return makeBoolean(false); 4578 } 4579 boolean ok = checkHtmlNames(x, true); 4580 if (ok && VersionUtilities.isR6Plus(this.worker.getVersion())) { 4581 ok = checkForContent(x); 4582 } 4583 return makeBoolean(ok); 4584 } 4585 4586 private List<Base> funcHtmlChecks2(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4587 // todo: actually check the HTML 4588 if (focus.size() != 1) { 4589 return makeBoolean(false); 4590 } 4591 XhtmlNode x = focus.get(0).getXhtml(); 4592 if (x == null) { 4593 return makeBoolean(false); 4594 } 4595 return makeBoolean(checkForContent(x)); 4596 } 4597 4598 private boolean checkForContent(XhtmlNode x) { 4599 if ((x.getNodeType() == NodeType.Text && !Utilities.noString(x.getContent().trim())) || (x.getNodeType() == NodeType.Element && "img".equals(x.getName()))) { 4600 return true; 4601 } 4602 for (XhtmlNode c : x.getChildNodes()) { 4603 if (checkForContent(c)) { 4604 return true; 4605 } 4606 } 4607 return false; 4608 } 4609 4610 private List<Base> funcComparable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4611 if (focus.size() != 1 || !(focus.get(0).fhirType().equals("Quantity"))) { 4612 return makeBoolean(false); 4613 } 4614 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4615 if (nl.size() != 1 || !(nl.get(0).fhirType().equals("Quantity"))) { 4616 return makeBoolean(false); 4617 } 4618 String s1 = getNamedValue(focus.get(0), "system"); 4619 String u1 = getNamedValue(focus.get(0), "code"); 4620 String s2 = getNamedValue(nl.get(0), "system"); 4621 String u2 = getNamedValue(nl.get(0), "code"); 4622 4623 if (s1 == null || s2 == null || !s1.equals(s2)) { 4624 return makeBoolean(false); 4625 } 4626 if (u1 == null || u2 == null) { 4627 return makeBoolean(false); 4628 } 4629 if (u1.equals(u2)) { 4630 return makeBoolean(true); 4631 } 4632 if (s1.equals("http://unitsofmeasure.org") && worker.getUcumService() != null) { 4633 try { 4634 return makeBoolean(worker.getUcumService().isComparable(u1, u2)); 4635 } catch (UcumException e) { 4636 return makeBoolean(false); 4637 } 4638 } else { 4639 return makeBoolean(false); 4640 } 4641 } 4642 4643 4644 private String getNamedValue(Base base, String name) { 4645 Property p = base.getChildByName(name); 4646 if (p.hasValues() && p.getValues().size() == 1) { 4647 return p.getValues().get(0).primitiveValue(); 4648 } 4649 return null; 4650 } 4651 4652 private boolean checkHtmlNames(XhtmlNode node, boolean block) { 4653 if (node.getNodeType() == NodeType.Comment) { 4654 if (node.getContent().startsWith("DOCTYPE")) 4655 return false; 4656 } 4657 if (node.getNodeType() == NodeType.Element) { 4658 if (block) { 4659 if (!Utilities.existsInList(node.getName(), 4660 "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", 4661 "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", 4662 "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", 4663 "code", "samp", "img", "map", "area")) { 4664 return false; 4665 } 4666 } else { 4667 if (!Utilities.existsInList(node.getName(), 4668 "a", "span", "b", "em", "i", "strong", "small", "big", "small", "q", "var", "abbr", "acronym", "cite", "kbd", "q", "sub", "sup", "code", "samp", "img", "map", "area")) { 4669 return false; 4670 } 4671 } 4672 for (String an : node.getAttributes().keySet()) { 4673 boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, 4674 "title", "style", "class", "id", "idref", "lang", "xml:lang", "xml:space", "dir", "accesskey", "tabindex", 4675 // tables 4676 "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") || 4677 4678 Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite", 4679 "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src", 4680 "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape", 4681 "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border", 4682 "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap" 4683 ); 4684 if (!ok) { 4685 return false; 4686 } 4687 } 4688 for (XhtmlNode c : node.getChildNodes()) { 4689 if (!checkHtmlNames(c, block && !"p".equals(c))) { 4690 return false; 4691 } 4692 } 4693 } 4694 return true; 4695 } 4696 4697 private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4698 List<Base> result = new ArrayList<Base>(); 4699 if (exp.getParameters().size() == 1) { 4700 List<Base> pc = new ArrayList<Base>(); 4701 boolean all = true; 4702 for (Base item : focus) { 4703 pc.clear(); 4704 pc.add(item); 4705 Equality eq = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp); 4706 if (eq != Equality.True) { 4707 all = false; 4708 break; 4709 } 4710 } 4711 result.add(new BooleanType(all).noExtensions()); 4712 } else {// (exp.getParameters().size() == 0) { 4713 boolean all = true; 4714 for (Base item : focus) { 4715 Equality eq = asBool(item, true); 4716 if (eq != Equality.True) { 4717 all = false; 4718 break; 4719 } 4720 } 4721 result.add(new BooleanType(all).noExtensions()); 4722 } 4723 return result; 4724 } 4725 4726 4727 private ExecutionContext changeThis(ExecutionContext context, Base newThis) { 4728 ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, newThis); 4729 // append all of the defined variables from the context into the new context 4730 if (context.definedVariables != null) { 4731 for (String s : context.definedVariables.keySet()) { 4732 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 4733 } 4734 } 4735 return newContext; 4736 } 4737 4738 private ExecutionContext contextForParameter(ExecutionContext context) { 4739 ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.thisItem); 4740 newContext.total = context.total; 4741 newContext.index = context.index; 4742 // append all of the defined variables from the context into the new context 4743 if (context.definedVariables != null) { 4744 for (String s : context.definedVariables.keySet()) { 4745 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 4746 } 4747 } 4748 return newContext; 4749 } 4750 4751 private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) { 4752 ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis); 4753 // append all of the defined variables from the context into the new context 4754 if (context.definedVariables != null) { 4755 for (String s : context.definedVariables.keySet()) { 4756 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 4757 } 4758 } 4759 return newContext; 4760 } 4761 4762 private ExecutionTypeContext contextForParameter(ExecutionTypeContext context) { 4763 ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.resource, context.context, context.thisItem); 4764 // append all of the defined variables from the context into the new context 4765 if (context.definedVariables != null) { 4766 for (String s : context.definedVariables.keySet()) { 4767 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 4768 } 4769 } 4770 return newContext; 4771 } 4772 4773 private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4774 List<Base> result = new ArrayList<Base>(); 4775 result.add(DateTimeType.now()); 4776 return result; 4777 } 4778 4779 4780 private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4781 List<Base> result = new ArrayList<Base>(); 4782 result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY)); 4783 return result; 4784 } 4785 4786 4787 private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4788 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4789 if (nl.size() != 1 || focus.size() != 1) { 4790 return new ArrayList<Base>(); 4791 } 4792 4793 String url = nl.get(0).primitiveValue(); 4794 ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.findTxResource(ValueSet.class, url); 4795 if (vs == null) { 4796 return new ArrayList<Base>(); 4797 } 4798 Base l = focus.get(0); 4799 if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) { 4800 return makeBoolean(worker.validateCode(terminologyServiceOptions.withGuessSystem(), TypeConvertor.castToCoding(l), vs).isOk()); 4801 } else if (l.fhirType().equals("Coding")) { 4802 return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCoding(l), vs).isOk()); 4803 } else if (l.fhirType().equals("CodeableConcept")) { 4804 return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCodeableConcept(l), vs).isOk()); 4805 } else { 4806 // System.out.println("unknown type in funcMemberOf: "+l.fhirType()); 4807 return new ArrayList<Base>(); 4808 } 4809 } 4810 4811 4812 private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4813 List<Base> result = new ArrayList<Base>(); 4814 List<Base> current = new ArrayList<Base>(); 4815 current.addAll(focus); 4816 List<Base> added = new ArrayList<Base>(); 4817 boolean more = true; 4818 while (more) { 4819 added.clear(); 4820 for (Base item : current) { 4821 getChildrenByName(item, "*", added); 4822 } 4823 more = !added.isEmpty(); 4824 result.addAll(added); 4825 current.clear(); 4826 current.addAll(added); 4827 } 4828 return result; 4829 } 4830 4831 4832 private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4833 List<Base> result = new ArrayList<Base>(); 4834 for (Base b : focus) { 4835 getChildrenByName(b, "*", result); 4836 } 4837 return result; 4838 } 4839 4840 4841 private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException, PathEngineException { 4842 List<Base> result = new ArrayList<Base>(); 4843 List<Base> tB = execute(context, focus, expr.getParameters().get(0), true); 4844 String t = convertToString(tB); 4845 List<Base> rB = execute(context, focus, expr.getParameters().get(1), true); 4846 String r = convertToString(rB); 4847 4848 if (focus.size() == 0 || tB.size() == 0 || rB.size() == 0) { 4849 // 4850 } else if (focus.size() == 1) { 4851 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 4852 String f = convertToString(focus.get(0)); 4853 if (Utilities.noString(f)) { 4854 result.add(new StringType("")); 4855 } else { 4856 String n = f.replace(t, r); 4857 result.add(new StringType(n)); 4858 } 4859 } 4860 } else { 4861 throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "replace", focus.size()); 4862 } 4863 return result; 4864 } 4865 4866 4867 private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4868 List<Base> result = new ArrayList<Base>(); 4869 List<Base> regexB = execute(context, focus, exp.getParameters().get(0), true); 4870 String regex = convertToString(regexB); 4871 List<Base> replB = execute(context, focus, exp.getParameters().get(1), true); 4872 String repl = convertToString(replB); 4873 4874 if (focus.size() == 0 || regexB.size() == 0 || replB.size() == 0) { 4875 // 4876 } else if (focus.size() == 1 && !Utilities.noString(regex)) { 4877 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 4878 result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions()); 4879 } 4880 } else { 4881 result.add(new StringType(convertToString(focus.get(0))).noExtensions()); 4882 } 4883 return result; 4884 } 4885 4886 4887 private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4888 List<Base> result = new ArrayList<Base>(); 4889 List<Base> swb = execute(context, focus, exp.getParameters().get(0), true); 4890 String sw = convertToString(swb); 4891 4892 if (focus.size() == 0) { 4893 // 4894 } else if (swb.size() == 0) { 4895 // 4896 } else if (Utilities.noString(sw)) { 4897 result.add(new BooleanType(true).noExtensions()); 4898 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 4899 if (focus.size() == 1 && !Utilities.noString(sw)) { 4900 result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions()); 4901 } else { 4902 result.add(new BooleanType(false).noExtensions()); 4903 } 4904 } 4905 return result; 4906 } 4907 4908 4909 private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4910 List<Base> result = new ArrayList<Base>(); 4911 result.add(new StringType(convertToString(focus)).noExtensions()); 4912 return result; 4913 } 4914 4915 private List<Base> funcToBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4916 List<Base> result = new ArrayList<Base>(); 4917 if (focus.size() == 1) { 4918 if (focus.get(0) instanceof BooleanType) { 4919 result.add(focus.get(0)); 4920 } else if (focus.get(0) instanceof IntegerType) { 4921 int i = Integer.parseInt(focus.get(0).primitiveValue()); 4922 if (i == 0) { 4923 result.add(new BooleanType(false).noExtensions()); 4924 } else if (i == 1) { 4925 result.add(new BooleanType(true).noExtensions()); 4926 } 4927 } else if (focus.get(0) instanceof DecimalType) { 4928 if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0) { 4929 result.add(new BooleanType(false).noExtensions()); 4930 } else if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0) { 4931 result.add(new BooleanType(true).noExtensions()); 4932 } 4933 } else if (focus.get(0) instanceof StringType) { 4934 if ("true".equalsIgnoreCase(focus.get(0).primitiveValue())) { 4935 result.add(new BooleanType(true).noExtensions()); 4936 } else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue())) { 4937 result.add(new BooleanType(false).noExtensions()); 4938 } 4939 } 4940 } 4941 return result; 4942 } 4943 4944 private List<Base> funcToQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4945 List<Base> result = new ArrayList<Base>(); 4946 if (focus.size() == 1) { 4947 if (focus.get(0) instanceof Quantity) { 4948 result.add(focus.get(0)); 4949 } else if (focus.get(0) instanceof StringType) { 4950 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 4951 if (q != null) { 4952 result.add(q.noExtensions()); 4953 } 4954 } else if (focus.get(0) instanceof IntegerType) { 4955 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 4956 } else if (focus.get(0) instanceof DecimalType) { 4957 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 4958 } 4959 } 4960 return result; 4961 } 4962 4963 private List<Base> funcToDateTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4964 // List<Base> result = new ArrayList<Base>(); 4965 // result.add(new BooleanType(convertToBoolean(focus))); 4966 // return result; 4967 throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toDateTime"); 4968 } 4969 4970 private List<Base> funcToTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4971 // List<Base> result = new ArrayList<Base>(); 4972 // result.add(new BooleanType(convertToBoolean(focus))); 4973 // return result; 4974 throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toTime"); 4975 } 4976 4977 4978 private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4979 String s = convertToString(focus); 4980 List<Base> result = new ArrayList<Base>(); 4981 if (Utilities.isDecimal(s, true)) { 4982 result.add(new DecimalType(s).noExtensions()); 4983 } 4984 if ("true".equals(s)) { 4985 result.add(new DecimalType(1).noExtensions()); 4986 } 4987 if ("false".equals(s)) { 4988 result.add(new DecimalType(0).noExtensions()); 4989 } 4990 return result; 4991 } 4992 4993 4994 private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4995 if (focus.size() > 1) { 4996 throw makeException(exp, I18nConstants.FHIRPATH_NO_COLLECTION, "iif", focus.size()); 4997 } 4998 4999 List<Base> n1 = execute(focus.isEmpty() ? context : changeThis(context, focus.get(0)), focus, exp.getParameters().get(0), true); 5000 Equality v = asBool(n1, exp); 5001 if (v == Equality.True) { 5002 return execute(context, focus, exp.getParameters().get(1), true); 5003 } else if (exp.getParameters().size() < 3) { 5004 return new ArrayList<Base>(); 5005 } else { 5006 return execute(context, focus, exp.getParameters().get(2), true); 5007 } 5008 } 5009 5010 5011 private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5012 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 5013 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 5014 5015 List<Base> result = new ArrayList<Base>(); 5016 for (int i = 0; i < Math.min(focus.size(), i1); i++) { 5017 result.add(focus.get(i)); 5018 } 5019 return result; 5020 } 5021 5022 5023 private List<Base> funcUnion(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5024 List<Base> result = new ArrayList<Base>(); 5025 for (Base item : focus) { 5026 if (!doContains(result, item)) { 5027 result.add(item); 5028 } 5029 } 5030 for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) { 5031 if (!doContains(result, item)) { 5032 result.add(item); 5033 } 5034 } 5035 return result; 5036 } 5037 5038 private List<Base> funcCombine(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5039 List<Base> result = new ArrayList<Base>(); 5040 for (Base item : focus) { 5041 result.add(item); 5042 } 5043 for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) { 5044 result.add(item); 5045 } 5046 return result; 5047 } 5048 5049 private List<Base> funcIntersect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5050 List<Base> result = new ArrayList<Base>(); 5051 List<Base> other = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5052 5053 for (Base item : focus) { 5054 if (!doContains(result, item) && doContains(other, item)) { 5055 result.add(item); 5056 } 5057 } 5058 return result; 5059 } 5060 5061 private List<Base> funcExclude(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5062 List<Base> result = new ArrayList<Base>(); 5063 List<Base> other = execute(context, focus, exp.getParameters().get(0), true); 5064 5065 for (Base item : focus) { 5066 if (!doContains(other, item)) { 5067 result.add(item); 5068 } 5069 } 5070 return result; 5071 } 5072 5073 5074 private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws PathEngineException { 5075 if (focus.size() == 1) { 5076 return focus; 5077 } 5078 throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "single", focus.size()); 5079 } 5080 5081 5082 private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws PathEngineException { 5083 if (focus.size() == 0 || focus.size() > 1) { 5084 return makeNull(); 5085 } 5086 String ns = null; 5087 String n = null; 5088 5089 ExpressionNode texp = expr.getParameters().get(0); 5090 if (texp.getKind() != Kind.Name) { 5091 throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "0", "is"); 5092 } 5093 if (texp.getInner() != null) { 5094 if (texp.getInner().getKind() != Kind.Name) { 5095 throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "1", "is"); 5096 } 5097 ns = texp.getName(); 5098 n = texp.getInner().getName(); 5099 } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Date", "Time", "SimpleTypeInfo", "ClassInfo")) { 5100 ns = "System"; 5101 n = texp.getName(); 5102 } else { 5103 ns = "FHIR"; 5104 n = texp.getName(); 5105 } 5106 if (ns.equals("System")) { 5107 if (focus.get(0) instanceof Resource) { 5108 return makeBoolean(false); 5109 } 5110 if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) { 5111 String t = Utilities.capitalize(focus.get(0).fhirType()); 5112 if (n.equals(t)) { 5113 return makeBoolean(true); 5114 } 5115 if ("Date".equals(t) && n.equals("DateTime")) { 5116 return makeBoolean(true); 5117 } else { 5118 return makeBoolean(false); 5119 } 5120 } else { 5121 return makeBoolean(false); 5122 } 5123 } else if (ns.equals("FHIR")) { 5124 if (n.equals(focus.get(0).fhirType())) { 5125 return makeBoolean(true); 5126 } else { 5127 StructureDefinition sd = worker.fetchTypeDefinition(focus.get(0).fhirType()); 5128 while (sd != null) { 5129 if (n.equals(sd.getType())) { 5130 return makeBoolean(true); 5131 } 5132 sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 5133 } 5134 return makeBoolean(false); 5135 } 5136 } else { 5137 return makeBoolean(false); 5138 } 5139 } 5140 5141 5142 private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 5143 List<Base> result = new ArrayList<Base>(); 5144 String tn; 5145 if (expr.getParameters().get(0).getInner() != null) { 5146 tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName(); 5147 } else { 5148 tn = "FHIR."+expr.getParameters().get(0).getName(); 5149 } 5150 if (!isKnownType(tn)) { 5151 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); 5152 } 5153 if (!doNotEnforceAsSingletonRule && focus.size() > 1) { 5154 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_AS_COLLECTION, focus.size(), expr.toString()), I18nConstants.FHIRPATH_AS_COLLECTION); 5155 } 5156 5157 for (Base b : focus) { 5158 if (tn.startsWith("System.")) { 5159 if (b instanceof Element &&((Element) b).isDisallowExtensions()) { 5160 if (b.hasType(tn.substring(7))) { 5161 result.add(b); 5162 } 5163 } 5164 5165 } else if (tn.startsWith("FHIR.")) { 5166 String tnp = tn.substring(5); 5167 if (b.fhirType().equals(tnp)) { 5168 result.add(b); 5169 } else { 5170 StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType()); 5171 while (sd != null) { 5172 if (compareTypeNames(tnp, sd.getType())) { 5173 result.add(b); 5174 break; 5175 } 5176 sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 5177 } 5178 } 5179 } 5180 } 5181 return result; 5182 } 5183 5184 5185 private List<Base> funcOfType(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 5186 List<Base> result = new ArrayList<Base>(); 5187 String tn; 5188 if (expr.getParameters().get(0).getInner() != null) { 5189 tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName(); 5190 } else { 5191 tn = "FHIR."+expr.getParameters().get(0).getName(); 5192 } 5193 if (!isKnownType(tn)) { 5194 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); 5195 } 5196 5197 5198 for (Base b : focus) { 5199 if (tn.startsWith("System.")) { 5200 if (b instanceof Element &&((Element) b).isDisallowExtensions()) { 5201 if (b.hasType(tn.substring(7))) { 5202 result.add(b); 5203 } 5204 } 5205 5206 } else if (tn.startsWith("FHIR.")) { 5207 String tnp = tn.substring(5); 5208 if (b.fhirType().equals(tnp)) { 5209 result.add(b); 5210 } else { 5211 StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType()); 5212 while (sd != null) { 5213 if (tnp.equals(sd.getType())) { 5214 result.add(b); 5215 break; 5216 } 5217 sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 5218 } 5219 } 5220 } else if (tn.startsWith("CDA.")) { 5221 String tnp = Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", tn.substring(4)); 5222 if (b.fhirType().equals(tnp)) { 5223 result.add(b); 5224 } else { 5225 StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType()); 5226 while (sd != null) { 5227 if (tnp.equals(sd.getType())) { 5228 result.add(b); 5229 break; 5230 } 5231 sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 5232 } 5233 } 5234 } 5235 } 5236 return result; 5237 } 5238 5239 private List<Base> funcType(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5240 List<Base> result = new ArrayList<Base>(); 5241 for (Base item : focus) { 5242 result.add(new ClassTypeInfo(item)); 5243 } 5244 return result; 5245 } 5246 5247 5248 private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5249 List<Base> result = new ArrayList<Base>(); 5250 List<Base> current = new ArrayList<Base>(); 5251 current.addAll(focus); 5252 List<Base> added = new ArrayList<Base>(); 5253 boolean more = true; 5254 while (more) { 5255 added.clear(); 5256 List<Base> pc = new ArrayList<Base>(); 5257 for (Base item : current) { 5258 pc.clear(); 5259 pc.add(item); 5260 added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false)); 5261 } 5262 more = false; 5263 current.clear(); 5264 for (Base b : added) { 5265 boolean isnew = true; 5266 for (Base t : result) { 5267 if (b.equalsDeep(t)) { 5268 isnew = false; 5269 } 5270 } 5271 if (isnew) { 5272 result.add(b); 5273 current.add(b); 5274 more = true; 5275 } 5276 } 5277 } 5278 return result; 5279 } 5280 5281 5282 private List<Base> funcAggregate(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5283 List<Base> total = new ArrayList<Base>(); 5284 if (exp.parameterCount() > 1) { 5285 total = execute(context, focus, exp.getParameters().get(1), false); 5286 } 5287 5288 List<Base> pc = new ArrayList<Base>(); 5289 for (Base item : focus) { 5290 ExecutionContext c = changeThis(context, item); 5291 c.total = total; 5292 c.next(); 5293 total = execute(c, pc, exp.getParameters().get(0), true); 5294 } 5295 return total; 5296 } 5297 5298 5299 5300 private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5301 if (focus.size() < 1) { 5302 return makeBoolean(true); 5303 } 5304 if (focus.size() == 1) { 5305 return makeBoolean(true); 5306 } 5307 5308 boolean distinct = true; 5309 for (int i = 0; i < focus.size(); i++) { 5310 for (int j = i+1; j < focus.size(); j++) { 5311 Boolean eq = doEquals(focus.get(j), focus.get(i)); 5312 if (eq == null) { 5313 return new ArrayList<Base>(); 5314 } else if (eq == true) { 5315 distinct = false; 5316 break; 5317 } 5318 } 5319 } 5320 return makeBoolean(distinct); 5321 } 5322 5323 5324 private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5325 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 5326 5327 boolean valid = true; 5328 for (Base item : target) { 5329 boolean found = false; 5330 for (Base t : focus) { 5331 if (Base.compareDeep(item, t, false)) { 5332 found = true; 5333 break; 5334 } 5335 } 5336 if (!found) { 5337 valid = false; 5338 break; 5339 } 5340 } 5341 List<Base> result = new ArrayList<Base>(); 5342 result.add(new BooleanType(valid).noExtensions()); 5343 return result; 5344 } 5345 5346 5347 private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5348 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 5349 5350 boolean valid = true; 5351 for (Base item : focus) { 5352 boolean found = false; 5353 for (Base t : target) { 5354 if (Base.compareDeep(item, t, false)) { 5355 found = true; 5356 break; 5357 } 5358 } 5359 if (!found) { 5360 valid = false; 5361 break; 5362 } 5363 } 5364 List<Base> result = new ArrayList<Base>(); 5365 result.add(new BooleanType(valid).noExtensions()); 5366 return result; 5367 } 5368 5369 5370 private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5371 List<Base> result = new ArrayList<Base>(); 5372 boolean empty = true; 5373 List<Base> pc = new ArrayList<Base>(); 5374 for (Base f : focus) { 5375 if (exp.getParameters().size() == 1) { 5376 pc.clear(); 5377 pc.add(f); 5378 Equality v = asBool(execute(changeThis(context, f), pc, exp.getParameters().get(0), true), exp); 5379 if (v == Equality.True) { 5380 empty = false; 5381 } 5382 } else if (!f.isEmpty()) { 5383 empty = false; 5384 } 5385 } 5386 result.add(new BooleanType(!empty).noExtensions()); 5387 return result; 5388 } 5389 5390 5391 private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5392 List<Base> result = new ArrayList<Base>(); 5393 Base refContext = null; 5394 for (Base item : focus) { 5395 String s = convertToString(item); 5396 if (item.fhirType().equals("Reference")) { 5397 refContext = item; 5398 Property p = item.getChildByName("reference"); 5399 if (p != null && p.hasValues()) { 5400 s = convertToString(p.getValues().get(0)); 5401 } else { 5402 s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it) 5403 } 5404 } 5405 if (item.fhirType().equals("canonical")) { 5406 s = item.primitiveValue(); 5407 refContext = item; 5408 } 5409 if (s != null) { 5410 Base res = null; 5411 if (s.startsWith("#")) { 5412 String t = s.substring(1); 5413 Property p = context.rootResource.getChildByName("contained"); 5414 if (p != null) { 5415 for (Base c : p.getValues()) { 5416 if (t.equals(c.getIdBase())) { 5417 res = c; 5418 break; 5419 } 5420 } 5421 } 5422 } else if (hostServices != null) { 5423 try { 5424 res = hostServices.resolveReference(this, context.appInfo, s, refContext); 5425 } catch (Exception e) { 5426 res = null; 5427 } 5428 } 5429 if (res != null) { 5430 result.add(res); 5431 } 5432 } 5433 } 5434 5435 return result; 5436 } 5437 5438 private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5439 List<Base> result = new ArrayList<Base>(); 5440 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 5441 String url = nl.get(0).primitiveValue(); 5442 5443 for (Base item : focus) { 5444 List<Base> ext = new ArrayList<Base>(); 5445 getChildrenByName(item, "extension", ext); 5446 getChildrenByName(item, "modifierExtension", ext); 5447 for (Base ex : ext) { 5448 List<Base> vl = new ArrayList<Base>(); 5449 getChildrenByName(ex, "url", vl); 5450 if (convertToString(vl).equals(url)) { 5451 result.add(ex); 5452 } 5453 } 5454 } 5455 return result; 5456 } 5457 5458 private List<Base> funcAllFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5459 List<Base> result = new ArrayList<Base>(); 5460 if (exp.getParameters().size() == 1) { 5461 boolean all = true; 5462 List<Base> pc = new ArrayList<Base>(); 5463 for (Base item : focus) { 5464 pc.clear(); 5465 pc.add(item); 5466 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5467 Equality v = asBool(res, exp); 5468 if (v != Equality.False) { 5469 all = false; 5470 break; 5471 } 5472 } 5473 result.add(new BooleanType(all).noExtensions()); 5474 } else { 5475 boolean all = true; 5476 for (Base item : focus) { 5477 if (!canConvertToBoolean(item)) { 5478 throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); 5479 } 5480 5481 Equality v = asBool(item, true); 5482 if (v != Equality.False) { 5483 all = false; 5484 break; 5485 } 5486 } 5487 result.add(new BooleanType(all).noExtensions()); 5488 } 5489 return result; 5490 } 5491 5492 private List<Base> funcAnyFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5493 List<Base> result = new ArrayList<Base>(); 5494 if (exp.getParameters().size() == 1) { 5495 boolean any = false; 5496 List<Base> pc = new ArrayList<Base>(); 5497 for (Base item : focus) { 5498 pc.clear(); 5499 pc.add(item); 5500 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5501 Equality v = asBool(res, exp); 5502 if (v == Equality.False) { 5503 any = true; 5504 break; 5505 } 5506 } 5507 result.add(new BooleanType(any).noExtensions()); 5508 } else { 5509 boolean any = false; 5510 for (Base item : focus) { 5511 if (!canConvertToBoolean(item)) { 5512 throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); 5513 } 5514 5515 Equality v = asBool(item, true); 5516 if (v == Equality.False) { 5517 any = true; 5518 break; 5519 } 5520 } 5521 result.add(new BooleanType(any).noExtensions()); 5522 } 5523 return result; 5524 } 5525 5526 private List<Base> funcAllTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5527 List<Base> result = new ArrayList<Base>(); 5528 if (exp.getParameters().size() == 1) { 5529 boolean all = true; 5530 List<Base> pc = new ArrayList<Base>(); 5531 for (Base item : focus) { 5532 pc.clear(); 5533 pc.add(item); 5534 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5535 Equality v = asBool(res, exp); 5536 if (v != Equality.True) { 5537 all = false; 5538 break; 5539 } 5540 } 5541 result.add(new BooleanType(all).noExtensions()); 5542 } else { 5543 boolean all = true; 5544 for (Base item : focus) { 5545 if (!canConvertToBoolean(item)) { 5546 throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); 5547 } 5548 Equality v = asBool(item, true); 5549 if (v != Equality.True) { 5550 all = false; 5551 break; 5552 } 5553 } 5554 result.add(new BooleanType(all).noExtensions()); 5555 } 5556 return result; 5557 } 5558 5559 private List<Base> funcAnyTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5560 List<Base> result = new ArrayList<Base>(); 5561 if (exp.getParameters().size() == 1) { 5562 boolean any = false; 5563 List<Base> pc = new ArrayList<Base>(); 5564 for (Base item : focus) { 5565 pc.clear(); 5566 pc.add(item); 5567 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5568 Equality v = asBool(res, exp); 5569 if (v == Equality.True) { 5570 any = true; 5571 break; 5572 } 5573 } 5574 result.add(new BooleanType(any).noExtensions()); 5575 } else { 5576 boolean any = false; 5577 for (Base item : focus) { 5578 if (!canConvertToBoolean(item)) { 5579 throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); 5580 } 5581 5582 Equality v = asBool(item, true); 5583 if (v == Equality.True) { 5584 any = true; 5585 break; 5586 } 5587 } 5588 result.add(new BooleanType(any).noExtensions()); 5589 } 5590 return result; 5591 } 5592 5593 private boolean canConvertToBoolean(Base item) { 5594 return (item.isBooleanPrimitive()); 5595 } 5596 5597 private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5598 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 5599 String name = nl.get(0).primitiveValue(); 5600 if (exp.getParameters().size() == 2) { 5601 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 5602 log(name, n2); 5603 } else { 5604 log(name, focus); 5605 } 5606 return focus; 5607 } 5608 5609 private List<Base> funcDefineVariable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5610 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 5611 String name = nl.get(0).primitiveValue(); 5612 List<Base> value; 5613 if (exp.getParameters().size() == 2) { 5614 value = execute(context, focus, exp.getParameters().get(1), true); 5615 } else { 5616 value = focus; 5617 } 5618 // stash the variable into the context 5619 context.setDefinedVariable(name, value); 5620 return focus; 5621 } 5622 5623 private List<Base> funcCheck(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException { 5624 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 5625 if (!convertToBoolean(n1)) { 5626 List<Base> n2 = execute(context, focus, expr.getParameters().get(1), true); 5627 String name = n2.get(0).primitiveValue(); 5628 throw makeException(expr, I18nConstants.FHIRPATH_CHECK_FAILED, name); 5629 } 5630 return focus; 5631 } 5632 5633 private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5634 if (focus.size() <= 1) { 5635 return focus; 5636 } 5637 5638 List<Base> result = new ArrayList<Base>(); 5639 for (int i = 0; i < focus.size(); i++) { 5640 boolean found = false; 5641 for (int j = i+1; j < focus.size(); j++) { 5642 Boolean eq = doEquals(focus.get(j), focus.get(i)); 5643 if (eq == null) 5644 return new ArrayList<Base>(); 5645 else if (eq == true) { 5646 found = true; 5647 break; 5648 } 5649 } 5650 if (!found) { 5651 result.add(focus.get(i)); 5652 } 5653 } 5654 return result; 5655 } 5656 5657 private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5658 List<Base> result = new ArrayList<Base>(); 5659 List<Base> swb = execute(context, focus, exp.getParameters().get(0), true); 5660 String sw = convertToString(swb); 5661 5662 if (focus.size() == 0 || swb.size() == 0) { 5663 // 5664 } else if (focus.size() == 1 && !Utilities.noString(sw)) { 5665 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5666 String st = convertToString(focus.get(0)); 5667 if (Utilities.noString(st)) { 5668 result.add(new BooleanType(false).noExtensions()); 5669 } else { 5670 Pattern p = Pattern.compile("(?s)" + sw); 5671 Matcher m = p.matcher(st); 5672 boolean ok = m.find(); 5673 result.add(new BooleanType(ok).noExtensions()); 5674 } 5675 } 5676 } else { 5677 result.add(new BooleanType(false).noExtensions()); 5678 } 5679 return result; 5680 } 5681 5682 private List<Base> funcMatchesFull(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5683 List<Base> result = new ArrayList<Base>(); 5684 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 5685 5686 if (focus.size() == 1 && !Utilities.noString(sw)) { 5687 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5688 String st = convertToString(focus.get(0)); 5689 if (Utilities.noString(st)) { 5690 result.add(new BooleanType(false).noExtensions()); 5691 } else { 5692 Pattern p = Pattern.compile("(?s)" + sw); 5693 Matcher m = p.matcher(st); 5694 boolean ok = m.matches(); 5695 result.add(new BooleanType(ok).noExtensions()); 5696 } 5697 } 5698 } else { 5699 result.add(new BooleanType(false).noExtensions()); 5700 } 5701 return result; 5702 } 5703 5704 private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5705 List<Base> result = new ArrayList<Base>(); 5706 List<Base> swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5707 String sw = convertToString(swb); 5708 5709 if (focus.size() != 1) { 5710 // 5711 } else if (swb.size() != 1) { 5712 // 5713 } else if (Utilities.noString(sw)) { 5714 result.add(new BooleanType(true).noExtensions()); 5715 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5716 String st = convertToString(focus.get(0)); 5717 if (Utilities.noString(st)) { 5718 result.add(new BooleanType(false).noExtensions()); 5719 } else { 5720 result.add(new BooleanType(st.contains(sw)).noExtensions()); 5721 } 5722 } 5723 return result; 5724 } 5725 5726 private List<Base> baseToList(Base b) { 5727 List<Base> res = new ArrayList<>(); 5728 res.add(b); 5729 return res; 5730 } 5731 5732 private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5733 List<Base> result = new ArrayList<Base>(); 5734 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 5735 String s = convertToString(focus.get(0)); 5736 result.add(new IntegerType(s.length()).noExtensions()); 5737 } 5738 return result; 5739 } 5740 5741 private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5742 List<Base> result = new ArrayList<Base>(); 5743 if (focus.size() == 1) { 5744 String s = convertToString(focus.get(0)); 5745 result.add(new BooleanType(!Utilities.noString(s)).noExtensions()); 5746 } else { 5747 result.add(new BooleanType(false).noExtensions()); 5748 } 5749 return result; 5750 } 5751 5752 private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5753 List<Base> result = new ArrayList<Base>(); 5754 List<Base> swb = execute(context, focus, exp.getParameters().get(0), true); 5755 String sw = convertToString(swb); 5756 5757 if (focus.size() == 0) { 5758 // no result 5759 } else if (swb.size() == 0) { 5760 // no result 5761 } else if (Utilities.noString(sw)) { 5762 result.add(new BooleanType(true).noExtensions()); 5763 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5764 String s = convertToString(focus.get(0)); 5765 if (s == null) { 5766 result.add(new BooleanType(false).noExtensions()); 5767 } else { 5768 result.add(new BooleanType(s.startsWith(sw)).noExtensions()); 5769 } 5770 } 5771 return result; 5772 } 5773 5774 private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5775 List<Base> result = new ArrayList<Base>(); 5776 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 5777 String s = convertToString(focus.get(0)); 5778 if (!Utilities.noString(s)) { 5779 result.add(new StringType(s.toLowerCase()).noExtensions()); 5780 } 5781 } 5782 return result; 5783 } 5784 5785 private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5786 List<Base> result = new ArrayList<Base>(); 5787 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 5788 String s = convertToString(focus.get(0)); 5789 if (!Utilities.noString(s)) { 5790 result.add(new StringType(s.toUpperCase()).noExtensions()); 5791 } 5792 } 5793 return result; 5794 } 5795 5796 private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5797 List<Base> result = new ArrayList<Base>(); 5798 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 5799 String s = convertToString(focus.get(0)); 5800 for (char c : s.toCharArray()) { 5801 result.add(new StringType(String.valueOf(c)).noExtensions()); 5802 } 5803 } 5804 return result; 5805 } 5806 5807 private List<Base> funcIndexOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5808 List<Base> result = new ArrayList<Base>(); 5809 5810 List<Base> swb = execute(context, focus, exp.getParameters().get(0), true); 5811 String sw = convertToString(swb); 5812 if (focus.size() == 0) { 5813 // no result 5814 } else if (swb.size() == 0) { 5815 // no result 5816 } else if (Utilities.noString(sw)) { 5817 result.add(new IntegerType(0).noExtensions()); 5818 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5819 String s = convertToString(focus.get(0)); 5820 if (s == null) { 5821 result.add(new IntegerType(0).noExtensions()); 5822 } else { 5823 result.add(new IntegerType(s.indexOf(sw)).noExtensions()); 5824 } 5825 } 5826 return result; 5827 } 5828 5829 private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5830 List<Base> result = new ArrayList<Base>(); 5831 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 5832 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 5833 int i2 = -1; 5834 if (exp.parameterCount() == 2) { 5835 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 5836 if (n2.isEmpty()|| !n2.get(0).isPrimitive() || !Utilities.isInteger(n2.get(0).primitiveValue())) { 5837 return new ArrayList<Base>(); 5838 } 5839 i2 = Integer.parseInt(n2.get(0).primitiveValue()); 5840 } 5841 5842 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 5843 String sw = convertToString(focus.get(0)); 5844 String s; 5845 if (i1 < 0 || i1 >= sw.length()) { 5846 return new ArrayList<Base>(); 5847 } 5848 if (exp.parameterCount() == 2) { 5849 s = sw.substring(i1, Math.min(sw.length(), i1+i2)); 5850 } else { 5851 s = sw.substring(i1); 5852 } 5853 if (!Utilities.noString(s)) { 5854 result.add(new StringType(s).noExtensions()); 5855 } 5856 } 5857 return result; 5858 } 5859 5860 private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5861 String s = convertToString(focus); 5862 List<Base> result = new ArrayList<Base>(); 5863 if (Utilities.isInteger(s)) { 5864 result.add(new IntegerType(s).noExtensions()); 5865 } else if ("true".equals(s)) { 5866 result.add(new IntegerType(1).noExtensions()); 5867 } else if ("false".equals(s)) { 5868 result.add(new IntegerType(0).noExtensions()); 5869 } 5870 return result; 5871 } 5872 5873 private List<Base> funcIsInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5874 List<Base> result = new ArrayList<Base>(); 5875 if (focus.size() != 1) { 5876 result.add(new BooleanType(false).noExtensions()); 5877 } else if (focus.get(0) instanceof IntegerType) { 5878 result.add(new BooleanType(true).noExtensions()); 5879 } else if (focus.get(0) instanceof BooleanType) { 5880 result.add(new BooleanType(true).noExtensions()); 5881 } else if (focus.get(0) instanceof StringType) { 5882 result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions()); 5883 } else { 5884 result.add(new BooleanType(false).noExtensions()); 5885 } 5886 return result; 5887 } 5888 5889 private List<Base> funcIsBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5890 List<Base> result = new ArrayList<Base>(); 5891 if (focus.size() != 1) { 5892 result.add(new BooleanType(false).noExtensions()); 5893 } else if (focus.get(0) instanceof IntegerType) { 5894 result.add(new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1).noExtensions()); 5895 } else if (focus.get(0) instanceof DecimalType) { 5896 result.add(new BooleanType(((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0 || ((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0).noExtensions()); 5897 } else if (focus.get(0) instanceof BooleanType) { 5898 result.add(new BooleanType(true).noExtensions()); 5899 } else if (focus.get(0) instanceof StringType) { 5900 result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")).noExtensions()); 5901 } else { 5902 result.add(new BooleanType(false).noExtensions()); 5903 } 5904 return result; 5905 } 5906 5907 private List<Base> funcIsDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5908 List<Base> result = new ArrayList<Base>(); 5909 if (focus.size() != 1) { 5910 result.add(new BooleanType(false).noExtensions()); 5911 } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) { 5912 result.add(new BooleanType(true).noExtensions()); 5913 } else if (focus.get(0) instanceof StringType) { 5914 result.add(new BooleanType((convertToString(focus.get(0)).matches 5915 ("([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()); 5916 } else { 5917 result.add(new BooleanType(false).noExtensions()); 5918 } 5919 return result; 5920 } 5921 5922 private List<Base> funcIsDate(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5923 List<Base> result = new ArrayList<Base>(); 5924 if (focus.size() != 1) { 5925 result.add(new BooleanType(false).noExtensions()); 5926 } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) { 5927 result.add(new BooleanType(true).noExtensions()); 5928 } else if (focus.get(0) instanceof StringType) { 5929 result.add(new BooleanType((convertToString(focus.get(0)).matches 5930 ("([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()); 5931 } else { 5932 result.add(new BooleanType(false).noExtensions()); 5933 } 5934 return result; 5935 } 5936 5937 private List<Base> funcConformsTo(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException { 5938 if (hostServices == null) { 5939 throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "conformsTo"); 5940 } 5941 List<Base> result = new ArrayList<Base>(); 5942 if (focus.size() != 1) { 5943 result.add(new BooleanType(false).noExtensions()); 5944 } else { 5945 String url = convertToString(execute(context, focus, expr.getParameters().get(0), true)); 5946 result.add(new BooleanType(hostServices.conformsToProfile(this, context.appInfo, focus.get(0), url)).noExtensions()); 5947 } 5948 return result; 5949 } 5950 5951 private List<Base> funcIsTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5952 List<Base> result = new ArrayList<Base>(); 5953 if (focus.size() != 1) { 5954 result.add(new BooleanType(false).noExtensions()); 5955 } else if (focus.get(0) instanceof TimeType) { 5956 result.add(new BooleanType(true).noExtensions()); 5957 } else if (focus.get(0) instanceof StringType) { 5958 result.add(new BooleanType((convertToString(focus.get(0)).matches 5959 ("(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()); 5960 } else { 5961 result.add(new BooleanType(false).noExtensions()); 5962 } 5963 return result; 5964 } 5965 5966 private List<Base> funcIsString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5967 List<Base> result = new ArrayList<Base>(); 5968 if (focus.size() != 1) { 5969 result.add(new BooleanType(false).noExtensions()); 5970 } else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType)) { 5971 result.add(new BooleanType(true).noExtensions()); 5972 } else { 5973 result.add(new BooleanType(false).noExtensions()); 5974 } 5975 return result; 5976 } 5977 5978 private List<Base> funcIsQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5979 List<Base> result = new ArrayList<Base>(); 5980 if (focus.size() != 1) { 5981 result.add(new BooleanType(false).noExtensions()); 5982 } else if (focus.get(0) instanceof IntegerType) { 5983 result.add(new BooleanType(true).noExtensions()); 5984 } else if (focus.get(0) instanceof DecimalType) { 5985 result.add(new BooleanType(true).noExtensions()); 5986 } else if (focus.get(0) instanceof Quantity) { 5987 result.add(new BooleanType(true).noExtensions()); 5988 } else if (focus.get(0) instanceof BooleanType) { 5989 result.add(new BooleanType(true).noExtensions()); 5990 } else if (focus.get(0) instanceof StringType) { 5991 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 5992 result.add(new BooleanType(q != null).noExtensions()); 5993 } else { 5994 result.add(new BooleanType(false).noExtensions()); 5995 } 5996 return result; 5997 } 5998 5999 public Quantity parseQuantityString(String s) { 6000 if (s == null) { 6001 return null; 6002 } 6003 s = s.trim(); 6004 if (s.contains(" ")) { 6005 String v = s.substring(0, s.indexOf(" ")).trim(); 6006 s = s.substring(s.indexOf(" ")).trim(); 6007 if (!Utilities.isDecimal(v, false)) { 6008 return null; 6009 } 6010 if (s.startsWith("'") && s.endsWith("'")) { 6011 return Quantity.fromUcum(v, s.substring(1, s.length()-1)); 6012 } 6013 if (s.equals("year") || s.equals("years")) { 6014 return Quantity.fromUcum(v, "a"); 6015 } else if (s.equals("month") || s.equals("months")) { 6016 return Quantity.fromUcum(v, "mo_s"); 6017 } else if (s.equals("week") || s.equals("weeks")) { 6018 return Quantity.fromUcum(v, "wk"); 6019 } else if (s.equals("day") || s.equals("days")) { 6020 return Quantity.fromUcum(v, "d"); 6021 } else if (s.equals("hour") || s.equals("hours")) { 6022 return Quantity.fromUcum(v, "h"); 6023 } else if (s.equals("minute") || s.equals("minutes")) { 6024 return Quantity.fromUcum(v, "min"); 6025 } else if (s.equals("second") || s.equals("seconds")) { 6026 return Quantity.fromUcum(v, "s"); 6027 } else if (s.equals("millisecond") || s.equals("milliseconds")) { 6028 return Quantity.fromUcum(v, "ms"); 6029 } else { 6030 return null; 6031 } 6032 } else { 6033 if (Utilities.isDecimal(s, true)) { 6034 return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1"); 6035 } else { 6036 return null; 6037 } 6038 } 6039 } 6040 6041 6042 private List<Base> funcIsDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6043 List<Base> result = new ArrayList<Base>(); 6044 if (focus.size() != 1) { 6045 result.add(new BooleanType(false).noExtensions()); 6046 } else if (focus.get(0) instanceof IntegerType) { 6047 result.add(new BooleanType(true).noExtensions()); 6048 } else if (focus.get(0) instanceof BooleanType) { 6049 result.add(new BooleanType(true).noExtensions()); 6050 } else if (focus.get(0) instanceof DecimalType) { 6051 result.add(new BooleanType(true).noExtensions()); 6052 } else if (focus.get(0) instanceof StringType) { 6053 result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)), true)).noExtensions()); 6054 } else { 6055 result.add(new BooleanType(false).noExtensions()); 6056 } 6057 return result; 6058 } 6059 6060 private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6061 List<Base> result = new ArrayList<Base>(); 6062 result.add(new IntegerType(focus.size()).noExtensions()); 6063 return result; 6064 } 6065 6066 private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6067 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 6068 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 6069 6070 List<Base> result = new ArrayList<Base>(); 6071 for (int i = i1; i < focus.size(); i++) { 6072 result.add(focus.get(i)); 6073 } 6074 return result; 6075 } 6076 6077 private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6078 List<Base> result = new ArrayList<Base>(); 6079 for (int i = 1; i < focus.size(); i++) { 6080 result.add(focus.get(i)); 6081 } 6082 return result; 6083 } 6084 6085 private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6086 List<Base> result = new ArrayList<Base>(); 6087 if (focus.size() > 0) { 6088 result.add(focus.get(focus.size()-1)); 6089 } 6090 return result; 6091 } 6092 6093 private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6094 List<Base> result = new ArrayList<Base>(); 6095 if (focus.size() > 0) { 6096 result.add(focus.get(0)); 6097 } 6098 return result; 6099 } 6100 6101 6102 private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6103 List<Base> result = new ArrayList<Base>(); 6104 List<Base> pc = new ArrayList<Base>(); 6105 for (Base item : focus) { 6106 pc.clear(); 6107 pc.add(item); 6108 Equality v = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp); 6109 if (v == Equality.True) { 6110 result.add(item); 6111 } 6112 } 6113 return result; 6114 } 6115 6116 private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6117 List<Base> result = new ArrayList<Base>(); 6118 List<Base> pc = new ArrayList<Base>(); 6119 int i = 0; 6120 for (Base item : focus) { 6121 pc.clear(); 6122 pc.add(item); 6123 result.addAll(execute(changeThis(context, item).setIndex(i), pc, exp.getParameters().get(0), true)); 6124 i++; 6125 } 6126 return result; 6127 } 6128 6129 6130 private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6131 List<Base> result = new ArrayList<Base>(); 6132 String s = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 6133 if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) { 6134 result.add(focus.get(Integer.parseInt(s))); 6135 } 6136 return result; 6137 } 6138 6139 private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6140 List<Base> result = new ArrayList<Base>(); 6141 result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions()); 6142 return result; 6143 } 6144 6145 private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 6146 List<Base> result = new ArrayList<Base>(); 6147 Equality v = asBool(focus, exp); 6148 if (v != Equality.Null) { 6149 result.add(new BooleanType(v != Equality.True)); 6150 } 6151 return result; 6152 } 6153 6154 private class ElementDefinitionMatch { 6155 private ElementDefinition definition; 6156 private ElementDefinition sourceDefinition; // if there was a content reference 6157 private String fixedType; 6158 public ElementDefinitionMatch(ElementDefinition definition, String fixedType) { 6159 super(); 6160 this.definition = definition; 6161 this.fixedType = fixedType; 6162 } 6163 public ElementDefinition getDefinition() { 6164 return definition; 6165 } 6166 public ElementDefinition getSourceDefinition() { 6167 return sourceDefinition; 6168 } 6169 public String getFixedType() { 6170 return fixedType; 6171 } 6172 6173 } 6174 6175 private void getChildTypesByName(String type, String name, TypeDetails result, ExpressionNode expr, TypeDetails focus, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException { 6176 if (Utilities.noString(type)) { 6177 throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, "", "getChildTypesByName"); 6178 } 6179 if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) { 6180 return; 6181 } 6182 6183 if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { 6184 getSimpleTypeChildTypesByName(name, result); 6185 } else if (type.equals(TypeDetails.FP_ClassInfo)) { 6186 getClassInfoChildTypesByName(name, result); 6187 } else { 6188 if (type.startsWith(Constants.NS_SYSTEM_TYPE)) { 6189 return; 6190 } 6191 6192 String url = null; 6193 if (type.contains("#")) { 6194 url = type.substring(0, type.indexOf("#")); 6195 } else { 6196 url = type; 6197 } 6198 String tail = ""; 6199 StructureDefinition sd = worker.fetchTypeDefinition(url); 6200 if (sd == null) { 6201 sd = worker.fetchResource(StructureDefinition.class, url); 6202 } 6203 if (sd == null) { 6204 if (url.startsWith(TypeDetails.FP_NS)) { 6205 return; 6206 } else { 6207 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, url, "getChildTypesByName#1"); 6208 } 6209 } 6210 List<StructureDefinition> sdl = new ArrayList<StructureDefinition>(); 6211 ElementDefinitionMatch m = null; 6212 if (type.contains("#")) { 6213 List<ElementDefinitionMatch> list = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false, expr); 6214 m = list.size() == 1 ? list.get(0) : null; 6215 } 6216 if (m != null && hasDataType(m.definition)) { 6217 if (m.fixedType != null) { 6218 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, null), sd); 6219 if (dt == null) { 6220 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(m.fixedType, null), "getChildTypesByName#2"); 6221 } 6222 sdl.add(dt); 6223 } else 6224 for (TypeRefComponent t : m.definition.getType()) { 6225 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), null)); 6226 if (dt == null) { 6227 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(t.getCode(), null), "getChildTypesByName#3"); 6228 } 6229 addTypeAndDescendents(sdl, dt, cu.allStructures()); 6230 // also add any descendant types 6231 } 6232 } else { 6233 addTypeAndDescendents(sdl, sd, cu.allStructures()); 6234 if (type.contains("#")) { 6235 tail = type.substring(type.indexOf("#")+1); 6236 if (tail.contains(".")) { 6237 tail = tail.substring(tail.indexOf(".")); 6238 } else { 6239 tail = ""; 6240 } 6241 } 6242 } 6243 6244 for (StructureDefinition sdi : sdl) { 6245 String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."; 6246 if (name.equals("**")) { 6247 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 6248 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 6249 if (ed.getPath().startsWith(path)) { 6250 if (ed.hasContentReference()) { 6251 String cpath = ed.getContentReference(); 6252 String tn = sdi.getType()+cpath; 6253 if (!result.hasType(worker, tn)) { 6254 if (elementDependencies != null) { 6255 elementDependencies.add(ed); 6256 } 6257 getChildTypesByName(result.addType(tn), "**", result, expr, null, elementDependencies); 6258 } 6259 } else { 6260 for (TypeRefComponent t : ed.getType()) { 6261 if (t.hasCode() && t.getCodeElement().hasValue()) { 6262 String tn = null; 6263 if (Utilities.existsInList(t.getCode(), "Element", "BackboneElement", "Base") || cu.isAbstractType(t.getCode())) { 6264 tn = sdi.getType()+"#"+ed.getPath(); 6265 } else { 6266 tn = t.getCode(); 6267 } 6268 if (t.getCode().equals("Resource")) { 6269 for (String rn : worker.getResourceNames()) { 6270 if (!result.hasType(worker, rn)) { 6271 if (elementDependencies != null) { 6272 elementDependencies.add(ed); 6273 } 6274 getChildTypesByName(result.addType(rn), "**", result, expr, null, elementDependencies); 6275 } 6276 } 6277 } else if (!result.hasType(worker, tn)) { 6278 if (elementDependencies != null) { 6279 elementDependencies.add(ed); 6280 } 6281 getChildTypesByName(result.addType(tn), "**", result, expr, null, elementDependencies); 6282 } 6283 } 6284 } 6285 } 6286 } 6287 } 6288 } else if (name.equals("*")) { 6289 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 6290 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 6291 if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains(".")) 6292 for (TypeRefComponent t : ed.getType()) { 6293 if (Utilities.noString(t.getCode())) { // Element.id or Extension.url 6294 if (elementDependencies != null) { 6295 elementDependencies.add(ed); 6296 } 6297 result.addType("System.string"); 6298 } else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) { 6299 if (elementDependencies != null) { 6300 elementDependencies.add(ed); 6301 } 6302 result.addType(sdi.getType()+"#"+ed.getPath()); 6303 } else if (t.getCode().equals("Resource")) { 6304 if (elementDependencies != null) { 6305 elementDependencies.add(ed); 6306 } 6307 result.addTypes(worker.getResourceNames()); 6308 } else { 6309 if (elementDependencies != null) { 6310 elementDependencies.add(ed); 6311 } 6312 result.addType(t.getCode()); 6313 copyTargetProfiles(ed, t, focus, result); 6314 } 6315 } 6316 } 6317 } else { 6318 path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name; 6319 6320 List<ElementDefinitionMatch> edl = getElementDefinition(sdi, path, isAllowPolymorphicNames(), expr); 6321 for (ElementDefinitionMatch ed : edl) { 6322 if (ed.getDefinition().isChoice()) { 6323 result.setChoice(true); 6324 } 6325 if (!Utilities.noString(ed.getFixedType())) { 6326 if (elementDependencies != null) { 6327 elementDependencies.add(ed.definition); 6328 } 6329 result.addType(ed.getFixedType()); 6330 } else if (ed.getSourceDefinition() != null) { 6331 ProfiledType pt = new ProfiledType(sdi.getType()+"#"+ed.definition.getPath()); 6332 result.addType(ed.getSourceDefinition().unbounded() ? CollectionStatus.ORDERED : CollectionStatus.SINGLETON, pt); 6333 } else { 6334 for (TypeRefComponent t : ed.getDefinition().getType()) { 6335 if (Utilities.noString(t.getCode())) { 6336 if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", "Extension.url")) { 6337 if (elementDependencies != null) { 6338 elementDependencies.add(ed.definition); 6339 } 6340 result.addType(TypeDetails.FP_NS, "System.String"); 6341 } 6342 break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path); 6343 } 6344 6345 ProfiledType pt = null; 6346 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement") || isAbstractType(t.getCode())) { 6347 pt = new ProfiledType(sdi.getUrl()+"#"+path); 6348 } else if (t.getCode().equals("Resource")) { 6349 if (elementDependencies != null) { 6350 elementDependencies.add(ed.definition); 6351 } 6352 result.addTypes(worker.getResourceNames()); 6353 } else { 6354 pt = new ProfiledType(t.getCode()); 6355 } 6356 if (pt != null) { 6357 if (t.hasProfile()) { 6358 pt.addProfiles(t.getProfile()); 6359 } 6360 if (ed.getDefinition().hasBinding()) { 6361 pt.addBinding(ed.getDefinition().getBinding()); 6362 } 6363 if (elementDependencies != null) { 6364 elementDependencies.add(ed.definition); 6365 } 6366 result.addType(ed.definition.unbounded() ? CollectionStatus.ORDERED : CollectionStatus.SINGLETON, pt); 6367 copyTargetProfiles(ed.getDefinition(), t, focus, result); 6368 } 6369 } 6370 } 6371 } 6372 } 6373 } 6374 } 6375 } 6376 6377 private void copyTargetProfiles(ElementDefinition ed, TypeRefComponent t, TypeDetails focus, TypeDetails result) { 6378 if (t.hasTargetProfile()) { 6379 for (CanonicalType u : t.getTargetProfile()) { 6380 result.addTarget(u.primitiveValue()); 6381 } 6382 } else if (focus != null && focus.hasType("CodeableReference") && ed.getPath().endsWith(".reference") && focus.getTargets() != null) { // special case, targets are on parent 6383 for (String s : focus.getTargets()) { 6384 result.addTarget(s); 6385 } 6386 } 6387 } 6388 6389 private void addTypeAndDescendents(List<StructureDefinition> sdl, StructureDefinition dt, List<StructureDefinition> types) { 6390 sdl.add(dt); 6391 for (StructureDefinition sd : types) { 6392 if (sd.hasBaseDefinition() && sd.getBaseDefinition().equals(dt.getUrl()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 6393 addTypeAndDescendents(sdl, sd, types); 6394 } 6395 } 6396 } 6397 6398 private void getClassInfoChildTypesByName(String name, TypeDetails result) { 6399 if (name.equals("namespace")) { 6400 result.addType(TypeDetails.FP_String); 6401 } 6402 if (name.equals("name")) { 6403 result.addType(TypeDetails.FP_String); 6404 } 6405 } 6406 6407 6408 private void getSimpleTypeChildTypesByName(String name, TypeDetails result) { 6409 if (name.equals("namespace")) { 6410 result.addType(TypeDetails.FP_String); 6411 } 6412 if (name.equals("name")) { 6413 result.addType(TypeDetails.FP_String); 6414 } 6415 } 6416 6417 6418 public List<ElementDefinitionMatch> getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, ExpressionNode expr) throws PathEngineException { 6419 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 6420 if (ed.getPath().equals(path)) { 6421 if (ed.hasContentReference()) { 6422 ElementDefinitionMatch res = getElementDefinitionById(sd, ed.getContentReference()); 6423 if (res == null) { 6424 throw new Error("Unable to find "+ed.getContentReference()); 6425 } else { 6426 res.sourceDefinition = ed; 6427 } 6428 return ml(res); 6429 } else { 6430 return ml(new ElementDefinitionMatch(ed, null)); 6431 } 6432 } 6433 if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3) { 6434 return ml(new ElementDefinitionMatch(ed, null)); 6435 } 6436 if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) { 6437 String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3)); 6438 if (primitiveTypes.contains(s)) { 6439 return ml(new ElementDefinitionMatch(ed, s)); 6440 } else { 6441 return ml(new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3))); 6442 } 6443 } 6444 if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 6445 // now we walk into the type. 6446 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 6447 List<ElementDefinitionMatch> list = new ArrayList<>(); 6448 // for each type, does it have the next node in the path? 6449 for (TypeRefComponent tr : ed.getType()) { 6450 StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tr.getCode(), null), sd); 6451 if (nsd == null) { 6452 throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), "getElementDefinition"); 6453 } 6454 List<ElementDefinitionMatch> edl = getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr); 6455 list.addAll(edl); 6456 } 6457 return list; 6458 } 6459 StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), null), sd); 6460 if (nsd == null) { 6461 throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), "getElementDefinition"); 6462 } 6463 return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr); 6464 } 6465 if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) { 6466 ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference()); 6467 List<ElementDefinitionMatch> res = getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName, expr); 6468 if (res.size() == 0) { 6469 throw new Error("Unable to find "+ed.getContentReference()); 6470 } else { 6471 for (ElementDefinitionMatch item : res) { 6472 item.sourceDefinition = ed; 6473 } 6474 } 6475 return res; 6476 } 6477 } 6478 return ml(null); 6479 } 6480 6481 private List<ElementDefinitionMatch> ml(ElementDefinitionMatch item) { 6482 List<ElementDefinitionMatch> list = new ArrayList<>(); 6483 if (item != null) { 6484 list.add(item); 6485 } 6486 return list; 6487 } 6488 6489 private boolean isAbstractType(List<TypeRefComponent> list) { 6490 if (list.size() != 1) { 6491 return false; 6492 } else { 6493 return isAbstractType(list.get(0).getCode()); 6494 } 6495 } 6496 6497 private boolean isAbstractType(String code) { 6498 StructureDefinition sd = worker.fetchTypeDefinition(code); 6499 return sd != null && sd.getAbstract() && sd.getKind() != StructureDefinitionKind.RESOURCE; 6500 } 6501 6502 6503 private boolean hasType(ElementDefinition ed, String s) { 6504 for (TypeRefComponent t : ed.getType()) { 6505 if (s.equalsIgnoreCase(t.getCode())) { 6506 return true; 6507 } 6508 } 6509 return false; 6510 } 6511 6512 private boolean hasDataType(ElementDefinition ed) { 6513 return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement") || isAbstractType(ed.getType().get(0).getCode())); 6514 } 6515 6516 private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) { 6517 if (ref.startsWith(sd.getUrl()+"#")) { 6518 ref = ref.replace(sd.getUrl()+"#", "#"); 6519 } 6520 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 6521 if (ref.equals("#"+ed.getId())) { 6522 return new ElementDefinitionMatch(ed, null); 6523 } 6524 } 6525 return null; 6526 } 6527 6528 6529 public boolean hasLog() { 6530 return log != null && log.length() > 0; 6531 } 6532 6533 6534 public String takeLog() { 6535 if (!hasLog()) { 6536 return ""; 6537 } 6538 String s = log.toString(); 6539 log = new StringBuilder(); 6540 return s; 6541 } 6542 6543 6544 /** given an element definition in a profile, what element contains the differentiating fixed 6545 * for the element, given the differentiating expresssion. The expression is only allowed to 6546 * use a subset of FHIRPath 6547 * 6548 * @param profile 6549 * @param element 6550 * @return 6551 * @throws PathEngineException 6552 * @throws DefinitionException 6553 */ 6554 public TypedElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, TypedElementDefinition element, StructureDefinition source, boolean dontWalkIntoReferences) throws DefinitionException { 6555 StructureDefinition sd = profile; 6556 TypedElementDefinition focus = null; 6557 boolean okToNotResolve = false; 6558 6559 if (expr.getKind() == Kind.Name) { 6560 if (element.getElement().hasSlicing()) { 6561 ElementDefinition slice = pickMandatorySlice(sd, element.getElement()); 6562 if (slice == null) { 6563 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED, element.getElement().getId()); 6564 } 6565 element = new TypedElementDefinition(slice); 6566 } 6567 6568 if (expr.getName().equals("$this")) { 6569 focus = element; 6570 } else { 6571 SourcedChildDefinitions childDefinitions; 6572 childDefinitions = profileUtilities.getChildMap(sd, element.getElement()); 6573 // if that's empty, get the children of the type 6574 if (childDefinitions.getList().isEmpty()) { 6575 6576 sd = fetchStructureByType(element, expr); 6577 if (sd == null) { 6578 throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getElement().getType().get(0).getProfile(), element.getElement().getId()); 6579 } 6580 childDefinitions = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep()); 6581 } 6582 for (ElementDefinition t : childDefinitions.getList()) { 6583 if (tailMatches(t, expr.getName()) && !t.hasSlicing()) { // GG: slicing is a problem here. This is for an exetnsion with a fixed value (type slicing) 6584 focus = new TypedElementDefinition(t); 6585 break; 6586 } 6587 } 6588 } 6589 } else if (expr.getKind() == Kind.Function) { 6590 if ("resolve".equals(expr.getName())) { 6591 if (element.getTypes().size() == 0) { 6592 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NO_TYPE, element.getElement().getId()); 6593 } 6594 if (element.getTypes().size() > 1) { 6595 throw makeExceptionPlural(element.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_MULTIPLE_TYPES, element.getElement().getId()); 6596 } 6597 if (!element.getTypes().get(0).hasTarget()) { 6598 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NOT_REFERENCE, element.getElement().getId(), element.getElement().getType().get(0).getCode()+")"); 6599 } 6600 if (element.getTypes().get(0).getTargetProfile().size() > 1) { 6601 throw makeExceptionPlural(element.getTypes().get(0).getTargetProfile().size(), expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_NO_TARGET, element.getElement().getId()); 6602 } 6603 sd = worker.fetchResource(StructureDefinition.class, element.getTypes().get(0).getTargetProfile().get(0).getValue(), profile); 6604 if (sd == null) { 6605 throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getTypes().get(0).getTargetProfile(), element.getElement().getId()); 6606 } 6607 focus = new TypedElementDefinition(sd.getSnapshot().getElementFirstRep()); 6608 } else if ("extension".equals(expr.getName())) { 6609 String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue(); 6610 SourcedChildDefinitions childDefinitions = profileUtilities.getChildMap(sd, element.getElement()); 6611 for (ElementDefinition t : childDefinitions.getList()) { 6612 if (t.getPath().endsWith(".extension") && t.hasSliceName()) { 6613 StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() || t.getType().get(0).getProfile().isEmpty()) ? 6614 null : worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue(), profile); 6615 while (exsd != null && !exsd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) { 6616 exsd = worker.fetchResource(StructureDefinition.class, exsd.getBaseDefinition(), exsd); 6617 } 6618 if (exsd != null && exsd.getUrl().equals(targetUrl)) { 6619 if (profileUtilities.getChildMap(sd, t).getList().isEmpty()) { 6620 sd = exsd; 6621 } 6622 focus = new TypedElementDefinition(t); 6623 break; 6624 } 6625 } 6626 } 6627 if (focus == null) { 6628 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION, expr.toString(), targetUrl, element.getElement().getId(), sd.getUrl()); 6629 } 6630 } else if ("ofType".equals(expr.getName())) { 6631 if (!element.getElement().hasType()) { 6632 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_TYPE_NONE, element.getElement().getId()); 6633 } 6634 List<String> atn = new ArrayList<>(); 6635 for (TypeRefComponent tr : element.getTypes()) { 6636 if (!tr.hasCode()) { 6637 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NO_CODE, element.getElement().getId()); 6638 } 6639 atn.add(tr.getCode()); 6640 } 6641 String stn = expr.getParameters().get(0).getName(); 6642 okToNotResolve = true; 6643 if ((atn.contains(stn))) { 6644 if (element.getTypes().size() > 1) { 6645 focus = new TypedElementDefinition( element.getSrc(), element.getElement(), stn); 6646 } else { 6647 focus = element; 6648 } 6649 } 6650 } else { 6651 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_NAME, expr.getName()); 6652 } 6653 } else if (expr.getKind() == Kind.Group) { 6654 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP, expr.toString()); 6655 } else if (expr.getKind() == Kind.Constant) { 6656 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST); 6657 } 6658 6659 if (focus == null) { 6660 if (okToNotResolve) { 6661 return null; 6662 } else { 6663 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString(), source.getUrl(), element.getElement().getId(), profile.getUrl()); 6664 } 6665 } else { 6666 // 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 6667 // then we don't do that. .resolve() is allowed on the Reference.reference, but the target of the reference will be defined 6668 // on the Reference, not the reference.reference. 6669 ExpressionNode next = expr.getInner(); 6670 if (dontWalkIntoReferences && focus.hasType("Reference") && next != null && next.getKind() == Kind.Name && next.getName().equals("reference")) { 6671 next = next.getInner(); 6672 } 6673 if (next == null) { 6674 return focus; 6675 } else { 6676 return evaluateDefinition(next, sd, focus, profile, dontWalkIntoReferences); 6677 } 6678 } 6679 } 6680 6681 private ElementDefinition pickMandatorySlice(StructureDefinition sd, ElementDefinition element) throws DefinitionException { 6682 List<ElementDefinition> list = profileUtilities.getSliceList(sd, element); 6683 for (ElementDefinition ed : list) { 6684 if (ed.getMin() > 0) { 6685 return ed; 6686 } 6687 } 6688 return null; 6689 } 6690 6691 6692 private StructureDefinition fetchStructureByType(TypedElementDefinition ed, ExpressionNode expr) throws DefinitionException { 6693 if (ed.getTypes().size() == 0) { 6694 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NOTYPE, ed.getElement().getId()); 6695 } 6696 if (ed.getTypes().size() > 1) { 6697 throw makeExceptionPlural(ed.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES, ed.getElement().getId()); 6698 } 6699 if (ed.getTypes().get(0).getProfile().size() > 1) { 6700 throw makeExceptionPlural(ed.getTypes().get(0).getProfile().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES, ed.getElement().getId()); 6701 } 6702 if (ed.getTypes().get(0).hasProfile()) { 6703 return worker.fetchResource(StructureDefinition.class, ed.getTypes().get(0).getProfile().get(0).getValue(), ed.getSrc()); 6704 } else { 6705 return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getTypes().get(0).getCode(), null), ed.getSrc()); 6706 } 6707 } 6708 6709 6710 private boolean tailMatches(ElementDefinition t, String d) { 6711 String tail = tailDot(t.getPath()); 6712 if (d.contains("[")) { 6713 return tail.startsWith(d.substring(0, d.indexOf('['))); 6714 } else if (tail.equals(d)) { 6715 return true; 6716 } 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())) { 6717 return tail.startsWith(d); 6718 } else if (t.getPath().endsWith("[x]") && tail.startsWith(d)) { 6719 return true; 6720 } 6721 return false; 6722 } 6723 6724 private String tailDot(String path) { 6725 return path.substring(path.lastIndexOf(".") + 1); 6726 } 6727 6728 private Equality asBool(List<Base> items, ExpressionNode expr) throws PathEngineException { 6729 if (items.size() == 0) { 6730 return Equality.Null; 6731 } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) { 6732 return asBool(items.get(0), true); 6733 } else if (items.size() == 1) { 6734 return Equality.True; 6735 } else { 6736 throw makeException(expr, I18nConstants.FHIRPATH_UNABLE_BOOLEAN, convertToString(items)); 6737 } 6738 } 6739 6740 private Equality asBoolFromInt(String s) { 6741 try { 6742 int i = Integer.parseInt(s); 6743 switch (i) { 6744 case 0: return Equality.False; 6745 case 1: return Equality.True; 6746 default: return Equality.Null; 6747 } 6748 } catch (Exception e) { 6749 return Equality.Null; 6750 } 6751 } 6752 6753 private Equality asBoolFromDec(String s) { 6754 try { 6755 BigDecimal d = new BigDecimal(s); 6756 if (d.compareTo(BigDecimal.ZERO) == 0) { 6757 return Equality.False; 6758 } else if (d.compareTo(BigDecimal.ONE) == 0) { 6759 return Equality.True; 6760 } else { 6761 return Equality.Null; 6762 } 6763 } catch (Exception e) { 6764 return Equality.Null; 6765 } 6766 } 6767 6768 private Equality asBool(Base item, boolean narrow) { 6769 if (item instanceof BooleanType) { 6770 return boolToTriState(((BooleanType) item).booleanValue()); 6771 } else if (item.isBooleanPrimitive()) { 6772 if (Utilities.existsInList(item.primitiveValue(), "true")) { 6773 return Equality.True; 6774 } else if (Utilities.existsInList(item.primitiveValue(), "false")) { 6775 return Equality.False; 6776 } else { 6777 return Equality.Null; 6778 } 6779 } else if (narrow) { 6780 return Equality.False; 6781 } else if (item instanceof IntegerType || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) { 6782 return asBoolFromInt(item.primitiveValue()); 6783 } else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal")) { 6784 return asBoolFromDec(item.primitiveValue()); 6785 } else if (Utilities.existsInList(item.fhirType(), FHIR_TYPES_STRING)) { 6786 if (Utilities.existsInList(item.primitiveValue(), "true", "t", "yes", "y")) { 6787 return Equality.True; 6788 } else if (Utilities.existsInList(item.primitiveValue(), "false", "f", "no", "n")) { 6789 return Equality.False; 6790 } else if (Utilities.isInteger(item.primitiveValue())) { 6791 return asBoolFromInt(item.primitiveValue()); 6792 } else if (Utilities.isDecimal(item.primitiveValue(), true)) { 6793 return asBoolFromDec(item.primitiveValue()); 6794 } else { 6795 return Equality.Null; 6796 } 6797 } 6798 return Equality.Null; 6799 } 6800 6801 private Equality boolToTriState(boolean b) { 6802 return b ? Equality.True : Equality.False; 6803 } 6804 6805 6806 public ValidationOptions getTerminologyServiceOptions() { 6807 return terminologyServiceOptions; 6808 } 6809 6810 6811 public IWorkerContext getWorker() { 6812 return worker; 6813 } 6814 6815 public boolean isAllowPolymorphicNames() { 6816 return allowPolymorphicNames; 6817 } 6818 6819 public void setAllowPolymorphicNames(boolean allowPolymorphicNames) { 6820 this.allowPolymorphicNames = allowPolymorphicNames; 6821 } 6822 6823 public boolean isLiquidMode() { 6824 return liquidMode; 6825 } 6826 6827 public void setLiquidMode(boolean liquidMode) { 6828 this.liquidMode = liquidMode; 6829 } 6830 6831 public ProfileUtilities getProfileUtilities() { 6832 return profileUtilities; 6833 } 6834 6835 public boolean isAllowDoubleQuotes() { 6836 return allowDoubleQuotes; 6837 } 6838 public void setAllowDoubleQuotes(boolean allowDoubleQuotes) { 6839 this.allowDoubleQuotes = allowDoubleQuotes; 6840 } 6841 6842 public boolean isEmitSQLonFHIRWarning() { 6843 return emitSQLonFHIRWarning; 6844 } 6845 6846 public void setEmitSQLonFHIRWarning(boolean emitSQLonFHIRWarning) { 6847 this.emitSQLonFHIRWarning = emitSQLonFHIRWarning; 6848 } 6849 6850}