From 420110b733fb3a461285c403b7533aafdc590a8c Mon Sep 17 00:00:00 2001 From: jpirnay Date: Sat, 28 Mar 2026 11:01:22 +0100 Subject: [PATCH] Fix index issue --- lib/KOReaderSync/ChapterXPathIndexer.cpp | 33 ++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/KOReaderSync/ChapterXPathIndexer.cpp b/lib/KOReaderSync/ChapterXPathIndexer.cpp index e4d2f862..c6f2b60e 100644 --- a/lib/KOReaderSync/ChapterXPathIndexer.cpp +++ b/lib/KOReaderSync/ChapterXPathIndexer.cpp @@ -45,7 +45,8 @@ size_t countVisibleBytes(const XML_Char* text, const int len) { } // Canonicalize a KOReader XPath for comparison: -// - remove whitespace, lowercase, strip /text() with optional char offset. +// - remove whitespace, lowercase, strip /text() with optional char offset, +// strip trailing .N text-child-index suffix on the last segment (e.g. br.0 → br). std::string normalizeXPath(const std::string& input) { if (input.empty()) { return ""; @@ -71,6 +72,25 @@ std::string normalizeXPath(const std::string& input) { } } + // Strip trailing .N text-child-index suffix on the last path segment + // (KOReader notation, e.g. /div/br.0 → /div/br). + const size_t lastSlash = out.rfind('/'); + if (lastSlash != std::string::npos) { + const size_t dotPos = out.find('.', lastSlash + 1); + if (dotPos != std::string::npos && dotPos + 1 < out.size()) { + bool allDigits = true; + for (size_t i = dotPos + 1; i < out.size(); i++) { + if (!std::isdigit(static_cast(out[i]))) { + allDigits = false; + break; + } + } + if (allDigits) { + out.erase(dotPos); + } + } + } + while (!out.empty() && out.back() == '/') { out.pop_back(); } @@ -450,7 +470,16 @@ void XMLCALL revStart(void* ud, const XML_Char* name, const XML_Char**) { static_cast(ud)->pushElement(name); } -void XMLCALL revEnd(void* ud, const XML_Char*) { static_cast(ud)->popElement(); } +void XMLCALL revEnd(void* ud, const XML_Char*) { + auto* state = static_cast(ud); + // Textless elements (e.g.
) never trigger onChar, so check for a match + // before popping. The byte offset recorded is the text seen so far, which + // is the correct position ("just before this element"). + if (!state->stack.empty() && !state->stack.back().hasText) { + state->checkMatch(); + } + state->popElement(); +} void XMLCALL revChar(void* ud, const XML_Char* text, const int len) { static_cast(ud)->onChar(text, len);