fb2save.cpp 13 KB

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