结合sigil和ebooklib及beautifulsoup配合改造epub电子书

网上下载一本说文解字epub电子书,这书也有注释引用和注释编号,但引用和编号都不是超链接,只能如纸质书一样对照着注释引用与注释编号来阅读,完全丧失了电子书的优势,因此勾起了我鼓捣一下这本电子书的兴趣,这个过程还涉及到了如何使用ebooklib结合beautifulsoup来编辑电子书中的HTML文件,有一定的参考意义,故此记录下来。

用sigil(当然也可以用calibre的epub编辑器)将这本电子书打开,可以发现这本书中每个HTML文件里包含了多个以每个字的说文解字原文、注释(有些单元没有)、译文、参证(有些单元没有)构成的单元,如以下HTML代码显示了该书的代表性结构:

<p class="calibre6">屮<img alt="" class="inline" src="../images/07310.jpeg"/> 艸木初生也。象丨出形<sup class="calibre8">①</sup>,有枝茎也。古文或以为艸字。读若彻<sup class="calibre8">②</sup>。凡屮之属皆从屮。尹彤说<sup class="calibre8">③</sup>。<span>丑列切(chè)</span></p>
  <p class="calibre6"><b class="calibre5">【注释】</b></p>
  <p class="block">①丨:段玉裁《说文解字注》:“丨,读若囟,引而上行也。”丨出,即开出、长出。</p>
  <p class="block">②读若彻:段玉裁《说文解字注》:“彻,通也。义存乎音。”</p>
  <p class="block">③尹彤说:徐锴《说文解字系传》:“尹彤,当时说文字者。所谓‘博采通人’也。”段玉裁《说文解字注》:“三字当在‘凡屮’上。转写者倒之。”</p>
  <p class="calibre6"><b class="calibre5">【译文】</b></p>
  <p class="block">屮,草木初生。象草木长出地面的形状,而且有了枝茎。古文有时把它当作艸(草)字。音读像“彻”字。大凡屮的部属都从屮。这是尹彤的说法。</p>
  <p class="calibre6"><b class="calibre5">【参证】</b></p>
  <p class="block">甲文作<img alt="" class="inline" src="../images/06777.jpeg"/>,金文作<img alt="" class="inline" src="../images/07205.jpeg"/>。象一棵初生的小草。商承祚《说文中之古文考》:“《石经·春秋经》:‘陨(从天降下的)霜不杀(冻杀)屮’,艸(cǎo)之古文作屮。案,屮、艸本一字。初生为屮,蔓延为艸。”</p>
  <p class="calibre6">屯<img alt="" class="inline" src="../images/06811.jpeg"/> 难也。象艸木之初生,屯然而难<sup class="calibre8">①</sup>。从屮贯一,一,地也;尾曲<sup class="calibre8">②</sup>。《易》曰:“屯,刚柔始交而难生。”<sup class="calibre8">③</sup><span>陟伦切(zhūn)</span></p>
  <p class="calibre6"><b class="calibre5">【注释】</b></p>
  <p class="block">①屯然:曲折之貌。然,助词。</p>
  <p class="block">②尾曲:徐灏《说文解字注笺》:“此篆从屮曲之,以象难生之意。”</p>
  <p class="block">③屯,刚柔始交而难生:语见《周易·屯卦》。刚,指阳。柔,指阴。</p>
  <p class="calibre6"><b class="calibre5">【译文】</b></p>
  <p class="block">屯,艰难。象草木初生,曲折而又艰难的形状。其形由屮贯穿一构成,一代表地面;屯字的尾部弯曲。《周易》说:“屯卦,是阴柔、阳刚二气开始交合而出现艰难的形象。”</p>
  <p class="calibre6"><b class="calibre5">【参证】</b></p>
  <p class="block">甲文作<img alt="" class="inline" src="../images/07053.jpeg"/>,金文作<img alt="" class="inline" src="../images/06843.jpeg"/>、<img alt="" class="inline" src="../images/06984.jpeg"/>。构形不明。张舜徽《说文解字约注》:“皆象草木萌芽出土之形。”存参。</p>

