Skip to content

Commit

Permalink
Add host parameter to AdaptiveCards.Templating (#8328)
Browse files Browse the repository at this point in the history
* Add host parameter to AdaptiveCards.Templating

* .sln cleanup

* Remove net6.0 TFM
  • Loading branch information
paulcam206 authored Feb 28, 2023
1 parent ee62bd3 commit 54470ff
Show file tree
Hide file tree
Showing 8 changed files with 395 additions and 63 deletions.
4 changes: 0 additions & 4 deletions source/dotnet/AdaptiveCards.sln
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveCards.Rendering.Wpf
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveCards.Rendering.Wpf.Xceed", "Library\AdaptiveCards.Rendering.Wpf.Xceed\AdaptiveCards.Rendering.Wpf.Xceed.csproj", "{4741ABEC-33B0-424F-B5F1-464EC31AEEBD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveCards.Sample.Html", "Samples\AdaptiveCards.Sample.Html\AdaptiveCards.Sample.Html.csproj", "{C015DC5F-E523-4828-8E46-118A8242B51A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveCards.Test", "Test\AdaptiveCards.Test\AdaptiveCards.Test.csproj", "{4DB2C1D1-630A-4445-95F3-4E342ABD9342}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveCards.Sample.ImageRender", "samples\AdaptiveCards.Sample.ImageRender\AdaptiveCards.Sample.ImageRender.csproj", "{BCFC1329-903B-4440-ABE1-160C22A3AE23}"
Expand All @@ -31,8 +29,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveCards.Templating",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveCards.Templating.Test", "Test\AdaptiveCards.Templating.Test\AdaptiveCards.Templating.Test.csproj", "{9E7E5953-A5B4-4867-9A06-046A754AFAC1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A751CC8F-EF13-4EA1-B9FD-611135AD7C09}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,33 @@ public string Expand(EvaluationContext context, Func<string, object> nullSubstit
return jsonTemplateString;
}

string jsonData = "";
string rootJsonData = "";
if (context?.Root != null)
{
if (context.Root is string root)
{
rootJsonData = root;
}
else
{
rootJsonData = JsonConvert.SerializeObject(context.Root);
}
}

if (context != null && context.Root != null)
string hostJsonData = "";
if (context?.Host != null)
{
if (context.Root is string)
if (context.Host is string host)
{
jsonData = context.Root as string;
hostJsonData = host;
}
else
{
jsonData = JsonConvert.SerializeObject(context.Root);
hostJsonData = JsonConvert.SerializeObject(context.Host);
}
}

AdaptiveCardsTemplateVisitor eval = new AdaptiveCardsTemplateVisitor(nullSubstitutionOption, jsonData);
AdaptiveCardsTemplateVisitor eval = new AdaptiveCardsTemplateVisitor(nullSubstitutionOption, rootJsonData, hostJsonData);
AdaptiveCardsTemplateResult result = eval.Visit(parseTree);

templateExpansionWarnings = eval.getTemplateVisitorWarnings();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>

<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<Authors>Microsoft</Authors>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public sealed class AdaptiveCardsTemplateVisitor : AdaptiveCardsTemplateParserBa
{
private Stack<DataContext> dataContext = new Stack<DataContext>();
private readonly JToken root;
private readonly JToken host;
private readonly Options options;
private ArrayList templateVisitorWarnings;

Expand All @@ -33,10 +34,12 @@ private sealed class DataContext
{
public JToken token;
public AdaptiveCardsTemplateSimpleObjectMemory AELMemory;
public bool IsArrayType = false;
public bool IsArrayType;

public JToken RootDataContext;
public JToken HostDataContext;
public const string rootKeyword = "$root";
public const string hostKeyword = "$host";
public const string dataKeyword = "$data";
public const string indexKeyword = "$index";

Expand All @@ -45,9 +48,10 @@ private sealed class DataContext
/// </summary>
/// <param name="jtoken">new data to kept as data context</param>
/// <param name="rootDataContext">root data context</param>
public DataContext(JToken jtoken, JToken rootDataContext)
/// <param name="hostDataContext">optional host data context</param>
public DataContext(JToken jtoken, JToken rootDataContext, JToken hostDataContext = null)
{
Init(jtoken, rootDataContext);
Init(jtoken, rootDataContext, hostDataContext);
}

/// <summary>
Expand All @@ -56,25 +60,30 @@ public DataContext(JToken jtoken, JToken rootDataContext)
/// <exception cref="JsonException"><c>JToken.Parse(text)</c> can throw JsonException if <paramref name="text"/> is invalid json</exception>
/// <param name="text">json in string</param>
/// <param name="rootDataContext">a root data context</param>
public DataContext(string text, JToken rootDataContext)
/// <param name="hostDataContext">optional host data context</param>
public DataContext(string text, JToken rootDataContext, JToken hostDataContext = null)
{
// disable date parsing handling
var jsonReader = new JsonTextReader(new StringReader(text)) { DateParseHandling = DateParseHandling.None };
var jtoken = JToken.Load(jsonReader);
Init(jtoken, rootDataContext);
using (var jsonReader = new JsonTextReader(new StringReader(text)) { DateParseHandling = DateParseHandling.None })
{
var jtoken = JToken.Load(jsonReader);
Init(jtoken, rootDataContext, hostDataContext);
}
}

/// <summary>
/// Initializer method that takes jtoken and root data context to initialize a data context object
/// </summary>
/// <param name="jtoken">current data context</param>
/// <param name="rootDataContext">root data context</param>
private void Init(JToken jtoken, JToken rootDataContext)
/// <param name="hostDataContext">optional host data context</param>
private void Init(JToken jtoken, JToken rootDataContext, JToken hostDataContext)
{
AELMemory = (jtoken is JObject) ? new AdaptiveCardsTemplateSimpleObjectMemory(jtoken) : new AdaptiveCardsTemplateSimpleObjectMemory(new JObject());

token = jtoken;
RootDataContext = rootDataContext;
HostDataContext = hostDataContext;

if (jtoken is JArray)
{
Expand All @@ -83,6 +92,7 @@ private void Init(JToken jtoken, JToken rootDataContext)

AELMemory.SetValue(dataKeyword, token);
AELMemory.SetValue(rootKeyword, rootDataContext);
AELMemory.SetValue(hostKeyword, hostDataContext);
}

/// <summary>
Expand All @@ -94,7 +104,7 @@ public DataContext GetDataContextAtIndex(int index)
{
var jarray = token as JArray;
var jtokenAtIndex = jarray[index];
var dataContext = new DataContext(jtokenAtIndex, RootDataContext);
var dataContext = new DataContext(jtokenAtIndex, RootDataContext, HostDataContext);
dataContext.AELMemory.SetValue(indexKeyword, index);
return dataContext;
}
Expand All @@ -104,23 +114,41 @@ public DataContext GetDataContextAtIndex(int index)
/// a constructor for AdaptiveCardsTemplateVisitor
/// </summary>
/// <param name="nullSubstitutionOption">it will called upon when AEL finds no suitable functions registered in given AEL expression during evaluation the expression</param>
/// <param name="data">json data in string which will be set as a root data context</param>
public AdaptiveCardsTemplateVisitor(Func<string, object> nullSubstitutionOption, string data = null)
/// <param name="data">json data as string which will be set as a root data context</param>
/// <param name="hostData">json data as string which will be set as the host data context</param>
public AdaptiveCardsTemplateVisitor(Func<string, object> nullSubstitutionOption, string data = null, string hostData = null)
{
if (data?.Length != 0)
if (!String.IsNullOrEmpty(hostData))
{
// parse and save host context
try
{
using (var jsonReader = new JsonTextReader(new StringReader(hostData)) { DateParseHandling = DateParseHandling.None })
{
host = JToken.Load(jsonReader);
}
}
catch (JsonException innerException)
{
throw new AdaptiveTemplateException("Setting host data failed", innerException);
}
}

if (!String.IsNullOrEmpty(data))
{
// set data as root data context
try
{
var jsonReader = new JsonTextReader(new StringReader(data)) { DateParseHandling = DateParseHandling.None };
root = JToken.Load(jsonReader);
PushDataContext(data, root);
using (var jsonReader = new JsonTextReader(new StringReader(data)) { DateParseHandling = DateParseHandling.None })
{
root = JToken.Load(jsonReader);
PushDataContext(data, root);
}
}
catch (JsonException innerException)
{
throw new AdaptiveTemplateException("Setting root data failed with given data context", innerException);
}

}

// if null, set default option
Expand Down Expand Up @@ -148,7 +176,7 @@ private DataContext GetCurrentDataContext()
/// <param name="rootDataContext">current root data context</param>
private void PushDataContext(string stringToParse, JToken rootDataContext)
{
dataContext.Push(new DataContext(stringToParse, rootDataContext));
dataContext.Push(new DataContext(stringToParse, rootDataContext, host));
}

/// <summary>
Expand Down Expand Up @@ -177,13 +205,13 @@ private void PushTemplatedDataContext(string jpath)
{
if (value is JToken jvalue)
{
dataContext.Push(new DataContext(jvalue, parentDataContext.RootDataContext));
dataContext.Push(new DataContext(jvalue, parentDataContext.RootDataContext, parentDataContext.HostDataContext));

}
else
{
var serializedValue = JsonConvert.SerializeObject(value);
dataContext.Push(new DataContext(serializedValue, parentDataContext.RootDataContext));
dataContext.Push(new DataContext(serializedValue, parentDataContext.RootDataContext, parentDataContext.HostDataContext));
}
}
else
Expand Down Expand Up @@ -220,7 +248,7 @@ public ArrayList getTemplateVisitorWarnings()
}

/// <summary>
/// antlr runtime wil call this method when parse tree's context is <see cref="AdaptiveCardsTemplateParser.TemplateDataContext"/>
/// antlr runtime will call this method when parse tree's context is <see cref="AdaptiveCardsTemplateParser.TemplateDataContext"/>
/// <para>It is used in parsing a pair that has $data as key</para>
/// <para>It creates new data context, and set it as current memory scope</para>
/// </summary>
Expand Down Expand Up @@ -254,7 +282,7 @@ public override AdaptiveCardsTemplateResult VisitTemplateData([NotNull] Adaptive
var templateLiteral = (templateStrings[0] as AdaptiveCardsTemplateParser.TemplatedStringContext).TEMPLATELITERAL();
try
{
string templateLiteralExpression = templateLiteral.GetText();
string templateLiteralExpression = templateLiteral.GetText();
PushTemplatedDataContext(templateLiteralExpression.Substring(2, templateLiteralExpression.Length - 3));
}
catch (ArgumentNullException)
Expand All @@ -268,7 +296,7 @@ public override AdaptiveCardsTemplateResult VisitTemplateData([NotNull] Adaptive
}
}
else
// else clause handles all of the ordinary json values
// else clause handles all of the ordinary json values
{
string childJson = templateDataValueNode.GetText();
try
Expand Down Expand Up @@ -535,7 +563,7 @@ public override AdaptiveCardsTemplateResult VisitObj([NotNull] AdaptiveCardsTemp

templateVisitorWarnings.Add($"WARN: Could not evaluate {returnedResult} because it is not an expression or the " +
$"expression is invalid. The $when condition has been set to false by default.");

}
else
{
Expand Down Expand Up @@ -579,7 +607,7 @@ public override AdaptiveCardsTemplateResult VisitObj([NotNull] AdaptiveCardsTemp
PopDataContext();
}

// all existing json obj in input and repeated json obj if any have been removed
// all existing json obj in input and repeated json obj if any have been removed
if (removedCounts == repeatsCounts)
{
combinedResult.HasItBeenDropped = true;
Expand All @@ -589,7 +617,7 @@ public override AdaptiveCardsTemplateResult VisitObj([NotNull] AdaptiveCardsTemp
}

/// <summary>
/// Visitor method for <c>ITernminalNode</c>
/// Visitor method for <c>ITernminalNode</c>
/// <para>collects token as string and expand template if needed</para>
/// </summary>
/// <param name="node"></param>
Expand Down Expand Up @@ -651,7 +679,7 @@ public static string Expand(string unboundString, IMemory data, bool isTemplated
{
exp = Expression.Parse(unboundString.Substring(2, unboundString.Length - 3));
}
// AEL can throw any errors, for example, System.Data.Syntax error will be thrown from AEL's ANTLR
// AEL can throw any errors, for example, System.Data.Syntax error will be thrown from AEL's ANTLR
// when AEL encounters unknown functions.
// We can't possibly know all errors and we simply want to leave the expression as it is when there are any exceptions
#pragma warning disable CA1031 // Do not catch general exception types
Expand Down Expand Up @@ -701,9 +729,9 @@ public override AdaptiveCardsTemplateResult VisitTemplateWhen([NotNull] Adaptive
{
throw new ArgumentNullException(nameof(context));
}
// when this node is visited, the children of this node is shown as below:
// when this node is visited, the children of this node is shown as below:
// this node is visited only when parsing was correctly done
// [ '{', '$when', ':', ',', 'expression']
// [ '{', '$when', ':', ',', 'expression']
var result = Visit(context.templateExpression());

if (!result.IsWhen)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public sealed class EvaluationContext
/// ""person"": {
/// ""firstName"": ""Hello"",
/// ""lastName"": ""World""
/// }
/// }";
///
/// var context = new EvaluationContext()
Expand All @@ -29,22 +30,56 @@ public sealed class EvaluationContext
public object Root
{ get; set; }

/// <summary>
/// Provides Host Data Context
/// </summary>
/// <remarks>
/// Typically this is supplied by the host application providing additional context for template binding. For example, the host might supply language or theming information that the template can use for layout.
/// </remarks>
/// <example>
/// <code>
///
/// string jsonData = @"{
/// ""person"": {
/// ""firstName"": ""Hello"",
/// ""lastName"": ""World""
/// }
/// }";
///
/// string hostData = @"{
/// ""applicationName"": ""Contoso AdaptiveCards Host",
/// ""platform"": ""mobile""
/// }";
///
/// var context = new EvaluationContext()
/// {
/// Root = jsonData,
/// Host = hostData
/// };
///
/// </code>
/// </example>
public object Host
{ get; set; }

/// <summary>
/// default consturctor
/// </summary>
public EvaluationContext()
{
Root = null;
Host = null;
}

/// <summary>
/// constructor for <c>EvaluationContext</c> that takes one argument that will be used for root data context
/// constructor for <c>EvaluationContext</c> that takes one required argument used for root data context and one optional argument supplying host data
/// </summary>
/// <param name="rootData"></param>
public EvaluationContext(object rootData)
/// <param name="rootData">Data to use while binding</param>
/// <param name="hostData">Data supplied by the host for use while binding</param>
public EvaluationContext(object rootData, object hostData = null)
{
Root = rootData;
Host = hostData;
}

}
}
Loading

0 comments on commit 54470ff

Please sign in to comment.