0
点赞
收藏
分享

微信扫一扫

Go环境搭建(vscode调试)

一世独秀 2024-11-04 阅读 15
golangepub

如题,本篇简单分析如何使用go语言解析epub格式的电子书,获取其内部资源内容。

EPUB格式

首先我们需要了解epub格式具有哪些特点。

已知的是,epub是一种类似doc或者pdf,可以提供图文并茂电子书的格式。

那么我们首先使用二进制编辑器打开,看看能收集到哪些信息:

在这里插入图片描述

可以看到是 50 4B (PK) 开头的文件,也就是说,他本身根本就是个 ZIP 压缩文件。

经常上班的朋友都知道,zip格式是比较简单的压缩格式,其文件头部分包含了整篇文件的目录索引。那么我们直接通过压缩软件打开EPUB格式一探究竟。

在这里插入图片描述

非常顺利,我们发现epub就是个zip包,其中的内容大概包括配置文件、html或xml、图片资源等等。
那么我们先使用 golang 读取这个文件及其内容。

解包

首先,我使用 archive/zip 这个库来解包

	// 创建ZIP读取器
	info, _ := file.Stat()
	zipReader, err := zip.NewReader(file, info.Size())
	if err != nil {
		fmt.Println("Error creating ZIP reader:", err)
		return
	}

	// 在内存中存储文本文件内容
	textFiles := make(map[string][]byte)

	for _, zipFile := range zipReader.File {

		var content bytes.Buffer

		// 打开并读取ZIP条目的内容
		rc, err := zipFile.Open()
		if err != nil {
			fmt.Println("Error opening ZIP entry for reading:", err)
			continue
		}
		defer rc.Close()

		_, err = io.Copy(&content, rc)
		if err != nil {
			fmt.Println("Error copying data from ZIP entry:", err)
			continue
		}

		textFiles[zipFile.Name] = content.Bytes()
	}

我将文件路径作为 key,文件内容作为 value 存储到 textFiles 这个 map 中。

定位资源

这里省略逐个文件探索的过程,直接给出解析思路。
在这部分我使用 github.com/PuerkitoBio/goquery 库来解析 xml、html 文件

首先,我们要解析 META-INF/container.xml 这个文件,这个文件应当是整个 epub 的入口。我们来看看这个文件的内容大概是什么样的。

<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
<rootfiles>
<rootfile full-path="OPS/content.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>

可以见得,入口文件在 full-path 属性指定了一个后缀为 opf 的文件作为引导,除此之外没有多少有用的信息。其实这个 OPS/content.opf 据我的实践,在大部分情况下都固定是这位置。但我还是建议大家从头解析,因为你不确定你遇到的格式是否遵循主流约定。

以下三步是从头解析,或者你也可以跳过前两步,直接解析 OPS/content.opf 但我不推荐你这样做。

// 使用 go-query 解析 META-INF/container.xml
	metaDoc, metaErr := goquery.NewDocumentFromReader(bytes.NewReader([]byte(textFiles["META-INF/container.xml"])))
	if metaErr != nil {
		log.Printf("读取 META-INF 出错: %v", metaErr)
		//c.JSON(core.RetErr(metaErr.Error()))
		return
	}
// 获取opf文件路径
	confPath, _ := metaDoc.Find("rootfile").Attr("full-path")
// 解析opf文件
	confDoc, confErr := goquery.NewDocumentFromReader(bytes.NewReader([]byte(textFiles[confPath])))
	if confErr != nil {
		log.Printf("读取配置文件 %s 出错: %v", confPath, confErr)
	}

这里我们已经解析了opf文件,实际上,我们通常会看看opf的文件结构是什么样的,这里补充一下吧。opf是纯文本格式,可以直接通过记事本打开。

在这里插入图片描述
红色标注的分别是封面资源、书籍内容资源。注意,这个xml的格式并不规范,这两部分标签应当是平级的。(下方红框处有多余的缩进,这是错误的)

解析内容

在头部的 metadata 标签中,还会提供一些额外的信息,感兴趣的小伙伴可以分析一下。简单看起来包括语言、作者等信息。

//计算 opf 文件的相对路径
	opfPath := confPath[:strings.LastIndex(confPath, "/")+1]
// 解析opf文件
	confDoc, confErr := goquery.NewDocumentFromReader(bytes.NewReader([]byte(textFiles[confPath])))
	if confErr != nil {
		log.Printf("读取配置文件 %s 出错: %v", confPath, confErr)
	}
// 读取作者(如果有的话)
    confDoc.Find("dc\\:creator").Eq(0).Text()

注意,我们核心关注的是,解析 manifest 标签中的内容,该文件会有其他标签容易混淆。例如 spine 标签,其中也有章节结构的,但我们要解析的是 manifest 标签。

// 解析 manifest 标签
confDoc.Find("manifest").Find("item").Each(func(i int, item *goquery.Selection) {
		mediaType, _ := item.Attr("media-type")
		//log.Printf("正在处理第 %d 个章节, href=%s, media-type=%s", i, item.AttrOr("href", ""), mediaType)
	})

我的思路比较懒,因为只关注类型为 html / xml 等内容,所以我判断 media-type 是否以 “ml” 结尾。
小伙伴们可以做一些更复杂的逻辑,例如进一步解析图片、css、或其他内容。

	if strings.HasSuffix(mediaType, "ml") {
			//内容页
			chapterDoc, chapterErr := goquery.NewDocumentFromReader(bytes.NewReader(textFiles[opfPath+item.AttrOr("href", "")]))
			if chapterErr != nil {
				log.Printf("读取章节文件 %s 出错: %v", item.AttrOr("href", ""), chapterErr)
			}

			//处理章节内容
			var strBuilder strings.Builder
			chapterDoc.Find("*").Each(func(i int, item *goquery.Selection) {
				strBuilder.WriteString(item.Text())
				strBuilder.WriteString("\n")
			})

			内容字串 := strBuilder.String()

		} else if strings.HasPrefix(mediaType, "image/") {
			imgCover, _ = item.Attr("href")
			//提取图片
			图片文件 := textFiles[opfPath+imgCover]
		}

本篇完

举报

相关推荐

0 条评论