可以发现,所有的注释引用都用的<sup class="calibre8">①</sup>这种形式,如果要将这种形式改成链接,肯定要增加a标签,并且a标签还需要具有唯一id,毫无疑问,序号可以作为唯一id的一部分。但是带圈数字序号不适宜直接拿来用,所以考虑先用sigil将所有HTML文件中的带圈数字序号依次替换为[1]这种形式。替换完成后用下面的正则表达式为注释引用包裹上a标签:

然后按下面的配置为注释内容编号也添加a标签,为p标签添加id作为注释引用的链接对象:

之所以这里不用ebooklib库编程来完成,是因为这样做效率显然更高。这样替换完以后,超链接的动作有了,但是每个HTML文件中有很多个id为ref1和ft1的a标签与p标签,点击以后无法链接到正确的位置,所以为每个单元进行编号,再在每个单元中为a标签和p标签的id加上单元编号,既可以保证id的唯一性,也能确保id一一对应。

然而原始的HTML文件并没有将每个单元包含在一对标签之内。好在原始HTML文件可以方便地看出每个单元的起始位置的特征:或者是<p class="calibre6">后面接着一个汉字,或者<p class="calibre6">后面接着一个img标签(无法显示的汉字的文字字形图),并且整个文件没有使用section标签,因此,我们可以考虑用section标签将每个单元包裹起来。

用下面的配置处理<p class="calibre6">后面接着一个汉字的情形,其中“(?!)”形式的表达式是所谓负向先行,即继续向前读入的字符(也就是后接字符,不过后接字符与先行这个名称感觉上有点拗☺)不得是“!”后面接着的表达式。也可用正向先行(即后接字符必须是“=”后面接着的表达式)来代替,即“(?=[一-龟])”,其中[一-龟]接近于整个汉字,除了最后那个难以输入的“”。与先行对应的回顾则在“?”后面加了一个向左的尖括号“(?<)”,正向仍然是“=”,负向仍然是“!”。

再用下面的配置处理<p class="calibre6">后面接着一个img标签的情形:

这样处理完后,每个文件最开头多了一个</section>,最后缺了一个</section>,可以通过将</body>替换为</section></body>在最后添加</section>,将<body>\n</section>或者</h1>\n</section>(有些HTML文件顶部body标签后有个h1标签)替换为<body>或</h1>删掉开头多余的</section>。另外,上面分两个步骤添加section标签的时候会添加一些空的section标签对,利用正则表达式<section>\s*\n\s*</section>替换为空,删掉空标签对。这样,对HTML文件的结构改造就完了。现在sigil没法处理为有关的p标签和a标签的id属性和href属性添加章节号了,这就可以去求助ebooklib和beautifulsoup了。下面的程序可以为有关的p标签和a标签的id属性和href属性添加章节号,请通过其中的注释了解代码的具体功能:

import os
from ebooklib import epub
import ebooklib
from bs4 import BeautifulSoup


