I could've sworn I saw som开发者_运维百科e articles a while ago about imperfect but useful string interpolation methods for C, but no such luck right now. However, there's Razor, which does more or less what I want.
Suppose you have a database client application with tickets, and e-mail notifications are to be sent whenever tickets get created, significant parameters change, etc. The user would like to customize the wording of those notification e-mails, which would be easiest using string interpolation, i.e. accessing various properties of the ticket
object from within the string, like so:
Dear
@user
,the ticket
@ticket.ID
(@ticket.URL
) has changed in priority from@previousTicket.priority
to@currentTicket.priority
.
What I'd like is a method that I pass various objects (in this case user
, oldTicket
and ticket
), and have it evaluate the string and get the necessary properties through reflection.
You can use a simple replacement step to achieve a simple keyword replacement functionality.
Just replace your keywords with {0}
, {1}
, etc and use string.Format
with the right parameter in the right place.
Dictionary<string, int> keywords = new Dictionary<string, int>();
keywords["@user"] = 0;
keywords["@ticket.ID"] = 1;
keywords["@ticket.URL"] = 2;
// etc...
string template = @"Dear @user,
the ticket @ticket.ID (@ticket.URL) has changed in priority from @previousTicket.priority to @currentTicket.priority.";
string replacedTemplate = template;
foreach (var keyword in keywords)
{
replacedTemplate = replacedTemplate.Replace(keyword.Key, "{" + keyword.Value + "}");
}
string formattedMessage = string.Format(replacedTemplate, userName, ticket.ID, ticket.URL); // corresponding to the dictionary
This assumes that you have a well defined and limited amount of keywords.
While I'm sure there's many engines out there that do this, we settled on Castle NVelocity, and it does it really well.
http://www.castleproject.org/others/nvelocity/usingit.html
It accepts the data via name/value pairs, and runs it through a template. It can be used for generating all kinds of textual output in memory. It supports includes, conditional sections, and also repeating data (eg. lines on an order).
Most importantly, it's damn easy to use.
I hadn't seen the two answers, so I went ahead and did my own implementation:
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace StringInterpolation {
/// <summary>
/// An object with an explicit, available-at-runtime name.
/// </summary>
public struct NamedObject {
public string Name;
public object Object;
public NamedObject(string name, object obj) {
Name = name;
Object = obj;
}
}
public static class StringInterpolation {
/// <summary>
/// Parses a string for basic Razor-like interpolation with explicitly passed objects.
/// For example, pass a NamedObject user, and you can use @user and @user.SomeProperty in your string.
/// </summary>
/// <param name="s">The string to be parsed.</param>
/// <param name="objects">A NamedObject array for objects too allow for parsing.</param>
public static string Interpolate(this string s, params NamedObject[] objects) {
System.Diagnostics.Debug.WriteLine(s);
List<NamedObject> namedObjects = new List<NamedObject>(objects);
Dictionary<NamedObject, Dictionary<string, string>> objectsWithProperties = new Dictionary<NamedObject, Dictionary<string, string>>();
foreach (NamedObject no in objects) {
Dictionary<string, string> properties = new Dictionary<string, string>();
foreach (System.Reflection.PropertyInfo pInfo in no.Object.GetType().GetProperties())
properties.Add(pInfo.Name, pInfo.GetValue(no.Object, new object[] { }).ToString());
objectsWithProperties.Add(no, properties);
}
foreach (Match match in Regex.Matches(s, @"@(\w+)(\.(\w+))?")) {
NamedObject no;
no = namedObjects.Find(delegate(NamedObject n) { return n.Name == match.Groups[1].Value; });
if (no.Name != null && match.Groups.Count == 4)
if (string.IsNullOrEmpty(match.Groups[3].Value))
s = s.Replace(match.Value, no.Object.ToString());
else {
Dictionary<string, string> properties = null;
string value;
objectsWithProperties.TryGetValue(no, out properties);
if (properties != null && properties.TryGetValue(match.Groups[3].Value, out value))
s = s.Replace(match.Value, value);
}
}
return s;
}
}
}
And here's a test:
using StringInterpolation;
namespace StringInterpolationTest {
class User {
public string Name { get; set; }
}
class Ticket {
public string ID { get; set; }
public string Priority { get; set; }
}
class Program {
static void Main(string[] args) {
User user = new User();
user.Name = "Joe";
Ticket previousTicket = new Ticket();
previousTicket.ID = "1";
previousTicket.Priority = "Low";
Ticket currentTicket = new Ticket();
currentTicket.ID = "1";
currentTicket.Priority = "High";
System.Diagnostics.Debug.WriteLine("User: @user, Username: @user.Name, Previous ticket priority: @previousTicket.Priority, New priority: @currentTicket.Priority".Interpolate(
new NamedObject("user", user),
new NamedObject("previousTicket", previousTicket),
new NamedObject("currentTicket", currentTicket)
));
}
}
}
It's quite a bit more code than Albin's variant, but doesn't require manually setting up IDs (though it still needs you to know ahead of time which objects to 'export' for potential interpolation).
Thanks guys!
See also: http://nuget.org/List/Packages/Expansive
精彩评论