1
0

fb2save.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. #include <QtGui>
  2. #include <QtDebug>
  3. #include "fb2page.hpp"
  4. #include "fb2save.hpp"
  5. #include "fb2text.hpp"
  6. #include "fb2utils.h"
  7. #include "fb2html.h"
  8. #include <QAbstractNetworkCache>
  9. #include <QBuffer>
  10. #include <QComboBox>
  11. #include <QDateTime>
  12. #include <QFileDialog>
  13. #include <QGridLayout>
  14. #include <QLabel>
  15. #include <QList>
  16. #include <QNetworkReply>
  17. #include <QNetworkRequest>
  18. #include <QScopedPointer>
  19. #include <QTextCodec>
  20. #include <QWebFrame>
  21. #include <QWebPage>
  22. #include <QtDebug>
  23. #define XMLAutoFormatting
  24. //#define ImgTypePrint
  25. //---------------------------------------------------------------------------
  26. // FbSaveDialog
  27. //---------------------------------------------------------------------------
  28. FbSaveDialog::FbSaveDialog(QWidget *parent, Qt::WindowFlags f)
  29. : QFileDialog(parent, f)
  30. {
  31. init();
  32. }
  33. FbSaveDialog::FbSaveDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter)
  34. : QFileDialog(parent, caption, directory, filter)
  35. {
  36. init();
  37. }
  38. void FbSaveDialog::init()
  39. {
  40. QMap<QString, QString> codecMap;
  41. for (const int &mib: QTextCodec::availableMibs()) {
  42. QTextCodec *codec = QTextCodec::codecForMib(mib);
  43. QString sortKey = codec->name().toUpper();
  44. int rank;
  45. if (sortKey.startsWith("UTF-8")) {
  46. rank = 1;
  47. } else if (sortKey.startsWith("KOI8")) {
  48. rank = 2;
  49. } else if (sortKey.startsWith("WINDOWS")) {
  50. rank = 3;
  51. } else {
  52. rank = 4;
  53. }
  54. sortKey.prepend(QChar('0' + rank));
  55. codecMap.insert(sortKey, codec->name());
  56. }
  57. setAcceptMode(AcceptSave);
  58. setOption(DontConfirmOverwrite, false);
  59. setDefaultSuffix("fb2");
  60. QStringList filters;
  61. filters << tr("Fiction book files (*.fb2)");
  62. filters << tr("Any files (*.*)");
  63. setNameFilters(filters);
  64. combo = new QComboBox(this);
  65. for (const QString &codec: codecMap) {
  66. combo->addItem(codec);
  67. }
  68. combo->setCurrentIndex(0);
  69. label = new QLabel(this);
  70. label->setText(tr("&Encoding"));
  71. label->setBuddy(combo);
  72. layout()->addWidget(label);
  73. layout()->addWidget(combo);
  74. }
  75. QString FbSaveDialog::fileName() const
  76. {
  77. for (const QString &filename: selectedFiles()) {
  78. return filename;
  79. }
  80. return QString();
  81. }
  82. QString FbSaveDialog::codec() const
  83. {
  84. return combo->currentText();
  85. }
  86. //---------------------------------------------------------------------------
  87. // FbHtmlHandler
  88. //---------------------------------------------------------------------------
  89. QString FbHtmlHandler::local(const QString &name)
  90. {
  91. return name.mid(name.lastIndexOf(":"));
  92. }
  93. void FbHtmlHandler::onAttr(const QString &name, const QString &value)
  94. {
  95. m_atts.append(name, local(name), value);
  96. }
  97. void FbHtmlHandler::onNew(const QString &name)
  98. {
  99. startElement("", local(name), name, m_atts);
  100. m_atts.clear();
  101. }
  102. void FbHtmlHandler::onTxt(const QString &text)
  103. {
  104. m_lastTextLength = text.length();
  105. characters(text);
  106. }
  107. void FbHtmlHandler::onCom(const QString &text)
  108. {
  109. comment(text);
  110. }
  111. void FbHtmlHandler::onEnd(const QString &name)
  112. {
  113. endElement("", local(name), name);
  114. }
  115. //---------------------------------------------------------------------------
  116. // FbSaveWriter
  117. //---------------------------------------------------------------------------
  118. FbSaveWriter::FbSaveWriter(FbTextEdit &view, QByteArray *array)
  119. : QXmlStreamWriter(array)
  120. , m_view(view)
  121. , m_string(0)
  122. , m_anchor(0)
  123. , m_focus(0)
  124. {
  125. if (QWebFrame * frame = m_view.page()->mainFrame()) {
  126. m_style = frame->findFirstElement("html>head>style#origin").toPlainText();
  127. }
  128. #ifdef XMLAutoFormatting
  129. setAutoFormatting(true);
  130. #endif
  131. }
  132. FbSaveWriter::FbSaveWriter(FbTextEdit &view, QIODevice *device)
  133. : QXmlStreamWriter(device)
  134. , m_view(view)
  135. , m_string(0)
  136. , m_anchor(0)
  137. , m_focus(0)
  138. {
  139. #ifdef XMLAutoFormatting
  140. setAutoFormatting(true);
  141. #endif
  142. }
  143. FbSaveWriter::FbSaveWriter(FbTextEdit &view, QString *string)
  144. : QXmlStreamWriter(string)
  145. , m_view(view)
  146. , m_string(string)
  147. , m_anchor(0)
  148. , m_focus(0)
  149. {
  150. #ifdef XMLAutoFormatting
  151. setAutoFormatting(true);
  152. #endif
  153. }
  154. void FbSaveWriter::writeComment(const QString &ch)
  155. {
  156. writeLineEnd();
  157. QXmlStreamWriter::writeComment(ch);
  158. }
  159. void FbSaveWriter::writeStartDocument()
  160. {
  161. if (device()) {
  162. QXmlStreamWriter::writeStartDocument();
  163. } else if (m_string) {
  164. m_string->append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  165. }
  166. }
  167. void FbSaveWriter::writeStartElement(const QString &name, int level)
  168. {
  169. #ifndef XMLAutoFormatting
  170. Q_UNUSED(level)
  171. if (level) writeLineEnd();
  172. for (int i = 1; i < level; ++i) writeCharacters(" ");
  173. #endif
  174. QXmlStreamWriter::writeStartElement(name);
  175. }
  176. void FbSaveWriter::writeEndElement(int level)
  177. {
  178. #ifndef XMLAutoFormatting
  179. Q_UNUSED(level)
  180. if (level) writeLineEnd();
  181. for (int i = 1; i < level; ++i) writeCharacters(" ");
  182. #endif
  183. QXmlStreamWriter::writeEndElement();
  184. }
  185. void FbSaveWriter::writeLineEnd()
  186. {
  187. #ifndef XMLAutoFormatting
  188. writeCharacters("\n");
  189. #endif
  190. }
  191. QByteArray FbSaveWriter::downloadFile(const QUrl &url)
  192. {
  193. QNetworkRequest request(url);
  194. QNetworkAccessManager * network = m_view.page()->networkAccessManager();
  195. QScopedPointer<QNetworkReply> reply(network->get(request));
  196. if (reply.isNull()) return QByteArray();
  197. QEventLoop loop;
  198. QObject::connect(reply.data(), SIGNAL(finished()), &loop, SLOT(quit()));
  199. loop.exec();
  200. return reply->readAll();
  201. }
  202. QString FbSaveWriter::append(const QString &name)
  203. {
  204. if (m_names.indexOf(name) < 0) {
  205. m_names.append(name);
  206. }
  207. return name;
  208. }
  209. QString FbSaveWriter::filename(const QString &path)
  210. {
  211. FbStore *store = m_view.store();
  212. if (!store) return QString();
  213. if (path.left(1) == "#") {
  214. QString name = path.mid(1);
  215. if (store->exists(name)) {
  216. return append(name);
  217. } else {
  218. return QString();
  219. }
  220. } else {
  221. QUrl url = path;
  222. QByteArray data = downloadFile(url);
  223. if (data.size() == 0) return QString();
  224. QString name = store->add(url.path(), data);
  225. return append(name);
  226. }
  227. }
  228. void FbSaveWriter::writeStyle()
  229. {
  230. if (m_style.isEmpty()) return;
  231. const QString postfix = "\n ";
  232. writeStartElement("stylesheet", 2);
  233. writeAttribute("type", "text/css");
  234. writeCharacters(postfix);
  235. QStringList list = m_style.split("}", Qt::SkipEmptyParts);
  236. QString line;
  237. for (const QString &str: list) {
  238. line = str.simplified();
  239. if (line.isEmpty()) continue;
  240. writeCharacters(" " + line + "}" + postfix);
  241. }
  242. QXmlStreamWriter::writeEndElement();
  243. }
  244. void FbSaveWriter::writeFiles()
  245. {
  246. FbStore *store = m_view.store();
  247. if (!store) return;
  248. QStringListIterator it(m_names);
  249. while (it.hasNext()) {
  250. QString name = it.next();
  251. if (name.isEmpty()) continue;
  252. FbBinary * file = store->get(name);
  253. if (!file) continue;
  254. writeStartElement("binary", 2);
  255. writeAttribute("id", name);
  256. QByteArray array = file->data();
  257. QString data = array.toBase64();
  258. writeContentType(name, array);
  259. writeLineEnd();
  260. int pos = 0;
  261. while (true) {
  262. QString text = data.mid(pos, 76);
  263. if (text.isEmpty()) break;
  264. writeCharacters(text);
  265. writeLineEnd();
  266. pos += 76;
  267. }
  268. writeCharacters(" ");
  269. QXmlStreamWriter::writeEndElement();
  270. }
  271. }
  272. void FbSaveWriter::writeContentType(const QString &name, QByteArray &data)
  273. {
  274. QBuffer buffer(&data);
  275. buffer.open(QIODevice::ReadOnly);
  276. QString type = QImageReader::imageFormat(&buffer);
  277. #ifdef ImgTypePrint
  278. qCritical()<<"Img type: "<< type;
  279. #endif
  280. if (type.isEmpty()) {
  281. qCritical() << QObject::tr("Unknown image format: %1").arg(name);
  282. return;
  283. }
  284. type.prepend("image/");
  285. writeAttribute("content-type", type);
  286. }
  287. void FbSaveWriter::setAnchor(int offset)
  288. {
  289. if (m_string) m_anchor = m_string->length() + offset;
  290. }
  291. void FbSaveWriter::setFocus(int offset)
  292. {
  293. if (m_string) m_focus = m_string->length() + offset;
  294. }
  295. //---------------------------------------------------------------------------
  296. // FbSaveHandler::TextHandler
  297. //---------------------------------------------------------------------------
  298. FB2_BEGIN_KEYHASH(FbSaveHandler::TextHandler)
  299. FB2_KEY( Origin , "table" );
  300. FB2_KEY( Origin , "td" );
  301. FB2_KEY( Origin , "th" );
  302. FB2_KEY( Origin , "tr" );
  303. FB2_KEY( Origin , "a" );
  304. FB2_KEY( Image , "img" );
  305. FB2_KEY( Parag , "p" );
  306. FB2_KEY( Strong , "b" );
  307. FB2_KEY( Emphas , "i" );
  308. FB2_KEY( Span , "span" );
  309. FB2_KEY( Strike , "strike" );
  310. FB2_KEY( Sub , "sub" );
  311. FB2_KEY( Sup , "sup" );
  312. FB2_KEY( Code , "tt" );
  313. FB2_END_KEYHASH
  314. FbSaveHandler::TextHandler::TextHandler(FbSaveWriter &writer, const QString &name, const QXmlStreamAttributes &atts, const QString &tag)
  315. : NodeHandler(name)
  316. , m_writer(writer)
  317. , m_tag(tag)
  318. , m_level(1)
  319. , m_hasChild(false)
  320. {
  321. if (tag.isEmpty()) return;
  322. m_writer.writeStartElement(m_tag, m_level);
  323. writeAtts(atts);
  324. }
  325. FbSaveHandler::TextHandler::TextHandler(TextHandler *parent, const QString &name, const QXmlStreamAttributes &atts, const QString &tag)
  326. : NodeHandler(name)
  327. , m_writer(parent->m_writer)
  328. , m_tag(tag)
  329. , m_level(parent->nextLevel())
  330. , m_hasChild(false)
  331. {
  332. if (tag.isEmpty()) return;
  333. m_writer.writeStartElement(m_tag, m_level);
  334. writeAtts(atts);
  335. }
  336. void FbSaveHandler::TextHandler::writeAtts(const QXmlStreamAttributes &atts)
  337. {
  338. for (const auto& attr : atts) {
  339. QString name = attr.qualifiedName().toString();
  340. QString value = attr.value().toString();
  341. if (m_tag == "image") {
  342. if (name == "src") {
  343. name = "l:href";
  344. value = m_writer.filename(value).prepend('#');
  345. }
  346. } else if (m_tag == "a") {
  347. if (name == "href") name = "l:href";
  348. }
  349. m_writer.writeAttribute(name, value);
  350. }
  351. }
  352. FbXmlHandler::NodeHandler * FbSaveHandler::TextHandler::NewTag(const QString &name, const QXmlStreamAttributes &atts)
  353. {
  354. m_hasChild = true;
  355. QString tag = QString();
  356. switch (toKeyword(name)) {
  357. case Origin : tag = name; break;
  358. case Parag : return new ParagHandler(this, name, atts);
  359. case Span : return new SpanHandler(this, name, atts);
  360. case Image : tag = "image" ; break;
  361. case Strong : tag = "strong" ; break;
  362. case Emphas : tag = "emphasis" ; break;
  363. case Strike : tag = "strikethrough" ; break;
  364. case Code : tag = "code" ; break;
  365. case Sub : tag = "sub" ; break;
  366. case Sup : tag = "sup" ; break;
  367. default: if (name.left(3) == "fb:") tag = name.mid(3);
  368. }
  369. return new TextHandler(this, name, atts, tag);
  370. }
  371. void FbSaveHandler::TextHandler::TxtTag(const QString &text)
  372. {
  373. m_writer.writeCharacters(text);
  374. }
  375. void FbSaveHandler::TextHandler::EndTag(const QString &name)
  376. {
  377. Q_UNUSED(name);
  378. if (m_tag.isEmpty()) return;
  379. m_writer.writeEndElement(m_hasChild ? m_level : 0);
  380. }
  381. int FbSaveHandler::TextHandler::nextLevel() const
  382. {
  383. return m_level ? m_level + 1 : 0;
  384. }
  385. //---------------------------------------------------------------------------
  386. // FbSaveHandler::RootHandler
  387. //---------------------------------------------------------------------------
  388. FbSaveHandler::RootHandler::RootHandler(FbSaveWriter &writer, const QString &name)
  389. : NodeHandler(name)
  390. , m_writer(writer)
  391. {
  392. }
  393. FbXmlHandler::NodeHandler * FbSaveHandler::RootHandler::NewTag(const QString &name, const QXmlStreamAttributes &atts)
  394. {
  395. Q_UNUSED(atts);
  396. return name == "body" ? new BodyHandler(m_writer, name) : NULL;
  397. }
  398. //---------------------------------------------------------------------------
  399. // FbSaveHandler::BodyHandler
  400. //---------------------------------------------------------------------------
  401. FbSaveHandler::BodyHandler::BodyHandler(FbSaveWriter &writer, const QString &name)
  402. : TextHandler(writer, name, QXmlStreamAttributes(), "FictionBook")
  403. {
  404. m_writer.writeAttribute("xmlns", "http://www.gribuser.ru/xml/fictionbook/2.0");
  405. m_writer.writeAttribute("xmlns:l", "http://www.w3.org/1999/xlink");
  406. m_writer.writeStyle();
  407. }
  408. void FbSaveHandler::BodyHandler::EndTag(const QString &name)
  409. {
  410. m_writer.writeFiles();
  411. TextHandler::EndTag(name);
  412. }
  413. //---------------------------------------------------------------------------
  414. // FbSaveHandler::SpanHandler
  415. //---------------------------------------------------------------------------
  416. FbSaveHandler::SpanHandler::SpanHandler(TextHandler *parent, const QString &name, const QXmlStreamAttributes &atts)
  417. : TextHandler(parent, name, atts, Value(atts, "class") == "Apple-style-span" ? "" : "style")
  418. {
  419. }
  420. //---------------------------------------------------------------------------
  421. // FbSaveHandler::ParagHandler
  422. //---------------------------------------------------------------------------
  423. FbSaveHandler::ParagHandler::ParagHandler(TextHandler *parent, const QString &name, const QXmlStreamAttributes &atts)
  424. : TextHandler(parent, name, atts, "")
  425. , m_parent(parent->tag())
  426. , m_empty(true)
  427. {
  428. for (const auto& attr : atts) {
  429. QString qName = attr.qualifiedName().toString();
  430. QString value = attr.value().toString();
  431. if (qName == "fb:class") {
  432. m_class = value;
  433. } else {
  434. m_atts.append(qName, value);
  435. }
  436. }
  437. }
  438. FbXmlHandler::NodeHandler * FbSaveHandler::ParagHandler::NewTag(const QString &name, const QXmlStreamAttributes &atts)
  439. {
  440. if (m_empty && name != "br") start();
  441. return TextHandler::NewTag(name, atts);
  442. }
  443. void FbSaveHandler::ParagHandler::TxtTag(const QString &text)
  444. {
  445. if (m_empty) {
  446. if (isWhiteSpace(text)) return;
  447. start();
  448. }
  449. TextHandler::TxtTag(text);
  450. }
  451. void FbSaveHandler::ParagHandler::EndTag(const QString &name)
  452. {
  453. Q_UNUSED(name);
  454. if (m_empty) m_writer.writeStartElement("empty-line", m_level);
  455. m_writer.writeEndElement(0);
  456. }
  457. void FbSaveHandler::ParagHandler::start()
  458. {
  459. if (!m_empty) return;
  460. QString tag = "p";
  461. if (m_class.isEmpty()) {
  462. if (m_parent == "stanza") tag = "v";
  463. } else {
  464. tag = m_class;
  465. }
  466. m_writer.writeStartElement(tag, m_level);
  467. writeAtts(m_atts);
  468. m_empty = false;
  469. }
  470. //---------------------------------------------------------------------------
  471. // FbSaveHandler
  472. //---------------------------------------------------------------------------
  473. FbSaveHandler::FbSaveHandler(FbSaveWriter &writer)
  474. : FbHtmlHandler()
  475. , m_writer(writer)
  476. {
  477. }
  478. bool FbSaveHandler::comment(const QString& ch)
  479. {
  480. m_writer.writeComment(ch);
  481. return true;
  482. }
  483. void FbSaveHandler::onAnchor(int offset)
  484. {
  485. m_writer.setAnchor(offset - m_lastTextLength);
  486. }
  487. void FbSaveHandler::onFocus(int offset)
  488. {
  489. m_writer.setFocus(offset - m_lastTextLength);
  490. }
  491. FbXmlHandler::NodeHandler * FbSaveHandler::CreateRoot(const QString &name, const QXmlStreamAttributes &atts)
  492. {
  493. Q_UNUSED(atts);
  494. if (name == "html") return new RootHandler(m_writer, name);
  495. m_error = QObject::tr("The tag <html> was not found.");
  496. return 0;
  497. }
  498. void FbSaveHandler::setDocumentInfo(QWebFrame * frame)
  499. {
  500. QString info1 = qApp->applicationName() += QString(" ") += qApp->applicationVersion();
  501. QDateTime now = QDateTime::currentDateTime();
  502. QString info2 = now.toString("dd MMM yyyy");
  503. QString value = now.toString("yyyy-MM-dd hh:mm:ss");
  504. FbTextElement parent = frame->documentElement().findFirst("BODY");
  505. parent = parent["FB:DESCRIPTION"];
  506. parent = parent["FB:DOCUMENT-INFO"];
  507. FbTextElement child1 = parent["FB:PROGRAM-USED"];
  508. child1.setInnerXml(info1);
  509. FbTextElement child2 = parent["FB:DATE"];
  510. child2.setInnerXml(info2);
  511. child2.setAttribute("value", value);
  512. }
  513. bool FbSaveHandler::save()
  514. {
  515. FbTextPage *page = m_writer.view().page();
  516. if (!page) return false;
  517. QWebFrame *frame = page->mainFrame();
  518. if (!frame) return false;
  519. m_writer.writeStartDocument();
  520. if (page->isModified()) setDocumentInfo(frame);
  521. QString javascript = jScript("export.js");
  522. frame->addToJavaScriptWindowObject("handler", this);
  523. frame->evaluateJavaScript(javascript);
  524. m_writer.writeEndDocument();
  525. return true;
  526. }