fb2save.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. #include <QtGui>
  2. #include <QtDebug>
  3. #include "fb2save.hpp"
  4. #include "fb2view.hpp"
  5. #include "fb2utils.h"
  6. #include <QAbstractNetworkCache>
  7. #include <QComboBox>
  8. #include <QFileDialog>
  9. #include <QGridLayout>
  10. #include <QLabel>
  11. #include <QList>
  12. #include <QNetworkReply>
  13. #include <QNetworkRequest>
  14. #include <QScopedPointer>
  15. #include <QTextCodec>
  16. #include <QWebFrame>
  17. #include <QWebPage>
  18. //---------------------------------------------------------------------------
  19. // Fb2SaveDialog
  20. //---------------------------------------------------------------------------
  21. Fb2SaveDialog::Fb2SaveDialog(QWidget *parent, Qt::WindowFlags f)
  22. : QFileDialog(parent, f)
  23. {
  24. init();
  25. }
  26. Fb2SaveDialog::Fb2SaveDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter)
  27. : QFileDialog(parent, caption, directory, filter)
  28. {
  29. init();
  30. }
  31. void Fb2SaveDialog::init()
  32. {
  33. QMap<QString, QString> codecMap;
  34. foreach (int mib, QTextCodec::availableMibs()) {
  35. QTextCodec *codec = QTextCodec::codecForMib(mib);
  36. QString sortKey = codec->name().toUpper();
  37. int rank;
  38. if (sortKey.startsWith("UTF-8")) {
  39. rank = 1;
  40. } else if (sortKey.startsWith("KOI8")) {
  41. rank = 2;
  42. } else if (sortKey.startsWith("WINDOWS")) {
  43. rank = 3;
  44. } else {
  45. rank = 4;
  46. }
  47. sortKey.prepend(QChar('0' + rank));
  48. codecMap.insert(sortKey, codec->name());
  49. }
  50. setAcceptMode(AcceptSave);
  51. setConfirmOverwrite(true);
  52. setDefaultSuffix("fb2");
  53. QStringList filters;
  54. filters << tr("Fiction book files (*.fb2)");
  55. filters << tr("Any files (*.*)");
  56. setNameFilters(filters);
  57. combo = new QComboBox(this);
  58. foreach (QString codec, codecMap) {
  59. combo->addItem(codec);
  60. }
  61. combo->setCurrentIndex(0);
  62. label = new QLabel(this);
  63. label->setText(tr("&Encoding"));
  64. label->setBuddy(combo);
  65. layout()->addWidget(label);
  66. layout()->addWidget(combo);
  67. }
  68. QString Fb2SaveDialog::fileName() const
  69. {
  70. foreach (QString filename, selectedFiles()) {
  71. return filename;
  72. }
  73. return QString();
  74. }
  75. QString Fb2SaveDialog::codec() const
  76. {
  77. return combo->currentText();
  78. }
  79. //---------------------------------------------------------------------------
  80. // Fb2HtmlHandler
  81. //---------------------------------------------------------------------------
  82. QString Fb2HtmlHandler::local(const QString &name)
  83. {
  84. return name.mid(name.lastIndexOf(":"));
  85. }
  86. void Fb2HtmlHandler::onAttr(const QString &name, const QString &value)
  87. {
  88. m_atts.append(name, "", local(name), value);
  89. }
  90. void Fb2HtmlHandler::onNew(const QString &name)
  91. {
  92. startElement("", local(name), name, m_atts);
  93. m_atts.clear();
  94. }
  95. void Fb2HtmlHandler::onTxt(const QString &text)
  96. {
  97. characters(text);
  98. }
  99. void Fb2HtmlHandler::onEnd(const QString &name)
  100. {
  101. endElement("", local(name), name);
  102. }
  103. //---------------------------------------------------------------------------
  104. // Fb2SaveWriter
  105. //---------------------------------------------------------------------------
  106. Fb2SaveWriter::Fb2SaveWriter(Fb2WebView &view, QByteArray *array, QList<int> *folds)
  107. : QXmlStreamWriter(array)
  108. , m_folds(folds)
  109. , m_view(view)
  110. , m_line(0)
  111. {
  112. }
  113. Fb2SaveWriter::Fb2SaveWriter(Fb2WebView &view, QIODevice *device, QList<int> *folds)
  114. : QXmlStreamWriter(device)
  115. , m_folds(folds)
  116. , m_view(view)
  117. , m_line(0)
  118. {
  119. }
  120. Fb2SaveWriter::Fb2SaveWriter(Fb2WebView &view, QString *string, QList<int> *folds)
  121. : QXmlStreamWriter(string)
  122. , m_folds(folds)
  123. , m_view(view)
  124. , m_line(0)
  125. {
  126. }
  127. void Fb2SaveWriter::writeStartElement(const QString &name, int level)
  128. {
  129. if (level) writeLineEnd();
  130. for (int i = 1; i < level; i++) writeCharacters(" ");
  131. QXmlStreamWriter::writeStartElement(name);
  132. }
  133. void Fb2SaveWriter::writeEndElement(int level)
  134. {
  135. if (level) writeLineEnd();
  136. for (int i = 1; i < level; i++) writeCharacters(" ");
  137. QXmlStreamWriter::writeEndElement();
  138. }
  139. void Fb2SaveWriter::writeLineEnd()
  140. {
  141. writeCharacters("\n");
  142. m_line++;
  143. }
  144. QByteArray Fb2SaveWriter::downloadFile(const QUrl &url)
  145. {
  146. QNetworkRequest request(url);
  147. QNetworkAccessManager * network = m_view.page()->networkAccessManager();
  148. QScopedPointer<QNetworkReply> reply(network->get(request));
  149. if (reply.isNull()) return QByteArray();
  150. QEventLoop loop;
  151. QObject::connect(reply.data(), SIGNAL(finished()), &loop, SLOT(quit()));
  152. loop.exec();
  153. return reply->readAll();
  154. }
  155. QString Fb2SaveWriter::newFileName(const QString &path)
  156. {
  157. QFileInfo info(path);
  158. QString name = info.fileName();
  159. if (!m_view.files().exists(name) && !m_files.exists(name)) return name;
  160. QString base = info.baseName();
  161. QString suff = info.suffix();
  162. for (int i = 1; ; i++) {
  163. QString name = QString("%1(%2).%3").arg(base).arg(i).arg(suff);
  164. if (m_view.files().exists(name)) continue;
  165. if (m_files.exists(name)) continue;
  166. return name;
  167. }
  168. }
  169. QString Fb2SaveWriter::getFileName(const QString &path)
  170. {
  171. QUrl url = path;
  172. if (url.scheme() == "fb2") {
  173. QString name = url.path();
  174. if (m_view.files().exists(name)) {
  175. m_names.append(name);
  176. return name;
  177. }
  178. return QString();
  179. } else {
  180. QByteArray data = downloadFile(url);
  181. if (data.size() == 0) return QString();
  182. QString hash = Fb2TemporaryFile::md5(data);
  183. QString name = m_view.files().name(hash);
  184. if (name.isEmpty()) m_files.name(hash);
  185. if (name.isEmpty()) {
  186. name = newFileName(url.path());
  187. m_files.set(name, data, hash);
  188. m_names.append(name);
  189. }
  190. return name;
  191. }
  192. }
  193. QByteArray Fb2SaveWriter::getFileData(const QString &name)
  194. {
  195. QByteArray data = m_view.files().data(name);
  196. if (data.size() == 0) data = m_files.data(name);
  197. return data;
  198. }
  199. void Fb2SaveWriter::writeFiles()
  200. {
  201. QStringListIterator it(m_names);
  202. while (it.hasNext()) {
  203. QString name = it.next();
  204. if (name.isEmpty()) continue;
  205. QString data = getFileData(name).toBase64();
  206. if (data.isEmpty()) continue;
  207. writeStartElement("binary", 2);
  208. if (m_folds) m_folds->append(m_line);
  209. writeAttribute("id", name);
  210. writeLineEnd();
  211. int pos = 0;
  212. while (true) {
  213. QString text = data.mid(pos, 76);
  214. if (text.isEmpty()) break;
  215. writeCharacters(text);
  216. writeLineEnd();
  217. pos += 76;
  218. }
  219. writeCharacters(" ");
  220. QXmlStreamWriter::writeEndElement();
  221. }
  222. }
  223. //---------------------------------------------------------------------------
  224. // Fb2SaveHandler::BodyHandler
  225. //---------------------------------------------------------------------------
  226. FB2_BEGIN_KEYHASH(Fb2SaveHandler::BodyHandler)
  227. FB2_KEY( Section , "div" );
  228. FB2_KEY( Anchor , "a" );
  229. FB2_KEY( Image , "img" );
  230. FB2_KEY( Table , "table" );
  231. FB2_KEY( Parag , "p" );
  232. FB2_KEY( Strong , "b" );
  233. FB2_KEY( Emphas , "i" );
  234. FB2_KEY( Strike , "strike" );
  235. FB2_KEY( Sub , "sub" );
  236. FB2_KEY( Sup , "sup" );
  237. FB2_KEY( Code , "tt" );
  238. FB2_END_KEYHASH
  239. Fb2SaveHandler::BodyHandler::BodyHandler(Fb2SaveWriter &writer, const QString &name, const QXmlAttributes &atts, const QString &tag)
  240. : NodeHandler(name)
  241. , m_writer(writer)
  242. , m_tag(tag)
  243. , m_level(1)
  244. , m_hasChild(false)
  245. {
  246. Init(atts);
  247. }
  248. Fb2SaveHandler::BodyHandler::BodyHandler(BodyHandler *parent, const QString &name, const QXmlAttributes &atts, const QString &tag)
  249. : NodeHandler(name)
  250. , m_writer(parent->m_writer)
  251. , m_tag(tag)
  252. , m_level(parent->nextLevel())
  253. , m_hasChild(false)
  254. {
  255. Init(atts);
  256. }
  257. void Fb2SaveHandler::BodyHandler::Init(const QXmlAttributes &atts)
  258. {
  259. if (m_tag.isEmpty()) return;
  260. m_writer.writeStartElement(m_tag, m_level);
  261. int count = atts.count();
  262. for (int i = 0; i < count; i++) {
  263. QString name = atts.qName(i);
  264. if (name == "id") {
  265. m_writer.writeAttribute(name, atts.value(i));
  266. } else if (name == "name") {
  267. m_writer.writeAttribute(name, atts.value(i));
  268. } else if (name.left(4) == "fb2:") {
  269. m_writer.writeAttribute(name.mid(4), atts.value(i));
  270. }
  271. }
  272. }
  273. Fb2XmlHandler::NodeHandler * Fb2SaveHandler::BodyHandler::NewTag(const QString &name, const QXmlAttributes &atts)
  274. {
  275. m_hasChild = true;
  276. QString tag = QString();
  277. switch (toKeyword(name)) {
  278. case Section : tag = atts.value("class") ; break;
  279. case Anchor : return new AnchorHandler(this, name, atts);
  280. case Image : return new ImageHandler(this, name, atts);
  281. case Parag : return new ParagHandler(this, name, atts);
  282. case Strong : tag = "strong" ; break;
  283. case Emphas : tag = "emphasis" ; break;
  284. case Strike : tag = "strikethrough" ; break;
  285. case Code : tag = "code" ; break;
  286. case Sub : tag = "sub" ; break;
  287. case Sup : tag = "sup" ; break;
  288. default: ;
  289. }
  290. return new BodyHandler(this, name, atts, tag);
  291. }
  292. void Fb2SaveHandler::BodyHandler::TxtTag(const QString &text)
  293. {
  294. m_writer.writeCharacters(text);
  295. }
  296. void Fb2SaveHandler::BodyHandler::EndTag(const QString &name)
  297. {
  298. Q_UNUSED(name);
  299. if (m_tag.isEmpty()) return;
  300. m_writer.writeEndElement(m_hasChild ? m_level : 0);
  301. }
  302. int Fb2SaveHandler::BodyHandler::nextLevel() const
  303. {
  304. return m_level ? m_level + 1 : 0;
  305. }
  306. //---------------------------------------------------------------------------
  307. // Fb2SaveHandler::RootHandler
  308. //---------------------------------------------------------------------------
  309. Fb2SaveHandler::RootHandler::RootHandler(Fb2SaveWriter &writer, const QString &name, const QXmlAttributes &atts)
  310. : BodyHandler(writer, name, atts, "FictionBook")
  311. {
  312. m_writer.writeAttribute("xmlns", "http://www.gribuser.ru/xml/fictionbook/2.0");
  313. m_writer.writeAttribute("xmlns:l", "http://www.w3.org/1999/xlink");
  314. }
  315. void Fb2SaveHandler::RootHandler::EndTag(const QString &name)
  316. {
  317. m_writer.writeFiles();
  318. BodyHandler::EndTag(name);
  319. }
  320. //---------------------------------------------------------------------------
  321. // Fb2SaveHandler::AnchorHandler
  322. //---------------------------------------------------------------------------
  323. Fb2SaveHandler::AnchorHandler::AnchorHandler(BodyHandler *parent, const QString &name, const QXmlAttributes &atts)
  324. : BodyHandler(parent, name, atts, "a")
  325. {
  326. QString href = Value(atts, "href");
  327. m_writer.writeAttribute("l:href", href);
  328. }
  329. //---------------------------------------------------------------------------
  330. // Fb2SaveHandler::ImageHandler
  331. //---------------------------------------------------------------------------
  332. Fb2SaveHandler::ImageHandler::ImageHandler(BodyHandler *parent, const QString &name, const QXmlAttributes &atts)
  333. : BodyHandler(parent, name, atts, "image")
  334. {
  335. QString src = Value(atts, "src");
  336. QString file = m_writer.getFileName(src);
  337. file.prepend('#');
  338. m_writer.writeAttribute("l:href", file);
  339. m_writer.writeEndElement(0);
  340. }
  341. //---------------------------------------------------------------------------
  342. // Fb2SaveHandler::ParagHandler
  343. //---------------------------------------------------------------------------
  344. Fb2SaveHandler::ParagHandler::ParagHandler(BodyHandler *parent, const QString &name, const QXmlAttributes &atts)
  345. : BodyHandler(parent, name, atts, "")
  346. , m_parent(parent->tag())
  347. , m_empty(true)
  348. {
  349. }
  350. Fb2XmlHandler::NodeHandler * Fb2SaveHandler::ParagHandler::NewTag(const QString &name, const QXmlAttributes &atts)
  351. {
  352. if (m_empty) start();
  353. return BodyHandler::NewTag(name, atts);
  354. }
  355. void Fb2SaveHandler::ParagHandler::TxtTag(const QString &text)
  356. {
  357. if (m_empty) {
  358. if (isWhiteSpace(text)) return;
  359. start();
  360. }
  361. BodyHandler::TxtTag(text);
  362. }
  363. void Fb2SaveHandler::ParagHandler::EndTag(const QString &name)
  364. {
  365. Q_UNUSED(name);
  366. if (m_empty) m_writer.writeStartElement("empty-line", m_level);
  367. m_writer.writeEndElement(0);
  368. }
  369. void Fb2SaveHandler::ParagHandler::start()
  370. {
  371. if (!m_empty) return;
  372. QString tag = m_parent == "stanza" ? "v" : "p";
  373. m_writer.writeStartElement(tag, m_level);
  374. m_empty = false;
  375. }
  376. //---------------------------------------------------------------------------
  377. // Fb2SaveHandler
  378. //---------------------------------------------------------------------------
  379. Fb2SaveHandler::Fb2SaveHandler(Fb2SaveWriter &writer)
  380. : Fb2HtmlHandler()
  381. , m_writer(writer)
  382. {
  383. }
  384. Fb2XmlHandler::NodeHandler * Fb2SaveHandler::CreateRoot(const QString &name, const QXmlAttributes &atts)
  385. {
  386. if (name == "body") return new RootHandler(m_writer, name, atts);
  387. m_error = QObject::tr("The tag <body> was not found.");
  388. return 0;
  389. }
  390. bool Fb2SaveHandler::save()
  391. {
  392. m_writer.writeStartDocument();
  393. QWebFrame * frame = m_writer.view().page()->mainFrame();
  394. static const QString javascript = FB2::read(":/js/export.js");
  395. frame->addToJavaScriptWindowObject("handler", this);
  396. frame->evaluateJavaScript(javascript);
  397. m_writer.writeEndDocument();
  398. return true;
  399. }