private void Capture(ScriptScopeContext 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); } var minified = GetMinifiedOutputCache(strFragment.Value); 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 + minified; return; } scope.PageResult.Args[nameString] = minified.ToString(); }
/// <summary> /// Writes the asynchronous. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <param name="token">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>Task.</returns> /// <exception cref="System.NotSupportedException">'partial' block is missing name of partial</exception> /// <exception cref="System.NotSupportedException">Any 'partial' argument must be an Object Dictionary</exception> public override Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { var literal = block.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(ScriptConstants.Format, out var oFormat)) { format = oFormat.ToString(); args.Remove(ScriptConstants.Format); } var nameString = name.ToString(); var partial = new SharpPartialPage(scope.Context, nameString, block.Body, format, args); scope.PageResult.Partials[nameString] = partial; return(TypeConstants.EmptyTask); }
public override async Task WriteAsync( ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { var collection = (IEnumerable)block.Argument.GetJsExpressionAndEvaluate(scope, ifNone: () => throw new NotSupportedException("'each' block does not have a valid expression")); var index = 0; if (collection != null) { foreach (var element in collection) { var scopeArgs = element.ToObjectDictionary(); scopeArgs["it"] = element; scopeArgs[nameof(index)] = index++; var itemScope = scope.ScopeWithParams(scopeArgs); await WriteBodyAsync(itemScope, block, token); } } if (index == 0) { await WriteElseAsync(scope, block.ElseBlocks, token); } }
//Extract usages of Span outside of async method /// <summary> /// Parses the specified scope. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <returns>Tuple.</returns> /// <exception cref="System.NotSupportedException">'capture' block is missing name of variable to assign captured output to</exception> /// <exception cref="System.NotSupportedException">'capture' block is missing name of variable to assign captured output to</exception> /// <exception cref="System.NotSupportedException">Any 'capture' argument must be an Object Dictionary</exception> private Tuple Parse(ScriptScopeContext scope, PageBlockFragment block) { if (block.Argument.IsNullOrWhiteSpace()) { throw new NotSupportedException("'capture' block is missing name of variable to assign captured output to"); } var literal = block.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(new Tuple(name.ToString(), scopeArgs, appendTo)); }
protected bool Equals(PageBlockFragment other) { return(Name == other.Name && Argument.Span.SequenceEqual(other.Argument.Span) && Body.EquivalentTo(other.Body) && ElseBlocks.EquivalentTo(other.ElseBlocks)); }
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken cancel) { await scope.OutputStream.WriteAsync("<b>", cancel); await WriteBodyAsync(scope, fragment, cancel); await scope.OutputStream.WriteAsync("</b>", cancel); }
public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { await scope.OutputStream.WriteAsync("<b>", token); await WriteBodyAsync(scope, block, token); await scope.OutputStream.WriteAsync("</b>", token); }
private static void Capture(TemplateScopeContext scope, PageBlockFragment fragment, PageStringFragment strFragment) { var literal = fragment.Argument.AdvancePastWhitespace(); literal = literal.ParseVarName(out var name); var nameString = name.ToString(); scope.PageResult.Args[nameString] = MarkdownConfig.Transform(strFragment.ValueString).ToRawString(); }
/// <summary> /// Writes the asynchronous. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <param name="ct">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>Task.</returns> public override Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken ct) { var literal = block.Argument.ParseVarName(out var name); var strFragment = (PageStringFragment)block.Body[0]; var csvList = Context.DefaultMethods.parseCsv(strFragment.ValueString); scope.PageResult.Args[name.ToString()] = csvList; return(TypeConstants.EmptyTask); }
public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { var strFragment = (PageStringFragment)block.Body[0]; if (!block.Argument.IsNullOrWhiteSpace()) { Capture(scope, block, strFragment); } else { await scope.OutputStream.WriteAsync(MarkdownConfig.Transform(strFragment.ValueString), token); } }
public override async Task WriteAsync(TemplateScopeContext scope, PageBlockFragment fragment, CancellationToken cancel) { var strFragment = (PageStringFragment)fragment.Body[0]; if (!fragment.Argument.IsNullOrWhiteSpace()) { Capture(scope, fragment, strFragment); } else { await scope.OutputStream.WriteAsync(MarkdownConfig.Transform(strFragment.ValueString), cancel); } }
/// <summary> /// Write as an asynchronous operation. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <param name="token">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>A Task representing the asynchronous operation.</returns> public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { var result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope, ifNone : () => throw new NotSupportedException("'if' block does not have a valid expression")).ConfigAwait(); if (result) { await WriteBodyAsync(scope, block, token).ConfigAwait(); } else { await WriteElseAsync(scope, block.ElseBlocks, token).ConfigAwait(); } }
public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { var strFragment = (PageStringFragment)block.Body[0]; if (!block.Argument.IsNullOrWhiteSpace()) { Capture(scope, block, strFragment); } else { var minified = GetMinifiedOutputCache(strFragment.Value); await scope.OutputStream.WriteAsync(minified.Span, token); } }
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(); literal = literal.ParseVarName(out var name); var nameString = name.Value; scope.PageResult.Args[nameString] = MarkdownConfig.Transform(strFragment.Value.Value).ToRawString(); } else { await scope.OutputStream.WriteAsync(MarkdownConfig.Transform(strFragment.Value.Value), cancel); } }
public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { if (block.Argument.IsEmpty) throw new NotSupportedException($"Name required in {Name} script block"); var argumentStr = block.Argument.ToString(); var args = argumentStr.SplitOnFirst(' '); var name = args[0].Trim(); using (var ms = MemoryStreamFactory.GetStream()) { var useScope = scope.ScopeWithStream(ms); await WriteBodyAsync(useScope, block, token); var capturedSvg = ms.ReadToEnd(); Svg.AddImage(capturedSvg, name, args.Length == 2 ? args[1].Trim() : null); } }
/// <summary> /// Write as an asynchronous operation. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <param name="token">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>A Task representing the asynchronous operation.</returns> public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { var argValue = await block.Argument.GetJsExpressionAndEvaluateAsync(scope); var args = argValue as Dictionary <string, object> ?? new Dictionary <string, object>(); var format = scope.Context.PageFormats.First().Extension; if (args.TryGetValue(ScriptConstants.Format, out var oFormat)) { format = oFormat.ToString(); args.Remove(ScriptConstants.Format); } var htmlDecode = false; if (args.TryGetValue(nameof(htmlDecode), out var oHtmlDecode) && oHtmlDecode is bool b) { htmlDecode = b; args.Remove(nameof(htmlDecode)); } var context = scope.CreateNewContext(args); var unrenderedBody = new SharpPartialPage(scope.Context, "eval-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).ConfigAwait(); // ReSharper disable once MethodHasAsyncOverload var renderedBody = ms.ReadToEnd(); if (htmlDecode) { renderedBody = renderedBody.HtmlDecode(); } var pageResult = new PageResult(context.OneTimePage(renderedBody)) { Args = args, }; await pageResult.WriteToAsync(scope.OutputStream, token).ConfigAwait(); }
/// <summary> /// Write as an asynchronous operation. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <param name="token">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>A Task representing the asynchronous operation.</returns> public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { var tuple = Parse(scope, block); var name = tuple.name; using var ms = MemoryStreamFactory.GetStream(); var useScope = scope.ScopeWith(tuple.scopeArgs, ms); await WriteBodyAsync(useScope, block, token).ConfigAwait(); // ReSharper disable once MethodHasAsyncOverload var capturedOutput = ms.ReadToEnd(); if (tuple.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; }
/// <summary> /// Writes the asynchronous. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <param name="ct">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>Task.</returns> /// <exception cref="System.NotSupportedException">#keyvalues expected string delimiter but was {token.DebugToken()}</exception> public override Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken ct) { var literal = block.Argument.Span.ParseVarName(out var name); var delimiter = " "; literal = literal.AdvancePastWhitespace(); if (literal.Length > 0) { literal = literal.ParseJsToken(out var token); if (token is not JsLiteral litToken) { throw new NotSupportedException($"#keyvalues expected string delimiter but was {token.DebugToken()}"); } delimiter = litToken.Value.ToString(); } var strFragment = (PageStringFragment)block.Body[0]; var strDict = Context.DefaultMethods.parseKeyValues(strFragment.ValueString, delimiter); scope.PageResult.Args[name.ToString()] = strDict; return(TypeConstants.EmptyTask); }
/// <summary> /// Write as an asynchronous operation. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <param name="ct">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>A Task representing the asynchronous operation.</returns> public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken ct) { var result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope, ifNone : () => throw new NotSupportedException("'while' block does not have a valid expression")).ConfigAwait(); var iterations = 0; if (result) { do { await WriteBodyAsync(scope, block, ct); result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope, ifNone : () => throw new NotSupportedException("'while' block does not have a valid expression")); Context.DefaultMethods.AssertWithinMaxQuota(iterations++); } while (result); } else { await WriteElseAsync(scope, block.ElseBlocks, ct); } }
public static List <PageFragment> ParseTemplatePage(ReadOnlyMemory <char> text) { var to = new List <PageFragment>(); if (text.IsNullOrWhiteSpace()) { return(to); } int pos; var lastPos = 0; while ((pos = text.IndexOf("{{", lastPos)) != -1) { var block = text.Slice(lastPos, pos - lastPos); if (!block.IsNullOrEmpty()) { to.Add(new PageStringFragment(block)); } var varStartPos = pos + 2; var firstChar = text.Span[varStartPos]; if (firstChar == '*') //comment { lastPos = text.IndexOf("*}}", varStartPos) + 3; } else if (firstChar == '#') //block statement { var literal = text.Slice(varStartPos + 1); literal = literal.ParseVarName(out var blockNameSpan); var blockName = blockNameSpan.ToString(); var endExprPos = literal.IndexOf("}}"); if (endExprPos == -1) { throw new SyntaxErrorException($"Unterminated '{blockName}' block expression, near '{literal.DebugLiteral()}'"); } var blockExpr = literal.Slice(0, endExprPos).Trim(); literal = literal.Advance(endExprPos + 2); if (!ScriptConfig.DontEvaluateBlocksNamed.Contains(blockName)) { literal = literal.ParseStatementBody(blockNameSpan, out var body); var elseStatements = new List <PageElseBlock>(); while (literal.StartsWith("{{else")) { literal = literal.ParseElseStatement(blockName, out var elseStatement); elseStatements.Add(elseStatement); } literal = literal.Advance(2 + 1 + blockName.Length + 2); //remove new line after partial block end tag literal = literal.TrimFirstNewLine(); var length = text.Length - pos - literal.Length; var originalText = text.Slice(pos, length); lastPos = pos + length; var statement = new PageBlockFragment(originalText, blockName, blockExpr, body, elseStatements); to.Add(statement); } else { var endBlock = "{{/" + blockName + "}}"; var endBlockPos = literal.IndexOf(endBlock); if (endBlockPos == -1) { throw new SyntaxErrorException($"Unterminated end block '{endBlock}'"); } var endBlockBody = literal.Slice(0, endBlockPos); literal = literal.Advance(endBlockPos + endBlock.Length).TrimFirstNewLine(); var body = new List <PageFragment> { new PageStringFragment(endBlockBody) }; var length = text.Length - pos - literal.Length; var originalText = text.Slice(pos, length); lastPos = pos + length; var statement = new PageBlockFragment(originalText, blockName, blockExpr, body); to.Add(statement); } } else { var literal = text.Slice(varStartPos).Span; literal = literal.ParseJsExpression(out var expr, filterExpression: true); var filters = new List <JsCallExpression>(); if (!literal.StartsWith("}}")) { literal = literal.AdvancePastWhitespace(); if (literal.FirstCharEquals(FilterSep)) { literal = literal.Advance(1); while (true) { literal = literal.ParseJsCallExpression(out var filter, filterExpression: true); filters.Add(filter); literal = literal.AdvancePastWhitespace(); if (literal.IsNullOrEmpty()) { throw new SyntaxErrorException("Unterminated filter expression"); } if (literal.StartsWith("}}")) { literal = literal.Advance(2); break; } if (!literal.FirstCharEquals(FilterSep)) { throw new SyntaxErrorException( $"Expected filter separator '|' but was {literal.DebugFirstChar()}"); } literal = literal.Advance(1); } } else { if (!literal.IsNullOrEmpty()) { literal = literal.Advance(1); } } } else { literal = literal.Advance(2); } var length = text.Length - pos - literal.Length; var originalText = text.Slice(pos, length); lastPos = pos + length; var varFragment = new PageVariableFragment(originalText, expr, filters); to.Add(varFragment); var newLineLen = literal.StartsWith("\n") ? 1 : literal.StartsWith("\r\n") ? 2 : 0; if (newLineLen > 0) { var lastExpr = varFragment.FilterExpressions?.LastOrDefault(); var filterName = lastExpr?.Name ?? varFragment?.InitialExpression?.Name ?? varFragment.Binding; if (filterName != null && ScriptConfig.RemoveNewLineAfterFiltersNamed.Contains(filterName)) { lastPos += newLineLen; } } } } if (lastPos != text.Length) { var lastBlock = lastPos == 0 ? text : text.Slice(lastPos); to.Add(new PageStringFragment(lastBlock)); } return(to); }
/// <summary> /// Write as an asynchronous operation. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <param name="token">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>A Task representing the asynchronous operation.</returns> /// <exception cref="System.NotSupportedException">'where' should be a string expression but instead found '{oWhere.GetType().Name}'</exception> public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { var htmlAttrs = await block.Argument.GetJsExpressionAndEvaluateAsync(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 (DefaultScripts.isFalsy(oIf)) { return; } htmlAttrs.Remove("if"); } if (htmlAttrs.TryGetValue(nameof(where), out var oWhere)) { if (oWhere is not 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.HtmlMethods.htmlClassList(oClass); if (string.IsNullOrEmpty(cls)) { htmlAttrs.Remove("class"); } else { htmlAttrs["class"] = cls; } } } var attrString = scope.Context.HtmlMethods.htmlAttrsList(htmlAttrs); if (HtmlScripts.VoidElements.Contains(Tag)) //e.g. img, input, br, etc { await scope.OutputStream.WriteAsync($"<{Tag}{attrString}>{Suffix}", token).ConfigAwait(); } else { if (hasEach) { var hasElements = each != null && each.GetEnumerator().MoveNext(); if (hasElements) { await scope.OutputStream.WriteAsync($"<{Tag}{attrString}>{Suffix}", token).ConfigAwait(); 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 = await where.EvaluateToBoolAsync(itemScope); if (!result) { continue; } } itemScope.ScopedParams[nameof(index)] = AssertWithinMaxQuota(index++); await WriteBodyAsync(itemScope, block, token).ConfigAwait(); } await scope.OutputStream.WriteAsync($"</{Tag}>{Suffix}", token).ConfigAwait(); } else { await WriteElseAsync(scope, block.ElseBlocks, token).ConfigAwait(); } } else { await scope.OutputStream.WriteAsync($"<{Tag}{attrString}>{Suffix}", token).ConfigAwait(); await WriteBodyAsync(scope, block, token).ConfigAwait(); await scope.OutputStream.WriteAsync($"</{Tag}>{Suffix}", token).ConfigAwait(); } } }
/// <summary> /// Writes the asynchronous. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <param name="token">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>Task.</returns> public abstract Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token);
/// <summary> /// Gets the call trace. /// </summary> /// <param name="fragment">The fragment.</param> /// <returns>System.String.</returns> protected virtual string GetCallTrace(PageBlockFragment fragment) => "Block: " + Name + (fragment.Argument.IsNullOrEmpty() ? "" : " (" + fragment.Argument + ")");
/// <summary> /// Writes the asynchronous. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <param name="token">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>Task.</returns> public override Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { // block.Argument key is unique to exact memory fragment, not string equality // Parse into AST once for all Page Results var invokerCtx = (Tuple <string, StaticMethodInvoker>)scope.Context.CacheMemory.GetOrAdd(block.Argument, _ => { var literal = block.Argument.Span.ParseVarName(out var name); var strName = name.ToString(); literal = literal.AdvancePastWhitespace(); literal = literal.AdvancePastWhitespace(); var args = TypeConstants.EmptyStringArray; if (!literal.IsEmpty) { literal.ParseArgumentsList(out var argIdentifiers); args = new string[argIdentifiers.Count]; for (var i = 0; i < argIdentifiers.Count; i++) { args[i] = argIdentifiers[i].Name; } } StaticMethodInvoker invoker = null; // Allow recursion by initializing lazy Delegate object LazyInvoker(object instance, object[] paramValues) { if (invoker == null) { throw new NotSupportedException($"Uninitialized function '{strName}'"); } return(invoker(instance, paramValues)); } invoker = paramValues => { scope.PageResult.StackDepth++; try { var page = new SharpPage(Context, block.Body); var pageResult = new PageResult(page) { Args = { [strName] = (MethodInvoker)LazyInvoker }, StackDepth = scope.PageResult.StackDepth }; var len = Math.Min(paramValues.Length, args.Length); for (int i = 0; i < len; i++) { var paramValue = paramValues[i]; pageResult.Args[args[i]] = paramValue; } if (pageResult.EvaluateResult(out var returnValue)) { return(returnValue); } return(IgnoreResult.Value); } finally { scope.PageResult.StackDepth--; } };
/// <summary> /// Parses the argument. /// </summary> /// <param name="scope">The scope.</param> /// <param name="fragment">The fragment.</param> /// <returns>EachArg.</returns> /// <exception cref="System.NotSupportedException">'each' block requires the collection to iterate</exception> /// <exception cref="System.NotSupportedException">'each' block expected identifier but was {token.DebugToken()}</exception> /// <exception cref="System.NotSupportedException">'each' block requires the collection to iterate</exception> EachArg ParseArgument(ScriptScopeContext 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, orderBy, orderByDescending, skip, take; JsToken where = orderBy = orderByDescending = skip = take = null; var hasExplicitBinding = literal.StartsWith("in "); if (hasExplicitBinding) { if (token is not 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)); }
/// <summary> /// Write as an asynchronous operation. /// </summary> /// <param name="scope">The scope.</param> /// <param name="block">The block.</param> /// <param name="token">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>A Task representing the asynchronous operation.</returns> /// <exception cref="System.NotSupportedException">'each' block requires the collection to iterate</exception> public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { if (block.Argument.IsNullOrEmpty()) { throw new NotSupportedException("'each' block requires the collection to iterate"); } var cache = (EachArg)scope.Context.Cache.GetOrAdd(block.ArgumentString, _ => ParseArgument(scope, block)); var collection = cache.Source.Evaluate(scope, out var syncResult, out var asyncResult) ? (IEnumerable)syncResult : (IEnumerable)await asyncResult.ConfigAwait(); 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).ConfigAwait(); 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, block, token).ConfigAwait(); } } 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, block, token).ConfigAwait(); } } } if (index == 0) { await WriteElseAsync(scope, block.ElseBlocks, token).ConfigAwait(); } }
public JsPageBlockFragmentStatement(PageBlockFragment block) => Block = block;
/// <summary> /// Write body as an asynchronous operation. /// </summary> /// <param name="scope">The scope.</param> /// <param name="fragment">The fragment.</param> /// <param name="token">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>A Task representing the asynchronous operation.</returns> protected virtual async Task WriteBodyAsync(ScriptScopeContext scope, PageBlockFragment fragment, CancellationToken token) { await WriteAsync(scope, fragment.Body, GetCallTrace(fragment), token).ConfigAwait(); }