From bb38e0cfbdc68bfad223f3b651045cebd99bdd55 Mon Sep 17 00:00:00 2001 From: Claire Davis Date: Sun, 16 Feb 2020 18:52:02 -0800 Subject: [PATCH] Completed design of template renderer --- Adaptive Card Editor UWP/MainPage.xaml.cs | 91 +++++++++- Scripts/template parser.linq | 200 +++++++++++++++++++--- 2 files changed, 264 insertions(+), 27 deletions(-) diff --git a/Adaptive Card Editor UWP/MainPage.xaml.cs b/Adaptive Card Editor UWP/MainPage.xaml.cs index 1e0b146..905deed 100644 --- a/Adaptive Card Editor UWP/MainPage.xaml.cs +++ b/Adaptive Card Editor UWP/MainPage.xaml.cs @@ -162,14 +162,14 @@ namespace Adaptive_Card_Editor_UWP // render a card using template and data try { - string Template = txtInput.Text; - string Data = txtData.Text; + string template = txtInput.Text; + string data = txtData.Text; - string Rendered = JsonFromTemplate(Template, Data); + string rendered = JsonFromTemplate(template, data); // render the card from the rendered template + data AdaptiveCardRenderer cardRenderer = new AdaptiveCardRenderer(); - AdaptiveCardParseResult parsedCard = AdaptiveCard.FromJsonString(Rendered); + AdaptiveCardParseResult parsedCard = AdaptiveCard.FromJsonString(rendered); RenderedAdaptiveCard theCard = cardRenderer.RenderAdaptiveCard(parsedCard.AdaptiveCard); grdTemplated.Children.Add(theCard.FrameworkElement); } @@ -211,7 +211,90 @@ namespace Adaptive_Card_Editor_UWP JObject Template = JObject.Parse(strTemplate); JObject Data = JObject.Parse(strData); + // replace instances of $data with x-data + // this is to work around JSON Path limitations + strTemplate = strTemplate.Replace("$", "x-"); + // first create JSON objects out of the input + JObject template = JObject.Parse(strTemplate); + JObject data = JObject.Parse(strData); + + // this is the old way - regex against the string + // maybe do it a better way + string queryString = @"..[?(@ =~ /\{(.+)?\}/)]"; + + List nodeList = template.SelectTokens(queryString).ToList(); + + // for each templated entity + // retrieve entity from data JSON + // if empty, remove entity + foreach (JValue node in nodeList) + { + // get string literal for template entity + string fulltext = node.Value(); + + // find the matching element in the data JSON + // strip { and } from fulltext + string cleantext = fulltext.Substring(1, fulltext.Length - 2); + + string searchString = "$." + cleantext; + + // find the token in Data JSON + // can be JValue or JArray + JToken searchResult = data.SelectToken(searchString); + + // if this element exists in the data + // and isn't $data + // replace element with text from Data JSON + // otherwise remove it + if (searchResult != null) + { + if (searchResult.GetType() == typeof(JValue)) + { + string oldValue = node.Value(); + string newValue = oldValue.Replace(fulltext, ((JValue)searchResult.Value()).ToString()); + node.Value = node.Value().ToString().Replace(oldValue, newValue); + } + else if (searchResult.GetType() == typeof(JArray)) + { + // it's a JArray + JArray searchArray = (JArray)searchResult; + + foreach (string item in searchArray) + { + JObject newItem = new JObject((JObject)node.Parent.Parent); + newItem["x-data"].Parent.Remove(); + newItem["text"] = item; + + node.Parent.Parent.Parent.Add(newItem); + } + + node.Parent.Parent.Remove(); + } + } + else if (fulltext.Contains("{x-")) + { + // this is handled differently + } + else + { + // remove this node + node.Parent.Parent.Remove(); + } + } + // now find any empty arrays and nuke their parents + // keep doing this until there are no more [] left in the JSON + + while (template.Descendants().OfType().Where(v => v.Type == JTokenType.Array && v.Count() == 0).ToList().Count() > 0) + { + List EmptyNodes = template.Descendants().OfType().Where(v => v.Type == JTokenType.Array && v.Count() == 0).ToList(); + foreach (JArray Node in EmptyNodes) + { + Node.Parent.Parent.Remove(); + } + } + + output = JsonConvert.SerializeObject(template); return output; } diff --git a/Scripts/template parser.linq b/Scripts/template parser.linq index 3eb5733..2a25e79 100644 --- a/Scripts/template parser.linq +++ b/Scripts/template parser.linq @@ -11,43 +11,105 @@ void Main() { string Rendered = JsonFromTemplate(Globals.strTemplate, Globals.strData); + Rendered.Dump(); } // render adaptive card JSON public string JsonFromTemplate(string strTemplate, string strData) { string output = ""; + + // replace instances of $data with x-data + // this is to work around JSON Path limitations + strTemplate = strTemplate.Replace("$","x-"); // first create JSON objects out of the input - JObject Template = JObject.Parse(strTemplate); - JObject Data = JObject.Parse(strData); - - // body = array of jobjects - - JArray TemplateBody = (JArray)Template["body"]; - - // for each jobject in this array - + JObject template = JObject.Parse(strTemplate); + JObject data = JObject.Parse(strData); + + // this is the old way - regex against the string + // maybe do it a better way + string queryString = @"..[?(@ =~ /\{(.+)?\}/)]"; + + List nodeList = template.SelectTokens(queryString).ToList(); + + // for each templated entity + // retrieve entity from data JSON + // if empty, remove entity + foreach (JValue node in nodeList) + { + // get string literal for template entity + string fulltext = node.Value(); + + // find the matching element in the data JSON + // strip { and } from fulltext + string cleantext = fulltext.Substring(1, fulltext.Length - 2); + + string searchString = "$." + cleantext; + + // find the token in Data JSON + // can be JValue or JArray + JToken searchResult = data.SelectToken(searchString); + + // if this element exists in the data + // and isn't $data + // replace element with text from Data JSON + // otherwise remove it + if (searchResult != null) + { + if (searchResult.GetType() == typeof(JValue)) + { + string oldValue = node.Value(); + string newValue = oldValue.Replace(fulltext, ((JValue)searchResult.Value()).ToString()); + node.Value = node.Value().ToString().Replace(oldValue, newValue); + } + else if (searchResult.GetType() == typeof(JArray)) + { + // it's a JArray + JArray searchArray = (JArray)searchResult; + + foreach (string item in searchArray) + { + JObject newItem = new JObject((JObject)node.Parent.Parent); + newItem["x-data"].Parent.Remove(); + newItem["text"] = item; + + node.Parent.Parent.Parent.Add(newItem); + } + + node.Parent.Parent.Remove(); + } + } + else if (fulltext.Contains("{x-")) + { + // this is handled differently + } + else + { + // remove this node + node.Parent.Parent.Remove(); + } + } + // now find any empty arrays and nuke their parents + // keep doing this until there are no more [] left in the JSON - TemplateBody.Dump(); + while (template.Descendants().OfType().Where(v => v.Type == JTokenType.Array && v.Count() == 0).ToList().Count() > 0) + { + List EmptyNodes = template.Descendants().OfType().Where(v => v.Type == JTokenType.Array && v.Count() == 0).ToList(); + foreach (JArray Node in EmptyNodes) + { + Node.Parent.Parent.Remove(); + } + } + + JsonConvert.SerializeObject(template, Newtonsoft.Json.Formatting.Indented).Dump(); return output; } // set up global statics public class Globals -{ - public static Dictionary Containers = new Dictionary - { - { "ActionSet", "actions"}, - { "Container", "items" }, - { "ColumnSet", "columns"}, - { "Column", "items"}, - { "FactSet", "facts"}, - { "Fact", ""}, - { "ImageSet", "images"} - }; - +{ public static string strData = @" { ""template"" : @@ -278,4 +340,96 @@ public class Globals ""$schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"", ""version"": ""1.0"" }"; -} \ No newline at end of file +} + +// How I used to do things with regex: +// ========================================================================================================================================== +// // get list of {template.refs} from JSON string +// Match m = Regex.Match(strTemplate, @"\{(.+)?\}"); +// +// //List Elements = new List(); +// +// Dictionary> elements = new Dictionary>(); +// +// while(m.Success) +// { +// if (!elements.Keys.Contains(m.Value)) +// { +// List elementList = new List(); +// // find the JToken(s) containing this element +// foreach (string el in Globals.CheckElements) +// { +// IEnumerable tempList = template.SelectTokens("$.." + el).Where(y => y.Value().Contains(m.Value)); +// elementList.AddRange(tempList); +// } +// +// // strip {} from element name +// string searchTerm = m.Groups[1].Value; +// +// string searchString = "$." + searchTerm; +// +// // find the token in Data JSON +// // can be JValue or JArray +// var searchResult = data.SelectToken(searchString); +// +// // if this element exists in the data +// // and isn't $data +// // replace element with text from Data JSON +// // otherwise remove it +// if (searchResult != null) +// { +// if (searchResult.GetType() == typeof(JValue)) +// { +// // it's a JValue +// // String.Replace value with SearchResult value as string +// // Elements.Add(m.Value, elementList); +// // for every element in elementList +// // replace m.Value with SearchResult.Value.ToString() +// foreach (JValue element in elementList) +// { +// string oldValue = element.Value(); +// string newValue = oldValue.Replace(m.Value,((JValue)searchResult.Value()).ToString()); +// element.Value = element.Value.ToString().Replace(oldValue, newValue); +// } +// } +// else if (searchResult.GetType() == typeof(JArray)) +// { +// // it's a JArray +// } +// } +// else if (m.Value == "{x-data}") +// { +// // this needs to be output as a list of data +// +// } +// else +// { +// // what is this? +// // find the token in the Template array and remove it +// string nodeText = m.Groups[1].Value; +// +// // search for all nodes containing this value in any of the keys found in CheckElements +// // add to list FoundNodes +// // build a search string +// +// // Evaluates to .*\{item\.name}.* +// string innerValue = Regex.Escape("{" + nodeText + "}"); +// string regexString = $".*{innerValue}.*"; +// +// string jsonPath = $"..[?(@ =~ /{regexString}/)]"; +// +// List FoundNodes = template.SelectTokens(jsonPath).ToList(); +// +// foreach (JToken Node in FoundNodes) +// { +// // find node by path +// // this is the value of the property +// // parent is property +// // grandparent is node +// Node.Parent.Parent.Remove(); +// } +// } +// } +// +// m = m.NextMatch(); +// } \ No newline at end of file