Created
November 19, 2017 10:50
-
-
Save missdeer/80266e75aadfd9c83dbe31096aea796c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"bytes" | |
"errors" | |
"fmt" | |
"io/ioutil" | |
"log" | |
"os" | |
"regexp" | |
"strings" | |
"time" | |
"unicode/utf8" | |
"github.com/dfordsoft/golib/ic" | |
) | |
const ( | |
contentFile = `index/js/book.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>类库大魔王制作</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")) | |
if len(lines) == 3 { | |
title = string(ic.Convert("gbk", "utf-8", lines[0])) | |
content = string(ic.Convert("gbk", "utf-8", lines[2])) | |
} else if len(lines) == 2 { | |
content = string(ic.Convert("gbk", "utf-8", lines[1])) | |
idx := strings.Index(content, `;document.write`) | |
if idx > 0 { | |
title = content[:idx+1] | |
content = content[idx+1:] | |
} | |
} else { | |
log.Println("not valid content file", path) | |
return | |
} | |
title = strings.Replace(title, `宋体`, "", -1) | |
title = strings.Replace(title, ` `, " ", -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(`^booklist\[[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 := 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