public async Task includeFileWithCache(TemplateScopeContext scope, string virtualPath, object options)
        {
            var scopedParams = scope.AssertOptions(nameof(includeUrl), options);
            var expireIn     = scopedParams.TryGetValue("expireInSecs", out object value)
                ? TimeSpan.FromSeconds(value.ConvertTo <int>())
                : (TimeSpan)scope.Context.Args[TemplateConstants.DefaultFileCacheExpiry];

            var cacheKey = CreateCacheKey("file:" + scope.PageResult.VirtualPath + ">" + virtualPath, scopedParams);

            if (Context.ExpiringCache.TryGetValue(cacheKey, out Tuple <DateTime, object> cacheEntry))
            {
                if (cacheEntry.Item1 > DateTime.UtcNow && cacheEntry.Item2 is byte[] bytes)
                {
                    await scope.OutputStream.WriteAsync(bytes);

                    return;
                }
            }

            var file = ResolveFile(nameof(includeFileWithCache), scope, virtualPath);
            var ms   = MemoryStreamFactory.GetStream();

            using (ms)
            {
                using (var reader = file.OpenRead())
                {
                    await reader.CopyToAsync(ms);
                }

                ms.Position = 0;
                var bytes = ms.ToArray();
                Context.ExpiringCache[cacheKey] = Tuple.Create(DateTime.UtcNow.Add(expireIn), (object)bytes);
                await scope.OutputStream.WriteAsync(bytes);
            }
        }
示例#2
0
        public async Task includeUrl(TemplateScopeContext scope, string url, object options)
        {
            var scopedParams = scope.AssertOptions(nameof(includeUrl), options);
            var dataType     = scopedParams.TryGetValue("dataType", out object value) ? ConvertDataTypeToContentType((string)value) : null;

            var webReq = (HttpWebRequest)WebRequest.Create(url);

            if (scopedParams.TryGetValue("method", out value))
            {
                webReq.Method = (string)value;
            }
            if (scopedParams.TryGetValue("contentType", out value) || dataType != null)
            {
                webReq.ContentType = (string)value ?? dataType;
            }
            if (scopedParams.TryGetValue("accept", out value) || dataType != null)
            {
                webReq.Accept = (string)value ?? dataType;
            }
            if (scopedParams.TryGetValue("userAgent", out value))
            {
#if NET45 || NET40
                webReq.UserAgent = value.ToString();
#else
                webReq.Headers[HttpRequestHeader.UserAgent] = value.ToString();
#endif
            }

            if (scopedParams.TryRemove("data", out object data))
            {
                if (webReq.Method == null)
                {
                    webReq.Method = HttpMethods.Post;
                }

                if (webReq.ContentType == null)
                {
                    webReq.ContentType = MimeTypes.FormUrlEncoded;
                }

                var body = ConvertDataToString(data, webReq.ContentType);
                using (var stream = await webReq.GetRequestStreamAsync())
                {
                    await stream.WriteAsync(body);
                }
            }

            using (var webRes = await webReq.GetResponseAsync())
                using (var stream = webRes.GetResponseStream())
                {
                    await stream.CopyToAsync(scope.OutputStream);
                }
        }
        public async Task includeUrlWithCache(TemplateScopeContext scope, string url, object options)
        {
            var scopedParams = scope.AssertOptions(nameof(includeUrl), options);
            var expireIn     = scopedParams.TryGetValue("expireInSecs", out var value)
                               ? TimeSpan.FromSeconds(value.ConvertTo <int>())
                               : (TimeSpan)scope.Context.Args[TemplateConstants.DefaultUrlCacheExpiry];

            var cacheKey = CreateCacheKey($"url:{url}", scopedParams);

            if (Context.ExpiringCache.TryGetValue(cacheKey, out var cacheEntry))
            {
                if (cacheEntry.Item1 > DateTime.UtcNow && cacheEntry.Item2 is byte[] bytes)
                {
                    await scope.OutputStream.WriteAsync(bytes);

                    return;
                }
            }

            var dataType = scopedParams.TryGetValue("dataType", out value)
                               ? ConvertDataTypeToContentType((string)value)
                               : null;

            if (scopedParams.TryGetValue("method", out value) && !((string)value).EqualsIgnoreCase("GET"))
            {
                throw new NotSupportedException(
                          $"Only GET requests can be used in {nameof(includeUrlWithCache)} filters in page '{scope.Page.VirtualPath}'");
            }
            if (scopedParams.TryGetValue("data", out value))
            {
                throw new NotSupportedException(
                          $"'data' is not supported in {nameof(includeUrlWithCache)} filters in page '{scope.Page.VirtualPath}'");
            }

            var ms = MemoryStreamFactory.GetStream();

            using (ms)
            {
                var captureScope = scope.ScopeWithStream(ms);
                await includeUrl(captureScope, url, options);

                ms.Position = 0;
                var expireAt = DateTime.UtcNow.Add(expireIn);

                var bytes = ms.ToArray();
                Context.ExpiringCache[cacheKey] =
                    cacheEntry = Tuple.Create(DateTime.UtcNow.Add(expireIn), (object)bytes);
                await scope.OutputStream.WriteAsync(bytes);
            }
        }
示例#4
0
        public static Dictionary <string, object> GetParamsWithItemBindingOnly(this TemplateScopeContext scope, string filterName, TemplatePage page, object scopedParams, out string itemBinding)
        {
            var pageParams = scope.AssertOptions(filterName, scopedParams);

            itemBinding = pageParams.TryGetValue("it", out object bindingName) && bindingName is string binding
                ? binding
                : "it";

            if (bindingName != null && !(bindingName is string))
            {
                throw new NotSupportedException($"'it' option in filter '{filterName}' should contain the name to bind to but contained a '{bindingName.GetType().Name}' instead");
            }

            // page vars take precedence
            if (page != null && page.Args.TryGetValue("it", out object pageBinding))
            {
                itemBinding = (string)pageBinding;
            }

            return(pageParams);
        }
示例#5
0
        public async Task includeFileWithCache(TemplateScopeContext scope, string virtualPath, object options)
        {
            var scopedParams = scope.AssertOptions(nameof(includeUrl), options);
            var expireIn     = scopedParams.TryGetValue("expireInSecs", out object value)
                ? TimeSpan.FromSeconds(value.ConvertTo <int>())
                : (TimeSpan)scope.Context.Args[TemplateConstants.DefaultCacheExpiry];

            var cacheKey = CreateCacheKey("file:" + virtualPath, scopedParams);

            if (Context.ExpiringCache.TryGetValue(cacheKey, out Tuple <DateTime, object> cacheEntry))
            {
                if (cacheEntry.Item1 > DateTime.UtcNow && cacheEntry.Item2 is byte[] bytes)
                {
                    await scope.OutputStream.WriteAsync(bytes);

                    return;
                }
            }

            var file = scope.Context.VirtualFiles.GetFile(virtualPath);

            if (file == null)
            {
                throw new FileNotFoundException($"{nameof(includeFileWithCache)} with '{virtualPath}' in page '{scope.Page.VirtualPath}' was not found");
            }

            var ms = MemoryStreamFactory.GetStream();

            using (ms)
            {
                var captureScope = scope.ScopeWithStream(ms);
                await includeFile(captureScope, virtualPath);

                ms.Position = 0;
                var bytes = ms.ToArray();
                Context.ExpiringCache[cacheKey] = cacheEntry = Tuple.Create(DateTime.UtcNow.Add(expireIn), (object)bytes);
                await scope.OutputStream.WriteAsync(bytes);
            }
        }
        public IRawString htmlDump(TemplateScopeContext scope, object target, object scopeOptions)
        {
            var scopedParams = scope.AssertOptions(nameof(htmlDump), scopeOptions);
            var depth        = scopedParams.TryGetValue("depth", out object oDepth) ? (int)oDepth : 0;
            var childDepth   = scopedParams.TryGetValue("childDepth", out object oChildDepth) ? oChildDepth.ConvertTo <int>() : 1;

            scopedParams["depth"] = depth + 1;

            try
            {
                if (!isComplexType(target))
                {
                    return(GetScalarHtml(target).ToRawString());
                }

                scopedParams.TryGetValue("captionIfEmpty", out object captionIfEmpty);
                scopedParams.TryGetValue("headerStyle", out object oHeaderStyle);
                scopedParams.TryGetValue("className", out object parentClass);
                scopedParams.TryGetValue("childClass", out object childClass);
                var headerStyle = oHeaderStyle as string ?? "splitCase";
                var className   = ((depth < childDepth ? parentClass : childClass ?? parentClass)
                                   ?? Context.Args[TemplateConstants.DefaultTableClassName]).ToString();

                if (target is IEnumerable e)
                {
                    var objs = e.Map(x => x);

                    var isEmpty = objs.Count == 0;
                    if (isEmpty && captionIfEmpty == null)
                    {
                        return(RawString.Empty);
                    }

                    var first = !isEmpty ? objs[0] : null;
                    if (first is IDictionary)
                    {
                        return(htmlList(scope, target, scopeOptions));
                    }

                    var sb = StringBuilderCacheAlt.Allocate();

                    sb.Append("<table");

                    if (scopedParams.TryGetValue("id", out object id))
                    {
                        sb.Append(" id=\"").Append(id).Append("\"");
                    }

                    sb.Append(" class=\"").Append(className).Append("\"");

                    sb.Append(">");

                    scopedParams.TryGetValue("caption", out object caption);
                    if (isEmpty)
                    {
                        caption = captionIfEmpty;
                    }

                    if (caption != null && !scopedParams.TryGetValue("hasCaption", out _))
                    {
                        sb.Append("<caption>").Append(caption.ToString().HtmlEncode()).Append("</caption>");
                        scopedParams["hasCaption"] = true;
                    }

                    if (!isEmpty)
                    {
                        sb.Append("<tbody>");

                        if (first is KeyValuePair <string, object> )
                        {
                            foreach (var o in objs)
                            {
                                if (o is KeyValuePair <string, object> kvp)
                                {
                                    if (kvp.Value == target)
                                    {
                                        break;                      // Prevent cyclical deps like 'it' binding
                                    }
                                    sb.Append("<tr>");
                                    sb.Append("<th>");
                                    sb.Append(Context.DefaultFilters?.textStyle(kvp.Key, headerStyle)?.HtmlEncode());
                                    sb.Append("</th>");
                                    sb.Append("<td>");
                                    if (!isComplexType(kvp.Value))
                                    {
                                        sb.Append(GetScalarHtml(kvp.Value));
                                    }
                                    else
                                    {
                                        var body = htmlDump(scope, kvp.Value, scopeOptions);
                                        sb.Append(body.ToRawString());
                                    }
                                    sb.Append("</td>");
                                    sb.Append("</tr>");
                                }
                            }
                        }
                        else if (!isComplexType(first))
                        {
                            foreach (var o in objs)
                            {
                                sb.Append("<tr>");
                                sb.Append("<td>");
                                sb.Append(GetScalarHtml(o));
                                sb.Append("</td>");
                                sb.Append("</tr>");
                            }
                        }
                        else
                        {
                            if (objs.Count > 1)
                            {
                                var rows = objs.Map(x => x.ToObjectDictionary());
                                sb.Append("<tr>");
                                sb.Append("<td>");
                                var list = htmlList(scope, rows, scopeOptions);
                                sb.Append(list.ToRawString());
                                sb.Append("</td>");
                                sb.Append("</tr>");
                            }
                            else
                            {
                                foreach (var o in objs)
                                {
                                    sb.Append("<tr>");

                                    if (!isComplexType(o))
                                    {
                                        sb.Append("<td>");
                                        sb.Append(GetScalarHtml(o));
                                        sb.Append("</td>");
                                    }
                                    else
                                    {
                                        sb.Append("<td>");
                                        var body = htmlDump(scope, o, scopeOptions);
                                        sb.Append(body.ToRawString());
                                        sb.Append("</td>");
                                    }

                                    sb.Append("</tr>");
                                }
                            }
                        }

                        sb.Append("</tbody>");
                    }

                    sb.Append("</table>");

                    var html = StringBuilderCacheAlt.ReturnAndFree(sb);
                    return(html.ToRawString());
                }

                return(htmlDump(scope, target.ToObjectDictionary(), scopeOptions));
            }
            finally
            {
                scopedParams["depth"] = depth;
            }
        }
        public IRawString htmlList(TemplateScopeContext scope, object target, object scopeOptions)
        {
            if (target is IDictionary <string, object> single)
            {
                target = new[] { single }
            }
            ;

            var items        = target.AssertEnumerable(nameof(htmlList));
            var scopedParams = scope.AssertOptions(nameof(htmlList), scopeOptions);
            var depth        = scopedParams.TryGetValue("depth", out object oDepth) ? (int)oDepth : 0;
            var childDepth   = scopedParams.TryGetValue("childDepth", out object oChildDepth) ? oChildDepth.ConvertTo <int>() : 1;

            scopedParams["depth"] = depth + 1;

            try
            {
                scopedParams.TryGetValue("className", out object parentClass);
                scopedParams.TryGetValue("childClass", out object childClass);
                var className = ((depth < childDepth ? parentClass : childClass ?? parentClass)
                                 ?? Context.Args[TemplateConstants.DefaultTableClassName]).ToString();

                scopedParams.TryGetValue("headerStyle", out object oHeaderStyle);
                scopedParams.TryGetValue("headerTag", out object oHeaderTag);
                scopedParams.TryGetValue("captionIfEmpty", out object captionIfEmpty);
                var headerTag   = oHeaderTag as string ?? "th";
                var headerStyle = oHeaderStyle as string ?? "splitCase";

                var           sbHeader = StringBuilderCache.Allocate();
                var           sbRows   = StringBuilderCacheAlt.Allocate();
                List <string> keys     = null;

                foreach (var item in items)
                {
                    if (item is IDictionary <string, object> d)
                    {
                        if (keys == null)
                        {
                            keys = d.Keys.ToList();
                            sbHeader.Append("<tr>");
                            foreach (var key in keys)
                            {
                                sbHeader.Append('<').Append(headerTag).Append('>');
                                sbHeader.Append(Context.DefaultFilters?.textStyle(key, headerStyle)?.HtmlEncode());
                                sbHeader.Append("</").Append(headerTag).Append('>');
                            }
                            sbHeader.Append("</tr>");
                        }

                        sbRows.Append("<tr>");
                        foreach (var key in keys)
                        {
                            var value = d[key];
                            if (value == target)
                            {
                                break;                  // Prevent cyclical deps like 'it' binding
                            }
                            sbRows.Append("<td>");

                            if (!isComplexType(value))
                            {
                                sbRows.Append(GetScalarHtml(value));
                            }
                            else
                            {
                                var htmlValue = htmlDump(scope, value, scopeOptions);
                                sbRows.Append(htmlValue.ToRawString());
                            }

                            sbRows.Append("</td>");
                        }
                        sbRows.Append("</tr>");
                    }
                }

                var isEmpty = sbRows.Length == 0;
                if (isEmpty && captionIfEmpty == null)
                {
                    return(RawString.Empty);
                }

                var htmlHeaders = StringBuilderCache.ReturnAndFree(sbHeader);
                var htmlRows    = StringBuilderCacheAlt.ReturnAndFree(sbRows);

                var sb = StringBuilderCache.Allocate();
                sb.Append("<table");

                if (scopedParams.TryGetValue("id", out object id))
                {
                    sb.Append(" id=\"").Append(id).Append("\"");
                }
                if (!string.IsNullOrEmpty(className))
                {
                    sb.Append(" class=\"").Append(className).Append("\"");
                }

                sb.Append(">");

                scopedParams.TryGetValue("caption", out object caption);
                if (isEmpty)
                {
                    caption = captionIfEmpty;
                }

                if (caption != null && !scopedParams.TryGetValue("hasCaption", out _))
                {
                    sb.Append("<caption>").Append(caption.ToString().HtmlEncode()).Append("</caption>");
                    scopedParams["hasCaption"] = true;
                }

                if (htmlHeaders.Length > 0)
                {
                    sb.Append("<thead>").Append(htmlHeaders).Append("</thead>");
                }
                if (htmlRows.Length > 0)
                {
                    sb.Append("<tbody>").Append(htmlRows).Append("</tbody>");
                }

                sb.Append("</table>");

                var html = StringBuilderCache.ReturnAndFree(sb);
                return(html.ToRawString());
            }
            finally
            {
                scopedParams["depth"] = depth;
            }
        }
