Skip to content

Commit

Permalink
Merge pull request #844 from lvscar/master
Browse files Browse the repository at this point in the history
Adding a search method to section for find out result spanning multiple nodes.
  • Loading branch information
fchasen committed May 14, 2020
2 parents 9bd8440 + f5022af commit 5c04784
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 11 deletions.
69 changes: 69 additions & 0 deletions src/section.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,75 @@ class Section {
return matches;
};


/**
* Search a string in multiple sequential Element of the section. If the document.createTreeWalker api is missed(eg: IE8), use `find` as a fallback.
* @param {string} _query The query string to search
* @param {int} maxSeqEle The maximum number of Element that are combined for search, defualt value is 5.
* @return {object[]} A list of matches, with form {cfi, excerpt}
*/
search(_query , maxSeqEle = 5){
if (typeof(document.createTreeWalker) == "undefined") {
return this.find(_query);
}
let matches = [];
const excerptLimit = 150;
const section = this;
const query = _query.toLowerCase();
const search = function(nodeList){
const textWithCase = nodeList.reduce((acc ,current)=>{
return acc + current.textContent;
},"");
const text = textWithCase.toLowerCase();
const pos = text.indexOf(query);
if (pos != -1){
const startNodeIndex = 0 , endPos = pos + query.length;
let endNodeIndex = 0 , l = 0;
if (pos < nodeList[startNodeIndex].length){
let cfi;
while( endNodeIndex < nodeList.length - 1 ){
l += nodeList[endNodeIndex].length;
if ( endPos <= l){
break;
}
endNodeIndex += 1;
}

let startNode = nodeList[startNodeIndex] , endNode = nodeList[endNodeIndex];
let range = section.document.createRange();
range.setStart(startNode,pos);
let beforeEndLengthCount = nodeList.slice(0, endNodeIndex).reduce((acc,current)=>{return acc+current.textContent.length;},0) ;
range.setEnd(endNode, beforeEndLengthCount > endPos ? endPos : endPos - beforeEndLengthCount );
cfi = section.cfiFromRange(range);

let excerpt = nodeList.slice(0, endNodeIndex+1).reduce((acc,current)=>{return acc+current.textContent ;},"");
if (excerpt.length > excerptLimit){
excerpt = excerpt.substring(pos - excerptLimit/2, pos + excerptLimit/2);
excerpt = "..." + excerpt + "...";
}
matches.push({
cfi: cfi,
excerpt: excerpt
});
}
}
}

const treeWalker = document.createTreeWalker(section.document, NodeFilter.SHOW_TEXT, null, false);
let node , nodeList = [];
while (node = treeWalker.nextNode()) {
nodeList.push(node);
if (nodeList.length == maxSeqEle){
search(nodeList.slice(0 , maxSeqEle));
nodeList = nodeList.slice(1, maxSeqEle);
}
}
if (nodeList.length > 0){
search(nodeList);
}
return matches;
}

/**
* Reconciles the current chapters layout properies with
* the global layout properities.
Expand Down
66 changes: 55 additions & 11 deletions test/section.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ describe("section", function() {
return book.ready.then(function() {
var section = book.section("chapter_001.xhtml");
return section.load().then(function() {
var results = section.find("they were filled with cupboards and book-shelves");
assert.equal(results.length, 1);
assert.equal(results[0].cfi, "epubcfi(/6/8[chapter_001]!/4/2/16,/1:275,/1:323)");
assert.equal(results[0].excerpt, "... see anything; then she looked at the sides of the well and\n\t\tnoticed that they were filled with cupboards and book-shelves; here and there she saw\n\t\t...");
const queryString = "they were filled with cupboards and book-shelves";
const findResults = section.find(queryString);
const searchResults = section.search(queryString);
[findResults , searchResults].forEach( (results)=>{
assert.equal(results.length, 1);
assert.equal(results[0].cfi, "epubcfi(/6/8[chapter_001]!/4/2/16,/1:275,/1:323)");
assert.equal(results[0].excerpt, "... see anything; then she looked at the sides of the well and\n\t\tnoticed that they were filled with cupboards and book-shelves; here and there she saw\n\t\t...");
});
});
});
});
Expand All @@ -20,14 +24,54 @@ describe("section", function() {
return book.ready.then(function() {
var section = book.section("chapter_001.xhtml");
return section.load().then(function() {
var results = section.find("white rabbit");
assert.equal(results.length, 2);
assert.equal(results[0].cfi, "epubcfi(/6/8[chapter_001]!/4/2/8,/1:240,/1:252)");
assert.equal(results[0].excerpt, "...e worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her....");
assert.equal(results[1].cfi, "epubcfi(/6/8[chapter_001]!/4/2/20,/1:148,/1:160)");
assert.equal(results[1].excerpt, "...ut it was\n\t\tall dark overhead; before her was another long passage and the White Rabbit was still\n\t\tin sight, hurrying down it. There was not a moment...");
});
const queryString = "white rabbit";
const findResults = section.find(queryString);
const searchResults = section.search(queryString);
[findResults , searchResults].forEach( (results)=>{
assert.equal(results.length, 2);
assert.equal(results[0].cfi, "epubcfi(/6/8[chapter_001]!/4/2/8,/1:240,/1:252)");
assert.equal(results[0].excerpt, "...e worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her....");
assert.equal(results[1].cfi, "epubcfi(/6/8[chapter_001]!/4/2/20,/1:148,/1:160)");
assert.equal(results[1].excerpt, "...ut it was\n\t\tall dark overhead; before her was another long passage and the White Rabbit was still\n\t\tin sight, hurrying down it. There was not a moment...");
});
});
});

});

it("finds result that spanning multiple document nodes, tag at ending", function() {
var book = ePub("./fixtures/alice/", {width: 400, height: 400});
return book.ready.then(function() {
var section = book.section("chapter_010.xhtml");
return section.load().then(function() {
const queryString = "I beg";

const findResult = section.find(queryString);
assert.equal(findResult.length, 0);

const searchResults = section.search(queryString);
assert.equal(searchResults.length, 1);
assert.equal(searchResults[0].cfi, "epubcfi(/6/26[chapter_010]!/4/2/6,/1:5,/2/1:3)");
assert.equal(searchResults[0].excerpt,'"Oh, I beg');
});
});
});

it("finds result that spanning multiple document nodes, tag at middle", function() {
var book = ePub("./fixtures/alice/", {width: 400, height: 400});
return book.ready.then(function() {
var section = book.section("chapter_010.xhtml");
return section.load().then(function() {
const queryString = "I beg your pardon";

const findResult = section.find(queryString);
assert.equal(findResult.length, 0);

const searchResults = section.search(queryString);
assert.equal(searchResults.length, 1);
assert.equal(searchResults[0].cfi, "epubcfi(/6/26[chapter_010]!/4/2/6,/1:5,/3:12)");
assert.equal(searchResults[0].excerpt,'"Oh, I beg your pardon!" she exclaimed in a tone of great dismay.');
});
});
});
});

0 comments on commit 5c04784

Please sign in to comment.