|
@@ -1,15 +1,159 @@
|
|
const XmlParser = require('../xml/XmlParser');
|
|
const XmlParser = require('../xml/XmlParser');
|
|
|
|
|
|
class Fb2Parser extends XmlParser {
|
|
class Fb2Parser extends XmlParser {
|
|
|
|
+ get xlinkNS() {
|
|
|
|
+ if (!this._xlinkNS) {
|
|
|
|
+ const rootAttrs = this.$self().attrs();
|
|
|
|
+ let ns = 'l';
|
|
|
|
+ for (const [key, value] of rootAttrs) {
|
|
|
|
+ if (value == 'http://www.w3.org/1999/xlink') {
|
|
|
|
+ ns = key.split(':')[1] || ns;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this._xlinkNS = ns;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return this._xlinkNS;
|
|
|
|
+ }
|
|
|
|
+
|
|
bookInfo(fb2Object) {
|
|
bookInfo(fb2Object) {
|
|
|
|
+ const result = {};
|
|
|
|
+
|
|
if (!fb2Object)
|
|
if (!fb2Object)
|
|
fb2Object = this.toObject();
|
|
fb2Object = this.toObject();
|
|
|
|
|
|
- //const result = {};
|
|
|
|
|
|
+ const desc = this.inspector(fb2Object).$('fictionbook/description');
|
|
|
|
+
|
|
|
|
+ if (!desc)
|
|
|
|
+ return result;
|
|
|
|
+
|
|
|
|
+ //title-info
|
|
|
|
+ const titleInfo = desc.$('title-info');
|
|
|
|
+ if (titleInfo) {
|
|
|
|
+ const info = {};
|
|
|
|
+
|
|
|
|
+ info.genre = [];
|
|
|
|
+ for (const g of titleInfo.$$('genre'))
|
|
|
|
+ info.genre.push(g.text());
|
|
|
|
+
|
|
|
|
+ const parseAuthors = (tagName) => {
|
|
|
|
+ const authors = [];
|
|
|
|
+ for (const a of titleInfo.$$(tagName)) {
|
|
|
|
+ let names = [];
|
|
|
|
+ names.push(a.text('last-name'));
|
|
|
|
+ names.push(a.text('first-name'));
|
|
|
|
+ names.push(a.text('middle-name'));
|
|
|
|
+ names = names.filter(n => n);
|
|
|
|
+ if (!names.length)
|
|
|
|
+ names.push(a.text('nickname'));
|
|
|
|
+
|
|
|
|
+ authors.push(names.join(' '));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return authors;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ info.author = parseAuthors('author');
|
|
|
|
+
|
|
|
|
+ info.bookTitle = titleInfo.text('book-title');
|
|
|
|
+
|
|
|
|
+ info.annotation = null;
|
|
|
|
+ info.annotationHtml = null;
|
|
|
|
+ const node = titleInfo.$('annotation') && titleInfo.$('annotation').value;
|
|
|
|
+
|
|
|
|
+ if (node) {
|
|
|
|
+ //annotation как кусок xml
|
|
|
|
+ info.annotation = (new XmlParser()).fromObject(node).toString({noHeader: true});
|
|
|
|
+
|
|
|
|
+ //annotation как html
|
|
|
|
+ info.annotationHtml = this.toHtml(info.annotation);
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ info.keywords = titleInfo.text('keywords');
|
|
|
|
+ info.date = titleInfo.text('date');
|
|
|
|
+ info.coverpage = titleInfo.$('coverpage') && titleInfo.$('coverpage').value;
|
|
|
|
+ info.lang = titleInfo.text('lang');
|
|
|
|
+ info.srcLang = titleInfo.text('src-lang');
|
|
|
|
+
|
|
|
|
+ info.translator = parseAuthors('translator');
|
|
|
|
+
|
|
|
|
+ const seqAttrs = titleInfo.attrs('sequence') || new Map();
|
|
|
|
+ info.sequenceName = seqAttrs.get('name') || null;
|
|
|
|
+ info.sequenceNum = seqAttrs.get('number') || null;
|
|
|
|
+ info.sequenceLang = seqAttrs.get('xml:lang') || null;
|
|
|
|
+
|
|
|
|
+ result.titleInfo = info;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return result;
|
|
}
|
|
}
|
|
|
|
|
|
- bookInfoList(fb2Object) {
|
|
|
|
|
|
+ bookInfoList(fb2Object, options = {}) {
|
|
|
|
+ let {
|
|
|
|
+ correctMapping = false,
|
|
|
|
+ valueToString = false,
|
|
|
|
+ } = options;
|
|
|
|
+
|
|
|
|
+ if (!correctMapping)
|
|
|
|
+ correctMapping = mapping => mapping;
|
|
|
|
+
|
|
|
|
+ if (!valueToString) {
|
|
|
|
+ valueToString = (value, nodePath) => {//eslint-disable-line no-unused-vars
|
|
|
|
+ if (typeof(value) === 'string') {
|
|
|
|
+ return value;
|
|
|
|
+ } else if (Array.isArray(value)) {
|
|
|
|
+ return value.join(', ');
|
|
|
|
+ } else if (typeof(value) === 'object') {
|
|
|
|
+ return JSON.stringify(value);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return value;
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let mapping = [
|
|
|
|
+ {name: 'titleInfo', label: 'Общая информация', value: [
|
|
|
|
+ {name: 'author', label: 'Автор(ы)'},
|
|
|
|
+ {name: 'bookTitle', label: 'Название'},
|
|
|
|
+ {name: 'sequenceName', label: 'Серия'},
|
|
|
|
+ {name: 'sequenceNum', label: 'Номер в серии'},
|
|
|
|
+ {name: 'genre', label: 'Жанр'},
|
|
|
|
+
|
|
|
|
+ {name: 'date', label: 'Дата'},
|
|
|
|
+ {name: 'lang', label: 'Язык книги'},
|
|
|
|
+ {name: 'srcLang', label: 'Язык оригинала'},
|
|
|
|
+ {name: 'translator', label: 'Переводчик(и)'},
|
|
|
|
+ {name: 'keywords', label: 'Ключевые слова'},
|
|
|
|
+ ]},
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ mapping = correctMapping(mapping);
|
|
|
|
+ const bookInfo = this.bookInfo(fb2Object);
|
|
|
|
+
|
|
|
|
+ //заполняем mapping
|
|
|
|
+ let result = [];
|
|
|
|
+ for (const item of mapping) {
|
|
|
|
+ const itemOut = {name: item.name, label: item.label, value: []};
|
|
|
|
+ const info = bookInfo[item.name];
|
|
|
|
+ if (!info)
|
|
|
|
+ continue;
|
|
|
|
+
|
|
|
|
+ for (const subItem of item.value) {
|
|
|
|
+ if (info[subItem.name] !== null)
|
|
|
|
+ itemOut.value.push({
|
|
|
|
+ name: subItem.name,
|
|
|
|
+ label: subItem.label,
|
|
|
|
+ value: valueToString(info[subItem.name])
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (itemOut.value.length)
|
|
|
|
+ result.push(itemOut);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return result;
|
|
}
|
|
}
|
|
|
|
|
|
toHtml(xmlString) {
|
|
toHtml(xmlString) {
|