示例#8
0
        public IRawString textDump(TemplateScopeContext scope, object target, object scopeOptions)
        {
            var scopedParams = scope.AssertOptions(nameof(textDump), scopeOptions);
            var depth        = scopedParams.TryGetValue("depth", out object oDepth) ? (int)oDepth : 0;

            scopedParams["depth"] = depth + 1;

            try
            {
                if (!isComplexType(target))
                {
                    return(GetScalarText(target).ToRawString());
                }

                scopedParams.TryGetValue("captionIfEmpty", out object captionIfEmpty);
                scopedParams.TryGetValue("headerStyle", out object oHeaderStyle);
                var headerStyle = oHeaderStyle as string ?? "splitCase";

                if (target is IEnumerable e)
                {
                    var objs = e.Map(x => x);

                    var isEmpty = objs.Count == 0;
                    if (isEmpty && captionIfEmpty == null)
                    {
                        return(RawString.Empty);
                    }

                    var first = !isEmpty ? objs[0] : null;
                    if (first is IDictionary)
                    {
                        return(textList(scope, target, scopeOptions));
                    }

                    scopedParams.TryGetValue("caption", out object caption);
                    if (isEmpty)
                    {
                        caption = captionIfEmpty;
                    }

                    var sb = StringBuilderCacheAlt.Allocate();

                    string writeCaption = null;
                    if (caption != null && !scopedParams.TryGetValue("hasCaption", out _))
                    {
                        writeCaption = caption.ToString();
                        scopedParams["hasCaption"] = true;
                    }

                    if (!isEmpty)
                    {
                        var keys   = new List <string>();
                        var values = new List <string>();

                        if (first is KeyValuePair <string, object> )
                        {
                            foreach (var o in objs)
                            {
                                if (o is KeyValuePair <string, object> kvp)
                                {
                                    if (kvp.Value == target)
                                    {
                                        break;                      // Prevent cyclical deps like 'it' binding
                                    }
                                    keys.Add(Context.DefaultFilters?.textStyle(kvp.Key, headerStyle) ?? "");

                                    var field = !isComplexType(kvp.Value)
                                        ? GetScalarText(kvp.Value)
                                        : textDump(scope, kvp.Value, scopeOptions).ToRawString();

                                    values.Add(field);
                                }
                            }

                            var keySize    = keys.Max(x => x.Length);
                            var valuesSize = values.Max(x => x.Length);

                            sb.AppendLine(writeCaption != null
                                ? $"| {writeCaption} ||"
                                : $"|||");
                            sb.AppendLine("|-|-|");

                            for (var i = 0; i < keys.Count; i++)
                            {
                                sb.Append("| ")
                                .Append(keys[i].PadRight(keySize, ' '))
                                .Append(" | ")
                                .Append(values[i].PadRight(valuesSize, ' '))
                                .Append(" |")
                                .AppendLine();
                            }
                        }
                        else
                        {
                            if (!isComplexType(first))
                            {
                                sb.AppendLine(writeCaption != null
                                    ? $"| {writeCaption} |"
                                    : $"||");
                                sb.AppendLine("|-|");

                                foreach (var o in objs)
                                {
                                    sb.Append("| ")
                                    .Append(GetScalarText(o))
                                    .Append(" |")
                                    .AppendLine();
                                }
                            }
                            else
                            {
                                if (writeCaption != null)
                                {
                                    sb.AppendLine(writeCaption);
                                }

                                if (objs.Count > 1)
                                {
                                    var rows = objs.Map(x => x.ToObjectDictionary());
                                    var list = textList(scope, rows, scopeOptions).ToRawString();
                                    sb.AppendLine(list);
                                }
                                else
                                {
                                    foreach (var o in objs)
                                    {
                                        if (!isComplexType(o))
                                        {
                                            sb.AppendLine(GetScalarText(o));
                                        }
                                        else
                                        {
                                            var body = textDump(scope, o, scopeOptions).ToRawString();
                                            sb.AppendLine(body);
                                        }
                                    }
                                }
                            }
                        }
                    }

                    return(StringBuilderCache.ReturnAndFree(sb).ToRawString());
                }

                return(textDump(scope, target.ToObjectDictionary(), scopeOptions));
            }
            finally
            {
                scopedParams["depth"] = depth;
            }
        }
