From cce2e5114fda00ec9345af02d4f07379fc69ed4c Mon Sep 17 00:00:00 2001 From: EthanFennell Date: Wed, 6 Nov 2024 15:56:59 -0500 Subject: [PATCH] Completed parseBookCollections Moved functions below convertConfig function and renamed them Removed some usages of global variable 'data' in parse functions (to be continued) --- convert/convertConfig.ts | 1039 +++++++++----------------------------- 1 file changed, 230 insertions(+), 809 deletions(-) diff --git a/convert/convertConfig.ts b/convert/convertConfig.ts index dbc7a97e1..e1dbd594c 100644 --- a/convert/convertConfig.ts +++ b/convert/convertConfig.ts @@ -5,8 +5,10 @@ import { Task, TaskOutput } from './Task'; import { convertMarkdownsToHTML } from './convertMarkdown'; import { splitVersion } from './stringUtils'; import type { ConfigData, BookCollectionData, BookCollectionAudioData, StyleData } from '$config'; +import { ConvertFirebase } from 'convertFirebase'; const data: ConfigData = {}; +const fontFamilies: string[] = []; function decodeFromXml(input: string): string { return input @@ -175,40 +177,12 @@ function convertCollectionFooter(collectionTag: Element, document: Document) { return footer; } -// -----Start here, we will take a closer look at above code later------ - -// We may move these type definitions elsewhere later - -// This is for data common to BOTH SAB AND DAB -export type AppConfig = { - name?: string; - package?: string; - version?: string; - programType?: string; - programVersion?: string; - mainFeatures?: any; - // Add to this in order as they appear in convertConfig -} - -// Add SAB-only data here -export type ScriptureConfig = AppConfig & { - bookCollections?: BookCollectionData[]; - // Add to this in order as they appear in convertConfig -} - -// Add DAB-only data here -export type DictionaryConfig = AppConfig & { - // Add to this in order as they appear in convertConfig -} - function convertConfig(dataDir: string, verbose: number) { const dom = new jsdom.JSDOM(readFileSync(path.join(dataDir, 'appdef.xml')).toString(), { contentType: 'text/xml' }); const { document } = dom.window; - // We can probably leave these small blocks in convertConfig - // Name data.name = document.getElementsByTagName('app-name')[0].innerHTML; if (verbose) console.log(`Converting ${data.name}...`); @@ -231,23 +205,81 @@ function convertConfig(dataDir: string, verbose: number) { data.programVersion = '100.0'; } - // Break out these larger blocks into their own functions and call them here. For example, convertFeatures(...), convertThemes(...), etc. - // ...Then we will add returned data to our defined types above (AppConfig, ScriptureConfig, or DictionaryConfig) - // This will require knowledge of what is needed for each. Building PWA files for SAB and DAB should show this. + data.mainFeatures = parseFeatures(document, verbose); + + data.fonts = parseFonts(document, verbose); + + const { themes, defaultTheme } = parseColorThemes(document, verbose); + data.themes = themes; + if (defaultTheme !== '') { + data.defaultTheme = defaultTheme; + } + + const mainStyles = document.querySelector('styles')!; + data.styles = parseStyles(mainStyles, verbose); + + data.traits = parseTraits(document, dataDir, verbose); + + data.bookCollections = parseBookCollections(document, verbose); + + // After all the book collections have been parsed, we can determine some traits + data.traits['has-glossary'] = + data.bookCollections.filter( + (bc) => bc.books.filter((b) => b.type === 'glossary').length > 0 + ).length > 0; + + parseInterfaceLanguages(document, data, verbose); + + parseMenuLocalizations(document, data, verbose); + + parseKeys(document, data, verbose); + + /* about?: string; */ + + parseAnalytics(document, data, verbose); + + parseFirebase(document, data, verbose); + + parseAudioSources(document, data, verbose); + + parseVideos(document, data, verbose); - // ------- - - // Features + parseIllustrations(document, data, verbose); + + parseLayouts(document, data, verbose); + + parseBackgroundImages(document, data, verbose); + + parseWatermarkImages(document, data, verbose); + + parseMenuItems(document, data, verbose); + + parsePlans(document, data, verbose); + + /* + security?: { + features?: { + [key: string]: any; + }; + pin: string; + mode: string; + }; + */ + + return filterFeaturesNotReady(data); +} + +function parseFeatures(document: Document, verbose: number) { const mainFeatureTags = document .querySelector('features[type=main]') ?.getElementsByTagName('e'); if (!mainFeatureTags) throw new Error('Features tag not found in xml'); - data.mainFeatures = {}; + let mainFeatures: { [key: string]: any } = {}; for (const tag of mainFeatureTags) { try { const value: any = tag.attributes.getNamedItem('value')!.value; - data.mainFeatures[tag.attributes.getNamedItem('name')!.value] = parseConfigValue(value); + mainFeatures[tag.attributes.getNamedItem('name')!.value] = parseConfigValue(value); } catch (e) { if (e instanceof ReferenceError) { console.error( @@ -256,12 +288,14 @@ function convertConfig(dataDir: string, verbose: number) { } else throw e; } } - if (verbose) console.log(`Converted ${Object.keys(data.mainFeatures).length} features`); - - // Fonts + if (verbose) console.log(`Converted ${Object.keys(mainFeatures).length} features`); + + return mainFeatures; +} + +function parseFonts(document: Document, verbose: number) { const fontTags = document.getElementsByTagName('fonts')[0].getElementsByTagName('font'); - const fontFamilies: string[] = []; - data.fonts = []; + let fonts = []; for (const tag of fontTags) { const family = tag.attributes.getNamedItem('family')!.value; @@ -274,22 +308,27 @@ function convertConfig(dataDir: string, verbose: number) { .querySelector('sd[property=font-weight]')! .attributes.getNamedItem('value')!.value; fontFamilies.push(family); - data.fonts.push({ family, name, file, fontStyle, fontWeight }); + fonts.push({ family, name, file, fontStyle, fontWeight }); } - if (verbose) console.log(`Converted ${data.fonts.length} fonts`); - // Color themes + if (verbose) console.log(`Converted ${fonts.length} fonts`); + + return fonts; +} + +function parseColorThemes(document: Document, verbose: number) { const colorThemeTags = document .getElementsByTagName('color-themes')[0] .getElementsByTagName('color-theme'); const colorSetTags = document.getElementsByTagName('colors'); - data.themes = []; + let themes = []; + let defaultTheme = ""; for (const tag of colorThemeTags) { const theme = tag.attributes.getNamedItem('name')!.value; if (verbose >= 2) console.log(`. theme ${theme}`); - data.themes.push({ + themes.push({ name: theme, enabled: tag.attributes.getNamedItem('enabled')?.value === 'true', colorSets: Array.from(colorSetTags).map((cst) => { @@ -303,6 +342,7 @@ function convertConfig(dataDir: string, verbose: number) { if (verbose >= 3) console.log(`.. colors[${name}]=${value} `); } if (verbose >= 3) console.log(`.. done with colorTags`); + Object.keys(colors).forEach((x) => { if (verbose >= 3) console.log(`.. ${x}: colors[${x}]=${colors[x]}`); while (!colors[x].startsWith('#')) { @@ -318,6 +358,7 @@ function convertConfig(dataDir: string, verbose: number) { } }); if (verbose >= 3) console.log(`.. done with resolving colors`); + const type = cst.getAttribute('type')!; if (verbose >= 2) console.log(`.. ${type}: ${JSON.stringify(colors)}`); return { @@ -326,34 +367,39 @@ function convertConfig(dataDir: string, verbose: number) { }; }) }); + if (tag.attributes.getNamedItem('default')?.value === 'true') - data.defaultTheme = data.themes[data.themes.length - 1].name; + defaultTheme = themes[themes.length - 1].name; } - if (verbose) console.log(`Converted ${data.themes.length} themes`); - // Styles - const mainStyles = document.querySelector('styles')!; - data.styles = parseStyles(mainStyles, verbose); + if (verbose) console.log(`Converted ${themes.length} themes`); + + return { themes, defaultTheme }; +} - // Traits +function parseTraits(document: Document, dataDir: string, verbose: number) { const traitTags = document.getElementsByTagName('traits')[0]?.getElementsByTagName('trait'); - data.traits = {}; + let traits: { [key: string]: any } = {}; if (traitTags?.length > 0) { for (const tag of traitTags) { - data.traits[tag.attributes.getNamedItem('name')!.value] = + traits[tag.attributes.getNamedItem('name')!.value] = tag.attributes.getNamedItem('value')?.value === 'true'; } } + // Add traits - data.traits['has-borders'] = !dirEmpty(path.join(dataDir, 'borders')); - data.traits['has-illustrations'] = !dirEmpty(path.join(dataDir, 'illustrations')); + traits['has-borders'] = !dirEmpty(path.join(dataDir, 'borders')); + traits['has-illustrations'] = !dirEmpty(path.join(dataDir, 'illustrations')); + + if (verbose) console.log(`Converted ${Object.keys(traits).length} traits`); - if (verbose) console.log(`Converted ${Object.keys(data.traits).length} traits`); + return traits; +} - // Book collections +function parseBookCollections(document: Document, verbose: number) { const booksTags = document.getElementsByTagName('books'); - data.bookCollections = []; + let bookCollections = []; for (const tag of booksTags) { if (verbose >= 2) console.log(`Converting Collection: ${tag.id}`); @@ -506,7 +552,7 @@ function convertConfig(dataDir: string, verbose: number) { const bcStyles = tag.querySelector('styles'); const styles = bcStyles ? parseStyles(bcStyles, verbose) : undefined; - data.bookCollections.push({ + bookCollections.push({ id: tag.id, collectionName, collectionAbbreviation, @@ -524,82 +570,93 @@ function convertConfig(dataDir: string, verbose: number) { if (verbose >= 3) console.log( `.... collection: `, - JSON.stringify(data.bookCollections[data.bookCollections.length - 1]) + JSON.stringify(bookCollections[bookCollections.length - 1]) ); } - // After all the book collections have been parsed, we can determine some traits - data.traits['has-glossary'] = - data.bookCollections.filter( - (bc) => bc.books.filter((b) => b.type === 'glossary').length > 0 - ).length > 0; + if (verbose) console.log( - `Converted ${data.bookCollections.length} book collections with [${data.bookCollections + `Converted ${bookCollections.length} book collections with [${bookCollections .map((x) => x.books.length) .join(', ')}] books` ); - // Inteface Languages + return bookCollections; +} + +function parseInterfaceLanguages(document: Document, data: any, verbose: number): void { const interfaceLanguagesTag = document.getElementsByTagName('interface-languages')[0]; const useSystemLanguage = parseTrait(interfaceLanguagesTag, 'use-system-language') === 'true'; data.interfaceLanguages = { useSystemLanguage, writingSystems: {} }; + const writingSystemsTags = interfaceLanguagesTag .getElementsByTagName('writing-systems')[0] .getElementsByTagName('writing-system'); + for (const tag of writingSystemsTags) { const code: string = tag.attributes.getNamedItem('code')!.value; const fontFamily = tag.getElementsByTagName('font-family')[0].innerHTML; const textDirection = parseTrait(tag, 'text-direction'); + if (verbose >= 2) console.log(`.. writingSystem: ${code}`); + const displaynamesTag = tag.getElementsByTagName('display-names')[0]; - const displayNames: typeof data.interfaceLanguages.writingSystems.displayNames.displayNames = - {}; + const displayNames: Record = {}; + for (const form of displaynamesTag.getElementsByTagName('form')) { displayNames[form.attributes.getNamedItem('lang')!.value] = form.innerHTML; } + data.interfaceLanguages.writingSystems[code] = { fontFamily, textDirection, displayNames }; } - - // Menu localizations + + if (verbose) console.log(`Converted ${Object.keys(data.interfaceLanguages.writingSystems).length} writing systems`); +} + +function parseMenuLocalizations(document: Document, data: any, verbose: number): void { const translationMappingsTags = document.getElementsByTagName('translation-mappings'); + for (const translationMappingsTag of translationMappingsTags) { const defaultLang = translationMappingsTag.attributes.getNamedItem('default-lang')!.value; data.translationMappings = { defaultLang, mappings: {} }; - const translationMappingsTags = translationMappingsTag.getElementsByTagName('tm'); - for (const tag of translationMappingsTags) { + const translationMappingTags = translationMappingsTag.getElementsByTagName('tm'); + + for (const tag of translationMappingTags) { if (verbose >= 2) console.log(`.. translationMapping: ${tag.id}`); - const localizations: typeof data.translationMappings.mappings.key = {}; + + const localizations: Record = {}; for (const localization of tag.getElementsByTagName('t')) { - localizations[localization.attributes.getNamedItem('lang')!.value] = decodeFromXml( - localization.innerHTML - ); + const lang = localization.attributes.getNamedItem('lang')!.value; + localizations[lang] = decodeFromXml(localization.innerHTML); } + if (verbose >= 3) console.log(`....`, JSON.stringify(localizations)); data.translationMappings.mappings[tag.id] = localizations; } - if (verbose) + + if (verbose) { console.log( - `Converted ${Object.keys(data.translationMappings.mappings).length - } translation mappings` + `Converted ${Object.keys(data.translationMappings.mappings).length} translation mappings` ); + } } +} - // Keys +function parseKeys(document: Document, data: any, verbose: number): void { if (document.getElementsByTagName('keys').length > 0) { data.keys = Array.from( document.getElementsByTagName('keys')[0].getElementsByTagName('key') ).map((key) => key.innerHTML); if (verbose) console.log(`Converted ${data.keys.length} keys`); } +} - /* about?: string; */ - - // Analytics +function parseAnalytics(document: Document, data: any, verbose: number): void { const analyticsElements = document.getElementsByTagName('analytics'); - // Ensure data.analytics is initialized if it doesn't exist - necessary?? + // Initialize data.analytics if it doesn't exist if (!data.analytics) { data.analytics = { enabled: false, @@ -610,7 +667,7 @@ function convertConfig(dataDir: string, verbose: number) { for (const analyticsElement of analyticsElements) { const enabledAttribute = analyticsElement.getAttribute('enabled'); - if (enabledAttribute !== null && enabledAttribute === 'true') { + if (enabledAttribute === 'true') { data.analytics.enabled = true; // Get all analytics-provider elements within the current analytics element @@ -625,12 +682,12 @@ function convertConfig(dataDir: string, verbose: number) { let parameters: { [key: string]: string } | undefined = undefined; const parametersTags = providerElement.getElementsByTagName('analytics-parameter'); - if (parametersTags?.length > 0) { + if (parametersTags.length > 0) { parameters = {}; for (const parameterTag of parametersTags) { - const name = parameterTag.getAttribute('name')!; - const value = parameterTag.getAttribute('value')!; - parameters[name] = value; + const paramName = parameterTag.getAttribute('name')!; + const paramValue = parameterTag.getAttribute('value')!; + parameters[paramName] = paramValue; } } @@ -644,12 +701,13 @@ function convertConfig(dataDir: string, verbose: number) { } } - if (verbose) console.log(`Converted ${analyticsElements.length} analyticsElements`); + if (verbose) console.log(`Converted ${analyticsElements.length} analytics elements`); +} - // Firebase +function parseFirebase(document: Document, data: any, verbose: number): void { const firebaseElements = document.getElementsByTagName('firebase'); - // Ensure data.firebase is initialized if it doesn't exist + // Initialize data.firebase if it doesn't exist if (!data.firebase) { data.firebase = { features: {} @@ -675,30 +733,28 @@ function convertConfig(dataDir: string, verbose: number) { } } - if (verbose) console.log(`Converted ${firebaseElements.length} firebaseElements`); + if (verbose) console.log(`Converted ${firebaseElements.length} firebase elements`); +} - - // Audio Sources - const audioSources = document - .getElementsByTagName('audio-sources')[0] +function parseAudioSources(document: Document, data: any, verbose: number): void { + const audioSources = document.getElementsByTagName('audio-sources')[0] ?.getElementsByTagName('audio-source'); + if (audioSources?.length > 0) { data.audio = { sources: {} }; + for (const source of audioSources) { - const id = source.getAttribute('id')!.toString(); + const id = source.getAttribute('id')!; if (verbose >= 2) console.log(`Converting audioSource: ${id}`); - const type = source.getAttribute('type')!.toString(); + const type = source.getAttribute('type')!; const name = source.getElementsByTagName('name')[0].innerHTML; - if (verbose >= 3) console.log(` type=${type}, name=${name}`); - data.audio.sources[id] = { - type: type, - name: name - }; + + data.audio.sources[id] = { type, name }; + if (type !== 'assets') { data.audio.sources[id].accessMethods = source .getElementsByTagName('access-methods')[0] ?.getAttribute('value')! - .toString() .split('|'); data.audio.sources[id].folder = source.getElementsByTagName('folder')[0]?.innerHTML; @@ -709,157 +765,121 @@ function convertConfig(dataDir: string, verbose: number) { if (type === 'fcbh') { data.audio.sources[id].key = source.getElementsByTagName('key')[0].innerHTML; - data.audio.sources[id].damId = - source.getElementsByTagName('dam-id')[0].innerHTML; + data.audio.sources[id].damId = source.getElementsByTagName('dam-id')[0].innerHTML; } } if (verbose >= 3) console.log(`....`, JSON.stringify(data.audio.sources[id])); } - const audioTags = document - .getElementsByTagName('audio-files')[0] + // Audio files + const audioTags = document.getElementsByTagName('audio-files')[0] ?.getElementsByTagName('audio'); if (audioTags?.length > 0) { data.audio.files = []; - for (const tag of audioTags) { const fileEntry = tag.getElementsByTagName('filename')[0]; if (!fileEntry) continue; const filename = fileEntry.innerHTML; const src = fileEntry.getAttribute('src') ?? ''; - - data.audio.files.push({ - name: filename, - src: src - }); + data.audio.files.push({ name: filename, src }); } } } - if (verbose) console.log(`Converted ${audioSources?.length} audio sources`); +} +function parseVideos(document: Document, data: any, verbose: number): void { const videoTags = document.getElementsByTagName('videos')[0]?.getElementsByTagName('video'); if (videoTags?.length > 0) { data.videos = []; for (const tag of videoTags) { const placementTag = tag.getElementsByTagName('placement')[0]; - const placement = - placementTag == undefined - ? undefined - : { - pos: placementTag.attributes.getNamedItem('pos')!.value, - ref: placementTag.attributes.getNamedItem('ref')!.value.split('|')[1], - collection: placementTag.attributes - .getNamedItem('ref')! - .value.split('|')[0] - }; - const tagWidth = tag.attributes.getNamedItem('width') - ? parseInt(tag.attributes.getNamedItem('width')!.value) - : 0; - const tagHeight = tag.attributes.getNamedItem('height') - ? parseInt(tag.attributes.getNamedItem('height')!.value) - : 0; - let onlineUrlHTML = tag.getElementsByTagName('online-url')[0] - ? tag.getElementsByTagName('online-url')[0]?.innerHTML - : ''; - if (onlineUrlHTML) { - onlineUrlHTML = convertVideoUrl(onlineUrlHTML); - } + const placement = placementTag ? { + pos: placementTag.attributes.getNamedItem('pos')!.value, + ref: placementTag.attributes.getNamedItem('ref')!.value.split('|')[1], + collection: placementTag.attributes.getNamedItem('ref')!.value.split('|')[0] + } : undefined; + + const width = tag.getAttribute('width') ? parseInt(tag.getAttribute('width')!) : 0; + const height = tag.getAttribute('height') ? parseInt(tag.getAttribute('height')!) : 0; + + let onlineUrl = tag.getElementsByTagName('online-url')[0]?.innerHTML || ''; + if (onlineUrl) onlineUrl = convertVideoUrl(onlineUrl); + const filenameTag = tag.getElementsByTagName('filename')[0]; const filename = filenameTag ? filenameTag.innerHTML : ''; data.videos.push({ - id: tag.attributes.getNamedItem('id')!.value, - src: tag.attributes.getNamedItem('src')?.value, - width: tagWidth, - height: tagHeight, + id: tag.getAttribute('id')!, + src: tag.getAttribute('src'), + width, + height, title: tag.getElementsByTagName('title')[0]?.innerHTML, thumbnail: tag.getElementsByTagName('thumbnail')[0]?.innerHTML, - onlineUrl: decodeFromXml(onlineUrlHTML), - filename: filename, + onlineUrl: decodeFromXml(onlineUrl), + filename, placement }); } } + data.traits['has-video'] = !!data.videos?.length; +} - data.traits['has-video'] = data.videos && data.videos.length > 0; +function parseIllustrations(document: Document, data: any, verbose: number): void { const imagesTags = document.getElementsByTagName('images'); if (imagesTags?.length > 0) { data.illustrations = []; + for (const tag of imagesTags) { - const imageType = tag.attributes.getNamedItem('type') - ? tag.attributes.getNamedItem('type')!.value - : ''; - if (imageType === 'illustration') { + if (tag.getAttribute('type') === 'illustration') { const illustrationTags = tag.getElementsByTagName('image'); - if (illustrationTags?.length > 0) { - for (const image of illustrationTags) { - const filename = image.getElementsByTagName('filename')[0] - ? image.getElementsByTagName('filename')[0]?.innerHTML - : image.innerHTML; - const imageWidth = image.attributes.getNamedItem('width') - ? parseInt(image.attributes.getNamedItem('width')!.value) - : 0; - const imageHeight = image.attributes.getNamedItem('height') - ? parseInt(image.attributes.getNamedItem('height')!.value) - : 0; - const placementTag = image.getElementsByTagName('placement')[0]; - const placement = - placementTag == undefined - ? undefined - : { - pos: placementTag.attributes.getNamedItem('pos')!.value, - ref: placementTag.attributes - .getNamedItem('ref')! - .value.split('|')[1], - caption: placementTag.attributes.getNamedItem('caption') - ? placementTag.attributes.getNamedItem('caption')!.value - : '', - collection: placementTag.attributes - .getNamedItem('ref')! - .value.split('|')[0] - }; - data.illustrations.push({ - filename: filename, - width: imageWidth, - height: imageHeight, - placement - }); - } + + for (const image of illustrationTags) { + const filename = image.getElementsByTagName('filename')[0]?.innerHTML || image.innerHTML; + const width = image.getAttribute('width') ? parseInt(image.getAttribute('width')!) : 0; + const height = image.getAttribute('height') ? parseInt(image.getAttribute('height')!) : 0; + + const placementTag = image.getElementsByTagName('placement')[0]; + const placement = placementTag ? { + pos: placementTag.getAttribute('pos')!, + ref: placementTag.getAttribute('ref')?.split('|')[1], + caption: placementTag.getAttribute('caption') || '', + collection: placementTag.getAttribute('ref')?.split('|')[0] + } : undefined; + + data.illustrations.push({ filename, width, height, placement }); } } } } + if (verbose) console.log(`Converted ${imagesTags?.length} illustrations`); +} + +function parseLayouts(document: Document, data: any, verbose: number): void { const layoutRoot = document.getElementsByTagName('layouts')[0]; - data.defaultLayout = layoutRoot?.attributes.getNamedItem('default')?.value; + data.defaultLayout = layoutRoot?.getAttribute('default'); const layouts = layoutRoot?.getElementsByTagName('layout'); if (layouts?.length > 0) { data.layouts = []; + for (const layout of layouts) { - const mode = layout.attributes.getNamedItem('mode')!.value; + const mode = layout.getAttribute('mode')!; if (verbose >= 2) console.log(`Converting layout`, mode); - const enabled = layout.attributes.getNamedItem('enabled')!.value === 'true'; - const featureElements = layout.getElementsByTagName('features')[0]; + + const enabled = layout.getAttribute('enabled') === 'true'; + const features: { [key: string]: string } = {}; + const featureElements = layout.getElementsByTagName('features')[0]; if (featureElements) { for (const feature of featureElements.getElementsByTagName('e')) { - const name = feature.attributes.getNamedItem('name')!.value; - const value = feature.attributes.getNamedItem('value')!.value; - if (verbose >= 2) - console.log(`.. Converting feature: name=${name}, value=${value}`); - features[name] = value; + features[feature.getAttribute('name')!] = feature.getAttribute('value')!; } } - const layoutCollectionElements = layout.getElementsByTagName('layout-collection'); - const layoutCollections = - layoutCollectionElements.length > 0 - ? Array.from(layoutCollectionElements).map((element) => { - return element.attributes.getNamedItem('id')!.value; - }) - : [data.bookCollections[0].id]; + + const layoutCollections = Array.from(layout.getElementsByTagName('layout-collection')).map(element => element.getAttribute('id')!) || [data.bookCollections[0]?.id]; data.layouts.push({ mode, @@ -870,8 +890,9 @@ function convertConfig(dataDir: string, verbose: number) { } } if (verbose) console.log(`Converted ${layouts?.length} layouts`); +} - // Background images +function parseBackgroundImages(document: Document, data: any, verbose: number) { const backgroundImages = document .querySelector('images[type=background]') ?.getElementsByTagName('image'); @@ -884,8 +905,10 @@ function convertConfig(dataDir: string, verbose: number) { data.backgroundImages.push({ width, height, filename }); } } + if (verbose) console.log(`Converted ${backgroundImages?.length} background images`); +} - // Watermark images +function parseWatermarkImages(document: Document, data: any, verbose: number) { const watermarkImages = document .querySelector('images[type=watermark]') ?.getElementsByTagName('image'); @@ -898,611 +921,10 @@ function convertConfig(dataDir: string, verbose: number) { data.watermarkImages.push({ width, height, filename }); } } + if (verbose) console.log(`Converted ${watermarkImages?.length} watermark images`); +} - // Menu Items - const menuItems = document - .getElementsByTagName('menu-items')[0] - ?.getElementsByTagName('menu-item'); - if (menuItems?.length > 0) { - data.menuItems = []; - for (const menuItem of menuItems) { - const type = menuItem.attributes.getNamedItem('type')!.value; - if (verbose >= 2) console.log(`.. Converting menuItem: ${type}`); - if (verbose >= 3) console.log('.... menuItem:', menuItem.outerHTML); - - const titleTags = menuItem.getElementsByTagName('title')[0].getElementsByTagName('t'); - const title: { [lang: string]: string } = {}; - for (const titleTag of titleTags) { - title[titleTag.attributes.getNamedItem('lang')!.value] = titleTag.innerHTML; - } - - const linkTags = menuItem.getElementsByTagName('link')[0]?.getElementsByTagName('t'); - const link: { [lang: string]: string } = {}; - if (linkTags) { - for (const linkTag of linkTags) { - link[linkTag.attributes.getNamedItem('lang')!.value] = linkTag.innerHTML; - } - } - - const linkIdTags = menuItem - .getElementsByTagName('link-id')[0] - ?.getElementsByTagName('t'); - const linkId: { [lang: string]: string } = {}; - if (linkIdTags) { - for (const linkIdTag of linkIdTags) { - linkId[linkIdTag.attributes.getNamedItem('lang')!.value] = linkIdTag.innerHTML; - } - } - - const imageTags = menuItem - .getElementsByTagName('images')[0] - ?.getElementsByTagName('image'); - const images = []; - if (imageTags) { - for (const imageTag of imageTags) { - images.push({ - width: parseInt(imageTag.attributes.getNamedItem('width')!.value), - height: parseInt(imageTag.attributes.getNamedItem('height')!.value), - file: imageTag.innerHTML - }); - } - } - - data.menuItems.push({ - type, - title, - link, - linkId, - images - }); - - if (verbose >= 3) console.log(`....`, JSON.stringify(data.menuItems)); - } - } - - - //plans - const plansTags = document.getElementsByTagName('plans'); - if (plansTags?.length > 0) { - const plansTag = plansTags[0]; - const featuresTag = plansTag.getElementsByTagName('features')[0]; - const features: { [key: string]: string } = {}; - if (featuresTag) { - for (const feature of featuresTag.getElementsByTagName('e')) { - const name = feature.attributes.getNamedItem('name')!.value; - const value = feature.attributes.getNamedItem('value')!.value; - if (verbose >= 2) - console.log(`.. Converting feature: name=${name}, value=${value}`); - features[name] = value; - } - } - - const planTags = plansTag.getElementsByTagName('plan'); - const plans = []; - if (planTags?.length > 0) { - for (const tag of planTags) { - const titleTags = tag.getElementsByTagName('title')[0].getElementsByTagName('t'); - const title: { [lang: string]: string } = {}; - for (const titleTag of titleTags) { - title[titleTag.attributes.getNamedItem('lang')!.value] = titleTag.innerHTML; - } - //image - const imageTag = tag.getElementsByTagName('image')[0]; - - let image = undefined; - if (imageTag?.innerHTML) { - image = { - width: Number(imageTag.attributes.getNamedItem('width')!.value), - height: Number(imageTag.attributes.getNamedItem('height')!.value), - file: imageTag.innerHTML - }; - } - - const plan = { - id: tag.attributes.getNamedItem('id')!.value, - days: Number(tag.attributes.getNamedItem('days')!.value), - title, - filename: tag.getElementsByTagName('filename')[0].innerHTML, - image - }; - plans.push(plan); - } - data.plans = { - features, - plans - }; - } - } - - - - /* - security?: { - features?: { - [key: string]: any; - }; - pin: string; - mode: string; - }; - */ - - return filterFeaturesNotReady(data); -} - -/* End here*/ -/*Move all new functions right here*/ - -function convertFeatures(document: Document, data: any, verbose: number): void { - // Features - const mainFeatureTags = document - .querySelector('features[type=main]') - ?.getElementsByTagName('e'); - if (!mainFeatureTags) throw new Error('Features tag not found in xml'); - data.mainFeatures = {}; - - for (const tag of mainFeatureTags) { - try { - const value: any = tag.attributes.getNamedItem('value')!.value; - data.mainFeatures[tag.attributes.getNamedItem('name')!.value] = parseConfigValue(value); - } catch (e) { - if (e instanceof ReferenceError) { - console.error( - 'The main features section did not have the expected attributes `name` and `value`' - ); - } else throw e; - } - } - if (verbose) console.log(`Converted ${Object.keys(data.mainFeatures).length} features`); -} - -function convertFonts(document: Document, data: any, verbose: number): void { - const fontTags = document.getElementsByTagName('fonts')[0].getElementsByTagName('font'); - const fontFamilies: string[] = []; - data.fonts = []; - - for (const tag of fontTags) { - const family = tag.attributes.getNamedItem('family')!.value; - const name = tag.getElementsByTagName('display-name')[0]?.innerHTML; - const file = tag.getElementsByTagName('f')[0].innerHTML; - const fontStyle = tag - .querySelector('sd[property=font-style]')! - .attributes.getNamedItem('value')!.value; - const fontWeight = tag - .querySelector('sd[property=font-weight]')! - .attributes.getNamedItem('value')!.value; - fontFamilies.push(family); - data.fonts.push({ family, name, file, fontStyle, fontWeight }); - } - - if (verbose) console.log(`Converted ${data.fonts.length} fonts`); -} - -function convertColorThemes(document: Document, data: any, verbose: number): void { - const colorThemeTags = document - .getElementsByTagName('color-themes')[0] - .getElementsByTagName('color-theme'); - const colorSetTags = document.getElementsByTagName('colors'); - data.themes = []; - - for (const tag of colorThemeTags) { - const theme = tag.attributes.getNamedItem('name')!.value; - if (verbose >= 2) console.log(`. theme ${theme}`); - - data.themes.push({ - name: theme, - enabled: tag.attributes.getNamedItem('enabled')?.value === 'true', - colorSets: Array.from(colorSetTags).map((cst) => { - const colorTags = cst.getElementsByTagName('color'); - const colors: { [key: string]: string } = {}; - for (const color of colorTags) { - const cm = color.querySelector(`cm[theme="${theme}"]`); - const name = color.getAttribute('name'); - const value = cm?.getAttribute('value'); - if (name && value) colors[name] = value; - if (verbose >= 3) console.log(`.. colors[${name}]=${value} `); - } - if (verbose >= 3) console.log(`.. done with colorTags`); - - Object.keys(colors).forEach((x) => { - if (verbose >= 3) console.log(`.. ${x}: colors[${x}]=${colors[x]}`); - while (!colors[x].startsWith('#')) { - const key = colors[x]; - const value = colors[key]; - if (value === x) { - throw `Color value equals color name! Can't Resolve!\ncolor=${x}, theme=${theme}, value=${value} `; - } - if (!value) { - break; - } - colors[x] = value; - } - }); - if (verbose >= 3) console.log(`.. done with resolving colors`); - - const type = cst.getAttribute('type')!; - if (verbose >= 2) console.log(`.. ${type}: ${JSON.stringify(colors)}`); - return { - type, - colors - }; - }) - }); - - if (tag.attributes.getNamedItem('default')?.value === 'true') - data.defaultTheme = data.themes[data.themes.length - 1].name; - } - - if (verbose) console.log(`Converted ${data.themes.length} themes`); -} - -function convertStyles(document: Document, data: any, verbose: number): void { - const mainStyles = document.querySelector('styles')!; - data.styles = parseStyles(mainStyles, verbose); -} - -function convertTraits(document: Document, data: any, dataDir: string, verbose: number): void { - const traitTags = document.getElementsByTagName('traits')[0]?.getElementsByTagName('trait'); - data.traits = {}; - - if (traitTags?.length > 0) { - for (const tag of traitTags) { - data.traits[tag.attributes.getNamedItem('name')!.value] = - tag.attributes.getNamedItem('value')?.value === 'true'; - } - } - - // Add traits - data.traits['has-borders'] = !dirEmpty(path.join(dataDir, 'borders')); - data.traits['has-illustrations'] = !dirEmpty(path.join(dataDir, 'illustrations')); - - if (verbose) console.log(`Converted ${Object.keys(data.traits).length} traits`); -} - -function convertInterfaceLanguages(document: Document, data: any, verbose: number): void { - // Interface Languages - const interfaceLanguagesTag = document.getElementsByTagName('interface-languages')[0]; - const useSystemLanguage = parseTrait(interfaceLanguagesTag, 'use-system-language') === 'true'; - - data.interfaceLanguages = { useSystemLanguage, writingSystems: {} }; - - const writingSystemsTags = interfaceLanguagesTag - .getElementsByTagName('writing-systems')[0] - .getElementsByTagName('writing-system'); - - for (const tag of writingSystemsTags) { - const code: string = tag.attributes.getNamedItem('code')!.value; - const fontFamily = tag.getElementsByTagName('font-family')[0].innerHTML; - const textDirection = parseTrait(tag, 'text-direction'); - - if (verbose >= 2) console.log(`.. writingSystem: ${code}`); - - const displaynamesTag = tag.getElementsByTagName('display-names')[0]; - const displayNames: Record = {}; - - for (const form of displaynamesTag.getElementsByTagName('form')) { - displayNames[form.attributes.getNamedItem('lang')!.value] = form.innerHTML; - } - - data.interfaceLanguages.writingSystems[code] = { fontFamily, textDirection, displayNames }; - } - - if (verbose) console.log(`Converted ${Object.keys(data.interfaceLanguages.writingSystems).length} writing systems`); -} - -function convertMenuLocalizations(document: Document, data: any, verbose: number): void { - // Menu Localizations - const translationMappingsTags = document.getElementsByTagName('translation-mappings'); - - for (const translationMappingsTag of translationMappingsTags) { - const defaultLang = translationMappingsTag.attributes.getNamedItem('default-lang')!.value; - data.translationMappings = { defaultLang, mappings: {} }; - - const translationMappingTags = translationMappingsTag.getElementsByTagName('tm'); - - for (const tag of translationMappingTags) { - if (verbose >= 2) console.log(`.. translationMapping: ${tag.id}`); - - const localizations: Record = {}; - for (const localization of tag.getElementsByTagName('t')) { - const lang = localization.attributes.getNamedItem('lang')!.value; - localizations[lang] = decodeFromXml(localization.innerHTML); - } - - if (verbose >= 3) console.log(`....`, JSON.stringify(localizations)); - data.translationMappings.mappings[tag.id] = localizations; - } - - if (verbose) { - console.log( - `Converted ${Object.keys(data.translationMappings.mappings).length} translation mappings` - ); - } - } -} - -function convertKeys(document: Document, data: any, verbose: number): void { - // Keys - if (document.getElementsByTagName('keys').length > 0) { - data.keys = Array.from( - document.getElementsByTagName('keys')[0].getElementsByTagName('key') - ).map((key) => key.innerHTML); - if (verbose) console.log(`Converted ${data.keys.length} keys`); - } -} - -function convertAnalytics(document: Document, data: any, verbose: number): void { - // Analytics - const analyticsElements = document.getElementsByTagName('analytics'); - - // Initialize data.analytics if it doesn't exist - if (!data.analytics) { - data.analytics = { - enabled: false, - providers: [] - }; - } - - for (const analyticsElement of analyticsElements) { - const enabledAttribute = analyticsElement.getAttribute('enabled'); - - if (enabledAttribute === 'true') { - data.analytics.enabled = true; - - // Get all analytics-provider elements within the current analytics element - const providerElements = analyticsElement.getElementsByTagName('analytics-provider'); - - // Iterate over provider elements to construct the providers array - for (const providerElement of providerElements) { - // Retrieve the id, name, and type attributes from the provider element - const id = providerElement.getAttribute('id'); - const name = providerElement.getAttribute('name'); - const type = providerElement.getAttribute('type'); - - let parameters: { [key: string]: string } | undefined = undefined; - const parametersTags = providerElement.getElementsByTagName('analytics-parameter'); - if (parametersTags.length > 0) { - parameters = {}; - for (const parameterTag of parametersTags) { - const paramName = parameterTag.getAttribute('name')!; - const paramValue = parameterTag.getAttribute('value')!; - parameters[paramName] = paramValue; - } - } - - // Add the provider to the providers array - if (id && name && type) { - data.analytics.providers.push({ id, name, type, parameters }); - } - } - - break; // Exit loop early if found enabled=true - } - } - - if (verbose) console.log(`Converted ${analyticsElements.length} analytics elements`); -} - -function convertFirebase(document: Document, data: any, verbose: number): void { - // Firebase - const firebaseElements = document.getElementsByTagName('firebase'); - - // Initialize data.firebase if it doesn't exist - if (!data.firebase) { - data.firebase = { - features: {} - }; - } - - // Iterate over firebaseElements and update data.firebase.features - for (const firebaseElement of firebaseElements) { - const featuresElements = firebaseElement.getElementsByTagName('features'); - - for (const featuresElement of featuresElements) { - const eElements = featuresElement.getElementsByTagName('e'); - - for (const eElement of eElements) { - const name = eElement.getAttribute('name'); - const value = eElement.getAttribute('value') === 'true'; // Convert string 'true'/'false' to boolean - - // Update data.firebase.features - if (name) { - data.firebase.features[name] = value; - } - } - } - } - - if (verbose) console.log(`Converted ${firebaseElements.length} firebase elements`); -} - -function convertAudioSources(document: Document, data: any, verbose: number): void { - const audioSources = document.getElementsByTagName('audio-sources')[0] - ?.getElementsByTagName('audio-source'); - - if (audioSources?.length > 0) { - data.audio = { sources: {} }; - - for (const source of audioSources) { - const id = source.getAttribute('id')!; - if (verbose >= 2) console.log(`Converting audioSource: ${id}`); - const type = source.getAttribute('type')!; - const name = source.getElementsByTagName('name')[0].innerHTML; - - data.audio.sources[id] = { type, name }; - - if (type !== 'assets') { - data.audio.sources[id].accessMethods = source - .getElementsByTagName('access-methods')[0] - ?.getAttribute('value')! - .split('|'); - data.audio.sources[id].folder = source.getElementsByTagName('folder')[0]?.innerHTML; - - const address = source.getElementsByTagName('address')[0]?.innerHTML; - if (isValidUrl(address)) { - data.audio.sources[id].address = address; - } - - if (type === 'fcbh') { - data.audio.sources[id].key = source.getElementsByTagName('key')[0].innerHTML; - data.audio.sources[id].damId = source.getElementsByTagName('dam-id')[0].innerHTML; - } - } - if (verbose >= 3) console.log(`....`, JSON.stringify(data.audio.sources[id])); - } - - // Audio files - const audioTags = document.getElementsByTagName('audio-files')[0] - ?.getElementsByTagName('audio'); - if (audioTags?.length > 0) { - data.audio.files = []; - for (const tag of audioTags) { - const fileEntry = tag.getElementsByTagName('filename')[0]; - if (!fileEntry) continue; - - const filename = fileEntry.innerHTML; - const src = fileEntry.getAttribute('src') ?? ''; - data.audio.files.push({ name: filename, src }); - } - } - } - if (verbose) console.log(`Converted ${audioSources?.length} audio sources`); -} - -function convertVideos(document: Document, data: any, verbose: number): void { - const videoTags = document.getElementsByTagName('videos')[0]?.getElementsByTagName('video'); - if (videoTags?.length > 0) { - data.videos = []; - - for (const tag of videoTags) { - const placementTag = tag.getElementsByTagName('placement')[0]; - const placement = placementTag ? { - pos: placementTag.attributes.getNamedItem('pos')!.value, - ref: placementTag.attributes.getNamedItem('ref')!.value.split('|')[1], - collection: placementTag.attributes.getNamedItem('ref')!.value.split('|')[0] - } : undefined; - - const width = tag.getAttribute('width') ? parseInt(tag.getAttribute('width')!) : 0; - const height = tag.getAttribute('height') ? parseInt(tag.getAttribute('height')!) : 0; - - let onlineUrl = tag.getElementsByTagName('online-url')[0]?.innerHTML || ''; - if (onlineUrl) onlineUrl = convertVideoUrl(onlineUrl); - - const filenameTag = tag.getElementsByTagName('filename')[0]; - const filename = filenameTag ? filenameTag.innerHTML : ''; - - data.videos.push({ - id: tag.getAttribute('id')!, - src: tag.getAttribute('src'), - width, - height, - title: tag.getElementsByTagName('title')[0]?.innerHTML, - thumbnail: tag.getElementsByTagName('thumbnail')[0]?.innerHTML, - onlineUrl: decodeFromXml(onlineUrl), - filename, - placement - }); - } - } - data.traits['has-video'] = !!data.videos?.length; -} - -function convertIllustrations(document: Document, data: any): void { - const imagesTags = document.getElementsByTagName('images'); - if (imagesTags?.length > 0) { - data.illustrations = []; - - for (const tag of imagesTags) { - if (tag.getAttribute('type') === 'illustration') { - const illustrationTags = tag.getElementsByTagName('image'); - - for (const image of illustrationTags) { - const filename = image.getElementsByTagName('filename')[0]?.innerHTML || image.innerHTML; - const width = image.getAttribute('width') ? parseInt(image.getAttribute('width')!) : 0; - const height = image.getAttribute('height') ? parseInt(image.getAttribute('height')!) : 0; - - const placementTag = image.getElementsByTagName('placement')[0]; - const placement = placementTag ? { - pos: placementTag.getAttribute('pos')!, - ref: placementTag.getAttribute('ref')?.split('|')[1], - caption: placementTag.getAttribute('caption') || '', - collection: placementTag.getAttribute('ref')?.split('|')[0] - } : undefined; - - data.illustrations.push({ filename, width, height, placement }); - } - } - } - } -} - -function convertLayouts(document: Document, data: any, verbose: number): void { - const layoutRoot = document.getElementsByTagName('layouts')[0]; - data.defaultLayout = layoutRoot?.getAttribute('default'); - - const layouts = layoutRoot?.getElementsByTagName('layout'); - if (layouts?.length > 0) { - data.layouts = []; - - for (const layout of layouts) { - const mode = layout.getAttribute('mode')!; - if (verbose >= 2) console.log(`Converting layout`, mode); - - const enabled = layout.getAttribute('enabled') === 'true'; - - const features: { [key: string]: string } = {}; - const featureElements = layout.getElementsByTagName('features')[0]; - if (featureElements) { - for (const feature of featureElements.getElementsByTagName('e')) { - features[feature.getAttribute('name')!] = feature.getAttribute('value')!; - } - } - - const layoutCollections = Array.from(layout.getElementsByTagName('layout-collection')).map(element => element.getAttribute('id')!) || [data.bookCollections[0]?.id]; - - data.layouts.push({ - mode, - enabled, - layoutCollections, - features - }); - } - } - if (verbose) console.log(`Converted ${layouts?.length} layouts`); -} - -function convertBackgroundImages(data: any) { - // Background images - const backgroundImages = document - .querySelector('images[type=background]') - ?.getElementsByTagName('image'); - if (backgroundImages) { - data.backgroundImages = []; - for (const backgroundImage of backgroundImages) { - const width = backgroundImage.getAttribute('width')!; - const height = backgroundImage.getAttribute('height')!; - const filename = backgroundImage.innerHTML; - data.backgroundImages.push({ width, height, filename }); - } - } -} - -function convertWatermarkImages(data: any) { - // Watermark images - const watermarkImages = document - .querySelector('images[type=watermark]') - ?.getElementsByTagName('image'); - if (watermarkImages) { - data.watermarkImages = []; - for (const watermarkImage of watermarkImages) { - const width = watermarkImage.getAttribute('width')!; - const height = watermarkImage.getAttribute('height')!; - const filename = watermarkImage.innerHTML; - data.watermarkImages.push({ width, height, filename }); - } - } -} -function convertMenuItems(data: any, verbose: number) { - // Menu Items +function parseMenuItems(document: Document, data: any, verbose: number) { const menuItems = document .getElementsByTagName('menu-items')[0] ?.getElementsByTagName('menu-item'); @@ -1564,8 +986,7 @@ function convertMenuItems(data: any, verbose: number) { } } -function convertPlans(data: any, verbose: number) { - // Plans +function parsePlans(document: Document, data: any, verbose: number) { const plansTags = document.getElementsByTagName('plans'); if (plansTags?.length > 0) { const plansTag = plansTags[0];