From 81dfbcd2bef1016868fe20a6c2b33b7c3b35ff7a Mon Sep 17 00:00:00 2001 From: Kevin Hendricks Date: Wed, 16 Oct 2024 11:45:06 -0400 Subject: [PATCH] allow more than one semantic per file --- src/CMakeLists.txt | 3 + src/Dialogs/SelectHyperlink.cpp | 2 +- src/Dialogs/SemanticTargetID.cpp | 90 ++++++++++++++++++++++ src/Dialogs/SemanticTargetID.h | 59 +++++++++++++++ src/Form_Files/SemanticTargetID.ui | 107 +++++++++++++++++++++++++++ src/MainUI/BookBrowser.cpp | 15 +++- src/MainUI/MainWindow.cpp | 74 ++++++++++-------- src/MainUI/OPFModel.cpp | 4 +- src/ResourceObjects/NavProcessor.cpp | 106 ++++++++++++++++++++------ src/ResourceObjects/NavProcessor.h | 17 +++-- src/ResourceObjects/OPFResource.cpp | 107 ++++++++++++++++++--------- src/ResourceObjects/OPFResource.h | 24 +++--- 12 files changed, 497 insertions(+), 111 deletions(-) create mode 100644 src/Dialogs/SemanticTargetID.cpp create mode 100644 src/Dialogs/SemanticTargetID.h create mode 100644 src/Form_Files/SemanticTargetID.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f409c834bb..3e2be2ee45 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -127,6 +127,8 @@ set( DIALOG_FILES Dialogs/SelectIndexTitle.h Dialogs/SelectFiles.cpp Dialogs/SelectFiles.h + Dialogs/SemanticTargetID.cpp + Dialogs/SemanticTargetID.h Dialogs/AddAutomateTool.cpp Dialogs/AddAutomateTool.h Dialogs/AddAutomatePlugin.cpp @@ -528,6 +530,7 @@ set( UI_FILES Form_Files/SelectId.ui Form_Files/SelectIndexTitle.ui Form_Files/SelectFiles.ui + Form_Files/SemanticTargetID.ui Form_Files/MetaEditor.ui Form_Files/AddAutomateTool.ui Form_Files/AddAutomatePlugin.ui diff --git a/src/Dialogs/SelectHyperlink.cpp b/src/Dialogs/SelectHyperlink.cpp index 25aa6003fc..e54dfdae51 100644 --- a/src/Dialogs/SelectHyperlink.cpp +++ b/src/Dialogs/SelectHyperlink.cpp @@ -66,7 +66,7 @@ void SelectHyperlink::SetList() { m_SelectHyperlinkModel->clear(); QStringList header; - header.append(tr("Targets in the Book")); + header.append(tr("Targets")); m_SelectHyperlinkModel->setHorizontalHeaderLabels(header); ui.list->setSelectionBehavior(QAbstractItemView::SelectRows); ui.list->setModel(m_SelectHyperlinkModel); diff --git a/src/Dialogs/SemanticTargetID.cpp b/src/Dialogs/SemanticTargetID.cpp new file mode 100644 index 0000000000..03375f469f --- /dev/null +++ b/src/Dialogs/SemanticTargetID.cpp @@ -0,0 +1,90 @@ +/************************************************************************ +** +** Copyright (C) 2024 Kevin B. Hendricks, Stratford Ontario Canada +** +** This file is part of Sigil. +** +** Sigil is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Sigil is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Sigil. If not, see . +** +*************************************************************************/ + +#include + +#include "Dialogs/SemanticTargetID.h" +#include "ResourceObjects/HTMLResource.h" +#include "Misc/SettingsStore.h" + +static QString SETTINGS_GROUP = "semantic_target_id"; + +SemanticTargetID::SemanticTargetID(HTMLResource *html_resource, QWidget *parent) + : + QDialog(parent), + m_SelectedText(""), + m_HTMLResource(html_resource) +{ + ui.setupUi(this); + connectSignalsSlots(); + ReadSettings(); + SetList(); +} + +void SemanticTargetID::SetList() +{ + QString BookPath = m_HTMLResource->GetRelativePath(); + QString xhtmlsrc = m_HTMLResource->GetText(); + QStringList ids = XhtmlDoc::GetAllDescendantIDs(xhtmlsrc); + ui.id->addItem(BookPath); + foreach(QString id, ids) { + ui.id->addItem(id); + } + ui.id->setEditText(BookPath); +} + + +QString SemanticTargetID::GetID() +{ + return m_SelectedText; +} + +void SemanticTargetID::SetSelectedText() +{ + QString tgt = ui.id->currentText(); + if (tgt == m_HTMLResource->GetRelativePath()) tgt = ""; + m_SelectedText = tgt; +} + +void SemanticTargetID::ReadSettings() +{ + SettingsStore settings; + settings.beginGroup(SETTINGS_GROUP); + QByteArray geometry = settings.value("geometry").toByteArray(); + if (!geometry.isNull()) { + restoreGeometry(geometry); + } + settings.endGroup(); +} + +void SemanticTargetID::WriteSettings() +{ + SetSelectedText(); + SettingsStore settings; + settings.beginGroup(SETTINGS_GROUP); + settings.setValue("geometry", saveGeometry()); + settings.endGroup(); +} + +void SemanticTargetID::connectSignalsSlots() +{ + connect(this, SIGNAL(accepted()), this, SLOT(WriteSettings())); +} diff --git a/src/Dialogs/SemanticTargetID.h b/src/Dialogs/SemanticTargetID.h new file mode 100644 index 0000000000..9b302512ec --- /dev/null +++ b/src/Dialogs/SemanticTargetID.h @@ -0,0 +1,59 @@ +/************************************************************************ +** +** Copyright (C) 2024 Kevin B. Hendricks, Stratford Ontario Canada +** +** This file is part of Sigil. +** +** Sigil is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Sigil is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Sigil. If not, see . +** +*************************************************************************/ + +#pragma once +#ifndef SEMANTICTARGETID_H +#define SEMANTICTARGETID_H + +#include + +#include "ResourceObjects/Resource.h" +#include "ResourceObjects/HTMLResource.h" +#include "ui_SemanticTargetID.h" + +class SemanticTargetID: public QDialog +{ + Q_OBJECT + +public: + SemanticTargetID(HTMLResource *html_resource, QWidget *parent = 0); + + void SetList(); + + QString GetID(); + +private slots: + void WriteSettings(); + +private: + void SetSelectedText(); + + void ReadSettings(); + void connectSignalsSlots(); + + QString m_SelectedText; + + HTMLResource *m_HTMLResource; + + Ui::SemanticTargetID ui; +}; + +#endif // SEMANTICTARGETID_H diff --git a/src/Form_Files/SemanticTargetID.ui b/src/Form_Files/SemanticTargetID.ui new file mode 100644 index 0000000000..6501d47503 --- /dev/null +++ b/src/Form_Files/SemanticTargetID.ui @@ -0,0 +1,107 @@ + + + SemanticTargetID + + + + 0 + 0 + 504 + 106 + + + + Target for Semantic + + + + + + + + Select an ID or use the default file level target for this Semantic setting. + + + ID: + + + + + + + + 0 + 0 + + + + Qt::WheelFocus + + + false + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SemanticTargetID + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SemanticTargetID + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/MainUI/BookBrowser.cpp b/src/MainUI/BookBrowser.cpp index 549eb724c6..133cf92d81 100644 --- a/src/MainUI/BookBrowser.cpp +++ b/src/MainUI/BookBrowser.cpp @@ -38,6 +38,7 @@ #include "BookManipulation/FolderKeeper.h" #include "Dialogs/DeleteFiles.h" #include "Dialogs/RenameTemplate.h" +#include "Dialogs/SemanticTargetID.h" #include "Dialogs/AddSemantics.h" #include "Dialogs/SelectFolder.h" #include "Dialogs/RERenamer.h" @@ -1686,6 +1687,12 @@ void BookBrowser::AddSemanticCode() } HTMLResource *html_resource = qobject_cast(resource); + QString tgt_id = ""; + SemanticTargetID getTarget(html_resource, this); + if (getTarget.exec() == QDialog::Accepted) { + tgt_id = getTarget.GetID(); + } + QString version = m_Book->GetConstOPF()->GetEpubVersion(); HTMLResource * nav_resource = NULL; if (version.startsWith('3')) { @@ -1697,9 +1704,9 @@ void BookBrowser::AddSemanticCode() if (version.startsWith('3')) { NavProcessor navproc(nav_resource); - current_code = navproc.GetLandmarkCodeForResource(resource); + current_code = navproc.GetLandmarkCodeForResource(resource, tgt_id); } else { - current_code = m_Book->GetOPF()->GetGuideSemanticCodeForResource(resource); + current_code = m_Book->GetOPF()->GetGuideSemanticCodeForResource(resource, tgt_id); } if (version.startsWith('3')) { @@ -1711,7 +1718,7 @@ void BookBrowser::AddSemanticCode() // do allow a user to change only the toc semantics on the nav resource if ((html_resource != nav_resource) || (new_code == "toc")) { NavProcessor navproc(nav_resource); - navproc.AddLandmarkCode(html_resource, new_code); + navproc.AddLandmarkCode(html_resource, new_code, true, tgt_id); m_OPFModel->Refresh(); emit BookContentModified(); } @@ -1722,7 +1729,7 @@ void BookBrowser::AddSemanticCode() if (addmeaning.exec() == QDialog::Accepted) { codes = addmeaning.GetSelectedEntries(); if (!codes.isEmpty()) { - m_Book->GetOPF()->AddGuideSemanticCode(html_resource, codes.at(0)); + m_Book->GetOPF()->AddGuideSemanticCode(html_resource, codes.at(0), true, tgt_id); m_OPFModel->Refresh(); emit BookContentModified(); } diff --git a/src/MainUI/MainWindow.cpp b/src/MainUI/MainWindow.cpp index ccfb83a6b4..69e0ca3dda 100644 --- a/src/MainUI/MainWindow.cpp +++ b/src/MainUI/MainWindow.cpp @@ -2548,25 +2548,23 @@ bool MainWindow::GenerateNCXGuideFromNav() // collect all of the current nav landmark codes NavProcessor navproc(nav_resource); - QHash nav_landmark_codes = navproc.GetLandmarkCodeForPaths(); - // Walk through all html resources and if they have a landmark code - // lookup its equivalent in the guide and set it if it exists - QList html_resources = GetAllHTMLResources(); - foreach(Resource * resource, html_resources) { - HTMLResource *html_resource = qobject_cast(resource); + QStringList landmark_info = navproc.GetAllLandmarkInfoByBookPath(); + + // Walk through the landmark info and convert to guide entries where possible + foreach(QString lm, landmark_info) { + HTMLResource * html_resource = nullptr; + QStringList parts = lm.split(QChar(30), Qt::KeepEmptyParts); + // parts(0)=bookpath, parts(1)=fragment, parts(2)=code, parts.at(3)=title + Resource* resource = m_Book->GetFolderKeeper()->GetResourceByBookPathNoThrow(parts.at(0)); + if (resource) html_resource = qobject_cast(resource); if (html_resource) { - QString respath = resource->GetRelativePath(); - if (nav_landmark_codes.contains(respath)) { - QString landmark_code = nav_landmark_codes[respath]; - QString guide_code = Landmarks::instance()->GuideLandMapping(landmark_code); - if (!guide_code.isEmpty()) { - m_Book->GetOPF()->AddGuideSemanticCode(html_resource, guide_code, false); - } + QString guide_code = Landmarks::instance()->GuideLandMapping(parts.at(2)); + if (!guide_code.isEmpty()) { + m_Book->GetOPF()->AddGuideSemanticCode(html_resource, guide_code, false, parts.at(1)); } } } - m_TableOfContents->Refresh(); m_BookBrowser->BookContentModified(); m_BookBrowser->Refresh(); @@ -2611,44 +2609,56 @@ void MainWindow::CreateIndex() } // get semantic (guide/landmark) information for all resources - QHash semantic_types; QString version = m_Book->GetOPF()->GetEpubVersion(); HTMLResource * nav_resource = m_Book->GetConstOPF()->GetNavResource(); + QHash semantic_codes; if (version.startsWith('3')) { if (nav_resource) { NavProcessor navproc(nav_resource); - semantic_types = navproc.GetLandmarkCodeForPaths(); + semantic_codes = navproc.GetLandmarkCodeForPaths(); + } else { + semantic_codes = m_Book->GetOPF()->GetSemanticCodeForPaths(); } } else { - semantic_types = m_Book->GetOPF()->GetSemanticCodeForPaths(); + semantic_codes = m_Book->GetOPF()->GetSemanticCodeForPaths(); } QStringList allow_index; allow_index << "bodymatter" << "chapter" << "conclusion" << "division" << "epilogue" << "introduction" << "part" << "preamble" << "prologue" << "subchapter" << "text" << "volume"; - HTMLResource *index_resource = NULL; + HTMLResource *index_resource = nullptr; QList html_resources; // Turn the list of Resources that are really HTMLResources to a real list // of HTMLResources stripping out any front or back matter + QList resources = GetAllHTMLResources(); foreach(Resource * resource, resources) { + QStringList codeslist; + bool keepit = false; HTMLResource *html_resource = qobject_cast(resource); - + if (html_resource) { + QString resource_path = html_resource->GetRelativePath(); - QString semantic_code; - if (semantic_types.contains(resource_path)) { - semantic_code = semantic_types[resource_path]; + + codeslist = semantic_codes.value(resource_path, QStringList()); + if (codeslist.isEmpty()) { + keepit = true; } - if (semantic_code.isEmpty() || allow_index.contains(semantic_code)) { - html_resources.append(html_resource); + + if (!keepit) { + foreach(QString code, allow_index) { + if (codeslist.contains(code)) keepit = true; + } } - + + if (keepit) html_resources.append(html_resource); + // Check if this is an existing index file. - if (semantic_code == "index") { + if (codeslist.contains("index")) { index_resource = html_resource; } else if (resource->Filename() == HTML_INDEX_FILE && html_resource == NULL) { index_resource = html_resource; @@ -2656,21 +2666,21 @@ void MainWindow::CreateIndex() } } - if (index_resource != NULL) { + if (index_resource) { QString msg = tr("An existing Index file has been found."); if (!ProceedToOverwrite(msg, index_resource->ShortPathName())) { - index_resource = NULL; + index_resource = nullptr; } } // Close the tab so the focus saving doesn't overwrite the text were // replacing in the resource. - if (index_resource != NULL) { + if (index_resource) { m_TabManager->CloseTabForResource(index_resource); } // Create an HTMLResource for the INDEX if it doesn't exist. - if (index_resource == NULL) { + if (index_resource == nullptr) { index_resource = m_Book->CreateEmptyHTMLFile(); index_resource->RenameTo(HTML_INDEX_FILE); } @@ -2695,8 +2705,8 @@ void MainWindow::CreateIndex() QString stylebookpath = styleresource->GetRelativePath(); // Write out the HTML index file. - IndexHTMLWriter index(indexbookpath, stylebookpath); - index_resource->SetText(index.WriteXML(version)); + IndexHTMLWriter makeindex(indexbookpath, stylebookpath); + index_resource->SetText(makeindex.WriteXML(version)); // Normally Setting a semantic on a resource that already has it set will remove the semantic. // Pass along toggle as false to disable this default behaviour diff --git a/src/MainUI/OPFModel.cpp b/src/MainUI/OPFModel.cpp index e0c5ddd653..62f4da5a8e 100644 --- a/src/MainUI/OPFModel.cpp +++ b/src/MainUI/OPFModel.cpp @@ -541,7 +541,7 @@ void OPFModel::InitializeModel() QList resources = m_Book->GetFolderKeeper()->GetResourceList(); QHash reading_order_all = m_Book->GetOPF()->GetReadingOrderAll(resources); QString version = m_Book->GetConstOPF()->GetEpubVersion(); - QHash semantic_type_all; + QHash semantic_type_all; QHash manifest_properties_all; SettingsStore ss; if (version.startsWith('3')) { @@ -572,7 +572,7 @@ void OPFModel::InitializeModel() } if (semantic_type_all.contains(path)) { - tooltip += " (" + semantic_type_all[path] + ")"; + tooltip += " (" + semantic_type_all[path].join(",") + ")"; } if (manifest_properties_all.contains(path)) { tooltip += " [" + manifest_properties_all[path] + "]"; diff --git a/src/ResourceObjects/NavProcessor.cpp b/src/ResourceObjects/NavProcessor.cpp index 2035acb107..93514ae5ce 100644 --- a/src/ResourceObjects/NavProcessor.cpp +++ b/src/ResourceObjects/NavProcessor.cpp @@ -38,6 +38,8 @@ static const QString NAV_PAGELIST_PATTERN = "\\s*\\s*"; static const QString NAV_TOC_PATTERN = "\\s*\\s*"; +static const QString _RS = QString(QChar(30)); // Ascii Record Separator + NavProcessor::NavProcessor(HTMLResource * nav_resource) : m_NavResource(nav_resource) { @@ -489,12 +491,12 @@ void NavProcessor::SetTOC(const QList & toclist) } -void NavProcessor::AddLandmarkCode(const Resource *resource, QString new_code, bool toggle) +void NavProcessor::AddLandmarkCode(const Resource *resource, QString new_code, bool toggle, QString tgt_id) { if (new_code.isEmpty()) return; QList landlist = GetLandmarks(); QWriteLocker locker(&m_NavResource->GetLock()); - int pos = GetResourceLandmarkPos(resource, landlist); + int pos = GetResourceLandmarkPos(resource, landlist, tgt_id); QString current_code; if (pos > -1) { NavLandmarkEntry le = landlist.at(pos); @@ -511,7 +513,11 @@ void NavProcessor::AddLandmarkCode(const Resource *resource, QString new_code, b NavLandmarkEntry le; le.etype = new_code; le.title = title; - le.href = ConvertBookPathToNavRelative(resource->GetRelativePath()); + QString href = ConvertBookPathToNavRelative(resource->GetRelativePath()); + if (!tgt_id.isEmpty()) { + href = href + "#" + tgt_id; + } + le.href = href; // special case the nav setting the toc semantic on itself if ((resource == m_NavResource) && (new_code == "toc")) { le.href = "#toc"; @@ -525,37 +531,59 @@ void NavProcessor::AddLandmarkCode(const Resource *resource, QString new_code, b SetLandmarks(landlist); } -void NavProcessor::RemoveLandmarkForResource(const Resource * resource) +void NavProcessor::RemoveLandmarkForResource(const Resource * resource, QString tgt_id) { QList landlist = GetLandmarks(); QWriteLocker locker(&m_NavResource->GetLock()); - int pos = GetResourceLandmarkPos(resource, landlist); - while((pos > -1) && !landlist.isEmpty()) { + int pos = GetResourceLandmarkPos(resource, landlist, tgt_id); + if (pos > -1) { landlist.removeAt(pos); - pos = GetResourceLandmarkPos(resource, landlist); } SetLandmarks(landlist); } -int NavProcessor::GetResourceLandmarkPos(const Resource *resource, const QList & landlist) +void NavProcessor::RemoveAllLandmarksForResource(const Resource * resource) { + QList landlist = GetLandmarks(); + QWriteLocker locker(&m_NavResource->GetLock()); QString resource_book_path = resource->GetRelativePath(); - for (int i=0; i < landlist.count(); ++i) { + QList positions_to_delete; + for (int i=0; i < landlist.size(); i++) { NavLandmarkEntry le = landlist.at(i); QString href = ConvertHREFToBookPath(le.href); QStringList parts = href.split('#', Qt::KeepEmptyParts); if (parts.at(0) == resource_book_path) { + positions_to_delete << i; + } + } + while(positions_to_delete.size() > 0) { + int pos = positions_to_delete.takeLast(); + landlist.removeAt(pos); + } + SetLandmarks(landlist); +} + +int NavProcessor::GetResourceLandmarkPos(const Resource *resource, const QList & landlist, QString tgt_id) +{ + QString resource_book_path = resource->GetRelativePath(); + if (!tgt_id.isEmpty()) { + resource_book_path = resource_book_path + "#" + tgt_id; + } + for (int i=0; i < landlist.count(); ++i) { + NavLandmarkEntry le = landlist.at(i); + QString href = ConvertHREFToBookPath(le.href); + if (href == resource_book_path) { return i; } } return -1; } -QString NavProcessor::GetLandmarkCodeForResource(const Resource *resource) +QString NavProcessor::GetLandmarkCodeForResource(const Resource *resource, QString tgt_id) { const QList landlist = GetLandmarks(); QReadLocker locker(&m_NavResource->GetLock()); - int pos = GetResourceLandmarkPos(resource, landlist); + int pos = GetResourceLandmarkPos(resource, landlist, tgt_id); QString etype; if (pos > -1) { NavLandmarkEntry le = landlist.at(pos); @@ -564,42 +592,74 @@ QString NavProcessor::GetLandmarkCodeForResource(const Resource *resource) return etype; } -QString NavProcessor::GetLandmarkNameForResource(const Resource *resource) +QString NavProcessor::GetLandmarkNameForResource(const Resource *resource, QString tgt_id) { QString name; - QString etype = GetLandmarkCodeForResource(resource); + QString etype = GetLandmarkCodeForResource(resource, tgt_id); if (!etype.isEmpty()) { name = Landmarks::instance()->GetName(etype); } return name; } -QHash NavProcessor::GetLandmarkNameForPaths() +// return the landmark info converted to bookpath +// with the following formant for each string in the returned list +// bookpath|fragment|code|title +QStringList NavProcessor::GetAllLandmarkInfoByBookPath() +{ + const QList landlist = GetLandmarks(); + QReadLocker locker(&m_NavResource->GetLock()); + QStringList landmark_info; + foreach(NavLandmarkEntry le, landlist) { + QString rec; + QString href = ConvertHREFToBookPath(le.href); + QString frag = ""; + QStringList parts = href.split('#', Qt::KeepEmptyParts); + if (parts.size() > 1) frag = parts.at(1); + rec = parts.at(0) + _RS + frag + _RS + le.etype + _RS + le.title; + landmark_info << rec; + } + return landmark_info; +} + +// create a hash of bookpaths to a list of all landmarks names contained therein +QHash NavProcessor::GetLandmarkNameForPaths() { const QList landlist = GetLandmarks(); QReadLocker locker(&m_NavResource->GetLock()); - QHash semantic_types; + QHash semantic_types; foreach(NavLandmarkEntry le, landlist) { QString href = ConvertHREFToBookPath(le.href); QStringList parts = href.split('#', Qt::KeepEmptyParts); QString etype = le.etype; - semantic_types[parts.at(0)] = Landmarks::instance()->GetName(etype); + QStringList names; + if (semantic_types.contains(parts.at(0))) { + names = semantic_types[parts.at(0)]; + } + names << Landmarks::instance()->GetName(etype); + semantic_types[parts.at(0)] = names; } return semantic_types; } -QHash NavProcessor::GetLandmarkCodeForPaths() +// create a hash of bookpaths to a list of all landmarks names contained therein +QHash NavProcessor::GetLandmarkCodeForPaths() { const QList landlist = GetLandmarks(); QReadLocker locker(&m_NavResource->GetLock()); - QHash semantic_types; + QHash semantic_codes; foreach(NavLandmarkEntry le, landlist) { - QString href = ConvertHREFToBookPath(le.href); - QStringList parts = href.split('#', Qt::KeepEmptyParts); - QString etype = le.etype; - semantic_types[parts.at(0)] = etype; + QString href = ConvertHREFToBookPath(le.href); + QStringList parts = href.split('#', Qt::KeepEmptyParts); + QString etype = le.etype; + QStringList codes; + if (semantic_codes.contains(parts.at(0))) { + codes = semantic_codes[parts.at(0)]; + } + codes << etype; + semantic_codes[parts.at(0)] = codes; } - return semantic_types; + return semantic_codes; } diff --git a/src/ResourceObjects/NavProcessor.h b/src/ResourceObjects/NavProcessor.h index 1aac3621d7..d3e24990ec 100755 --- a/src/ResourceObjects/NavProcessor.h +++ b/src/ResourceObjects/NavProcessor.h @@ -23,6 +23,7 @@ #include #include +#include #include "BookManipulation/Book.h" #include "BookManipulation/Headings.h" #include "ResourceObjects/HTMLResource.h" @@ -68,12 +69,14 @@ class NavProcessor TOCModel::TOCEntry GetRootTOCEntry(); // For Working with Landmarks - void AddLandmarkCode(const Resource * resource, QString new_code, bool toggle = true); - void RemoveLandmarkForResource(const Resource * resource); - QString GetLandmarkCodeForResource(const Resource * resource); - QString GetLandmarkNameForResource(const Resource * resource); - QHash GetLandmarkCodeForPaths(); - QHash GetLandmarkNameForPaths(); + void AddLandmarkCode(const Resource * resource, QString new_code, bool toggle = true, QString tgt_id=""); + void RemoveLandmarkForResource(const Resource * resource, QString tgt_id=""); + void RemoveAllLandmarksForResource(const Resource * resource); + QString GetLandmarkCodeForResource(const Resource * resource, QString tgt_id=""); + QString GetLandmarkNameForResource(const Resource * resource, QString tgt_id=""); + QHash GetLandmarkCodeForPaths(); + QHash GetLandmarkNameForPaths(); + QStringList GetAllLandmarkInfoByBookPath(); private: @@ -85,7 +88,7 @@ class NavProcessor void SetLandmarks(const QList & landlist); void SetPageList(const QList & pagelist); - int GetResourceLandmarkPos(const Resource * resource, const QList & landlist); + int GetResourceLandmarkPos(const Resource * resource, const QList & landlist, QString tgt_id=""); QList GetNodeTOC(GumboInterface & gi, const GumboNode* node, int lvl); QList HeadingWalker(const Headings::Heading & heading, int lvl); diff --git a/src/ResourceObjects/OPFResource.cpp b/src/ResourceObjects/OPFResource.cpp index 276329b7d6..34e9d918d4 100644 --- a/src/ResourceObjects/OPFResource.cpp +++ b/src/ResourceObjects/OPFResource.cpp @@ -773,11 +773,11 @@ void OPFResource::BulkRemoveResources(const QListresources) break; } } - RemoveGuideReferenceForResource(resource, p); + RemoveAllGuideReferencesForResource(resource, p); QString version = GetEpubVersion(); if (version.startsWith('3')) { NavProcessor navproc(GetNavResource()); - navproc.RemoveLandmarkForResource(resource); + navproc.RemoveAllLandmarksForResource(resource); } } if (pos > -1) { @@ -821,11 +821,11 @@ void OPFResource::RemoveResource(const Resource *resource) break; } } - RemoveGuideReferenceForResource(resource, p); + RemoveAllGuideReferencesForResource(resource, p); QString version = GetEpubVersion(); if (version.startsWith('3')) { NavProcessor navproc(GetNavResource()); - navproc.RemoveLandmarkForResource(resource); + navproc.RemoveAllLandmarksForResource(resource); } } if (pos > -1) { @@ -855,7 +855,7 @@ void OPFResource::ClearSemanticCodesInGuide() } -void OPFResource::AddGuideSemanticCode(HTMLResource *html_resource, QString new_code, bool toggle) +void OPFResource::AddGuideSemanticCode(HTMLResource *html_resource, QString new_code, bool toggle, QString tgt_id) { //first get primary book language QString lang = GetPrimaryBookLanguage(); @@ -863,23 +863,23 @@ void OPFResource::AddGuideSemanticCode(HTMLResource *html_resource, QString new_ QString source = CleanSource::ProcessXML(GetText(),"application/oebps-package+xml"); OPFParser p; p.parse(source); - QString current_code = GetGuideSemanticCodeForResource(html_resource, p); + QString current_code = GetGuideSemanticCodeForResource(html_resource, p, tgt_id); if ((current_code != new_code) || !toggle) { RemoveDuplicateGuideCodes(new_code, p); - SetGuideSemanticCodeForResource(new_code, html_resource, p, lang); + SetGuideSemanticCodeForResource(new_code, html_resource, p, lang, tgt_id); } else { // If the current code is the same as the new one, // we toggle it off. - RemoveGuideReferenceForResource(html_resource, p); + RemoveGuideReferenceForResource(html_resource, p, tgt_id); } UpdateText(p); } -QString OPFResource::GetGuideSemanticCodeForResource(const Resource *resource, const OPFParser &p) const +QString OPFResource::GetGuideSemanticCodeForResource(const Resource *resource, const OPFParser &p, QString tgt_id) const { QString gtype; - int pos = GetGuideReferenceForResourcePos(resource, p); + int pos = GetGuideReferenceForResourcePos(resource, p, tgt_id); if (pos > -1) { GuideEntry ge = p.m_guide.at(pos); gtype = ge.m_type; @@ -887,14 +887,16 @@ QString OPFResource::GetGuideSemanticCodeForResource(const Resource *resource, c return gtype; } -int OPFResource::GetGuideReferenceForResourcePos(const Resource *resource, const OPFParser &p) const +int OPFResource::GetGuideReferenceForResourcePos(const Resource *resource, const OPFParser &p, QString tgt_id) const { - QString href_to_resource_from_opf = Utility::URLEncodePath(GetRelativePathToResource(resource)); + QString href_to_resource_from_opf = Utility::URLEncodePath(GetRelativePathToResource(resource)); for (int i=0; i < p.m_guide.count(); ++i) { GuideEntry ge = p.m_guide.at(i); QString href = ge.m_href; - QStringList parts = href.split('#', Qt::KeepEmptyParts); - if (parts.at(0) == href_to_resource_from_opf) { + if (!tgt_id.isEmpty()){ + href_to_resource_from_opf = href_to_resource_from_opf + "#" + tgt_id; + } + if (href == href_to_resource_from_opf) { return i; } } @@ -924,15 +926,35 @@ void OPFResource::RemoveDuplicateGuideCodes(QString code, OPFParser& p) } } -void OPFResource::RemoveGuideReferenceForResource(const Resource *resource, OPFParser& p) +void OPFResource::RemoveGuideReferenceForResource(const Resource *resource, OPFParser& p, QString tgt_id) +{ + if (p.m_guide.isEmpty()) return; + int pos = GetGuideReferenceForResourcePos(resource, p, tgt_id); + while((pos > -1) && (!p.m_guide.isEmpty())) { + p.m_guide.removeAt(pos); + pos = GetGuideReferenceForResourcePos(resource, p, tgt_id); + } +} + +void OPFResource::RemoveAllGuideReferencesForResource(const Resource *resource, OPFParser& p) { // if guide hrefs use fragments, the same resource may be there in multiple // guide entries. Since resource being deleted, remove them all if (p.m_guide.isEmpty()) return; - int pos = GetGuideReferenceForResourcePos(resource, p); - while((pos > -1) && (!p.m_guide.isEmpty())) { + QString href_to_resource_from_opf = Utility::URLEncodePath(GetRelativePathToResource(resource)); + QList positions_to_delete; + for (int i=0; i < p.m_guide.count(); ++i) { + GuideEntry ge = p.m_guide.at(i); + QString href = ge.m_href; + QStringList parts = href.split('#', Qt::KeepEmptyParts); + if (parts.at(0) == href_to_resource_from_opf) { + positions_to_delete << i; + } + } + // handle deletions in reverse order to maintain position info + while(positions_to_delete.size() > 0) { + int pos = positions_to_delete.takeLast(); p.m_guide.removeAt(pos); - pos = GetGuideReferenceForResourcePos(resource, p); } } @@ -1001,10 +1023,11 @@ void OPFResource::UpdateGuideAfterMerge(QList &merged_resources, QHas UpdateText(p); } -void OPFResource::SetGuideSemanticCodeForResource(QString code, const Resource *resource, OPFParser& p, const QString &lang) +void OPFResource::SetGuideSemanticCodeForResource(QString code, const Resource *resource, + OPFParser& p, const QString &lang, QString tgt_id) { if (code.isEmpty()) return; - int pos = GetGuideReferenceForResourcePos(resource, p); + int pos = GetGuideReferenceForResourcePos(resource, p, tgt_id); QString title = GuideItems::instance()->GetTitle(code, lang); if (pos > -1) { GuideEntry ge = p.m_guide.at(pos); @@ -1015,63 +1038,79 @@ void OPFResource::SetGuideSemanticCodeForResource(QString code, const Resource * GuideEntry ge; ge.m_type = code; ge.m_title = title; - ge.m_href = Utility::URLEncodePath(GetRelativePathToResource(resource)); + QString href = Utility::URLEncodePath(GetRelativePathToResource(resource)); + if (!tgt_id.isEmpty()) { + href = href + "#" + tgt_id; + } + ge.m_href = href; p.m_guide.append(ge); } } -QString OPFResource::GetGuideSemanticCodeForResource(const Resource *resource) const +QString OPFResource::GetGuideSemanticCodeForResource(const Resource *resource, QString tgt_id) const { QReadLocker locker(&GetLock()); QString source = CleanSource::ProcessXML(GetText(),"application/oebps-package+xml"); OPFParser p; p.parse(source); - return GetGuideSemanticCodeForResource(resource, p); + return GetGuideSemanticCodeForResource(resource, p, tgt_id); } -QString OPFResource::GetGuideSemanticNameForResource(Resource *resource) +QString OPFResource::GetGuideSemanticNameForResource(Resource *resource, QString tgt_id) { - return GuideItems::instance()->GetName(GetGuideSemanticCodeForResource(resource)); + return GuideItems::instance()->GetName(GetGuideSemanticCodeForResource(resource, tgt_id)); } -QHash OPFResource::GetSemanticCodeForPaths() +// returns a hash of bookpath to a list of all semantic codes that exist in that file +QHash OPFResource::GetSemanticCodeForPaths() { QReadLocker locker(&GetLock()); QString source = CleanSource::ProcessXML(GetText(),"application/oebps-package+xml"); OPFParser p; p.parse(source); - QHash semantic_types; + QHash semantic_codes; foreach(GuideEntry ge, p.m_guide) { QString href = ge.m_href; QStringList parts = href.split('#', Qt::KeepEmptyParts); QString apath = Utility::URLDecodePath(parts.at(0)); QString bkpath = Utility::buildBookPath(apath, GetFolder()); QString gtype = ge.m_type; - semantic_types[bkpath] = gtype; + QStringList codes; + if (semantic_codes.contains(bkpath)) { + codes = semantic_codes[bkpath]; + } + codes << gtype; + semantic_codes[bkpath] = codes; } - return semantic_types; + return semantic_codes; } -QHash OPFResource::GetGuideSemanticNameForPaths() +// returns a hash of bookpath to a list of all semantic names that exist in that file +QHash OPFResource::GetGuideSemanticNameForPaths() { QReadLocker locker(&GetLock()); QString source = CleanSource::ProcessXML(GetText(),"application/oebps-package+xml"); OPFParser p; p.parse(source); - QHash semantic_types; + QHash semantic_types; foreach(GuideEntry ge, p.m_guide) { QString href = ge.m_href; QStringList parts = href.split('#', Qt::KeepEmptyParts); QString gtype = ge.m_type; QString apath = Utility::URLDecodePath(parts.at(0)); QString bkpath = Utility::buildBookPath(apath, GetFolder()); - semantic_types[bkpath] = GuideItems::instance()->GetName(gtype); + QStringList names; + if (semantic_types.contains(bkpath)) { + names = semantic_types[bkpath]; + } + names << GuideItems::instance()->GetName(gtype); + semantic_types[bkpath] = names; } // Cover image semantics don't use reference @@ -1082,7 +1121,9 @@ QHash OPFResource::GetGuideSemanticNameForPaths() ManifestEntry man = p.m_manifest.at(p.m_idpos[cover_id]); QString apath = Utility::URLDecodePath(man.m_href); QString bkpath = Utility::buildBookPath(apath, GetFolder()); - semantic_types[bkpath] = GuideItems::instance()->GetName("cover"); + QStringList al; + al << GuideItems::instance()->GetName("cover"); + semantic_types[bkpath] = al; } return semantic_types; } diff --git a/src/ResourceObjects/OPFResource.h b/src/ResourceObjects/OPFResource.h index 634186fb49..869ae61091 100644 --- a/src/ResourceObjects/OPFResource.h +++ b/src/ResourceObjects/OPFResource.h @@ -26,6 +26,9 @@ #define OPFRESOURCE_H #include +#include +#include +#include #include "Misc/GuideItems.h" #include "ResourceObjects/XMLResource.h" #include "Parsers/OPFParser.h" @@ -64,10 +67,10 @@ class OPFResource : public XMLResource virtual bool LoadFromDisk(); - QString GetGuideSemanticCodeForResource(const Resource *resource) const; - QString GetGuideSemanticNameForResource(Resource *resource); - QHash GetSemanticCodeForPaths(); - QHash GetGuideSemanticNameForPaths(); + QString GetGuideSemanticCodeForResource(const Resource *resource, QString tgt_id="") const; + QString GetGuideSemanticNameForResource(Resource *resource, QString tgt_id=""); + QHash GetSemanticCodeForPaths(); + QHash GetGuideSemanticNameForPaths(); void ClearSemanticCodesInGuide(); @@ -167,7 +170,7 @@ public slots: void RemoveResource(const Resource *resource); void BulkRemoveResources(const QListresources); - void AddGuideSemanticCode(HTMLResource *html_resource, QString code, bool toggle = true); + void AddGuideSemanticCode(HTMLResource *html_resource, QString code, bool toggle = true, QString tgt_id=""); void SetResourceAsCoverImage(ImageResource *image_resource); @@ -216,13 +219,16 @@ public slots: int GetMainIdentifier(const OPFParser &p) const; // CAN BE -1 which means no reference for resource - int GetGuideReferenceForResourcePos(const Resource *resource, const OPFParser &p) const; + int GetGuideReferenceForResourcePos(const Resource *resource, const OPFParser &p, QString tgt_id="") const; - void RemoveGuideReferenceForResource(const Resource *resource, OPFParser &p); + void RemoveGuideReferenceForResource(const Resource *resource, OPFParser &p, QString tgt_id=""); + + void RemoveAllGuideReferencesForResource(const Resource *resource, OPFParser& p); - QString GetGuideSemanticCodeForResource(const Resource *resource, const OPFParser &p) const; + QString GetGuideSemanticCodeForResource(const Resource *resource, const OPFParser &p, QString tgt_id="") const; - void SetGuideSemanticCodeForResource(QString code, const Resource *resource, OPFParser &p, const QString &lang); + void SetGuideSemanticCodeForResource(QString code, const Resource *resource, OPFParser &p, + const QString &lang, QString tgt_id=""); void RemoveDuplicateGuideCodes(QString code, OPFParser &p);