Skip to content

Commit c09c5c6

Browse files
Florian KrönertFlorian Krönert
authored andcommitted
Implemented snippets handler
1 parent 77ea2e3 commit c09c5c6

File tree

19 files changed

+1758
-721
lines changed

19 files changed

+1758
-721
lines changed

paket.lock

Lines changed: 395 additions & 386 deletions
Large diffs are not rendered by default.

src/lib/Xrm.Oss.XTL.Interpreter/FunctionHandlers.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,98 @@ private static T CheckedCast<T>(object input, string errorMessage, bool failOnEr
884884
}
885885
};
886886

887+
private static Entity FetchSnippetByUniqueName(string uniqueName, IOrganizationService service)
888+
{
889+
var fetch = $@"<fetch no-lock=""true"">
890+
<entity name=""oss_xtlsnippet"">
891+
<attribute name=""oss_xtlexpression"" />
892+
<attribute name=""oss_containsplaintext"" />
893+
<filter operator=""and"">
894+
<condition attribute=""oss_uniquename"" operator=""eq"" value=""{uniqueName}"" />
895+
</filter>
896+
</entity>
897+
</fetch>";
898+
899+
var snippet = service.RetrieveMultiple(new FetchExpression(fetch))
900+
.Entities
901+
.FirstOrDefault();
902+
903+
return snippet;
904+
}
905+
906+
private static Entity FetchSnippet(string name, string filter, Entity primary, OrganizationConfig organizationConfig, IOrganizationService service, ITracingService tracing)
907+
{
908+
var uniqueNameSnippet = FetchSnippetByUniqueName(name, service);
909+
910+
if (uniqueNameSnippet != null)
911+
{
912+
tracing.Trace("Found snippet by unique name");
913+
return uniqueNameSnippet;
914+
}
915+
916+
if (!string.IsNullOrEmpty(filter))
917+
{
918+
tracing.Trace("Processing tokens in custom snippet filter");
919+
}
920+
921+
var fetch = $@"<fetch no-lock=""true"">
922+
<entity name=""oss_xtlsnippet"">
923+
<attribute name=""oss_xtlexpression"" />
924+
<attribute name=""oss_containsplaintext"" />
925+
<filter operator=""and"">
926+
<condition attribute=""oss_name"" operator=""eq"" value=""{name}"" />
927+
{ (!string.IsNullOrEmpty(filter) ? TokenMatcher.ProcessTokens(filter, primary, organizationConfig, service, tracing) : string.Empty) }
928+
</filter>
929+
</entity>
930+
</fetch>";
931+
932+
if (!string.IsNullOrEmpty(filter))
933+
{
934+
tracing.Trace("Done processing tokens in custom snippet filter");
935+
}
936+
937+
var snippet = service.RetrieveMultiple(new FetchExpression(fetch))
938+
.Entities
939+
.FirstOrDefault();
940+
941+
return snippet;
942+
}
943+
944+
public static FunctionHandler Snippet = (primary, service, tracing, organizationConfig, parameters) =>
945+
{
946+
if (parameters.Count < 1)
947+
{
948+
throw new InvalidPluginExecutionException("Snippet needs at least a name as first parameter and optionally a config for defining further options");
949+
}
950+
951+
var name = CheckedCast<string>(parameters[0].Value, "Name must be a string!");
952+
var config = GetConfig(parameters);
953+
954+
var filter = config?.GetValue<string>("filter", "filter must be a string containing your fetchXml filter, which may contain XTL expressions on its own");
955+
956+
var snippet = FetchSnippet(name, filter, primary, organizationConfig, service, tracing);
957+
958+
if (snippet == null)
959+
{
960+
tracing.Trace("Failed to find a snippet matching the input");
961+
return new ValueExpression(string.Empty, null);
962+
}
963+
964+
var containsPlainText = snippet.GetAttributeValue<bool>("oss_containsplaintext");
965+
var value = snippet.GetAttributeValue<string>("oss_xtlexpression");
966+
967+
// Wrap it in ${{ ... }} block
968+
var processedValue = containsPlainText ? value : $"${{{{ {value} }}}}";
969+
970+
tracing.Trace("Processing snippet tokens");
971+
972+
var result = TokenMatcher.ProcessTokens(processedValue, primary, organizationConfig, service, tracing);
973+
974+
tracing.Trace("Done processing snippet tokens");
975+
976+
return new ValueExpression(result, result);
977+
};
978+
887979
public static FunctionHandler ConvertDateTime = (primary, service, tracing, organizationConfig, parameters) =>
888980
{
889981
if (parameters.Count < 2)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using Microsoft.Xrm.Sdk;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Text.RegularExpressions;
7+
8+
namespace Xrm.Oss.XTL.Interpreter
9+
{
10+
public static class TokenMatcher
11+
{
12+
private static Regex tokenRegex = new Regex(@"\${{([\s\S]*?(?=}}))}}", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Multiline);
13+
14+
public static List<string> MatchTokens(string templateText)
15+
{
16+
var tokens = tokenRegex.Matches(templateText)
17+
.Cast<Match>()
18+
.Select(m => m.Groups[1].Value)
19+
.Distinct()
20+
.ToList();
21+
22+
return tokens;
23+
}
24+
25+
public static string ProcessTokens(string templateText, Entity dataSource, OrganizationConfig config, IOrganizationService service, ITracingService tracing)
26+
{
27+
var tokens = MatchTokens(templateText);
28+
29+
tokens.ForEach(token =>
30+
{
31+
tracing.Trace($"Processing token '{token}'");
32+
33+
var processor = new XTLInterpreter(token, dataSource, config, service, tracing);
34+
var processed = string.Empty;
35+
36+
try
37+
{
38+
processed = processor.Produce();
39+
}
40+
catch (Exception ex)
41+
{
42+
tracing.Trace($"Exception while processing token {token}, replacing by empty string. Message: {ex.Message}");
43+
}
44+
45+
templateText = templateText.Replace($"${{{{{token}}}}}", processed);
46+
tracing.Trace($"Replacing token with '{processed}'");
47+
});
48+
49+
return templateText;
50+
}
51+
}
52+
}

src/lib/Xrm.Oss.XTL.Interpreter/XTLInterpreter.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public class XTLInterpreter
5454
{ "RecordTable", FunctionHandlers.RenderRecordTable },
5555
{ "RecordUrl", FunctionHandlers.GetRecordUrl },
5656
{ "Replace", FunctionHandlers.Replace },
57+
{ "Snippet", FunctionHandlers.Snippet },
5758
{ "Sort", FunctionHandlers.Sort },
5859
{ "Static", FunctionHandlers.Static },
5960
{ "Substring", FunctionHandlers.Substring },
@@ -146,15 +147,16 @@ private List<ValueExpression> Expression(char[] terminators)
146147
if (_current == ',') {
147148
GetChar();
148149
}
149-
else if (_current == '"')
150+
else if (_current == '"' || _current == '\'')
150151
{
152+
var delimiter = _current;
151153
var stringConstant = string.Empty;
152154

153155
// Skip opening quote
154156
GetChar();
155157

156158
// Allow to escape quotes by backslashes
157-
while ((_current != '"' || _previous == '\\') && !_eof)
159+
while ((_current != delimiter || _previous == '\\') && !_eof)
158160
{
159161
stringConstant += _current;
160162
GetChar();
@@ -302,7 +304,7 @@ private ValueExpression Formula()
302304

303305
return new ValueExpression(string.Join(", ", dictionary.Select(p => $"{p.Key}: {p.Value}")), dictionary);
304306
}
305-
else if (char.IsDigit(_current) || _current == '"' || _current == '-')
307+
else if (char.IsDigit(_current) || _current == '"' || _current == '\'' || _current == '-')
306308
{
307309
// This is only called in object initializers / dictionaries. Only one value should be entered here
308310
return Expression(new[] { '}', ',' }).First();

src/lib/Xrm.Oss.XTL.Interpreter/Xrm.Oss.XTL.Interpreter.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<Compile Include="$(MSBuildThisFileDirectory)IConfig.cs" />
1616
<Compile Include="$(MSBuildThisFileDirectory)OrganizationConfig.cs" />
1717
<Compile Include="$(MSBuildThisFileDirectory)PropertyStringifier.cs" />
18+
<Compile Include="$(MSBuildThisFileDirectory)TokenMatcher.cs" />
1819
<Compile Include="$(MSBuildThisFileDirectory)ValueExpression.cs" />
1920
<Compile Include="$(MSBuildThisFileDirectory)XTLInterpreter.cs" />
2021
</ItemGroup>

src/plugin/Xrm.Oss.XTL.Templating/XTLProcessor.cs

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ private void HandleCustomAction(IPluginExecutionContext context, PersistentTraci
117117
return;
118118
}
119119

120-
var output = ProcessTemplate(templateText, dataSource, new OrganizationConfig { OrganizationUrl = config.OrganizationUrl }, service, tracing);
120+
var output = TokenMatcher.ProcessTokens(templateText, dataSource, new OrganizationConfig { OrganizationUrl = config.OrganizationUrl }, service, tracing);
121121

122122
var result = new ProcessingResult
123123
{
@@ -193,44 +193,12 @@ private void HandleNonCustomAction(IPluginExecutionContext context, ITracingServ
193193
return;
194194
}
195195

196-
var output = ProcessTemplate(templateText, dataSource, _organizationConfig, service, tracing);
196+
var output = TokenMatcher.ProcessTokens(templateText, dataSource, _organizationConfig, service, tracing);
197197

198198
target[targetField] = output;
199199
TriggerUpdateConditionally(output, dataSource, _config, service);
200200
}
201201

202-
private string ProcessTemplate(string templateText, Entity dataSource, OrganizationConfig config, IOrganizationService service, ITracingService tracing)
203-
{
204-
var tokenRegex = new Regex(@"\${{([\s\S]*?(?=}}))}}", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Multiline);
205-
206-
var tokens = tokenRegex.Matches(templateText)
207-
.Cast<Match>()
208-
.Select(m => m.Groups[1].Value)
209-
.Distinct()
210-
.ToList();
211-
212-
tokens.ForEach(token =>
213-
{
214-
tracing.Trace($"Processing token '{token}'");
215-
216-
var processor = new XTLInterpreter(token, dataSource, config, service, tracing);
217-
var processed = string.Empty;
218-
219-
try
220-
{
221-
processed = processor.Produce();
222-
}
223-
catch (Exception ex)
224-
{
225-
tracing.Trace($"Exception while processing token {token}, replacing by empty string. Message: {ex.Message}");
226-
}
227-
228-
templateText = templateText.Replace($"${{{{{token}}}}}", processed);
229-
tracing.Trace($"Replacing token with '{processed}'");
230-
});
231-
return templateText;
232-
}
233-
234202
private static string RetrieveTemplate(string template, string templateField, Entity dataSource, IOrganizationService service, ITracingService tracing)
235203
{
236204
string templateText;

src/plugin/Xrm.Oss.XTL.Templating/Xrm.Oss.XTL.Templating.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
<Import Project="..\..\lib\Xrm.Oss.XTL.Interpreter\Xrm.Oss.XTL.Interpreter.projitems" Label="Shared" />
6565
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
6666
<Choose>
67-
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5.2' Or $(TargetFrameworkVersion) == 'v4.5.3' Or $(TargetFrameworkVersion) == 'v4.6' Or $(TargetFrameworkVersion) == 'v4.6.1' Or $(TargetFrameworkVersion) == 'v4.6.2' Or $(TargetFrameworkVersion) == 'v4.6.3' Or $(TargetFrameworkVersion) == 'v4.7' Or $(TargetFrameworkVersion) == 'v4.7.1' Or $(TargetFrameworkVersion) == 'v4.7.2')">
67+
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5.2' Or $(TargetFrameworkVersion) == 'v4.5.3' Or $(TargetFrameworkVersion) == 'v4.6' Or $(TargetFrameworkVersion) == 'v4.6.1' Or $(TargetFrameworkVersion) == 'v4.6.2' Or $(TargetFrameworkVersion) == 'v4.6.3' Or $(TargetFrameworkVersion) == 'v4.7' Or $(TargetFrameworkVersion) == 'v4.7.1' Or $(TargetFrameworkVersion) == 'v4.7.2' Or $(TargetFrameworkVersion) == 'v4.8')">
6868
<ItemGroup>
6969
<Reference Include="Microsoft.Crm.Sdk.Proxy">
7070
<HintPath>..\..\..\packages\Microsoft.CrmSdk.CoreAssemblies\lib\net452\Microsoft.Crm.Sdk.Proxy.dll</HintPath>
@@ -80,7 +80,7 @@
8080
</When>
8181
</Choose>
8282
<Choose>
83-
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v3.5' Or $(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.0.3' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.1' Or $(TargetFrameworkVersion) == 'v4.5.2' Or $(TargetFrameworkVersion) == 'v4.5.3' Or $(TargetFrameworkVersion) == 'v4.6' Or $(TargetFrameworkVersion) == 'v4.6.1' Or $(TargetFrameworkVersion) == 'v4.6.2' Or $(TargetFrameworkVersion) == 'v4.6.3' Or $(TargetFrameworkVersion) == 'v4.7' Or $(TargetFrameworkVersion) == 'v4.7.1' Or $(TargetFrameworkVersion) == 'v4.7.2')">
83+
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v3.5' Or $(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.0.3' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.1' Or $(TargetFrameworkVersion) == 'v4.5.2' Or $(TargetFrameworkVersion) == 'v4.5.3' Or $(TargetFrameworkVersion) == 'v4.6' Or $(TargetFrameworkVersion) == 'v4.6.1' Or $(TargetFrameworkVersion) == 'v4.6.2' Or $(TargetFrameworkVersion) == 'v4.6.3' Or $(TargetFrameworkVersion) == 'v4.7' Or $(TargetFrameworkVersion) == 'v4.7.1' Or $(TargetFrameworkVersion) == 'v4.7.2' Or $(TargetFrameworkVersion) == 'v4.8')">
8484
<ItemGroup>
8585
<Reference Include="Microsoft.IdentityModel">
8686
<HintPath>..\..\..\packages\Microsoft.IdentityModel\lib\net35\Microsoft.IdentityModel.dll</HintPath>

0 commit comments

Comments
 (0)