fb2save.cpp 13 KB

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