export: handle HTML resources

This commit is contained in:
Le Tan 2018-02-26 19:30:06 +08:00
parent b7e6301136
commit fe7c446e5f
11 changed files with 272 additions and 22 deletions

View File

@ -208,8 +208,16 @@ QWidget *VExportDialog::setupHTMLAdvancedSettings()
m_embedStyleCB->setToolTip(tr("Embed CSS styles in HTML file")); m_embedStyleCB->setToolTip(tr("Embed CSS styles in HTML file"));
m_embedStyleCB->setChecked(true); m_embedStyleCB->setChecked(true);
// Complete HTML.
m_completeHTMLCB = new QCheckBox(tr("Complete page"), this);
m_completeHTMLCB->setToolTip(tr("Export the whole web page along with pictures "
"which may not keep the HTML link structure of "
"the original page"));
m_completeHTMLCB->setChecked(true);
QFormLayout *advLayout = new QFormLayout(); QFormLayout *advLayout = new QFormLayout();
advLayout->addRow(m_embedStyleCB); advLayout->addRow(m_embedStyleCB);
advLayout->addRow(m_completeHTMLCB);
advLayout->setContentsMargins(0, 0, 0, 0); advLayout->setContentsMargins(0, 0, 0, 0);
@ -303,7 +311,8 @@ void VExportDialog::startExport()
m_renderStyleCB->currentData().toString(), m_renderStyleCB->currentData().toString(),
m_renderCodeBlockStyleCB->currentData().toString(), m_renderCodeBlockStyleCB->currentData().toString(),
&m_pageLayout, &m_pageLayout,
m_embedStyleCB->isChecked()); m_embedStyleCB->isChecked(),
m_completeHTMLCB->isChecked());
s_lastExportFormat = opt.m_format; s_lastExportFormat = opt.m_format;

View File

@ -47,7 +47,8 @@ struct ExportOption
const QString &p_renderStyle, const QString &p_renderStyle,
const QString &p_renderCodeBlockStyle, const QString &p_renderCodeBlockStyle,
QPageLayout *p_layout, QPageLayout *p_layout,
bool p_embedCssStyle) bool p_embedCssStyle,
bool p_completeHTML)
: m_source(p_source), : m_source(p_source),
m_format(p_format), m_format(p_format),
m_renderer(p_renderer), m_renderer(p_renderer),
@ -55,7 +56,8 @@ struct ExportOption
m_renderStyle(p_renderStyle), m_renderStyle(p_renderStyle),
m_renderCodeBlockStyle(p_renderCodeBlockStyle), m_renderCodeBlockStyle(p_renderCodeBlockStyle),
m_layout(p_layout), m_layout(p_layout),
m_embedCssStyle(p_embedCssStyle) m_embedCssStyle(p_embedCssStyle),
m_completeHTML(p_completeHTML)
{ {
} }
@ -71,6 +73,7 @@ struct ExportOption
QPageLayout *m_layout; QPageLayout *m_layout;
bool m_embedCssStyle; bool m_embedCssStyle;
bool m_completeHTML;
}; };
@ -186,6 +189,8 @@ private:
QCheckBox *m_embedStyleCB; QCheckBox *m_embedStyleCB;
QCheckBox *m_completeHTMLCB;;
VNotebook *m_notebook; VNotebook *m_notebook;
VDirectory *m_directory; VDirectory *m_directory;

View File

