/
epub.go
175 lines (153 loc) · 4.67 KB
/
epub.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Package epub provides a mechanism for generating a (nominally) valid ePub 3 file from a list
// of metadata and content.
package epub
import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"path/filepath"
"text/template"
)
const (
mimeTypeFileName = "mimetype"
// mimetype is the required mime type of an epub file
mimetype = "application/epub+zip"
// TODO: these files should be distributed as part of the binary, probably.
// templateGlob is the location of the epub xml templates on your file system.
templateGlob = "./templates/*.tpl"
)
// EpubArchive contains the necessary structs to generate an epub
type EpubArchive struct {
zip.Writer
Opf Opf
}
// getMetadata gets the book metadata from a JSON file.
func (w *EpubArchive) getMetadata(filePath string) (Metadata, error) {
var metadata Metadata
bytes, err := ioutil.ReadFile(filePath)
if err != nil {
log.Printf("Unable to read metadata file: %v", err)
return metadata, err
}
err = json.Unmarshal(bytes, &metadata)
if err != nil {
log.Printf("Unable to unmarshall metadat file: %v", err)
return metadata, err
}
return metadata, nil
}
// MetaInfFile contains information about the meta inf files which must be included for an epub
// to be valid.
type MetaInfFile struct {
Name string
Content []byte
}
// buildMetaInfFiles creates the xml files for the epub via the information specified in the opf.
func (w *EpubArchive) buildMetaInfFiles(title string, opf Opf) []MetaInfFile {
var metaInfFiles []MetaInfFile
tmpl := template.Must(template.ParseGlob(templateGlob))
buf := new(bytes.Buffer)
err := tmpl.ExecuteTemplate(buf, "container.xml.tpl", opf)
if err != nil {
log.Fatal("template execution: %s", err) // TODO: this should return as err
}
containerXml := MetaInfFile{Name: "META-INF/container.xml",
Content: buf.Bytes()}
metaInfFiles = append(metaInfFiles, containerXml)
buf = new(bytes.Buffer)
err = tmpl.ExecuteTemplate(buf, "opf.xml.tpl", opf.RootFiles[0])
if err != nil {
log.Fatalf("template execution: %s", err)
}
opfXml := MetaInfFile{Name: fmt.Sprintf("OEBPS/%s.opf", title),
Content: buf.Bytes()}
metaInfFiles = append(metaInfFiles, opfXml)
return metaInfFiles
}
// Build generates an epub file if possible from the title, opf, and chapters
func (w *EpubArchive) Build(title string, opf Opf, chapters []Chapter) ([]byte, error) {
buf := new(bytes.Buffer)
w.Writer = *zip.NewWriter(buf)
// The mimetype must always be first in the file, and must not be compressed.
header := &zip.FileHeader{
Name: mimeTypeFileName,
Method: zip.Store,
}
f, err := w.CreateHeader(header)
if err != nil {
log.Printf("%v", err)
return nil, err
}
_, err = f.Write([]byte(mimetype))
if err != nil {
log.Printf("%v", err)
return nil, err
}
tocManifest := ManifestItem{
Id: "tocref",
Href: "chapters/toc.xhtml",
MediaType: "application/xhtml+xml",
Properties: []string{"nav"},
}
opf.RootFiles = append(opf.RootFiles, OpfRootFile{})
opf.RootFiles[0].Manifest.ManifestItems = append(opf.RootFiles[0].Manifest.ManifestItems,
tocManifest)
metaFiles := w.buildMetaInfFiles(title, opf)
w.addMetaInfFileToArchive(metaFiles)
w.addChaptersToArchive(chapters)
w.addChaptersToArchive([]Chapter{w.buildToc(opf)})
err = w.Writer.Close()
if err != nil {
log.Printf("%v", err)
return nil, err
}
return buf.Bytes(), nil
}
// buildToc generates the table of contents for the epub file.
func (w *EpubArchive) buildToc(opf Opf) Chapter {
tmpl := template.Must(template.ParseGlob(templateGlob))
buf := new(bytes.Buffer)
err := tmpl.ExecuteTemplate(buf, "toc.xhtml.tpl", opf.RootFiles[0].Manifest)
if err != nil {
log.Fatalf("template execution: %s", err)
}
toc := Chapter{FileName: "chapters/toc.xhtml",
Contents: string(buf.Bytes())}
return toc
}
//TODO: chapter and meta-inf file can likely be combined into one info that takes bytes.
// addMetaInfFileToArchive adds the meta-inf files to the archive.
func (w *EpubArchive) addMetaInfFileToArchive(files []MetaInfFile) error {
for _, file := range files {
f, err := w.Writer.Create(file.Name)
if err != nil {
log.Printf("%v", err)
return err
}
_, err = f.Write(file.Content)
if err != nil {
log.Printf("%v", err)
return err
}
}
return nil
}
// addChaptersToArchive appends the content for each chapter with the specified filename to the zip.
func (w *EpubArchive) addChaptersToArchive(chapters []Chapter) error {
for _, chapter := range chapters {
f, err := w.Writer.Create(filepath.Join("OEBPS", chapter.FileName))
if err != nil {
log.Printf("%v", err)
return err
}
_, err = f.Write([]byte(chapter.Contents))
if err != nil {
log.Printf("%v", err)
return err
}
}
return nil
}