XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Text;
using AngleSharp.Dom;

namespace XFESystem.Browser;

internal static class HtmlTextRenderer
{
    private static readonly HashSet<string> BlockTags = new(StringComparer.Ordinal)
    {
        "address", "article", "aside", "blockquote", "div", "dl", "dt", "dd", "fieldset", "figcaption",
        "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hr", "li", "main",
        "nav", "ol", "p", "pre", "section", "table", "tr", "ul"
    };

    public static string Render(IDocument document, Uri address, BrowserLimits limits)
    {
        var output = new StringBuilder();
        var links = new List<(string Label, Uri Address)>();
        var pending = new Stack<(INode Node, bool End, bool Pre)>();
        if (document.Body is not null) pending.Push((document.Body, false, false));
        var whitespacePending = false;

        void Append(string text)
        {
            if (text.Length > limits.MaximumOutputCharacters - output.Length)
                throw new BrowserLimitException("The rendered page exceeds its character limit.");
            output.Append(text);
        }
        void NewLine()
        {
            whitespacePending = false;
            if (output.Length != 0 && output[^1] != '\n') Append("\n");
        }

        while (pending.TryPop(out var entry))
        {
            if (entry.Node.NodeType == NodeType.Text)
            {
                foreach (var character in entry.Node.TextContent)
                {
                    if (entry.Pre)
                    {
                        if (!char.IsControl(character) || character is '\n' or '\t') Append(character.ToString());
                    }
                    else if (char.IsWhiteSpace(character)) whitespacePending = true;
                    else if (!char.IsControl(character))
                    {
                        if (whitespacePending && output.Length != 0 && output[^1] != '\n') Append(" ");
                        whitespacePending = false;
                        Append(character.ToString());
                    }
                }
                continue;
            }
            if (entry.Node is not IElement element) continue;
            var tag = element.LocalName;
            if (tag is "script" or "style" or "head" or "template" or "noscript" || element.HasAttribute("hidden")) continue;
            if (entry.End)
            {
                if (BlockTags.Contains(tag)) NewLine();
                if (tag == "a" && Uri.TryCreate(address, element.GetAttribute("href"), out var target) &&
                    target.Scheme is "http" or "https" && string.IsNullOrEmpty(target.UserInfo))
                {
                    if (links.Count >= 256) throw new BrowserLimitException("The page exceeds its link limit.");
                    links.Add((BoundedText.Safe(element.TextContent.Trim(), 256), target));
                    Append($" [{links.Count}]");
                }
                continue;
            }
            if (tag is "br" or "hr") { NewLine(); continue; }
            if (BlockTags.Contains(tag)) NewLine();
            if (tag == "li") Append("• ");
            if (tag == "img" && element.GetAttribute("alt") is { Length: > 0 } alt) Append("[" + BoundedText.Safe(alt, 512) + "]");
            pending.Push((element, true, entry.Pre));
            for (var index = element.ChildNodes.Length - 1; index >= 0; index--)
                pending.Push((element.ChildNodes[index], false, entry.Pre || tag == "pre"));
        }

        if (links.Count != 0)
        {
            NewLine();
            Append("\nLinks:\n");
            for (var index = 0; index < links.Count; index++)
                Append($"[{index + 1}] {links[index].Label} — {links[index].Address.AbsoluteUri}\n");
        }
        return output.ToString().TrimEnd();
    }
}