MOBI6: build TOC from end-of-file TOC

Fixes #16
This commit is contained in:
John Factotum
2023-10-29 19:04:52 +08:00
parent 05329eba6e
commit 4d7d010e29
+53 -24
View File
@@ -643,6 +643,20 @@ export class MOBI extends PDB {
const mbpPagebreakRegex = /<\s*(?:mbp:)?pagebreak[^>]*>/gi const mbpPagebreakRegex = /<\s*(?:mbp:)?pagebreak[^>]*>/gi
const fileposRegex = /<[^<>]+filepos=['"]{0,1}(\d+)[^<>]*>/gi const fileposRegex = /<[^<>]+filepos=['"]{0,1}(\d+)[^<>]*>/gi
const getIndent = el => {
let x = 0
while (el) {
const parent = el.parentElement
if (parent) {
const tag = parent.tagName.toLowerCase()
if (tag === 'p') x += 1.5
else if (tag === 'blockquote') x += 2
}
el = parent
}
return x
}
class MOBI6 { class MOBI6 {
parser = new DOMParser() parser = new DOMParser()
serializer = new XMLSerializer() serializer = new XMLSerializer()
@@ -690,32 +704,47 @@ class MOBI6 {
size: section.end - section.start, size: section.end - section.start,
})) }))
const fileposInNCX = []
try { try {
const ncx = await this.mobi.getNCX()
const map = ({ label, offset, children }) => {
const filepos = offset.toString().padStart(10, '0')
const href = `filepos:${filepos}`
fileposInNCX.push(filepos)
label = unescapeHTML(label)
return { label, href, subitems: children?.map(map) }
}
this.toc = ncx?.map(map)
this.landmarks = await this.getGuide() this.landmarks = await this.getGuide()
const tocHref = this.landmarks
// try to build TOC if there's no NCX .find(({ type }) => type?.includes('toc'))?.href
if (!this.toc) { if (tocHref) {
const tocHref = this.landmarks const { index } = this.resolveHref(tocHref)
.find(({ type }) => type?.includes('toc'))?.href const doc = await this.sections[index].createDocument()
if (tocHref) { let lastItem
const { index } = this.resolveHref(tocHref) let lastLevel = 0
const doc = await this.sections[index].createDocument() let lastIndent = 0
this.toc = Array.from(doc.querySelectorAll('a[filepos]'), const lastLevelOfIndent = new Map()
a => ({ const lastParentOfLevel = new Map()
this.toc = Array.from(doc.querySelectorAll('a[filepos]'))
.reduce((arr, a) => {
const indent = getIndent(a)
const item = {
label: a.innerText?.trim(), label: a.innerText?.trim(),
href: `filepos:${a.getAttribute('filepos')}`, href: `filepos:${a.getAttribute('filepos')}`,
})) }
} const level = indent > lastIndent ? lastLevel + 1
: indent === lastIndent ? lastLevel
: lastLevelOfIndent.get(indent) ?? Math.max(0, lastLevel - 1)
if (level > lastLevel) {
if (lastItem) {
lastItem.subitems ??= []
lastItem.subitems.push(item)
lastParentOfLevel.set(level, lastItem)
}
else arr.push(item)
}
else {
const parent = lastParentOfLevel.get(level)
if (parent) parent.subitems.push(item)
else arr.push(item)
}
lastItem = item
lastLevel = level
lastIndent = indent
lastLevelOfIndent.set(indent, level)
return arr
}, [])
} }
} catch(e) { } catch(e) {
console.warn(e) console.warn(e)
@@ -724,8 +753,8 @@ class MOBI6 {
// get list of all `filepos` references in the book, // get list of all `filepos` references in the book,
// which will be used to insert anchor elements // which will be used to insert anchor elements
// because only then can they be referenced in the DOM // because only then can they be referenced in the DOM
this.#fileposList = [...new Set(fileposInNCX this.#fileposList = [...new Set(
.concat(Array.from(str.matchAll(fileposRegex), m => m[1])))] Array.from(str.matchAll(fileposRegex), m => m[1]))]
.map(filepos => ({ filepos, number: Number(filepos) })) .map(filepos => ({ filepos, number: Number(filepos) }))
.sort((a, b) => a.number - b.number) .sort((a, b) => a.number - b.number)