Files
bookhoard/internal/sync/cfi_debug_test.go
T
john-okeefe 5a76fed099 feat(sync): add CFI converter for CREngine XPointer to standard epubcfi
Implements ConvertCREToStandard which converts CREngine XPointers
(e.g. /body/DocFragment[6]/body/div/p[47]/text().2399) to standard
epubcfi format (e.g. epubcfi(/6/12!/4/2[id]/4/1:7)).

Key components:
- indexChildNodes: faithful port of foliate-js's epubcfi.js algorithm
  for computing CFI-compatible child node indices including virtual
  positions, null positions between adjacent elements, and text chunks
- preprocessXHTML: converts XHTML self-closing tags (e.g. <a id="x"/>)
  to open/close pairs so Go's HTML parser produces the same DOM as the
  browser's XHTML parser
- buildCFI: walks up from a text node to body, computing CFI indices
  at each level using indexChildNodes
- findTextInNode: regex-based whitespace-flexible text search for
  context_text fallback positioning
- convertByPercentageOffset: estimates position via book-wide character
  counts when no context_text is available
- ConvertCREToStandard: orchestrates text search → percentage fallback

Supports CREngine XPointer format, CREngine fragment ID format
(#_doc_fragment_N_anchor), and includes round-trip test coverage for
1984 and Crime and Punishment EPUBs.
2026-06-02 19:44:05 -04:00

38 lines
999 B
Go

package sync
import "testing"
func TestDebugHess(t *testing.T) {
epubPath := "/home/nymusicman/Code/bookhoard/William L Hess/Crushing the Great Serpent_ Did God (42)/Crushing the Great Serpent_ Did - William L Hess.epub"
c := NewCFIConverter(epubPath)
xp, err := ParseCREXPointer("/body/DocFragment[5]/body/div[3]/p[28]/text().500")
if err != nil {
t.Fatal(err)
}
t.Logf("FragmentIndex: %d, CharOffset: %d", xp.FragmentIndex, xp.CharOffset)
doc, href, err := c.getContentDoc(xp.FragmentIndex)
if err != nil {
t.Fatalf("getContentDoc error: %v", err)
}
t.Logf("href: %s", href)
body := findBody(doc)
if body == nil {
t.Fatal("no body")
}
totalChars := countTextChars(body)
t.Logf("total chars in doc: %d", totalChars)
target, foundOffset := findNodeAtCharOffset(body, 500)
if target == nil {
t.Fatal("target is nil")
}
t.Logf("foundOffset: %d target: <%s>", foundOffset, target.Data)
cfi, err := buildCFI(5, target, 0)
t.Logf("cfi: %q err: %v", cfi, err)
}