• 【epub.js】将CFI定位符转换为HTML5的Range对象


    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 }    
  • 相关阅读:
    配置webpack.config.js中的文件
    webpack-dev-server运行时报错
    vue 中的通过搜索框进行数据过滤的过程
    vue中的computed(计算属性)和watch(监听属性)的特点,以及深度监听
    关于vue-router 中参数传递的那些坑(params,query)
    treeSet比较功能
    练习:往HashSet中存储学生对象(姓名,年龄) 同姓名,同年龄视为一个人,不存
    LinkedList实现堆栈或者队列
    LInkedList特有方法演示
    ★★★ArrayList去除指定对象的重复元素
  • 原文地址:https://www.cnblogs.com/xiaozhaoqi/p/8031611.html
Copyright © 2020-2023  润新知