Last active
November 20, 2017 09:29
-
-
Save missdeer/ceb5613024d8179e16429f94abe5abf0 to your computer and use it in GitHub Desktop.
convert book from https://www.hi-pda.com/forum/viewthread.php?tid=2207689
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 = `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, ` `, " ", -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