- [ . . . ]
+
diff --git a/client/components/Search/Search.vue b/client/components/Search/Search.vue
index a07514d..3ac28b4 100644
--- a/client/components/Search/Search.vue
+++ b/client/components/Search/Search.vue
@@ -204,7 +204,7 @@
-
@@ -232,7 +232,7 @@
-
+
diff --git a/client/share/utils.js b/client/share/utils.js
index 573cb6f..3607954 100644
--- a/client/share/utils.js
+++ b/client/share/utils.js
@@ -1,4 +1,4 @@
-import moment from 'moment';
+import dayjs from 'dayjs';
import {Buffer} from 'safe-buffer';
//import _ from 'lodash';
@@ -121,11 +121,11 @@ export function isDigit(c) {
}
export function dateFormat(date, format = 'DD.MM.YYYY') {
- return moment(date).format(format);
+ return dayjs(date).format(format);
}
export function sqlDateFormat(date, format = 'DD.MM.YYYY') {
- return moment(date, 'YYYY-MM-DD').format(format);
+ return dayjs(date, 'YYYY-MM-DD').format(format);
}
export function isManualDate(date) {
diff --git a/package-lock.json b/package-lock.json
index d61ac87..19fa89d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,18 +1,19 @@
{
"name": "inpx-web",
- "version": "1.2.0",
+ "version": "1.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "inpx-web",
- "version": "1.2.0",
+ "version": "1.2.1",
"hasInstallScript": true,
"license": "CC0-1.0",
"dependencies": {
"@quasar/extras": "^1.15.0",
"axios": "^0.27.2",
"chardet": "^1.5.0",
+ "dayjs": "^1.11.6",
"express": "^4.18.1",
"fs-extra": "^10.1.0",
"iconv-lite": "^0.6.3",
@@ -20,7 +21,6 @@
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"minimist": "^1.2.6",
- "moment": "^2.29.4",
"node-stream-zip": "^1.15.0",
"quasar": "^2.7.5",
"safe-buffer": "^5.2.1",
@@ -3433,6 +3433,11 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
},
+ "node_modules/dayjs": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.6.tgz",
+ "integrity": "sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ=="
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -5519,14 +5524,6 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"dev": true
},
- "node_modules/moment": {
- "version": "2.29.4",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
- "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
- "engines": {
- "node": "*"
- }
- },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -11324,6 +11321,11 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
},
+ "dayjs": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.6.tgz",
+ "integrity": "sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ=="
+ },
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -12879,11 +12881,6 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"dev": true
},
- "moment": {
- "version": "2.29.4",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
- "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
- },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
diff --git a/package.json b/package.json
index 5015b26..885c135 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "inpx-web",
- "version": "1.2.0",
+ "version": "1.2.1",
"author": "Book Pauk
",
"license": "CC0-1.0",
"repository": "bookpauk/inpx-web",
@@ -52,6 +52,7 @@
"@quasar/extras": "^1.15.0",
"axios": "^0.27.2",
"chardet": "^1.5.0",
+ "dayjs": "^1.11.6",
"express": "^4.18.1",
"fs-extra": "^10.1.0",
"iconv-lite": "^0.6.3",
@@ -59,7 +60,6 @@
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"minimist": "^1.2.6",
- "moment": "^2.29.4",
"node-stream-zip": "^1.15.0",
"quasar": "^2.7.5",
"safe-buffer": "^5.2.1",
diff --git a/server/core/WebWorker.js b/server/core/WebWorker.js
index c862b0d..b1d715c 100644
--- a/server/core/WebWorker.js
+++ b/server/core/WebWorker.js
@@ -369,7 +369,7 @@ class WebWorker {
const link = `${this.config.filesPathStatic}/${hash}`;
const bookFile = `${this.config.filesDir}/${hash}`;
- const bookFileDesc = `${bookFile}.json`;
+ const bookFileDesc = `${bookFile}.d.json`;
if (!await fs.pathExists(bookFile) || !await fs.pathExists(bookFileDesc)) {
if (!await fs.pathExists(bookFile) && extractedFile) {
@@ -435,7 +435,7 @@ class WebWorker {
if (rows.length) {//хеш найден по bookPath
const hash = rows[0].hash;
const bookFile = `${this.config.filesDir}/${hash}`;
- const bookFileDesc = `${bookFile}.json`;
+ const bookFileDesc = `${bookFile}.d.json`;
if (await fs.pathExists(bookFile) && await fs.pathExists(bookFileDesc)) {
link = `${this.config.filesPathStatic}/${hash}`;
@@ -467,9 +467,9 @@ class WebWorker {
let bookInfo = await this.getBookLink(bookId);
const hash = path.basename(bookInfo.link);
const bookFile = `${this.config.filesDir}/${hash}`;
- const bookFileInfo = `${bookFile}.info`;
+ const bookFileInfo = `${bookFile}.i.json`;
- const restoreBookInfo = async() => {
+ const restoreBookInfo = async(info) => {
const result = {};
const rows = await db.select({table: 'book', where: `@@id(${db.esc(bookId)})`});
@@ -478,10 +478,12 @@ class WebWorker {
result.book = book;
result.cover = '';
result.fb2 = false;
+ let parser = null;
if (book.ext == 'fb2') {
const {fb2, cover, coverExt} = await this.fb2Helper.getDescAndCover(bookFile);
- result.fb2 = fb2;
+ parser = fb2;
+ result.fb2 = fb2.rawNodes;
if (cover) {
result.cover = `${this.config.filesPathStatic}/${hash}${coverExt}`;
@@ -489,12 +491,16 @@ class WebWorker {
}
}
- return result;
+ Object.assign(info ,result);
+ await fs.writeFile(bookFileInfo, JSON.stringify(info));
+
+ if (this.config.branch === 'development') {
+ await fs.writeFile(`${bookFile}.dev`, `${JSON.stringify(info, null, 2)}\n\n${parser ? parser.toString({format: true}) : ''}`);
+ }
};
if (!await fs.pathExists(bookFileInfo)) {
- Object.assign(bookInfo, await restoreBookInfo());
- await fs.writeFile(bookFileInfo, JSON.stringify(bookInfo, null, 2));
+ await restoreBookInfo(bookInfo);
} else {
await utils.touchFile(bookFileInfo);
const info = await fs.readFile(bookFileInfo, 'utf-8');
@@ -506,8 +512,7 @@ class WebWorker {
coverFile = `${this.config.publicFilesDir}${tmpInfo.cover}`;
if (coverFile && !await fs.pathExists(coverFile)) {
- Object.assign(bookInfo, await restoreBookInfo());
- await fs.writeFile(bookFileInfo, JSON.stringify(bookInfo, null, 2));
+ await restoreBookInfo(bookInfo);
} else {
bookInfo = tmpInfo;
}
diff --git a/server/core/fb2/Fb2Helper.js b/server/core/fb2/Fb2Helper.js
index e70e30d..5a3383b 100644
--- a/server/core/fb2/Fb2Helper.js
+++ b/server/core/fb2/Fb2Helper.js
@@ -63,12 +63,11 @@ class Fb2Helper {
pickNode: route => route.indexOf('fictionbook/body') !== 0,
});
- const desc = parser.$$('description').toObject();
- const coverImage = parser.inspector(desc).$('description/title-info/coverpage/image');
+ const coverImage = parser.$$('/description/title-info/coverpage/image');
let cover = null;
let coverExt = '';
- if (coverImage) {
+ if (coverImage.count) {
const coverAttrs = coverImage.attrs();
const href = coverAttrs[`${parser.xlinkNS}:href`];
let coverType = coverAttrs['content-type'];
@@ -79,24 +78,21 @@ class Fb2Helper {
const binaryId = (href[0] == '#' ? href.substring(1) : href);
//найдем нужный image
- parser.$$('binary').eachSelf(node => {
+ for (const node of parser.$$array('/binary')) {
let attrs = node.attrs();
if (!attrs)
return;
- attrs = Object.fromEntries(attrs);
if (attrs.id === binaryId) {
- const textNode = new Fb2Parser(node.value);
- const base64 = textNode.$self('*TEXT').value;
-
+ const base64 = node.text();
cover = (base64 ? Buffer.from(base64, 'base64') : null);
}
- });
+ }
}
}
parser.remove('binary');
- return {fb2: parser.toObject(), cover, coverExt};
+ return {fb2: parser, cover, coverExt};
}
}
diff --git a/server/core/fb2/Fb2Parser.js b/server/core/fb2/Fb2Parser.js
index 70e2815..4a23075 100644
--- a/server/core/fb2/Fb2Parser.js
+++ b/server/core/fb2/Fb2Parser.js
@@ -3,7 +3,7 @@ const XmlParser = require('../xml/XmlParser');
class Fb2Parser extends XmlParser {
get xlinkNS() {
if (!this._xlinkNS) {
- const rootAttrs = this.$self().attrs();
+ const rootAttrs = this.selectFirstSelf().attrs();
let ns = 'l';
for (const [key, value] of rootAttrs) {
if (value == 'http://www.w3.org/1999/xlink') {
@@ -18,27 +18,24 @@ class Fb2Parser extends XmlParser {
return this._xlinkNS;
}
- bookInfo(fb2Object) {
+ bookInfo() {
const result = {};
- if (!fb2Object)
- fb2Object = this.toObject();
-
- const desc = this.inspector(fb2Object).$('fictionbook/description');
+ const desc = this.$$('/description/');
if (!desc)
return result;
const parseAuthors = (node, tagName) => {
const authors = [];
- for (const a of node.$$(tagName)) {
+ for (const a of node.$$array(tagName)) {
let names = [];
- names.push(a.text('last-name'));
- names.push(a.text('first-name'));
- names.push(a.text('middle-name'));
+ 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'));
+ names.push(a.text('/nickname'));
authors.push(names.join(' '));
}
@@ -48,7 +45,7 @@ class Fb2Parser extends XmlParser {
const parseSequence = (node, tagName) => {
const sequence = [];
- for (const s of node.$$(tagName)) {
+ for (const s of node.$$array(tagName)) {
const seqAttrs = s.attrs() || {};
const name = seqAttrs['name'] || null;
const num = seqAttrs['number'] || null;
@@ -64,7 +61,7 @@ class Fb2Parser extends XmlParser {
const info = {};
info.genre = [];
- for (const g of titleInfo.$$('genre'))
+ for (const g of titleInfo.$$array('genre'))
info.genre.push(g.text());
info.author = parseAuthors(titleInfo, 'author');
@@ -77,7 +74,7 @@ class Fb2Parser extends XmlParser {
info.annotationHtml = null;
if (info.annotation) {
//annotation как кусок xml
- info.annotationXml = (new XmlParser()).fromObject(info.annotation).toString({noHeader: true});
+ info.annotationXml = titleInfo.$$('annotation/').toString({noHeader: true});
//annotation как html
info.annotationHtml = this.toHtml(info.annotationXml);
@@ -97,19 +94,19 @@ class Fb2Parser extends XmlParser {
}
//title-info
- const titleInfo = desc.$('title-info');
+ const titleInfo = desc.$$('title-info/');
if (titleInfo) {
result.titleInfo = parseTitleInfo(titleInfo);
}
//src-title-info
- const srcTitleInfo = desc.$('src-title-info');
+ const srcTitleInfo = desc.$$('src-title-info/');
if (srcTitleInfo) {
result.srcTitleInfo = parseTitleInfo(srcTitleInfo);
}
//document-info
- const documentInfo = desc.$('document-info');
+ const documentInfo = desc.$$('document-info/');
if (documentInfo) {
const info = {};
@@ -118,7 +115,7 @@ class Fb2Parser extends XmlParser {
info.date = documentInfo.text('date');
info.srcUrl = [];
- for (const url of documentInfo.$$('src-url'))
+ for (const url of documentInfo.$$array('src-url'))
info.srcUrl.push(url.text());
info.srcOcr = documentInfo.text('src-ocr');
@@ -131,7 +128,7 @@ class Fb2Parser extends XmlParser {
info.historyHtml = null;
if (info.history) {
//history как кусок xml
- info.historyXml = (new XmlParser()).fromObject(info.history).toString({noHeader: true});
+ info.historyXml = documentInfo.$$('history/').toString({noHeader: true});
//history как html
info.historyHtml = this.toHtml(info.historyXml);
@@ -143,7 +140,7 @@ class Fb2Parser extends XmlParser {
}
//publish-info
- const publishInfo = desc.$('publish-info');
+ const publishInfo = desc.$$('publish-info/');
if (publishInfo) {
const info = {};
@@ -160,7 +157,7 @@ class Fb2Parser extends XmlParser {
return result;
}
- bookInfoList(fb2Object, options = {}) {
+ bookInfoList(bookInfo, options = {}) {
let {
correctMapping = false,
valueToString = false,
@@ -236,7 +233,7 @@ class Fb2Parser extends XmlParser {
];
mapping = correctMapping(mapping);
- const bookInfo = this.bookInfo(fb2Object);
+ bookInfo = (bookInfo ? bookInfo : this.bookInfo());
//заполняем mapping
let result = [];
diff --git a/server/core/xml/ObjectInspector.js b/server/core/xml/ObjectInspector.js
index 0adc188..21cf747 100644
--- a/server/core/xml/ObjectInspector.js
+++ b/server/core/xml/ObjectInspector.js
@@ -3,7 +3,7 @@ class ObjectInspector {
this.raw = raw;
}
- makeSelector(selector) {
+ narrowSelector(selector) {
const result = [];
selector = selector.trim();
@@ -31,7 +31,7 @@ class ObjectInspector {
}
select(selector = '') {
- selector = this.makeSelector(selector);
+ selector = this.narrowSelector(selector);
let raw = this.raw;
for (const s of selector) {
@@ -50,14 +50,10 @@ class ObjectInspector {
}
if (raw === undefined || raw === null) {
- raw = null;
- break;
+ return [];
}
}
- if (raw === null)
- return [];
-
raw = (Array.isArray(raw) ? raw : [raw]);
const result = [];
diff --git a/server/core/xml/XmlParser.js b/server/core/xml/XmlParser.js
index 26da7e7..2c073c6 100644
--- a/server/core/xml/XmlParser.js
+++ b/server/core/xml/XmlParser.js
@@ -1,5 +1,4 @@
const sax = require('./sax');
-const ObjectInspector = require('./ObjectInspector');
//node types
const NODE = 1;
@@ -22,7 +21,7 @@ const type2name = {
};
class NodeBase {
- makeSelectorObj(selectorString) {
+ wideSelector(selectorString) {
const result = {all: false, before: false, type: 0, name: ''};
if (selectorString === '') {
@@ -153,7 +152,7 @@ class NodeObject extends NodeBase {
if (this.type !== NODE)
return;
- const selectorObj = this.makeSelectorObj(after);
+ const selectorObj = this.wideSelector(after);
if (!Array.isArray(this.raw[3]))
this.raw[3] = [];
@@ -172,7 +171,7 @@ class NodeObject extends NodeBase {
if (this.type !== NODE || !this.raw[3])
return;
- const selectorObj = this.makeSelectorObj(selector);
+ const selectorObj = this.wideSelector(selector);
this.rawRemove(this.raw[3], selectorObj);
if (!this.raw[3].length)
@@ -233,6 +232,14 @@ class XmlParser extends NodeBase {
return this.rawNodes.length;
}
+ get nodes() {
+ const result = [];
+ for (const n of this.rawNodes)
+ result.push(new NodeObject(n));
+
+ return result;
+ }
+
nodeObject(node) {
return new NodeObject(node);
}
@@ -279,7 +286,7 @@ class XmlParser extends NodeBase {
}
add(node, after = '*') {
- const selectorObj = this.makeSelectorObj(after);
+ const selectorObj = this.wideSelector(after);
for (const n of this.rawNodes) {
if (n && n[0] === NODE) {
@@ -299,7 +306,7 @@ class XmlParser extends NodeBase {
}
addRoot(node, after = '*') {
- const selectorObj = this.makeSelectorObj(after);
+ const selectorObj = this.wideSelector(after);
if (Array.isArray(node)) {
for (const node_ of node)
@@ -312,7 +319,7 @@ class XmlParser extends NodeBase {
}
remove(selector = '') {
- const selectorObj = this.makeSelectorObj(selector);
+ const selectorObj = this.wideSelector(selector);
for (const n of this.rawNodes) {
if (n && n[0] === NODE && Array.isArray(n[3])) {
@@ -326,7 +333,7 @@ class XmlParser extends NodeBase {
}
removeRoot(selector = '') {
- const selectorObj = this.makeSelectorObj(selector);
+ const selectorObj = this.wideSelector(selector);
this.rawRemove(this.rawNodes, selectorObj);
@@ -409,7 +416,7 @@ class XmlParser extends NodeBase {
newRawNodes = res.rawNodes;
} else {
- const selectorObj = this.makeSelectorObj(selector);
+ const selectorObj = this.wideSelector(selector);
if (self) {
this.rawSelect(this.rawNodes, selectorObj, (node) => {
@@ -429,11 +436,7 @@ class XmlParser extends NodeBase {
return new XmlParser(newRawNodes);
}
- $$(selector, self) {
- return this.select(selector, self);
- }
-
- $$self(selector) {
+ selectSelf(selector) {
return this.select(selector, true);
}
@@ -443,11 +446,7 @@ class XmlParser extends NodeBase {
return new NodeObject(node);
}
- $(selector, self) {
- return this.selectFirst(selector, self);
- }
-
- $self(selector) {
+ selectFirstSelf(selector) {
return this.selectFirst(selector, true);
}
@@ -760,12 +759,138 @@ class XmlParser extends NodeBase {
return this;
}
- inspector(obj) {
- if (!obj)
- obj = this.toObject();
+ // XML Inspector start
+ narrowSelector(selector) {
+ const result = [];
+ selector = selector.trim();
+
+ //последний индекс не учитывется, только если не задан явно
+ if (selector && selector[selector.length - 1] == ']')
+ selector += '/';
- return new ObjectInspector(obj);
+ const levels = selector.split('/');
+
+ for (const level of levels) {
+ const [name, indexPart] = level.split('[');
+ let index = 0;
+ if (indexPart) {
+ const i = indexPart.indexOf(']');
+ index = parseInt(indexPart.substring(0, i), 10) || 0;
+ }
+
+ let type = NODE;
+ if (name[0] === '*') {
+ const typeName = name.substring(1);
+ type = name2type[typeName];
+ if (!type)
+ throw new Error(`Unknown selector type: ${typeName}`);
+ }
+
+ result.push({type, name, index});
+ }
+
+ if (result.length);
+ result[result.length - 1].last = true;
+
+ return result;
}
+
+ inspect(selector = '') {
+ selector = this.narrowSelector(selector);
+
+ let raw = this.rawNodes;
+ for (const s of selector) {
+ if (s.name) {
+ let found = [];
+ for (const n of raw) {
+ if (n[0] === s.type && (n[0] !== NODE || s.name === '*NODE' || n[1] === s.name)) {
+ found.push(n);
+
+ if (found.length > s.index && !s.last)
+ break;
+ }
+ }
+
+ raw = found;
+ }
+
+ if (raw.length && !s.last) {
+ if (s.index < raw.length) {
+ raw = raw[s.index];
+ if (raw[0] === NODE && raw[3])
+ raw = raw[3];
+ else {
+ raw = [];
+ break;
+ }
+ } else {
+ raw = [];
+ break;
+ }
+ }
+ }
+
+ return new XmlParser(raw);
+ }
+
+ $$(selector) {
+ return this.inspect(selector);
+ }
+
+ $$array(selector) {
+ const res = this.inspect(selector);
+ const result = [];
+ for (const n of res.rawNodes)
+ if (n[0] === NODE)
+ result.push(new XmlParser([n]));
+
+ return result;
+ }
+
+ $(selector) {
+ const res = this.inspect(selector);
+ const node = (res.count ? res.rawNodes[0] : null);
+ return new NodeObject(node);
+ }
+
+ v(selector = '') {
+ const res = this.$(selector);
+ return (res.type ? res.value : null);
+ }
+
+ text(selector = '') {
+ const res = this.$(`${selector}/*TEXT`);
+ return (res.type === TEXT ? res.value : null);
+ }
+
+ comment(selector = '') {
+ const res = this.$(`${selector}/*COMMENT`);
+ return (res.type === COMMENT ? res.value : null);
+ }
+
+ cdata(selector = '') {
+ const res = this.$(`${selector}/*CDATA`);
+ return (res.type === CDATA ? res.value : null);
+ }
+
+ concat(selector = '') {
+ const res = this.$$(selector);
+ const out = [];
+ for (const n of res.rawNodes) {
+ const node = new NodeObject(n);
+ if (node.type && node.type !== NODE)
+ out.push(node.value);
+ }
+
+ return (out.length ? out.join('') : null);
+ }
+
+ attrs(selector = '') {
+ const res = this.$(selector);
+ const attrs = res.attrs();
+ return (res.type === NODE && attrs ? Object.fromEntries(attrs) : null);
+ }
+ // XML Inspector finish
}
module.exports = XmlParser;
\ No newline at end of file
diff --git a/server/index.js b/server/index.js
index 15e387f..9e2e8aa 100644
--- a/server/index.js
+++ b/server/index.js
@@ -191,7 +191,7 @@ function initStatic(app, config) {
if (path.extname(req.path) == '') {
const bookFile = `${config.publicFilesDir}${req.path}`;
- const bookFileDesc = `${bookFile}.json`;
+ const bookFileDesc = `${bookFile}.d.json`;
let downFileName = '';
//восстановим из json-файла описания