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