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 ct) { var result = await block.Argument.GetJsExpressionAndEvaluateToBoolAsync(scope, ifNone: () => throw new NotSupportedException("'while' block does not have a valid expression")); 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); } }
// {{#if ...}} // ^ public static ReadOnlyMemory <char> ParseTemplateScriptBlock(this ReadOnlyMemory <char> literal, ScriptContext context, out PageBlockFragment blockFragment) { literal = literal.ParseVarName(out var blockNameSpan); PageBlockFragment statement; var blockName = blockNameSpan.ToString(); var endBlock = "{{/" + blockName + "}}"; var endExprPos = literal.IndexOf("}}"); if (endExprPos == -1) { throw new SyntaxErrorException($"Unterminated '{blockName}' block expression, near '{literal.DebugLiteral()}'"); } var argument = literal.Slice(0, endExprPos).Trim(); literal = literal.Advance(endExprPos + 2); var language = context.ParseAsLanguage.TryGetValue(blockName, out var lang) ? lang : ScriptTemplate.Language; if (language.Name == ScriptVerbatim.Language.Name) { var endBlockPos = literal.IndexOf(endBlock); if (endBlockPos == -1) { throw new SyntaxErrorException($"Unterminated end block '{endBlock}'"); } var body = literal.Slice(0, endBlockPos); literal = literal.Advance(endBlockPos + endBlock.Length).TrimFirstNewLine(); blockFragment = language.ParseVerbatimBlock(blockName, argument, body); return(literal); } literal = literal.ParseTemplateBody(blockNameSpan, out var bodyText); var bodyFragments = language.Parse(context, bodyText); var elseBlocks = new List <PageElseBlock>(); while (literal.StartsWith("{{else")) { literal = literal.ParseTemplateElseBlock(blockName, out var elseArgument, out var elseBody); var elseBlock = new PageElseBlock(elseArgument, language.Parse(context, elseBody)); elseBlocks.Add(elseBlock); } literal = literal.Advance(2 + 1 + blockName.Length + 2); //remove new line after partial block end tag literal = literal.TrimFirstNewLine(); blockFragment = new PageBlockFragment(blockName, argument, bodyFragments, elseBlocks); return(literal); }
public virtual PageBlockFragment ParseVerbatimBlock(string blockName, ReadOnlyMemory <char> argument, ReadOnlyMemory <char> body) { var bodyFragment = new List <PageFragment> { new PageStringFragment(body) }; var blockFragment = new PageBlockFragment(blockName, argument, bodyFragment); return(blockFragment); }
protected virtual async Task WriteBodyAsync(ScriptScopeContext scope, PageBlockFragment fragment, CancellationToken token) { if (fragment.Body != null) { await WriteAsync(scope, fragment.Body, GetCallTrace(fragment), token); } else if (fragment.BodyStatement?.Statements != null) { await WriteAsync(scope, fragment.BodyStatement.Statements, GetCallTrace(fragment), token); } }
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 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 Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken ct) { var literal = block.Argument.ParseVarName(out var name); var strFragment = (PageStringFragment)block.Body[0]; var trimmedBody = StringBuilderCache.Allocate(); foreach (var line in strFragment.ValueString.ReadLines()) { trimmedBody.AppendLine(line.Trim()); } var strList = trimmedBody.ToString().FromCsv <List <List <string> > >(); scope.PageResult.Args[name.ToString()] = strList; return(TypeConstants.EmptyTask); }
public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { var result = await block.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, block, token); } else { await WriteElseAsync(scope, block.ElseBlocks, token); } }
public override async Task WriteAsync(ScriptScopeContext 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(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); var renderedBody = await ms.ReadToEndAsync(); if (htmlDecode) { renderedBody = renderedBody.HtmlDecode(); } var pageResult = new PageResult(context.OneTimePage(renderedBody)) { Args = args, }; await pageResult.WriteToAsync(scope.OutputStream, token); } }
// #if ... // ^ public static ReadOnlyMemory <char> ParseCodeScriptBlock(this ReadOnlyMemory <char> literal, ScriptContext context, out PageBlockFragment blockFragment) { literal = literal.ParseVarName(out var blockNameSpan); var endArgumentPos = literal.IndexOf('\n'); var argument = literal.Slice(0, endArgumentPos).Trim(); literal = literal.Slice(endArgumentPos + 1); var blockName = blockNameSpan.ToString(); var language = context.ParseAsLanguage.TryGetValue(blockName, out var lang) ? lang : ScriptCode.Language; if (language.Name == ScriptVerbatim.Language.Name) { literal = literal.ParseCodeBody(blockNameSpan, out var body); body = body.ChopNewLine(); blockFragment = language.ParseVerbatimBlock(blockName, argument, body); return(literal); } literal = literal.ParseCodeBody(blockNameSpan, out var bodyText); var bodyFragments = language.Parse(context, bodyText); var elseBlocks = new List <PageElseBlock>(); literal = literal.AdvancePastWhitespace(); while (literal.StartsWith("else")) { literal = literal.ParseCodeElseBlock(blockNameSpan, out var elseArgument, out var elseBody); var elseBlock = new PageElseBlock(elseArgument, language.Parse(context, elseBody)); elseBlocks.Add(elseBlock); literal = literal.AdvancePastWhitespace(); } blockFragment = new PageBlockFragment(blockName, argument, bodyFragments, elseBlocks); return(literal); }
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; }
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 JsLiteral litToken)) { throw new NotSupportedException($"'keyvalues' block expected string delimiter but was {token.DebugToken()}"); } delimiter = litToken.Value.ToString(); } var strFragment = (PageStringFragment)block.Body[0]; var strDict = strFragment.ValueString.Trim().ParseAsKeyValues(delimiter); scope.PageResult.Args[name.ToString()] = strDict; return(TypeConstants.EmptyTask); }
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, 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)); }
// {{#if ...}} // ^ public static ReadOnlyMemory <char> ParseTemplateScriptBlock(this ReadOnlyMemory <char> literal, ScriptContext context, out PageBlockFragment blockFragment) { literal = literal.ParseVarName(out var blockNameSpan); PageBlockFragment statement; var blockName = blockNameSpan.ToString(); var endBlock = "{{/" + blockName + "}}"; 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 (context.ParseAsVerbatimBlock.Contains(blockName)) { var endBlockPos = literal.IndexOf(endBlock); if (endBlockPos == -1) { throw new SyntaxErrorException($"Unterminated end block '{endBlock}'"); } var blockBody = literal.Slice(0, endBlockPos); literal = literal.Advance(endBlockPos + endBlock.Length).TrimFirstNewLine(); var body = new List <PageFragment> { new PageStringFragment(blockBody) }; blockFragment = new PageBlockFragment(blockName, blockExpr, body); return(literal); } else { var parseAsCode = context.ParseAsCodeBlock.Contains(blockName); literal = literal.ParseTemplateBody(blockNameSpan, out var bodyText); List <PageFragment> bodyFragments = null; JsBlockStatement bodyStatements = null; if (!parseAsCode) { bodyFragments = context.ParseTemplate(bodyText); } else { bodyStatements = context.ParseCode(bodyText); } var elseBlocks = new List <PageElseBlock>(); while (literal.StartsWith("{{else")) { literal = literal.ParseTemplateElseBlock(blockName, out var elseArgument, out var elseBody); var elseBlock = !parseAsCode ? new PageElseBlock(elseArgument, context.ParseTemplate(elseBody)) : new PageElseBlock(elseArgument, context.ParseCode(elseBody)); elseBlocks.Add(elseBlock); } literal = literal.Advance(2 + 1 + blockName.Length + 2); //remove new line after partial block end tag literal = literal.TrimFirstNewLine(); blockFragment = !parseAsCode ? new PageBlockFragment(blockName, blockExpr, bodyFragments, elseBlocks) : new PageBlockFragment(blockName, blockExpr, bodyStatements, elseBlocks); return(literal); } }
public override Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { // block.Argument key is unique to exact memory fragment, not string equality var invokerCtx = (Tuple <string, StaticMethodInvoker>)scope.Context.CacheMemory.GetOrAdd(block.Argument, key => { 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 = 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 MethodInvoker LazyInvoker = (instance, 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] = 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--; } };
public override Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) => TypeConstants.EmptyTask;
public override Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { var invokerCtx = (Tuple <string, MethodInvoker>)scope.Context.CacheMemory.GetOrAdd(block.Argument, key => { var literal = block.Argument.Span.ParseVarName(out var name); var strName = name.ToString(); literal = literal.AdvancePastWhitespace(); literal = literal.AdvancePastWhitespace(); var args = TypeConstants.EmptyStringList; if (!literal.IsEmpty) { literal = literal.ParseArgumentsList(out var argIdentifiers); args = argIdentifiers.Map(x => x.Name); } var strFragment = (PageStringFragment)block.Body[0]; var script = ScriptPreprocessors.TransformStatementBody(strFragment.ValueString); var parsedScript = scope.Context.OneTimePage(script); MethodInvoker 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 = (instance, paramValues) => { scope.PageResult.StackDepth++; try { var pageResult = new PageResult(parsedScript) { Args = { [strName] = (MethodInvoker)LazyInvoker }, StackDepth = scope.PageResult.StackDepth }; var len = Math.Min(paramValues.Length, args.Count); for (int i = 0; i < len; i++) { var paramValue = paramValues[i]; pageResult.Args[args[i]] = paramValue; } var discard = ScriptContextUtils.GetPageResultOutput(pageResult); if (pageResult.ReturnValue == null) { throw new NotSupportedException(ScriptContextUtils.ErrorNoReturn); } return(pageResult.ReturnValue.Result); } finally { scope.PageResult.StackDepth--; } }; return(Tuple.Create(strName, invoker)); }); scope.PageResult.Args[invokerCtx.Item1] = invokerCtx.Item2; return(TypeConstants.EmptyTask); }
protected virtual async Task WriteBodyAsync(ScriptScopeContext scope, PageBlockFragment fragment, CancellationToken token) { await WriteAsync(scope, fragment.Body, GetCallTrace(fragment), token); }
public abstract Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token);
protected virtual string GetCallTrace(PageBlockFragment fragment) => "Block: " + Name + (fragment.Argument.IsNullOrEmpty() ? "" : " (" + fragment.Argument + ")");
public override async Task WriteAsync(ScriptScopeContext scope, PageBlockFragment block, CancellationToken token) { var htmlAttrs = block.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 (Script.DefaultScripts.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.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); } else { if (hasEach) { var hasElements = each != null && each.GetEnumerator().MoveNext(); if (hasElements) { await scope.OutputStream.WriteAsync($"<{Tag}{attrString}>{Suffix}", token); 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, block, token); } await scope.OutputStream.WriteAsync($"</{Tag}>{Suffix}", token); } else { await WriteElseAsync(scope, block.ElseBlocks, token); } } else { await scope.OutputStream.WriteAsync($"<{Tag}{attrString}>{Suffix}", token); await WriteBodyAsync(scope, block, token); await scope.OutputStream.WriteAsync($"</{Tag}>{Suffix}", token); } } }
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(); } }