1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-03 14:59:18 +02:00

Allow location to use XMLDom, add locations tests

This commit is contained in:
Fred Chasen 2016-11-21 14:36:17 +01:00
parent 662a3e1079
commit db798e7934
9 changed files with 254 additions and 100 deletions

View file

@ -77,7 +77,7 @@
book.ready.then(function(){
// Load in stored locations from json or local storage
var key = book.key()+'-locations';
var stored = false;//localStorage.getItem(key);
var stored = localStorage.getItem(key);
if (stored) {
return book.locations.load(stored);
} else {
@ -88,7 +88,6 @@
}
})
.then(function(locations){
controls.style.display = "block";
slider.setAttribute("type", "range");
slider.setAttribute("min", 0);

View file

@ -45,8 +45,8 @@ module.exports = function(config) {
webpack:{
externals: {
"jszip": "JSZip",
"xmldom": "xmldom"
"jszip": "JSZip"
// "xmldom": "xmldom"
},
devtool: 'inline-source-map',
resolve: {

View file

@ -485,11 +485,10 @@ function type(obj){
return Object.prototype.toString.call(obj).slice(8, -1);
}
function parse(markup, mime) {
function parse(markup, mime, forceXMLDom) {
var doc;
// console.log("parse", markup);
if (typeof DOMParser === "undefined") {
if (typeof DOMParser === "undefined" || forceXMLDom) {
DOMParser = require('xmldom').DOMParser;
}
@ -501,6 +500,9 @@ function parse(markup, mime) {
function qs(el, sel) {
var elements;
if (!el) {
throw new Error('No Element Provided');
}
if (typeof el.querySelector != "undefined") {
return el.querySelector(sel);
@ -547,6 +549,61 @@ function qsp(el, sel, props) {
}
}
/**
* Sprint through all text nodes in a document
* @param {element} root element to start with
* @param {function} func function to run on each element
*/
function sprint(root, func) {
var doc = root.ownerDocument || root;
if (typeof(doc.createTreeWalker) !== "undefined") {
treeWalker(root, func, NodeFilter.SHOW_TEXT);
} else {
walk(root, function(node) {
if (node && node.nodeType === 3) { // Node.TEXT_NODE
func(node);
}
}, true);
}
}
function treeWalker(root, func, filter) {
var treeWalker = document.createTreeWalker(root, filter, null, false);
while ((node = treeWalker.nextNode())) {
func(node);
}
}
// function walk(root, func, onlyText) {
// var node = root;
//
// if (node && !onlyText || node.nodeType === 3) { // Node.TEXT_NODE
// func(node);
// }
// console.log(root);
//
// node = node.firstChild;
// while(node) {
// walk(node, func, onlyText);
// node = node.nextSibling;
// }
// }
/**
* @param callback return false for continue,true for break
* @return boolean true: break visit;
*/
function walk(node,callback){
if(callback(node)){
return true;
}
if(node = node.firstChild){
do{
if(walk(node,callback)){return true}
}while(node=node.nextSibling)
}
}
function blob2base64(blob, cb) {
var reader = new FileReader();
reader.readAsDataURL(blob);
@ -605,7 +662,17 @@ function defer() {
}
}
function children(el) {
var children = [];
var childNodes = el.parentNode.childNodes;
for (var i = 0; i < childNodes.length; i++) {
node = childNodes[i];
if (node.nodeType === 1) {
children.push(node);
}
};
return children;
}
module.exports = {
'isElement': isElement,
@ -640,5 +707,7 @@ module.exports = {
'defer': defer,
'Url': Url,
'Path': Path,
'querySelectorByType': querySelectorByType
'querySelectorByType': querySelectorByType,
'sprint' : sprint,
'children' : children
};

View file

@ -14,6 +14,11 @@ var core = require('./core');
- Text Location Assertion ([)
*/
var ELEMENT_NODE = 1;
var TEXT_NODE = 3;
var COMMENT_NODE = 8;
var DOCUMENT_NODE = 9;
function EpubCFI(cfiFrom, base, ignoreClass){
var type;
@ -64,7 +69,7 @@ EpubCFI.prototype.checkType = function(cfi) {
if (this.isCfiString(cfi)) {
return 'string';
// Is a range object
} else if (typeof cfi === 'object' && core.type(cfi) === "Range"){
} else if (typeof cfi === 'object' && (core.type(cfi) === "Range" || typeof(cfi.startContainer) != "undefined")){
return 'range';
} else if (typeof cfi === 'object' && typeof(cfi.nodeType) != "undefined" ){ // || typeof cfi === 'function'
return 'node';
@ -372,7 +377,7 @@ EpubCFI.prototype.compare = function(cfiOne, cfiTwo) {
};
EpubCFI.prototype.step = function(node) {
var nodeType = (node.nodeType === Node.TEXT_NODE) ? 'text' : 'element';
var nodeType = (node.nodeType === TEXT_NODE) ? 'text' : 'element';
return {
'id' : node.id,
@ -392,7 +397,7 @@ EpubCFI.prototype.filteredStep = function(node, ignoreClass) {
}
// Otherwise add the filter node in
nodeType = (filteredNode.nodeType === Node.TEXT_NODE) ? 'text' : 'element';
nodeType = (filteredNode.nodeType === TEXT_NODE) ? 'text' : 'element';
return {
'id' : filteredNode.id,
@ -414,7 +419,7 @@ EpubCFI.prototype.pathTo = function(node, offset, ignoreClass) {
var step;
while(currentNode && currentNode.parentNode &&
currentNode.parentNode.nodeType != Node.DOCUMENT_NODE) {
currentNode.parentNode.nodeType != DOCUMENT_NODE) {
if (ignoreClass) {
step = this.filteredStep(currentNode, ignoreClass);
@ -461,6 +466,7 @@ EpubCFI.prototype.equalStep = function(stepA, stepB) {
return false;
};
EpubCFI.prototype.fromRange = function(range, base, ignoreClass) {
var cfi = {
range: false,
@ -576,14 +582,13 @@ EpubCFI.prototype.fromNode = function(anchor, base, ignoreClass) {
return cfi;
};
EpubCFI.prototype.filter = function(anchor, ignoreClass) {
var needsIgnoring;
var sibling; // to join with
var parent, prevSibling, nextSibling;
var isText = false;
if (anchor.nodeType === Node.TEXT_NODE) {
if (anchor.nodeType === TEXT_NODE) {
isText = true;
parent = anchor.parentNode;
needsIgnoring = anchor.parentNode.classList.contains(ignoreClass);
@ -597,9 +602,9 @@ EpubCFI.prototype.filter = function(anchor, ignoreClass) {
nextSibling = parent.nextSibling;
// If the sibling is a text node, join the nodes
if (previousSibling && previousSibling.nodeType === Node.TEXT_NODE) {
if (previousSibling && previousSibling.nodeType === TEXT_NODE) {
sibling = previousSibling;
} else if (nextSibling && nextSibling.nodeType === Node.TEXT_NODE) {
} else if (nextSibling && nextSibling.nodeType === TEXT_NODE) {
sibling = nextSibling;
}
@ -624,7 +629,7 @@ EpubCFI.prototype.patchOffset = function(anchor, offset, ignoreClass) {
var needsIgnoring;
var sibling;
if (anchor.nodeType != Node.TEXT_NODE) {
if (anchor.nodeType != TEXT_NODE) {
console.error("Anchor must be a text node");
return;
}
@ -638,7 +643,7 @@ EpubCFI.prototype.patchOffset = function(anchor, offset, ignoreClass) {
}
while (curr.previousSibling) {
if(curr.previousSibling.nodeType === Node.ELEMENT_NODE) {
if(curr.previousSibling.nodeType === ELEMENT_NODE) {
// Originally a text node, so join
if(curr.previousSibling.classList.contains(ignoreClass)){
totalOffset += curr.previousSibling.textContent.length;
@ -669,14 +674,14 @@ EpubCFI.prototype.normalizedMap = function(children, nodeType, ignoreClass) {
currNodeType = children[i].nodeType;
// Check if needs ignoring
if (currNodeType === Node.ELEMENT_NODE &&
if (currNodeType === ELEMENT_NODE &&
children[i].classList.contains(ignoreClass)) {
currNodeType = Node.TEXT_NODE;
currNodeType = TEXT_NODE;
}
if (i > 0 &&
currNodeType === Node.TEXT_NODE &&
prevNodeType === Node.TEXT_NODE) {
currNodeType === TEXT_NODE &&
prevNodeType === TEXT_NODE) {
// join text nodes
output[i] = prevIndex;
} else if (nodeType === currNodeType){
@ -693,9 +698,12 @@ EpubCFI.prototype.normalizedMap = function(children, nodeType, ignoreClass) {
EpubCFI.prototype.position = function(anchor) {
var children, index, map;
if (anchor.nodeType === Node.ELEMENT_NODE) {
var childNodes, node;
if (anchor.nodeType === ELEMENT_NODE) {
children = anchor.parentNode.children;
if (!children) {
children = core.children(anchor.parentNode);
}
index = Array.prototype.indexOf.call(children, anchor);
} else {
children = this.textNodes(anchor.parentNode);
@ -708,9 +716,9 @@ EpubCFI.prototype.position = function(anchor) {
EpubCFI.prototype.filteredPosition = function(anchor, ignoreClass) {
var children, index, map;
if (anchor.nodeType === Node.ELEMENT_NODE) {
if (anchor.nodeType === ELEMENT_NODE) {
children = anchor.parentNode.children;
map = this.normalizedMap(children, Node.ELEMENT_NODE, ignoreClass);
map = this.normalizedMap(children, ELEMENT_NODE, ignoreClass);
} else {
children = anchor.parentNode.childNodes;
// Inside an ignored node
@ -718,7 +726,7 @@ EpubCFI.prototype.filteredPosition = function(anchor, ignoreClass) {
anchor = anchor.parentNode;
children = anchor.parentNode.childNodes;
}
map = this.normalizedMap(children, Node.TEXT_NODE, ignoreClass);
map = this.normalizedMap(children, TEXT_NODE, ignoreClass);
}
@ -784,7 +792,7 @@ EpubCFI.prototype.stepsToQuerySelector = function(steps) {
EpubCFI.prototype.textNodes = function(container, ignoreClass) {
return Array.prototype.slice.call(container.childNodes).
filter(function (node) {
if (node.nodeType === Node.TEXT_NODE) {
if (node.nodeType === TEXT_NODE) {
return true;
} else if (ignoreClass && node.classList.contains(ignoreClass)) {
return true;
@ -834,7 +842,7 @@ EpubCFI.prototype.findNode = function(steps, _doc, ignoreClass) {
EpubCFI.prototype.fixMiss = function(steps, offset, _doc, ignoreClass) {
var container = this.findNode(steps.slice(0,-1), _doc, ignoreClass);
var children = container.childNodes;
var map = this.normalizedMap(children, Node.TEXT_NODE, ignoreClass);
var map = this.normalizedMap(children, TEXT_NODE, ignoreClass);
var i;
var child;
var len;
@ -850,7 +858,7 @@ EpubCFI.prototype.fixMiss = function(steps, offset, _doc, ignoreClass) {
if(offset > len) {
offset = offset - len;
} else {
if (child.nodeType === Node.ELEMENT_NODE) {
if (child.nodeType === ELEMENT_NODE) {
container = child.childNodes[0];
} else {
container = child;

View file

@ -39,7 +39,7 @@ Locations.prototype.generate = function(chars) {
this.spine.each(function(section) {
this.q.enqueue(this.process, section);
this.q.enqueue(this.process.bind(this), section);
}.bind(this));
@ -56,85 +56,100 @@ Locations.prototype.generate = function(chars) {
};
Locations.prototype.createRange = function () {
return {
startContainer: undefined,
startOffset: undefined,
endContainer: undefined,
endOffset: undefined
}
};
Locations.prototype.process = function(section) {
return section.load(this.request)
.then(function(contents) {
var range;
var doc = contents.ownerDocument;
var body = core.qs(doc, 'body');
var counter = 0;
this.sprint(body, function(node) {
var len = node.length;
var dist;
var pos = 0;
console.log(counter);
// Start range
if (counter == 0) {
range = doc.createRange();
range.setStart(node, 0);
}
dist = this.break - counter;
// Node is smaller than a break
if(dist > len){
counter += len;
pos = len;
}
console.log(counter);
while (pos < len) {
counter = this.break;
pos += this.break;
// Gone over
if(pos >= len){
// Continue counter for next node
counter = len - (pos - this.break);
// At End
} else {
// End the previous range
range.setEnd(node, pos);
cfi = section.cfiFromRange(range);
this._locations.push(cfi);
counter = 0;
console.log(cfi);
// Start new range
pos += 1;
range = doc.createRange();
range.setStart(node, pos);
}
console.log(counter);
}
}.bind(this));
// Close remaining
if (range) {
range.setEnd(prev, prev.length);
cfi = section.cfiFromRange(range);
this._locations.push(cfi)
counter = 0;
}
var locations = this.parse(contents, section.cfiBase);
this._locations = this._locations.concat(locations);
}.bind(this));
};
Locations.prototype.sprint = function(root, func) {
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
Locations.prototype.parse = function(contents, cfiBase, chars) {
var locations = [];
var range;
var doc = contents.ownerDocument;
var body = core.qs(doc, 'body');
var counter = 0;
var prev;
var _break = chars || this.break;
var parser = function(node) {
var len = node.length;
var dist;
var pos = 0;
while ((node = treeWalker.nextNode())) {
func(node);
if (node.textContent.trim().length === 0) {
return false; // continue
}
// Start range
if (counter == 0) {
range = this.createRange();
range.startContainer = node;
range.startOffset = 0;
}
dist = _break - counter;
// Node is smaller than a break,
// skip over it
if(dist > len){
counter += len;
pos = len;
}
while (pos < len) {
// counter = this.break;
pos += dist;
// Gone over
if(pos >= len){
// Continue counter for next node
counter = len - (pos - _break);
// At End
} else {
// End the previous range
range.endContainer = node;
range.endOffset = pos;
// cfi = section.cfiFromRange(range);
cfi = new EpubCFI(range, cfiBase).toString();
locations.push(cfi);
counter = 0;
// Start new range
pos += 1;
range = this.createRange();
range.startContainer = node;
range.startOffset = pos;
dist = _break;
}
}
prev = node;
};
core.sprint(body, parser.bind(this));
// Close remaining
if (range && range.startContainer && prev) {
range.endContainer = prev;
range.endOffset = prev.length;
// cfi = section.cfiFromRange(range);
cfi = new EpubCFI(range, cfiBase).toString();
locations.push(cfi);
counter = 0;
}
return locations;
};
Locations.prototype.locationFromCfi = function(cfi){

28
test/fixtures/locations.xhtml vendored Normal file
View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
<head>
<title>
Moby-Dick</title>
<link rel="stylesheet" href="css/stylesheet.css" type="text/css"></link>
<meta charset="utf-8"/>
</head>
<body>
<section class="body-rw Chapter-rw" epub:type="bodymatter chapter">
<header>
<!-- 20 -->
<h1>Chapter 1. Loomings.</h1>
</header>
<!-- 1107 -->
<p>Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.
It is a way I have of driving off the spleen and regulating the circulation.
Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires a strong moral principle to prevent me from deliberately stepping into the street, and methodically knocking peoples hats off—then, I account it high time to get to sea as soon as I can.
This is my substitute for pistol and ball.
With a philosophical flourish Cato throws himself upon his sword; I quietly take to the ship.
There is nothing surprising in this.
If they but knew it, almost all men in their degree, some time or other, cherish very nearly the same feelings towards the ocean with me.</p>
<!-- 387 -->
<p>There now is your insular city of the Manhattoes, belted round by wharves as Indian isles by coral reefs—commerce surrounds it with her surf. Right and left, the streets take you waterward. Its extreme downtown is the battery, where that noble mole is washed by waves, and cooled by breezes, which a few hours previous were out of sight of land. Look at the crowds of water-gazers there.</p>
</section></body></html>

35
test/locations.js Normal file
View file

@ -0,0 +1,35 @@
var assert = require('assert');
describe('Locations', function() {
var Locations = require('../src/locations');
var core = require('../src/core');
var chapter = require('raw-loader!./fixtures/locations.xhtml');
describe('#parse', function() {
var Locations = require('../src/locations');
var core = require('../src/core');
var chapter = require('raw-loader!./fixtures/locations.xhtml');
it('parse locations from a document', function() {
var doc = core.parse(chapter, "application/xhtml+xml");
var contents = doc.documentElement;
var locations = new Locations();
var result = locations.parse(contents, "/6/4[chap01ref]", 100);
assert.equal(result.length, 15);
});
it('parse locations from xmldom', function() {
var doc = core.parse(chapter, "application/xhtml+xml", true);
var contents = doc.documentElement;
var locations = new Locations();
var result = locations.parse(contents, "/6/4[chap01ref]", 100);
console.log(result);
assert.equal(result.length, 15);
});
});
});