/// <summary> /// Parse the template, and capture paths used in the template to determine a suitable structure for the required model. /// </summary> /// <param name="templateSource">The template content to parse.</param> /// <param name="disableContentEscaping">In some cases, content should not be escaped (such as when rendering text bodies and subjects in emails). /// By default, we use content escaping, but this parameter allows it to be disabled.</param> /// <returns></returns> public static ExtendedParseInformation ParseWithModelInference(string templateSource, bool disableContentEscaping = false) { var tokens = new Queue<TokenPair>(Tokenizer.Tokenize(templateSource)); var options = new ParsingOptions { DisableContentSafety = disableContentEscaping }; var inferredModel = new InferredTemplateModel(); var internalTemplate = Parse(tokens, options, inferredModel); Func<IDictionary<String, object>, String> template = (model) => { var retval = new StringBuilder(); var context = new ContextObject() { Value = model, Key = "" }; internalTemplate(retval, context); return retval.ToString(); }; var result = new ExtendedParseInformation() { InferredModel = inferredModel, ParsedTemplate = template }; return result; }
private static Action <StringBuilder, ContextObject> HandleCollectionOpen(TokenPair token, Queue <TokenPair> remainder, ParsingOptions options, InferredTemplateModel scope) { if (scope != null) { scope = scope.GetInferredModelForPath(token.Value, InferredTemplateModel.UsedAs.Collection); } var innerTemplate = Parse(remainder, options, scope); return((builder, context) => { //if we're in the same scope, just negating, then we want to use the same object var c = context.GetContextForPath(token.Value); //"falsey" values by Javascript standards... if (!c.Exists()) { return; } IEnumerable cVal = null; if (c.Value is IEnumerable && !(c.Value is String) && !(c.Value is IDictionary <string, object>)) { cVal = c.Value as IEnumerable; } else { //Ok, this is a scalar value or an Object. So lets box it into an IEnumerable cVal = new ArrayList() { c.Value }; } var index = 0; foreach (object i in cVal) { var innerContext = new ContextObject() { Value = i, Key = String.Format("[{0}]", index), Parent = c }; innerTemplate(builder, innerContext); index++; } }); }
/// <summary> /// Parse the template content, producing a function that can be used to apply variables to the template. /// The provided function can be reused (i.e. no state will "leak" from one application of the function to the next). /// </summary> /// <param name="template">The content of the template to be parsed.</param> /// <param name="disableContentEscaping">In some cases, content should not be escaped (such as when rendering text bodies and subjects in emails). /// By default, we use content escaping, but this parameter allows it to be disabled.</param> /// <returns></returns> public static Func<IDictionary<String, object>, String> Parse(string template, bool disableContentEscaping = false) { var tokens = new Queue<TokenPair>(Tokenizer.Tokenize(template)); var internalTemplate = Parse(tokens, new ParsingOptions { DisableContentSafety = disableContentEscaping }); return (model) => { var retval = new StringBuilder(); var context = new ContextObject() { Value = model, Key = "" }; internalTemplate(retval, context); return retval.ToString(); }; }
/// <summary> /// Parse the template content, producing a function that can be used to apply variables to the template. /// The provided function can be reused (i.e. no state will "leak" from one application of the function to the next). /// </summary> /// <param name="template">The content of the template to be parsed.</param> /// <param name="options">Options for configuring/extending the parser.</param> /// <returns></returns> public static Func <IDictionary <String, object>, String> Parse(string template, ParsingOptions options) { var tokensQueue = GetTokensQueue(template, options); var internalTemplate = Parse(tokensQueue, options); return((model) => { var retval = new StringBuilder(); var context = new ContextObject() { Value = model, Key = "" }; internalTemplate(retval, context); return retval.ToString(); }); }
/// <summary> /// Parse the template content, producing a function that can be used to apply variables to the template. /// The provided function can be reused (i.e. no state will "leak" from one application of the function to the next). /// </summary> /// <param name="template">The content of the template to be parsed.</param> /// <param name="disableContentEscaping">In some cases, content should not be escaped (such as when rendering text bodies and subjects in emails). /// By default, we use content escaping, but this parameter allows it to be disabled.</param> /// <returns></returns> public static Func <IDictionary <String, object>, String> Parse(string template, bool disableContentEscaping = false) { var tokens = new Queue <TokenPair> (Tokenizer.Tokenize(template)); var internalTemplate = Parse(tokens, new ParsingOptions { DisableContentSafety = disableContentEscaping }); return((model) => { var retval = new StringBuilder(); var context = new ContextObject() { Value = model, Key = "" }; internalTemplate(retval, context); return retval.ToString(); }); }
private static Action <StringBuilder, ContextObject> HandleCollectionOpen(TokenPair token, Queue <TokenPair> remainder, ParsingOptions options, InferredTemplateModel scope) { if (scope != null) { scope = scope.GetInferredModelForPath(token.Value, InferredTemplateModel.UsedAs.Collection); } var innerTemplate = Parse(remainder, options, scope); return((builder, context) => { //if we're in the same scope, just negating, then we want to use the same object var c = context.GetContextForPath(token.Value); //"falsey" values by Javascript standards... if (!c.Exists()) { return; } if (c.Value is IEnumerable && !(c.Value is String) && !(c.Value is IDictionary <string, object>)) { var index = 0; foreach (object i in c.Value as IEnumerable) { var innerContext = new ContextObject() { Value = i, Key = String.Format("[{0}]", index), Parent = c }; innerTemplate(builder, innerContext); index++; } } else { throw new IndexedParseException("'{0}' is used like an array by the template, but is a scalar value or object in your model.", token.Value); } }); }
private ContextObject GetContextForPath(Queue <String> elements) { var retval = this; if (elements.Any()) { var element = elements.Dequeue(); if (element.StartsWith("..")) { if (Parent != null) { retval = Parent.GetContextForPath(elements); } else { //calling "../" too much may be "ok" in that if we're at root, //we may just stop recursion and traverse down the path. retval = GetContextForPath(elements); } } //TODO: handle array accessors and maybe "special" keys. else { //ALWAYS return the context, even if the value is null. var innerContext = new ContextObject(); innerContext.Key = element; innerContext.Parent = this; var ctx = this.Value as IDictionary <string, object>; if (ctx != null) { object o; ctx.TryGetValue(element, out o); innerContext.Value = o; } retval = innerContext.GetContextForPath(elements); } } return(retval); }
private static Action<StringBuilder, ContextObject> HandleCollectionOpen(TokenPair token, Queue<TokenPair> remainder, ParsingOptions options, InferredTemplateModel scope) { if (scope != null) { scope = scope.GetInferredModelForPath(token.Value, InferredTemplateModel.UsedAs.Collection); } var innerTemplate = Parse(remainder, options, scope); return (builder, context) => { //if we're in the same scope, just negating, then we want to use the same object var c = context.GetContextForPath(token.Value); //"falsey" values by Javascript standards... if (!c.Exists()) return; if (c.Value is IEnumerable && !(c.Value is String) && !(c.Value is IDictionary<string, object>)) { var index = 0; foreach (object i in c.Value as IEnumerable) { var innerContext = new ContextObject() { Value = i, Key = String.Format("[{0}]", index), Parent = c }; innerTemplate(builder, innerContext); index++; } } else { throw new IndexedParseException("'{0}' is used like an array by the template, but is a scalar value or object in your model.", token.Value); } }; }
private ContextObject GetContextForPath(Queue<String> elements) { var retval = this; if (elements.Any()) { var element = elements.Dequeue(); if (element.StartsWith("..")) { if (Parent != null) { retval = Parent.GetContextForPath(elements); } else { //calling "../" too much may be "ok" in that if we're at root, //we may just stop recursion and traverse down the path. retval = GetContextForPath(elements); } } //TODO: handle array accessors and maybe "special" keys. else { //ALWAYS return the context, even if the value is null. var innerContext = new ContextObject(); innerContext.Key = element; innerContext.Parent = this; var ctx = this.Value as IDictionary<string, object>; if (ctx != null) { object o; ctx.TryGetValue(element, out o); innerContext.Value = o; } retval = innerContext.GetContextForPath(elements); } } return retval; }