Skip to content

Instantly share code, notes, and snippets.

@missdeer
Last active November 20, 2017 09:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save missdeer/ceb5613024d8179e16429f94abe5abf0 to your computer and use it in GitHub Desktop.
Save missdeer/ceb5613024d8179e16429f94abe5abf0 to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"regexp"
"strings"
"time"
"unicode/utf8"
"github.com/dfordsoft/golib/ic"
)
const (
contentFile = `jscss/content.js`
)
// Mobi generate files that used to make a mobi file by kindlegen
type Mobi struct {
title string
id string
uid int64
count int
tocTmp *os.File
contentTmp *os.File
navTmp *os.File
}
var (
mobi *Mobi
contentHTMLTemplate = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>%s</title>
<style type="text/css">
@font-face{
font-family: "CustomFont";
src: url(fonts/CustomFont.ttf);
}
body{
font-family: "CustomFont";
font-size: 1.2em;
margin:0 5px;
}
h1{
font-family: "CustomFont";
font-size:4em;
font-weight:bold;
}
h2 {
font-family: "CustomFont";
font-size: 1.2em;
font-weight: bold;
margin:0;
}
a {
color: inherit;
text-decoration: inherit;
cursor: default
}
a[href] {
color: blue;
text-decoration: underline;
cursor: pointer
}
p{
font-family: "CustomFont";
text-indent:1.5em;
line-height:1.3em;
margin-top:0;
margin-bottom:0;
}
.italic {
font-style: italic
}
.do_article_title{
line-height:1.5em;
page-break-before: always;
}
#cover{
text-align:center;
}
#toc{
page-break-before: always;
}
#content{
margin-top:10px;
page-break-after: always;
}
</style>
</head>
<body>
<div id="cover">
<h1 id="title">%s</h1>
<a href="#content">跳到第一篇</a><br />%s
</div>
<div id="toc">
<h2>目录</h2>
<ol>
%s
</ol>
</div>
<mbp:pagebreak></mbp:pagebreak>
<div id="content">
%s
</div>
</body>
</html>`
tocNCXTemplate = `<?xml version="1.0" encoding="UTF-8"?>
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="zh-CN">
<head>
<meta name="dtb:uid" content="%d" />
<meta name="dtb:depth" content="4" />
<meta name="dtb:totalPageCount" content="0" />
<meta name="dtb:maxPageNumber" content="0" />
</head>
<docTitle><text>%s</text></docTitle>
<docAuthor><text>类库大魔王</text></docAuthor>
<navMap>
<navPoint class="book">
<navLabel><text>%s</text></navLabel>
<content src="content.html" />
%s
</navPoint>
</navMap>
</ncx>`
contentOPFTemplate = `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="uid">
<metadata>
<dc-metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
<dc:title>%s</dc:title>
<dc:language>zh-CN</dc:language>
<dc:identifier id="uid">%d%s</dc:identifier>
<dc:creator>GetNovel</dc:creator>
<dc:publisher>类库大魔王</dc:publisher>
<dc:subject>%s</dc:subject>
<dc:date>%s</dc:date>
<dc:description></dc:description>
</dc-metadata>
</metadata>
<manifest>
<item id="content" media-type="application/xhtml+xml" href="content.html"></item>
<item id="toc" media-type="application/x-dtbncx+xml" href="toc.ncx"></item>
</manifest>
<spine toc="toc">
<itemref idref="content"/>
</spine>
<guide>
<reference type="start" title="start" href="content.html#content"></reference>
<reference type="toc" title="toc" href="content.html#toc"></reference>
<reference type="text" title="cover" href="content.html#cover"></reference>
</guide>
</package>
`
)
// Begin prepare book environment
func (m *Mobi) Begin() {
var err error
m.tocTmp, err = os.OpenFile(`toc.tmp`, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Println("opening file toc.tmp for writing failed ", err)
return
}
m.contentTmp, err = os.OpenFile(`content.tmp`, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Println("opening file content.tmp for writing failed ", err)
return
}
m.navTmp, err = os.OpenFile(`nav.tmp`, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Println("opening file nav.tmp for writing failed ", err)
return
}
}
// End generate files that kindlegen needs
func (m *Mobi) End() {
m.contentTmp.Close()
m.tocTmp.Close()
m.navTmp.Close()
m.writeContentHTML()
m.writeTocNCX()
m.writeContentOPF()
os.Remove(`toc.tmp`)
os.Remove(`content.tmp`)
os.Remove(`nav.tmp`)
// cmd := exec.Command("D:\\Tools\\kindlegen.exe",
// "-c2",
// "-o",
// fmt.Sprintf("%s.mobi", m.title),
// "content.opf")
// cmd.Dir = m.title
// fmt.Println("Generating", fmt.Sprintf("%s/%s.mobi", m.title, m.title))
// err := cmd.Run()
// if err != nil {
// fmt.Println("Generating", fmt.Sprintf("%s/%s.mobi", m.title, m.title), "failed:", err, cmd.Args)
// } else {
// fmt.Println("Generated", fmt.Sprintf("%s/%s.mobi", m.title, m.title))
// }
}
// StartSection append a section
func (m *Mobi) StartSection(sectionTitle, sectionID string) {
m.contentTmp.WriteString(fmt.Sprintf(`<div id="section_%s" class="section">`, sectionID))
m.tocTmp.WriteString(fmt.Sprintf(`<li><h3><a href="#section_%s">%s</a></h3></li>`, sectionID, sectionTitle))
m.navTmp.WriteString(fmt.Sprintf(`<navPoint class="chapter" id="%d" playOrder="1"><navLabel><text>%s</text></navLabel><content src="content.html#section_%s" /></navPoint>`,
m.count, sectionTitle, sectionID))
m.count++
}
// EndSection ends section
func (m *Mobi) EndSection() {
m.contentTmp.WriteString(`</div>`)
}
// AppendArticle append book content
func (m *Mobi) AppendArticle(articleTitle, articleURL, articleContent string) {
m.contentTmp.WriteString(fmt.Sprintf(`<div id="article_%d" class="article"><h2 class="do_article_title"><a href="%s">%s</a></h2><div><p>%s</p></div></div>`,
m.count, articleURL, articleTitle, articleContent))
m.tocTmp.WriteString(fmt.Sprintf(`<li><a href="#article_%d">%s</a></li>`, m.count, articleTitle))
m.navTmp.WriteString(fmt.Sprintf(`<navPoint class="chapter" id="%d" playOrder="2"><navLabel><text>%s</text></navLabel><content src="content.html#article_%d" /></navPoint>`,
m.count, articleTitle, m.count))
m.count++
}
// SetTitle set book title
func (m *Mobi) SetTitle(title string) {
m.title = title
os.Mkdir(title, 0644)
}
// SetID set current section id
func (m *Mobi) SetID(id string) {
m.id = id
}
func (m *Mobi) writeContentHTML() {
contentHTML, err := os.OpenFile(m.title+`/content.html`, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Println("opening file content.html for writing failed ", err)
return
}
tocTmp, err := os.OpenFile(`toc.tmp`, os.O_RDONLY, 0644)
if err != nil {
log.Println("opening file toc.tmp for reading failed ", err)
return
}
tocC, err := ioutil.ReadAll(tocTmp)
if err != nil {
log.Println("reading file toc.tmp failed ", err)
return
}
contentTmp, err := os.OpenFile(`content.tmp`, os.O_RDONLY, 0644)
if err != nil {
log.Println("opening file content.tmp for reading failed ", err)
return
}
contentC, err := ioutil.ReadAll(contentTmp)
if err != nil {
log.Println("reading file content.tmp failed ", err)
return
}
contentHTML.WriteString(fmt.Sprintf(contentHTMLTemplate, m.title, m.title, time.Now().String(),
string(tocC), string(contentC)))
contentHTML.Close()
tocTmp.Close()
contentTmp.Close()
}
func (m *Mobi) writeContentOPF() {
contentOPF, err := os.OpenFile(m.title+"/content.opf", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Println("opening file content.opf for writing failed ", err)
return
}
contentOPF.WriteString(fmt.Sprintf(contentOPFTemplate,
m.title, m.uid, time.Now().String(), m.title, time.Now().String()))
contentOPF.Close()
}
func (m *Mobi) writeTocNCX() {
tocNCX, err := os.OpenFile(m.title+"/toc.ncx", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Println("opening file toc.ncx for writing failed ", err)
return
}
m.uid = time.Now().UnixNano()
navTmp, err := os.OpenFile(`nav.tmp`, os.O_RDONLY, 0644)
if err != nil {
log.Println("opening file nav.tmp for reading failed ", err)
return
}
navC, err := ioutil.ReadAll(navTmp)
if err != nil {
log.Println("reading file nav.tmp failed ", err)
return
}
tocNCX.WriteString(fmt.Sprintf(tocNCXTemplate, m.uid, m.title, m.title, string(navC)))
tocNCX.Close()
navTmp.Close()
}
func isFileExists(path string) (bool, error) {
stat, err := os.Stat(path)
if err == nil {
if stat.Mode()&os.ModeType == 0 {
return true, nil
}
return false, errors.New(path + " exists but is not regular file")
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func pickFile(path string) (title string, content string) {
//fmt.Println(path)
contentFileFd, err := os.OpenFile(path, os.O_RDONLY, 0644)
if err != nil {
log.Println("opening file", path, "for reading failed ", err)
return
}
defer contentFileFd.Close()
b, e := ioutil.ReadAll(contentFileFd)
if e != nil {
log.Println("reading file", path, "failed:", e)
}
lines := bytes.Split(b, []byte("\r\n"))
title = string(ic.Convert("gbk", "utf-8", lines[0]))
content = string(ic.Convert("gbk", "utf-8", lines[2]))
title = strings.Replace(title, `宋体`, "", -1)
title = strings.Replace(title, `&nbsp;`, " ", -1)
for i, s := range title {
if utf8.RuneLen(s) > 1 {
title = title[i:]
break
}
}
titleEndingStr := `</b><p>");`
idx := strings.LastIndex(title, titleEndingStr)
if idx > 0 {
title = title[:len(title)-len(titleEndingStr)]
}
if r := []rune(title); len(r) > 50 {
mobi.AppendArticle(`本章简介`, "", title)
return
}
contentLeadingStr := `document.write ('  `
idx = strings.Index(content, contentLeadingStr)
if idx >= 0 {
content = content[len(contentLeadingStr):]
}
contentEndingStr := `')`
idx = strings.LastIndex(content, contentEndingStr)
if idx > 0 {
content = content[:len(content)-len(contentEndingStr)]
}
content = strings.Replace(content, `  `, "", -1)
mobi.AppendArticle(title, "", content)
return
}
func makeBook(title string, id string, author string) {
//fmt.Println(title, id, author)
bookEnd := false
sectionAvailable := true
for i := 1; !bookEnd && sectionAvailable; i++ {
sectionAvailable = false
sectionEnd := false
for j := 1; !sectionEnd; j++ {
filePath := fmt.Sprintf("txt/%s/txt/%d_%d.txt", id, i, j)
if b, _ := isFileExists(filePath); b {
pickFile(filePath)
sectionAvailable = true
continue
}
filePath = fmt.Sprintf("txt/%s/txt/%d_%.2d.txt", id, i, j)
if b, _ := isFileExists(filePath); b {
pickFile(filePath)
sectionAvailable = true
continue
}
filePath = fmt.Sprintf("txt/%s/txt/%.2d_%.2d.txt", id, i, j)
if b, _ := isFileExists(filePath); b {
pickFile(filePath)
sectionAvailable = true
continue
}
filePath = fmt.Sprintf("txt/%s/txt/%.2d_%d.txt", id, i, j)
if b, _ := isFileExists(filePath); b {
pickFile(filePath)
sectionAvailable = true
continue
}
filePath = fmt.Sprintf("txt/%s/txt/%.3d_%.2d.txt", id, i, j)
if b, _ := isFileExists(filePath); b {
pickFile(filePath)
sectionAvailable = true
continue
}
filePath = fmt.Sprintf("txt/%s/txt/%.3d_%d.txt", id, i, j)
if b, _ := isFileExists(filePath); b {
pickFile(filePath)
sectionAvailable = true
continue
}
sectionEnd = true
}
}
}
func main() {
contentFileFd, err := os.OpenFile(contentFile, os.O_RDONLY, 0644)
if err != nil {
log.Println("opening file ", contentFile, "for reading failed ", err)
return
}
defer contentFileFd.Close()
scanner := bufio.NewScanner(contentFileFd)
scanner.Split(bufio.ScanLines)
r, _ := regexp.Compile(`content\[[0-9]+\]=\['([^']+)','([^']+)','([^']+)','([^']+)'\]`)
for scanner.Scan() {
line := ic.ConvertString("gbk", "utf-8", scanner.Text())
ss := r.FindAllStringSubmatch(line, -1)
if len(ss) > 0 {
author := strings.Replace(ss[0][4], "/", "_", -1)
id := ss[0][3]
title := strings.TrimSpace(ss[0][1])
mobi = &Mobi{}
mobi.Begin()
mobi.SetTitle(title)
mobi.SetID(id)
mobi.StartSection(title, id)
makeBook(title, id, author)
mobi.EndSection()
mobi.End()
mobi = nil
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment