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