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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.RegularExpressions;
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
using XFESystem.Browser;
using XFESystem.Documents;

if(args.Length!=3)throw new ArgumentException("repository root, website root and output file are required");
string root=Path.GetFullPath(args[0]),website=Path.GetFullPath(args[1]),output=Path.GetFullPath(args[2]);
var parser=new HtmlParser();
// Use the real generated article shell and its exact stylesheet order. No JS,
// Monaco placeholder, host browser or hand-written selector precedence is used.
var document=parser.ParseDocument(new DocumentPreviewBuilder(new()).MarkdownWeb("",dark:true).Html);
var manifest=JsonDocument.Parse(WebsiteAssets.ReadText("Exact/SourceManifest.json"));
var cssSources=new List<object>();var sheets=new List<BrowserStyleSheet>();
var selectors=new HashSet<string>(StringComparer.Ordinal);
foreach(var link in document.QuerySelectorAll("link[rel='stylesheet']"))
{
    string url=link.GetAttribute("href")!;
    var resource=WebsiteAssets.WebResources.Single(entry=>entry.UrlPath==url);
    byte[] embedded;using(var stream=WebsiteAssets.Open(resource.AssetName)){using var bytes=new MemoryStream();stream.CopyTo(bytes);embedded=bytes.ToArray();}
    var entry=manifest.RootElement.GetProperty("Entries").EnumerateArray().Single(value=>value.TryGetProperty("UrlPath",out var path)&&path.GetString()==url);
    string sourcePath=entry.GetProperty("SourcePath").GetString()!;
    byte[] original=File.ReadAllBytes(Path.Combine(website,sourcePath));
    string sha=Convert.ToHexStringLower(SHA256.HashData(embedded));
    if(!embedded.AsSpan().SequenceEqual(original)||sha!=entry.GetProperty("SourceSha256").GetString())throw new InvalidOperationException("Website stylesheet bytes differ: "+url);
    cssSources.Add(new{UrlPath=url,SourcePath=sourcePath,SnapshotPath="Source/XFESystem.Documents/Assets/Website/"+resource.AssetName,Sha256=sha,Bytes=embedded.Length});
    string css=System.Text.Encoding.UTF8.GetString(embedded);
    sheets.Add(new(css,link));
    foreach(Match rule in Regex.Matches(Regex.Replace(css,@"/\*.*?\*/","",RegexOptions.Singleline),@"([^{}]+)\{([^{}]*)\}"))
        foreach(string candidate in rule.Groups[1].Value.Split(','))
        {
            string selector=candidate.Trim();
            if(selector!=".hljs"&&selector.Contains(".hljs-",StringComparison.Ordinal)&&Regex.IsMatch(selector,@"^(?:\.[\w-]+)+(?:\s+(?:\.[\w-]+)+)*$"))selectors.Add(selector);
        }
}
var article=document.QuerySelector(".markdown-body")!;
article.InnerHtml="<pre class='code-block-enhanced'><span class='code-block-viewport'><code class='language-csharp hljs'><span id='PalettePlain'>plain</span></code></span></pre>";
var code=article.QuerySelector("code")!;
var samples=new List<(string Selector,string[] Scopes,IElement Leaf)>{("<plain code text>",[],document.GetElementById("PalettePlain")!)};
foreach(string selector in selectors.Order(StringComparer.Ordinal))
{
    IElement parent=code;var scopes=new List<string>();
    foreach(string segment in Regex.Split(selector,@"\s+"))
    {
        var classes=Regex.Matches(segment,@"\.([\w-]+)").Select(match=>match.Groups[1].Value).ToArray();scopes.AddRange(classes);
        var span=document.CreateElement("span");span.ClassName=string.Join(' ',classes);parent.AppendChild(span);parent=span;
    }
    parent.TextContent="X";samples.Add((selector,scopes.ToArray(),parent));code.AppendChild(document.CreateTextNode(" "));
}
var layout=new BrowserLayoutEngine().Layout(document,new(800,600),sheets);
var rules=samples.Select(sample=>{
    var computed=layout.GetComputedStyle(sample.Leaf)??throw new InvalidOperationException("No computed token style: "+sample.Selector);
    string token=sample.Scopes.Length==0?"xfe":"xfe."+string.Join('.',sample.Scopes.Select(scope=>scope.StartsWith("hljs-",StringComparison.Ordinal)?scope[5..].Replace('-','_'):scope.Replace('-','_')));
    string fontStyle=(computed.Font.Italic?"italic ":"")+(computed.Font.Weight>=600?"bold":"");
    return new{Selector=sample.Selector,Scopes=sample.Scopes,Token=token,Foreground=computed.Color.Rgb.ToString("x6"),
        Background=computed.Background.Alpha>=1?computed.Background.Rgb.ToString("x6"):null,
        FontStyle=fontStyle.Trim(),ComputedFontFamily=computed.Font.Family};
}).ToArray();
string cascadeSha=Convert.ToHexStringLower(SHA256.HashData(JsonSerializer.SerializeToUtf8Bytes(cssSources)));
var result=new{Kind="WebsiteActualArticleCascadePalette",CascadeCssSha256=cascadeSha,CssSources=cssSources,
    CssSha256=cssSources.Select(value=>JsonSerializer.SerializeToElement(value)).Single(value=>value.GetProperty("UrlPath").GetString()=="/css/docs/code-style.css").GetProperty("Sha256").GetString(),
    Derivation="Real generated MarkdownWeb shell + all original styles in original order; production CSS specificity, inheritance and !important resolver; no last-rule grep",Rules=rules};
Directory.CreateDirectory(Path.GetDirectoryName(output)!);File.WriteAllText(output,JsonSerializer.Serialize(result,new JsonSerializerOptions{WriteIndented=true}));
Console.WriteLine(output);