public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken cancel) { var strFragment = (PageStringFragment)fragment.Body[0]; if (!fragment.Argument.IsNullOrWhiteSpace()) { var literal = fragment.Argument.AdvancePastWhitespace(); bool appendTo = false; if (literal.StartsWith("appendTo ")) { appendTo = true; literal = literal.Advance("appendTo ".Length); } literal = literal.ParseVarName(out var name); var nameString = name.Value; if (appendTo && scope.PageResult.Args.TryGetValue(nameString, out var oVar) && oVar is string existingString) { scope.PageResult.Args[nameString] = existingString + strFragment.Value.Value; return; } scope.PageResult.Args[nameString] = strFragment.Value.Value; } else { await scope.OutputStream.WriteAsync(strFragment.Value, cancel); } }
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment block, CancellationToken token) { var argValue = block.Argument.GetJsExpressionAndEvaluate(scope); var args = argValue as Dictionary <string, object> ?? new Dictionary <string, object>(); var format = scope.Context.PageFormats.First().Extension; if (args.TryGetValue("format", out var oFormat)) { format = oFormat.ToString(); args.Remove("format"); } var unrenderedBody = new TemplatePartialPage(scope.Context, "evalSafe-page", block.Body, format, args); using (var ms = MemoryStreamFactory.GetStream()) { var captureScope = scope.ScopeWith(outputStream: ms, scopedParams: args); await scope.PageResult.WritePageAsync(unrenderedBody, captureScope, token); var renderedBody = await ms.ReadToEndAsync(); var context = new TemplateContext().Init(); var pageResult = new PageResult(context.OneTimePage(renderedBody)) { Args = args, }; await pageResult.WriteToAsync(scope.OutputStream, token); } }
public override Task WriteAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken cancel) { var literal = fragment.Argument.ParseVarName(out var name); if (name.IsNullOrEmpty()) { throw new NotSupportedException("'partial' block is missing name of partial"); } literal = literal.AdvancePastWhitespace(); var argValue = literal.GetJsExpressionAndEvaluate(scope); var args = argValue as Dictionary <string, object>; if (argValue != null && args == null) { throw new NotSupportedException("Any 'partial' argument must be an Object Dictionary"); } var format = scope.Context.PageFormats.First().Extension; if (args != null && args.TryGetValue("format", out var oFormat)) { format = oFormat.ToString(); args.Remove("format"); } var nameString = name.Value; var partial = new TemplatePartialPage(scope.Context, nameString, fragment.Body, format, args); scope.PageResult.Partials[nameString] = partial; return(TypeConstants.EmptyTask); }
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken cancel) { if (fragment.Argument.IsNullOrWhiteSpace()) { throw new NotSupportedException("'capture' block is missing name of variable to assign captured output to"); } var literal = fragment.Argument.AdvancePastWhitespace(); bool appendTo = false; if (literal.StartsWith("appendTo ")) { appendTo = true; literal = literal.Advance("appendTo ".Length); } literal = literal.ParseVarName(out var name); if (name.IsNullOrEmpty()) { throw new NotSupportedException("'capture' block is missing name of variable to assign captured output to"); } literal = literal.AdvancePastWhitespace(); var argValue = literal.GetJsExpressionAndEvaluate(scope); var scopeArgs = argValue as Dictionary <string, object>; if (argValue != null && scopeArgs == null) { throw new NotSupportedException("Any 'capture' argument must be an Object Dictionary"); } var ms = MemoryStreamFactory.GetStream(); var useScope = scope.ScopeWith(scopeArgs, ms); await WriteBodyAsync(useScope, fragment, cancel); var capturedOutput = ms.ReadToEnd(); var nameString = name.Value; if (appendTo && scope.PageResult.Args.TryGetValue(nameString, out var oVar) && oVar is string existingString) { scope.PageResult.Args[nameString] = existingString + capturedOutput; return; } scope.PageResult.Args[nameString] = capturedOutput; }
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment block, CancellationToken token) { var strFragment = (PageStringFragment)block.Body[0]; if (!block.Argument.IsNullOrWhiteSpace()) { Capture(scope, block, strFragment); } else { await scope.OutputStream.WriteAsync(strFragment.Value.Span, token); } }
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment block, CancellationToken token) { var result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope, ifNone : () => throw new NotSupportedException("'if' block does not have a valid expression")); if (result) { await WriteBodyAsync(scope, block, token); } else { await WriteElseAsync(scope, block.ElseBlocks, token); } }
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken cancel) { if (string.IsNullOrEmpty(fragment.ArgumentString)) { throw new NotSupportedException("'each' block requires the collection to iterate"); } var cache = (EachArg)scope.Context.Cache.GetOrAdd(fragment.ArgumentString, _ => ParseArgument(scope, fragment)); IEnumerable collection = (IEnumerable)await cache.Source.EvaluateAsync(scope); var index = 0; var whereIndex = 0; if (collection != null) { foreach (var element in collection) { // Add all properties into scope if called without explicit in argument var scopeArgs = !cache.HasExplicitBinding && CanExportScopeArgs(element) ? element.ToObjectDictionary() : new Dictionary <string, object>(); scopeArgs[cache.Binding] = element; scopeArgs[nameof(index)] = AssertWithinMaxQuota(whereIndex++); var itemScope = scope.ScopeWithParams(scopeArgs); if (cache.Where != null) { var result = await cache.Where.EvaluateToBoolAsync(itemScope); if (!result) { continue; } } itemScope.ScopedParams[nameof(index)] = AssertWithinMaxQuota(index++); await WriteBodyAsync(itemScope, fragment, cancel); } } if (index == 0) { await WriteElseBlocks(scope, fragment.ElseBlocks, cancel); } }
EachArg ParseArgument(TemplateScopeContext scope, PageBlockFragment fragment) { var literal = fragment.Argument.ParseJsExpression(out var token); if (token == null) { throw new NotSupportedException("'each' block requires the collection to iterate"); } var binding = "it"; literal = literal.AdvancePastWhitespace(); JsToken source, where = null; var hasExplicitBinding = literal.StartsWith("in "); if (hasExplicitBinding) { if (!(token is JsIdentifier identifier)) { throw new NotSupportedException($"'each' block expected identifier but was {token.DebugToken()}"); } binding = identifier.NameString; literal = literal.Advance(3); literal = literal.ParseJsExpression(out source); if (source == null) { throw new NotSupportedException("'each' block requires the collection to iterate"); } } else { source = token; } if (literal.StartsWith("where ")) { literal = literal.Advance(6); literal = literal.ParseJsExpression(out where); } return(new EachArg(binding, hasExplicitBinding, source, where)); }
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken cancel) { var result = await fragment.Argument.GetJsExpressionAndEvaluateAsync(scope, ifNone : () => throw new NotSupportedException("'with' block does not have a valid expression")); if (result != null) { var resultAsMap = result.ToObjectDictionary(); var withScope = scope.ScopeWithParams(resultAsMap); await WriteBodyAsync(withScope, fragment, cancel); } else { await WriteElseBlocks(scope, fragment.ElseBlocks, cancel); } }
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken cancel) { var(name, scopeArgs, appendTo) = Parse(scope, fragment); var ms = MemoryStreamFactory.GetStream(); var useScope = scope.ScopeWith(scopeArgs, ms); await WriteBodyAsync(useScope, fragment, cancel); var capturedOutput = ms.ReadToEnd(); if (appendTo && scope.PageResult.Args.TryGetValue(name, out var oVar) && oVar is string existingString) { scope.PageResult.Args[name] = existingString + capturedOutput; return; } scope.PageResult.Args[name] = capturedOutput; }
private static void Capture(TemplateScopeContext scope, PageBlockFragment block, PageStringFragment strFragment) { var literal = block.Argument.Span.AdvancePastWhitespace(); bool appendTo = false; if (literal.StartsWith("appendTo ")) { appendTo = true; literal = literal.Advance("appendTo ".Length); } literal = literal.ParseVarName(out var name); var nameString = name.Value(); if (appendTo && scope.PageResult.Args.TryGetValue(nameString, out var oVar) && oVar is string existingString) { scope.PageResult.Args[nameString] = existingString + strFragment.Value; return; } scope.PageResult.Args[nameString] = strFragment.Value.ToString(); }
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment block, CancellationToken token) { var argValue = block.Argument.GetJsExpressionAndEvaluate(scope); var args = argValue as Dictionary <string, object> ?? new Dictionary <string, object>(); var format = scope.Context.PageFormats.First().Extension; if (args.TryGetValue(TemplateConstants.Format, out var oFormat)) { format = oFormat.ToString(); args.Remove(TemplateConstants.Format); } var htmlDecode = false; if (args.TryGetValue(nameof(htmlDecode), out var oHtmlDecode) && oHtmlDecode is bool b) { htmlDecode = b; args.Remove(nameof(htmlDecode)); } var context = new TemplateContext(); if (args.TryGetValue("use", out var oUse)) { var use = (Dictionary <string, object>)oUse; if (use.TryGetValue("context", out var oContext) && oContext is bool useContext && useContext) { context = scope.Context; } else { // Use same ThreadSafe plugin instance to preserve configuration var plugins = use.TryGetValue("plugins", out var oPlugins) ? ToStrings("plugins", oPlugins) : null; if (plugins != null) { foreach (var name in plugins) { var plugin = scope.Context.Plugins.FirstOrDefault(x => x.GetType().Name == name); if (plugin == null) { throw new NotSupportedException($"Plugin '{name}' is not registered in parent context"); } context.Plugins.Add(plugin); } } // Use new filter and block instances which cannot be shared between contexts var filters = use.TryGetValue("filters", out var oFilters) ? ToStrings("filters", oFilters) : null; if (filters != null) { foreach (var name in filters) { var filter = scope.Context.TemplateFilters.FirstOrDefault(x => x.GetType().Name == name); if (filter == null) { throw new NotSupportedException($"Filter '{name}' is not registered in parent context"); } context.TemplateFilters.Add(filter.GetType().CreateInstance <TemplateFilter>()); } } var blocks = use.TryGetValue("blocks", out var oBlocks) ? ToStrings("blocks", oBlocks) : null; if (blocks != null) { foreach (var name in blocks) { var useBlock = scope.Context.TemplateBlocks.FirstOrDefault(x => x.GetType().Name == name); if (useBlock == null) { throw new NotSupportedException($"Block '{name}' is not registered in parent context"); } context.TemplateBlocks.Add(useBlock.GetType().CreateInstance <TemplateBlock>()); } } } args.Remove(nameof(use)); }
protected virtual async Task WriteBodyAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken token) { await WriteAsync(scope, fragment.Body, GetCallTrace(fragment), token); }
public abstract Task WriteAsync(TemplateScopeContext scope, PageBlockFragment block, CancellationToken token);
protected virtual string GetCallTrace(PageBlockFragment fragment) => "Block: " + Name + (fragment.Argument.IsNullOrEmpty() ? "" : " (" + fragment.Argument + ")");
public override Task WriteAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken cancel) => TypeConstants.EmptyTask;
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken cancel) { var htmlAttrs = fragment.Argument.GetJsExpressionAndEvaluate(scope) as Dictionary <string, object>; var hasEach = false; IEnumerable each = null; var binding = "it"; var hasExplicitBinding = false; JsToken where = null; if (htmlAttrs != null) { if (htmlAttrs.TryGetValue("if", out var oIf)) { if (TemplateDefaultFilters.isFalsy(oIf)) { return; } htmlAttrs.Remove("if"); } if (htmlAttrs.TryGetValue(nameof(where), out var oWhere)) { if (!(oWhere is string whereExpr)) { throw new NotSupportedException($"'where' should be a string expression but instead found '{oWhere.GetType().Name}'"); } where = whereExpr.GetCachedJsExpression(scope); htmlAttrs.Remove(nameof(where)); } if (htmlAttrs.TryGetValue(nameof(each), out var oEach)) { hasEach = true; htmlAttrs.Remove(nameof(each)); } each = oEach as IEnumerable; if (htmlAttrs.TryGetValue("it", out var oIt) && oIt is string it) { binding = it; hasExplicitBinding = true; htmlAttrs.Remove("it"); } if (htmlAttrs.TryGetValue("class", out var oClass)) { var cls = scope.Context.HtmlFilters.htmlClassList(oClass); if (string.IsNullOrEmpty(cls)) { htmlAttrs.Remove("class"); } else { htmlAttrs["class"] = cls; } } } var attrString = scope.Context.HtmlFilters.htmlAttrsList(htmlAttrs); if (TemplateHtmlFilters.VoidElements.Contains(Tag)) //e.g. img, input, br, etc { await scope.OutputStream.WriteAsync($"<{Tag}{attrString}>{Suffix}", cancel); } else { if (hasEach) { var hasElements = each != null && each.GetEnumerator().MoveNext(); if (hasElements) { await scope.OutputStream.WriteAsync($"<{Tag}{attrString}>{Suffix}", cancel); var index = 0; var whereIndex = 0; foreach (var element in each) { // Add all properties into scope if called without explicit in argument var scopeArgs = !hasExplicitBinding && CanExportScopeArgs(element) ? element.ToObjectDictionary() : new Dictionary <string, object>(); scopeArgs[binding] = element; scopeArgs[nameof(index)] = AssertWithinMaxQuota(whereIndex++); var itemScope = scope.ScopeWithParams(scopeArgs); if (where != null) { var result = where.EvaluateToBool(itemScope); if (!result) { continue; } } itemScope.ScopedParams[nameof(index)] = AssertWithinMaxQuota(index++); await WriteBodyAsync(itemScope, fragment, cancel); } await scope.OutputStream.WriteAsync($"</{Tag}>{Suffix}", cancel); } else { await WriteElseBlocks(scope, fragment.ElseBlocks, cancel); } } else { await scope.OutputStream.WriteAsync($"<{Tag}{attrString}>{Suffix}", cancel); await WriteBodyAsync(scope, fragment, cancel); await scope.OutputStream.WriteAsync($"</{Tag}>{Suffix}", cancel); } } }
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken cancel) { if (string.IsNullOrEmpty(fragment.ArgumentString)) { throw new NotSupportedException("'each' block requires the collection to iterate"); } var cache = (EachArg)scope.Context.Cache.GetOrAdd(fragment.ArgumentString, _ => ParseArgument(scope, fragment)); var collection = cache.Source.Evaluate(scope, out var syncResult, out var asyncResult) ? (IEnumerable)syncResult : (IEnumerable)(await asyncResult); var index = 0; if (collection != null) { if (cache.Where != null || cache.OrderBy != null || cache.OrderByDescending != null || cache.Skip != null || cache.Take != null) { var filteredResults = new List <Dictionary <string, object> >(); foreach (var element in collection) { // Add all properties into scope if called without explicit in argument var scopeArgs = !cache.HasExplicitBinding && CanExportScopeArgs(element) ? element.ToObjectDictionary() : new Dictionary <string, object>(); scopeArgs[cache.Binding] = element; scopeArgs[nameof(index)] = AssertWithinMaxQuota(index++); var itemScope = scope.ScopeWithParams(scopeArgs); if (cache.Where != null) { var result = await cache.Where.EvaluateToBoolAsync(itemScope); if (!result) { continue; } } filteredResults.Add(scopeArgs); } IEnumerable <Dictionary <string, object> > selectedResults = filteredResults; var comparer = (IComparer <object>)Comparer <object> .Default; if (cache.OrderBy != null) { var i = 0; selectedResults = selectedResults.OrderBy(scopeArgs => { scopeArgs[nameof(index)] = i++; return(cache.OrderBy.Evaluate(scope.ScopeWithParams(scopeArgs))); }, comparer); } else if (cache.OrderByDescending != null) { var i = 0; selectedResults = selectedResults.OrderByDescending(scopeArgs => { scopeArgs[nameof(index)] = i++; return(cache.OrderByDescending.Evaluate(scope.ScopeWithParams(scopeArgs))); }, comparer); } if (cache.Skip != null) { var skip = cache.Skip.Evaluate(scope).ConvertTo <int>(); selectedResults = selectedResults.Skip(skip); } if (cache.Take != null) { var take = cache.Take.Evaluate(scope).ConvertTo <int>(); selectedResults = selectedResults.Take(take); } index = 0; foreach (var scopeArgs in selectedResults) { var itemScope = scope.ScopeWithParams(scopeArgs); itemScope.ScopedParams[nameof(index)] = index++; await WriteBodyAsync(itemScope, fragment, cancel); } } else { foreach (var element in collection) { // Add all properties into scope if called without explicit in argument var scopeArgs = !cache.HasExplicitBinding && CanExportScopeArgs(element) ? element.ToObjectDictionary() : new Dictionary <string, object>(); scopeArgs[cache.Binding] = element; scopeArgs[nameof(index)] = AssertWithinMaxQuota(index++); var itemScope = scope.ScopeWithParams(scopeArgs); await WriteBodyAsync(itemScope, fragment, cancel); } } } if (index == 0) { await WriteElseBlocks(scope, fragment.ElseBlocks, cancel); } }
EachArg ParseArgument(TemplateScopeContext scope, PageBlockFragment fragment) { var literal = fragment.Argument.Span.ParseJsExpression(out var token); if (token == null) { throw new NotSupportedException("'each' block requires the collection to iterate"); } var binding = "it"; literal = literal.AdvancePastWhitespace(); JsToken source, where, orderBy, orderByDescending, skip, take; where = orderBy = orderByDescending = skip = take = null; var hasExplicitBinding = literal.StartsWith("in "); if (hasExplicitBinding) { if (!(token is JsIdentifier identifier)) { throw new NotSupportedException($"'each' block expected identifier but was {token.DebugToken()}"); } binding = identifier.Name; literal = literal.Advance(3); literal = literal.ParseJsExpression(out source); if (source == null) { throw new NotSupportedException("'each' block requires the collection to iterate"); } } else { source = token; } if (literal.StartsWith("where ")) { literal = literal.Advance("where ".Length); literal = literal.ParseJsExpression(out where); } literal = literal.AdvancePastWhitespace(); if (literal.StartsWith("orderby ")) { literal = literal.Advance("orderby ".Length); literal = literal.ParseJsExpression(out orderBy); literal = literal.AdvancePastWhitespace(); if (literal.StartsWith("descending")) { literal = literal.Advance("descending".Length); orderByDescending = orderBy; orderBy = null; } } literal = literal.AdvancePastWhitespace(); if (literal.StartsWith("skip ")) { literal = literal.Advance("skip ".Length); literal = literal.ParseJsExpression(out skip); } literal = literal.AdvancePastWhitespace(); if (literal.StartsWith("take ")) { literal = literal.Advance("take ".Length); literal = literal.ParseJsExpression(out take); } return(new EachArg(binding, hasExplicitBinding, source, where, orderBy, orderByDescending, skip, take)); }
//Extract Span out of async method private (string name, Dictionary <string, object> scopeArgs, bool appendTo) Parse(TemplateScopeContext scope, PageBlockFragment fragment) { if (fragment.Argument.IsNullOrWhiteSpace()) { throw new NotSupportedException("'capture' block is missing name of variable to assign captured output to"); } var literal = fragment.Argument.AdvancePastWhitespace(); bool appendTo = false; if (literal.StartsWith("appendTo ")) { appendTo = true; literal = literal.Advance("appendTo ".Length); } literal = literal.ParseVarName(out var name); if (name.IsNullOrEmpty()) { throw new NotSupportedException("'capture' block is missing name of variable to assign captured output to"); } literal = literal.AdvancePastWhitespace(); var argValue = literal.GetJsExpressionAndEvaluate(scope); var scopeArgs = argValue as Dictionary <string, object>; if (argValue != null && scopeArgs == null) { throw new NotSupportedException("Any 'capture' argument must be an Object Dictionary"); } return(name.ToString(), scopeArgs, appendTo); }
public override Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) => WriteAsync((TemplateScopeContext)scope, block, token);