Nearly all business solutions require sending a notification, one way or another. Microsoft Dynamics easily provides this functionality for emails: ‘send email’ workflow step or in a written code. Going a step further, when the business requires more dynamic text to be filled out (e.g. a customer’s name in the email greeting), the user can insert placeholders into the text to be filled by the platform’s dynamic text parser before sending the email.
The out-of-the-box functionality is great for simpler scenarios; however, when asked to add conditions to the text, simplify localisation, retrieve an updated value from a web service, retrieve a value in a deeply nested table, or simply format a date in a way that fits the context more, it becomes a bit harder to achieve, that might require a complex solution for such small ask.
Dynamic Text Parser
To resolve this, I created a parser that overcomes many challenges in the context of Dynamics 365 dynamic text.
I will go through the ‘expressions’ that are supported by this parser, after some terminologies and giving an overview of how it works.
Improvement requests and suggestions are more than welcome, of course. Please open a ticket on GitHub and let’s start a discussion.
Basic Structure
The general structure is text with code placed between curly braces ({ }
).
The code itself is basically a chain of expressions (pipeline). The initial value is an object reference set by the user, and then functions or operations are done on said object.
Objects can be referenced at any point to replace the current pipeline value.
Quick Sample
Features
- Operations:
+
,-
,*
,/
,==
,<=
,??
,?:
,&&
,||
… etc. - CRM queries: retrieve row, FetchXML, actions
- Traversal: columns and relationships
- Collection functions: sum, min, max, distinct, map, count, filter, … etc.
- Date functions: add days, months, … etc.; output in a custom format … etc.
- String functions
- Functions: length, sub-string, trim, pad, title case, format numbers, encode HTML … etc.
- Regex: functions to target part of the input text only
- Memory: supports variables (store and load of values)
Installation and Usage
The link below contains the full manual of the solution; however, I will just list some of the notable features here as a showcase.
The YS Common solution is required for the configuration entities. It can be skipped if the dictionary
or configuration
constructs are not needed.
Install either Yagasoft.Libraries.Common
(DLL installed) or Yagasoft.Libraries.Common.File
(the parser class itself is embedded in the project itself) NuGet package, and then reference the CrmParser
class.
https://github.com/yagasoft/Dynamics365-YsCommonSolution
https://www.nuget.org/packages/Yagasoft.Libraries.Common/
https://www.nuget.org/packages/Yagasoft.Libraries.Common.File/
Manual: https://github.com/yagasoft/Dynamics365-CrmTextParser
Testing Tool
All of the functionality of this parser can be tested using an XrmToolBox plugin:
https://www.xrmtoolbox.com/plugins/D365-CrmTextParser-Tester-Plugin
Core Algorithm
Interpretation
- The parser starts by interpreting the input text given.
- Text is converted into a list of “tokens”.
- Tokens are then processed into an expression tree to determine the execution order.
Evaluation
- The tree is traversed in order.
- Each node is evaluated (along with its children) and the result is passed on to the next node on the same level.
- Some nodes do their work without output; they simply pass on their input value to the next node.
By default, all entries are merged into a single output string, unless an ‘aggregate’ is used.
Expressions
The parser supports evaluating operations. Below are the rules in order of precedence. The higher entries in the table are evaluated first unless wrapped in parenthesis.
Operators can be used in a block anywhere, except between ticks (`
).
Pattern | Description | ||||||||||||
*,/ | Multiply and divide. | ||||||||||||
+,- | Add and subtract.
Can be a number or date. If date, it must be in the following form: The value is a number. The unit is one of the following:
|
||||||||||||
<,>,<=,>= | Greater and less than. | ||||||||||||
==,!= | Equality. | ||||||||||||
&& | And. | ||||||||||||
|| | Or. | ||||||||||||
?? | If the left side is empty, take the right side. | ||||||||||||
?: | Ternary conditional: <predicate>?<true-clause>:<false-clause> .
E.g., |
Data retrieval and traversal
Column
The most important of all expressions. Returns a column value. Supports traversing using dot (properties and columns) and sharp (relationships) operators.
1
|
{@this.fullname} |
1
|
{@this.ownerid#ys_servicerequests_OwnerId} |
In this example, we are getting the record of the owner and then his assigned requests.
Fetch
Retrieves from CRM using FetchXML.
In this example, we are defining a query that will retrieve any account.
Arrays
Distinct
Filter down to only unique records.
In this example, we are retrieving related accounts and then making them distinct over the account type.
Map
Maps an array to a value based on expressions evaluation.
1
|
{[`x`,`yy`,`zzz`]$map($length)} |
In this example, we are defining an array, and then replacing each value with its length.
Strings
Truncate
Shortens the output to the specified length, and then append the given replacement.
{@this.ys_description$trunc(6,`...`)} |
In this example, given description
is This is a test.
, the output is This i...
.
Title
Converts the text to the title-case. Supports regex to target a sub-string.
{@this.ys_description$title} |
In this example, given description
is this is a test.
, the output is This is a Test
.
Sum
Returns the sum of all items.
{@this#accounts$map(.ys_taxpercent)$sum} |
In this example, if the system has 3 accounts stored with the tax percentages: 15, 10, and 20; the output will be 45
.
Conclusion
There are a lot more expressions. Please refer to the manual linked above for the full guide.
Table of Contents