Golang开源EPUB3电子书生成工具EPUBGen
Golang开源EPUB3电子书生成工具EPUBGen 亲爱的 Go 开发者们,
EPUBGen 是我学习 Go 编程语言的第一个项目。我决定构建一个命令行程序,以帮助生成和打包符合 EPUB3 标准的电子书。与其构建一个花哨的视觉编辑器来创建电子书,为什么不使用单个 HTML 文件作为电子书的源文件呢?作为一名开发者,我可以使用许多功能强大的免费开源 IDE 和文本编辑器(例如 Eclipse、ItelliJ IDEA Community 和 VSCodium/VSCode)来创建 HTML 文件。如果您更喜欢花哨的视觉编辑器,可以看看 Sigil。
EPUBGen 是一个文本处理器,它读取并处理 HTML 源文件,并在 EPUB3 预期的目录结构中生成所有文件。然后,可以通过运行工具 EPUBCheck 来检查生成的目录及其子目录和内容,并将其打包成 .epub 文件。
完整的源代码和示例电子书可以在 GitHub 上找到。
请随时将这篇帖子转发给您的朋友和同事。如果您遇到任何问题,请发送电子邮件至 roslan@meridiand.com。
更多关于Golang开源EPUB3电子书生成工具EPUBGen的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang开源EPUB3电子书生成工具EPUBGen的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
这是一个非常棒的Go语言项目!EPUBGen作为学习Go的第一个项目,展示了很好的工程实践和实用性。让我从技术角度分析一下这个工具的实现要点。
核心实现分析
EPUB3电子书生成的关键在于正确的目录结构和文件格式。以下是核心的Go实现示例:
// EPUB包结构生成示例
package main
import (
"archive/zip"
"encoding/xml"
"io"
"os"
"path/filepath"
"strings"
)
// MIME类型文件
const containerXML = `<?xml version="1.0" encoding="UTF-8"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>`
// EPUB生成器结构
type EPUBGenerator struct {
Title string
Author string
HTMLContent string
OutputPath string
}
// 生成EPUB文件
func (g *EPUBGenerator) Generate() error {
// 创建临时目录结构
tempDir, err := os.MkdirTemp("", "epubgen-*")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
// 创建必需目录
dirs := []string{
"META-INF",
"OEBPS",
"OEBPS/images",
"OEBPS/styles",
}
for _, dir := range dirs {
if err := os.MkdirAll(filepath.Join(tempDir, dir), 0755); err != nil {
return err
}
}
// 写入容器文件
if err := os.WriteFile(
filepath.Join(tempDir, "META-INF/container.xml"),
[]byte(containerXML),
0644,
); err != nil {
return err
}
// 生成content.opf文件
opfContent := g.generateOPF()
if err := os.WriteFile(
filepath.Join(tempDir, "OEBPS/content.opf"),
[]byte(opfContent),
0644,
); err != nil {
return err
}
// 写入HTML内容
if err := os.WriteFile(
filepath.Join(tempDir, "OEBPS/chapter1.xhtml"),
[]byte(g.HTMLContent),
0644,
); err != nil {
return err
}
// 打包为ZIP文件(EPUB格式)
return g.createEPUB(tempDir)
}
// 生成OPF包文档
func (g *EPUBGenerator) generateOPF() string {
return `<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:identifier id="uid">urn:uuid:` + generateUUID() + `</dc:identifier>
<dc:title>` + g.Title + `</dc:title>
<dc:creator>` + g.Author + `</dc:creator>
<dc:language>en</dc:language>
<meta property="dcterms:modified">` + getCurrentTime() + `</meta>
</metadata>
<manifest>
<item id="toc" href="toc.xhtml" media-type="application/xhtml+xml" properties="nav"/>
<item id="chapter1" href="chapter1.xhtml" media-type="application/xhtml+xml"/>
<item id="css" href="styles/style.css" media-type="text/css"/>
</manifest>
<spine>
<itemref idref="chapter1"/>
</spine>
</package>`
}
// 创建EPUB文件(ZIP格式)
func (g *EPUBGenerator) createEPUB(sourceDir string) error {
zipFile, err := os.Create(g.OutputPath)
if err != nil {
return err
}
defer zipFile.Close()
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// 遍历目录并添加文件到ZIP
return filepath.Walk(sourceDir, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
relPath, err := filepath.Rel(sourceDir, filePath)
if err != nil {
return err
}
// EPUB要求mimetype必须是ZIP中的第一个文件且不压缩
if relPath == "mimetype" {
return g.addMimetype(zipWriter, filePath)
}
return g.addFileToZip(zipWriter, filePath, relPath)
})
}
// 添加mimetype文件(必须不压缩)
func (g *EPUBGenerator) addMimetype(zipWriter *zip.Writer, filePath string) error {
header := &zip.FileHeader{
Name: "mimetype",
Method: zip.Store, // 不压缩
}
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
data, err := os.ReadFile(filePath)
if err != nil {
return err
}
_, err = writer.Write(data)
return err
}
// 添加普通文件到ZIP
func (g *EPUBGenerator) addFileToZip(zipWriter *zip.Writer, filePath, relPath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
writer, err := zipWriter.Create(relPath)
if err != nil {
return err
}
_, err = io.Copy(writer, file)
return err
}
HTML处理示例
// HTML解析和处理
package processor
import (
"golang.org/x/net/html"
"strings"
)
type HTMLProcessor struct {
SourceHTML string
}
// 提取标题
func (p *HTMLProcessor) ExtractTitle() string {
doc, err := html.Parse(strings.NewReader(p.SourceHTML))
if err != nil {
return ""
}
var title string
var traverse func(*html.Node)
traverse = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "title" {
if n.FirstChild != nil {
title = n.FirstChild.Data
}
return
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
traverse(c)
}
}
traverse(doc)
return title
}
// 清理HTML并转换为XHTML
func (p *HTMLProcessor) ToXHTML() string {
// 实现HTML到XHTML的转换逻辑
// 包括:闭合所有标签、添加XML声明、确保属性引号等
return `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
<head>
<title>Converted Document</title>
</head>
<body>
` + p.cleanHTML() + `
</body>
</html>`
}
// 清理HTML内容
func (p *HTMLProcessor) cleanHTML() string {
// 移除不需要的标签,确保XHTML兼容性
// 这里可以添加具体的清理逻辑
return p.SourceHTML
}
命令行接口示例
// 命令行工具实现
package main
import (
"flag"
"fmt"
"log"
"os"
)
func main() {
// 定义命令行参数
inputFile := flag.String("input", "", "输入HTML文件路径")
outputFile := flag.String("output", "output.epub", "输出EPUB文件路径")
title := flag.String("title", "", "电子书标题")
author := flag.String("author", "Unknown", "作者")
flag.Parse()
// 验证输入
if *inputFile == "" {
log.Fatal("必须指定输入HTML文件")
}
// 读取HTML内容
htmlContent, err := os.ReadFile(*inputFile)
if err != nil {
log.Fatalf("读取文件失败: %v", err)
}
// 创建生成器
generator := &EPUBGenerator{
Title: *title,
Author: *author,
HTMLContent: string(htmlContent),
OutputPath: *outputFile,
}
// 生成EPUB
if err := generator.Generate(); err != nil {
log.Fatalf("生成EPUB失败: %v", err)
}
fmt.Printf("EPUB文件已生成: %s\n", *outputFile)
}
测试示例
// 单元测试
package main
import (
"testing"
"os"
)
func TestEPUBGeneration(t *testing.T) {
tempFile := "test_output.epub"
defer os.Remove(tempFile)
generator := &EPUBGenerator{
Title: "测试电子书",
Author: "测试作者",
HTMLContent: "<h1>测试内容</h1><p>这是一个测试段落。</p>",
OutputPath: tempFile,
}
if err := generator.Generate(); err != nil {
t.Fatalf("生成失败: %v", err)
}
// 验证文件存在
if _, err := os.Stat(tempFile); os.IsNotExist(err) {
t.Fatal("EPUB文件未生成")
}
// 可以添加更多验证,如ZIP结构、XML有效性等
}
这个项目很好地展示了Go在文件处理、XML生成、ZIP打包和命令行工具开发方面的能力。通过将HTML转换为符合EPUB3标准的结构,EPUBGen为开发者提供了一个简单而有效的电子书生成方案。