def modify_epub(epub_path):
    # 读取epub文件
    book = epub.read_epub(epub_path)

    # 遍历所有的html文件
    for item in book.get_items_of_type(ebooklib.ITEM_DOCUMENT):
        # 解析html内容
        soup = BeautifulSoup(item.content, 'html.parser')
        # 找到所有section标签
        sections = soup.find_all('section')
        i = 1 # 单元编号
        # 遍历所有section
        for section in sections:
            # 选择所有id属性以ft开头的p标签
            tag_ps = section.select('p[id^="ft"]')
            # 遍历找到的p标签,为其id加上带单元编号的前缀
            for p in tag_ps:
                p['id'] = f'c{i}_{p["id"]}'
            # 选择所有id属性以ref开头的a标签
            tag_as = section.select('a[id^="ref"]')
            # 遍历找到的a标签,为其id加上带单元编号的前缀
            for a in tag_as:
                a['id'] = f'c{i}_{a["id"]}'
            # 选择所有href属性以#ft开头的a标签
            tad_a_fts = section.select('a[href^="#ft"]')
            # 遍历找到的a标签,为其href加上带单元编号的前缀
            for a in tad_a_fts:
                a['href'] = f'#c{i}_{a["href"][1:]}'
            # 选择所有href属性以#ref开头的a标签
            tad_a_refs = section.select('a[href^="#ref"]')
            # 遍历找到的a标签,为其href加上带单元编号的前缀
            for a in tad_a_refs:
                a['href'] = f'#c{i}_{a["href"][1:]}'
            # 处理完一个单元,单元编号增加
            i = i + 1

        # 更新修改后的html内容
        item.content = str(soup.prettify()).encode('utf-8')

    # 生成修改后的epub文件
    new_epub_path = os.path.splitext(epub_path)[0] + '_modified.epub'
    epub.write_epub(new_epub_path, book, {})


if __name__ == "__main__":
    epub_file_path = "F:\\downloads\\说文解字.epub"
    modify_epub(epub_file_path)

上面的代码在使用beautifulsoup查找元素时,只使用了css选择器方式,如果在查找元素时需要结合多个属性,或者过滤属性值时使用更复杂的正则表达式或函数/lambda表达式,可以使用find或find_all方法,这两个方法有以下几种使用方法:

# 使用find方法查找具有多个特定属性的<div>元素
recipe_card 
= soup.find('div', attrs={
    'class': 'recipe-card',
    'data-award': 'true',
    'data-category': 'main-course'
})




# 使用正则表达式匹配data-award属性值以"true"开头的<div>元素
recipe_card = soup.find('div', attrs={
    'class': 'recipe-card',
    'data-award': re.compile(r'^true')
})



# 使用lambda表达式匹配data-award属性值为"true"且类名包含"card"的<div>元素
recipe_card = soup.find('div', attrs={
    'class': lambda value: value and "card" in value,
    'data-award': lambda value: value == "true"
})

如果要根据元素内部的文本内容(textContent)查找元素,则可以使用string=参数,这个参数可以接受多种类型的过滤器,包括字符串、正则表达式、列表、函数或True。下面是几种用法的示例:

# 使用string参数查找包含特定文本的元素
story = soup.find(string="The Dormouse's story")
print(story)

# 使用正则表达式查找包含特定文本的元素
sisters = soup.find_all(string=re.compile("sister"))
for sister in sisters:
    print(sister)

# 使用列表查找包含特定文本的元素
names = soup.find_all(string=["Elsie", "Lacie", "Tillie"])
for name in names:
    print(name)

处理完后与最开始的HTML对应的内容变成了下面这个样子:

