[fix] xpath & cfi resolution
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Evan Reichard 2023-11-06 19:41:35 -05:00
parent 5cc1e2d71c
commit 856bc7e2e6
2 changed files with 67 additions and 59 deletions

View File

@ -910,7 +910,7 @@ class EBookReader {
* Get XPath from current location * Get XPath from current location
**/ **/
async getXPathFromCFI(cfi) { async getXPathFromCFI(cfi) {
// Get DocFragment (current book spline index) // Get DocFragment (Spine Index)
let startCFI = cfi.replace("epubcfi(", ""); let startCFI = cfi.replace("epubcfi(", "");
let docFragmentIndex = let docFragmentIndex =
this.book.spine.spineItems.find((item) => this.book.spine.spineItems.find((item) =>
@ -918,58 +918,62 @@ class EBookReader {
).index + 1; ).index + 1;
// Base Progress // Base Progress
let newPos = "/body/DocFragment[" + docFragmentIndex + "]/body"; let basePos = "/body/DocFragment[" + docFragmentIndex + "]/body";
// Get first visible node // Get First Node & Element Reference
let contents = this.rendition.getContents()[0]; let contents = this.rendition.getContents()[0];
let node = contents.range(cfi).startContainer; let currentNode = contents.range(cfi).startContainer;
let element = null; let element =
currentNode.nodeType == Node.ELEMENT_NODE
? currentNode
: currentNode.parentElement;
// Walk upwards and build progress until body // XPath Reference
let childPos = ""; let allPos = "";
while (node.nodeName != "BODY") {
let ownValue;
switch (node.nodeType) { // Walk Upwards
case Node.ELEMENT_NODE: while (currentNode.nodeName != "BODY") {
// Store First Element Node // Get Parent
if (!element) element = node; let parentElement = currentNode.parentElement;
let relativeIndex =
Array.from(node.parentNode.children)
.filter((item) => item.nodeName == node.nodeName)
.indexOf(node) + 1;
ownValue = node.nodeName.toLowerCase() + "[" + relativeIndex + "]"; // Unknown Node -> Update Reference
break; if (currentNode.nodeType != Node.ELEMENT_NODE) {
case Node.ATTRIBUTE_NODE: console.log("[getXPathFromCFI] Unknown Node Type:", currentNode);
ownValue = "@" + node.nodeName; currentNode = parentElement;
break; continue;
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE:
ownValue = "text()";
break;
case Node.PROCESSING_INSTRUCTION_NODE:
ownValue = "processing-instruction()";
break;
case Node.COMMENT_NODE:
ownValue = "comment()";
break;
case Node.DOCUMENT_NODE:
ownValue = "";
break;
default:
ownValue = "";
break;
} }
// Prepend childPos & Update node reference /**
childPos = "/" + ownValue + childPos; * Exclude A tags. This could potentially be all inline elements:
node = node.parentNode; * https://github.com/koreader/crengine/blob/master/cr3gui/data/epub.css#L149
**/
while (parentElement.nodeName == "A") {
parentElement = parentElement.parentElement;
} }
let xpath = newPos + childPos; /**
* Note: This is depth / document order first, which means that this
* _could_ return incorrect results when dealing with nested "A" tags
* (dependent on how KOReader deals with nested "A" tags)
**/
let allDescendents = parentElement.querySelectorAll(currentNode.nodeName);
let relativeIndex = Array.from(allDescendents).indexOf(currentNode) + 1;
// Return derived progress // Get Node Position
let nodePos =
currentNode.nodeName.toLowerCase() + "[" + relativeIndex + "]";
// Update Reference
currentNode = parentElement;
// Update Position
allPos = "/" + nodePos + allPos;
}
// Combine XPath
let xpath = basePos + allPos;
// Return Derived Progress
return { xpath, element }; return { xpath, element };
} }
@ -977,19 +981,13 @@ class EBookReader {
* Get CFI from current location * Get CFI from current location
**/ **/
async getCFIFromXPath(xpath) { async getCFIFromXPath(xpath) {
// XPath Reference - Example: /body/DocFragment[15]/body/div[10]/text().184
//
// - /body/DocFragment[15] = 15th item in book spline
// - [...]/body/div[10] = 10th child div under body (direct descendents only)
// - [...]/text().184 = text node of parent, character offset @ 184 chars?
// No XPath // No XPath
if (!xpath || xpath == "") return {}; if (!xpath || xpath == "") return {};
// Match Document Fragment Index // Match Document Fragment Index
let fragMatch = xpath.match(/^\/body\/DocFragment\[(\d+)\]/); let fragMatch = xpath.match(/^\/body\/DocFragment\[(\d+)\]/);
if (!fragMatch) { if (!fragMatch) {
console.warn("No XPath Match"); console.warn("[getCFIFromXPath] No XPath Match");
return {}; return {};
} }
@ -1031,10 +1029,25 @@ class EBookReader {
// Remove potential trailing `text()` // Remove potential trailing `text()`
.replace(/\/text\(\)(\[\d+\])?$/, ""); .replace(/\/text\(\)(\[\d+\])?$/, "");
// XPath to CSS Selector // XPath to Element
let derivedSelector = remainingXPath let derivedSelectorElement = remainingXPath
.replace(/^\/html\/body/, "body") .replace(/^\/html\/body/, "body")
.replace(/\/(\w+)\[(\d+)\]/g, " $1:nth-of-type($2)"); .split("/")
.reduce((el, item) => {
// No Match
if (!el) return null;
// Non Index
let indexMatch = item.match(/(\w+)\[(\d+)\]$/);
if (!indexMatch) return el.querySelector(item);
// Get @ Index
let tag = indexMatch[1];
let index = parseInt(indexMatch[2]) - 1;
return el.querySelectorAll(tag)[index];
}, docItem);
console.log("[getCFIFromXPath] Selector Element:", derivedSelectorElement);
// Validate Namespace // Validate Namespace
if (namespaceURI) remainingXPath = remainingXPath.replaceAll("/", "/ns:"); if (namespaceURI) remainingXPath = remainingXPath.replaceAll("/", "/ns:");
@ -1070,9 +1083,7 @@ class EBookReader {
**/ **/
// Get Element & CFI (XPath -> CSS Selector Fallback) // Get Element & CFI (XPath -> CSS Selector Fallback)
let element = let element = docSearch.iterateNext() || derivedSelectorElement;
docSearch.iterateNext() || docItem.querySelector(derivedSelector);
let cfi = sectionItem.cfiFromElement(element); let cfi = sectionItem.cfiFromElement(element);
return { cfi, element }; return { cfi, element };

View File

@ -101,9 +101,6 @@ func getSVGBezierOpposedLine(pointA SVGGraphPoint, pointB SVGGraphPoint) SVGBezi
Length: int(math.Sqrt(math.Pow(lengthX, 2) + math.Pow(lengthY, 2))), Length: int(math.Sqrt(math.Pow(lengthX, 2) + math.Pow(lengthY, 2))),
Angle: int(math.Atan2(lengthY, lengthX)), Angle: int(math.Atan2(lengthY, lengthX)),
} }
// length = Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
// angle = Math.atan2(lengthY, lengthX)
} }
func getSVGBezierControlPoint(currentPoint *SVGGraphPoint, prevPoint *SVGGraphPoint, nextPoint *SVGGraphPoint, isReverse bool) SVGGraphPoint { func getSVGBezierControlPoint(currentPoint *SVGGraphPoint, prevPoint *SVGGraphPoint, nextPoint *SVGGraphPoint, isReverse bool) SVGGraphPoint {