@ -40,19 +40,56 @@ if (typeof VEnableImageCaption == 'undefined') {
VEnableImageCaption = false; VEnableImageCaption = false;
} }
var getUrlScheme = function(url) {
var idx = url.indexOf(':');
if (idx > -1) {
return url.substr(0, idx);
} else {
return null;
}
};
var replaceCssUrl = function(baseUrl, match, p1, offset, str) {
if (getUrlScheme(p1)) {
return match;
}
var url = baseUrl + '/' + p1;
return "url(\"" + url + "\");";
};
var translateCssUrlToAbsolute = function(baseUrl, css) {
return css.replace(/\burl\(\"([^\"\)]+)\"\);/g, replaceCssUrl.bind(undefined, baseUrl));
};
var styleContent = function() { var styleContent = function() {
var styles = ""; var styles = "";
for (var i = 0; i < document.styleSheets.length; ++i) { for (var i = 0; i < document.styleSheets.length; ++i) {
var styleSheet = document.styleSheets[i]; var styleSheet = document.styleSheets[i];
if (styleSheet.cssRules) { if (styleSheet.cssRules) {
var baseUrl = null;
if (styleSheet.href) {
var scheme = getUrlScheme(styleSheet.href);
// We only translate local resources.
if (scheme == 'file' || scheme == 'qrc') {
baseUrl = styleSheet.href.substr(0, styleSheet.href.lastIndexOf('/'));
}
}
for (var j = 0; j < styleSheet.cssRules.length; ++j) { for (var j = 0; j < styleSheet.cssRules.length; ++j) {
styles = styles + styleSheet.cssRules[j].cssText + "\n"; var css = styleSheet.cssRules[j].cssText;
if (baseUrl) {
// Try to replace the url() with absolute path.
css = translateCssUrlToAbsolute(baseUrl, css);
}
styles = styles + css + "\n";
} }
} }
} }
return styles; return styles;
} };
var htmlContent = function() { var htmlContent = function() {
content.htmlContentCB("", styleContent(), placeholder.innerHTML); content.htmlContentCB("", styleContent(), placeholder.innerHTML);

View File

@ -74,17 +74,32 @@ QString VUtils::readFileFromDisk(const QString &filePath)
return fileText; return fileText;
} }
bool VUtils::writeFileToDisk(const QString &filePath, const QString &text) bool VUtils::writeFileToDisk(const QString &p_filePath, const QString &p_text)
{ {
QFile file(filePath); QFile file(p_filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "fail to open file" << filePath << "to write"; qWarning() << "fail to open file" << p_filePath << "to write";
return false; return false;
} }
QTextStream stream(&file); QTextStream stream(&file);
stream << text; stream << p_text;
file.close(); file.close();
qDebug() << "write file content:" << filePath; qDebug() << "write file content:" << p_filePath;
return true;
}
bool VUtils::writeFileToDisk(const QString &p_filePath, const QByteArray &p_data)
{
QFile file(p_filePath);
if (!file.open(QIODevice::WriteOnly)) {
qWarning() << "fail to open file" << p_filePath << "to write";
return false;
}
file.write(p_data);
file.close();
qDebug() << "write file content:" << p_filePath;
return true; return true;
} }

View File

@ -80,7 +80,9 @@ class VUtils
public: public:
static QString readFileFromDisk(const QString &filePath); static QString readFileFromDisk(const QString &filePath);
static bool writeFileToDisk(const QString &filePath, const QString &text); static bool writeFileToDisk(const QString &p_filePath, const QString &p_text);
static bool writeFileToDisk(const QString &p_filePath, const QByteArray &p_data);
static bool writeJsonToDisk(const QString &p_filePath, const QJsonObject &p_json); static bool writeJsonToDisk(const QString &p_filePath, const QJsonObject &p_json);

View File

@ -3,9 +3,13 @@
#include <QFileInfo> #include <QFileInfo>
#include <QObject> #include <QObject>
#include <QDebug> #include <QDebug>
#include <QDir>
#include <QUrl>
#include "vpalette.h" #include "vpalette.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vutils.h"
#include "vdownloader.h"
extern VPalette *g_palette; extern VPalette *g_palette;
@ -891,3 +895,32 @@ bool VWebUtils::fixXHtmlTags(QString &p_html)
return altered; return altered;
} }
QString VWebUtils::copyResource(const QUrl &p_url, const QString &p_folder) const
{
Q_ASSERT(!p_url.isRelative());
QDir dir(p_folder);
if (!dir.exists()) {
VUtils::makePath(p_folder);
}
QString file = p_url.isLocalFile() ? p_url.toLocalFile() : p_url.toString();
QString fileName = VUtils::fileNameFromPath(file);
fileName = VUtils::getFileNameWithSequence(p_folder, fileName, true);
QString targetFile = dir.absoluteFilePath(fileName);
bool succ = false;
if (p_url.scheme() == "https" || p_url.scheme() == "http") {
// Download it.
QByteArray data = VDownloader::downloadSync(p_url);
if (!data.isEmpty()) {
succ = VUtils::writeFileToDisk(targetFile, data);
}
} else if (QFileInfo::exists(file)) {
// Do a copy.
succ = VUtils::copyFile(file, targetFile, false);
}
return succ ? targetFile : QString();
}

