fix: Switch to xpath map for paragraph level syncing in KOSync (#1686)

Switch KOReader sync progress mapping from chapter matching to
XPath-based mapping.

- resolves KOReader positions using real XHTML ancestry paths
- supports paragraph-based upload mapping with text offsets
where needed
- passes the current paragraph index into sync so uploads map
back to KOReader more accurately

No HTTP client changes are included. No reader-state or resume-flow
changes are included.

---------

Co-authored-by: jpirnay <jens@pirnay.com>
This commit is contained in:
Justin Mitchell
2026-04-20 12:47:41 -05:00
committed by GitHub
co-authored by jpirnay
parent e8645ed92e
commit 302dea1eea
12 changed files with 1058 additions and 118 deletions
+10 -7
View File
@@ -8,9 +8,11 @@
* CrossPoint position representation.
*/
struct CrossPointPosition {
int spineIndex; // Current spine item (chapter) index
int pageNumber; // Current page within the spine item
int totalPages; // Total pages in the current spine item
int spineIndex; // Current spine item (chapter) index
int pageNumber; // Current page within the spine item
int totalPages; // Total pages in the current spine item
uint16_t paragraphIndex = 0; // 1-based synthetic paragraph index from XPath p[N]
bool hasParagraphIndex = false; // True when paragraphIndex was resolved from XPath
};
/**
@@ -59,9 +61,10 @@ class ProgressMapper {
private:
/**
* Generate XPath for KOReader compatibility.
* Format: /body/DocFragment[spineIndex+1]/body
* Since CrossPoint doesn't preserve HTML structure, we rely on percentage for positioning.
* Generate a fallback XPath by streaming the spine item's XHTML and resolving
* a paragraph/text position from intra-spine progress.
* Produces a full ancestry path such as
* /body/DocFragment[3]/body/p[42]/text().17.
*/
static std::string generateXPath(int spineIndex, int pageNumber, int totalPages);
static std::string generateXPath(const std::shared_ptr<Epub>& epub, int spineIndex, float intraSpineProgress);
};