using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using HtmlAgilityPack; namespace AIStudio.Assistants.VisualBriefing; public sealed partial class VisualBriefingArtifactService { /// /// Matches the version-independent artifact header at the start of standalone HTML. /// private static readonly Regex HEADER_REGEX = HeaderRegex(); /// /// Matches the version-independent artifact header at the start of standalone HTML. /// [GeneratedRegex(@"\A\n\n", RegexOptions.CultureInvariant)] private static partial Regex HeaderRegex(); /// /// Matches the generated presentation stylesheet. /// private static readonly Regex STYLE_REGEX = StyleRegex(); /// /// Matches the generated presentation stylesheet. /// [GeneratedRegex("""(?[\s\S]*?)""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] private static partial Regex StyleRegex(); /// /// Matches the embedded declarative runtime. /// private static readonly Regex RUNTIME_REGEX = RuntimeRegex(); /// /// Matches the embedded declarative runtime. /// [GeneratedRegex("""(?[\s\S]*?)""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] private static partial Regex RuntimeRegex(); /// /// Matches the optional embedded chart runtime. /// private static readonly Regex ECHARTS_REGEX = EChartsRegex(); /// /// Matches the optional embedded chart runtime. /// [GeneratedRegex("""(?[\s\S]*?)""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] private static partial Regex EChartsRegex(); /// /// Reads an intact standalone artifact without applying current compiler or runtime rules. /// public static bool TryParse(string html, out VisualBriefingArtifactParts parts, out string issue) { parts = null!; issue = string.Empty; if (string.IsNullOrWhiteSpace(html)) { issue = "The briefing file is empty."; return false; } if (!html.EndsWith("", StringComparison.Ordinal)) { issue = "The briefing document wrapper is invalid or incomplete."; return false; } var headerMatch = HEADER_REGEX.Match(html); if (!headerMatch.Success) { issue = "The briefing artifact header is missing or misplaced."; return false; } VisualBriefingExportManifest? exportManifest; try { var json = Encoding.UTF8.GetString(Convert.FromBase64String(headerMatch.Groups["value"].Value)); using var headerDocument = JsonDocument.Parse(json); exportManifest = HasDuplicateProperties(headerDocument.RootElement) ? null : headerDocument.RootElement.Deserialize(JSON_OPTIONS); } catch (Exception exception) when (exception is FormatException or JsonException) { issue = "The briefing artifact header is invalid."; return false; } if (!ValidateHeader(exportManifest, out issue)) return false; var documentHash = exportManifest!.DocumentHash; exportManifest.DocumentHash = DOCUMENT_HASH_PLACEHOLDER; var placeholderHeader = $"\n\n"; exportManifest.DocumentHash = documentHash; var placeholderDocument = placeholderHeader + html[headerMatch.Length..]; var computedDocumentHash = VisualBriefingHashing.Compute(placeholderDocument); if (!string.Equals(computedDocumentHash, documentHash, StringComparison.OrdinalIgnoreCase)) { issue = "The briefing document hash does not match its contents."; return false; } var document = new HtmlDocument(); document.LoadHtml(html); var htmlNode = FindUniqueNode(document, "//html"); var headNode = FindUniqueNode(document, "//head"); var bodyNode = FindUniqueNode(document, "//body"); var dataNode = FindUniqueElementById(document, DATA_ELEMENT_ID); var rootNode = FindUniqueElementById(document, "mwai-briefing-root"); var footerNode = FindUniqueElementById(document, "mwai-static-footer"); var headerNodes = FindNodes(document.DocumentNode, "//*[@id='mwai-static-header']")?.ToArray() ?? []; var generatedStyleNode = FindUniqueElementById(document, "mwai-briefing-style"); var protectedStyleNode = FindUniqueElementById(document, "mwai-protected-style"); var runtimeNode = FindUniqueElementById(document, "mwai-briefing-runtime"); var echartsNode = FindUniqueElementById(document, "mwai-echarts-runtime"); var styleMatch = STYLE_REGEX.Match(html); var runtimeMatch = RUNTIME_REGEX.Match(html); var echartsMatch = ECHARTS_REGEX.Match(html); if (htmlNode is null || headNode is null || bodyNode is null || dataNode is null || rootNode is null || footerNode is null || generatedStyleNode is null || protectedStyleNode is null || runtimeNode is null || headerNodes.Length > 1 || (headerNodes.Length == 1 && (!headerNodes[0].Name.Equals("header", StringComparison.OrdinalIgnoreCase) || headerNodes[0].ParentNode != bodyNode)) || !styleMatch.Success || !runtimeMatch.Success || (echartsNode is not null) != echartsMatch.Success) { issue = "The briefing envelope is incomplete or ambiguous."; return false; } var scriptNodes = FindNodes(document.DocumentNode, "//script")?.ToArray() ?? []; var styleNodes = FindNodes(document.DocumentNode, "//style")?.ToArray() ?? []; if (scriptNodes.Any(node => node.Id is not DATA_ELEMENT_ID and not "mwai-echarts-runtime" and not "mwai-briefing-runtime") || scriptNodes.Count(node => node.Id == DATA_ELEMENT_ID) != 1 || scriptNodes.Count(node => node.Id == "mwai-briefing-runtime") != 1 || scriptNodes.Count(node => node.Id == "mwai-echarts-runtime") > 1 || styleNodes.Length != 2 || styleNodes.Count(node => node.Id == "mwai-briefing-style") != 1 || styleNodes.Count(node => node.Id == "mwai-protected-style") != 1 || !string.Equals(dataNode.GetAttributeValue("type", string.Empty), "application/json", StringComparison.OrdinalIgnoreCase)) { issue = "The briefing contains unknown or duplicated executable resources."; return false; } var bodyChildren = FindNodes(document.DocumentNode, "//body/*")?.ToArray() ?? []; var allowedBodyIds = new HashSet(StringComparer.Ordinal) { DATA_ELEMENT_ID, "mwai-static-header", "mwai-briefing-root", "mwai-static-footer", "mwai-echarts-runtime", "mwai-briefing-runtime", }; if (bodyChildren.Any(node => !allowedBodyIds.Contains(node.Id)) || bodyChildren.Select(node => node.Id).Distinct(StringComparer.Ordinal).Count() != bodyChildren.Length) { issue = "The briefing body contains elements outside the stable artifact envelope."; return false; } JsonElement data; try { using var parsedData = JsonDocument.Parse(dataNode.InnerText); data = parsedData.RootElement.Clone(); } catch (JsonException) { issue = "The briefing data block is invalid."; return false; } var template = CanonicalizeTemplate(rootNode.InnerHtml); var css = styleMatch.Groups["value"].Value.Trim(); var runtime = runtimeMatch.Groups["value"].Value; var echarts = echartsMatch.Success ? echartsMatch.Groups["value"].Value : null; parts = new(exportManifest, data, template, css, runtime, echarts, documentHash); var cspNodes = FindNodes(document.DocumentNode, "//meta[@http-equiv='Content-Security-Policy']")?.ToArray() ?? []; var actualCsp = cspNodes.Length == 1 ? cspNodes[0].GetAttributeValue("content", string.Empty) : string.Empty; if (!string.Equals(actualCsp, GetContentSecurityPolicy(parts), StringComparison.Ordinal)) { parts = null!; issue = "The briefing Content Security Policy is missing or inconsistent with its embedded scripts."; return false; } return true; } /// /// Reads an intact artifact and additionally applies the current semantic compiler contract. /// internal static bool TryParseForRecompile(string html, out VisualBriefingArtifactParts parts, out string issue) { if (!TryParse(html, out parts, out issue)) return false; if (parts.ExportManifest.SchemaVersion != VisualBriefingVersions.SCHEMA) { parts = null!; issue = "The briefing data schema is not compatible with the current compiler."; return false; } issue = ValidateProtectedData(parts.ExportManifest, parts.Data); if (!string.IsNullOrEmpty(issue)) { parts = null!; return false; } issue = ValidateGeneratedParts( null, parts.Data, parts.TemplateHtml, parts.Css, !string.IsNullOrWhiteSpace(parts.EChartsScript)); if (!string.IsNullOrEmpty(issue)) { parts = null!; return false; } return true; } /// /// Validates stable artifact-header fields without imposing current runtime or schema versions. /// private static bool ValidateHeader(VisualBriefingExportManifest? exportManifest, out string issue) { issue = string.Empty; if (exportManifest is null || exportManifest.ArtifactVersion != VisualBriefingVersions.ARTIFACT || exportManifest.SchemaVersion <= 0 || exportManifest.RuntimeVersion <= 0 || exportManifest.BriefingId == Guid.Empty || exportManifest.RevisionId == Guid.Empty || string.IsNullOrWhiteSpace(exportManifest.Name) || string.IsNullOrWhiteSpace(exportManifest.AIStudioVersion) || string.IsNullOrWhiteSpace(exportManifest.RuntimeAIStudioVersion) || exportManifest.DocumentHash.Length != 64 || !exportManifest.DocumentHash.All(Uri.IsHexDigit) || exportManifest.TargetLanguage is CommonLanguages.OTHER && string.IsNullOrWhiteSpace(exportManifest.CustomTargetLanguage) || exportManifest.ProtectionLevel is VisualBriefingProtectionLevel.OTHER && string.IsNullOrWhiteSpace(exportManifest.CustomProtectionLevel)) { issue = "The briefing artifact header contains invalid or unsupported metadata."; return false; } return true; } /// /// Finds exactly one node for an XPath expression. /// private static HtmlNode? FindUniqueNode(HtmlDocument document, string xpath) { var nodes = FindNodes(document.DocumentNode, xpath)?.ToArray() ?? []; return nodes.Length == 1 ? nodes[0] : null; } /// /// Finds exactly one element by ID. /// private static HtmlNode? FindUniqueElementById(HtmlDocument document, string id) { var nodes = FindNodes(document.DocumentNode, $"//*[@id='{id}']")?.ToArray() ?? []; return nodes.Length == 1 ? nodes[0] : null; } /// /// Validates current protected data needed for recompilation. /// private static string ValidateProtectedData(VisualBriefingExportManifest exportManifest, JsonElement data) { if (!data.TryGetProperty("_mwai", out var protectedData) || protectedData.ValueKind is not JsonValueKind.Object || !protectedData.TryGetProperty("schemaVersion", out var schemaVersion) || schemaVersion.ValueKind is not JsonValueKind.Number || !schemaVersion.TryGetInt32(out var parsedSchemaVersion) || parsedSchemaVersion != VisualBriefingVersions.SCHEMA || !protectedData.TryGetProperty("runtimeVersion", out var runtimeVersion) || runtimeVersion.ValueKind is not JsonValueKind.Number || !runtimeVersion.TryGetInt32(out var parsedRuntimeVersion) || parsedRuntimeVersion != exportManifest.RuntimeVersion || !protectedData.TryGetProperty("aiStudioVersion", out var aiStudioVersion) || aiStudioVersion.ValueKind is not JsonValueKind.String || !string.Equals(aiStudioVersion.GetString(), exportManifest.AIStudioVersion, StringComparison.Ordinal) || !protectedData.TryGetProperty("assets", out var protectedAssets) || protectedAssets.ValueKind is not JsonValueKind.Object || data.TryGetProperty("assets", out _)) return "The protected briefing data block is incomplete or inconsistent."; var protectedAssetProperties = protectedAssets.EnumerateObject().ToArray(); if (protectedAssetProperties.Any(property => property.Value.ValueKind is not JsonValueKind.String || !property.Value.GetString()!.StartsWith("data:image/", StringComparison.Ordinal)) || protectedAssetProperties.Select(property => property.Name).Distinct(StringComparer.Ordinal).Count() != protectedAssetProperties.Length) return "The protected embedded asset map contains invalid or duplicated entries."; if (!protectedData.TryGetProperty("assetMetadata", out var assetMetadata) || assetMetadata.ValueKind is not JsonValueKind.Object) return "The protected visual asset metadata is missing."; var metadataProperties = assetMetadata.EnumerateObject().ToArray(); if (metadataProperties.Length != protectedAssetProperties.Length || metadataProperties.Any(property => !protectedAssets.TryGetProperty(property.Name, out _) || property.Value.ValueKind is not JsonValueKind.Object || !property.Value.TryGetProperty("description", out var description) || description.ValueKind is not JsonValueKind.String || string.IsNullOrWhiteSpace(description.GetString()) || !property.Value.TryGetProperty("altText", out var altText) || altText.ValueKind is not JsonValueKind.String || string.IsNullOrWhiteSpace(altText.GetString()))) return "The protected visual asset metadata is invalid or incomplete."; if (!protectedData.TryGetProperty("footer", out var footer) || footer.ValueKind is not JsonValueKind.Object) return "The protected briefing footer data is missing."; string[] footerFields = ["createdWith", "models", "createdAt", "authors", "protection"]; return footerFields.Any(field => !footer.TryGetProperty(field, out var value) || value.ValueKind is not JsonValueKind.String || string.IsNullOrWhiteSpace(value.GetString())) ? "The protected briefing footer data is incomplete." : string.Empty; } }