示例#9
0
        public IRawString textList(TemplateScopeContext scope, object target, object scopeOptions)
        {
            if (target is IDictionary <string, object> single)
            {
                target = new[] { single }
            }
            ;

            var items        = target.AssertEnumerable(nameof(textList));
            var scopedParams = scope.AssertOptions(nameof(textList), scopeOptions);
            var depth        = scopedParams.TryGetValue("depth", out object oDepth) ? (int)oDepth : 0;

            scopedParams["depth"] = depth + 1;

            try
            {
                scopedParams.TryGetValue("headerStyle", out object oHeaderStyle);
                scopedParams.TryGetValue("captionIfEmpty", out object captionIfEmpty);
                var headerStyle = oHeaderStyle as string ?? "splitCase";

                List <string> keys = null;

                var table = new MarkdownTable();

                if (scopedParams.TryGetValue("rowNumbers", out object rowNumbers))
                {
                    table.IncludeRowNumbers = !(rowNumbers is bool b) || b;
                }

                foreach (var item in items)
                {
                    if (item is IDictionary <string, object> d)
                    {
                        if (keys == null)
                        {
                            keys = d.Keys.ToList();
                            foreach (var key in keys)
                            {
                                table.Headers.Add(Context.DefaultFilters?.textStyle(key, headerStyle));
                            }
                        }

                        var row = new List <string>();

                        foreach (var key in keys)
                        {
                            var value = d[key];
                            if (value == target)
                            {
                                break;                  // Prevent cyclical deps like 'it' binding
                            }
                            if (!isComplexType(value))
                            {
                                row.Add(GetScalarText(value));
                            }
                            else
                            {
                                var cellValue = textDump(scope, value, scopeOptions);
                                row.Add(cellValue.ToRawString());
                            }
                        }
                        table.Rows.Add(row);
                    }
                }

                var isEmpty = table.Rows.Count == 0;
                if (isEmpty && captionIfEmpty == null)
                {
                    return(RawString.Empty);
                }

                scopedParams.TryGetValue("caption", out object caption);
                if (isEmpty)
                {
                    caption = captionIfEmpty;
                }

                if (caption != null && !scopedParams.TryGetValue("hasCaption", out _))
                {
                    table.Caption = caption.ToString();
                    scopedParams["hasCaption"] = true;
                }

                var txt = table.Render();
                return(txt.ToRawString());
            }
            finally
            {
                scopedParams["depth"] = depth;
            }
        }
        public IRawString htmltable(TemplateScopeContext scope, object target, object scopeOptions)
        {
            if (target is IDictionary <string, object> single)
            {
                target = new[] { single }
            }
            ;

            var items        = target.AssertEnumerable(nameof(htmltable));
            var scopedParams = scope.AssertOptions(nameof(htmltable), scopeOptions);

            scopedParams.TryGetValue("headerStyle", out object oHeaderStyle);
            scopedParams.TryGetValue("headerTag", out object oHeaderTag);
            scopedParams.TryGetValue("emptyCaption", out object emptyCaption);
            var headerTag   = oHeaderTag as string ?? "th";
            var headerStyle = oHeaderStyle as string ?? "splitCase";

            var           sbHeader = StringBuilderCache.Allocate();
            var           sbRows   = StringBuilderCacheAlt.Allocate();
            List <string> keys     = null;

            foreach (var item in items)
            {
                if (item is IDictionary <string, object> d)
                {
                    if (keys == null)
                    {
                        keys = d.Keys.ToList();
                        sbHeader.Append("<tr>");
                        foreach (var key in keys)
                        {
                            sbHeader.Append('<').Append(headerTag).Append('>');
                            sbHeader.Append(TemplateDefaultFilters.Instance.textStyle(key, headerStyle)?.HtmlEncode());
                            sbHeader.Append("</").Append(headerTag).Append('>');
                        }
                        sbHeader.Append("</tr>");
                    }

                    sbRows.Append("<tr>");
                    foreach (var key in keys)
                    {
                        var value        = d[key];
                        var encodedValue = value?.ToString()?.HtmlEncode();
                        sbRows.Append("<td>").Append(encodedValue).Append("</td>");
                    }
                    sbRows.Append("</tr>");
                }
            }

            var isEmpty = sbRows.Length == 0;

            if (isEmpty && emptyCaption == null)
            {
                return(RawString.Empty);
            }

            var htmlHeaders = StringBuilderCache.ReturnAndFree(sbHeader);
            var htmlRows    = StringBuilderCacheAlt.ReturnAndFree(sbRows);

            var sb = StringBuilderCache.Allocate();

            sb.Append("<table");

            if (scopedParams.TryGetValue("id", out object id))
            {
                sb.Append(" id=\"").Append(id).Append("\"");
            }
            if (scopedParams.TryGetValue("className", out object className))
            {
                sb.Append(" class=\"").Append(className).Append("\"");
            }

            sb.Append(">");

            scopedParams.TryGetValue("caption", out object caption);
            if (isEmpty)
            {
                caption = emptyCaption;
            }

            if (caption != null)
            {
                sb.Append("<caption>").Append(caption.ToString().HtmlEncode()).Append("</caption>");
            }

            if (htmlHeaders.Length > 0)
            {
                sb.Append("<thead>").Append(htmlHeaders).Append("</thead>");
            }
            if (htmlRows.Length > 0)
            {
                sb.Append("<tbody>").Append(htmlRows).Append("</tbody>");
            }

            sb.Append("</table>");

            var html = StringBuilderCache.ReturnAndFree(sb);

            return(html.ToRawString());
        }
    }