2020-03-31 00:51:20 +00:00
/ *
2021-01-01 19:50:41 +00:00
* Copyright ( C ) 2020 - 2021 Yomichan Authors
2020-03-31 00:51:20 +00:00
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
* /
/ * g l o b a l
* JSZip
2021-05-22 19:45:20 +00:00
* JsonSchema
2021-02-14 16:32:30 +00:00
* MediaUtil
2020-03-31 00:51:20 +00:00
* /
class DictionaryImporter {
2021-07-31 22:08:51 +00:00
constructor ( mediaLoader , onProgress ) {
2021-07-31 16:30:31 +00:00
this . _mediaLoader = mediaLoader ;
2021-07-31 23:13:41 +00:00
this . _onProgress = typeof onProgress === 'function' ? onProgress : ( ) => { } ;
this . _progressData = null ;
2020-03-31 00:51:20 +00:00
}
2021-07-31 22:08:51 +00:00
async importDictionary ( dictionaryDatabase , archiveContent , details ) {
2020-06-28 21:24:06 +00:00
if ( ! dictionaryDatabase ) {
2020-03-31 00:51:20 +00:00
throw new Error ( 'Invalid database' ) ;
}
2020-06-28 21:24:06 +00:00
if ( ! dictionaryDatabase . isPrepared ( ) ) {
2020-03-31 00:51:20 +00:00
throw new Error ( 'Database is not ready' ) ;
}
2021-07-31 23:13:41 +00:00
this . _progressReset ( ) ;
2020-03-31 00:51:20 +00:00
// Read archive
2021-07-31 16:30:31 +00:00
const archive = await JSZip . loadAsync ( archiveContent ) ;
2020-03-31 00:51:20 +00:00
// Read and validate index
const indexFileName = 'index.json' ;
2021-07-31 20:53:38 +00:00
const indexFile = archive . file ( indexFileName ) ;
2020-03-31 00:51:20 +00:00
if ( ! indexFile ) {
throw new Error ( 'No dictionary index found in archive' ) ;
}
const index = JSON . parse ( await indexFile . async ( 'string' ) ) ;
2021-02-13 00:56:24 +00:00
const indexSchema = await this . _getSchema ( '/data/schemas/dictionary-index-schema.json' ) ;
2020-03-31 00:51:20 +00:00
this . _validateJsonSchema ( index , indexSchema , indexFileName ) ;
const dictionaryTitle = index . title ;
const version = index . format || index . version ;
if ( ! dictionaryTitle || ! index . revision ) {
throw new Error ( 'Unrecognized dictionary format' ) ;
}
// Verify database is not already imported
2020-06-28 21:24:06 +00:00
if ( await dictionaryDatabase . dictionaryExists ( dictionaryTitle ) ) {
2020-03-31 00:51:20 +00:00
throw new Error ( 'Dictionary is already imported' ) ;
}
// Data format converters
2021-07-31 20:53:38 +00:00
const convertTermBankEntry = ( version === 1 ? this . _convertTermBankEntryV1 . bind ( this ) : this . _convertTermBankEntryV3 . bind ( this ) ) ;
const convertTermMetaBankEntry = this . _convertTermMetaBankEntry . bind ( this ) ;
const convertKanjiBankEntry = ( version === 1 ? this . _convertKanjiBankEntryV1 . bind ( this ) : this . _convertKanjiBankEntryV3 . bind ( this ) ) ;
const convertKanjiMetaBankEntry = this . _convertKanjiMetaBankEntry . bind ( this ) ;
const convertTagBankEntry = this . _convertTagBankEntry . bind ( this ) ;
2020-03-31 00:51:20 +00:00
// Load schemas
2021-07-31 23:13:41 +00:00
this . _progressNextStep ( 0 ) ;
2020-03-31 00:51:20 +00:00
const dataBankSchemaPaths = this . _getDataBankSchemaPaths ( version ) ;
const dataBankSchemas = await Promise . all ( dataBankSchemaPaths . map ( ( path ) => this . _getSchema ( path ) ) ) ;
2021-07-31 20:53:38 +00:00
// Files
const termFiles = this . _getArchiveFiles ( archive , 'term_bank_?.json' ) ;
const termMetaFiles = this . _getArchiveFiles ( archive , 'term_meta_bank_?.json' ) ;
const kanjiFiles = this . _getArchiveFiles ( archive , 'kanji_bank_?.json' ) ;
const kanjiMetaFiles = this . _getArchiveFiles ( archive , 'kanji_meta_bank_?.json' ) ;
const tagFiles = this . _getArchiveFiles ( archive , 'tag_bank_?.json' ) ;
2020-03-31 00:51:20 +00:00
// Load data
2021-07-31 23:13:41 +00:00
this . _progressNextStep ( termFiles . length + termMetaFiles . length + kanjiFiles . length + kanjiMetaFiles . length + tagFiles . length ) ;
2021-07-31 20:53:38 +00:00
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 ) ;
this . _addOldIndexTags ( index , tagList , dictionaryTitle ) ;
2020-03-31 00:51:20 +00:00
// Prefix wildcard support
const prefixWildcardsSupported = ! ! details . prefixWildcardsSupported ;
if ( prefixWildcardsSupported ) {
for ( const entry of termList ) {
entry . expressionReverse = stringReverse ( entry . expression ) ;
entry . readingReverse = stringReverse ( entry . reading ) ;
}
}
2020-03-02 03:36:42 +00:00
// Extended data support
2021-07-31 23:13:41 +00:00
this . _progressNextStep ( termList . length ) ;
const formatProgressInterval = 1000 ;
2021-07-31 14:53:35 +00:00
const requirements = [ ] ;
2021-07-31 23:13:41 +00:00
for ( let i = 0 , ii = termList . length ; i < ii ; ++ i ) {
const entry = termList [ i ] ;
2020-03-02 03:36:42 +00:00
const glossaryList = entry . glossary ;
2021-07-31 23:13:41 +00:00
for ( let j = 0 , jj = glossaryList . length ; j < jj ; ++ j ) {
const glossary = glossaryList [ j ] ;
2020-03-02 03:36:42 +00:00
if ( typeof glossary !== 'object' || glossary === null ) { continue ; }
2021-07-31 23:13:41 +00:00
glossaryList [ j ] = this . _formatDictionaryTermGlossaryObject ( glossary , entry , requirements ) ;
}
if ( ( i % formatProgressInterval ) === 0 ) {
this . _progressData . index = i ;
this . _progress ( ) ;
2020-03-02 03:36:42 +00:00
}
}
2021-07-31 23:13:41 +00:00
this . _progress ( ) ;
2020-03-02 03:36:42 +00:00
2021-07-31 14:53:35 +00:00
// Async requirements
2021-07-31 23:13:41 +00:00
this . _progressNextStep ( requirements . length ) ;
2021-07-31 14:53:35 +00:00
const { media } = await this . _resolveAsyncRequirements ( requirements , archive ) ;
2020-03-02 03:36:42 +00:00
2021-08-14 22:16:30 +00:00
// Add dictionary descriptor
2021-09-01 01:42:18 +00:00
this . _progressNextStep ( termList . length + termMetaList . length + kanjiList . length + kanjiMetaList . length + tagList . length + media . length ) ;
2020-03-31 00:51:20 +00:00
2021-09-07 15:12:58 +00:00
const counts = {
terms : { total : termList . length } ,
termMeta : this . _getMetaCounts ( termMetaList ) ,
kanji : { total : kanjiList . length } ,
kanjiMeta : this . _getMetaCounts ( kanjiMetaList ) ,
tagMeta : { total : tagList . length } ,
media : { total : media . length }
} ;
const summary = this . _createSummary ( dictionaryTitle , version , index , { prefixWildcardsSupported , counts } ) ;
2020-06-28 21:24:06 +00:00
dictionaryDatabase . bulkAdd ( 'dictionaries' , [ summary ] , 0 , 1 ) ;
2020-03-31 00:51:20 +00:00
// Add data
const errors = [ ] ;
const maxTransactionLength = 1000 ;
const bulkAdd = async ( objectStoreName , entries ) => {
const ii = entries . length ;
for ( let i = 0 ; i < ii ; i += maxTransactionLength ) {
const count = Math . min ( maxTransactionLength , ii - i ) ;
try {
2020-06-28 21:24:06 +00:00
await dictionaryDatabase . bulkAdd ( objectStoreName , entries , i , count ) ;
2020-03-31 00:51:20 +00:00
} catch ( e ) {
2020-09-04 21:54:34 +00:00
errors . push ( e ) ;
2020-03-31 00:51:20 +00:00
}
2021-07-31 23:13:41 +00:00
this . _progressData . index += count ;
this . _progress ( ) ;
2020-03-31 00:51:20 +00:00
}
} ;
await bulkAdd ( 'terms' , termList ) ;
await bulkAdd ( 'termMeta' , termMetaList ) ;
await bulkAdd ( 'kanji' , kanjiList ) ;
await bulkAdd ( 'kanjiMeta' , kanjiMetaList ) ;
await bulkAdd ( 'tagMeta' , tagList ) ;
2020-03-02 03:36:42 +00:00
await bulkAdd ( 'media' , media ) ;
2020-03-31 00:51:20 +00:00
2021-07-31 23:13:41 +00:00
this . _progress ( ) ;
2020-03-31 00:51:20 +00:00
return { result : summary , errors } ;
}
2021-07-31 23:13:41 +00:00
_progressReset ( ) {
this . _progressData = {
stepIndex : 0 ,
stepCount : 6 ,
index : 0 ,
count : 0
} ;
this . _progress ( ) ;
}
_progressNextStep ( count ) {
++ this . _progressData . stepIndex ;
this . _progressData . index = 0 ;
this . _progressData . count = count ;
this . _progress ( ) ;
}
_progress ( ) {
this . _onProgress ( this . _progressData ) ;
}
2020-04-05 18:45:54 +00:00
_createSummary ( dictionaryTitle , version , index , details ) {
const summary = {
title : dictionaryTitle ,
revision : index . revision ,
sequenced : index . sequenced ,
2021-09-07 15:12:58 +00:00
version ,
importDate : Date . now ( )
2020-04-05 18:45:54 +00:00
} ;
2021-09-26 15:08:25 +00:00
const { author , url , description , attribution , frequencyMode } = index ;
2020-04-05 18:45:54 +00:00
if ( typeof author === 'string' ) { summary . author = author ; }
if ( typeof url === 'string' ) { summary . url = url ; }
if ( typeof description === 'string' ) { summary . description = description ; }
if ( typeof attribution === 'string' ) { summary . attribution = attribution ; }
2021-09-26 15:08:25 +00:00
if ( typeof frequencyMode === 'string' ) { summary . frequencyMode = frequencyMode ; }
2020-04-05 18:45:54 +00:00
Object . assign ( summary , details ) ;
return summary ;
}
2020-03-31 00:51:20 +00:00
async _getSchema ( fileName ) {
2021-05-22 19:45:20 +00:00
const schema = await this . _fetchJsonAsset ( fileName ) ;
return new JsonSchema ( schema ) ;
}
2020-03-31 00:51:20 +00:00
_validateJsonSchema ( value , schema , fileName ) {
try {
2021-05-22 19:45:20 +00:00
schema . validate ( value ) ;
2020-03-31 00:51:20 +00:00
} catch ( e ) {
throw this . _formatSchemaError ( e , fileName ) ;
}
}
_formatSchemaError ( e , fileName ) {
2021-05-22 21:56:44 +00:00
const valuePathString = this . _getSchemaErrorPathString ( e . valueStack , 'dictionary' ) ;
const schemaPathString = this . _getSchemaErrorPathString ( e . schemaStack , 'schema' ) ;
2020-03-31 00:51:20 +00:00
const e2 = new Error ( ` Dictionary has invalid data in ' ${ fileName } ' for value ' ${ valuePathString } ', validated against ' ${ schemaPathString } ': ${ e . message } ` ) ;
e2 . data = e ;
return e2 ;
}
_getSchemaErrorPathString ( infoList , base = '' ) {
let result = base ;
2021-05-22 19:45:20 +00:00
for ( const { path } of infoList ) {
2021-05-22 21:56:44 +00:00
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 ;
2020-03-31 00:51:20 +00:00
}
2021-05-22 21:56:44 +00:00
}
2020-03-31 00:51:20 +00:00
}
}
return result ;
}
_getDataBankSchemaPaths ( version ) {
const termBank = (
version === 1 ?
2021-02-13 00:56:24 +00:00
'/data/schemas/dictionary-term-bank-v1-schema.json' :
'/data/schemas/dictionary-term-bank-v3-schema.json'
2020-03-31 00:51:20 +00:00
) ;
2021-02-13 00:56:24 +00:00
const termMetaBank = '/data/schemas/dictionary-term-meta-bank-v3-schema.json' ;
2020-03-31 00:51:20 +00:00
const kanjiBank = (
version === 1 ?
2021-02-13 00:56:24 +00:00
'/data/schemas/dictionary-kanji-bank-v1-schema.json' :
'/data/schemas/dictionary-kanji-bank-v3-schema.json'
2020-03-31 00:51:20 +00:00
) ;
2021-02-13 00:56:24 +00:00
const kanjiMetaBank = '/data/schemas/dictionary-kanji-meta-bank-v3-schema.json' ;
const tagBank = '/data/schemas/dictionary-tag-bank-v3-schema.json' ;
2020-03-31 00:51:20 +00:00
return [ termBank , termMetaBank , kanjiBank , kanjiMetaBank , tagBank ] ;
}
2020-03-02 03:36:42 +00:00
2021-07-31 14:53:35 +00:00
_formatDictionaryTermGlossaryObject ( data , entry , requirements ) {
2020-03-02 03:36:42 +00:00
switch ( data . type ) {
case 'text' :
return data . text ;
case 'image' :
2021-07-31 14:53:35 +00:00
return this . _formatDictionaryTermGlossaryImage ( data , entry , requirements ) ;
2021-05-19 22:24:50 +00:00
case 'structured-content' :
2021-07-31 14:53:35 +00:00
return this . _formatStructuredContent ( data , entry , requirements ) ;
2020-03-02 03:36:42 +00:00
default :
throw new Error ( ` Unhandled data type: ${ data . type } ` ) ;
}
}
2021-07-31 14:53:35 +00:00
_formatDictionaryTermGlossaryImage ( data , entry , requirements ) {
const target = { } ;
requirements . push ( { type : 'image' , target , args : [ data , entry ] } ) ;
return target ;
2021-05-19 22:24:50 +00:00
}
2021-07-31 14:53:35 +00:00
_formatStructuredContent ( data , entry , requirements ) {
const content = this . _prepareStructuredContent ( data . content , entry , requirements ) ;
2021-05-19 22:24:50 +00:00
return {
type : 'structured-content' ,
content
} ;
}
2021-07-31 14:53:35 +00:00
_prepareStructuredContent ( content , entry , requirements ) {
2021-05-19 22:24:50 +00:00
if ( typeof content === 'string' || ! ( typeof content === 'object' && content !== null ) ) {
return content ;
}
if ( Array . isArray ( content ) ) {
for ( let i = 0 , ii = content . length ; i < ii ; ++ i ) {
2021-07-31 14:53:35 +00:00
content [ i ] = this . _prepareStructuredContent ( content [ i ] , entry , requirements ) ;
2021-05-19 22:24:50 +00:00
}
return content ;
}
const { tag } = content ;
switch ( tag ) {
case 'img' :
2021-07-31 14:53:35 +00:00
return this . _prepareStructuredContentImage ( content , entry , requirements ) ;
2021-05-19 22:24:50 +00:00
}
const childContent = content . content ;
if ( typeof childContent !== 'undefined' ) {
2021-07-31 14:53:35 +00:00
content . content = this . _prepareStructuredContent ( childContent , entry , requirements ) ;
2021-05-19 22:24:50 +00:00
}
return content ;
}
2021-07-31 14:53:35 +00:00
_prepareStructuredContentImage ( content , entry , requirements ) {
const target = { } ;
requirements . push ( { type : 'structured-content-image' , target , args : [ content , entry ] } ) ;
return target ;
}
async _resolveAsyncRequirements ( requirements , archive ) {
const media = new Map ( ) ;
const context = { archive , media } ;
for ( const requirement of requirements ) {
2021-09-04 02:33:58 +00:00
await this . _resolveAsyncRequirement ( context , requirement ) ;
2021-07-31 14:53:35 +00:00
}
return {
media : [ ... media . values ( ) ]
} ;
}
async _resolveAsyncRequirement ( context , requirement ) {
const { type , target , args } = requirement ;
let result ;
switch ( type ) {
case 'image' :
result = await this . _resolveDictionaryTermGlossaryImage ( context , ... args ) ;
break ;
case 'structured-content-image' :
result = await this . _resolveStructuredContentImage ( context , ... args ) ;
break ;
default :
return ;
}
Object . assign ( target , result ) ;
2021-07-31 23:13:41 +00:00
++ this . _progressData . index ;
this . _progress ( ) ;
2021-07-31 14:53:35 +00:00
}
async _resolveDictionaryTermGlossaryImage ( context , data , entry ) {
return await this . _createImageData ( context , data , entry , { type : 'image' } ) ;
}
async _resolveStructuredContentImage ( context , content , entry ) {
2021-05-20 23:33:08 +00:00
const { verticalAlign , sizeUnits } = content ;
2021-07-31 14:53:35 +00:00
const result = await this . _createImageData ( context , content , entry , { tag : 'img' } ) ;
2021-05-19 22:24:50 +00:00
if ( typeof verticalAlign === 'string' ) { result . verticalAlign = verticalAlign ; }
2021-05-20 23:33:08 +00:00
if ( typeof sizeUnits === 'string' ) { result . sizeUnits = sizeUnits ; }
2021-05-19 22:24:50 +00:00
return result ;
}
2021-07-31 14:53:35 +00:00
async _createImageData ( context , data , entry , attributes ) {
2021-06-06 18:47:48 +00:00
const {
path ,
width : preferredWidth ,
height : preferredHeight ,
title ,
description ,
pixelated ,
imageRendering ,
appearance ,
background ,
collapsed ,
collapsible
} = data ;
2021-07-31 14:53:35 +00:00
const { width , height } = await this . _getImageMedia ( context , path , entry ) ;
2021-05-19 22:24:50 +00:00
const newData = Object . assign ( { } , attributes , { path , width , height } ) ;
2021-05-17 00:11:32 +00:00
if ( typeof preferredWidth === 'number' ) { newData . preferredWidth = preferredWidth ; }
if ( typeof preferredHeight === 'number' ) { newData . preferredHeight = preferredHeight ; }
if ( typeof title === 'string' ) { newData . title = title ; }
if ( typeof description === 'string' ) { newData . description = description ; }
if ( typeof pixelated === 'boolean' ) { newData . pixelated = pixelated ; }
2021-06-06 18:47:48 +00:00
if ( typeof imageRendering === 'string' ) { newData . imageRendering = imageRendering ; }
if ( typeof appearance === 'string' ) { newData . appearance = appearance ; }
if ( typeof background === 'boolean' ) { newData . background = background ; }
2021-05-18 21:41:27 +00:00
if ( typeof collapsed === 'boolean' ) { newData . collapsed = collapsed ; }
if ( typeof collapsible === 'boolean' ) { newData . collapsible = collapsible ; }
2021-05-17 00:11:32 +00:00
return newData ;
}
2021-07-31 14:53:35 +00:00
async _getImageMedia ( context , path , entry ) {
2021-05-17 00:11:32 +00:00
const { media } = context ;
2021-07-31 18:00:19 +00:00
const { dictionary } = entry ;
2020-03-02 03:36:42 +00:00
2021-07-31 18:00:19 +00:00
const createError = ( message ) => {
const { expression , reading } = entry ;
const readingSource = reading . length > 0 ? ` ( ${ reading } ) ` : '' ;
return new Error ( ` ${ message } at path ${ JSON . stringify ( path ) } for ${ expression } ${ readingSource } in ${ dictionary } ` ) ;
} ;
2021-05-17 00:11:32 +00:00
// Check if already added
let mediaData = media . get ( path ) ;
if ( typeof mediaData !== 'undefined' ) {
if ( MediaUtil . getFileExtensionFromImageMediaType ( mediaData . mediaType ) === null ) {
throw createError ( 'Media file is not a valid image' ) ;
}
return mediaData ;
2020-03-02 03:36:42 +00:00
}
2021-05-17 00:11:32 +00:00
// Find file in archive
2020-03-02 03:36:42 +00:00
const file = context . archive . file ( path ) ;
if ( file === null ) {
2021-05-17 00:11:32 +00:00
throw createError ( 'Could not find image' ) ;
2020-03-02 03:36:42 +00:00
}
2021-05-17 00:11:32 +00:00
// Load file content
2021-09-04 02:33:58 +00:00
let content = await file . async ( 'arraybuffer' ) ;
2021-03-14 22:04:19 +00:00
const mediaType = MediaUtil . getImageMediaTypeFromFileName ( path ) ;
2020-03-02 03:36:42 +00:00
if ( mediaType === null ) {
2021-05-17 00:11:32 +00:00
throw createError ( 'Could not determine media type for image' ) ;
2020-03-02 03:36:42 +00:00
}
2021-05-17 00:11:32 +00:00
// Load image data
2021-07-30 01:16:55 +00:00
let width ;
let height ;
2020-03-02 03:36:42 +00:00
try {
2021-09-04 02:33:58 +00:00
( { content , width , height } = await this . _mediaLoader . getImageDetails ( content , mediaType ) ) ;
2020-03-02 03:36:42 +00:00
} catch ( e ) {
2021-05-17 00:11:32 +00:00
throw createError ( 'Could not load image' ) ;
2020-03-02 03:36:42 +00:00
}
// Create image data
2021-05-17 00:11:32 +00:00
mediaData = {
2020-03-02 03:36:42 +00:00
dictionary ,
path ,
mediaType ,
2021-07-30 01:16:55 +00:00
width ,
height ,
2020-04-19 14:16:59 +00:00
content
2020-03-02 03:36:42 +00:00
} ;
2021-05-17 00:11:32 +00:00
media . set ( path , mediaData ) ;
2020-03-02 03:36:42 +00:00
2021-05-17 00:11:32 +00:00
return mediaData ;
2020-03-02 03:36:42 +00:00
}
2020-08-02 17:30:55 +00:00
async _fetchJsonAsset ( url ) {
2021-07-31 18:00:19 +00:00
const response = await fetch ( url , {
2020-08-02 17:30:55 +00:00
method : 'GET' ,
mode : 'no-cors' ,
cache : 'default' ,
credentials : 'omit' ,
redirect : 'follow' ,
referrerPolicy : 'no-referrer'
} ) ;
if ( ! response . ok ) {
throw new Error ( ` Failed to fetch ${ url } : ${ response . status } ` ) ;
}
return await response . json ( ) ;
}
2021-07-31 20:53:38 +00:00
_convertTermBankEntryV1 ( entry , dictionary ) {
2021-09-10 03:25:24 +00:00
let [ expression , reading , definitionTags , rules , score , ... glossary ] = entry ;
expression = this . _normalizeTermOrReading ( expression ) ;
reading = this . _normalizeTermOrReading ( reading . length > 0 ? reading : expression ) ;
2021-07-31 20:53:38 +00:00
return { expression , reading , definitionTags , rules , score , glossary , dictionary } ;
}
_convertTermBankEntryV3 ( entry , dictionary ) {
2021-09-10 03:25:24 +00:00
let [ expression , reading , definitionTags , rules , score , glossary , sequence , termTags ] = entry ;
expression = this . _normalizeTermOrReading ( expression ) ;
reading = this . _normalizeTermOrReading ( reading . length > 0 ? reading : expression ) ;
2021-07-31 20:53:38 +00:00
return { expression , reading , definitionTags , rules , score , glossary , sequence , termTags , dictionary } ;
}
_convertTermMetaBankEntry ( entry , dictionary ) {
const [ expression , mode , data ] = entry ;
return { expression , mode , data , dictionary } ;
}
_convertKanjiBankEntryV1 ( entry , dictionary ) {
const [ character , onyomi , kunyomi , tags , ... meanings ] = entry ;
return { character , onyomi , kunyomi , tags , meanings , dictionary } ;
}
_convertKanjiBankEntryV3 ( entry , dictionary ) {
const [ character , onyomi , kunyomi , tags , meanings , stats ] = entry ;
return { character , onyomi , kunyomi , tags , meanings , stats , dictionary } ;
}
_convertKanjiMetaBankEntry ( entry , dictionary ) {
const [ character , mode , data ] = entry ;
return { character , mode , data , dictionary } ;
}
_convertTagBankEntry ( entry , dictionary ) {
const [ name , category , order , notes , score ] = entry ;
return { name , category , order , notes , score , dictionary } ;
}
_addOldIndexTags ( index , results , dictionary ) {
const { tagMeta } = index ;
if ( typeof tagMeta !== 'object' || tagMeta === null ) { return ; }
for ( const name of Object . keys ( tagMeta ) ) {
const { category , order , notes , score } = tagMeta [ name ] ;
results . push ( { name , category , order , notes , score , dictionary } ) ;
}
}
_getArchiveFiles ( archive , fileNameFormat ) {
const indexPosition = fileNameFormat . indexOf ( '?' ) ;
const prefix = fileNameFormat . substring ( 0 , indexPosition ) ;
const suffix = fileNameFormat . substring ( indexPosition + 1 ) ;
const results = [ ] ;
for ( let i = 1 ; true ; ++ i ) {
const fileName = ` ${ prefix } ${ i } ${ suffix } ` ;
const file = archive . file ( fileName ) ;
if ( ! file ) { break ; }
results . push ( file ) ;
}
return results ;
}
async _readFileSequence ( files , convertEntry , schema , dictionaryTitle ) {
2021-07-31 23:13:41 +00:00
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 ( ) ;
} ;
}
2021-07-31 20:53:38 +00:00
const results = [ ] ;
for ( const file of files ) {
const entries = JSON . parse ( await file . async ( 'string' ) ) ;
2021-07-31 23:13:41 +00:00
count = Array . isArray ( entries ) ? Math . max ( entries . length , 1 ) : 1 ;
startIndex = progressData . index ;
this . _progress ( ) ;
2021-07-31 20:53:38 +00:00
this . _validateJsonSchema ( entries , schema , file . name ) ;
2021-07-31 23:13:41 +00:00
progressData . index = startIndex + 1 ;
this . _progress ( ) ;
2021-07-31 20:53:38 +00:00
for ( const entry of entries ) {
results . push ( convertEntry ( entry , dictionaryTitle ) ) ;
}
}
return results ;
}
2021-09-07 15:12:58 +00:00
_getMetaCounts ( metaList ) {
const countsMap = new Map ( ) ;
for ( const { mode } of metaList ) {
let count = countsMap . get ( mode ) ;
count = typeof count !== 'undefined' ? count + 1 : 1 ;
countsMap . set ( mode , count ) ;
}
const counts = { total : metaList . length } ;
for ( const [ key , value ] of countsMap . entries ( ) ) {
if ( Object . prototype . hasOwnProperty . call ( counts , key ) ) { continue ; }
counts [ key ] = value ;
}
return counts ;
}
2021-09-10 03:25:24 +00:00
_normalizeTermOrReading ( text ) {
2021-09-26 17:29:50 +00:00
// Note: this function should not perform String.normalize on the text,
// as it will characters in an undesirable way.
// Thus, this function is currently a no-op.
// Example:
// - '\u9038'.normalize('NFC') => '\u9038' (逸)
// - '\ufa67'.normalize('NFC') => '\u9038' (逸 => 逸)
return text ;
2021-09-10 03:25:24 +00:00
}
2020-03-31 00:51:20 +00:00
}