-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
420 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { XHTMLBuilder, XHTMLNode } from './xhtml'; | ||
|
||
interface NavRoot { | ||
heading?: 1 | 2 | 3 | 4 | 5 | 6; | ||
|
||
title?: string; | ||
|
||
list: NavItem[]; | ||
} | ||
|
||
interface SubNavItem { | ||
href?: string; | ||
|
||
text: string; | ||
} | ||
|
||
interface NavItem extends SubNavItem { | ||
list?: SubNavItem[]; | ||
} | ||
|
||
export function buildTocNav(nav: NavRoot) { | ||
const builder = new XHTMLBuilder(); | ||
|
||
const root = { | ||
tag: 'nav', | ||
attrs: { | ||
'epub:type': 'toc' | ||
}, | ||
children: [] as XHTMLNode[] | ||
} satisfies XHTMLNode; | ||
|
||
if (nav.title && nav.heading) { | ||
root.children.push({ | ||
tag: 'h' + nav.heading, | ||
attrs: {}, | ||
children: nav.title | ||
}); | ||
} | ||
root.children.push({ | ||
tag: 'ol', | ||
attrs: {}, | ||
children: list(nav.list) | ||
}); | ||
|
||
return builder | ||
.title(nav.title ?? 'Toc') | ||
.body(root) | ||
.build(); | ||
|
||
function list(items: Array<SubNavItem | NavItem>): XHTMLNode[] { | ||
return items.map((i) => ({ | ||
tag: 'li', | ||
attrs: {}, | ||
children: [ | ||
i.href | ||
? { tag: 'a', attrs: { href: i.href }, children: i.text } | ||
: { tag: 'span', attrs: {}, children: i.text }, | ||
'list' in i && | ||
i.list && { | ||
tag: 'ol', | ||
attrs: {}, | ||
children: list(i.list) | ||
} | ||
].filter(Boolean) as XHTMLNode[] | ||
})); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { XMLBuilder } from 'fast-xml-parser'; | ||
|
||
import { TextCSS } from '../constant'; | ||
|
||
const builder = new XMLBuilder({ | ||
format: true, | ||
ignoreAttributes: false, | ||
suppressUnpairedNode: false, | ||
unpairedTags: ['link'] | ||
}); | ||
|
||
export interface XHTMLNode { | ||
tag: string; | ||
attrs: Record<string, string>; | ||
children?: string | Array<XHTMLNode>; | ||
} | ||
|
||
export class XHTMLBuilder { | ||
private info = { | ||
language: 'en', | ||
title: '' | ||
}; | ||
|
||
private _head: XHTMLNode[] = []; | ||
|
||
private _body: XHTMLNode[] = []; | ||
|
||
constructor() {} | ||
|
||
language(value: string) { | ||
this.info.language = value; | ||
return this; | ||
} | ||
|
||
title(value: string) { | ||
this.info.title = value; | ||
return this; | ||
} | ||
|
||
style(href: string) { | ||
this._head.push({ | ||
tag: 'link', | ||
attrs: { | ||
href, | ||
rel: 'stylesheet', | ||
type: TextCSS | ||
}, | ||
children: '' | ||
}); | ||
return this; | ||
} | ||
|
||
body(node: XHTMLNode) { | ||
this._body.push(node); | ||
return this; | ||
} | ||
|
||
public build(): string { | ||
function build(node: XHTMLNode) { | ||
const attrs = Object.fromEntries( | ||
Object.entries(node.attrs).map(([key, value]) => ['@_' + key, value]) | ||
); | ||
|
||
const obj: any = { | ||
...attrs | ||
}; | ||
if (typeof node.children === 'string') { | ||
obj['#text'] = node.children; | ||
} else if (Array.isArray(node.children)) { | ||
Object.assign(obj, list(node.children)); | ||
} | ||
|
||
return obj; | ||
} | ||
|
||
function list(nodes: XHTMLNode[]) { | ||
const obj: any = {}; | ||
for (const c of nodes) { | ||
if (c.tag in obj) { | ||
obj[c.tag].push(build(c)); | ||
} else { | ||
obj[c.tag] = [build(c)]; | ||
} | ||
} | ||
return obj; | ||
} | ||
|
||
return builder.build({ | ||
html: { | ||
'@_xmlns': 'http://www.w3.org/1999/xhtml', | ||
'@_xmlns:epub': 'http://www.w3.org/1999/xhtml', | ||
'@_xml:lang': this.info.language, | ||
head: { | ||
title: this.info.title, | ||
...list(this._head) | ||
}, | ||
body: list(this._body) | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||
|
||
exports[`Bundle Epub > generate toc 1`] = ` | ||
PackageDocument { | ||
"SpecVersion": "3.0", | ||
"_identifier": "12345", | ||
"_items": [ | ||
Html { | ||
"_properties": undefined, | ||
"content": "1", | ||
"file": "page1.xhtml", | ||
"mediaType": "application/xhtml+xml", | ||
}, | ||
Html { | ||
"_properties": undefined, | ||
"content": "2", | ||
"file": "page2.xhtml", | ||
"mediaType": "application/xhtml+xml", | ||
}, | ||
Html { | ||
"_properties": undefined, | ||
"content": "3", | ||
"file": "page3.xhtml", | ||
"mediaType": "application/xhtml+xml", | ||
}, | ||
Html { | ||
"_properties": undefined, | ||
"content": "4", | ||
"file": "page4.xhtml", | ||
"mediaType": "application/xhtml+xml", | ||
}, | ||
Html { | ||
"_properties": undefined, | ||
"content": "5", | ||
"file": "page5.xhtml", | ||
"mediaType": "application/xhtml+xml", | ||
}, | ||
], | ||
"_metadata": { | ||
"contributor": [], | ||
"coverage": "", | ||
"creator": "XLor", | ||
"date": 2023-02-01T11:00:00.000Z, | ||
"description": "for test usage", | ||
"format": "", | ||
"language": "zh-CN", | ||
"lastModified": 2023-02-26T11:00:00.000Z, | ||
"publisher": "", | ||
"relation": "", | ||
"rights": "", | ||
"source": "imagine", | ||
"subject": "", | ||
"title": "Test Book", | ||
"type": "", | ||
}, | ||
"_spine": [], | ||
"_toc": Html { | ||
"_properties": "nav", | ||
"content": "<html xmlns=\\"http://www.w3.org/1999/xhtml\\" xmlns:epub=\\"http://www.w3.org/1999/xhtml\\" xml:lang=\\"en\\"> | ||
<head> | ||
<title>Toc</title> | ||
</head> | ||
<body> | ||
<nav epub:type=\\"toc\\"> | ||
<h2>Toc</h2> | ||
<ol> | ||
<li> | ||
<a href=\\"page1.xhtml\\">1</a> | ||
</li> | ||
<li> | ||
<a href=\\"page2.xhtml\\">2</a> | ||
</li> | ||
<li> | ||
<span>Sub</span> | ||
<ol> | ||
<li> | ||
<a href=\\"page3.xhtml\\">3</a> | ||
</li> | ||
<li> | ||
<a href=\\"page4.xhtml\\">4</a> | ||
</li> | ||
</ol> | ||
</li> | ||
<li> | ||
<a href=\\"page5.xhtml\\">5</a> | ||
</li> | ||
</ol> | ||
</nav> | ||
</body> | ||
</html> | ||
", | ||
"file": "nav.xhtml", | ||
"mediaType": "application/xhtml+xml", | ||
}, | ||
"_uniqueIdentifier": "book-id", | ||
"file": "OEBPS/content.opf", | ||
} | ||
`; |
Oops, something went wrong.