From 7e28af02d120d77625bc27e2c3c5a5aff7e63800 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Mon, 15 Dec 2025 19:39:07 +0300 Subject: [PATCH 1/7] Enhance TOC parsing and chapter selection logic - Update .gitignore to include additional paths - Refactor Epub::parseContentOpf to improve NCX item retrieval - Modify ContentOpfParser to store media type in ManifestItem - Implement rebuildVisibleSpineIndices in EpubReaderChapterSelectionScreen for better chapter navigation - Adjust rendering logic to handle empty chapter lists gracefully --- .gitignore | 2 + lib/Epub/Epub.cpp | 25 ++++- lib/Epub/Epub/parsers/ContentOpfParser.cpp | 13 ++- lib/Epub/Epub/parsers/ContentOpfParser.h | 9 +- .../EpubReaderChapterSelectionScreen.cpp | 103 +++++++++++++++--- .../EpubReaderChapterSelectionScreen.h | 3 + 6 files changed, 130 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 29bccdd..25b36fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .pio .idea .DS_Store +.vscode +lib/EpdFont/fontsrc diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index f9273a9..0f4b860 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -69,16 +69,31 @@ bool Epub::parseContentOpf(const std::string& contentOpfFilePath) { // Grab data from opfParser into epub title = opfParser.title; + constexpr const char* NCX_MEDIA_TYPE = "application/x-dtbncx+xml"; - if (opfParser.items.count("ncx")) { - tocNcxItem = opfParser.items.at("ncx"); - } else if (opfParser.items.count("ncxtoc")) { - tocNcxItem = opfParser.items.at("ncxtoc"); + for (const auto& [id, manifestItem] : opfParser.items) { + (void)id; + if (manifestItem.mediaType == NCX_MEDIA_TYPE) { + tocNcxItem = manifestItem.href; + break; + } + } + + if (tocNcxItem.empty() && !opfParser.tocNcxPath.empty()) { + tocNcxItem = opfParser.tocNcxPath; + } + + if (tocNcxItem.empty()) { + if (opfParser.items.count("ncx")) { + tocNcxItem = opfParser.items.at("ncx").href; + } else if (opfParser.items.count("ncxtoc")) { + tocNcxItem = opfParser.items.at("ncxtoc").href; + } } for (auto& spineRef : opfParser.spineRefs) { if (opfParser.items.count(spineRef)) { - spine.emplace_back(spineRef, opfParser.items.at(spineRef)); + spine.emplace_back(spineRef, opfParser.items.at(spineRef).href); } } diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp index 1dcdb04..8c70b16 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp +++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp @@ -96,17 +96,24 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (self->state == IN_MANIFEST && (strcmp(name, "item") == 0 || strcmp(name, "opf:item") == 0)) { std::string itemId; - std::string href; + ManifestItem item; for (int i = 0; atts[i]; i += 2) { if (strcmp(atts[i], "id") == 0) { itemId = atts[i + 1]; } else if (strcmp(atts[i], "href") == 0) { - href = self->baseContentPath + atts[i + 1]; + item.href = self->baseContentPath + atts[i + 1]; + } else if (strcmp(atts[i], "media-type") == 0) { + item.mediaType = atts[i + 1]; } } - self->items[itemId] = href; + if (!itemId.empty()) { + self->items[itemId] = item; + if (item.mediaType == "application/x-dtbncx+xml" && self->tocNcxPath.empty()) { + self->tocNcxPath = item.href; + } + } return; } diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.h b/lib/Epub/Epub/parsers/ContentOpfParser.h index 5da16bd..67000d9 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.h +++ b/lib/Epub/Epub/parsers/ContentOpfParser.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include "Epub.h" #include "expat.h" @@ -26,9 +28,14 @@ class ContentOpfParser final : public Print { static void endElement(void* userData, const XML_Char* name); public: + struct ManifestItem { + std::string href; + std::string mediaType; + }; + std::string title; std::string tocNcxPath; - std::map items; + std::map items; std::vector spineRefs; explicit ContentOpfParser(const std::string& baseContentPath, const size_t xmlSize) diff --git a/src/screens/EpubReaderChapterSelectionScreen.cpp b/src/screens/EpubReaderChapterSelectionScreen.cpp index 26688a4..99bff76 100644 --- a/src/screens/EpubReaderChapterSelectionScreen.cpp +++ b/src/screens/EpubReaderChapterSelectionScreen.cpp @@ -13,13 +13,41 @@ void EpubReaderChapterSelectionScreen::taskTrampoline(void* param) { self->displayTaskLoop(); } +void EpubReaderChapterSelectionScreen::rebuildVisibleSpineIndices() { + visibleSpineIndices.clear(); + if (!epub) { + return; + } + + const int spineCount = epub->getSpineItemsCount(); + visibleSpineIndices.reserve(spineCount); + for (int i = 0; i < spineCount; i++) { + if (epub->getTocIndexForSpineIndex(i) != -1) { + visibleSpineIndices.push_back(i); + } + } +} + void EpubReaderChapterSelectionScreen::onEnter() { if (!epub) { return; } renderingMutex = xSemaphoreCreateMutex(); - selectorIndex = currentSpineIndex; + rebuildVisibleSpineIndices(); + + selectorIndex = 0; + if (!visibleSpineIndices.empty()) { + for (size_t i = 0; i < visibleSpineIndices.size(); i++) { + if (visibleSpineIndices[i] == currentSpineIndex) { + selectorIndex = static_cast(i); + break; + } + } + if (selectorIndex >= static_cast(visibleSpineIndices.size())) { + selectorIndex = static_cast(visibleSpineIndices.size()) - 1; + } + } // Trigger first update updateRequired = true; @@ -40,6 +68,7 @@ void EpubReaderChapterSelectionScreen::onExit() { } vSemaphoreDelete(renderingMutex); renderingMutex = nullptr; + visibleSpineIndices.clear(); } void EpubReaderChapterSelectionScreen::handleInput() { @@ -51,22 +80,53 @@ void EpubReaderChapterSelectionScreen::handleInput() { const bool skipPage = inputManager.getHeldTime() > SKIP_PAGE_MS; if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { - onSelectSpineIndex(selectorIndex); - } else if (inputManager.wasPressed(InputManager::BTN_BACK)) { + if (!visibleSpineIndices.empty()) { + if (selectorIndex >= static_cast(visibleSpineIndices.size())) { + selectorIndex = static_cast(visibleSpineIndices.size()) - 1; + } + onSelectSpineIndex(visibleSpineIndices[selectorIndex]); + } + return; + } + + if (inputManager.wasPressed(InputManager::BTN_BACK)) { onGoBack(); - } else if (prevReleased) { + return; + } + + const int chapterCount = static_cast(visibleSpineIndices.size()); + if (chapterCount == 0) { + return; + } + + if (selectorIndex >= chapterCount) { + selectorIndex = chapterCount - 1; + } + + if (prevReleased) { if (skipPage) { - selectorIndex = - ((selectorIndex / PAGE_ITEMS - 1) * PAGE_ITEMS + epub->getSpineItemsCount()) % epub->getSpineItemsCount(); + const int totalPages = (chapterCount + PAGE_ITEMS - 1) / PAGE_ITEMS; + int currentPage = selectorIndex / PAGE_ITEMS; + currentPage = (currentPage + totalPages - 1) % totalPages; + selectorIndex = currentPage * PAGE_ITEMS; + if (selectorIndex >= chapterCount) { + selectorIndex = chapterCount - 1; + } } else { - selectorIndex = (selectorIndex + epub->getSpineItemsCount() - 1) % epub->getSpineItemsCount(); + selectorIndex = (selectorIndex + chapterCount - 1) % chapterCount; } updateRequired = true; } else if (nextReleased) { if (skipPage) { - selectorIndex = ((selectorIndex / PAGE_ITEMS + 1) * PAGE_ITEMS) % epub->getSpineItemsCount(); + const int totalPages = (chapterCount + PAGE_ITEMS - 1) / PAGE_ITEMS; + int currentPage = selectorIndex / PAGE_ITEMS; + currentPage = (currentPage + 1) % totalPages; + selectorIndex = currentPage * PAGE_ITEMS; + if (selectorIndex >= chapterCount) { + selectorIndex = chapterCount - 1; + } } else { - selectorIndex = (selectorIndex + 1) % epub->getSpineItemsCount(); + selectorIndex = (selectorIndex + 1) % chapterCount; } updateRequired = true; } @@ -90,17 +150,28 @@ void EpubReaderChapterSelectionScreen::renderScreen() { const auto pageWidth = renderer.getScreenWidth(); renderer.drawCenteredText(READER_FONT_ID, 10, "Select Chapter", true, BOLD); + const int chapterCount = static_cast(visibleSpineIndices.size()); + if (chapterCount == 0) { + renderer.drawText(UI_FONT_ID, 20, 60, "No chapters available"); + renderer.displayBuffer(); + return; + } + + if (selectorIndex >= chapterCount) { + selectorIndex = chapterCount - 1; + } + const auto pageStartIndex = selectorIndex / PAGE_ITEMS * PAGE_ITEMS; renderer.fillRect(0, 60 + (selectorIndex % PAGE_ITEMS) * 30 + 2, pageWidth - 1, 30); - for (int i = pageStartIndex; i < epub->getSpineItemsCount() && i < pageStartIndex + PAGE_ITEMS; i++) { - const int tocIndex = epub->getTocIndexForSpineIndex(i); + for (int i = pageStartIndex; i < chapterCount && i < pageStartIndex + PAGE_ITEMS; i++) { + const int spineIndex = visibleSpineIndices[i]; + const int tocIndex = epub->getTocIndexForSpineIndex(spineIndex); if (tocIndex == -1) { - renderer.drawText(UI_FONT_ID, 20, 60 + (i % PAGE_ITEMS) * 30, "Unnamed", i != selectorIndex); - } else { - auto item = epub->getTocItem(tocIndex); - renderer.drawText(UI_FONT_ID, 20 + (item.level - 1) * 15, 60 + (i % PAGE_ITEMS) * 30, item.title.c_str(), - i != selectorIndex); + continue; // Filtered chapters should not reach here, but skip defensively. } + auto item = epub->getTocItem(tocIndex); + renderer.drawText(UI_FONT_ID, 20 + (item.level - 1) * 15, 60 + (i % PAGE_ITEMS) * 30, item.title.c_str(), + i != selectorIndex); } renderer.displayBuffer(); diff --git a/src/screens/EpubReaderChapterSelectionScreen.h b/src/screens/EpubReaderChapterSelectionScreen.h index 8cac4cb..0ba41f0 100644 --- a/src/screens/EpubReaderChapterSelectionScreen.h +++ b/src/screens/EpubReaderChapterSelectionScreen.h @@ -5,6 +5,7 @@ #include #include +#include #include "Screen.h" @@ -15,11 +16,13 @@ class EpubReaderChapterSelectionScreen final : public Screen { int currentSpineIndex = 0; int selectorIndex = 0; bool updateRequired = false; + std::vector visibleSpineIndices; const std::function onGoBack; const std::function onSelectSpineIndex; static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); + void rebuildVisibleSpineIndices(); void renderScreen(); public: From 675038040072170364b9a84c5226a94e03fb3e4f Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Mon, 15 Dec 2025 20:00:54 +0300 Subject: [PATCH 2/7] Refactor TOC parsing logic to streamline cover image and NCX item retrieval --- lib/Epub/Epub.cpp | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index 675486f..a59b32f 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -70,30 +70,13 @@ bool Epub::parseContentOpf(const std::string& contentOpfFilePath) { // Grab data from opfParser into epub title = opfParser.title; if (!opfParser.coverItemId.empty() && opfParser.items.count(opfParser.coverItemId) > 0) { - coverImageItem = opfParser.items.at(opfParser.coverItemId); - } - constexpr const char* NCX_MEDIA_TYPE = "application/x-dtbncx+xml"; - - for (const auto& [id, manifestItem] : opfParser.items) { - (void)id; - if (manifestItem.mediaType == NCX_MEDIA_TYPE) { - tocNcxItem = manifestItem.href; - break; - } + coverImageItem = opfParser.items.at(opfParser.coverItemId).href; } - if (tocNcxItem.empty() && !opfParser.tocNcxPath.empty()) { + if (!opfParser.tocNcxPath.empty()) { tocNcxItem = opfParser.tocNcxPath; } - if (tocNcxItem.empty()) { - if (opfParser.items.count("ncx")) { - tocNcxItem = opfParser.items.at("ncx").href; - } else if (opfParser.items.count("ncxtoc")) { - tocNcxItem = opfParser.items.at("ncxtoc").href; - } - } - for (auto& spineRef : opfParser.spineRefs) { if (opfParser.items.count(spineRef)) { spine.emplace_back(spineRef, opfParser.items.at(spineRef).href); From 8d2f1de660574d8b465eba7638bd7f57a3fa0e45 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Tue, 16 Dec 2025 17:23:56 +0300 Subject: [PATCH 3/7] Revert visible chapter toc --- .../EpubReaderChapterSelectionScreen.cpp | 103 +++--------------- .../EpubReaderChapterSelectionScreen.h | 3 - 2 files changed, 16 insertions(+), 90 deletions(-) diff --git a/src/screens/EpubReaderChapterSelectionScreen.cpp b/src/screens/EpubReaderChapterSelectionScreen.cpp index 99bff76..26688a4 100644 --- a/src/screens/EpubReaderChapterSelectionScreen.cpp +++ b/src/screens/EpubReaderChapterSelectionScreen.cpp @@ -13,41 +13,13 @@ void EpubReaderChapterSelectionScreen::taskTrampoline(void* param) { self->displayTaskLoop(); } -void EpubReaderChapterSelectionScreen::rebuildVisibleSpineIndices() { - visibleSpineIndices.clear(); - if (!epub) { - return; - } - - const int spineCount = epub->getSpineItemsCount(); - visibleSpineIndices.reserve(spineCount); - for (int i = 0; i < spineCount; i++) { - if (epub->getTocIndexForSpineIndex(i) != -1) { - visibleSpineIndices.push_back(i); - } - } -} - void EpubReaderChapterSelectionScreen::onEnter() { if (!epub) { return; } renderingMutex = xSemaphoreCreateMutex(); - rebuildVisibleSpineIndices(); - - selectorIndex = 0; - if (!visibleSpineIndices.empty()) { - for (size_t i = 0; i < visibleSpineIndices.size(); i++) { - if (visibleSpineIndices[i] == currentSpineIndex) { - selectorIndex = static_cast(i); - break; - } - } - if (selectorIndex >= static_cast(visibleSpineIndices.size())) { - selectorIndex = static_cast(visibleSpineIndices.size()) - 1; - } - } + selectorIndex = currentSpineIndex; // Trigger first update updateRequired = true; @@ -68,7 +40,6 @@ void EpubReaderChapterSelectionScreen::onExit() { } vSemaphoreDelete(renderingMutex); renderingMutex = nullptr; - visibleSpineIndices.clear(); } void EpubReaderChapterSelectionScreen::handleInput() { @@ -80,53 +51,22 @@ void EpubReaderChapterSelectionScreen::handleInput() { const bool skipPage = inputManager.getHeldTime() > SKIP_PAGE_MS; if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { - if (!visibleSpineIndices.empty()) { - if (selectorIndex >= static_cast(visibleSpineIndices.size())) { - selectorIndex = static_cast(visibleSpineIndices.size()) - 1; - } - onSelectSpineIndex(visibleSpineIndices[selectorIndex]); - } - return; - } - - if (inputManager.wasPressed(InputManager::BTN_BACK)) { + onSelectSpineIndex(selectorIndex); + } else if (inputManager.wasPressed(InputManager::BTN_BACK)) { onGoBack(); - return; - } - - const int chapterCount = static_cast(visibleSpineIndices.size()); - if (chapterCount == 0) { - return; - } - - if (selectorIndex >= chapterCount) { - selectorIndex = chapterCount - 1; - } - - if (prevReleased) { + } else if (prevReleased) { if (skipPage) { - const int totalPages = (chapterCount + PAGE_ITEMS - 1) / PAGE_ITEMS; - int currentPage = selectorIndex / PAGE_ITEMS; - currentPage = (currentPage + totalPages - 1) % totalPages; - selectorIndex = currentPage * PAGE_ITEMS; - if (selectorIndex >= chapterCount) { - selectorIndex = chapterCount - 1; - } + selectorIndex = + ((selectorIndex / PAGE_ITEMS - 1) * PAGE_ITEMS + epub->getSpineItemsCount()) % epub->getSpineItemsCount(); } else { - selectorIndex = (selectorIndex + chapterCount - 1) % chapterCount; + selectorIndex = (selectorIndex + epub->getSpineItemsCount() - 1) % epub->getSpineItemsCount(); } updateRequired = true; } else if (nextReleased) { if (skipPage) { - const int totalPages = (chapterCount + PAGE_ITEMS - 1) / PAGE_ITEMS; - int currentPage = selectorIndex / PAGE_ITEMS; - currentPage = (currentPage + 1) % totalPages; - selectorIndex = currentPage * PAGE_ITEMS; - if (selectorIndex >= chapterCount) { - selectorIndex = chapterCount - 1; - } + selectorIndex = ((selectorIndex / PAGE_ITEMS + 1) * PAGE_ITEMS) % epub->getSpineItemsCount(); } else { - selectorIndex = (selectorIndex + 1) % chapterCount; + selectorIndex = (selectorIndex + 1) % epub->getSpineItemsCount(); } updateRequired = true; } @@ -150,28 +90,17 @@ void EpubReaderChapterSelectionScreen::renderScreen() { const auto pageWidth = renderer.getScreenWidth(); renderer.drawCenteredText(READER_FONT_ID, 10, "Select Chapter", true, BOLD); - const int chapterCount = static_cast(visibleSpineIndices.size()); - if (chapterCount == 0) { - renderer.drawText(UI_FONT_ID, 20, 60, "No chapters available"); - renderer.displayBuffer(); - return; - } - - if (selectorIndex >= chapterCount) { - selectorIndex = chapterCount - 1; - } - const auto pageStartIndex = selectorIndex / PAGE_ITEMS * PAGE_ITEMS; renderer.fillRect(0, 60 + (selectorIndex % PAGE_ITEMS) * 30 + 2, pageWidth - 1, 30); - for (int i = pageStartIndex; i < chapterCount && i < pageStartIndex + PAGE_ITEMS; i++) { - const int spineIndex = visibleSpineIndices[i]; - const int tocIndex = epub->getTocIndexForSpineIndex(spineIndex); + for (int i = pageStartIndex; i < epub->getSpineItemsCount() && i < pageStartIndex + PAGE_ITEMS; i++) { + const int tocIndex = epub->getTocIndexForSpineIndex(i); if (tocIndex == -1) { - continue; // Filtered chapters should not reach here, but skip defensively. + renderer.drawText(UI_FONT_ID, 20, 60 + (i % PAGE_ITEMS) * 30, "Unnamed", i != selectorIndex); + } else { + auto item = epub->getTocItem(tocIndex); + renderer.drawText(UI_FONT_ID, 20 + (item.level - 1) * 15, 60 + (i % PAGE_ITEMS) * 30, item.title.c_str(), + i != selectorIndex); } - auto item = epub->getTocItem(tocIndex); - renderer.drawText(UI_FONT_ID, 20 + (item.level - 1) * 15, 60 + (i % PAGE_ITEMS) * 30, item.title.c_str(), - i != selectorIndex); } renderer.displayBuffer(); diff --git a/src/screens/EpubReaderChapterSelectionScreen.h b/src/screens/EpubReaderChapterSelectionScreen.h index 0ba41f0..8cac4cb 100644 --- a/src/screens/EpubReaderChapterSelectionScreen.h +++ b/src/screens/EpubReaderChapterSelectionScreen.h @@ -5,7 +5,6 @@ #include #include -#include #include "Screen.h" @@ -16,13 +15,11 @@ class EpubReaderChapterSelectionScreen final : public Screen { int currentSpineIndex = 0; int selectorIndex = 0; bool updateRequired = false; - std::vector visibleSpineIndices; const std::function onGoBack; const std::function onSelectSpineIndex; static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); - void rebuildVisibleSpineIndices(); void renderScreen(); public: From b540fbc6fddd0ecb824fa19f265ab5edc8b20c08 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Tue, 16 Dec 2025 17:43:09 +0300 Subject: [PATCH 4/7] Refactor ContentOpfParser to simplify item handling and improve TOC parsing --- lib/Epub/Epub.cpp | 4 ++-- lib/Epub/Epub/parsers/ContentOpfParser.cpp | 16 ++++++++-------- lib/Epub/Epub/parsers/ContentOpfParser.h | 9 +-------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index a59b32f..45cd8f9 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -70,7 +70,7 @@ bool Epub::parseContentOpf(const std::string& contentOpfFilePath) { // Grab data from opfParser into epub title = opfParser.title; if (!opfParser.coverItemId.empty() && opfParser.items.count(opfParser.coverItemId) > 0) { - coverImageItem = opfParser.items.at(opfParser.coverItemId).href; + coverImageItem = opfParser.items.at(opfParser.coverItemId); } if (!opfParser.tocNcxPath.empty()) { @@ -79,7 +79,7 @@ bool Epub::parseContentOpf(const std::string& contentOpfFilePath) { for (auto& spineRef : opfParser.spineRefs) { if (opfParser.items.count(spineRef)) { - spine.emplace_back(spineRef, opfParser.items.at(spineRef).href); + spine.emplace_back(spineRef, opfParser.items.at(spineRef)); } } diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp index d760bb8..c27feaa 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp +++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp @@ -110,23 +110,23 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (self->state == IN_MANIFEST && (strcmp(name, "item") == 0 || strcmp(name, "opf:item") == 0)) { std::string itemId; - ManifestItem item; + std::string href; + std::string mediaType; for (int i = 0; atts[i]; i += 2) { if (strcmp(atts[i], "id") == 0) { itemId = atts[i + 1]; } else if (strcmp(atts[i], "href") == 0) { - item.href = self->baseContentPath + atts[i + 1]; + href = self->baseContentPath + atts[i + 1]; } else if (strcmp(atts[i], "media-type") == 0) { - item.mediaType = atts[i + 1]; + mediaType = atts[i + 1]; } } - if (!itemId.empty()) { - self->items[itemId] = item; - if (item.mediaType == "application/x-dtbncx+xml" && self->tocNcxPath.empty()) { - self->tocNcxPath = item.href; - } + self->items[itemId] = href; + + if (mediaType == "application/x-dtbncx+xml") { + self->tocNcxPath = href; } return; } diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.h b/lib/Epub/Epub/parsers/ContentOpfParser.h index cb887ab..cba4551 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.h +++ b/lib/Epub/Epub/parsers/ContentOpfParser.h @@ -2,8 +2,6 @@ #include #include -#include -#include #include "Epub.h" #include "expat.h" @@ -28,15 +26,10 @@ class ContentOpfParser final : public Print { static void endElement(void* userData, const XML_Char* name); public: - struct ManifestItem { - std::string href; - std::string mediaType; - }; - std::string title; std::string tocNcxPath; std::string coverItemId; - std::map items; + std::map items; std::vector spineRefs; explicit ContentOpfParser(const std::string& baseContentPath, const size_t xmlSize) From 2527f2f3a599875144a3777fd7fb3eb6e7c1180a Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Tue, 16 Dec 2025 18:14:45 +0300 Subject: [PATCH 5/7] Update lib/Epub/Epub/parsers/ContentOpfParser.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/Epub/Epub/parsers/ContentOpfParser.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp index c27feaa..de52f1c 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp +++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp @@ -126,7 +126,11 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name self->items[itemId] = href; if (mediaType == "application/x-dtbncx+xml") { - self->tocNcxPath = href; + if (self->tocNcxPath.empty()) { + self->tocNcxPath = href; + } else { + Serial.printf("[%lu] [COF] Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s\n", millis(), href.c_str()); + } } return; } From 7074deb9ad4afd74d26bc58de3860cab7eee6a6f Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Tue, 16 Dec 2025 18:18:05 +0300 Subject: [PATCH 6/7] clang format fix --- lib/Epub/Epub/parsers/ContentOpfParser.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp index de52f1c..02e9d31 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp +++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp @@ -129,7 +129,8 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name if (self->tocNcxPath.empty()) { self->tocNcxPath = href; } else { - Serial.printf("[%lu] [COF] Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s\n", millis(), href.c_str()); + Serial.printf("[%lu] [COF] Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s\n", millis(), + href.c_str()); } } return; From 8a99436e6ace6559149a27bb2d76dd81cd990a8f Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Tue, 16 Dec 2025 20:20:06 +0300 Subject: [PATCH 7/7] Refactor ContentOpfParser to use a constexpr for MEDIA_TYPE_NCX --- lib/Epub/Epub/parsers/ContentOpfParser.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp index 02e9d31..472d76c 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp +++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp @@ -3,6 +3,10 @@ #include #include +namespace { +constexpr const char MEDIA_TYPE_NCX[] = "application/x-dtbncx+xml"; +} + bool ContentOpfParser::setup() { parser = XML_ParserCreate(nullptr); if (!parser) { @@ -125,7 +129,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name self->items[itemId] = href; - if (mediaType == "application/x-dtbncx+xml") { + if (mediaType == MEDIA_TYPE_NCX) { if (self->tocNcxPath.empty()) { self->tocNcxPath = href; } else {