网上下载一本说文解字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正式改造完成,如下图。有强迫症的,还可以将注释编号与注释引用重新替换回带圈数字序号☺。