1 /************************************************************************
2 **
3 ** Copyright (C) 2019-2021 Kevin B. Hendricks, Stratford, Ontario, Canada
4 ** Copyright (C) 2012 John Schember <john@nachtimwald.com>
5 ** Copyright (C) 2012 Dave Heiland
6 **
7 ** This file is part of Sigil.
8 **
9 ** Sigil is free software: you can redistribute it and/or modify
10 ** it under the terms of the GNU General Public License as published by
11 ** the Free Software Foundation, either version 3 of the License, or
12 ** (at your option) any later version.
13 **
14 ** Sigil is distributed in the hope that it will be useful,
15 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ** GNU General Public License for more details.
18 **
19 ** You should have received a copy of the GNU General Public License
20 ** along with Sigil. If not, see <http://www.gnu.org/licenses/>.
21 **
22 *************************************************************************/
23
24 #include <QtCore/QFile>
25 #include <QtCore/QHashIterator>
26 #include <QtWidgets/QFileDialog>
27 #include <QtGui/QFont>
28 #include <QtWidgets/QPushButton>
29 #include <QtWidgets/QMessageBox>
30
31 #include "sigil_constants.h"
32 #include "sigil_exception.h"
33 #include "BookManipulation/FolderKeeper.h"
34 #include "Dialogs/ReportsWidgets/CharactersInHTMLFilesWidget.h"
35 #include "Misc/NumericItem.h"
36 #include "Misc/SettingsStore.h"
37 #include "Misc/Utility.h"
38 #include "Misc/XMLEntities.h"
39 #include "Parsers/GumboInterface.h"
40 #include "ResourceObjects/HTMLResource.h"
41
42 static const QString SETTINGS_GROUP = "reports";
43 static const QString DEFAULT_REPORT_FILE = "CharactersInHTMLFilesReport.csv";
44
CharactersInHTMLFilesWidget()45 CharactersInHTMLFilesWidget::CharactersInHTMLFilesWidget()
46 :
47 m_ItemModel(new QStandardItemModel),
48 m_LastDirSaved(QString()),
49 m_LastFileSaved(QString()),
50 m_PageLoaded(false)
51 {
52 ui.setupUi(this);
53 connectSignalsSlots();
54 }
55
~CharactersInHTMLFilesWidget()56 CharactersInHTMLFilesWidget::~CharactersInHTMLFilesWidget()
57 {
58 delete m_ItemModel;
59 }
60
CreateReport(QSharedPointer<Book> book)61 void CharactersInHTMLFilesWidget::CreateReport(QSharedPointer<Book> book)
62 {
63 m_Book = book;
64 SetupTable();
65 AddTableData();
66
67 for (int i = 0; i < ui.fileTree->header()->count(); i++) {
68 ui.fileTree->resizeColumnToContents(i);
69 }
70
71 ui.fileTree->sortByColumn(0, Qt::AscendingOrder);
72 }
73
SetupTable()74 void CharactersInHTMLFilesWidget::SetupTable()
75 {
76 m_ItemModel->clear();
77 QStringList header;
78 header.append(tr("Character"));
79 header.append(tr("Decimal"));
80 header.append(tr("Hexadecimal"));
81 header.append(tr("Entity Name"));
82 header.append(tr("Entity Description"));
83 m_ItemModel->setHorizontalHeaderLabels(header);
84 ui.fileTree->setSelectionBehavior(QAbstractItemView::SelectRows);
85 ui.fileTree->setModel(m_ItemModel);
86 ui.fileTree->header()->setSortIndicatorShown(true);
87 ui.fileTree->header()->setToolTip(
88 tr("<p>This is a list of the characters used in all HTML files.<p>")
89 );
90 }
91
AddTableData()92 void CharactersInHTMLFilesWidget::AddTableData()
93 {
94 const QList<HTMLResource *> html_resources = m_Book->GetHTMLResources();
95 QList <QChar> characters = GetDisplayedCharacters(html_resources);
96 QString all_characters;
97 foreach (QChar c, characters) {
98 all_characters.append(c);
99 }
100 ui.Characters->setText(all_characters);
101
102 foreach (QChar c, characters) {
103 // Write the table entries
104 QList<QStandardItem *> rowItems;
105 // Character
106 QStandardItem *item = new QStandardItem();
107 item->setText(QString(c));
108 rowItems << item;
109 // Decimal number
110 item = new QStandardItem();
111 ushort char_number = c.unicode();
112 item->setText(QString::number(char_number));
113 rowItems << item;
114 // Hex number
115 item = new QStandardItem();
116 QString hexadecimal;
117 hexadecimal.setNum(char_number,16);
118 item->setText(hexadecimal.toUpper());
119 rowItems << item;
120 // Name
121 item = new QStandardItem();
122 item->setText(XMLEntities::instance()->GetEntityName(char_number));
123 rowItems << item;
124 // Description
125 item = new QStandardItem();
126 item->setText(XMLEntities::instance()->GetEntityDescription(char_number));
127 rowItems << item;
128
129 for (int i = 0; i < rowItems.count(); i++) {
130 rowItems[i]->setEditable(false);
131 }
132
133 m_ItemModel->appendRow(rowItems);
134 }
135 }
136
GetDisplayedCharacters(QList<HTMLResource * > resources)137 QList <QChar> CharactersInHTMLFilesWidget::GetDisplayedCharacters(QList<HTMLResource *> resources)
138 {
139 QString all_characters;
140 foreach (HTMLResource *resource, resources) {
141 QString replaced_html = resource->GetText();
142 replaced_html = replaced_html.replace("<html>", "<html xmlns=\"http://www.w3.org/1999/xhtml\">");
143 QString version = "any_version";
144 GumboInterface gi = GumboInterface(replaced_html, version);
145 QString text = gi.get_body_text();
146 all_characters.append(text);
147 }
148
149 QMap <QChar, QChar> character_map;
150 foreach (const QChar c, all_characters) {
151 if (c != '\n') {
152 character_map.insert(c, c);
153 }
154 }
155 QList <QChar> character_list;
156 character_list = character_map.values();
157
158 return character_list;
159 }
160
161
FilterEditTextChangedSlot(const QString & text)162 void CharactersInHTMLFilesWidget::FilterEditTextChangedSlot(const QString &text)
163 {
164 const QString lowercaseText = text.toLower();
165 QStandardItem *root_item = m_ItemModel->invisibleRootItem();
166 QModelIndex parent_index;
167 // Hide rows that don't contain the filter text
168 int first_visible_row = -1;
169
170 for (int row = 0; row < root_item->rowCount(); row++) {
171 if (text.isEmpty() || root_item->child(row, 0)->text().toLower().contains(lowercaseText) ||
172 root_item->child(row, 1)->text().toLower().contains(lowercaseText) ||
173 root_item->child(row, 2)->text().toLower().contains(lowercaseText) ||
174 root_item->child(row, 3)->text().toLower().contains(lowercaseText) ||
175 root_item->child(row, 4)->text().toLower().contains(lowercaseText)) {
176 ui.fileTree->setRowHidden(row, parent_index, false);
177
178 if (first_visible_row == -1) {
179 first_visible_row = row;
180 }
181 } else {
182 ui.fileTree->setRowHidden(row, parent_index, true);
183 }
184 }
185
186 if (!text.isEmpty() && first_visible_row != -1) {
187 // Select the first non-hidden row
188 ui.fileTree->setCurrentIndex(root_item->child(first_visible_row, 0)->index());
189 } else {
190 // Clear current and selection, which clears preview image
191 ui.fileTree->setCurrentIndex(QModelIndex());
192 }
193 }
194
DoubleClick()195 void CharactersInHTMLFilesWidget::DoubleClick()
196 {
197 QModelIndex index = ui.fileTree->selectionModel()->selectedRows(0).first();
198 QString text = m_ItemModel->itemFromIndex(index)->text();
199 emit FindText(text);
200 }
201
Save()202 void CharactersInHTMLFilesWidget::Save()
203 {
204 QStringList report_info;
205 QStringList heading_row;
206
207 // Get headings
208 for (int col = 0; col < ui.fileTree->header()->count(); col++) {
209 QStandardItem *item = m_ItemModel->horizontalHeaderItem(col);
210 QString text = "";
211 if (item) {
212 text = item->text();
213 }
214 heading_row << text;
215 }
216 report_info << Utility::createCSVLine(heading_row);
217
218 // Get data from table
219 for (int row = 0; row < m_ItemModel->rowCount(); row++) {
220 QStringList data_row;
221 for (int col = 0; col < ui.fileTree->header()->count(); col++) {
222 QStandardItem *item = m_ItemModel->item(row, col);
223 QString text = "";
224 if (item) {
225 text = item->text();
226 }
227 data_row << text;
228 }
229 report_info << Utility::createCSVLine(data_row);
230 }
231
232 QString data = report_info.join('\n') + '\n';
233 // Save the file
234 ReadSettings();
235 QString filter_string = "*.csv;;*.txt;;*.*";
236 QString default_filter = "";
237 QString save_path = m_LastDirSaved + "/" + m_LastFileSaved;
238 QFileDialog::Options options = QFileDialog::Options();
239 #ifdef Q_OS_MAC
240 options = options | QFileDialog::DontUseNativeDialog;
241 #endif
242
243 QString destination = QFileDialog::getSaveFileName(this,
244 tr("Save Report As Comma Separated File"),
245 save_path,
246 filter_string,
247 &default_filter,
248 options);
249
250 if (destination.isEmpty()) {
251 return;
252 }
253
254 try {
255 Utility::WriteUnicodeTextFile(data, destination);
256 } catch (CannotOpenFile&) {
257 QMessageBox::warning(this, tr("Sigil"), tr("Cannot save report file."));
258 }
259
260 m_LastDirSaved = QFileInfo(destination).absolutePath();
261 m_LastFileSaved = QFileInfo(destination).fileName();
262 WriteSettings();
263 }
264
ReadSettings()265 void CharactersInHTMLFilesWidget::ReadSettings()
266 {
267 SettingsStore settings;
268 settings.beginGroup(SETTINGS_GROUP);
269 // Last file open
270 m_LastDirSaved = settings.value("last_dir_saved").toString();
271 m_LastFileSaved = settings.value("last_file_saved_characters_in_html").toString();
272
273 if (m_LastFileSaved.isEmpty()) {
274 m_LastFileSaved = "CharactersInHTMLFilesReport.csv";
275 }
276
277 settings.endGroup();
278 }
279
WriteSettings()280 void CharactersInHTMLFilesWidget::WriteSettings()
281 {
282 SettingsStore settings;
283 settings.beginGroup(SETTINGS_GROUP);
284 // Last file open
285 settings.setValue("last_dir_saved", m_LastDirSaved);
286 settings.setValue("last_file_saved_characters_html", m_LastFileSaved);
287 settings.endGroup();
288 }
289
290
connectSignalsSlots()291 void CharactersInHTMLFilesWidget::connectSignalsSlots()
292 {
293 connect(ui.Filter, SIGNAL(textChanged(QString)),
294 this, SLOT(FilterEditTextChangedSlot(QString)));
295 connect(ui.fileTree, SIGNAL(doubleClicked(const QModelIndex &)),
296 this, SLOT(DoubleClick()));
297 connect(ui.buttonBox->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SIGNAL(CloseDialog()));
298 connect(ui.buttonBox->button(QDialogButtonBox::Save), SIGNAL(clicked()), this, SLOT(Save()));
299 }
300