001package org.hl7.fhir.validation.instance.utils;
002
003public class FHIRPathExpressionFixer {
004
005
006  public static String fixExpr(String expr, String key) {
007    // this is a hack work around for past publication of wrong FHIRPath expressions
008    // R4
009    // waiting for 4.0.2
010    //TODO is this expression below correct? @grahamegrieve
011    if ("probability is decimal implies (probability as decimal) <= 100".equals(expr)) {
012      return "probability.empty() or ((probability is decimal) implies ((probability as decimal) <= 100))";
013    }
014    if ("enableWhen.count() > 2 implies enableBehavior.exists()".equals(expr)) {
015      return "enableWhen.count() >= 2 implies enableBehavior.exists()";
016    }
017    if ("txt-2".equals(key)) {
018      return "htmlChecks2()";
019    }
020    if ("generated='generated' implies source.empty()".equals(expr)) {
021      return "generation='generated' implies source.empty()";
022    }
023    // fixes to string functions in FHIRPath
024    // ref-1
025    if (expr.equals("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource)")) { // R5
026      return "reference.exists() implies ("+expr+")";
027    }
028    if (expr.equals("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))")) { // R4/R4B
029      return "reference.exists() implies (reference = '#' or ("+expr+"))";
030    }
031    if (expr.equals("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))")) { // STU3
032      return "reference.exists() implies (reference = '#' or (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))))";
033    }
034    // bld-8
035    if (expr.equals("fullUrl.contains('/_history/').not()")) { // R4
036      return "fullUrl.exists() implies fullUrl.contains('/_history/').not()";      
037    }
038    if (expr.equals("name.matches('[A-Z]([A-Za-z0-9_]){0,254}')")) {
039      return "name.exists() implies name.matches('^[A-Z]([A-Za-z0-9_]){0,254}$')";
040    }
041    // canonical
042    if (expr.equals("name.matches('[A-Z]([A-Za-z0-9_]){0,254}')")) {
043      return ("name.exists() implies name.matches('[A-Z]([A-Za-z0-9_]){0,254}')");
044    }
045    
046    // R5 ballot
047    if (expr.equals("url.matches('([^|#])*')")) {
048      return ("$this.matches('([^|#])*')");
049    }
050    if (expr.equals("((kind in 'resource' | 'complex-type') and (specialization = 'derivation')) implies differential.element.where((min != 0 and min != 1) or (max != '1' and max != '*')).empty()")) {
051      return "((kind in 'resource' | 'complex-type') and (derivation = 'specialization')) implies differential.element.where((min.exists() and min != 0 and min != 1) or (max.exists() and max != '1' and max != '*')).empty()";
052    }  
053    
054    // clarification in FHIRPath spec
055    if ("eld-19".equals(key)) {
056      return "path.matches('^[^\\\\s\\\\.,:;\\\\\\'\"\\\\/|?!@#$%&*()\\\\[\\\\]{}]{1,64}(\\\\.[^\\\\s\\\\.,:;\\\\\\'\"\\\\/|?!@#$%&*()\\\\[\\\\]{}]{1,64}(\\\\[x\\\\])?(\\\\:[^\\\\s\\\\.]+)?)*$')";
057    }
058    if ("eld-20".equals(key)) {
059      return "path.matches('^[A-Za-z][A-Za-z0-9]*(\\\\.[a-z][A-Za-z0-9]*(\\\\[x])?)*$')";
060    }
061  
062    // handled in 4.0.1
063    if ("(component.empty() and hasMember.empty()) implies (dataAbsentReason or value)".equals(expr)) {
064      return "(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())";
065    }
066    if ("isModifier implies isModifierReason.exists()".equals(expr)) {
067      return "(isModifier.exists() and isModifier) implies isModifierReason.exists()";
068    }
069    if ("(%resource.kind = 'logical' or element.first().path.startsWith(%resource.type)) and (element.tail().not() or  element.tail().all(path.startsWith(%resource.differential.element.first().path.replaceMatches('\\\\..*','')&'.')))".equals(expr)) {
070      return "(%resource.kind = 'logical' or element.first().path.startsWith(%resource.type)) and (element.tail().empty() or  element.tail().all(path.startsWith(%resource.differential.element.first().path.replaceMatches('\\\\..*','')&'.')))";
071    }
072    if ("differential.element.all(id) and differential.element.id.trace('ids').isDistinct()".equals(expr)) {
073      return "differential.element.all(id.exists()) and differential.element.id.trace('ids').isDistinct()";
074    }
075    if ("snapshot.element.all(id) and snapshot.element.id.trace('ids').isDistinct()".equals(expr)) {
076      return "snapshot.element.all(id.exists()) and snapshot.element.id.trace('ids').isDistinct()";
077    }
078
079    // R3
080    if ("(code or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')".equals(expr)) {
081      return "(code.exists() or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')";
082    }
083    if ("value.empty() or code!=component.code".equals(expr)) {
084      return "value.empty() or (code in component.code).not()";
085    }
086    if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) {
087      return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)";
088    }
089    if ("element.all(definition and min and max)".equals(expr)) {
090      return "element.all(definition.exists() and min.exists() and max.exists())";
091    }
092    if ("telecom or endpoint".equals(expr)) {
093      return "telecom.exists() or endpoint.exists()";
094    }
095    if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) {
096      return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)";
097    }
098    if ("searchType implies type = 'string'".equals(expr)) {
099      return "searchType.exists() implies type = 'string'";
100    }
101    if ("abatement.empty() or (abatement as boolean).not()  or clinicalStatus='resolved' or clinicalStatus='remission' or clinicalStatus='inactive'".equals(expr)) {
102      return "abatement.empty() or (abatement is boolean).not() or (abatement as boolean).not() or (clinicalStatus = 'resolved') or (clinicalStatus = 'remission') or (clinicalStatus = 'inactive')";
103    }
104    if ("(component.empty() and related.empty()) implies (dataAbsentReason or value)".equals(expr)) {
105      return "(component.empty() and related.empty()) implies (dataAbsentReason.exists() or value.exists())";
106    }
107    if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))".equals(expr)) {
108      return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))";
109    }
110    if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))".equals(expr)) {
111      return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))";
112    }
113    if ("probability is decimal implies probability.as(decimal) <= 100".equals(expr)) {
114      if (key.equals("ras-2")) {
115        return "probability.empty() or (probability is decimal implies probability.as(decimal) <= 100)";
116      }
117    }
118    if ("".equals(expr)) {
119      return "";
120    }
121    return expr;
122  }
123  
124}