Този скрипт не може да бъде инсталиран директно. Това е библиотека за други скриптове и може да бъде използвана с мета-директива // @require https://update.greasyfork.org/scripts/468831/1478439/HTML2FB2Lib.js
- // ==UserScript==
- // @name HTML2FB2Lib
- // @name:ru HTML2FB2Lib
- // @namespace 90h.yy.zz
- // @version 0.10.4
- // @author Ox90
- // @description This library is designed to convert HTML to FB2.
- // @description:ru Эта библиотека предназначена для конвертирования HTML в FB2.
- // @license MIT
- // ==/UserScript==
-
- class FB2Parser {
- run(fb2doc, htmlNode, fromNode) {
- this._stop = null;
- this._notes = [];
- const res = this.parse(htmlNode, fromNode);
- this._notes.forEach(note => fb2doc.notes.push(note));
- delete this._notes;
- return res;
- }
-
- parse(htmlNode, fromNode) {
- const that = this;
- function _parse(node, from, fb2el, depth) {
- let n = from || node.firstChild;
- while (n) {
- const nn = that.startNode(n, depth, fb2el);
- if (nn) {
- const f = that.processElement(FB2Element.fromHTML(nn, false), depth);
- if (f) {
- if (fb2el) fb2el.children.push(f);
- _parse(nn, null, f, depth + 1);
- }
- that.endNode(nn, depth);
- }
- if (that._stop) break;
- n = n.nextSibling;
- }
- }
- _parse(htmlNode, fromNode, null, 0);
- return this._stop;
- }
-
- startNode(node, depth, fb2to) {
- return node;
- }
-
- processElement(fb2el, depth) {
- if (fb2el instanceof FB2Note) this._notes.push(fb2el);
- return fb2el;
- }
-
- endNode(node, depth) {
- }
- }
-
- class FB2AnnotationParser extends FB2Parser {
- run(fb2doc, htmlNode, fromNode) {
- this._binaries = [];
- const res = super.run(fb2doc, htmlNode, fromNode);
- fb2doc.annotation = this._annotation;
- if (fb2doc.annotation) {
- fb2doc.annotation.normalize();
- this._binaries.forEach(bin => fb2doc.binaries.push(bin));
- this._binaries = null;
- }
- return res;
- }
-
- parse(htmlNode, fromNode) {
- this._annotation = new FB2Annotation();
- const res = super.parse(htmlNode, fromNode);
- if (!this._annotation.children.length) this._annotation = null;
- return res;
- }
-
- processElement(fb2el, depth) {
- if (fb2el) {
- if (depth === 0) this._annotation.children.push(fb2el);
- if (fb2el instanceof FB2Image) this._binaries.push(fb2el);
- }
- return super.processElement(fb2el, depth);
- }
- }
-
- class FB2ChapterParser extends FB2Parser {
- run(fb2doc, htmlNode, title, fromNode) {
- this._binaries = [];
- const res = this.parse(title, htmlNode, fromNode);
- this._chapter.normalize();
- fb2doc.chapters.push(this._chapter);
- this._binaries.forEach(bin => fb2doc.binaries.push(bin));
- this._binaries = null;
- return res;
- }
-
- parse(title, htmlNode, fromNode) {
- this._chapter = new FB2Chapter(title);
- return super.parse(htmlNode, fromNode);
- }
-
- processElement(fb2el, depth) {
- if (fb2el) {
- if (depth === 0) this._chapter.children.push(fb2el);
- if (fb2el instanceof FB2Image) this._binaries.push(fb2el);
- }
- return super.processElement(fb2el, depth);
- }
- }
-
- class FB2Document {
- constructor() {
- this.notes = [];
- this.binaries = [];
- this.bookAuthors = [];
- this.annotation = null;
- this.genres = [];
- this.keywords = [];
- this.chapters = [];
- this.history = [];
- this.xmldoc = null;
- this._parsers = new Map();
- }
-
- toString() {
- this._ensureXMLDocument();
- const root = this.xmldoc.documentElement;
- this._markNotes();
- this._markBinaries();
- root.appendChild(this._makeDescriptionElement());
- root.appendChild(this._makeBodyElement());
- if (this.notes.length) root.appendChild(this._makeNotesElement());
- this._makeBinaryElements().forEach(el => root.appendChild(el));
- const res = (new XMLSerializer()).serializeToString(this.xmldoc);
- this.xmldoc = null;
- return res;
- }
-
- createElement(name) {
- this._ensureXMLDocument();
- return this.xmldoc.createElementNS(this.xmldoc.documentElement.namespaceURI, name);
- }
-
- createTextNode(value) {
- this._ensureXMLDocument();
- return this.xmldoc.createTextNode(value);
- }
-
- createDocumentFragment() {
- this._ensureXMLDocument();
- return this.xmldoc.createDocumentFragment();
- }
-
- bindParser(parserId, parser) {
- if (!parser && !parserId) {
- this._parsers.clear();
- return;
- }
- this._parsers.set(parserId, parser);
- }
-
- parse(parserId, ...args) {
- const parser = this._parsers.get(parserId);
- if (!parser) throw new Error(`Unknown parser id: ${parserId}`);
- return parser.run(this, ...args);
- }
-
- _ensureXMLDocument() {
- if (!this.xmldoc) {
- this.xmldoc = new DOMParser().parseFromString(
- '<?xml version="1.0" encoding="UTF-8"?><FictionBook xmlns="http://www.gribuser.ru/xml/fictionbook/2.0"/>',
- "application/xml"
- );
- this.xmldoc.documentElement.setAttribute("xmlns:l", "http://www.w3.org/1999/xlink");
- }
- }
-
- _makeDescriptionElement() {
- const desc = this.createElement("description");
- // title-info
- const t_info = this.createElement("title-info");
- desc.appendChild(t_info);
- //--
- const ch_num = t_info.children.length;
- this.genres.forEach(gi => {
- if (gi instanceof FB2Genre) {
- t_info.appendChild(gi.xml(this));
- } else if (typeof(gi) === "string") {
- (new FB2GenreList(gi)).forEach(g => t_info.appendChild(g.xml(this)));
- }
- });
- if (t_info.children.length === ch_num) t_info.appendChild((new FB2Genre("network_literature")).xml(this));
- //--
- (this.bookAuthors.length ? this.bookAuthors : [ new FB2Author("Неизвестный автор") ]).forEach(a => {
- t_info.appendChild(a.xml(this));
- });
- //--
- t_info.appendChild((new FB2Element("book-title", this.bookTitle)).xml(this));
- //--
- if (this.annotation) t_info.appendChild(this.annotation.xml(this));
- //--
- let keywords = null;
- if (Array.isArray(this.keywords) && this.keywords.length) {
- keywords = this.keywords.join(", ");
- } else if (typeof(this.keywords) === "string" && this.keywords.trim()) {
- keywords = this.keywords.trim();
- }
- if (keywords) t_info.appendChild((new FB2Element("keywords", keywords)).xml(this));
- //--
- if (this.bookDate) {
- const el = this.createElement("date");
- el.setAttribute("value", FB2Utils.dateToAtom(this.bookDate));
- el.textContent = this.bookDate.getFullYear();
- t_info.appendChild(el);
- }
- //--
- if (this.coverpage) {
- const el = this.createElement("coverpage");
- (Array.isArray(this.coverpage) ? this.coverpage : [ this.coverpage ]).forEach(img => {
- el.appendChild(img.xml(this));
- });
- t_info.appendChild(el);
- }
- //--
- const lang = this.createElement("lang");
- lang.textContent = "ru";
- t_info.appendChild(lang);
- //--
- if (this.sequence) {
- const el = this.createElement("sequence");
- el.setAttribute("name", this.sequence.name);
- if (this.sequence.number) el.setAttribute("number", this.sequence.number);
- t_info.appendChild(el);
- }
- // document-info
- const d_info = this.createElement("document-info");
- desc.appendChild(d_info);
- //--
- d_info.appendChild((new FB2Author("Ox90")).xml(this));
- //--
- if (this.programName) d_info.appendChild((new FB2Element("program-used", this.programName)).xml(this));
- //--
- d_info.appendChild((() => {
- const f_time = new Date();
- const el = this.createElement("date");
- el.setAttribute("value", FB2Utils.dateToAtom(f_time));
- el.textContent = f_time.toUTCString();
- return el;
- })());
- //--
- if (this.sourceURL) {
- d_info.appendChild((new FB2Element("src-url", this.sourceURL)).xml(this));
- }
- //--
- d_info.appendChild((new FB2Element("id", this._genBookId())).xml(this));
- //--
- d_info.appendChild((new FB2Element("version", "1.0")).xml(this));
- //--
- if (this.history.length) {
- const hs = this.createElement("history");
- d_info.appendChild(hs);
- this.history.forEach(it => hs.appendChild((new FB2Paragraph(it)).xml(this)));
- }
- //--
- return desc;
- }
-
- _makeBodyElement() {
- const body = this.createElement("body");
- if (this.bookTitle || this.bookAuthors.length) {
- const title = this.createElement("title");
- body.appendChild(title);
- if (this.bookAuthors.length) title.appendChild((new FB2Paragraph(this.bookAuthors.join(", "))).xml(this));
- if (this.bookTitle) title.appendChild((new FB2Paragraph(this.bookTitle)).xml(this));
- }
- this.chapters.forEach(ch => body.appendChild(ch.xml(this)));
- return body;
- }
-
- _markNotes() {
- let idx = 0;
- this.notes.forEach(note => {
- if (!note.id) note.id = "note" + (++idx);
- if (!note.title) note.title = idx.toString();
- });
- }
-
- _makeNotesElement() {
- const body = this.createElement("body");
- body.setAttribute("name", "notes");
- const title = this.createElement("title");
- title.appendChild(this.createElement("p")).textContent = "Примечания";
- body.append(title);
- this.notes.forEach(note => body.append(note.xmlSection(this)));
- return body;
- }
-
- _markBinaries() {
- let idx = 0;
- this.binaries.forEach(img => {
- if (!img.id) img.id = "image" + (++idx) + img.suffix();
- });
- }
-
- _makeBinaryElements() {
- return this.binaries.reduce((list, img) => {
- if (img.value) list.push(img.xmlBinary(this));
- return list;
- }, []);
- }
-
- _genBookId() {
- let str = this.sourceURL || this.bookTitle || "";
- let hash = 0;
- const slen = str.length;
- for (let i = 0; i < slen; ++i) {
- const ch = str.charCodeAt(i);
- hash = ((hash << 5) - hash) + ch;
- hash = hash & hash; // Convert to 32bit integer
- }
- return (this.idPrefix || "h2f2l_") + Math.abs(hash).toString() + (hash > 0 ? "1" : "");
- }
- }
-
- class FB2Element {
- constructor(name, value) {
- this.name = name;
- this.value = value !== undefined ? value : null;
- this.children = [];
- }
-
- static fromHTML(node, recursive) {
- let fb2el = null;
- const names = new Map([
- [ "U", "emphasis" ], [ "EM", "emphasis" ], [ "EMPHASIS", "emphasis" ], [ "I", "emphasis" ],
- [ "S", "strikethrough" ], [ "DEL", "strikethrough" ], [ "STRIKE", "strikethrough" ],
- [ "STRONG", "strong" ], [ "B", "strong" ], [ "BLOCKQUOTE", "cite" ],
- [ "SUB", "sub" ], [ "SUP", "sup" ],
- [ "SCRIPT", null ], [ "#comment", null ]
- ]);
- const node_name = node.nodeName;
- if (names.has(node_name)) {
- const name = names.get(node_name);
- if (!name) return null;
- fb2el = new FB2Element(names.get(node_name));
- } else {
- switch (node_name) {
- case "#text":
- return new FB2Text(node.textContent);
- case "SPAN":
- fb2el = new FB2Text();
- break;
- case "P":
- case "LI":
- fb2el = new FB2Paragraph();
- break;
- case "SUBTITLE":
- fb2el = new FB2Subtitle();
- break;
- case "A":
- fb2el = new FB2Link(node.href || node.getAttribute("l:href"));
- break;
- case "OL":
- fb2el = new FB2OrderedList();
- break;
- case "UL":
- fb2el = new FB2UnorderedList();
- break;
- case "BR":
- return new FB2EmptyLine();
- case "HR":
- return new FB2Paragraph("---");
- case "IMG":
- return new FB2Image(node.src);
- default:
- return new FB2UnknownNode(node);
- }
- }
- if (recursive) fb2el.appendContentFromHTML(node);
- return fb2el;
- }
-
- hasValue() {
- return ((this.value !== undefined && this.value !== null) || !!this.children.length);
- }
-
- setContentFromHTML(data, fb2doc, log) {
- this.children = [];
- this.appendContentFromHTML(data, fb2doc, log);
- }
-
- appendContentFromHTML(data, fb2doc, log) {
- for (const node of data.childNodes) {
- let fe = FB2Element.fromHTML(node, true);
- if (fe) this.children.push(fe);
- }
- }
-
- normalize() {
- const _normalize = function(list) {
- let done = true;
- let res_list = list.reduce((accum, cur_el) => {
- accum.push(cur_el);
- const tmp_ch = cur_el.children;
- cur_el.children = [];
- tmp_ch.forEach(el => {
- if (
- (
- (el instanceof FB2Paragraph || el instanceof FB2EmptyLine) &&
- (!(el instanceof FB2Chapter || el instanceof FB2Annotation || el.name === "cite" || el.name === "title"))
- ) || (
- (el.name === "cite") &&
- (!(el instanceof FB2Chapter || el instanceof FB2Annotation))
- ) || (
- (el instanceof FB2Subtitle) &&
- (!(el instanceof FB2Chapter || el.name === "cite"))
- )
- ) {
- // Вытолкнуть элемент вверх, разбив текущий элемент на две части
- accum.push(el);
- const nm = cur_el.name;
- cur_el = new cur_el.constructor();
- if (!cur_el.name) cur_el.name = nm;
- accum.push(cur_el);
- done = false;
- } else {
- let cnt = 0;
- el.normalize().forEach(e => {
- // Убрать избыточную вложенность: <el><el>value</el></el> --> <el>value</el>
- if (!e.value && e.children.length === 1 && e.name === e.children[0].name) {
- e = e.children[0];
- }
- if (e !== el) done = false;
- if (e.hasValue()) cur_el.children.push(e);
- });
- }
- });
- return accum;
- }, []);
- return { list: res_list, done: done };
- }
- //--
- let result = _normalize([ this ]);
- while (!result.done) {
- result = _normalize(result.list);
- }
- return result.list;
- }
-
- xml(doc) {
- const el = doc.createElement(this.name);
- if (this.value !== null) el.textContent = this.value;
- this.children.forEach(ch => el.appendChild(ch.xml(doc)));
- return el;
- }
- }
-
- class FB2BlockElement extends FB2Element {
- normalize() {
- // Предварительная нормализация
- this.children = this.children.reduce((list, ch) => {
- ch.normalize().forEach(cc => list.push(cc));
- return list;
- }, []);
- // Удалить пустоты справа
- while (this.children.length) {
- const el = this.children[this.children.length - 1];
- if (el instanceof FB2Text) el.trimRight();
- if (!el.hasValue()) {
- this.children.pop();
- continue;
- }
- break;
- }
- // Удалить пустоты слева
- while (this.children.length) {
- const el = this.children[0];
- if (el instanceof FB2Text) el.trimLeft();
- if (!el.hasValue()) {
- this.children.shift();
- continue;
- }
- break;
- }
- // Удалить пустоты в содержимом элемента
- if (!this.children.length && typeof(this.value) === "string") {
- this.value = this.value.trim();
- }
- // Окончательная нормализация
- return super.normalize();
- }
- }
-
- /**
- * FB2 элемент верхнего уровня section
- */
- class FB2Chapter extends FB2Element {
- constructor(title) {
- super("section");
- this.title = title;
- }
-
- normalize() {
- // Обернуть все запрещенные на этом уровне элементы в параграфы
- this.children = this.children.reduce((list, el) => {
- if (![ "p", "subtitle", "image", "empty-line", "cite" ].includes(el.name)) {
- const pe = new FB2Paragraph();
- pe.children.push(el);
- el = pe;
- }
- el.normalize().forEach(el => {
- if (el.hasValue()) list.push(el);
- });
- return list;
- }, []);
- return [ this ];
- }
-
- xml(doc) {
- const el = super.xml(doc);
- if (this.title) {
- const t_el = doc.createElement("title");
- const p_el = doc.createElement("p");
- p_el.textContent = this.title;
- t_el.appendChild(p_el);
- el.prepend(t_el);
- }
- return el;
- }
- }
-
- /**
- * FB2 элемент верхнего уровня annotation
- */
- class FB2Annotation extends FB2Element {
- constructor() {
- super("annotation");
- }
-
- normalize() {
- // Обернуть неформатированный текст, разделенный <br> в параграфы
- let lp = null;
- const newParagraph = list => {
- lp = new FB2Paragraph();
- list.push(lp);
- };
- this.children = this.children.reduce((list, el) => {
- if ([ "p", "subtitle", "cite" ].includes(el.name)) {
- list.push(el);
- lp = null;
- } else if (el.name === "empty-line") {
- if (!lp) {
- // Перенос между блоками
- if (list.length) list.push(new FB2EmptyLine);
- } else if (!lp.children.length) {
- // Более одного переноса подряд между inline элементами
- list.pop();
- list.push(new FB2EmptyLine());
- list.push(lp);
- } else {
- // Перенос между inline элементами
- newParagraph(list);
- }
- } else {
- if (!lp) newParagraph(list);
- lp.children.push(el);
- }
- return list;
- }, []);
- // Запустить собственную нормализацию дочерних элементов
- this.children = this.children.reduce((list, el) => {
- el.normalize().forEach(el => {
- if (el.hasValue()) list.push(el);
- });
- return list;
- }, []);
- // Удалить конечные пустые строки
- for (let len = this.children.length; len; ) {
- if (this.children[len - 1].name !== "empty-line") break;
- this.children.pop();
- --len;
- }
- }
- }
-
- class FB2Subtitle extends FB2BlockElement {
- constructor(value) {
- super("subtitle", value);
- }
- }
-
- class FB2Paragraph extends FB2BlockElement {
- constructor(value) {
- super("p", value);
- }
- }
-
- class FB2EmptyLine extends FB2Element {
- constructor() {
- super("empty-line");
- }
-
- hasValue() {
- return true;
- }
- }
-
- class FB2Text extends FB2Element {
- constructor(value) {
- super("text", value);
- }
-
- trimLeft() {
- if (typeof(this.value) === "string") this.value = this.value.trimLeft() || null;
- if (!this.value) {
- while (this.children.length) {
- const first_child = this.children[0];
- if (first_child instanceof FB2Text) first_child.trimLeft();
- if (first_child.hasValue()) break;
- this.children.shift();
- }
- }
- }
-
- trimRight() {
- while (this.children.length) {
- const last_child = this.children[this.children.length - 1];
- if (last_child instanceof FB2Text) last_child.trimRight();
- if (last_child.hasValue()) break;
- this.children.pop();
- }
- if (!this.children.length && typeof(this.value) === "string") {
- this.value = this.value.trimRight() || null;
- }
- }
-
- xml(doc) {
- if (!this.value && this.children.length) {
- let fr = doc.createDocumentFragment();
- for (const ch of this.children) {
- fr.appendChild(ch.xml(doc));
- }
- return fr;
- }
- return doc.createTextNode(this.value);
- }
- }
-
- class FB2Link extends FB2Element {
- constructor(href) {
- super("a");
- this.href = href;
- }
-
- xml(doc) {
- const el = super.xml(doc);
- el.setAttribute("l:href", this.href);
- return el;
- }
- }
-
- class FB2List extends FB2Element {
- constructor() {
- super("list");
- }
-
- xml(doc) {
- const fr = doc.createDocumentFragment();
- for (const ch of this.children) {
- if (ch.hasValue()) {
- let ch_el = null;
- if (ch instanceof FB2BlockElement) {
- ch_el = ch.xml(doc);
- } else {
- const par = new FB2Paragraph();
- par.children.push(ch);
- ch_el = par.xml(doc);
- }
- if (ch_el.textContent.trim() !== "") fr.appendChild(ch_el);
- }
- }
- return fr;
- }
- }
-
- class FB2OrderedList extends FB2List {
- xml(doc) {
- let pos = 0;
- const fr = super.xml(doc);
- for (const el of fr.children) {
- ++pos;
- el.prepend(`${pos}. `);
- }
- return fr;
- }
- }
-
- class FB2UnorderedList extends FB2List {
- xml(doc) {
- const fr = super.xml(doc);
- for (const el of fr.children) {
- el.prepend("- ");
- }
- return fr;
- }
- }
-
- class FB2Author extends FB2Element {
- constructor(s) {
- super("author");
- const a = s.split(" ");
- switch (a.length) {
- case 1:
- this.nickName = s;
- break;
- case 2:
- this.firstName = a[0];
- this.lastName = a[1];
- break;
- default:
- this.firstName = a[0];
- this.middleName = a.slice(1, -1).join(" ");
- this.lastName = a[a.length - 1];
- break;
- }
- this.homePage = null;
- }
-
- hasValue() {
- return (!!this.firstName || !!this.lastName || !!this.middleName);
- }
-
- toString() {
- if (!this.firstName) return this.nickName;
- return [ this.firstName, this.middleName, this.lastName ].reduce((list, name) => {
- if (name) list.push(name);
- return list;
- }, []).join(" ");
- }
-
- xml(doc) {
- let a_el = super.xml(doc);
- [
- [ "first-name", this.firstName ], [ "middle-name", this.middleName ],
- [ "last-name", this.lastName ], [ "nickname", this.nickName ],
- [ "home-page", this.homePage ]
- ].forEach(it => {
- if (it[1]) {
- const e = doc.createElement(it[0]);
- e.textContent = it[1];
- a_el.appendChild(e);
- }
- });
- return a_el;
- }
- }
-
- class FB2Image extends FB2Element {
- constructor(value) {
- super("image");
- if (typeof(value) === "string") {
- this.url = value;
- } else {
- this.value = value;
- }
- }
-
- async load(onprogress) {
- if (this.url) {
- const bin = await this._load(this.url, { responseType: "binary", onprogress: onprogress });
- this.type = bin.type;
- this.size = bin.size;
- if (!this.suffix()) throw new Error("Неизвестный формат изображения");
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.addEventListener("loadend", (event) => resolve(event.target.result));
- reader.readAsDataURL(bin);
- }).then(base64str => {
- this.value = this._getBase64String(base64str);
- }).catch(err => {
- throw new Error("Ошибка загрузки изображения");
- });
- }
- }
-
- hasValue() {
- return true;
- }
-
- xml(doc) {
- if (this.value) {
- const el = doc.createElement(this.name);
- el.setAttribute("l:href", "#" + this.id);
- return el
- }
- const id = this.id || "изображение";
- return doc.createTextNode(`[ ${id} ]`);
- }
-
- xmlBinary(doc) {
- const el = doc.createElement("binary");
- el.setAttribute("id", this.id);
- el.setAttribute("content-type", this.type);
- el.textContent = this.value
- return el;
- }
-
- suffix() {
- switch (this.type) {
- case "image/png":
- return ".png";
- case "image/jpeg":
- return ".jpg";
- case "image/gif":
- return ".gif";
- case "image/webp":
- return ".webp";
- }
- return "";
- }
-
- async convert(targetType) {
- return new Promise((resolve, reject) => {
- const img = new Image();
- img.addEventListener("load", () => {
- const cvs = document.createElement("canvas");
- cvs.width = img.width;
- cvs.height = img.height;
- cvs.getContext("2d", { alpha: false }).drawImage(img, 0, 0);
- this.value = this._getBase64String(cvs.toDataURL(targetType));
- this.type = targetType;
- resolve();
- });
- img.addEventListener("error", () => reject(new Error("Некорректный формат изображения")));
- img.src = `data:${this.type};base64,` + this.value;
- });
- }
-
- async _load(...args) {
- return FB2Loader.addJob(...args);
- }
-
- _getBase64String(data) {
- return data.substr(data.indexOf(",") + 1);
- }
- }
-
- class FB2Note extends FB2Element {
- constructor(value, title) {
- super("note");
- this.value = value;
- this.title = title;
- }
-
- xml(doc) {
- const el = doc.createElement("a");
- el.setAttribute("l:href", "#" + this.id);
- el.setAttribute("type", "note");
- el.textContent = `[${this.title}]`;
- return el;
- }
-
- xmlSection(doc) {
- const sec = new FB2Chapter(this.title);
- sec.children.push(new FB2Paragraph(this.value));
- const el = sec.xml(doc);
- el.setAttribute("id", this.id);
- return el;
- }
- }
-
- class FB2Genre extends FB2Element {
- constructor(value) {
- super("genre", value);
- }
- }
-
- class FB2UnknownNode extends FB2Element {
- constructor(value) {
- super("unknown", value);
- }
-
- xml(doc) {
- return doc.createTextNode(this.value && this.value.textContent || "");
- }
- }
-
- class FB2GenreList extends Array {
- constructor(...args) {
- if (args.length === 1 && typeof(args[0]) === "number") {
- super(args[0]);
- return;
- }
- const list = (args.length === 1) ? (Array.isArray(args[0]) ? args[0] : [ args[0] ]) : args;
- super();
- if (!list.length) return;
- const keys = FB2GenreList._keys;
- const gmap = new Map();
- const addWeight = (name, weight) => gmap.set(name, (gmap.get(name) || 0) + weight);
-
- list.forEach(p_str => {
- p_str = p_str.toLowerCase();
- let words = p_str.split(/[\s,.;]+/);
- if (words.length === 1) words = [];
- for (const it of keys) {
- const exact_names = Array.isArray(it[1]) ? it[1] : [ it[1] ];
- if (it[0] === p_str || exact_names.includes(p_str)) {
- addWeight(it[0], 3); // Exact match
- break;
- }
- // Scan each word
- let weight = words.some(w => exact_names.includes(w)) ? 2 : 0;
- it[2] && it[2].forEach(k => {
- if (words.includes(k)) ++weight;
- });
- if (weight >= 2) addWeight(it[0], weight);
- }
- });
-
- const res = [];
- gmap.forEach((weight, name) => res.push([ name, weight]));
- if (!res.length) return;
- res.sort((a, b) => b[1] > a[1]);
-
- // Add at least five genres with maximum weight
- let cur_w = 0;
- for (const it of res) {
- if (it[1] !== cur_w && this.length >= 5) break;
- cur_w = it[1];
- this.push(new FB2Genre(it[0]));
- }
- }
- }
-
- FB2GenreList._keys = [
- [ "adv_animal", "природа и животные", [ "приключения", "животные", "природа" ] ],
- [ "adventure", "приключения" ],
- [ "adv_geo", "путешествия и география", [ "приключения", "география", "путешествие" ] ],
- [ "adv_history", "исторические приключения", [ "история", "приключения" ] ],
- [ "adv_indian", "вестерн, про индейцев", [ "индейцы", "вестерн" ] ],
- [ "adv_maritime", "морские приключения", [ "приключения", "море" ] ],
- [ "adv_modern", "приключения в современном мире", [ "современный", "мир" ] ],
- [ "adv_story", "авантюрный роман" ],
- [ "antique", "старинное" ],
- [ "antique_ant", "античная литература", [ "старинное", "античность" ] ],
- [ "antique_east", "древневосточная литература", [ "старинное", "восток" ] ],
- [ "antique_european", "европейская старинная литература", [ "старинное", "европа" ] ],
- [ "antique_myths", "мифы. легенды. эпос", [ "мифы", "легенды", "эпос", "фольклор" ] ],
- [ "antique_russian", "древнерусская литература", [ "древнерусское", "старинное" ] ],
- [ "aphorism_quote", "афоризмы, цитаты", [ "афоризмы", "цитаты", "проза" ] ],
- [ "architecture_book", "скульптура и архитектура", [ "дизайн" ] ],
- [ "art_criticism", "искусствоведение" ],
- [ "art_world_culture", "мировая художественная культура", [ "искусство", "искусствоведение" ] ],
- [ "astrology", "астрология и хиромантия", [ "астрология", "хиромантия" ] ],
- [ "auto_business", "автодело" ],
- [ "auto_regulations", "автомобили и ПДД", [ "дорожного", "движения", "дорожное", "движение" ] ],
- [ "banking", "финансы", [ "банки", "деньги" ] ],
- [ "child_adv", "приключения для детей и подростков" ],
- [ "child_classical", "классическая детская литература" ],
- [ "child_det", "детская остросюжетная литература" ],
- [ "child_education", "детская образовательная литература" ],
- [ "child_folklore", "детский фольклор" ],
- [ "child_prose", "проза для детей" ],
- [ "children", "детская литература", [ "детское" ] ],
- [ "child_sf", "фантастика для детей" ],
- [ "child_tale", "сказки народов мира" ],
- [ "child_tale_rus", "русские сказки" ],
- [ "child_verse", "стихи для детей" ],
- [ "cine", "кино" ],
- [ "comedy", "комедия" ],
- [ "comics", "комиксы" ],
- [ "comp_db", "программирование, программы, базы данных", [ "программирование", "базы", "программы" ] ],
- [ "comp_hard", "компьютерное железо", [ "аппаратное" ] ],
- [ "comp_soft", "программное обеспечение" ],
- [ "computers", "компьютеры" ],
- [ "comp_www", "ос и сети, интернет", [ "ос", "сети", "интернет" ] ],
- [ "design", "дизайн" ],
- [ "det_action", [ "боевики", "боевик" ], [ "триллер" ] ],
- [ "det_classic", "классический детектив" ],
- [ "det_crime", "криминальный детектив", [ "криминал" ] ],
- [ "det_espionage", "шпионский детектив", [ "шпион", "шпионы", "детектив" ] ],
- [ "det_hard", "крутой детектив" ],
- [ "det_history", "исторический детектив", [ "история" ] ],
- [ "det_irony", "иронический детектив" ],
- [ "det_maniac", "про маньяков", [ "маньяки", "детектив" ] ],
- [ "det_police", "полицейский детектив", [ "полиция", "детектив" ] ],
- [ "det_political", "политический детектив", [ "политика", "детектив" ] ],
- [ "det_su", "советский детектив", [ "ссср", "детектив" ] ],
- [ "detective", "детектив", [ "детективы" ] ],
- [ "drama", "драма" ],
- [ "drama_antique", "античная драма" ],
- [ "dramaturgy", "драматургия" ],
- [ "economics", "экономика" ],
- [ "economics_ref", "деловая литература" ],
- [ "epic", "былины, эпопея", [ "былины", "эпопея" ] ],
- [ "epistolary_fiction", "эпистолярная проза" ],
- [ "equ_history", "история техники" ],
- [ "fairy_fantasy", "мифологическое фэнтези", [ "мифология", "фантастика" ] ],
- [ "family", "семейные отношения", [ "дом", "семья" ] ],
- [ "fanfiction", "фанфик" ],
- [ "folklore", "фольклор, загадки" ],
- [ "folk_songs", "народные песни" ],
- [ "folk_tale", "народные сказки" ],
- [ "foreign_antique", "средневековая классическая проза" ],
- [ "foreign_children", "зарубежная литература для детей" ],
- [ "foreign_prose", "зарубежная классическая проза" ],
- [ "geo_guides", "путеводители, карты, атласы", [ "география", "атласы", "карты", "путеводители" ] ],
- [ "gothic_novel", "готический роман" ],
- [ "great_story", "роман", [ "повесть" ] ],
- [ "home", "домоводство", [ "дом", "семья" ] ],
- [ "home_collecting", "коллекционирование" ],
- [ "home_cooking", "кулинария", [ "домашняя", "еда" ] ],
- [ "home_crafts", "хобби и ремесла" ],
- [ "home_diy", "сделай сам" ],
- [ "home_entertain", "развлечения" ],
- [ "home_garden", "сад и огород" ],
- [ "home_health", "здоровье" ],
- [ "home_pets", "домашние животные" ],
- [ "home_sex", "семейные отношения, секс" ],
- [ "home_sport", "боевые исскусства, спорт" ],
- [ "hronoopera", "хроноопера" ],
- [ "humor", "юмор" ],
- [ "humor_anecdote", "анекдоты" ],
- [ "humor_prose", "юмористическая проза" ],
- [ "humor_satire", "сатира" ],
- [ "humor_verse", "юмористические стихи, басни", [ "юмор", "стихи", "басни" ] ],
- [ "limerick", [ "частушки", "прибаутки", "потешки" ] ],
- [ "literature_18", "классическая проза XVII-XVIII веков" ],
- [ "literature_19", "классическая проза ХIX века" ],
- [ "literature_20", "классическая проза ХX века" ],
- [ "love", "любовные романы" ],
- [ "love_contemporary", "современные любовные романы" ],
- [ "love_detective", "остросюжетные любовные романы", [ "детектив", "любовь" ] ],
- [ "love_erotica", "эротика", [ "эротическая", "литература" ] ],
- [ "love_hard", "порно" ],
- [ "love_history", "исторические любовные романы", [ "история", "любовь" ] ],
- [ "love_sf", "любовное фэнтези" ],
- [ "love_short", "короткие любовные романы" ],
- [ "lyrics", "лирика" ],
- [ "military_history", "военная история", [ "война", "история" ] ],
- [ "military_special", "военное дело" ],
- [ "military_weapon", "военная техника и вооружение", [ "военная", "вооружение", "техника" ] ],
- [ "modern_tale", "современная сказка" ],
- [ "music", "музыка" ],
- [ "network_literature", "сетевая литература" ],
- [ "nonf_biography", "биографии и мемуары", [ "биография", "биографии", "мемуары" ] ],
- [ "nonf_criticism", "критика" ],
- [ "nonfiction", "документальная литература" ],
- [ "nonf_military", "военная документалистика и аналитика" ],
- [ "nonf_publicism", "публицистика" ],
- [ "notes:", "партитуры" ],
- [ "org_behavior", "маркентиг, pr", [ "организации" ] ],
- [ "painting", "живопись", [ "альбомы", "иллюстрированные", "каталоги" ] ],
- [ "palindromes", "визуальная и экспериментальная поэзия", [ "верлибры", "палиндромы", "поэзия" ] ],
- [ "periodic", "журналы, газеты", [ "журналы", "газеты" ]],
- [ "poem", "поэма", [ "эпическая", "поэзия" ] ],
- [ "poetry", "поэзия" ],
- [ "poetry_classical", "классическая поэзия" ],
- [ "poetry_east", "поэзия востока" ],
- [ "poetry_for_classical", "классическая зарубежная поэзия" ],
- [ "poetry_for_modern", "современная зарубежная поэзия" ],
- [ "poetry_modern", "современная поэзия" ],
- [ "poetry_rus_classical", "классическая русская поэзия" ],
- [ "poetry_rus_modern", "современная русская поэзия", [ "русская", "поэзия" ] ],
- [ "popadanec", "попаданцы", [ "попаданец" ] ],
- [ "popular_business", "карьера, кадры", [ "карьера", "дело", "бизнес" ] ],
- [ "prose", "проза" ],
- [ "prose_abs", "фантасмагория, абсурдистская проза" ],
- [ "prose_classic", "классическая проза" ],
- [ "prose_contemporary", "современная русская и зарубежная проза", [ "современная", "проза" ] ],
- [ "prose_counter", "контркультура" ],
- [ "prose_game", "игры, упражнения для детей", [ "игры", "упражнения" ] ],
- [ "prose_history", "историческая проза", [ "история", "проза" ] ],
- [ "prose_magic", "магический реализм", [ "магия", "проза" ] ],
- [ "prose_military", "проза о войне" ],
- [ "prose_neformatny", "неформатная проза", [ "экспериментальная", "проза" ] ],
- [ "prose_rus_classic", "русская классическая проза" ],
- [ "prose_su_classics", "советская классическая проза" ],
- [ "proverbs", "пословицы", [ "поговорки" ] ],
- [ "ref_dict", "словари", [ "справочник" ] ],
- [ "ref_encyc", "энциклопедии", [ "энциклопедия" ] ],
- [ "ref_guide", "руководства", [ "руководство", "справочник" ] ],
- [ "ref_ref", "справочники", [ "справочник" ] ],
- [ "reference", "справочная литература" ],
- [ "religion", "религия", [ "духовность", "эзотерика" ] ],
- [ "religion_budda", "буддизм" ],
- [ "religion_catholicism", "католицизм" ],
- [ "religion_christianity", "христианство" ],
- [ "religion_esoterics", "эзотерическая литература", [ "эзотерика" ] ],
- [ "religion_hinduism", "индуизм" ],
- [ "religion_islam", "ислам" ],
- [ "religion_judaism", "иудаизм" ],
- [ "religion_orthdoxy", "православие" ],
- [ "religion_paganism", "язычество" ],
- [ "religion_protestantism", "протестантизм" ],
- [ "religion_self", "самосовершенствование" ],
- [ "russian_fantasy", "славянское фэнтези", [ "русское", "фэнтези" ] ],
- [ "sci_biology", "биология", [ "биофизика", "биохимия" ] ],
- [ "sci_botany", "ботаника" ],
- [ "sci_build", "строительство и сопромат", [ "строительтво", "сопромат" ] ],
- [ "sci_chem", "химия" ],
- [ "sci_cosmos", "астрономия и космос", [ "астрономия", "космос" ] ],
- [ "sci_culture", "культурология" ],
- [ "sci_ecology", "экология" ],
- [ "sci_economy", "экономика" ],
- [ "science", "научная литература" ],
- [ "sci_geo", "геология и география" ],
- [ "sci_history", "история" ],
- [ "sci_juris", "юриспруденция" ],
- [ "sci_linguistic", "языкознание", [ "иностранный", "язык" ] ],
- [ "sci_math", "математика" ],
- [ "sci_medicine_alternative", "альтернативная медицина" ],
- [ "sci_medicine", "медицина" ],
- [ "sci_metal", "металлургия" ],
- [ "sci_oriental", "востоковедение" ],
- [ "sci_pedagogy", "педагогика, воспитание детей, литература для родителей", [ "воспитание", "детей" ] ],
- [ "sci_philology", "литературоведение" ],
- [ "sci_philosophy", "философия" ],
- [ "sci_phys", "физика" ],
- [ "sci_politics", "политика" ],
- [ "sci_popular", "зарубежная образовательная литература", [ "зарубежная", "научно-популярная" ] ],
- [ "sci_psychology", "психология и психотерапия" ],
- [ "sci_radio", "радиоэлектроника" ],
- [ "sci_religion", "религиоведение", [ "религия", "духовность" ] ],
- [ "sci_social_studies", "обществознание", [ "социология" ] ],
- [ "sci_state", "государство и право" ],
- [ "sci_tech", "технические науки", [ "техника", "наука" ] ],
- [ "sci_textbook", "учебники и пособия" ],
- [ "sci_theories", "альтернативные науки и научные теории" ],
- [ "sci_transport", "транспорт и авиация" ],
- [ "sci_veterinary", "ветеринария" ],
- [ "sci_zoo", "зоология" ],
- [ "science", "научная литература", [ "образование" ] ],
- [ "screenplays", "сценарии", [ "сценарий" ] ],
- [ "sf", "научная фантастика", [ "наука", "фантастика" ] ],
- [ "sf_action", "боевая фантастика" ],
- [ "sf_cyberpunk", "киберпанк" ],
- [ "sf_detective", "детективная фантастика", [ "детектив", "фантастика" ] ],
- [ "sf_epic", "эпическая фантастика", [ "эпическое", "фэнтези" ] ],
- [ "sf_etc", "фантастика" ],
- [ "sf_fantasy", "фэнтези" ],
- [ "sf_fantasy_city", "городское фэнтези" ],
- [ "sf_heroic", "героическая фантастика", [ "героическое", "герой", "фэнтези" ] ],
- [ "sf_history", "альтернативная история", [ "историческое", "фэнтези" ] ],
- [ "sf_horror", "ужасы", [ "фантастика" ] ],
- [ "sf_humor", "юмористическая фантастика", [ "юмор", "фантастика" ] ],
- [ "sf_litrpg", "литрпг", [ "litrpg", "рпг" ] ],
- [ "sf_mystic", "мистика", [ "мистическая", "фантастика" ] ],
- [ "sf_postapocalyptic", "постапокалипсис" ],
- [ "sf_realrpg", "реалрпг", [ "realrpg" ] ],
- [ "sf_social", "Социально-психологическая фантастика", [ "социум", "психология", "фантастика" ] ],
- [ "sf_space", "космическая фантастика", [ "космос", "фантастика" ] ],
- [ "sf_stimpank", "стимпанк" ],
- [ "sf_technofantasy", "технофэнтези" ],
- [ "song_poetry", "песенная поэзия" ],
- [ "story", "рассказ", [ "рассказы", "эссе", "новеллы", "новелла", "феерия", "сборник", "рассказов" ] ],
- [ "tale_chivalry", "рыцарский роман", [ "рыцари", "приключения" ] ],
- [ "tbg_computers", "учебные пособия, самоучители", [ "пособия", "самоучители" ] ],
- [ "tbg_higher", "учебники и пособия ВУЗов", [ "учебники", "пособия" ] ],
- [ "tbg_school", "школьные учебники и пособия, рефераты, шпаргалки", [ "школьные", "учебники", "шпаргалки", "рефераты" ] ],
- [ "tbg_secondary", "учебники и пособия для среднего и специального образования", [ "учебники", "пособия", "образование" ] ],
- [ "theatre", "театр" ],
- [ "thriller", "триллер", [ "триллеры", "детектив", "детективы" ] ],
- [ "tragedy", "трагедия", [ "драматургия" ] ],
- [ "travel_notes", " география, путевые заметки", [ "география", "заметки" ] ],
- [ "vaudeville", "мистерия", [ "буффонада", "водевиль" ] ],
- ];
-
- class FB2Loader {
- static async addJob(url, params) {
- params ||= {};
- const fp = {};
- fp.method = params.method || "GET";
- fp.credentials = "same-origin";
- fp.signal = this._getSignal();
- const resp = await fetch(url, fp);
- if (!resp.ok) throw new Error(`Сервер вернул ошибку (${resp.status})`);
- const reader = resp.body.getReader();
- const type = resp.headers.get("Content-Type");
- const total = +resp.headers.get("Content-Length");
- let loaded = 0;
- const chunks = [];
- const onprogress = (total && typeof(params.onprogress) === "function") ? params.onprogress : null;
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
- chunks.push(value);
- loaded += value.length;
- if (onprogress) onprogress(loaded, total);
- }
- let result = null;
- switch (params.responseType) {
- case "binary":
- result = new Blob(chunks, { type: type });
- break;
- default:
- {
- let pos = 0;
- const data = new Uint8Array(loaded);
- for (let ch of chunks) {
- data.set(ch, pos);
- pos += ch.length;
- }
- result = (new TextDecoder("utf-8")).decode(data);
- }
- break;
- }
- return params.extended ? { headers: resp.headers, response: result } : result;
- }
-
- static abortAll() {
- if (this._controller) {
- this._controller.abort();
- this._controller = null;
- }
- }
-
- static _getSignal() {
- let controller = this._controller;
- if (!controller) this._controller = controller = new AbortController();
- return controller.signal;
- }
- }
-
- class FB2Utils {
- static dateToAtom(date) {
- const m = date.getMonth() + 1;
- const d = date.getDate();
- return "" + date.getFullYear() + '-' + (m < 10 ? "0" : "") + m + "-" + (d < 10 ? "0" : "") + d;
- }
- }