【epubcfi函数generateRangeFromCfi和generateCfiFromRange】两者的区别和适用性,以及另一种实现
epubcfi是描述epub规范电子书中文本位置的一种描述符,它是形如“ epubcfi(/6/4[Section0017.xhtml]!4/42/178/1:0,4/42/198/1:1) "这样的字符串。表示在这个epub文档中,名为Section0017.xhtml的章节中第42个段落第178个(中文占2位)字符开始处到第42章第198个字符位置的结束处。
也就是说,这样的字符串表示的是一个范围,HTML5中Range对象表示页面上的一段连续区域,可以是跨标签的文本内容,通过Range对选中文本进行操作就变得很简单。如何把两者联系起来,使得提供任意一段epubcfi字符串,都能够转换为页面上的Range对象?
epub.js中提供了若干转换函数,其中两个就是互相转换的generateRangeFromCfi和generateCfiFromRange。
generateCfiFromRange():它能够将页面中的Range对象,转换为CFI字符串。我们知道Range对象中有这些属性:

通过startContainer和endContainer这两个DOM元素对象,就能够确定选中文本的起始位置和结束位置;startOffset和endOffset则是两者的偏移量,当epub解析完成时,页面中每个段落都由一个p标签包含,这两个变量就确定了起始位和结束位在段中的偏移量。这样再根据选中文本的位置,生成一个符合规范的epubcfi字符串。
generateRangeFromCfi(): 它能够将给定的epubcfi字符串,经过解析后,创建新的Range对象,该对象包含了epubcfi字符串指定的文本内容。
但为了精确的控制和展示标记,在我的阅读器中每个字都独占一个span标签,这时由于Range对象中有偏移量的设置,偏移量将只能为1或0(如上图所示),就没办法通过generateRangeFromCfi将CFI字符串生成Range对象,因为起始位置和结束位置一定不会在同一个标签内,甚至不在同一个父元素下(p段落),偏移量又不能跨标签表示,这个方法就不能生成跨标签的选中文本对象。
因此我重写了新的方法,使得给定任意一个CFI字符串,都能够生成相应的Range对象。
主要思路:
此前生成的Range对象,只有startContainer对象是正确的起始位置,endContainer因为跨标签而只能返回一个当前对象,在我的场景中其实就是startContainer,两者是相等的,因此生成一个正确的Range需要调整endContainer的位置,让它能够跨标签。代码:
1 var epubcfi = new EPUBJS.EpubCFI();
2 var doc = reader.book.renderer.doc;
3 var range = epubcfi.generateRangeFromCfi(data[i].cfi, doc); //这里生成的就是startContainer==endContainer的range对象,它只包含了一个正文字符。
4 if (range!=null)
5 {
6 var span = range.startContainer.parentNode;//确定起始位置
7 var spanEnd = span;//创建一个结束位置的DOM元素
8 var cfiFront = data[i].cfi.split('!')[1].split(',')[0].split('/');//手动解析epubcfi标签,拿到形如“4/42/178/1:0”的章节内部位置描述符,这个表示的是第42段的第178个字符的开始处
9 var cfiEnd = data[i].cfi.split('!')[1].split(',')[1].slice(0,-1).split('/');//同理,拿到选中文本的结尾处描述符。
10 var spanNum;
11 if(cfiFront[1]==cfiEnd[1])
12 { //此处表示当段落一致的情况(在同一个p标签父元素内),把结束位的DOM元素指向结尾处描述符所代表的DOM元素。
13 spanNum = (cfiEnd[2] - cfiFront[2]) / 2;
14 var j=0;
15 while(j<spanNum)
16 {
17 spanEnd = spanEnd.nextElementSibling;
18 j++;
19 }
20
21 }
22 else
23 { //段落不一致时,先按段落将结束位的DOM元素指向结束处描述符所代表的段落的首个子节点上。
24 while(cfiFront[1]!=cfiEnd[1])
25 {
26 spanEnd = spanEnd.parentNode.nextElementSibling.firstChild;//下一段的首节点
27 cfiFront[1] = parseInt(cfiFront[1]) + 2;
28 }
29 //
30 cfiFront[2] = '2';
31 while(cfiFront[2]!=cfiEnd[2]) //再让结束位的DOM元素指向结束处描述符表示的DOM元素。
32 {
33 spanEnd = spanEnd.nextElementSibling;//下一个兄弟节点
34 cfiFront[2] = parseInt(cfiFront[2]) + 2;
35 }
36 }
37 while(span!=spanEnd){ //对选中文本的操作。。。} // 这里首尾DOM元素都确定了,那么就可以直接对选中范围的文本进行遍历操作了,也可以生成真正的Range对象再进行操作。
38
39
40 }
【epubcfi函数generateRangeFromCfi和generateCfiFromRange】两者的区别和适用性,以及另一种实现的更多相关文章
- 关于T-SQL重编译那点事,内联函数和表值函数在编译生成执行计划的区别
本文出处:http://www.cnblogs.com/wy123/p/6266724.html 最近在学习 WITH RECOMPILE和OPTION(RECOMPILE)在重编译上的区别的时候,无 ...
- JQuery的ready函数与JS的onload的区别详解
JQuery的ready函数与JS的onload的区别:1.执行时间window.onload必须等到页面内包括图片的所有元素加载完毕后才能执行.$(document).ready()是DOM结构绘制 ...
- SQL Server函数---Union与Union All的区别
SQL Server函数---Union与Union All的区别 如果我们需要将两个select语句的结果作为一个整体显示出来,我们就需要用到union或者union all关键字.union(或称 ...
- 判断某个方法是否存在,解析php函数function_exists (),method_exists()与is_callable()的区别
php函数function_exists (),method_exists() 与is_callable()的区别在哪? 先来讲下后两个:method_exists() 与is_callable(): ...
- 方法(method)和函数(function)有什么区别?
方法(method)和函数(function)有什么区别? 定义和参数区别 函数是独立的功能,与对象无关,需要显示的传递数据 方法与对象和类相关,依赖对象而调用,可以直接处理对象上的数据,也就是隐式传 ...
- 闭包(Closure)和匿名函数(Anonymous function)/lambda表达式的区别
闭包(Closure)和匿名函数(Anonymous function)/lambda表达式的区别 函数最常见的形式是具名函数(named function): function foo(){ con ...
- javascript函数setInterval和setTimeout的使用区别详解
setTimeout和setInterval的使用 这两个方法都可以用来实现在一个固定时间段之后去执行JavaScript.不过两者各有各的应用场景. 方 法 实际上,setTimeout和setIn ...
- PHP数组函数: array_walk()与 array_map() 的区别
详细的介绍如下: PHP数组函数: array_walk() PHP数组函数: array_map() 实际应用中的一点区别与总结: array_walk() 主要用于对某个数组的迭代,相当于 for ...
- 立即执行函数: (function(){...})() 与 (function(){...}()) 有什么区别?
没有区别. function foo() {...} // 这是定义,Declaration:定义只是让解释器知道其存在,但是不会运行. foo(); // 这是语句,Statement:解释器遇到语 ...
随机推荐
- co源码分析及其实践
本文始发于我的个人博客,如需转载请注明出处. 为了更好的阅读体验,可以直接进去我的个人博客看. 前言 知识储备 阅读本文需要对Generator和Promise有一个基本的了解. 这里我简单地介绍一下 ...
- idea中的汉语注释出现乱码的解决方案
日记 - idea中的汉语注释出现乱码的解决方案 我是个idea的忠实用户,新公司的项目都是用eclipse做的,通过svn拉下代码后发现,注释的内容里,中文内容都是乱码.问过项目负责人,说可能是GB ...
- [Bayes] Variational Inference for Bayesian GMMs
为了世界和平,为了心知肚明,决定手算一次 Variational Inference for Bayesian GMMs 目的就是达到如下的智能效果,扔进去六个高斯,最后拟合结果成了两个高斯,当然,其 ...
- C++编译程序时的内存分配
一个由C/C++编译的程序占用的内存分为以下几个部分 1.栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈.程序结束时由编译器自动释放 ...
- c语言的,三个工具可以使编译器生成性能更佳的代码。
内联函数声明inline 函数有时可以非常短.短函数的每次调用可以用实现该函数功能的内联代码替代,以提高执行性能.意味着不需要给函数传递或返回一个值,要让编译器采用这种技术,可以把短函数指定为inli ...
- LNK4098: 默认库“MSVCRT”与其他库的使用冲突
LNK4098: 默认库"MSVCRT"与其他库的使用冲突 修改的方法:在项目属性中,在连接器-输入选项中,在忽略特定库中添加相应的库,具体添加那些苦请参照下面的表格. 下面的内容 ...
- Can you find it?
Can you find it? Time Limit: 10000/3000 MS (Java/Others) Memory Limit: 32768/10000 K (Java/Others) T ...
- Display 和Visible 区别
一. web页面前台编码时经常用到display:none样式,平常使用时发现有几点特征需要注意 1.如果在样式文件或页面文件代码中直接用display:none对元素进行了隐藏,载入页面后, ...
- 从成本角度看Java微服务
近年来,微服务因其良好的灵活性和伸缩性等特点备受追捧,很多公司开始采用微服务架构或将已有的单体系统改造成微服务.IBM也于近日开源了轻量级Java微服务应用服务器 Open Liberty .但是采用 ...
- python的xlwt模块的常用方法
工作中需要导出数据为excel格式,使用了xlwt模块,在此记录一下用到的内容. 1. 创建一个表,设置一个sheet import xlwt workbook = xlwt.Workbook(enc ...