View File

@ -21,6 +21,10 @@ public:
// Returns true if @p_html is modified. // Returns true if @p_html is modified.
bool alterHtmlAsTarget(const QUrl &p_baseUrl, QString &p_html, const QString &p_target) const; bool alterHtmlAsTarget(const QUrl &p_baseUrl, QString &p_html, const QString &p_target) const;
// Download or copy @p_url to @p_folder.
// Return the target file path on success or empty string on failure.
QString copyResource(const QUrl &p_url, const QString &p_folder) const;
private: private:
struct CopyTargetAction struct CopyTargetAction
{ {

View File

@ -1,5 +1,7 @@
#include "vdownloader.h" #include "vdownloader.h"
#include "utils/vutils.h"
VDownloader::VDownloader(QObject *parent) VDownloader::VDownloader(QObject *parent)
: QObject(parent) : QObject(parent)
{ {
@ -11,7 +13,6 @@ void VDownloader::handleDownloadFinished(QNetworkReply *reply)
{ {
data = reply->readAll(); data = reply->readAll();
reply->deleteLater(); reply->deleteLater();
qDebug() << "VDownloader receive" << reply->url().toString();
emit downloadFinished(data, reply->url().toString()); emit downloadFinished(data, reply->url().toString());
} }
@ -23,5 +24,29 @@ void VDownloader::download(const QUrl &p_url)
QNetworkRequest request(p_url); QNetworkRequest request(p_url);
webCtrl.get(request); webCtrl.get(request);
qDebug() << "VDownloader get" << p_url.toString(); }
QByteArray VDownloader::downloadSync(const QUrl &p_url)
{
QByteArray data;
if (!p_url.isValid()) {
return data;
}
bool finished = false;
QNetworkAccessManager nam;
connect(&nam, &QNetworkAccessManager::finished,
[&data, &finished](QNetworkReply *p_reply) {
data = p_reply->readAll();
p_reply->deleteLater();
finished = true;
});
nam.get(QNetworkRequest(p_url));
while (!finished) {
VUtils::sleepWait(100);
}
return data;
} }

View File

@ -15,6 +15,8 @@ public:
explicit VDownloader(QObject *parent = 0); explicit VDownloader(QObject *parent = 0);
void download(const QUrl &p_url); void download(const QUrl &p_url);
static QByteArray downloadSync(const QUrl &p_url);
signals: signals:
void downloadFinished(const QByteArray &data, const QString &url); void downloadFinished(const QByteArray &data, const QString &url);

View File

@ -3,6 +3,7 @@
#include <QDebug> #include <QDebug>
#include <QWidget> #include <QWidget>
#include <QWebChannel> #include <QWebChannel>
#include <QRegExp>
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vfile.h" #include "vfile.h"
@ -12,9 +13,12 @@
#include "vconstants.h" #include "vconstants.h"
#include "vmarkdownconverter.h" #include "vmarkdownconverter.h"
#include "vdocument.h" #include "vdocument.h"
#include "utils/vwebutils.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
extern VWebUtils *g_webUtils;
VExporter::VExporter(QWidget *p_parent) VExporter::VExporter(QWidget *p_parent)
: QObject(p_parent), : QObject(p_parent),
m_webViewer(NULL), m_webViewer(NULL),
@ -82,7 +86,8 @@ void VExporter::initWebViewer(VFile *p_file, const ExportOption &p_opt)
m_webDocument->setHtml(html); m_webDocument->setHtml(html);
} }
m_webViewer->setHtml(m_htmlTemplate, p_file->getBaseUrl()); m_baseUrl = p_file->getBaseUrl();
m_webViewer->setHtml(m_htmlTemplate, m_baseUrl);
} }
void VExporter::handleLogicsFinished() void VExporter::handleLogicsFinished()
@ -107,6 +112,7 @@ void VExporter::clearWebViewer()
delete m_webViewer; delete m_webViewer;
m_webViewer = NULL; m_webViewer = NULL;
m_webDocument = NULL; m_webDocument = NULL;
m_baseUrl.clear();
} }
bool VExporter::exportToPDF(VWebView *p_webViewer, bool VExporter::exportToPDF(VWebView *p_webViewer,
@ -122,16 +128,11 @@ bool VExporter::exportToPDF(VWebView *p_webViewer,
V_ASSERT(!p_filePath.isEmpty()); V_ASSERT(!p_filePath.isEmpty());
QFile file(p_filePath); if (!VUtils::writeFileToDisk(p_filePath, p_result)) {
if (!file.open(QFile::WriteOnly)) {
pdfPrinted = -1; pdfPrinted = -1;
return; return;
} }
file.write(p_result.data(), p_result.size());
file.close();
pdfPrinted = 1; pdfPrinted = 1;
}, p_layout); }, p_layout);
@ -201,6 +202,7 @@ bool VExporter::exportViaWebView(VFile *p_file,
exportRet = exportToHTML(m_webViewer, exportRet = exportToHTML(m_webViewer,
m_webDocument, m_webDocument,
p_opt.m_embedCssStyle, p_opt.m_embedCssStyle,
p_opt.m_completeHTML,
p_outputFile); p_outputFile);
break; break;
@ -237,6 +239,7 @@ exit:
bool VExporter::exportToHTML(VWebView *p_webViewer, bool VExporter::exportToHTML(VWebView *p_webViewer,
VDocument *p_webDocument, VDocument *p_webDocument,
bool p_embedCssStyle, bool p_embedCssStyle,
bool p_completeHTML,
const QString &p_filePath) const QString &p_filePath)
{ {
Q_UNUSED(p_webViewer); Q_UNUSED(p_webViewer);
@ -260,20 +263,40 @@ bool VExporter::exportToHTML(VWebView *p_webViewer,
return; return;
} }
QString resFolder = QFileInfo(p_filePath).completeBaseName() + "_files";
QString resFolderPath = QDir(VUtils::basePathFromPath(p_filePath)).filePath(resFolder);
qDebug() << "HTML files folder" << resFolderPath;
QString html(m_exportHtmlTemplate); QString html(m_exportHtmlTemplate);
if (!p_styleContent.isEmpty() && p_embedCssStyle) { if (!p_styleContent.isEmpty() && p_embedCssStyle) {
html.replace(HtmlHolder::c_styleHolder, p_styleContent); QString content(p_styleContent);
fixStyleResources(resFolderPath, content);
html.replace(HtmlHolder::c_styleHolder, content);
} }
if (!p_headContent.isEmpty()) { if (!p_headContent.isEmpty()) {
html.replace(HtmlHolder::c_headHolder, p_headContent); html.replace(HtmlHolder::c_headHolder, p_headContent);
} }
if (p_completeHTML) {
QString content(p_bodyContent);
fixBodyResources(m_baseUrl, resFolderPath, content);
html.replace(HtmlHolder::c_bodyHolder, content);
} else {
html.replace(HtmlHolder::c_bodyHolder, p_bodyContent); html.replace(HtmlHolder::c_bodyHolder, p_bodyContent);
}
file.write(html.toUtf8()); file.write(html.toUtf8());
file.close(); file.close();
// Delete empty resource folder.
QDir dir(resFolderPath);
if (dir.isEmpty()) {
dir.cdUp();
dir.rmdir(resFolder);
}
htmlExported = 1; htmlExported = 1;
}); });
@ -289,3 +312,80 @@ bool VExporter::exportToHTML(VWebView *p_webViewer,
return htmlExported == 1; return htmlExported == 1;
} }
bool VExporter::fixStyleResources(const QString &p_folder,
QString &p_html)
{
bool altered = false;
QRegExp reg("\\burl\\(\"((file|qrc):[^\"\\)]+)\"\\);");
int pos = 0;
while (pos < p_html.size()) {
int idx = p_html.indexOf(reg, pos);
if (idx == -1) {
break;
}
QString targetFile = g_webUtils->copyResource(QUrl(reg.cap(1)), p_folder);
if (targetFile.isEmpty()) {
pos = idx + reg.matchedLength();
} else {
// Replace the url string in html.
QString newUrl = QString("url(\"%1\");").arg(getResourceRelativePath(targetFile));
p_html.replace(idx, reg.matchedLength(), newUrl);
pos = idx + newUrl.size();
altered = true;
}
}
return altered;
}
bool VExporter::fixBodyResources(const QUrl &p_baseUrl,
const QString &p_folder,
QString &p_html)
{
bool altered = false;
if (p_baseUrl.isEmpty()) {
return altered;
}
QRegExp reg("<img ([^>]*)src=\"([^\"]+)\"([^>]*)>");
int pos = 0;
while (pos < p_html.size()) {
int idx = p_html.indexOf(reg, pos);
if (idx == -1) {
break;
}
if (reg.cap(2).isEmpty()) {
pos = idx + reg.matchedLength();
continue;
}
QUrl srcUrl(p_baseUrl.resolved(reg.cap(2)));
QString targetFile = g_webUtils->copyResource(srcUrl, p_folder);
if (targetFile.isEmpty()) {
pos = idx + reg.matchedLength();
} else {
// Replace the url string in html.
QString newUrl = QString("<img %1src=\"%2\"%3>").arg(reg.cap(1))
.arg(getResourceRelativePath(targetFile))
.arg(reg.cap(3));
p_html.replace(idx, reg.matchedLength(), newUrl);
pos = idx + newUrl.size();
altered = true;
}
}
return altered;
}
QString VExporter::getResourceRelativePath(const QString &p_file)
{
int idx = p_file.lastIndexOf('/');
int idx2 = p_file.lastIndexOf('/', idx - 1);
Q_ASSERT(idx > 0 && idx2 < idx);
return "." + p_file.mid(idx2);
}