<section>
   <p class="calibre6">
    屮
    <img alt="" class="inline" src="../images/07310.jpeg"/>
    艸木初生也。象丨出形
    <a epub:type="noteref" href="#c1_ft1" id="c1_ref1">
     <sup class="calibre8">[1]</sup>
    </a>
    ,有枝茎也。古文或以为艸字。读若彻
    <a epub:type="noteref" href="#c1_ft2" id="c1_ref2">
     <sup class="calibre8">[2]</sup>
    </a>
    。凡屮之属皆从屮。尹彤说
    <a epub:type="noteref" href="#c1_ft3" id="c1_ref3">
     <sup class="calibre8">[3]</sup>
    </a>
    。
    <span>
     丑列切(chè)
    </span>
   </p>
   <p class="calibre6"><b class="calibre5">【注释】</b></p>
   <p class="block" epub:type="footnote" id="c1_ft1">
    <a href="#c1_ref1">[1]</a>
    丨:段玉裁《说文解字注》:“丨,读若囟,引而上行也。”丨出,即开出、长出。
   </p>
   <p class="block" epub:type="footnote" id="c1_ft2">
    <a href="#c1_ref2">[2]</a>
    读若彻:段玉裁《说文解字注》:“彻,通也。义存乎音。”
   </p>
   <p class="block" epub:type="footnote" id="c1_ft3">
    <a href="#c1_ref3">[3]</a>
    尹彤说:徐锴《说文解字系传》:“尹彤,当时说文字者。所谓‘博采通人’也。”段玉裁《说文解字注》:“三字当在‘凡屮’上。转写者倒之。”
   </p>
   <p class="calibre6"><b class="calibre5">【译文】</b></p>
   <p class="block">
    屮,草木初生。象草木长出地面的形状,而且有了枝茎。古文有时把它当作艸(草)字。音读像“彻”字。大凡屮的部属都从屮。这是尹彤的说法。
   </p>
   <p class="calibre6"><b class="calibre5">【参证】</b></p>
   <p class="block">
    甲文作
    <img alt="" class="inline" src="../images/06777.jpeg"/>
    ,金文作
    <img alt="" class="inline" src="../images/07205.jpeg"/>
    。象一棵初生的小草。商承祚《说文中之古文考》:“《石经·春秋经》:‘陨(从天降下的)霜不杀(冻杀)屮’,艸(cǎo)之古文作屮。案,屮、艸本一字。初生为屮,蔓延为艸。”
   </p>
  </section>
  <section>
   <p class="calibre6">
    屯
    <img alt="" class="inline" src="../images/06811.jpeg"/>
    难也。象艸木之初生,屯然而难
    <a epub:type="noteref" href="#c2_ft1" id="c2_ref1">
     <sup class="calibre8">[1]</sup>
    </a>
    。从屮贯一,一,地也;尾曲
    <a epub:type="noteref" href="#c2_ft2" id="c2_ref2">
     <sup class="calibre8">[2]</sup>
    </a>
    。《易》曰:“屯,刚柔始交而难生。”
    <a epub:type="noteref" href="#c2_ft3" id="c2_ref3">
     <sup class="calibre8">[3]</sup>
    </a>
    <span>
     陟伦切(zhūn)
    </span>
   </p>
   <p class="calibre6"><b class="calibre5">【注释】</b></p>
   <p class="block" epub:type="footnote" id="c2_ft1">
    <a href="#c2_ref1">[1]</a>
    屯然:曲折之貌。然,助词。
   </p>
   <p class="block" epub:type="footnote" id="c2_ft2">
    <a href="#c2_ref2">[2]</a>
    尾曲:徐灏《说文解字注笺》:“此篆从屮曲之,以象难生之意。”
   </p>
   <p class="block" epub:type="footnote" id="c2_ft3">
    <a href="#c2_ref3">[3]</a>
    屯,刚柔始交而难生:语见《周易·屯卦》。刚,指阳。柔,指阴。
   </p>
   <p class="calibre6"><b class="calibre5">【译文】</b></p>
   <p class="block">
    屯,艰难。象草木初生,曲折而又艰难的形状。其形由屮贯穿一构成,一代表地面;屯字的尾部弯曲。《周易》说:“屯卦,是阴柔、阳刚二气开始交合而出现艰难的形象。”
   </p>
   <p class="calibre6"><b class="calibre5">【参证】</b></p>
   <p class="block">
    甲文作
    <img alt="" class="inline" src="../images/07053.jpeg"/>
    ,金文作
    <img alt="" class="inline" src="../images/06843.jpeg"/>
    、
    <img alt="" class="inline" src="../images/06984.jpeg"/>
    。构形不明。张舜徽《说文解字约注》:“皆象草木萌芽出土之形。”存参。

支持弹出式注释的epub正式改造完成,如下图。有强迫症的,还可以将注释编号与注释引用重新替换回带圈数字序号☺。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yivifu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值