diff --git a/UWP/FhirPathTesterUWP.csproj b/UWP/FhirPathTesterUWP.csproj index d3f79bf..0e66d2e 100644 --- a/UWP/FhirPathTesterUWP.csproj +++ b/UWP/FhirPathTesterUWP.csproj @@ -12,7 +12,7 @@ en-US UAP 10.0.16299.0 - 10.0.15063.0 + 10.0.16299.0 14 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -182,6 +182,9 @@ + + Designer + @@ -203,33 +206,37 @@ - 0.96.0 + 1.3.0 - 0.96.1 + 1.3.0 + + + 1.3.0 - 0.96.0 + 1.3.0 contentFiles + runtime; compile; build; native; analyzers - 0.96.0 + 1.3.0 - 0.96.0 + 1.3.0 - 0.96.1 + 1.3.0 - 6.1.7 + 6.2.8 - 4.0.0 + 5.1.1 - 4.0.0 + 5.1.1 diff --git a/UWP/MainPage.xaml.cs b/UWP/MainPage.xaml.cs index 28e8562..c4db56f 100644 --- a/UWP/MainPage.xaml.cs +++ b/UWP/MainPage.xaml.cs @@ -655,8 +655,8 @@ public ExpressionElementContext Child(string propertyName) } else { - if (item.ElementType != typeof(string)) // (only occurs for extension.url and elementdefinition.id) - newContext._cm3.Add(stu3::Hl7.Fhir.Introspection.ClassMapping.Create(item.ElementType)); + if (item.ImplementingType != typeof(string)) // (only occurs for extension.url and elementdefinition.id) + newContext._cm3.Add(stu3::Hl7.Fhir.Introspection.ClassMapping.Create(item.ImplementingType)); } } catch @@ -704,8 +704,8 @@ public ExpressionElementContext Child(string propertyName) } else { - if (item.ElementType != typeof(string)) // (only occurs for extension.url and elementdefinition.id) - newContext._cm2.Add(dstu2::Hl7.Fhir.Introspection.ClassMapping.Create(item.ElementType)); + if (item.ImplementingType != typeof(string)) // (only occurs for extension.url and elementdefinition.id) + newContext._cm2.Add(dstu2::Hl7.Fhir.Introspection.ClassMapping.Create(item.ImplementingType)); } } catch diff --git a/UWP/Properties/AssemblyInfo.cs b/UWP/Properties/AssemblyInfo.cs index 450db1c..23f72f3 100644 --- a/UWP/Properties/AssemblyInfo.cs +++ b/UWP/Properties/AssemblyInfo.cs @@ -24,6 +24,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.3.0")] -[assembly: AssemblyFileVersion("1.0.3.0")] +[assembly: AssemblyVersion("1.0.4.0")] +[assembly: AssemblyFileVersion("1.0.4.0")] [assembly: ComVisible(false)] \ No newline at end of file diff --git a/WPF/App.config b/WPF/App.config index 1d48207..9a1b0c9 100644 --- a/WPF/App.config +++ b/WPF/App.config @@ -9,18 +9,10 @@ - - - - - - - - \ No newline at end of file diff --git a/WPF/CustomFluentPathFunctions.cs b/WPF/CustomFluentPathFunctions.cs index 57a3e0a..bd01ba7 100644 --- a/WPF/CustomFluentPathFunctions.cs +++ b/WPF/CustomFluentPathFunctions.cs @@ -29,84 +29,64 @@ static public SymbolTable Scope // Custom function that returns the name of the property, rather than its value _st.Add("propname", (object f) => { - if (f is IEnumerable) + if (f is IEnumerable) { - object[] bits = (f as IEnumerable).Select(i => + object[] bits = (f as IEnumerable).Select(i => { - if (i is stu3.Hl7.Fhir.ElementModel.PocoNavigator) - { - return (i as stu3.Hl7.Fhir.ElementModel.PocoNavigator).Name; - } - if (i is dstu2.Hl7.Fhir.ElementModel.PocoNavigator) - { - return (i as dstu2.Hl7.Fhir.ElementModel.PocoNavigator).Name; - } - return "?"; + return i.Name; }).ToArray(); - return FhirValueList.Create(bits); + return FhirValueListCreate(bits); } - return FhirValueList.Create(new object[] { "?" } ); + return FhirValueListCreate(new object[] { "?" } ); }); _st.Add("pathname", (object f) => { - if (f is IEnumerable) + if (f is IEnumerable) { - object[] bits = (f as IEnumerable).Select(i => + object[] bits = (f as IEnumerable).Select(i => { - if (i is stu3.Hl7.Fhir.ElementModel.PocoNavigator) - { - return (i as stu3.Hl7.Fhir.ElementModel.PocoNavigator).Location; - } - if (i is dstu2.Hl7.Fhir.ElementModel.PocoNavigator) - { - return (i as dstu2.Hl7.Fhir.ElementModel.PocoNavigator).Location; - } - return "?"; + return i.Location; }).ToArray(); - return FhirValueList.Create(bits); + return FhirValueListCreate(bits); } - return FhirValueList.Create(new object[] { "?" }); + return FhirValueListCreate(new object[] { "?" }); }); _st.Add("shortpathname", (object f) => { - if (f is IEnumerable) + if (f is IEnumerable) { - object[] bits = (f as IEnumerable).Select(i => + object[] bits = (f as IEnumerable).Select(i => { - if (i is stu3.Hl7.Fhir.ElementModel.PocoNavigator) + if (i is IShortPathGenerator spg) { - return (i as stu3.Hl7.Fhir.ElementModel.PocoNavigator).ShortPath; - } - if (i is dstu2.Hl7.Fhir.ElementModel.PocoNavigator) - { - return (i as dstu2.Hl7.Fhir.ElementModel.PocoNavigator).ShortPath; + return spg.ShortPath; } return "?"; }).ToArray(); - return FhirValueList.Create(bits); + return FhirValueListCreate(bits); } - return FhirValueList.Create(new object[] { "?" }); - }); - _st.Add("commonpathname", (object f) => - { - if (f is IEnumerable) - { - object[] bits = (f as IEnumerable).Select(i => - { - if (i is stu3.Hl7.Fhir.ElementModel.PocoNavigator) - { - return (i as stu3.Hl7.Fhir.ElementModel.PocoNavigator).CommonPath; - } - if (i is dstu2.Hl7.Fhir.ElementModel.PocoNavigator) - { - return (i as dstu2.Hl7.Fhir.ElementModel.PocoNavigator).CommonPath; - } - return "?"; - }).ToArray(); - return FhirValueList.Create(bits); - } - return FhirValueList.Create(new object[] { "?" }); + return FhirValueListCreate(new object[] { "?" }); }); + //_st.Add("commonpathname", (object f) => + //{ + // if (f is IEnumerable) + // { + // object[] bits = (f as IEnumerable).Select(i => + // { + // if (i is stu3.Hl7.Fhir.ElementModel.PocoElementNode) + // { + // return (i as stu3.Hl7.Fhir.ElementModel.PocoElementNode).CommonPath; + // } + // if (i is dstu2.Hl7.Fhir.ElementModel.PocoElementNode) + // { + // return (i as dstu2.Hl7.Fhir.ElementModel.PocoElementNode).CommonPath; + // } + // return "?"; + // }).ToArray(); + // return FhirValueListCreate(bits); + // } + // return FhirValueListCreate(new object[] { "?" }); + //}); // Custom function for evaluating the date operation (custom Healthconnex) _st.Add("dateadd", (PartialDateTime f, string field, long amount) => @@ -141,6 +121,10 @@ static public SymbolTable Scope } } + private static object FhirValueListCreate(object[] values) + { + return ElementNode.CreateList(values); + } } public static class Luhn diff --git a/WPF/FhirPathExpressionProcessing.cs b/WPF/FhirPathExpressionProcessing.cs new file mode 100644 index 0000000..f2d0add --- /dev/null +++ b/WPF/FhirPathExpressionProcessing.cs @@ -0,0 +1,127 @@ +extern alias stu3; +// using Hl7.FhirPath.Expressions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Linq.Expressions; + +namespace FhirPathTester +{ + public class QuestionnaireExpressionProcessing + { + private Expression CheckExpression(Hl7.FhirPath.Expressions.Expression expr, Type T, List namedProps) + { + if (expr is Hl7.FhirPath.Expressions.ChildExpression ce) + { + var focusContext = CheckExpression(ce.Focus, T, namedProps); + var cm = stu3.Hl7.Fhir.Introspection.ClassMapping.Create(T); + var childContext = Expression.Property(focusContext, cm.FindMappedElementByName(ce.ChildName).Name); + if (childContext != null) + return childContext; + throw new ArgumentException("Failed to find parameter", ce.ChildName); + } + if (expr is Hl7.FhirPath.Expressions.FunctionCallExpression func) + { + var funcs = _compiler.Symbols.Filter(func.FunctionName, func.Arguments.Count() + 1); + if (funcs.Count() == 0 && !(expr is Hl7.FhirPath.Expressions.BinaryExpression)) + { + // AppendResults($"{prefix}{func.FunctionName} *invalid function name*", true); + } + else + { + AppendResults($"{prefix}{func.FunctionName}"); + } + var focusContext = CheckExpression(func.Focus, context); + + if (func.FunctionName == "binary.as") + { + if (func.Arguments.Count() != 2) + { + AppendResults($"{prefix}{func.FunctionName} INVALID AS Operation", true); + return focusContext; + } + var argContextResult = CheckExpression(func.Arguments.First(), focusContext); + var typeArg = func.Arguments.Skip(1).FirstOrDefault() as Hl7.FhirPath.Expressions.ConstantExpression; + string typeCast = typeArg?.Value as string; + argContextResult.RestrictToType(typeCast); + return argContextResult; + } + else if (func.FunctionName == "resolve") + { + // need to check what the available outcomes of resolving this are, and switch types to this + } + else + { + // if this is a where operation and the context inside is a linkId = , then check that the linkId is in context + if (func.FunctionName == "where" && func.Arguments.Count() == 1 && (func.Arguments.First() as Hl7.FhirPath.Expressions.BinaryExpression)?.Op == "=") + { + var op = func.Arguments.First() as Hl7.FhirPath.Expressions.BinaryExpression; + var argContextResult = CheckExpression(op, focusContext); + + // Filter the values that are not in this set + focusContext._2gs = argContextResult._2gs; + focusContext._2qs = argContextResult._2qs; + } + else + { + foreach (var item in func.Arguments) + { + var argContextResult = CheckExpression(item, focusContext); + } + } + if (func.FunctionName == "binary.=") + { + Hl7.FhirPath.Expressions.ChildExpression prop = (Hl7.FhirPath.Expressions.ChildExpression)func.Arguments.Where(a => a is Hl7.FhirPath.Expressions.ChildExpression).FirstOrDefault(); + Hl7.FhirPath.Expressions.ConstantExpression value = (Hl7.FhirPath.Expressions.ConstantExpression)func.Arguments.Where(a => a is Hl7.FhirPath.Expressions.ConstantExpression).FirstOrDefault(); + if (prop?.ChildName == "linkId" && value != null) + { + var groupLinkIds = focusContext._2gs?.Select(i => i.LinkId).ToArray(); + var questionLinkIds = focusContext._2qs?.Select(i => i.LinkId).ToArray(); + + // filter out all of the other linkIds from the list + focusContext._2gs?.RemoveAll(g => g.LinkId != value.Value as string); + focusContext._2qs?.RemoveAll(q => q.LinkId != value.Value as string); + + // Validate that there is an item with this value that is reachable + if (focusContext._2gs?.Count() == 0 || focusContext._2qs?.Count() == 0) + { + // this linkId didn't exist in this context! + string toolTip = "Available LinkIds:"; + if (groupLinkIds != null) + toolTip += $"\r\nGroup: {String.Join(", ", groupLinkIds)}"; + if (questionLinkIds != null) + toolTip += $"\r\nQuestion: {String.Join(", ", questionLinkIds)}"; + AppendResults($"{prefix}{func.FunctionName} LinkId is not valid in this context", true, toolTip); + } + } + } + } + + return focusContext; + } + //else if (expr is BinaryExpression) + //{ + // var func = expr as BinaryExpression; + // sb.AppendLine(func.FunctionName); + // CheckExpression(func.Left, sb); + // sb.AppendLine(func.Op); + // CheckExpression(func.Right, sb); + // return; + //} + else if (expr is Hl7.FhirPath.Expressions.ConstantExpression constExpr) + { + return Expression.Constant(constExpr.Value); // context doesn't propagate from this + } + else if (expr is Hl7.FhirPath.Expressions.VariableRefExpression vref) + { + // sb.AppendFormat("{0}{1} (variable ref)\r\n", prefix, func.Name); + return context; + } + // AppendResults(expr.GetType().ToString()); + return context; + } + + } +} diff --git a/WPF/FhirPathTester.csproj b/WPF/FhirPathTester.csproj index de232c9..d23f924 100644 --- a/WPF/FhirPathTester.csproj +++ b/WPF/FhirPathTester.csproj @@ -41,31 +41,35 @@ fhir-lab-ico-300x300.ico - - ..\packages\Hl7.Fhir.DSTU2.0.96.0\lib\net45\Hl7.Fhir.DSTU2.Core.dll + + ..\packages\Hl7.Fhir.DSTU2.1.3.0\lib\net45\Hl7.Fhir.DSTU2.Core.dll dstu2 - - ..\packages\Hl7.Fhir.Specification.DSTU2.0.96.0\lib\net45\Hl7.Fhir.DSTU2.Specification.dll - dstu2 + + ..\packages\Hl7.Fhir.Specification.DSTU2.1.3.0\lib\net45\Hl7.Fhir.DSTU2.Specification.dll + + + ..\packages\Hl7.Fhir.ElementModel.1.3.0\lib\net45\Hl7.Fhir.ElementModel.dll - - ..\packages\Hl7.Fhir.R4.0.96.1\lib\net45\Hl7.Fhir.R4.Core.dll + + ..\packages\Hl7.Fhir.R4.1.3.0\lib\net45\Hl7.Fhir.R4.Core.dll r4 - - ..\packages\Hl7.Fhir.STU3.0.96.0\lib\net45\Hl7.Fhir.STU3.Core.dll - stu3 + + ..\packages\Hl7.Fhir.Serialization.1.3.0\lib\net45\Hl7.Fhir.Serialization.dll - - ..\packages\Hl7.Fhir.Specification.STU3.0.96.0\lib\net45\Hl7.Fhir.STU3.Specification.dll + + ..\packages\Hl7.Fhir.STU3.1.3.0\lib\net45\Hl7.Fhir.STU3.Core.dll stu3 - - ..\packages\Hl7.Fhir.Support.0.96.1\lib\net45\Hl7.Fhir.Support.dll + + ..\packages\Hl7.Fhir.Specification.STU3.1.3.0\lib\net45\Hl7.Fhir.STU3.Specification.dll + + + ..\packages\Hl7.Fhir.Support.1.3.0\lib\net45\Hl7.Fhir.Support.dll - - ..\packages\Hl7.FhirPath.0.96.1\lib\net45\Hl7.FhirPath.dll + + ..\packages\Hl7.FhirPath.1.3.0\lib\net45\Hl7.FhirPath.dll ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll diff --git a/WPF/MainWindow.xaml.cs b/WPF/MainWindow.xaml.cs index 0e01c59..c3f2e4c 100644 --- a/WPF/MainWindow.xaml.cs +++ b/WPF/MainWindow.xaml.cs @@ -1,1158 +1,1142 @@ -extern alias dstu2; -extern alias stu3; -extern alias r4; - -using Hl7.Fhir.ElementModel; - -using fp2 = dstu2.Hl7.Fhir.FhirPath; -using f2 = dstu2.Hl7.Fhir.Model; - -using fp3 = stu3.Hl7.Fhir.FhirPath; -using f3 = stu3.Hl7.Fhir.Model; - -using fp4 = r4.Hl7.Fhir.FhirPath; -using f4 = r4.Hl7.Fhir.Model; - -using Hl7.FhirPath; -using Hl7.FhirPath.Expressions; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Windows; -using System.Windows.Input; -using System.Windows.Documents; -using System.Windows.Media; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json; - -namespace FhirPathTester -{ - /// - /// Interaction logic for MainWindow.xaml - /// - public partial class MainWindow : Window, INotifyPropertyChanged - { - public int TextControlFontSize { get; set; } - - public MainWindow() - { - TextControlFontSize = 22; - InitializeComponent(); - textboxInputXML.Text = "\r\n\r\n\r\n\r\n"; - textboxExpression.Text = "birthDate < today() | identifier.assigner.display.extension.where(url='3').value.code"; - } - - public event PropertyChangedEventHandler PropertyChanged; - - private IElementNavigator GetResourceNavigatorDSTU2(out string parseErrors) - { - f2.Resource resource = null; - try - { - if (textboxInputXML.Text.StartsWith("{")) - resource = new dstu2.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); - else - resource = new dstu2.Hl7.Fhir.Serialization.FhirXmlParser().Parse(textboxInputXML.Text); - } - catch (Exception ex) - { - parseErrors = "DSTU2 Resource read error:\r\n" + ex.Message; - return null; - } - parseErrors = null; - var inputNav = new dstu2.Hl7.Fhir.ElementModel.PocoNavigator(resource); - return inputNav; - } - private IElementNavigator GetResourceNavigatorSTU3(out string parseErrors) - { - f3.Resource resource = null; - try - { - if (textboxInputXML.Text.StartsWith("{")) - resource = new stu3.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); - else - resource = new stu3.Hl7.Fhir.Serialization.FhirXmlParser().Parse(textboxInputXML.Text); - } - catch (Exception ex) - { - parseErrors = "STU3 Resource read error:\r\n" + ex.Message; - return null; - } - parseErrors = null; - var inputNav = new stu3.Hl7.Fhir.ElementModel.PocoNavigator(resource); - return inputNav; - } - private IElementNavigator GetResourceNavigatorR4(out string parseErrors) - { - f4.Resource resource = null; - try - { - if (textboxInputXML.Text.StartsWith("{")) - resource = new r4.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); - else - resource = new r4.Hl7.Fhir.Serialization.FhirXmlParser().Parse(textboxInputXML.Text); - } - catch (Exception ex) - { - parseErrors = "R4 Resource read error:\r\n" + ex.Message; - return null; - } - parseErrors = null; - var inputNav = new r4.Hl7.Fhir.ElementModel.PocoNavigator(resource); - return inputNav; - } - - private IElementNavigator GetResourceNavigator() - { - string parseErrors2; - var inputNavDSTU2 = GetResourceNavigatorDSTU2(out parseErrors2); - string parseErrors3; - var inputNavSTU3 = GetResourceNavigatorSTU3(out parseErrors3); - string parseErrors4; - var inputNavR4 = GetResourceNavigatorR4(out parseErrors4); - - if (!string.IsNullOrEmpty(parseErrors2) || !string.IsNullOrEmpty(parseErrors3) || !string.IsNullOrEmpty(parseErrors4)) - { - ResetResults(); - textboxResult.AppendText(String.Join("\r\n--------------------\r\n", parseErrors2, parseErrors3, parseErrors4)); - } - - if (inputNavR4 != null) - labelR4.Visibility = Visibility.Visible; - else - labelR4.Visibility = Visibility.Collapsed; - if (inputNavSTU3 != null) - labelSTU3.Visibility = Visibility.Visible; - else - labelSTU3.Visibility = Visibility.Collapsed; - - if (inputNavDSTU2 != null) - labelDSTU2.Visibility = Visibility.Visible; - else - labelDSTU2.Visibility = Visibility.Collapsed; - - if (inputNavSTU3 != null) - return inputNavSTU3; - if (inputNavDSTU2 != null) - return inputNavDSTU2; - return inputNavR4; - } - - private void ButtonGo_Click(object sender, RoutedEventArgs e) - { - var inputNav = GetResourceNavigator(); - if (inputNav == null) - return; - - // Don't need to cache this, it is cached in the fhir-client - CompiledExpression xps = null; - try - { - xps = _compiler.Compile(textboxExpression.Text); - } - catch (Exception ex) - { - SetResults("Expression compilation error:\r\n" + ex.Message); - return; - } - - IEnumerable prepopulatedValues = null; - if (xps != null) - { - try - { - if (inputNav is stu3.Hl7.Fhir.ElementModel.PocoNavigator) - prepopulatedValues = xps(inputNav, new fp3.FhirEvaluationContext(inputNav)); - else if (inputNav is dstu2.Hl7.Fhir.ElementModel.PocoNavigator) - prepopulatedValues = xps(inputNav, new fp2.FhirEvaluationContext(inputNav)); - else - prepopulatedValues = xps(inputNav, new fp4.FhirEvaluationContext(inputNav)); - } - catch (Exception ex) - { - SetResults("Expression evaluation error:\r\n" + ex.Message); - AppendParseTree(); - return; - } - - ResetResults(); - try - { - if (prepopulatedValues.Count() > 0) - { - if (inputNav is stu3.Hl7.Fhir.ElementModel.PocoNavigator) - { - foreach (var item in prepopulatedValues) - { - string tooltip = null; - if (item is stu3.Hl7.Fhir.ElementModel.PocoNavigator pnav) - { - tooltip = pnav.ShortPath; - } - foreach (var t2 in fp3.ElementNavFhirExtensions.ToFhirValues(new IElementNavigator[] { item })) - { - if (t2 != null) - { - // output the content as XML fragments - var fragment = stu3.Hl7.Fhir.Serialization.FhirSerializer.SerializeToXml(t2, root: t2.TypeName); - AppendResults(fragment.Replace(" xmlns=\"http://hl7.org/fhir\"", "")); - } - // System.Diagnostics.Trace.WriteLine(string.Format("{0}: {1}", xpath.Value, t2.AsStringRepresentation())); - } - } - } - else if (inputNav is r4.Hl7.Fhir.ElementModel.PocoNavigator) - { - foreach (var item in prepopulatedValues) - { - string tooltip = null; - if (item is r4.Hl7.Fhir.ElementModel.PocoNavigator pnav) - { - tooltip = pnav.ShortPath; - } - foreach (var t2 in fp4.ElementNavFhirExtensions.ToFhirValues(new IElementNavigator[] { item })) - { - if (t2 != null) - { - // output the content as XML fragments - var fragment = r4.Hl7.Fhir.Serialization.FhirSerializer.SerializeToXml(t2, root: t2.TypeName); - AppendResults(fragment.Replace(" xmlns=\"http://hl7.org/fhir\"", "")); - } - // System.Diagnostics.Trace.WriteLine(string.Format("{0}: {1}", xpath.Value, t2.AsStringRepresentation())); - } - } - } - else - { - foreach (var item in prepopulatedValues) - { - string tooltip = null; - if (item is dstu2.Hl7.Fhir.ElementModel.PocoNavigator pnav) - { - tooltip = pnav.ShortPath; - } - foreach (var t2 in fp2.ElementNavFhirExtensions.ToFhirValues(new IElementNavigator[] { item })) - { - if (t2 != null) - { - // output the content as XML fragments - var fragment = dstu2.Hl7.Fhir.Serialization.FhirSerializer.SerializeToXml(t2, root: t2.TypeName); - AppendResults(fragment.Replace(" xmlns=\"http://hl7.org/fhir\"", ""), false, tooltip); - } - } - } - } - } - } - catch (Exception ex) - { - SetResults("Processing results error:\r\n" + ex.Message); - return; - } - } - - AppendParseTree(); - } - - private void AppendParseTree() - { - // Grab the parse expression - StringBuilder sb = new StringBuilder(); - var expr = _compiler.Parse(textboxExpression.Text); - OutputExpression(expr, sb, ""); - // textboxResult.Text += expr.Dump(); - AppendResults("\r\n\r\n----------------\r\n" + sb.ToString()); - } - - private void OutputExpression(Hl7.FhirPath.Expressions.Expression expr, StringBuilder sb, string prefix) - { - if (expr is ChildExpression) - { - var func = expr as ChildExpression; - OutputExpression(func.Focus, sb, prefix + "-- "); - sb.AppendFormat("{0}{1}\r\n", prefix, func.ChildName); - return; - } - if (expr is FunctionCallExpression) - { - var func = expr as FunctionCallExpression; - sb.AppendFormat("{0}{1}\r\n", prefix, func.FunctionName); - OutputExpression(func.Focus, sb, prefix + "-- "); - foreach (var item in func.Arguments) - { - OutputExpression(item, sb, prefix + " "); - } - return; - } - //else if (expr is BinaryExpression) - //{ - // var func = expr as BinaryExpression; - // sb.AppendLine(func.FunctionName); - // OutputExpression(func.Left, sb); - // sb.AppendLine(func.Op); - // OutputExpression(func.Right, sb); - // return; - //} - else if (expr is ConstantExpression) - { - var func = expr as ConstantExpression; - sb.AppendFormat("{0}{1} (constant)\r\n", prefix, func.Value.ToString()); - return; - } - else if (expr is VariableRefExpression) - { - var func = expr as VariableRefExpression; - // sb.AppendFormat("{0}{1} (variable ref)\r\n", prefix, func.Name); - return; - } - sb.Append(expr.GetType().ToString()); - } - - Hl7.FhirPath.FhirPathCompiler _compiler = new FhirPathCompiler(CustomFluentPathFunctions.Scope); - - private void ButtonPredicate_Click(object sender, RoutedEventArgs e) - { - var inputNav = GetResourceNavigator(); - if (inputNav == null) - return; - - // Don't need to cache this, it is cached in the fhir-client - Hl7.FhirPath.CompiledExpression xps = null; - try - { - xps = _compiler.Compile(textboxExpression.Text); - } - catch (Exception ex) - { - SetResults("Expression compilation error:\r\n" + ex.Message); - return; - } - - if (xps != null) - { - try - { - bool result; - if (inputNav is stu3.Hl7.Fhir.ElementModel.PocoNavigator) - result = xps.Predicate(inputNav, new fp3.FhirEvaluationContext(inputNav)); - else if (inputNav is dstu2.Hl7.Fhir.ElementModel.PocoNavigator) - result = xps.Predicate(inputNav, new fp2.FhirEvaluationContext(inputNav)); - else - result = xps.Predicate(inputNav, new fp4.FhirEvaluationContext(inputNav)); - SetResults(result.ToString()); - } - catch (Exception ex) - { - SetResults("Expression evaluation error:\r\n" + ex.Message); - return; - } - } - - AppendParseTree(); - } - - private void textboxExpression_MouseWheel(object sender, MouseWheelEventArgs e) - { - if (e.Delta > 100 || e.Delta < -100) - { - TextControlFontSize += e.Delta / 100; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TextControlFontSize))); - } - } - - private void textboxInputXML_Drop(object sender, DragEventArgs e) - { - // This is the place where we want to support the reading of the file from the file system - // to make the testing of other instances really easy - var formats = e.Data.GetFormats(); - if (e.Data.GetDataPresent("FileName")) - { - string[] contents = e.Data.GetData("FileName") as string[]; - if (contents.Length > 0) - textboxInputXML.Text = System.IO.File.ReadAllText(contents[0]); - e.Handled = true; - } - } - - private void textboxInputXML_DragOver(object sender, DragEventArgs e) - { - e.Effects = DragDropEffects.Copy | DragDropEffects.Link; - } - - private void textboxInputXML_PreviewDragOver(object sender, DragEventArgs e) - { - e.Handled = true; - } - - private void textboxInputXML_SelectionChanged(object sender, RoutedEventArgs e) - { - int lineIndex = 0; - int colIndex = 0; - try - { - int charIndex = textboxInputXML.CaretIndex; - lineIndex = textboxInputXML.GetLineIndexFromCharacterIndex(charIndex); - colIndex = charIndex - textboxInputXML.GetCharacterIndexFromLineIndex(lineIndex); - } - catch (Exception) - { - } - textboxRow.Content = $"Ln {lineIndex + 1}"; - textboxCol.Content = $"Col {colIndex}"; - } - - private void ButtonCheckExpression_Click(object sender, RoutedEventArgs e) - { - var inputNav = GetResourceNavigator(); - if (inputNav == null) - return; - - // Don't need to cache this, it is cached in the fhir-client - Hl7.FhirPath.Expressions.Expression expr = null; - try - { - expr = _compiler.Parse(textboxExpression.Text); - } - catch (Exception ex) - { - SetResults("Expression compilation error:\r\n" + ex.Message, true); - return; - } - - if (expr != null) - { - try - { - ExpressionElementContext context = new ExpressionElementContext(inputNav.Name); - if (inputNav is dstu2::Hl7.Fhir.ElementModel.PocoNavigator pn2) - { - if (pn2.FhirValue is f2.Questionnaire q) - { - context._q2 = q; - } - } - else if (inputNav is stu3::Hl7.Fhir.ElementModel.PocoNavigator pn3) - { - if (pn3.FhirValue is f3.Questionnaire q) - { - context._q3 = q; - } - } - else if (inputNav is r4::Hl7.Fhir.ElementModel.PocoNavigator pn4) - { - if (pn4.FhirValue is f4.Questionnaire q) - { - context._q4 = q; - } - } - ResetResults(); - CheckExpression(expr, "", context); - } - catch (Exception ex) - { - SetResults("Expression Check error:\r\n" + ex.Message, true); - return; - } - } - } - - private void ResetResults() - { - textboxResult.Document.Blocks.Clear(); - } - - private void SetResults(string text, bool error = false) - { - textboxResult.Document.Blocks.Clear(); - var run = new Run(text); - var para = new Paragraph(run); - textboxResult.Document.Blocks.Add(para); - if (error) - { - para.Foreground = Brushes.Red; - para.FontWeight = FontWeights.Bold; - } - } - private void AppendResults(string text, bool error = false, string tooltip = null) - { - var run = new Run(text); - var para = new Paragraph(run); - if (!string.IsNullOrEmpty(tooltip)) - para.ToolTip = tooltip; - para.Margin = new Thickness(2); - textboxResult.Document.Blocks.Add(para); - if (error) - { - para.Foreground = Brushes.Red; - para.FontWeight = FontWeights.Bold; - } - } - - public class ExpressionElementContext - { - public ExpressionElementContext(string typeName) - { - _typeName = typeName; - if (!string.IsNullOrEmpty(typeName)) - { - var t4 = r4.Hl7.Fhir.Model.ModelInfo.GetTypeForFhirType(typeName); - if (t4 != null) - { - try - { - _cm4 = new List() { r4.Hl7.Fhir.Introspection.ClassMapping.Create(t4) }; - } - catch - { - } - } - var t3 = stu3.Hl7.Fhir.Model.ModelInfo.GetTypeForFhirType(typeName); - if (t3 != null) - { - try - { - _cm3 = new List() { stu3.Hl7.Fhir.Introspection.ClassMapping.Create(t3) }; - } - catch - { - } - } - var t2 = dstu2.Hl7.Fhir.Model.ModelInfo.GetTypeForFhirType(typeName); - if (t2 != null) - { - try - { - _cm2 = new List() { dstu2.Hl7.Fhir.Introspection.ClassMapping.Create(t2) }; - } - catch - { - } - } - } - } - - private ExpressionElementContext() - { - _cm4 = new List(); - _cm3 = new List(); - _cm2 = new List(); - } - - // Questionnaire Context information when processing validation against a questionnaire - internal f2.Questionnaire _q2; - internal f3.Questionnaire _q3; - internal f4.Questionnaire _q4; - internal List _2gs; - internal List _2qs; - internal List _3is; - internal List _4is; - - // Where this is the context of a class - string _typeName; - List _cm4; - List _cm3; - List _cm2; - - public string possibleTypes() - { - return string.Join(", ", _cm2.Select(t => "::" + t.Name).Union(_cm3.Select(t => "::" + t.Name)).Union(_cm4.Select(t => "::" + t.Name))); - } - - bool IsMatchingPropName(string propertyName, r4::Hl7.Fhir.Introspection.PropertyMapping pm) - { - if (propertyName == pm.Name) - return true; - if (propertyName + "[x]" == pm.Name) - return true; - if (!pm.MatchesSuffixedName(propertyName)) - return false; - string ExpectedType = propertyName.Substring(pm.Name.Length); - if (Enum.TryParse(ExpectedType, out var result)) - { - if (String.Compare(ExpectedType, Hl7.Fhir.Utility.EnumUtility.GetLiteral(result), true) != 0) - return false; - if (f4.ModelInfo.IsDataType(result) || f4.ModelInfo.IsPrimitive(result)) - return true; - } - return false; - } - - bool IsMatchingPropName(string propertyName, stu3::Hl7.Fhir.Introspection.PropertyMapping pm) - { - if (propertyName == pm.Name) - return true; - if (propertyName + "[x]" == pm.Name) - return true; - if (!pm.MatchesSuffixedName(propertyName)) - return false; - string ExpectedType = propertyName.Substring(pm.Name.Length); - if (Enum.TryParse(ExpectedType, out var result)) - { - if (String.Compare(ExpectedType, Hl7.Fhir.Utility.EnumUtility.GetLiteral(result), true) != 0) - return false; - if (f3.ModelInfo.IsDataType(result) || f3.ModelInfo.IsPrimitive(result)) - return true; - } - return false; - } - - bool IsMatchingPropName(string propertyName, dstu2::Hl7.Fhir.Introspection.PropertyMapping pm) - { - if (propertyName == pm.Name) - return true; - if (propertyName + "[x]" == pm.Name) - return true; - if (!pm.MatchesSuffixedName(propertyName)) - return false; - string ExpectedType = propertyName.Substring(pm.Name.Length); - if (Enum.TryParse(ExpectedType, out var result)) - { - if (String.Compare(ExpectedType, Hl7.Fhir.Utility.EnumUtility.GetLiteral(result), true) != 0) - return false; - if (f2.ModelInfo.IsDataType(result) || f2.ModelInfo.IsPrimitive(result)) - return true; - } - return false; - } - - bool HasProperty(string propName, - out List dstu2, - out List stu3, - out List r4) - { - if (_cm4 == null) - r4 = null; - else - r4 = _cm4.Select(t => t.FindMappedElementByName(propName)).Where(t => t != null && IsMatchingPropName(propName, t)).ToList(); - if (_cm3 == null) - stu3 = null; - else - stu3 = _cm3.Select(t => t.FindMappedElementByName(propName)).Where(t => t != null && IsMatchingPropName(propName, t)).ToList(); - if (_cm2 == null) - dstu2 = null; - else - dstu2 = _cm2.Select(t => t.FindMappedElementByName(propName)).Where(t => t != null && IsMatchingPropName(propName, t)).ToList(); - return (dstu2?.Count() > 0) || (stu3?.Count() > 0) || (r4?.Count() > 0); - } - - public ExpressionElementContext Child(string propertyName) - { - // Special case for the top level node - if (propertyName == _typeName) - return this; - - if (!HasProperty(propertyName, out var dstu2, out var stu3, out var r4)) - return null; - - var newContext = new ExpressionElementContext(); - if (r4?.Any() == true) - { - foreach (var item in r4) - { - try - { - if (item.Choice == r4::Hl7.Fhir.Introspection.ChoiceType.DatatypeChoice) - { - if (item.Name != propertyName && item.MatchesSuffixedName(propertyName)) - { - string ExpectedType = propertyName.Substring(item.Name.Length); - if (Enum.TryParse(ExpectedType, out var result)) - { - if (f4.ModelInfo.IsDataType(result) || f4.ModelInfo.IsPrimitive(result)) - { - // may need to recheck that a typename of value23 won't work here - string name = f4.ModelInfo.FhirTypeToFhirTypeName(result); - var t = f4.ModelInfo.GetTypeForFhirType(name); - newContext._cm4.Add(r4::Hl7.Fhir.Introspection.ClassMapping.Create(t)); - } - } - } - else - { - // would be great to be able to filter through only the viable types - // but we don't have that, so just enumerate the available datatypes - foreach (f4.FHIRAllTypes ev in Enum.GetValues(typeof(f4.FHIRAllTypes))) - { - if (f4.ModelInfo.IsDataType(ev) && !f4.ModelInfo.IsCoreSuperType(ev)) - { - string name = f4.ModelInfo.FhirTypeToFhirTypeName(ev); - var t = f4.ModelInfo.GetTypeForFhirType(name); - newContext._cm4.Add(r4::Hl7.Fhir.Introspection.ClassMapping.Create(t)); - } - } - } - } - else - { - if (item.ElementType != typeof(string)) // (only occurs for extension.url and elementdefinition.id) - newContext._cm4.Add(r4::Hl7.Fhir.Introspection.ClassMapping.Create(item.ElementType)); - } - } - catch - { - } - } - } - if (stu3?.Any() == true) - { - foreach (var item in stu3) - { - try - { - if (item.Choice == stu3::Hl7.Fhir.Introspection.ChoiceType.DatatypeChoice) - { - if (item.Name != propertyName && item.MatchesSuffixedName(propertyName)) - { - string ExpectedType = propertyName.Substring(item.Name.Length); - if (Enum.TryParse(ExpectedType, out var result)) - { - if (f3.ModelInfo.IsDataType(result) || f3.ModelInfo.IsPrimitive(result)) - { - // may need to recheck that a typename of value23 won't work here - string name = f3.ModelInfo.FhirTypeToFhirTypeName(result); - var t = f3.ModelInfo.GetTypeForFhirType(name); - newContext._cm3.Add(stu3::Hl7.Fhir.Introspection.ClassMapping.Create(t)); - } - } - } - else - { - // would be great to be able to filter through only the viable types - // but we don't have that, so just enumerate the available datatypes - foreach (f3.FHIRAllTypes ev in Enum.GetValues(typeof(f3.FHIRAllTypes))) - { - if (f3.ModelInfo.IsDataType(ev) && !f3.ModelInfo.IsCoreSuperType(ev)) - { - string name = f3.ModelInfo.FhirTypeToFhirTypeName(ev); - var t = f3.ModelInfo.GetTypeForFhirType(name); - newContext._cm3.Add(stu3::Hl7.Fhir.Introspection.ClassMapping.Create(t)); - } - } - } - } - else - { - if (item.ElementType != typeof(string)) // (only occurs for extension.url and elementdefinition.id) - newContext._cm3.Add(stu3::Hl7.Fhir.Introspection.ClassMapping.Create(item.ElementType)); - } - } - catch - { - } - } - } - - if (dstu2?.Any() == true) - { - foreach (var item in dstu2) - { - try - { - if (item.Choice == dstu2::Hl7.Fhir.Introspection.ChoiceType.DatatypeChoice) - { - if (item.Name != propertyName && item.MatchesSuffixedName(propertyName)) - { - string ExpectedType = propertyName.Substring(item.Name.Length); - if (Enum.TryParse(ExpectedType, out var result)) - { - if (f2.ModelInfo.IsDataType(result) || f2.ModelInfo.IsPrimitive(result)) - { - // may need to recheck that a typename of value23 won't work here - string name = f2.ModelInfo.FhirTypeToFhirTypeName(result); - var t = f2.ModelInfo.GetTypeForFhirType(name); - newContext._cm2.Add(dstu2::Hl7.Fhir.Introspection.ClassMapping.Create(t)); - } - } - } - else - { - // would be great to be able to filter through only the viable types - // but we don't have that, so just enumerate the available datatypes - foreach (f2.FHIRDefinedType ev in Enum.GetValues(typeof(f2.FHIRDefinedType))) - { - if (f2.ModelInfo.IsDataType(ev) && !f2.ModelInfo.IsCoreSuperType(ev)) - { - string name = f2.ModelInfo.FhirTypeToFhirTypeName(ev); - var t = f2.ModelInfo.GetTypeForFhirType(name); - newContext._cm2.Add(dstu2::Hl7.Fhir.Introspection.ClassMapping.Create(t)); - } - } - } - } - else - { - if (item.ElementType != typeof(string)) // (only occurs for extension.url and elementdefinition.id) - newContext._cm2.Add(dstu2::Hl7.Fhir.Introspection.ClassMapping.Create(item.ElementType)); - } - } - catch - { - } - } - } - return newContext; - } - - internal void RestrictToType(string typeCast) - { - if (Enum.TryParse(typeCast, out var result4)) - { - if (f4.ModelInfo.IsDataType(result4) || f4.ModelInfo.IsPrimitive(result4)) - { - _cm4 = _cm4.Where(t => t.Name == typeCast).ToList(); - } - } - if (Enum.TryParse(typeCast, out var result)) - { - if (f3.ModelInfo.IsDataType(result) || f3.ModelInfo.IsPrimitive(result)) - { - _cm3 = _cm3.Where(t => t.Name == typeCast).ToList(); - } - } - if (Enum.TryParse(typeCast, out var result2)) - { - if (f2.ModelInfo.IsDataType(result2) || f2.ModelInfo.IsPrimitive(result2)) - { - _cm2 = _cm2.Where(t => t.Name == typeCast).ToList(); - } - } - } - - internal string Tooltip() - { - StringBuilder sb = new StringBuilder(); - if (_cm4?.Any() == true) - { - sb.Append("R4:"); - foreach (var i4 in _cm4.Select(c => c.Name).Distinct()) - { - sb.Append($" {i4}"); - } - } - if (_cm3?.Any() == true) - { - if (_cm4?.Any() == true) - sb.AppendLine(); - sb.Append("STU3:"); - foreach (var i3 in _cm3.Select(c => c.Name).Distinct()) - { - sb.Append($" {i3}"); - } - } - if (_cm2?.Any() == true) - { - if (_cm3?.Any() == true || _cm4?.Any() == true) - sb.AppendLine(); - sb.Append("DSTU2:"); - foreach (var i2 in _cm2.Select(c => c.Name).Distinct()) - { - sb.Append($" {i2}"); - } - } - var groupLinkIds = _2gs?.Select(i => i.LinkId).ToArray(); - var questionLinkIds = _2qs?.Select(i => i.LinkId).ToArray(); - if (groupLinkIds != null) - sb.Append($"\r\nGroup LinkIds: {String.Join(", ", groupLinkIds)}"); - if (questionLinkIds != null) - sb.Append($"\r\nQuestion LinkIds: {String.Join(", ", questionLinkIds)}"); - - var itemLinkIds3 = _3is?.Select(i => i.LinkId).ToArray(); - if (itemLinkIds3 != null) - sb.Append($"\r\nItem3 LinkIds: {String.Join(", ", itemLinkIds3)}"); - - var itemLinkIds4 = _4is?.Select(i => i.LinkId).ToArray(); - if (itemLinkIds4 != null) - sb.Append($"\r\nItem4 LinkIds: {String.Join(", ", itemLinkIds4)}"); - - return sb.ToString(); - } - } - - private ExpressionElementContext CheckExpression(Hl7.FhirPath.Expressions.Expression expr, string prefix, ExpressionElementContext context) - { - if (expr is ChildExpression) - { - var func = expr as ChildExpression; - var focusContext = CheckExpression(func.Focus, prefix + "-- ", context); - var childContext = focusContext.Child(func.ChildName); - if (childContext != null) - { - if (focusContext._q2 != null) - { - if (func.ChildName == "group") - { - childContext._2gs = new List(); - childContext._2gs.Add(focusContext._q2.Group); - } - } - if (focusContext._2gs != null) - { - if (func.ChildName == "group") - { - childContext._2gs = new List(); - foreach (var item in focusContext._2gs) - { - if (item.Group != null) - childContext._2gs.AddRange(item.Group); - } - } - else if (func.ChildName == "question") - { - childContext._2qs = new List(); - foreach (var item in focusContext._2gs) - { - if (item.Question != null) - childContext._2qs.AddRange(item.Question); - } - } - } - if (focusContext._2qs != null) - { - if (func.ChildName == "group") - { - childContext._2gs = new List(); - foreach (var item in focusContext._2qs) - { - if (item.Group != null) - childContext._2gs.AddRange(item.Group); - } - } - } - AppendResults($"{prefix}{func.ChildName}", false, childContext.Tooltip()); - return childContext; - } - else - { - AppendResults($"{prefix}{func.ChildName} *invalid property name*", true); - } - return context; - } - if (expr is FunctionCallExpression) - { - var func = expr as FunctionCallExpression; - var funcs = _compiler.Symbols.Filter(func.FunctionName, func.Arguments.Count() + 1); - if (funcs.Count() == 0 && !(expr is BinaryExpression)) - { - AppendResults($"{prefix}{func.FunctionName} *invalid function name*", true); - } - else - { - AppendResults($"{prefix}{func.FunctionName}"); - } - var focusContext = CheckExpression(func.Focus, prefix + "-- ", context); - - if (func.FunctionName == "binary.as") - { - if (func.Arguments.Count() != 2) - { - AppendResults($"{prefix}{func.FunctionName} INVALID AS Operation", true); - return focusContext; - } - var argContextResult = CheckExpression(func.Arguments.First(), prefix + " ", focusContext); - var typeArg = func.Arguments.Skip(1).FirstOrDefault() as ConstantExpression; - string typeCast = typeArg?.Value as string; - argContextResult.RestrictToType(typeCast); - return argContextResult; - } - else if (func.FunctionName == "resolve") - { - // need to check what the available outcomes of resolving this are, and switch types to this - } - else - { - // if this is a where operation and the context inside is a linkId = , then check that the linkId is in context - if (func.FunctionName == "where" && func.Arguments.Count() == 1 && (func.Arguments.First() as BinaryExpression)?.Op == "=") - { - var op = func.Arguments.First() as BinaryExpression; - var argContextResult = CheckExpression(op, prefix + " ", focusContext); - - // Filter the values that are not in this set - focusContext._2gs = argContextResult._2gs; - focusContext._2qs = argContextResult._2qs; - } - else - { - foreach (var item in func.Arguments) - { - var argContextResult = CheckExpression(item, prefix + " ", focusContext); - } - } - if (func.FunctionName == "binary.=") - { - ChildExpression prop = (ChildExpression)func.Arguments.Where(a => a is ChildExpression).FirstOrDefault(); - ConstantExpression value = (ConstantExpression)func.Arguments.Where(a => a is ConstantExpression).FirstOrDefault(); - if (prop?.ChildName == "linkId" && value != null) - { - var groupLinkIds = focusContext._2gs?.Select(i => i.LinkId).ToArray(); - var questionLinkIds = focusContext._2qs?.Select(i => i.LinkId).ToArray(); - - // filter out all of the other linkIds from the list - focusContext._2gs?.RemoveAll(g => g.LinkId != value.Value as string); - focusContext._2qs?.RemoveAll(q => q.LinkId != value.Value as string); - - // Validate that there is an item with this value that is reachable - if (focusContext._2gs?.Count() == 0 || focusContext._2qs?.Count() == 0) - { - // this linkId didn't exist in this context! - string toolTip = "Available LinkIds:"; - if (groupLinkIds != null) - toolTip += $"\r\nGroup: {String.Join(", ", groupLinkIds)}"; - if (questionLinkIds != null) - toolTip += $"\r\nQuestion: {String.Join(", ", questionLinkIds)}"; - AppendResults($"{prefix}{func.FunctionName} LinkId is not valid in this context", true, toolTip); - } - } - } - } - - return focusContext; - } - //else if (expr is BinaryExpression) - //{ - // var func = expr as BinaryExpression; - // sb.AppendLine(func.FunctionName); - // CheckExpression(func.Left, sb); - // sb.AppendLine(func.Op); - // CheckExpression(func.Right, sb); - // return; - //} - else if (expr is ConstantExpression) - { - var func = expr as ConstantExpression; - AppendResults($"{prefix}{func.Value.ToString()} (constant)"); - return null; // context doesn't propogate from this - } - else if (expr is VariableRefExpression) - { - var func = expr as VariableRefExpression; - // sb.AppendFormat("{0}{1} (variable ref)\r\n", prefix, func.Name); - return context; - } - AppendResults(expr.GetType().ToString()); - return context; - } - - private void lblXML_MouseUp(object sender, MouseButtonEventArgs e) - { - // prettify the XML (and convert to XML if it wasn't already) - f3.Resource resource = null; - string contentAsXML; - try - { - if (textboxInputXML.Text.StartsWith("{")) - { - resource = new stu3.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); - contentAsXML = new stu3.Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(resource); - } - else - contentAsXML = textboxInputXML.Text; - var doc = System.Xml.Linq.XDocument.Parse(contentAsXML); - textboxInputXML.Text = doc.ToString(System.Xml.Linq.SaveOptions.None); - - } - catch (Exception ex3) - { - f2.Resource resource2 = null; - try - { - if (textboxInputXML.Text.StartsWith("{")) - { - resource2 = new dstu2.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); - contentAsXML = new dstu2.Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(resource2); - } - else - contentAsXML = textboxInputXML.Text; - var doc = System.Xml.Linq.XDocument.Parse(contentAsXML); - textboxInputXML.Text = doc.ToString(System.Xml.Linq.SaveOptions.None); - } - catch (Exception ex2) - { - f4.Resource resource4 = null; - try - { - if (textboxInputXML.Text.StartsWith("{")) - { - resource4 = new r4.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); - contentAsXML = new r4.Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(resource4); - } - else - contentAsXML = textboxInputXML.Text; - var doc = System.Xml.Linq.XDocument.Parse(contentAsXML); - textboxInputXML.Text = doc.ToString(System.Xml.Linq.SaveOptions.None); - } - catch (Exception ex4) - { - } - } - } - } - - private void lblJson_MouseUp(object sender, MouseButtonEventArgs e) - { - // prettify the Json (and convert to Json if it wasn't already) - f3.Resource resource = null; - string contentAsJson; - try - { - if (textboxInputXML.Text.StartsWith("{")) - { - contentAsJson = textboxInputXML.Text; - } - else - { - resource = new stu3.Hl7.Fhir.Serialization.FhirXmlParser().Parse(textboxInputXML.Text); - contentAsJson = new stu3.Hl7.Fhir.Serialization.FhirJsonSerializer().SerializeToString(resource); - } - var sr = new System.IO.StringReader(contentAsJson); - var reader = new JsonTextReader(sr); - var doc = JObject.Load(reader); - textboxInputXML.Text = doc.ToString(Formatting.Indented); - } - catch (Exception ex3) - { - f2.Resource resource2 = null; - try - { - if (textboxInputXML.Text.StartsWith("{")) - { - contentAsJson = textboxInputXML.Text; - } - else - { - resource2 = new dstu2.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); - contentAsJson = new dstu2.Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(resource2); - } - var sr = new System.IO.StringReader(contentAsJson); - var reader = new JsonTextReader(sr); - var doc = JObject.Load(reader); - textboxInputXML.Text = doc.ToString(Formatting.Indented); - } - catch (Exception ex2) - { - f4.Resource resource4 = null; - try - { - if (textboxInputXML.Text.StartsWith("{")) - { - contentAsJson = textboxInputXML.Text; - } - else - { - resource4 = new r4.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); - contentAsJson = new r4.Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(resource4); - } - var sr = new System.IO.StringReader(contentAsJson); - var reader = new JsonTextReader(sr); - var doc = JObject.Load(reader); - textboxInputXML.Text = doc.ToString(Formatting.Indented); - } - catch (Exception ex4) - { - } - } - } - } - } -} +extern alias dstu2; +extern alias stu3; +extern alias r4; + +using Hl7.Fhir.ElementModel; + +using fp2 = dstu2.Hl7.Fhir.FhirPath; +using f2 = dstu2.Hl7.Fhir.Model; + +using fp3 = stu3.Hl7.Fhir.FhirPath; +using f3 = stu3.Hl7.Fhir.Model; + +using fp4 = r4.Hl7.Fhir.FhirPath; +using f4 = r4.Hl7.Fhir.Model; + +using Hl7.FhirPath; +using Hl7.FhirPath.Expressions; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Input; +using System.Windows.Documents; +using System.Windows.Media; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; + +namespace FhirPathTester +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window, INotifyPropertyChanged + { + public int TextControlFontSize { get; set; } + + public MainWindow() + { + TextControlFontSize = 22; + InitializeComponent(); + textboxInputXML.Text = "\r\n\r\n\r\n\r\n"; + textboxExpression.Text = "birthDate < today() | identifier.assigner.display.extension.where(url='3').value.code"; + } + + public event PropertyChangedEventHandler PropertyChanged; + + private ITypedElement GetResourceNavigatorDSTU2(out string parseErrors) + { + f2.Resource resource = null; + try + { + if (textboxInputXML.Text.StartsWith("{")) + resource = new dstu2.Hl7.Fhir.Serialization.FhirJsonParser(new dstu2.Hl7.Fhir.Serialization.ParserSettings() { PermissiveParsing = true }).Parse(textboxInputXML.Text); + else + resource = new dstu2.Hl7.Fhir.Serialization.FhirXmlParser(new dstu2.Hl7.Fhir.Serialization.ParserSettings() { PermissiveParsing = true }).Parse(textboxInputXML.Text); + } + catch (Exception ex) + { + parseErrors = "DSTU2 Resource read error:\r\n" + ex.Message; + return null; + } + parseErrors = null; + var inputNav = dstu2.Hl7.Fhir.ElementModel.PocoNavigatorExtensions.ToTypedElement(resource); + return inputNav; + } + private ITypedElement GetResourceNavigatorSTU3(out string parseErrors) + { + f3.Resource resource = null; + try + { + if (textboxInputXML.Text.StartsWith("{")) + resource = new stu3.Hl7.Fhir.Serialization.FhirJsonParser(new stu3.Hl7.Fhir.Serialization.ParserSettings() { PermissiveParsing = true }).Parse(textboxInputXML.Text); + else + resource = new stu3.Hl7.Fhir.Serialization.FhirXmlParser(new stu3.Hl7.Fhir.Serialization.ParserSettings() { PermissiveParsing = true }).Parse(textboxInputXML.Text); + } + catch (Exception ex) + { + parseErrors = "STU3 Resource read error:\r\n" + ex.Message; + return null; + } + parseErrors = null; + var inputNav = stu3.Hl7.Fhir.ElementModel.PocoNavigatorExtensions.ToTypedElement(resource); + return inputNav; + } + private ITypedElement GetResourceNavigatorR4(out string parseErrors) + { + f4.Resource resource = null; + try + { + if (textboxInputXML.Text.StartsWith("{")) + resource = new r4.Hl7.Fhir.Serialization.FhirJsonParser(new r4.Hl7.Fhir.Serialization.ParserSettings() { }).Parse(textboxInputXML.Text); + else + resource = new r4.Hl7.Fhir.Serialization.FhirXmlParser(new r4.Hl7.Fhir.Serialization.ParserSettings() { }).Parse(textboxInputXML.Text); + } + catch (Exception ex) + { + parseErrors = "R4 Resource read error:\r\n" + ex.Message; + return null; + } + parseErrors = null; + var inputNav = r4.Hl7.Fhir.ElementModel.PocoNavigatorExtensions.ToTypedElement(resource); + return inputNav; + } + + private ITypedElement GetResourceNavigator(out EvaluationContext evalContext) + { + string parseErrors2; + var inputNavDSTU2 = GetResourceNavigatorDSTU2(out parseErrors2); + string parseErrors3; + var inputNavSTU3 = GetResourceNavigatorSTU3(out parseErrors3); + string parseErrors4; + var inputNavR4 = GetResourceNavigatorR4(out parseErrors4); + + if (!string.IsNullOrEmpty(parseErrors2) || !string.IsNullOrEmpty(parseErrors3) || !string.IsNullOrEmpty(parseErrors4)) + { + ResetResults(); + textboxResult.AppendText(String.Join("\r\n--------------------\r\n", parseErrors2, parseErrors3, parseErrors4)); + } + + if (inputNavR4 != null) + labelR4.Visibility = Visibility.Visible; + else + labelR4.Visibility = Visibility.Collapsed; + if (inputNavSTU3 != null) + labelSTU3.Visibility = Visibility.Visible; + else + labelSTU3.Visibility = Visibility.Collapsed; + + if (inputNavDSTU2 != null) + labelDSTU2.Visibility = Visibility.Visible; + else + labelDSTU2.Visibility = Visibility.Collapsed; + + if (inputNavSTU3 != null) + { + evalContext = new fp3.FhirEvaluationContext(inputNavSTU3); + return inputNavSTU3; + } + if (inputNavDSTU2 != null) + { + evalContext = new fp2.FhirEvaluationContext(inputNavDSTU2); + return inputNavDSTU2; + } + evalContext = new fp4.FhirEvaluationContext(inputNavR4); + return inputNavR4; + } + + private void ButtonGo_Click(object sender, RoutedEventArgs e) + { + EvaluationContext evalContext; + var inputNav = GetResourceNavigator(out evalContext); + if (inputNav == null) + return; + + // Don't need to cache this, it is cached in the fhir-client + CompiledExpression xps = null; + try + { + xps = _compiler.Compile(textboxExpression.Text); + } + catch (Exception ex) + { + SetResults("Expression compilation error:\r\n" + ex.Message); + return; + } + + IEnumerable prepopulatedValues = null; + if (xps != null) + { + try + { + prepopulatedValues = xps(inputNav, evalContext); + } + catch (Exception ex) + { + SetResults("Expression evaluation error:\r\n" + ex.Message); + AppendParseTree(); + return; + } + + ResetResults(); + try + { + if (prepopulatedValues.Count() > 0) + { + foreach (var item in prepopulatedValues) + { + string tooltip = item.Annotation()?.ShortPath; + if (item is stu3.Hl7.Fhir.ElementModel.IFhirValueProvider) + { + foreach (var t2 in fp3.ElementNavFhirExtensions.ToFhirValues(new ITypedElement[] { item }).Where(i => i != null)) + { + // output the content as XML fragments + var fragment = stu3.Hl7.Fhir.Serialization.FhirSerializer.SerializeToXml(t2, root: t2.TypeName); + fragment = AppendXmlFramentResults(fragment); + } + } + else if (item is dstu2.Hl7.Fhir.ElementModel.IFhirValueProvider) + { + foreach (var t2 in fp2.ElementNavFhirExtensions.ToFhirValues(new ITypedElement[] { item }).Where(i => i != null)) + { + // output the content as XML fragments + var fragment = dstu2.Hl7.Fhir.Serialization.FhirSerializer.SerializeToXml(t2, root: t2.TypeName); + fragment = AppendXmlFramentResults(fragment); + } + } + else if (item is r4.Hl7.Fhir.ElementModel.IFhirValueProvider) + { + foreach (var t2 in fp4.ElementNavFhirExtensions.ToFhirValues(new ITypedElement[] { item }).Where(i => i != null)) + { + // output the content as XML fragments + var fragment = r4.Hl7.Fhir.Serialization.FhirSerializer.SerializeToXml(t2, root: t2.TypeName); + fragment = AppendXmlFramentResults(fragment); + } + } + else if (item is ITypedElement te) + { + AppendResults($"<{te.InstanceType} value=\"{te.Value}\">"); + } + } + } + } + catch (Exception ex) + { + SetResults("Processing results error:\r\n" + ex.Message); + return; + } + } + + AppendParseTree(); + } + + private string AppendXmlFramentResults(string fragment) + { + if (fragment.Length > 100) + { + // pretty print the content + var doc = System.Xml.Linq.XDocument.Parse(fragment); + fragment = doc.ToString(System.Xml.Linq.SaveOptions.None); + } + AppendResults(fragment.Replace(" xmlns=\"http://hl7.org/fhir\"", "")); + return fragment; + } + + private void AppendParseTree() + { + // Grab the parse expression + StringBuilder sb = new StringBuilder(); + var expr = _compiler.Parse(textboxExpression.Text); + OutputExpression(expr, sb, ""); + // textboxResult.Text += expr.Dump(); + AppendResults("\r\n\r\n----------------\r\n" + sb.ToString()); + } + + private void OutputExpression(Hl7.FhirPath.Expressions.Expression expr, StringBuilder sb, string prefix) + { + if (expr is ChildExpression) + { + var func = expr as ChildExpression; + OutputExpression(func.Focus, sb, prefix + "-- "); + sb.AppendFormat("{0}{1}\r\n", prefix, func.ChildName); + return; + } + if (expr is FunctionCallExpression) + { + var func = expr as FunctionCallExpression; + sb.AppendFormat("{0}{1}\r\n", prefix, func.FunctionName); + OutputExpression(func.Focus, sb, prefix + "-- "); + foreach (var item in func.Arguments) + { + OutputExpression(item, sb, prefix + " "); + } + return; + } + //else if (expr is BinaryExpression) + //{ + // var func = expr as BinaryExpression; + // sb.AppendLine(func.FunctionName); + // OutputExpression(func.Left, sb); + // sb.AppendLine(func.Op); + // OutputExpression(func.Right, sb); + // return; + //} + else if (expr is ConstantExpression) + { + var func = expr as ConstantExpression; + sb.AppendFormat("{0}{1} (constant)\r\n", prefix, func.Value.ToString()); + return; + } + else if (expr is VariableRefExpression) + { + var func = expr as VariableRefExpression; + // sb.AppendFormat("{0}{1} (variable ref)\r\n", prefix, func.Name); + return; + } + sb.Append(expr.GetType().ToString()); + } + + Hl7.FhirPath.FhirPathCompiler _compiler = new FhirPathCompiler(CustomFluentPathFunctions.Scope); + + private void ButtonPredicate_Click(object sender, RoutedEventArgs e) + { + EvaluationContext evalContext; + var inputNav = GetResourceNavigator(out evalContext); + if (inputNav == null) + return; + + // Don't need to cache this, it is cached in the fhir-client + Hl7.FhirPath.CompiledExpression xps = null; + try + { + xps = _compiler.Compile(textboxExpression.Text); + } + catch (Exception ex) + { + SetResults("Expression compilation error:\r\n" + ex.Message); + return; + } + + if (xps != null) + { + try + { + bool result = xps.Predicate(inputNav, evalContext); + SetResults(result.ToString()); + } + catch (Exception ex) + { + SetResults("Expression evaluation error:\r\n" + ex.Message); + return; + } + } + + AppendParseTree(); + } + + private void textboxExpression_MouseWheel(object sender, MouseWheelEventArgs e) + { + if (e.Delta > 100 || e.Delta < -100) + { + TextControlFontSize += e.Delta / 100; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TextControlFontSize))); + } + } + + private void textboxInputXML_Drop(object sender, DragEventArgs e) + { + // This is the place where we want to support the reading of the file from the file system + // to make the testing of other instances really easy + var formats = e.Data.GetFormats(); + if (e.Data.GetDataPresent("FileName")) + { + string[] contents = e.Data.GetData("FileName") as string[]; + if (contents.Length > 0) + textboxInputXML.Text = System.IO.File.ReadAllText(contents[0]); + e.Handled = true; + } + } + + private void textboxInputXML_DragOver(object sender, DragEventArgs e) + { + e.Effects = DragDropEffects.Copy | DragDropEffects.Link; + } + + private void textboxInputXML_PreviewDragOver(object sender, DragEventArgs e) + { + e.Handled = true; + } + + private void textboxInputXML_SelectionChanged(object sender, RoutedEventArgs e) + { + int lineIndex = 0; + int colIndex = 0; + try + { + int charIndex = textboxInputXML.CaretIndex; + lineIndex = textboxInputXML.GetLineIndexFromCharacterIndex(charIndex); + colIndex = charIndex - textboxInputXML.GetCharacterIndexFromLineIndex(lineIndex); + } + catch (Exception) + { + } + textboxRow.Content = $"Ln {lineIndex + 1}"; + textboxCol.Content = $"Col {colIndex}"; + } + + private void ButtonCheckExpression_Click(object sender, RoutedEventArgs e) + { + EvaluationContext evalContext; + var inputNav = GetResourceNavigator(out evalContext); + if (inputNav == null) + return; + + // Don't need to cache this, it is cached in the fhir-client + Hl7.FhirPath.Expressions.Expression expr = null; + try + { + expr = _compiler.Parse(textboxExpression.Text); + } + catch (Exception ex) + { + SetResults("Expression compilation error:\r\n" + ex.Message, true); + return; + } + + if (expr != null) + { + try + { + ExpressionElementContext context = new ExpressionElementContext(inputNav.Name); + if (inputNav is dstu2::Hl7.Fhir.ElementModel.IFhirValueProvider pn2) + { + if (pn2.FhirValue is f2.Questionnaire q) + { + context._q2 = q; + } + } + else if (inputNav is stu3::Hl7.Fhir.ElementModel.IFhirValueProvider pn3) + { + if (pn3.FhirValue is f3.Questionnaire q) + { + context._q3 = q; + } + } + else if (inputNav is r4::Hl7.Fhir.ElementModel.IFhirValueProvider pn4) + { + if (pn4.FhirValue is f4.Questionnaire q) + { + context._q4 = q; + } + } + ResetResults(); + CheckExpression(expr, "", context); + } + catch (Exception ex) + { + SetResults("Expression Check error:\r\n" + ex.Message, true); + return; + } + } + } + + private void ResetResults() + { + textboxResult.Document.Blocks.Clear(); + } + + private void SetResults(string text, bool error = false) + { + textboxResult.Document.Blocks.Clear(); + var run = new Run(text); + var para = new Paragraph(run); + textboxResult.Document.Blocks.Add(para); + if (error) + { + para.Foreground = Brushes.Red; + para.FontWeight = FontWeights.Bold; + } + } + private void AppendResults(string text, bool error = false, string tooltip = null) + { + var run = new Run(text); + var para = new Paragraph(run); + if (!string.IsNullOrEmpty(tooltip)) + para.ToolTip = tooltip; + para.Margin = new Thickness(2); + textboxResult.Document.Blocks.Add(para); + if (error) + { + para.Foreground = Brushes.Red; + para.FontWeight = FontWeights.Bold; + } + } + + public class ExpressionElementContext + { + public ExpressionElementContext(string typeName) + { + _typeName = typeName; + if (!string.IsNullOrEmpty(typeName)) + { + var t4 = r4.Hl7.Fhir.Model.ModelInfo.GetTypeForFhirType(typeName); + if (t4 != null) + { + try + { + _cm4 = new List() { r4.Hl7.Fhir.Introspection.ClassMapping.Create(t4) }; + } + catch + { + } + } + var t3 = stu3.Hl7.Fhir.Model.ModelInfo.GetTypeForFhirType(typeName); + if (t3 != null) + { + try + { + _cm3 = new List() { stu3.Hl7.Fhir.Introspection.ClassMapping.Create(t3) }; + } + catch + { + } + } + var t2 = dstu2.Hl7.Fhir.Model.ModelInfo.GetTypeForFhirType(typeName); + if (t2 != null) + { + try + { + _cm2 = new List() { dstu2.Hl7.Fhir.Introspection.ClassMapping.Create(t2) }; + } + catch + { + } + } + } + } + + private ExpressionElementContext() + { + _cm4 = new List(); + _cm3 = new List(); + _cm2 = new List(); + } + + // Questionnaire Context information when processing validation against a questionnaire + internal f2.Questionnaire _q2; + internal f3.Questionnaire _q3; + internal f4.Questionnaire _q4; + internal List _2gs; + internal List _2qs; + internal List _3is; + internal List _4is; + + // Where this is the context of a class + string _typeName; + List _cm4; + List _cm3; + List _cm2; + + public string possibleTypes() + { + return string.Join(", ", _cm2.Select(t => "::" + t.Name).Union(_cm3.Select(t => "::" + t.Name)).Union(_cm4.Select(t => "::" + t.Name))); + } + + bool IsMatchingPropName(string propertyName, r4::Hl7.Fhir.Introspection.PropertyMapping pm) + { + if (propertyName == pm.Name) + return true; + if (propertyName + "[x]" == pm.Name) + return true; + if (!pm.MatchesSuffixedName(propertyName)) + return false; + string ExpectedType = propertyName.Substring(pm.Name.Length); + if (Enum.TryParse(ExpectedType, out var result)) + { + if (String.Compare(ExpectedType, Hl7.Fhir.Utility.EnumUtility.GetLiteral(result), true) != 0) + return false; + if (f4.ModelInfo.IsDataType(result) || f4.ModelInfo.IsPrimitive(result)) + return true; + } + return false; + } + + bool IsMatchingPropName(string propertyName, stu3::Hl7.Fhir.Introspection.PropertyMapping pm) + { + if (propertyName == pm.Name) + return true; + if (propertyName + "[x]" == pm.Name) + return true; + if (!pm.MatchesSuffixedName(propertyName)) + return false; + string ExpectedType = propertyName.Substring(pm.Name.Length); + if (Enum.TryParse(ExpectedType, out var result)) + { + if (String.Compare(ExpectedType, Hl7.Fhir.Utility.EnumUtility.GetLiteral(result), true) != 0) + return false; + if (f3.ModelInfo.IsDataType(result) || f3.ModelInfo.IsPrimitive(result)) + return true; + } + return false; + } + + bool IsMatchingPropName(string propertyName, dstu2::Hl7.Fhir.Introspection.PropertyMapping pm) + { + if (propertyName == pm.Name) + return true; + if (propertyName + "[x]" == pm.Name) + return true; + if (!pm.MatchesSuffixedName(propertyName)) + return false; + string ExpectedType = propertyName.Substring(pm.Name.Length); + if (Enum.TryParse(ExpectedType, out var result)) + { + if (String.Compare(ExpectedType, Hl7.Fhir.Utility.EnumUtility.GetLiteral(result), true) != 0) + return false; + if (f2.ModelInfo.IsDataType(result) || f2.ModelInfo.IsPrimitive(result)) + return true; + } + return false; + } + + bool HasProperty(string propName, + out List dstu2, + out List stu3, + out List r4) + { + if (_cm4 == null) + r4 = null; + else + r4 = _cm4.Select(t => t.FindMappedElementByName(propName)).Where(t => t != null && IsMatchingPropName(propName, t)).ToList(); + if (_cm3 == null) + stu3 = null; + else + stu3 = _cm3.Select(t => t.FindMappedElementByName(propName)).Where(t => t != null && IsMatchingPropName(propName, t)).ToList(); + if (_cm2 == null) + dstu2 = null; + else + dstu2 = _cm2.Select(t => t.FindMappedElementByName(propName)).Where(t => t != null && IsMatchingPropName(propName, t)).ToList(); + return (dstu2?.Count() > 0) || (stu3?.Count() > 0) || (r4?.Count() > 0); + } + + public ExpressionElementContext Child(string propertyName) + { + // Special case for the top level node + if (propertyName == _typeName) + return this; + + if (!HasProperty(propertyName, out var dstu2, out var stu3, out var r4)) + return null; + + var newContext = new ExpressionElementContext(); + if (r4?.Any() == true) + { + foreach (var item in r4) + { + try + { + if (item.Choice == r4::Hl7.Fhir.Introspection.ChoiceType.DatatypeChoice) + { + if (item.Name != propertyName && item.MatchesSuffixedName(propertyName)) + { + string ExpectedType = propertyName.Substring(item.Name.Length); + if (Enum.TryParse(ExpectedType, out var result)) + { + if (f4.ModelInfo.IsDataType(result) || f4.ModelInfo.IsPrimitive(result)) + { + // may need to recheck that a typename of value23 won't work here + string name = f4.ModelInfo.FhirTypeToFhirTypeName(result); + var t = f4.ModelInfo.GetTypeForFhirType(name); + newContext._cm4.Add(r4::Hl7.Fhir.Introspection.ClassMapping.Create(t)); + } + } + } + else + { + // would be great to be able to filter through only the viable types + // but we don't have that, so just enumerate the available datatypes + foreach (f4.FHIRAllTypes ev in Enum.GetValues(typeof(f4.FHIRAllTypes))) + { + if (f4.ModelInfo.IsDataType(ev) && !f4.ModelInfo.IsCoreSuperType(ev)) + { + string name = f4.ModelInfo.FhirTypeToFhirTypeName(ev); + var t = f4.ModelInfo.GetTypeForFhirType(name); + newContext._cm4.Add(r4::Hl7.Fhir.Introspection.ClassMapping.Create(t)); + } + } + } + } + else + { + if (item.ElementType != typeof(string)) // (only occurs for extension.url and elementdefinition.id) + newContext._cm4.Add(r4::Hl7.Fhir.Introspection.ClassMapping.Create(item.ElementType)); + } + } + catch + { + } + } + } + if (stu3?.Any() == true) + { + foreach (var item in stu3) + { + try + { + if (item.Choice == stu3::Hl7.Fhir.Introspection.ChoiceType.DatatypeChoice) + { + if (item.Name != propertyName && item.MatchesSuffixedName(propertyName)) + { + string ExpectedType = propertyName.Substring(item.Name.Length); + if (Enum.TryParse(ExpectedType, out var result)) + { + if (f3.ModelInfo.IsDataType(result) || f3.ModelInfo.IsPrimitive(result)) + { + // may need to recheck that a typename of value23 won't work here + string name = f3.ModelInfo.FhirTypeToFhirTypeName(result); + var t = f3.ModelInfo.GetTypeForFhirType(name); + newContext._cm3.Add(stu3::Hl7.Fhir.Introspection.ClassMapping.Create(t)); + } + } + } + else + { + // would be great to be able to filter through only the viable types + // but we don't have that, so just enumerate the available datatypes + foreach (f3.FHIRAllTypes ev in Enum.GetValues(typeof(f3.FHIRAllTypes))) + { + if (f3.ModelInfo.IsDataType(ev) && !f3.ModelInfo.IsCoreSuperType(ev)) + { + string name = f3.ModelInfo.FhirTypeToFhirTypeName(ev); + var t = f3.ModelInfo.GetTypeForFhirType(name); + newContext._cm3.Add(stu3::Hl7.Fhir.Introspection.ClassMapping.Create(t)); + } + } + } + } + else + { + if (item.ImplementingType != typeof(string)) // (only occurs for extension.url and elementdefinition.id) + newContext._cm3.Add(stu3::Hl7.Fhir.Introspection.ClassMapping.Create(item.ImplementingType)); + } + } + catch + { + } + } + } + + if (dstu2?.Any() == true) + { + foreach (var item in dstu2) + { + try + { + if (item.Choice == dstu2::Hl7.Fhir.Introspection.ChoiceType.DatatypeChoice) + { + if (item.Name != propertyName && item.MatchesSuffixedName(propertyName)) + { + string ExpectedType = propertyName.Substring(item.Name.Length); + if (Enum.TryParse(ExpectedType, out var result)) + { + if (f2.ModelInfo.IsDataType(result) || f2.ModelInfo.IsPrimitive(result)) + { + // may need to recheck that a typename of value23 won't work here + string name = f2.ModelInfo.FhirTypeToFhirTypeName(result); + var t = f2.ModelInfo.GetTypeForFhirType(name); + newContext._cm2.Add(dstu2::Hl7.Fhir.Introspection.ClassMapping.Create(t)); + } + } + } + else + { + // would be great to be able to filter through only the viable types + // but we don't have that, so just enumerate the available datatypes + foreach (f2.FHIRDefinedType ev in Enum.GetValues(typeof(f2.FHIRDefinedType))) + { + if (f2.ModelInfo.IsDataType(ev) && !f2.ModelInfo.IsCoreSuperType(ev)) + { + string name = f2.ModelInfo.FhirTypeToFhirTypeName(ev); + var t = f2.ModelInfo.GetTypeForFhirType(name); + newContext._cm2.Add(dstu2::Hl7.Fhir.Introspection.ClassMapping.Create(t)); + } + } + } + } + else + { + if (item.ElementType != typeof(string)) // (only occurs for extension.url and elementdefinition.id) + newContext._cm2.Add(dstu2::Hl7.Fhir.Introspection.ClassMapping.Create(item.ElementType)); + } + } + catch + { + } + } + } + return newContext; + } + + internal void RestrictToType(string typeCast) + { + if (Enum.TryParse(typeCast, out var result4)) + { + if (f4.ModelInfo.IsDataType(result4) || f4.ModelInfo.IsPrimitive(result4)) + { + _cm4 = _cm4.Where(t => t.Name == typeCast).ToList(); + } + } + if (Enum.TryParse(typeCast, out var result)) + { + if (f3.ModelInfo.IsDataType(result) || f3.ModelInfo.IsPrimitive(result)) + { + _cm3 = _cm3.Where(t => t.Name == typeCast).ToList(); + } + } + if (Enum.TryParse(typeCast, out var result2)) + { + if (f2.ModelInfo.IsDataType(result2) || f2.ModelInfo.IsPrimitive(result2)) + { + _cm2 = _cm2.Where(t => t.Name == typeCast).ToList(); + } + } + } + + internal string Tooltip() + { + StringBuilder sb = new StringBuilder(); + if (_cm4?.Any() == true) + { + sb.Append("R4:"); + foreach (var i4 in _cm4.Select(c => c.Name).Distinct()) + { + sb.Append($" {i4}"); + } + } + if (_cm3?.Any() == true) + { + if (_cm4?.Any() == true) + sb.AppendLine(); + sb.Append("STU3:"); + foreach (var i3 in _cm3.Select(c => c.Name).Distinct()) + { + sb.Append($" {i3}"); + } + } + if (_cm2?.Any() == true) + { + if (_cm3?.Any() == true || _cm4?.Any() == true) + sb.AppendLine(); + sb.Append("DSTU2:"); + foreach (var i2 in _cm2.Select(c => c.Name).Distinct()) + { + sb.Append($" {i2}"); + } + } + var groupLinkIds = _2gs?.Select(i => i.LinkId).ToArray(); + var questionLinkIds = _2qs?.Select(i => i.LinkId).ToArray(); + if (groupLinkIds != null) + sb.Append($"\r\nGroup LinkIds: {String.Join(", ", groupLinkIds)}"); + if (questionLinkIds != null) + sb.Append($"\r\nQuestion LinkIds: {String.Join(", ", questionLinkIds)}"); + + var itemLinkIds3 = _3is?.Select(i => i.LinkId).ToArray(); + if (itemLinkIds3 != null) + sb.Append($"\r\nItem3 LinkIds: {String.Join(", ", itemLinkIds3)}"); + + var itemLinkIds4 = _4is?.Select(i => i.LinkId).ToArray(); + if (itemLinkIds4 != null) + sb.Append($"\r\nItem4 LinkIds: {String.Join(", ", itemLinkIds4)}"); + + return sb.ToString(); + } + } + + private ExpressionElementContext CheckExpression(Hl7.FhirPath.Expressions.Expression expr, string prefix, ExpressionElementContext context) + { + if (expr is ChildExpression) + { + var func = expr as ChildExpression; + var focusContext = CheckExpression(func.Focus, prefix + "-- ", context); + var childContext = focusContext.Child(func.ChildName); + if (childContext != null) + { + if (focusContext._q2 != null) + { + if (func.ChildName == "group") + { + childContext._2gs = new List(); + childContext._2gs.Add(focusContext._q2.Group); + } + } + if (focusContext._2gs != null) + { + if (func.ChildName == "group") + { + childContext._2gs = new List(); + foreach (var item in focusContext._2gs) + { + if (item.Group != null) + childContext._2gs.AddRange(item.Group); + } + } + else if (func.ChildName == "question") + { + childContext._2qs = new List(); + foreach (var item in focusContext._2gs) + { + if (item.Question != null) + childContext._2qs.AddRange(item.Question); + } + } + } + if (focusContext._2qs != null) + { + if (func.ChildName == "group") + { + childContext._2gs = new List(); + foreach (var item in focusContext._2qs) + { + if (item.Group != null) + childContext._2gs.AddRange(item.Group); + } + } + } + AppendResults($"{prefix}{func.ChildName}", false, childContext.Tooltip()); + return childContext; + } + else + { + AppendResults($"{prefix}{func.ChildName} *invalid property name*", true); + } + return context; + } + if (expr is FunctionCallExpression) + { + var func = expr as FunctionCallExpression; + var funcs = _compiler.Symbols.Filter(func.FunctionName, func.Arguments.Count() + 1); + if (funcs.Count() == 0 && !(expr is BinaryExpression)) + { + AppendResults($"{prefix}{func.FunctionName} *invalid function name*", true); + } + else + { + AppendResults($"{prefix}{func.FunctionName}"); + } + var focusContext = CheckExpression(func.Focus, prefix + "-- ", context); + + if (func.FunctionName == "binary.as") + { + if (func.Arguments.Count() != 2) + { + AppendResults($"{prefix}{func.FunctionName} INVALID AS Operation", true); + return focusContext; + } + var argContextResult = CheckExpression(func.Arguments.First(), prefix + " ", focusContext); + var typeArg = func.Arguments.Skip(1).FirstOrDefault() as ConstantExpression; + string typeCast = typeArg?.Value as string; + argContextResult.RestrictToType(typeCast); + return argContextResult; + } + else if (func.FunctionName == "resolve") + { + // need to check what the available outcomes of resolving this are, and switch types to this + } + else + { + // if this is a where operation and the context inside is a linkId = , then check that the linkId is in context + if (func.FunctionName == "where" && func.Arguments.Count() == 1 && (func.Arguments.First() as BinaryExpression)?.Op == "=") + { + var op = func.Arguments.First() as BinaryExpression; + var argContextResult = CheckExpression(op, prefix + " ", focusContext); + + // Filter the values that are not in this set + focusContext._2gs = argContextResult._2gs; + focusContext._2qs = argContextResult._2qs; + } + else + { + foreach (var item in func.Arguments) + { + var argContextResult = CheckExpression(item, prefix + " ", focusContext); + } + } + if (func.FunctionName == "binary.=") + { + ChildExpression prop = (ChildExpression)func.Arguments.Where(a => a is ChildExpression).FirstOrDefault(); + ConstantExpression value = (ConstantExpression)func.Arguments.Where(a => a is ConstantExpression).FirstOrDefault(); + if (prop?.ChildName == "linkId" && value != null) + { + var groupLinkIds = focusContext._2gs?.Select(i => i.LinkId).ToArray(); + var questionLinkIds = focusContext._2qs?.Select(i => i.LinkId).ToArray(); + + // filter out all of the other linkIds from the list + focusContext._2gs?.RemoveAll(g => g.LinkId != value.Value as string); + focusContext._2qs?.RemoveAll(q => q.LinkId != value.Value as string); + + // Validate that there is an item with this value that is reachable + if (focusContext._2gs?.Count() == 0 || focusContext._2qs?.Count() == 0) + { + // this linkId didn't exist in this context! + string toolTip = "Available LinkIds:"; + if (groupLinkIds != null) + toolTip += $"\r\nGroup: {String.Join(", ", groupLinkIds)}"; + if (questionLinkIds != null) + toolTip += $"\r\nQuestion: {String.Join(", ", questionLinkIds)}"; + AppendResults($"{prefix}{func.FunctionName} LinkId is not valid in this context", true, toolTip); + } + } + } + } + + return focusContext; + } + //else if (expr is BinaryExpression) + //{ + // var func = expr as BinaryExpression; + // sb.AppendLine(func.FunctionName); + // CheckExpression(func.Left, sb); + // sb.AppendLine(func.Op); + // CheckExpression(func.Right, sb); + // return; + //} + else if (expr is ConstantExpression) + { + var func = expr as ConstantExpression; + AppendResults($"{prefix}{func.Value.ToString()} (constant)"); + return null; // context doesn't propogate from this + } + else if (expr is VariableRefExpression) + { + var func = expr as VariableRefExpression; + // sb.AppendFormat("{0}{1} (variable ref)\r\n", prefix, func.Name); + return context; + } + AppendResults(expr.GetType().ToString()); + return context; + } + + private void lblXML_MouseUp(object sender, MouseButtonEventArgs e) + { + // prettify the XML (and convert to XML if it wasn't already) + f3.Resource resource = null; + string contentAsXML; + try + { + if (textboxInputXML.Text.StartsWith("{")) + { + resource = new stu3.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); + contentAsXML = new stu3.Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(resource); + } + else + contentAsXML = textboxInputXML.Text; + var doc = System.Xml.Linq.XDocument.Parse(contentAsXML); + textboxInputXML.Text = doc.ToString(System.Xml.Linq.SaveOptions.None); + + } + catch (Exception ex3) + { + f2.Resource resource2 = null; + try + { + if (textboxInputXML.Text.StartsWith("{")) + { + resource2 = new dstu2.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); + contentAsXML = new dstu2.Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(resource2); + } + else + contentAsXML = textboxInputXML.Text; + var doc = System.Xml.Linq.XDocument.Parse(contentAsXML); + textboxInputXML.Text = doc.ToString(System.Xml.Linq.SaveOptions.None); + } + catch (Exception ex2) + { + f4.Resource resource4 = null; + try + { + if (textboxInputXML.Text.StartsWith("{")) + { + resource4 = new r4.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); + contentAsXML = new r4.Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(resource4); + } + else + contentAsXML = textboxInputXML.Text; + var doc = System.Xml.Linq.XDocument.Parse(contentAsXML); + textboxInputXML.Text = doc.ToString(System.Xml.Linq.SaveOptions.None); + } + catch (Exception ex4) + { + } + } + } + } + + private void lblJson_MouseUp(object sender, MouseButtonEventArgs e) + { + // prettify the Json (and convert to Json if it wasn't already) + f3.Resource resource = null; + string contentAsJson; + try + { + if (textboxInputXML.Text.StartsWith("{")) + { + contentAsJson = textboxInputXML.Text; + } + else + { + resource = new stu3.Hl7.Fhir.Serialization.FhirXmlParser().Parse(textboxInputXML.Text); + contentAsJson = new stu3.Hl7.Fhir.Serialization.FhirJsonSerializer().SerializeToString(resource); + } + var sr = new System.IO.StringReader(contentAsJson); + var reader = new JsonTextReader(sr); + var doc = JObject.Load(reader); + textboxInputXML.Text = doc.ToString(Formatting.Indented); + } + catch (Exception) + { + f2.Resource resource2 = null; + try + { + if (textboxInputXML.Text.StartsWith("{")) + { + contentAsJson = textboxInputXML.Text; + } + else + { + resource2 = new dstu2.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); + contentAsJson = new dstu2.Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(resource2); + } + var sr = new System.IO.StringReader(contentAsJson); + var reader = new JsonTextReader(sr); + var doc = JObject.Load(reader); + textboxInputXML.Text = doc.ToString(Formatting.Indented); + } + catch (Exception) + { + f4.Resource resource4 = null; + try + { + if (textboxInputXML.Text.StartsWith("{")) + { + contentAsJson = textboxInputXML.Text; + } + else + { + resource4 = new r4.Hl7.Fhir.Serialization.FhirJsonParser().Parse(textboxInputXML.Text); + contentAsJson = new r4.Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(resource4); + } + var sr = new System.IO.StringReader(contentAsJson); + var reader = new JsonTextReader(sr); + var doc = JObject.Load(reader); + textboxInputXML.Text = doc.ToString(Formatting.Indented); + } + catch (Exception) + { + } + } + } + } + } +} diff --git a/WPF/Properties/AssemblyInfo.cs b/WPF/Properties/AssemblyInfo.cs index 9a16af5..46faf59 100644 --- a/WPF/Properties/AssemblyInfo.cs +++ b/WPF/Properties/AssemblyInfo.cs @@ -51,5 +51,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.3.0")] -[assembly: AssemblyFileVersion("1.0.3.0")] +[assembly: AssemblyVersion("1.0.4.0")] +[assembly: AssemblyFileVersion("1.0.4.0")] diff --git a/WPF/QuestionnaireExpressionProcessing.cs b/WPF/QuestionnaireExpressionProcessing.cs deleted file mode 100644 index 15eaca9..0000000 --- a/WPF/QuestionnaireExpressionProcessing.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FhirPathTester -{ - public class QuestionnaireExpressionProcessing - { - - } -} diff --git a/WPF/packages.config b/WPF/packages.config index 7ec7ff0..ee3c9f2 100644 --- a/WPF/packages.config +++ b/WPF/packages.config @@ -1,13 +1,15 @@  - - - - - - - + + + + + + + + + - + \ No newline at end of file