Files
Crosspoint/lib/KOReaderSync/ChapterXPathIndexer.cpp
T
2026-04-01 10:38:02 +02:00

104 lines
3.3 KiB
C++

#include "ChapterXPathIndexer.h"
#include "ChapterXPathForwardMapper.h"
#include "ChapterXPathIndexerInternal.h"
#include "ChapterXPathReverseMapper.h"
#include <Logging.h>
#include <cctype>
#include <cstdlib>
#include <limits>
#include <string>
using namespace ChapterXPathIndexerInternal;
// Public facade used by ProgressMapper. It intentionally stays thin and delegates
// heavy parsing/mapping work to the internal forward/reverse modules.
std::string ChapterXPathIndexer::findXPathForProgress(const std::shared_ptr<Epub>& epub, const int spineIndex,
const float intraSpineProgress) {
return findXPathForProgressInternal(epub, spineIndex, intraSpineProgress);
}
bool ChapterXPathIndexer::findProgressForXPath(const std::shared_ptr<Epub>& epub, const int spineIndex,
const std::string& xpath, float& outIntraSpineProgress,
bool& outExactMatch) {
return findProgressForXPathInternal(epub, spineIndex, xpath, outIntraSpineProgress, outExactMatch);
}
bool ChapterXPathIndexer::tryExtractSpineIndexFromXPath(const std::string& xpath, int& outSpineIndex) {
outSpineIndex = -1;
if (xpath.empty()) {
return false;
}
const std::string normalized = normalizeXPath(xpath);
const std::string key = "/docfragment[";
const size_t pos = normalized.find(key);
if (pos == std::string::npos) {
LOG_DBG("KOX", "No DocFragment in xpath: '%s'", xpath.c_str());
return false;
}
const size_t start = pos + key.size();
size_t end = start;
while (end < normalized.size() && std::isdigit(static_cast<unsigned char>(normalized[end]))) {
end++;
}
if (end == start || end >= normalized.size() || normalized[end] != ']') {
return false;
}
const std::string value = normalized.substr(start, end - start);
const long parsed = std::strtol(value.c_str(), nullptr, 10);
// XPath uses 1-based predicates; internal spine indexing is 0-based.
if (parsed < 1 || parsed > std::numeric_limits<int>::max()) {
return false;
}
outSpineIndex = static_cast<int>(parsed) - 1;
return true;
}
bool ChapterXPathIndexer::tryExtractParagraphIndexFromXPath(const std::string& xpath, uint16_t& outParagraphIndex) {
outParagraphIndex = 0;
if (xpath.empty()) {
return false;
}
const std::string normalized = normalizeXPath(xpath);
const std::string bodyKey = "/body";
size_t secondBody = normalized.find(bodyKey);
if (secondBody != std::string::npos) {
secondBody = normalized.find(bodyKey, secondBody + bodyKey.size());
}
const std::string pKey = "/p[";
const size_t pos = normalized.find(pKey, secondBody != std::string::npos ? secondBody : 0);
if (pos == std::string::npos) {
return false;
}
const size_t start = pos + pKey.size();
size_t end = start;
while (end < normalized.size() && std::isdigit(static_cast<unsigned char>(normalized[end]))) {
end++;
}
if (end == start || end >= normalized.size() || normalized[end] != ']') {
return false;
}
const long parsed = std::strtol(normalized.substr(start, end - start).c_str(), nullptr, 10);
// Paragraph index is preserved as 1-based to match XPath p[N] convention.
if (parsed < 1 || parsed > UINT16_MAX) {
return false;
}
outParagraphIndex = static_cast<uint16_t>(parsed);
return true;
}