diff options
Diffstat (limited to 'ext')
| -rw-r--r-- | ext/js/language/dictionary-importer.js | 130 | 
1 files changed, 79 insertions, 51 deletions
| diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index 2a2f4063..115e0726 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -16,10 +16,23 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import * as ajvSchemas from '../../lib/validate-schemas.js'; -import {BlobWriter, TextWriter, Uint8ArrayReader, ZipReader, configure} from '../../lib/zip.js'; +import * as ajvSchemas0 from '../../lib/validate-schemas.js'; +import { +    BlobWriter as BlobWriter0, +    TextWriter as TextWriter0, +    Uint8ArrayReader as Uint8ArrayReader0, +    ZipReader as ZipReader0, +    configure +} from '../../lib/zip.js';  import {stringReverse} from '../core.js';  import {MediaUtil} from '../media/media-util.js'; +import {ExtensionError} from '../core/extension-error.js'; + +const ajvSchemas = /** @type {import('dictionary-importer').CompiledSchemaValidators} */ (/** @type {unknown} */ (ajvSchemas0)); +const BlobWriter = /** @type {typeof import('@zip.js/zip.js').BlobWriter} */ (/** @type {unknown} */ (BlobWriter0)); +const TextWriter = /** @type {typeof import('@zip.js/zip.js').TextWriter} */ (/** @type {unknown} */ (TextWriter0)); +const Uint8ArrayReader = /** @type {typeof import('@zip.js/zip.js').Uint8ArrayReader} */ (/** @type {unknown} */ (Uint8ArrayReader0)); +const ZipReader = /** @type {typeof import('@zip.js/zip.js').ZipReader} */ (/** @type {unknown} */ (ZipReader0));  export class DictionaryImporter {      /** @@ -62,21 +75,21 @@ export class DictionaryImporter {          const zipFileReader = new Uint8ArrayReader(new Uint8Array(archiveContent));          const zipReader = new ZipReader(zipFileReader);          const zipEntries = await zipReader.getEntries(); -        const zipEntriesObject = {}; +        /** @type {import('dictionary-importer').ArchiveFileMap} */ +        const fileMap = new Map();          for (const entry of zipEntries) { -            zipEntriesObject[entry.filename] = entry; +            fileMap.set(entry.filename, entry);          }          // Read and validate index          const indexFileName = 'index.json'; -        const indexFile = zipEntriesObject[indexFileName]; -        if (!indexFile) { +        const indexFile = fileMap.get(indexFileName); +        if (typeof indexFile === 'undefined') {              throw new Error('No dictionary index found in archive');          } +        const indexFile2 = /** @type {import('@zip.js/zip.js').Entry} */ (indexFile); -        const indexContent = await indexFile.getData( -            new TextWriter() -        ); -        const index = JSON.parse(indexContent); +        const indexContent = await this._getData(indexFile2, new TextWriter()); +        const index = /** @type {import('dictionary-data').Index} */ (JSON.parse(indexContent));          if (!ajvSchemas.dictionaryIndex(index)) {              throw this._formatAjvSchemaError(ajvSchemas.dictionaryIndex, indexFileName); @@ -99,11 +112,11 @@ export class DictionaryImporter {          const dataBankSchemas = this._getDataBankSchemas(version);          // Files -        const termFiles      = this._getArchiveFiles(zipEntriesObject, 'term_bank_?.json'); -        const termMetaFiles  = this._getArchiveFiles(zipEntriesObject, 'term_meta_bank_?.json'); -        const kanjiFiles     = this._getArchiveFiles(zipEntriesObject, 'kanji_bank_?.json'); -        const kanjiMetaFiles = this._getArchiveFiles(zipEntriesObject, 'kanji_meta_bank_?.json'); -        const tagFiles       = this._getArchiveFiles(zipEntriesObject, 'tag_bank_?.json'); +        const termFiles      = this._getArchiveFiles(fileMap, 'term_bank_?.json'); +        const termMetaFiles  = this._getArchiveFiles(fileMap, 'term_meta_bank_?.json'); +        const kanjiFiles     = this._getArchiveFiles(fileMap, 'kanji_bank_?.json'); +        const kanjiMetaFiles = this._getArchiveFiles(fileMap, 'kanji_meta_bank_?.json'); +        const tagFiles       = this._getArchiveFiles(fileMap, 'tag_bank_?.json');          // Load data          this._progressNextStep(termFiles.length + termMetaFiles.length + kanjiFiles.length + kanjiMetaFiles.length + tagFiles.length); @@ -153,7 +166,7 @@ export class DictionaryImporter {          // Async requirements          this._progressNextStep(requirements.length); -        const {media} = await this._resolveAsyncRequirements(requirements, zipEntriesObject); +        const {media} = await this._resolveAsyncRequirements(requirements, fileMap);          // Add dictionary descriptor          this._progressNextStep(termList.length + termMetaList.length + kanjiList.length + kanjiMetaList.length + tagList.length + media.length); @@ -274,20 +287,20 @@ export class DictionaryImporter {      }      /** -     * -     * @param schema -     * @param fileName +     * @param {import('ajv').ValidateFunction} schema +     * @param {string} fileName +     * @returns {ExtensionError}       */      _formatAjvSchemaError(schema, fileName) { -        const e2 = new Error(`Dictionary has invalid data in '${fileName}'`); +        const e2 = new ExtensionError(`Dictionary has invalid data in '${fileName}'`);          e2.data = schema.errors;          return e2;      }      /** -     * -     * @param version +     * @param {number} version +     * @returns {import('dictionary-importer').CompiledSchemaNameArray}       */      _getDataBankSchemas(version) {          const termBank = ( @@ -402,13 +415,15 @@ export class DictionaryImporter {      }      /** -     * -     * @param requirements -     * @param zipEntriesObject +     * @param {import('dictionary-importer').ImportRequirement[]} requirements +     * @param {import('dictionary-importer').ArchiveFileMap} fileMap +     * @returns {Promise<{media: import('dictionary-database').MediaDataArrayBufferContent[]}>}       */ -    async _resolveAsyncRequirements(requirements, zipEntriesObject) { +    async _resolveAsyncRequirements(requirements, fileMap) { +        /** @type {Map<string, import('dictionary-database').MediaDataArrayBufferContent>} */          const media = new Map(); -        const context = {zipEntriesObject, media}; +        /** @type {import('dictionary-importer').ImportRequirementContext} */ +        const context = {fileMap, media};          for (const requirement of requirements) {              await this._resolveAsyncRequirement(context, requirement); @@ -537,15 +552,13 @@ export class DictionaryImporter {          }          // Find file in archive -        const file = context.zipEntriesObject[path]; -        if (file === null) { +        const file = context.fileMap.get(path); +        if (typeof file === 'undefined') {              throw createError('Could not find image');          }          // Load file content -        let content = await (await file.getData( -            new BlobWriter() -        )).arrayBuffer(); +        let content = await (await this._getData(file, new BlobWriter())).arrayBuffer();          const mediaType = MediaUtil.getImageMediaTypeFromFileName(path);          if (mediaType === null) { @@ -683,46 +696,48 @@ export class DictionaryImporter {      }      /** -     * -     * @param zipEntriesObject -     * @param fileNameFormat +     * @param {import('dictionary-importer').ArchiveFileMap} fileMap +     * @param {string} fileNameFormat +     * @returns {import('@zip.js/zip.js').Entry[]}       */ -    _getArchiveFiles(zipEntriesObject, fileNameFormat) { +    _getArchiveFiles(fileMap, fileNameFormat) {          const indexPosition = fileNameFormat.indexOf('?');          const prefix = fileNameFormat.substring(0, indexPosition);          const suffix = fileNameFormat.substring(indexPosition + 1); +        /** @type {import('@zip.js/zip.js').Entry[]} */          const results = []; -        for (const f of Object.keys(zipEntriesObject)) { -            if (f.startsWith(prefix) && f.endsWith(suffix)) { -                results.push(zipEntriesObject[f]); +        for (const [name, value] of fileMap.entries()) { +            if (name.startsWith(prefix) && name.endsWith(suffix)) { +                results.push(value);              }          }          return results;      }      /** -     * -     * @param files -     * @param convertEntry -     * @param schemaName -     * @param dictionaryTitle +     * @template [TEntry=unknown] +     * @template [TResult=unknown] +     * @param {import('@zip.js/zip.js').Entry[]} files +     * @param {(entry: TEntry, dictionaryTitle: string) => TResult} convertEntry +     * @param {import('dictionary-importer').CompiledSchemaName} schemaName +     * @param {string} dictionaryTitle +     * @returns {Promise<TResult[]>}       */      async _readFileSequence(files, convertEntry, schemaName, dictionaryTitle) {          const progressData = this._progressData;          let startIndex = 0;          const results = []; -        for (const fileName of Object.keys(files)) { -            const content = await files[fileName].getData( -                new TextWriter() -            ); -            const entries = JSON.parse(content); +        for (const file of files) { +            const content = await this._getData(file, new TextWriter()); +            const entries = /** @type {unknown} */ (JSON.parse(content));              startIndex = progressData.index;              this._progress(); -            if (!ajvSchemas[schemaName](entries)) { -                throw this._formatAjvSchemaError(ajvSchemas[schemaName], fileName); +            const schema = ajvSchemas[schemaName]; +            if (!schema(entries)) { +                throw this._formatAjvSchemaError(schema, file.filename);              }              progressData.index = startIndex + 1; @@ -771,4 +786,17 @@ export class DictionaryImporter {          // - '\ufa67'.normalize('NFC') => '\u9038' (逸 => 逸)          return text;      } + +    /** +     * @template [T=unknown] +     * @param {import('@zip.js/zip.js').Entry} entry +     * @param {import('@zip.js/zip.js').Writer<T>|import('@zip.js/zip.js').WritableWriter} writer +     * @returns {Promise<T>} +     */ +    async _getData(entry, writer) { +        if (typeof entry.getData === 'undefined') { +            throw new Error(`Cannot read ${entry.filename}`); +        } +        return await entry.getData(writer); +    }  } |