diff options
| -rw-r--r-- | .eslintrc.json | 5 | ||||
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | dev/build.js | 16 | ||||
| -rw-r--r-- | ext/data/schemas/custom-audio-list-schema.json | 1 | ||||
| -rw-r--r-- | ext/data/schemas/dictionary-index-schema.json | 1 | ||||
| -rw-r--r-- | ext/data/schemas/dictionary-kanji-bank-v1-schema.json | 3 | ||||
| -rw-r--r-- | ext/data/schemas/dictionary-kanji-bank-v3-schema.json | 3 | ||||
| -rw-r--r-- | ext/data/schemas/dictionary-kanji-meta-bank-v3-schema.json | 1 | ||||
| -rw-r--r-- | ext/data/schemas/dictionary-tag-bank-v3-schema.json | 1 | ||||
| -rw-r--r-- | ext/data/schemas/dictionary-term-bank-v1-schema.json | 1 | ||||
| -rw-r--r-- | ext/data/schemas/dictionary-term-bank-v3-schema.json | 1 | ||||
| -rw-r--r-- | ext/data/schemas/dictionary-term-meta-bank-v3-schema.json | 1 | ||||
| -rw-r--r-- | ext/data/schemas/options-schema.json | 1 | ||||
| -rw-r--r-- | ext/js/language/dictionary-importer.js | 99 | ||||
| -rw-r--r-- | ext/lib/ucs2length.js | 16 | ||||
| -rw-r--r-- | package-lock.json | 33 | ||||
| -rw-r--r-- | package.json | 3 | 
17 files changed, 106 insertions, 81 deletions
| diff --git a/.eslintrc.json b/.eslintrc.json index 56bbcf09..a7fb842b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,7 @@          "plugin:jsonc/recommended-with-json"      ],      "parserOptions": { -        "ecmaVersion": 9, +        "ecmaVersion": 11,          "sourceType": "script",          "ecmaFeatures": {              "globalReturn": false, @@ -401,7 +401,8 @@                  "DynamicProperty": "readonly",                  "EventDispatcher": "readonly",                  "EventListenerCollection": "readonly", -                "Logger": "readonly" +                "Logger": "readonly", +                "import": "readonly"              }          },          { @@ -7,3 +7,4 @@ dictionaries/  /playwright/.cache/  /test/playwright/__screenshots__/  ext/manifest.json +ext/lib/validate-schemas.js diff --git a/dev/build.js b/dev/build.js index 24b1e2d0..3bfb5418 100644 --- a/dev/build.js +++ b/dev/build.js @@ -24,7 +24,8 @@ const childProcess = require('child_process');  const util = require('./util');  const {getAllFiles, getArgs, testMain} = util;  const {ManifestUtil} = require('./manifest-util'); - +const Ajv = require('ajv'); +const standaloneCode = require('ajv/dist/standalone').default;  async function createZip(directory, excludeFiles, outputFileName, sevenZipExes, onUpdate, dryRun) {      try { @@ -130,6 +131,19 @@ async function build(buildDir, extDir, manifestUtil, variantNames, manifestPath,          process.stdout.write(message);      }; +    process.stdout.write('Building schema validators using ajv\n'); +    const schemaDir = path.join(extDir, 'data/schemas/'); +    const schemaFileNames = fs.readdirSync(schemaDir); +    const schemas = schemaFileNames.map((schemaFileName) => JSON.parse(fs.readFileSync(path.join(schemaDir, schemaFileName)))); +    const ajv = new Ajv({schemas: schemas, code: {source: true, esm: true}}); +    const moduleCode = standaloneCode(ajv); + +    // https://github.com/ajv-validator/ajv/issues/2209 +    const patchedModuleCode = moduleCode.replaceAll('require("ajv/dist/runtime/ucs2length").default', 'import("/lib/ucs2length.js").default'); + +    fs.writeFileSync(path.join(extDir, 'lib/validate-schemas.js'), patchedModuleCode); + +      process.stdout.write(`Version: ${yomitanVersion}...\n`);      for (const variantName of variantNames) { diff --git a/ext/data/schemas/custom-audio-list-schema.json b/ext/data/schemas/custom-audio-list-schema.json index 2cb3ca78..885ad087 100644 --- a/ext/data/schemas/custom-audio-list-schema.json +++ b/ext/data/schemas/custom-audio-list-schema.json @@ -1,4 +1,5 @@  { +    "$id": "customAudioList",      "$schema": "http://json-schema.org/draft-07/schema#",      "type": "object",      "required": [ diff --git a/ext/data/schemas/dictionary-index-schema.json b/ext/data/schemas/dictionary-index-schema.json index a8ca0f23..98b27143 100644 --- a/ext/data/schemas/dictionary-index-schema.json +++ b/ext/data/schemas/dictionary-index-schema.json @@ -1,4 +1,5 @@  { +    "$id": "dictionaryIndex",      "$schema": "http://json-schema.org/draft-07/schema#",      "type": "object",      "description": "Index file containing information about the data contained in the dictionary.", diff --git a/ext/data/schemas/dictionary-kanji-bank-v1-schema.json b/ext/data/schemas/dictionary-kanji-bank-v1-schema.json index 5aca2d6a..d506a19d 100644 --- a/ext/data/schemas/dictionary-kanji-bank-v1-schema.json +++ b/ext/data/schemas/dictionary-kanji-bank-v1-schema.json @@ -1,4 +1,5 @@  { +    "$id": "dictionaryKanjiBankV1",      "$schema": "http://json-schema.org/draft-07/schema#",      "type": "array",      "description": "Data file containing kanji information.", @@ -30,4 +31,4 @@              "description": "A meaning for the kanji character."          }      } -}
\ No newline at end of file +} diff --git a/ext/data/schemas/dictionary-kanji-bank-v3-schema.json b/ext/data/schemas/dictionary-kanji-bank-v3-schema.json index ee508294..763ce3b1 100644 --- a/ext/data/schemas/dictionary-kanji-bank-v3-schema.json +++ b/ext/data/schemas/dictionary-kanji-bank-v3-schema.json @@ -1,4 +1,5 @@  { +    "$id": "dictionaryKanjiBankV3",      "$schema": "http://json-schema.org/draft-07/schema#",      "type": "array",      "description": "Data file containing kanji information.", @@ -42,4 +43,4 @@              }          ]      } -}
\ No newline at end of file +} diff --git a/ext/data/schemas/dictionary-kanji-meta-bank-v3-schema.json b/ext/data/schemas/dictionary-kanji-meta-bank-v3-schema.json index e478de93..d8f5031b 100644 --- a/ext/data/schemas/dictionary-kanji-meta-bank-v3-schema.json +++ b/ext/data/schemas/dictionary-kanji-meta-bank-v3-schema.json @@ -1,4 +1,5 @@  { +    "$id": "dictionaryKanjiMetaBankV3",      "$schema": "http://json-schema.org/draft-07/schema#",      "definitions": {          "frequency": { diff --git a/ext/data/schemas/dictionary-tag-bank-v3-schema.json b/ext/data/schemas/dictionary-tag-bank-v3-schema.json index f7721119..ab6e3377 100644 --- a/ext/data/schemas/dictionary-tag-bank-v3-schema.json +++ b/ext/data/schemas/dictionary-tag-bank-v3-schema.json @@ -1,4 +1,5 @@  { +    "$id": "dictionaryTagBankV3",      "$schema": "http://json-schema.org/draft-07/schema#",      "type": "array",      "description": "Data file containing tag information for terms and kanji.", diff --git a/ext/data/schemas/dictionary-term-bank-v1-schema.json b/ext/data/schemas/dictionary-term-bank-v1-schema.json index 9366e9ff..ab4c49f6 100644 --- a/ext/data/schemas/dictionary-term-bank-v1-schema.json +++ b/ext/data/schemas/dictionary-term-bank-v1-schema.json @@ -1,4 +1,5 @@  { +    "$id": "dictionaryTermBankV1",      "$schema": "http://json-schema.org/draft-07/schema#",      "type": "array",      "description": "Data file containing term information.", diff --git a/ext/data/schemas/dictionary-term-bank-v3-schema.json b/ext/data/schemas/dictionary-term-bank-v3-schema.json index 335144c7..7d0b4868 100644 --- a/ext/data/schemas/dictionary-term-bank-v3-schema.json +++ b/ext/data/schemas/dictionary-term-bank-v3-schema.json @@ -1,4 +1,5 @@  { +    "$id": "dictionaryTermBankV3",      "$schema": "http://json-schema.org/draft-07/schema#",      "definitions": {          "structuredContent": { diff --git a/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json b/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json index eb4d3fed..86e4af93 100644 --- a/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json +++ b/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json @@ -1,4 +1,5 @@  { +    "$id": "dictionaryTermMetaBankV3",      "$schema": "http://json-schema.org/draft-07/schema#",      "definitions": {          "frequency": { diff --git a/ext/data/schemas/options-schema.json b/ext/data/schemas/options-schema.json index 601f5d06..8ccbfa94 100644 --- a/ext/data/schemas/options-schema.json +++ b/ext/data/schemas/options-schema.json @@ -1,4 +1,5 @@  { +    "$id": "options",      "$schema": "http://json-schema.org/draft-07/schema#",      "type": "object",      "required": [ diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index 718d9f1c..0cf3d5f5 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -18,7 +18,6 @@  /* global   * JSZip - * JsonSchema   * MediaUtil   */ @@ -51,8 +50,10 @@ class DictionaryImporter {          const index = JSON.parse(await indexFile.async('string')); -        const indexSchema = await this._getSchema('/data/schemas/dictionary-index-schema.json'); -        this._validateJsonSchema(index, indexSchema, indexFileName); +        const ajvSchemas = await import('/lib/validate-schemas.js'); +        if (!ajvSchemas.dictionaryIndex(index)) { +            throw this._formatAjvSchemaError(ajvSchemas.dictionaryIndex, indexFileName); +        }          const dictionaryTitle = index.title;          const version = index.format || index.version; @@ -75,8 +76,7 @@ class DictionaryImporter {          // Load schemas          this._progressNextStep(0); -        const dataBankSchemaPaths = this._getDataBankSchemaPaths(version); -        const dataBankSchemas = await Promise.all(dataBankSchemaPaths.map((path) => this._getSchema(path))); +        const dataBankSchemas = this._getDataBankSchemas(version);          // Files          const termFiles      = this._getArchiveFiles(archive, 'term_bank_?.json'); @@ -87,11 +87,11 @@ class DictionaryImporter {          // Load data          this._progressNextStep(termFiles.length + termMetaFiles.length + kanjiFiles.length + kanjiMetaFiles.length + tagFiles.length); -        const termList      = await this._readFileSequence(termFiles,      convertTermBankEntry,      dataBankSchemas[0], dictionaryTitle); -        const termMetaList  = await this._readFileSequence(termMetaFiles,  convertTermMetaBankEntry,  dataBankSchemas[1], dictionaryTitle); -        const kanjiList     = await this._readFileSequence(kanjiFiles,     convertKanjiBankEntry,     dataBankSchemas[2], dictionaryTitle); -        const kanjiMetaList = await this._readFileSequence(kanjiMetaFiles, convertKanjiMetaBankEntry, dataBankSchemas[3], dictionaryTitle); -        const tagList       = await this._readFileSequence(tagFiles,       convertTagBankEntry,       dataBankSchemas[4], dictionaryTitle); +        const termList      = await this._readFileSequence(ajvSchemas, termFiles,      convertTermBankEntry,      dataBankSchemas[0], dictionaryTitle); +        const termMetaList  = await this._readFileSequence(ajvSchemas, termMetaFiles,  convertTermMetaBankEntry,  dataBankSchemas[1], dictionaryTitle); +        const kanjiList     = await this._readFileSequence(ajvSchemas, kanjiFiles,     convertKanjiBankEntry,     dataBankSchemas[2], dictionaryTitle); +        const kanjiMetaList = await this._readFileSequence(ajvSchemas, kanjiMetaFiles, convertKanjiMetaBankEntry, dataBankSchemas[3], dictionaryTitle); +        const tagList       = await this._readFileSequence(ajvSchemas, tagFiles,       convertTagBankEntry,       dataBankSchemas[4], dictionaryTitle);          this._addOldIndexTags(index, tagList, dictionaryTitle);          // Prefix wildcard support @@ -214,68 +214,27 @@ class DictionaryImporter {          return summary;      } -    async _getSchema(fileName) { -        const schema = await this._fetchJsonAsset(fileName); -        return new JsonSchema(schema); -    } - -    _validateJsonSchema(value, schema, fileName) { -        try { -            schema.validate(value); -        } catch (e) { -            throw this._formatSchemaError(e, fileName); -        } -    } - -    _formatSchemaError(e, fileName) { -        const valuePathString = this._getSchemaErrorPathString(e.valueStack, 'dictionary'); -        const schemaPathString = this._getSchemaErrorPathString(e.schemaStack, 'schema'); - -        const e2 = new Error(`Dictionary has invalid data in '${fileName}' for value '${valuePathString}', validated against '${schemaPathString}': ${e.message}`); -        e2.data = e; +    _formatAjvSchemaError(schema, fileName) { +        const e2 = new Error(`Dictionary has invalid data in '${fileName}'`); +        e2.data = schema.errors;          return e2;      } -    _getSchemaErrorPathString(infoList, base='') { -        let result = base; -        for (const {path} of infoList) { -            const pathArray = Array.isArray(path) ? path : [path]; -            for (const pathPart of pathArray) { -                if (pathPart === null) { -                    result = base; -                } else { -                    switch (typeof pathPart) { -                        case 'string': -                            if (result.length > 0) { -                                result += '.'; -                            } -                            result += pathPart; -                            break; -                        case 'number': -                            result += `[${pathPart}]`; -                            break; -                    } -                } -            } -        } -        return result; -    } - -    _getDataBankSchemaPaths(version) { +    _getDataBankSchemas(version) {          const termBank = (              version === 1 ? -            '/data/schemas/dictionary-term-bank-v1-schema.json' : -            '/data/schemas/dictionary-term-bank-v3-schema.json' +            'dictionaryTermBankV1' : +            'dictionaryTermBankV3'          ); -        const termMetaBank = '/data/schemas/dictionary-term-meta-bank-v3-schema.json'; +        const termMetaBank = 'dictionaryTermMetaBankV3';          const kanjiBank = (              version === 1 ? -            '/data/schemas/dictionary-kanji-bank-v1-schema.json' : -            '/data/schemas/dictionary-kanji-bank-v3-schema.json' +            'dictionaryKanjiBankV1' : +            'dictionaryKanjiBankV3'          ); -        const kanjiMetaBank = '/data/schemas/dictionary-kanji-meta-bank-v3-schema.json'; -        const tagBank = '/data/schemas/dictionary-tag-bank-v3-schema.json'; +        const kanjiMetaBank = 'dictionaryKanjiMetaBankV3'; +        const tagBank = 'dictionaryTagBankV3';          return [termBank, termMetaBank, kanjiBank, kanjiMetaBank, tagBank];      } @@ -539,28 +498,20 @@ class DictionaryImporter {          return results;      } -    async _readFileSequence(files, convertEntry, schema, dictionaryTitle) { +    async _readFileSequence(ajvSchemas, files, convertEntry, schemaName, dictionaryTitle) {          const progressData = this._progressData; -        let count = 0;          let startIndex = 0; -        if (typeof this._onProgress === 'function') { -            schema.progressInterval = 1000; -            schema.progress = (s) => { -                const index = s.getValueStackLength() > 1 ? s.getValueStackItem(1).path : 0; -                progressData.index = startIndex + (index / count); -                this._progress(); -            }; -        }          const results = [];          for (const file of files) {              const entries = JSON.parse(await file.async('string')); -            count = Array.isArray(entries) ? Math.max(entries.length, 1) : 1;              startIndex = progressData.index;              this._progress(); -            this._validateJsonSchema(entries, schema, file.name); +            if (!ajvSchemas[schemaName](entries)) { +                throw this._formatAjvSchemaError(ajvSchemas[schemaName], file.name); +            }              progressData.index = startIndex + 1;              this._progress(); diff --git a/ext/lib/ucs2length.js b/ext/lib/ucs2length.js new file mode 100644 index 00000000..120a64d4 --- /dev/null +++ b/ext/lib/ucs2length.js @@ -0,0 +1,16 @@ +export default function ucs2length(str) { +    const len = str.length; +    let length = 0; +    let pos = 0; +    let value; +    while (pos < len) { +        length++; +        value = str.charCodeAt(pos++); +        if (value >= 0xd800 && value <= 0xdbff && pos < len) { +            // high surrogate, and there is a next character +            value = str.charCodeAt(pos); +            if ((value & 0xfc00) === 0xdc00) pos++; // low surrogate +        } +    } +    return length; +} diff --git a/package-lock.json b/package-lock.json index 24e49c86..6f6581b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@              "license": "GPL-3.0-or-later",              "devDependencies": {                  "@playwright/test": "^1.39.0", -                "ajv": "^8.11.0", +                "@types/node": "^20.8.10", +                "ajv": "^8.12.0",                  "browserify": "^17.0.0",                  "css": "^3.0.0",                  "eslint": "^8.52.0", @@ -523,6 +524,15 @@              "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==",              "dev": true          }, +        "node_modules/@types/node": { +            "version": "20.8.10", +            "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", +            "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", +            "dev": true, +            "dependencies": { +                "undici-types": "~5.26.4" +            } +        },          "node_modules/@types/normalize-package-data": {              "version": "2.4.1",              "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", @@ -5246,6 +5256,12 @@                  "undeclared-identifiers": "bin.js"              }          }, +        "node_modules/undici-types": { +            "version": "5.26.5", +            "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", +            "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", +            "dev": true +        },          "node_modules/universalify": {              "version": "0.2.0",              "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -5985,6 +6001,15 @@              "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==",              "dev": true          }, +        "@types/node": { +            "version": "20.8.10", +            "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", +            "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", +            "dev": true, +            "requires": { +                "undici-types": "~5.26.4" +            } +        },          "@types/normalize-package-data": {              "version": "2.4.1",              "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", @@ -9581,6 +9606,12 @@                  "xtend": "^4.0.1"              }          }, +        "undici-types": { +            "version": "5.26.5", +            "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", +            "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", +            "dev": true +        },          "universalify": {              "version": "0.2.0",              "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", diff --git a/package.json b/package.json index 22f5bd56..ee95d388 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@      },      "devDependencies": {          "@playwright/test": "^1.39.0", -        "ajv": "^8.11.0", +        "@types/node": "^20.8.10", +        "ajv": "^8.12.0",          "browserify": "^17.0.0",          "css": "^3.0.0",          "eslint": "^8.52.0", |