feat: publish JPW7 web editor
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { parseJpwabc, hasExactRoundTrip } from "../dist/domain/jpwabc.js";
|
||||
|
||||
const manifest = JSON.parse(await readFile("fixtures/manifest.json", "utf8"));
|
||||
const rows = [];
|
||||
const failures = [];
|
||||
for (const fixture of manifest.samples) {
|
||||
try {
|
||||
const bytes = await readFile(fixture.rawPath);
|
||||
const result = parseJpwabc(bytes);
|
||||
const exactRoundTrip = hasExactRoundTrip(result);
|
||||
const errors = result.diagnostics.filter((item) => item.severity === "error");
|
||||
const warnings = result.diagnostics.filter((item) => item.severity === "warning");
|
||||
const status = errors.length ? "recovered-error" : warnings.length || result.unknownTokens.length || result.unknownBlocks.length ? "parsed-with-diagnostics" : "parsed-clean";
|
||||
const row = {
|
||||
id: fixture.id,
|
||||
rawPath: fixture.rawPath,
|
||||
decodedPath: fixture.decodedPath,
|
||||
rawSha256: fixture.rawSha256,
|
||||
rawBytes: bytes.byteLength,
|
||||
status,
|
||||
sourcePreserved: exactRoundTrip,
|
||||
encoding: result.source.encoding,
|
||||
bom: result.source.bom,
|
||||
formatVersion: result.header.formatVersion,
|
||||
productVersion: result.header.productVersion,
|
||||
sections: result.sections.map((section) => ({ name: section.name, known: section.known, lineCount: section.lines.length })),
|
||||
semanticCounts: {
|
||||
options: result.options.document.length,
|
||||
unknownOptions: result.options.unknownDocument.length,
|
||||
fonts: result.fonts.length,
|
||||
titles: result.titles.length,
|
||||
voiceLines: result.voices.length,
|
||||
voiceTokens: result.voices.reduce((count, line) => count + line.tokens.length, 0),
|
||||
wordBlocks: result.words.length,
|
||||
attachments: result.attachments.length,
|
||||
pages: result.pages.length
|
||||
},
|
||||
preservationCounts: {
|
||||
rawTokens: result.rawTokens.length,
|
||||
unknownTokens: result.unknownTokens.length,
|
||||
unknownBlocks: result.unknownBlocks.length
|
||||
},
|
||||
diagnostics: result.diagnostics.map(({ code, severity, message, recovery }) => ({ code, severity, message, recovery })),
|
||||
compatibility: result.compatibility
|
||||
};
|
||||
rows.push(row);
|
||||
if (!exactRoundTrip) failures.push(`${fixture.id}: source bytes changed on no-edit serialization`);
|
||||
} catch (error) {
|
||||
failures.push(`${fixture.id}: parser threw ${error instanceof Error ? error.message : String(error)}`);
|
||||
rows.push({ id: fixture.id, rawPath: fixture.rawPath, status: "parser-threw", sourcePreserved: false, error: String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
if (rows.length !== manifest.sampleCount || rows.length !== 33) failures.push(`expected 33 classifications, got ${rows.length}`);
|
||||
if (rows.some((row) => !row.status || row.sourcePreserved !== true)) failures.push("one or more samples has no status or failed source preservation");
|
||||
|
||||
const report = {
|
||||
reportVersion: 1,
|
||||
milestone: "M1",
|
||||
purpose: "33-sample parse, preservation and diagnostic classification; not an external JPW7 compatibility pass",
|
||||
sampleCountExpected: 33,
|
||||
sampleCountActual: rows.length,
|
||||
statusCounts: Object.fromEntries([...new Set(rows.map((row) => row.status))].sort().map((status) => [status, rows.filter((row) => row.status === status).length])),
|
||||
allSourceBytesPreserved: failures.length === 0,
|
||||
failures,
|
||||
samples: rows
|
||||
};
|
||||
await mkdir("acceptance/m1", { recursive: true });
|
||||
await writeFile("acceptance/m1/sample-classification.json", `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
||||
const markdown = [
|
||||
"# M1 33 样例分类报告",
|
||||
"",
|
||||
"> 分类、诊断和保留结果是 Web parser 的实测结果,不代表 JPW7 原程序兼容通过;外部回验仍由 R0/R1 单独执行。",
|
||||
"",
|
||||
`- 样例:${report.sampleCountActual}/${report.sampleCountExpected}`,
|
||||
`- 无编辑字节级保留:${report.allSourceBytesPreserved ? "是" : "否"}`,
|
||||
`- 状态:${JSON.stringify(report.statusCounts)}`,
|
||||
`- 失败:${report.failures.length}`,
|
||||
"",
|
||||
"| 样例 | 状态 | 编码/BOM | 区块 | 未知 token | 未知区块 | 诊断 |",
|
||||
"|---|---|---|---:|---:|---:|---:|",
|
||||
...rows.map((row) => `| ${row.id} | ${row.status} | ${row.encoding}/${row.bom} | ${row.sections?.length ?? 0} | ${row.preservationCounts?.unknownTokens ?? "-"} | ${row.preservationCounts?.unknownBlocks ?? "-"} | ${row.diagnostics?.length ?? "-"} |`),
|
||||
"",
|
||||
"## 解释边界",
|
||||
"",
|
||||
"- `parsed-with-diagnostics` 表示语义/未知内容被保留并有报告,不是全量语法支持声明。",
|
||||
"- `recovered-error` 表示解析器发现错误但保留了原始字节;安全兼容导出必须阻止。",
|
||||
"- `sourcePreserved` 仅证明无编辑序列化稳定,不替代 JPW7 原程序回验。",
|
||||
""
|
||||
].join("\n");
|
||||
await writeFile("acceptance/m1/sample-classification.md", markdown, "utf8");
|
||||
console.log(JSON.stringify({ ok: failures.length === 0, samples: rows.length, statusCounts: report.statusCounts, failures: failures.length }, null, 2));
|
||||
if (failures.length) process.exitCode = 1;
|
||||
Reference in New Issue
Block a user