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