From 001036f9cd4c5223c67da075736d557288de2d28 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Thu, 9 Jan 2025 13:33:47 -0600 Subject: [PATCH] CLDR-16836 kbd: ebnf: also test XML files --- .../scripts/keyboard-abnf-tests/lib/index.mjs | 105 ++++++++++++++++++ .../{test => lib}/util.mjs | 3 +- .../keyboard-abnf-tests/package-lock.json | 27 +++++ .../scripts/keyboard-abnf-tests/package.json | 1 + .../test/abnf-valid.test.mjs | 2 +- .../test/datadriven.test.mjs | 23 ++-- .../keyboard-abnf-tests/test/xml.test.mjs | 22 ++++ 7 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 tools/scripts/keyboard-abnf-tests/lib/index.mjs rename tools/scripts/keyboard-abnf-tests/{test => lib}/util.mjs (92%) create mode 100644 tools/scripts/keyboard-abnf-tests/test/xml.test.mjs diff --git a/tools/scripts/keyboard-abnf-tests/lib/index.mjs b/tools/scripts/keyboard-abnf-tests/lib/index.mjs new file mode 100644 index 00000000000..302aca40b48 --- /dev/null +++ b/tools/scripts/keyboard-abnf-tests/lib/index.mjs @@ -0,0 +1,105 @@ +// Copyright (c) 2025 Unicode, Inc. +// For terms of use, see http://www.unicode.org/copyright.html +// SPDX-License-Identifier: Unicode-3.0 + +import { XMLParser } from "fast-xml-parser"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import * as abnf from "abnf"; +import peggy from "peggy"; + +/** relative path to ABNF */ +export const ABNF_DIR = "../../../keyboards/abnf"; + +/** + * @param {string} abnfPath path to .abnf file + * @returns the raw parser + */ +export async function getAbnfParser(abnfPath) { + const parsed = await abnf.parseFile(abnfPath); + const opts = { + grammarSource: abnfPath, + trace: false, + }; + const text = parsed.toFormat({ format: "peggy" }); + const parser = peggy.generate(text, opts); + + return parser; +} + +/** + * @param {string} abnfPath path to .abnf file + * @param {Object} parser parser from getAbnfParser + * @returns function taking a string and returning results (or throwing) + */ +export async function getParseFunction(abnfPath) { + const parser = await getAbnfParser(abnfPath); + const opts = { + grammarSource: abnfPath, + trace: false, + }; + const fn = (str) => parser.parse(str, opts); + return fn; +} + +/** @returns true if OK,otherwise throws */ +export async function checkXml(path) { + const parseFrom = await getParseFunction( + join(ABNF_DIR, "transform-from-required.abnf") + ); + const parseTo = await getParseFunction( + join(ABNF_DIR, "transform-to-required.abnf") + ); + + const text = readFileSync(path); + const parser = new XMLParser({ + ignoreAttributes: false, + trimValues: false, + htmlEntities: true, + }); + const r = parser.parse(text, false); + + let transforms = r?.keyboard3?.transforms; + + if (!transforms) return true; // no transforms + + if (!Array.isArray(transforms)) { + transforms = [transforms]; + } + + for (const transformSet of transforms) { + let transformGroups = transformSet?.transformGroup; + + if (!transformGroups) continue; // no transforms + + if (!Array.isArray(transformGroups)) { + // there was only one transformGroup + transformGroups = [transformGroups]; + } + + for (const transformGroup of transformGroups) { + let transforms = transformGroup?.transform; + if (!transforms) continue; + if (!Array.isArray(transforms)) { + transforms = [transforms]; + } + for (const transform of transforms) { + const fromStr = transform["@_from"]; + try { + parseFrom(fromStr); + } catch (e) { + throw Error(`Bad from="${fromStr}"`, { cause: e }); + } + const toStr = transform["@_to"]; + if (toStr) { + try { + parseTo(toStr); + } catch (e) { + throw Error(`Bad to="${toStr}"`, { cause: e }); + } + } + } + } + } + return true; +} diff --git a/tools/scripts/keyboard-abnf-tests/test/util.mjs b/tools/scripts/keyboard-abnf-tests/lib/util.mjs similarity index 92% rename from tools/scripts/keyboard-abnf-tests/test/util.mjs rename to tools/scripts/keyboard-abnf-tests/lib/util.mjs index 1f864445d07..dffabd4c463 100644 --- a/tools/scripts/keyboard-abnf-tests/test/util.mjs +++ b/tools/scripts/keyboard-abnf-tests/lib/util.mjs @@ -4,8 +4,7 @@ import { readFileSync, readdirSync } from "node:fs"; import { join } from "node:path"; - -export const ABNF_DIR = "../../../keyboards/abnf"; +import { ABNF_DIR } from "./index.mjs"; /** * diff --git a/tools/scripts/keyboard-abnf-tests/package-lock.json b/tools/scripts/keyboard-abnf-tests/package-lock.json index 41c066fa095..37e8089e870 100644 --- a/tools/scripts/keyboard-abnf-tests/package-lock.json +++ b/tools/scripts/keyboard-abnf-tests/package-lock.json @@ -10,6 +10,7 @@ "license": "Unicode-3.0", "dependencies": { "abnf": "^4.3.1", + "fast-xml-parser": "^4.5.1", "peggy": "^4.2.0" } }, @@ -50,6 +51,27 @@ "node": ">=18" } }, + "node_modules/fast-xml-parser": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.1.tgz", + "integrity": "sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/peggy": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/peggy/-/peggy-4.2.0.tgz", @@ -92,6 +114,11 @@ "engines": { "node": ">= 10" } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" } } } diff --git a/tools/scripts/keyboard-abnf-tests/package.json b/tools/scripts/keyboard-abnf-tests/package.json index 6fd3e260da9..1bcfdb96f4a 100644 --- a/tools/scripts/keyboard-abnf-tests/package.json +++ b/tools/scripts/keyboard-abnf-tests/package.json @@ -12,6 +12,7 @@ "private": true, "dependencies": { "abnf": "^4.3.1", + "fast-xml-parser": "^4.5.1", "peggy": "^4.2.0" } } diff --git a/tools/scripts/keyboard-abnf-tests/test/abnf-valid.test.mjs b/tools/scripts/keyboard-abnf-tests/test/abnf-valid.test.mjs index 352c6c8b984..91b8e005446 100644 --- a/tools/scripts/keyboard-abnf-tests/test/abnf-valid.test.mjs +++ b/tools/scripts/keyboard-abnf-tests/test/abnf-valid.test.mjs @@ -5,7 +5,7 @@ import * as abnf from "abnf"; import { test } from "node:test"; import * as assert from "node:assert"; -import { forEachAbnf } from "./util.mjs"; +import { forEachAbnf } from "../lib/util.mjs"; function check_refs(parsed) { const errs = abnf.checkRefs(parsed); diff --git a/tools/scripts/keyboard-abnf-tests/test/datadriven.test.mjs b/tools/scripts/keyboard-abnf-tests/test/datadriven.test.mjs index 249a3d6a2b2..b655e80caff 100644 --- a/tools/scripts/keyboard-abnf-tests/test/datadriven.test.mjs +++ b/tools/scripts/keyboard-abnf-tests/test/datadriven.test.mjs @@ -2,32 +2,27 @@ // For terms of use, see http://www.unicode.org/copyright.html // SPDX-License-Identifier: Unicode-3.0 -import * as abnf from "abnf"; import { existsSync, readFileSync, readdirSync } from "node:fs"; import { test } from "node:test"; import { basename, join } from "node:path"; import * as assert from "node:assert"; -import { forEachAbnf } from "./util.mjs"; -import peggy from "peggy"; +import { forEachAbnf } from "../lib/util.mjs"; +import { getParseFunction } from "../lib/index.mjs"; async function assertTest({ t, abnfPath, testText, expect }) { - const parsed = await abnf.parseFile(abnfPath); - const opts = { - grammarSource: abnfPath, - trace: false, - }; - const text = parsed.toFormat({ format: "peggy" }); - const parser = peggy.generate(text, opts); + const parser = await getParseFunction(abnfPath); for (const str of testText .trim() .split("\n") .filter((l) => !/^#/.test(l))) { await t.test(`"${str}"`, async (t) => { - const fn = () => parser.parse(str, opts); if (!expect) { - assert.throws(fn, `Expected this expression to fail parsing`); + assert.throws( + () => parser(str), + `Expected this expression to fail parsing` + ); } else { - const results = fn(); + const results = parser(str); assert.ok(results); } }); @@ -64,7 +59,5 @@ await forEachAbnf(async ({ abnfFile, abnfText, abnfPath }) => { }); } else throw Error(`Unknown testFile ${testFile}`); } - // const parsed = await abnf.parseFile(abnfPath); - // assert.equal(check_refs(parsed), 0); }); }); diff --git a/tools/scripts/keyboard-abnf-tests/test/xml.test.mjs b/tools/scripts/keyboard-abnf-tests/test/xml.test.mjs new file mode 100644 index 00000000000..49d423f02a1 --- /dev/null +++ b/tools/scripts/keyboard-abnf-tests/test/xml.test.mjs @@ -0,0 +1,22 @@ +// Copyright (c) 2025 Unicode, Inc. +// For terms of use, see http://www.unicode.org/copyright.html +// SPDX-License-Identifier: Unicode-3.0 + +import { join } from "node:path"; +import { readdirSync } from "node:fs"; +import { test } from "node:test"; +import { checkXml } from "../lib/index.mjs"; + +const KBD_DIR = "../../../keyboards/3.0"; + +await test("Testing Keyboard XML files for valid transform from/to attributes", async (t) => { + // keyboards, excluding -test.xml files + const kbds = readdirSync(KBD_DIR).filter((f) => + /^.*(? { + await checkXml(join(KBD_DIR, kbd)); + }); + } +});