Using field codes to replace values in content, templates, and reports allows users and designers a great deal of flexibility for creating and managing content and templates without requiring code changes.
This article is a summary of how to use field codes in content, templates, or reports and how to convert them to object property values and format and replace them when displayed or rendered. This is based on a similar pattern that Microsoft Word uses when formatting fields. It’s also commonly used in WordPress and WordPress themes.
Field Code Syntax
The most important aspect of the design is picking a syntax that can be easily identified within the content and doesn’t conflict with true content. Braces { } identify the field marker within the content and are easy to parse. The symbols $, %, < >, ( ) or [ ] could be used for this pattern as well.
The field marker contains two comma separated values, the named type separated by one or more periods and the format string. Quotes around each token make it easier to identify the tokens within the field marker and to not mistake the end of the format string for the end of the field marker.
{“object.expression” [,“format”] }
The brackets denote the format string is optional.
- Field marker: {“object”, “expression”, “format”}
- Object: A valid object name within the context used for binding
- Expression: “property” – must be a valid property expression
- Format string (C# string.format) “{0:yyy-mm-dd}” – optional
Depending on the application needs, a default type can be used and only the properties can be used for the type token, or multiple types can be used.
ASP.Net 2.0 uses a similar pattern for formatting data binding:
<%# Eval("expression"[, "format"]) %> <%# Eval("Price", "{0:C}") %> <%# Eval("Price", "Special Offer {0:C} for Today Only!") %>
Field Code Examples
- Summary: {"Summary"}
- Keywords: {“Item.Keywords"}
- Link: {“Item.Link"}
- Create Time: {“Item.CreateTime", "{0:yyyy-MM-dd HH:mm tt (zzz)}"}
- Create TimeUtc: {“Item.CreateTimeUtc", "{0:yyyy-MM-dd HH:mm tt}"}
- ViewCount: {“Item.ViewCount", "{0:###,###.##}"}
Matching Regular Expressions
Two regular expressions are used to parse the content or template. The first one to get the entire field marker (FieldRegex), the second one to parse individual tokens within the field type (TokenRegex: “type.property”).
The following example parses through the Body string and replaces the field marker with the appropriate formatted string.
private static readonly Regex FieldRegex = new Regex(@"\{\"".*?\""\}", RegexOptions.IgnoreCase);
private static readonly Regex TokenRegex = new Regex(@"\"".*?\""", RegexOptions.IgnoreCase);
private static readonly char[] charTrimSeparators = new char[] { '"', ' ' };
private static readonly char[] charKeySeparators = new char[] { '.', ' ' };
MatchCollection myMatches = FieldRegex.Matches(Body);
foreach (Match myMatch in myMatches)
{
string matchFieldSet = myMatch.ToString();
MatchCollection tokenMatches = TokenRegex.Matches(matchFieldSet);
string keyAll = tokenMatches[0].ToString().Trim(charTrimSeparators);
object obj = GetObj(item, keyAll);
string value = String.Empty;
if (obj != null)
{
switch (tokenMatches.Count)
{
case 2:
string format = tokenMatches[1].ToString().Trim(charTrimSeparators);
value = String.Format(format, obj);
break;
case 1:
value = obj.ToString();
break;
}
}
if (!String.IsNullOrEmpty(value))
{
Body = Body.Replace(matchFieldSet, value);
}
}
Using Reflection To Get Types And Properties
The tricky part is to use reflection to get the property in the field marker and find it in the object.
using System.Reflection;
private object GetObj(Sometype item, string keyAll)
{
object obj = null;
Type type = null;
PropertyInfo property = null;
string[] keyTokens = keyAll.Split(charKeySeparators, StringSplitOptions.RemoveEmptyEntries);
switch (keyTokens.Length)
{
case 1:
key = keyTokens[0];
parent = "Sometype";
break;
default:
key = keyTokens[1];
break;
}
type = post.GetType();
property = type.GetProperty(key);
if (property != null)
{
obj = property.GetValue(item, null);
}
return obj;
}
This reflection pattern works good for “Object.Property”. However, it becomes significantly more difficult for multiple levels of objects, i.e. “Object.Object.Object.Property”. If anyone has suggestions on how to resolve that without hand parsing each object, please let me know.
Performance Implications
Using reflection and the String.Replace methods can add significant processing time if there are numerous values to find and replace. Building a dictionary of the names / values that are found would in theory be faster than using reflection, but I’ve not measured this direct comparison. I have seen the reflection using about 10 ms (on a core 2 duo 2.5 Ghz) to retrieve and replace one property.
If a Dictionary is used, then converting the content to a StringBuilder and using StringBuilder.Replace instead of String.Replace should also improve performance.
Once the content is parsed and the field tokens are replaced, caching the replaced content is definitely recommended where possible.
References
- .Net Custom Numeric Format Strings
- .Net Custom DateTime Format Strings
- Regular Expression Based Token Replacement in ASP.NET
- Token Replacement in ASP.NET
- Template Tag Shortcodes: WordPress Plugin
- Vine Type Reference Variables
- WordPress Template Tags
- WordPress Theme Tags
- Simplified and Extended Data Binding Syntax in ASP.NET 2.0