From f5022af53e726c15fff4e155cb523f121fdac29b Mon Sep 17 00:00:00 2001 From: Bono Lv Date: Fri, 19 Jul 2019 18:54:26 +0800 Subject: [PATCH] Added a search method to Section object for find out result spanning multiple document nodes. --- src/section.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ test/section.js | 66 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 124 insertions(+), 11 deletions(-) diff --git a/src/section.js b/src/section.js index 350ef35..d5c19fe 100644 --- a/src/section.js +++ b/src/section.js @@ -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. diff --git a/test/section.js b/test/section.js index f983d1b..5c32fac 100644 --- a/test/section.js +++ b/test/section.js @@ -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..."); + }); }); }); }); @@ -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.'); + }); + }); + }); });