View File

@ -3,6 +3,7 @@
#include <QObject> #include <QObject>
#include <QPageLayout> #include <QPageLayout>
#include <QUrl>
#include "dialog/vexportdialog.h" #include "dialog/vexportdialog.h"
@ -75,8 +76,22 @@ private:
bool exportToHTML(VWebView *p_webViewer, bool exportToHTML(VWebView *p_webViewer,
VDocument *p_webDocument, VDocument *p_webDocument,
bool p_embedCssStyle, bool p_embedCssStyle,
bool p_completeHTML,
const QString &p_filePath); const QString &p_filePath);
// Fix @p_html's resources like url("...") with "file" or "qrc" schema.
// Copy the resource to @p_folder and fix the url string.
static bool fixStyleResources(const QString &p_folder,
QString &p_html);
// Fix @p_html's resources like <img>.
// Copy the resource to @p_folder and fix the url string.
static bool fixBodyResources(const QUrl &p_baseUrl,
const QString &p_folder,
QString &p_html);
static QString getResourceRelativePath(const QString &p_file);
QPageLayout m_pageLayout; QPageLayout m_pageLayout;
// Will be allocated and free for each conversion. // Will be allocated and free for each conversion.
@ -84,6 +99,9 @@ private:
VDocument *m_webDocument; VDocument *m_webDocument;
// Base URL of VWebView.
QUrl m_baseUrl;
QString m_htmlTemplate; QString m_htmlTemplate;
// Template to hold the export HTML result. // Template to hold the export HTML result.