使用epub-tools抓取网页生成epub电子书,用epubcheck进行校验

2010年03月4日

最近在看epub格式的电子书,于是对epub格式的电子书有一点了解。

然后下载了epub-tools和epubcheck的代码,写了个小程序,通过分析网上小说主页和章节内容,然后生成epub格式的电子书。

epub-tools 来源:http://code.google.com/p/epub-tools/

epubcheck来源:http://code.google.com/p/epubcheck/

部分代码如下:

————————————————–

package com.lizongbo.epub;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import com.adobe.dp.epub.io.DataSource;

/**

*网上图片的数据源

*/

public class ImgFileUrlDataSource extends DataSource {

String url;

public ImgFileUrlDataSource(String url) {
this.url = url;
}

public InputStream getInputStream() throws IOException {
return new URL(url).openStream();
}

}

————————————————–

package com.lizongbo.epub;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;

import com.adobe.dp.epub.io.BufferedDataSource;
import com.adobe.dp.epub.io.OCFContainerWriter;
import com.adobe.dp.epub.io.StringDataSource;
import com.adobe.dp.epub.ncx.TOCEntry;
import com.adobe.dp.epub.opf.BitmapImageResource;
import com.adobe.dp.epub.opf.NCXResource;
import com.adobe.dp.epub.opf.OPSResource;
import com.adobe.dp.epub.opf.Publication;
import com.adobe.dp.epub.opf.Resource;
import com.adobe.dp.epub.ops.Element;
import com.adobe.dp.epub.ops.OPSDocument;
import com.adobe.epubcheck.api.EpubCheck;
import com.adobe.epubcheck.api.Report;
import com.adobe.epubcheck.util.DefaultReportImpl;
import javax.imageio.ImageIO;

/**
* 从网上抓取网页下来,打包成epub
* 书目录url:
* 参考 http://code.google.com/p/epub-tools/wiki/HelloEPUB2
* http://code.google.com/p/epub-tools/w/list
*
* @author
*
*/
public class HtmlBook2epub {

/**
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
String bookId = “618119″;//
makeEpub(bookId, “/home/lizongbo/tmp”);

}

/**
* 根据小说id生成对应的epub文件
*
* @param HtmlBookId
* @return
* @throws Exception
*/
public static boolean makeEpub(String HtmlBookId, String epubDir)
throws Exception {
String bookCatalogUrl = “http://book.com/?bookid=”+ HtmlBookId;
String bookCatalogHtml = downloadUrlContent(bookCatalogUrl);
String bookTitle = HtmlBook2epub.getBookTitle(bookCatalogHtml);
String bookAuthor = HtmlBook2epub.getBookAuthor(bookCatalogHtml);
String dir = “OEBPS”;
Publication epub = new Publication(dir);
// see http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html#Section2.1
// <title>: 题名 <creator> :责任者 <subject> :主题词或关键词 <description> :内容描述
// <contributor> :贡献者或其它次要责任者 <date> :日期 <type> :类型 <format> :格式
// <identifier> :标识符 <source> :来源 <language> :语种 <relation> :相关信息
// <coverage> :履盖范围 <rights> :权限描述
epub.addDCMetadata(“title”, bookTitle);//添加标题
epub.addDCMetadata(“creator”, bookAuthor);//添加书作者
addIntro(epub, HtmlBookId);// 添加简介
epub.addDCMetadata(“publisher”, “lizongbo”);
epub.addDCMetadata(“contributor”, “lizongbo”);
///epub.addDCMetadata(“date”, “”);
///epub.addDCMetadata(“type”, “”);
///epub.addDCMetadata(“format”, “lizongbo”);
epub.addDCMetadata(“identifier”, “Htmlbook_” + HtmlBookId);
epub.addDCMetadata(“source”, bookCatalogUrl);
epub.addDCMetadata(“language”, “zh”);
///epub.addDCMetadata(“ralation”, “”);
///epub.addDCMetadata(“coverage”, “无”);
epub.addDCMetadata(“rights”, “本书由lizongbo整理网页生成”);
epub.addMetadata(null, “cover”, “cover-image”);//添加蜂蜜图片的id
String[] chapterIds = getChapterIds(bookCatalogHtml, HtmlBookId);
addCoverImg(epub, HtmlBookId);// 添加封面和缩略图
for (int i = 0; i < 3000 && i < chapterIds.length; i++) {
addChapter(epub, HtmlBookId, chapterIds[i]);// 添加章节
}
File outFile = new File(epubDir, “Htmlbook_” + HtmlBookId + “.epub”);
OutputStream out = new FileOutputStream(outFile);
OCFContainerWriter container = new OCFContainerWriter(out);
epub.serialize(container);
checkEpub(outFile.getAbsolutePath());
return false;

}

/**
* 检查 epub书格式是否ok
*
* @param epubName
*/
public static void checkEpub(String epubName) {
Report report = new DefaultReportImpl(epubName);
if (!epubName.endsWith(“.epub”))
report.warning(null, 0, “filename does not include ‘.epub’ suffix”);

EpubCheck check = new EpubCheck(new File(epubName), report);
if (check.validate())
System.out.println(“No errors or warnings detected”);
else {
System.err.println(“\nCheck finished with warnings or errors!\n”);
}
}

/**
* 添加封面图片和缩略图
*
* @param epub
* @param HtmlBookId
* @throws Exception
*/
public static void addCoverImg(Publication epub, String HtmlBookId)
throws Exception {
String bookUrl = “http://book.com/index_” + HtmlBookId + “.htm”;
String bookHtml = downloadUrlContent(bookUrl);
String coverImgUrl = getCoverImgUrl(bookHtml);
BitmapImageResource coverImg = epub.createBitmapImageResource(epub
.getContentFolder()
+ “/images/cover.jpg”, “image/jpeg”, new ImgFileUrlDataSource(
coverImgUrl));
coverImg.setId(“cover-image”);
// 还需要把封面图片转成缩略图 thumb.png
BufferedDataSource thumbDs = new BufferedDataSource();
BufferedImage bi = ImageIO.read(new URL(coverImgUrl));// 读到原图
BufferedImage tag = null;
tag = new BufferedImage(54, 75, BufferedImage.TYPE_INT_RGB);
tag.getGraphics().drawImage(bi, 0, 0, 54, 75, null); // 绘制缩小后的图
ImageIO.write(tag, “png”, thumbDs.getOutputStream());
BitmapImageResource thumbImg = epub.createBitmapImageResource(epub
.getContentFolder()
+ “/images/thumb.png”, “image/png”, thumbDs);
}

/**
* 根据章节id添加章节
*
* @param epub
* @param chapterUrl
*            章节url
* @throws Exception
*/
public static void addChapter(Publication epub, String HtmlBookId,
String chapterId) throws Exception {
String chapterUrl = “http://book.com/book/chapter_
+ HtmlBookId + “_” + chapterId + “.html”;
String chapterHtml = downloadUrlContent(chapterUrl);
String chapterTitle = HtmlBook2epub.getChapterTitle(chapterHtml);
String chapterText = HtmlBook2epub.getChapterText(chapterHtml);
chapterText = chapterText.replaceAll(“</p><p>”, “\n”);
chapterText = chapterText.replaceAll(“<p>”, “”);
chapterText = html2txt(chapterText.replaceAll(“</p>”, “”)).trim();
String chapterTextArr[] = chapterText.split(“\n”);
addChapter(epub, HtmlBookId, chapterId, chapterTitle, chapterTextArr);

}

/**根据章节内容添加章节
* @param epub
* @param chapterId
* @param title
* @param texts
*/
public static void addChapter(Publication epub, String HtmlBookId,
String chapterId, String title, String[] texts) {
if (texts == null || texts.length < 1) {
System.out.println(“warn: ” + HtmlBookId + “|” + chapterId + “|”
+ title + ” texts is empty”);
return;
}
if (title == null || title.length() < 1) {
System.out.println(“warn: ” + HtmlBookId + “|” + chapterId + “|”
+ title + ” title is empty”);
return;
}
NCXResource toc = epub.getTOC();
TOCEntry rootTOCEntry = toc.getRootTOCEntry();
String chapterFile = epub.getContentFolder() + “/” + chapterId
+ “.html”;
System.out.println(“addChapter ” + chapterFile + “|” + chapterId + “|”
+ title);
OPSResource chapter1 = epub.createOPSResource(chapterFile);
epub.addToSpine(chapter1);
OPSDocument chapter1Doc = chapter1.getDocument();
TOCEntry chapter1TOCEntry = toc.createTOCEntry(title, chapter1Doc
.getRootXRef());
rootTOCEntry.add(chapter1TOCEntry);
Element body1 = chapter1Doc.getBody();
Element header1 = chapter1Doc.createElement(“h1″);
header1.add(title);
body1.add(header1);
{// 添加原文来源:
String chapterUrl = “http://book.com/book/chapter_
+ HtmlBookId + “_” + chapterId + “.html”;
Element paragraph1 = chapter1Doc.createElement(“p”);
paragraph1.add(“原文来源:” + chapterUrl);
body1.add(paragraph1);
}
for (int i = 0; texts != null && i < texts.length; i++) {
Element paragraph1 = chapter1Doc.createElement(“p”);
paragraph1.add(texts[i]);
body1.add(paragraph1);
}

}

/**
* 添加小说简介
*
* @param epub
* @param HtmlBookId
* @throws Exception
*/
public static void addIntro(Publication epub, String HtmlBookId)
throws Exception {
String bookUrl = “http://book.com/book/index_” + HtmlBookId
+ “.html”;
String bookHtml = downloadUrlContent(bookUrl);
String startText = “<div >”;
String endText = “</div>”;
String intro = getStringLastBetween(bookHtml, startText, endText);
intro = intro.replaceAll(“<p>”, “”);
intro = html2txt(intro.replaceAll(“</p>”, “”));
intro = intro + “\n来源:” + bookUrl;
epub.addDCMetadata(“description”, intro);
Resource introRes = epub.createResource(“intro.txt”, “text/plain”,
new StringDataSource(intro));
startText = “<div class=\”linkOther\”>”;
endText = “</div>”;
String keywords = getStringLastBetween(bookHtml, startText, endText);
keywords = html2txt(keywords);
String ks[] = keywords.split(“\n”);
for (String s : ks) {
if (s != null && s.trim().length() > 0) {
epub.addDCMetadata(“subject”, s);//支持多个关键字
}
}

}

/**
* 用GB2312下载网页内容
*
* @param urlStr
* @return
* @throws Exception
*/
public static String downloadUrlContent(String urlStr) throws Exception {
return downloadUrlContent(urlStr, “GB2312″);
}

/**
* 根据章节内内容获得章节标题
*
* @param chapterHtml
* @return
*/
public static String getChapterTitle(String chapterHtml) {
String startText = “<h1>”;
String endText = “</h1>”;
return getStringLastBetween(chapterHtml, startText, endText);
}

/**
* 根据章节内容获取小说内容的html
*
* @param chapterHtml
* @return
*/
public static String getChapterText(String chapterHtml) {
String startText = “<div>”;
String endText = “</div>”;
return getStringLastBetween(chapterHtml, startText, endText);
}

/**
* 根据目录列表网页内容获取小说标题
*
* @param bookHtml
* @return
*/
public static String getBookTitle(String bookHtml) {
String startText = “<title>”;
String endText = “</title>”;
String title = getStringLastBetween(bookHtml, startText, endText);
if (title.contains(“_”)) {
title = title.substring(0, title.indexOf(“_”));
}
return html2txt(title);
}

/**
* 根据目录列表网页内容获取作者名称
*
* @param bookHtml
* @return
*/
public static String getBookAuthor(String bookHtml) {
String startText = “<h1>”;
String endText = “</h1>”;
String title = getStringLastBetween(bookHtml, startText, endText);
startText = “<span>”;
endText = “</span>”;
title = getStringLastBetween(title, startText, endText);
System.out.println(“getBookAuthor==” + title);
return title.length() > 0 ? html2txt(title) : “无名”;
}

/**
* 根据目录页面网页内容获得章节Id
*
* @param bookHtml
* @return
*/
public static String[] getChapterIds(String bookHtml, String HtmlBookId) {
java.util.List<String> chapterList = new ArrayList<String>();
String startText = “<a href=\”c_” + HtmlBookId + “_”;
String endText = “.html\”";
String chapterId = null;
while ((chapterId = getStringBetween(bookHtml, startText, endText))
.length() > 0) {
System.out.println(“chapterId==” + chapterId);
chapterList.add(chapterId);
bookHtml = bookHtml.substring(bookHtml.indexOf(startText)
+ startText.length());
}

return chapterList.toArray(new String[0]);
}

/**
* 根据小说首页html,提取封面图片路径
*
* @param bookHtml
* @return
*/
public static String getCoverImgUrl(String bookHtml) {
String startText = “http://book.com/cover”;
String endText = “.jpg”;
String url = getStringBetween(bookHtml, startText, endText);
url = startText + url + endText;
System.out.println(“getCoverImgUrl==” + url);
return url;
}

/**
* 获取文本中最后一次出现在两个字符串之间的文字,不包含开头和结尾的字符串
*
* @param src
* @param startText
* @param endText
* @return
*/
public static String getStringLastBetween(String src, String startText,
String endText) {
if (src != null && src.contains(startText)) {
int startIndex = src.lastIndexOf(startText);
int endIndex = src.indexOf(endText, startIndex);
if (endIndex > startIndex) {
return src.substring(startIndex + startText.length(), endIndex);

}
}
return “”;

}

/**
* 获取文本中第一次出现在两个字符串之间的文字,不包含开头和结尾的字符串
*
* @param src
* @param startText
* @param endText
* @return
*/
public static String getStringBetween(String src, String startText,
String endText) {
if (src != null && src.contains(startText)) {
int startIndex = src.indexOf(startText);
int endIndex = src.indexOf(endText, startIndex);
if (endIndex > startIndex) {
return src.substring(startIndex + startText.length(), endIndex);

}
}
return “”;

}

/**
* 用指定编码下载网页内容
*
* @param urlStr
* @param encoding
* @return
* @throws Exception
*/
public static String downloadUrlContent(String urlStr, String encoding)
throws Exception {
URL url = new URL(urlStr);
URLConnection urlc = url.openConnection();
urlc
.setRequestProperty(
“User-Agent”,
“Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2) Gecko/20100115 Firefox/3.6″);
urlc
.setRequestProperty(“Accept”,
“text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8″);
urlc.setRequestProperty(“Accept-Language”, “zh-cn,zh;q=0.5″);
urlc.setRequestProperty(“Accept-Charset”, “GB2312,utf-8;q=0.7,*;q=0.7″);
urlc.setConnectTimeout(5000);
urlc.connect();
StringBuilder sb = new StringBuilder(4096);
BufferedReader in = new BufferedReader(new InputStreamReader(urlc
.getInputStream(), encoding));
String line;
while ((line = in.readLine()) != null) {
sb.append(line).append(‘\n’);
}
in.close();
System.out.println(urlStr);
return sb.toString().trim();
}
/**
*提取html的文本内容
*/
public static String html2txt(String s) {
if (s != null) {
return s.replaceAll(“<.*?>”, “”);
}
return “”;
}
}

Tags: ebook, epub, Java

Related posts

Eclipse 3.5.2及插件安装

2010年03月4日
Eclipse 3.5.2及插件安装
1.首先下载Eclipse 3.5.2.

http://d2u376ub0heus3.cloudfront.net/technology/epp/downloads/release/galileo/SR2/eclipse-jee-galileo-SR2-win32.zip

来源:http://download.eclipse.org/eclipse/downloads/

2.下载相关插件:
a.Subclipse 1.6.8
来源:http://subclipse.tigris.org/
下载地址:http://subclipse.tigris.org/files/documents/906/47393/site-1.6.8.zip
b.Findbugs 1.3.9
来源:http://findbugs.sourceforge.net/
下载地址:http://nchc.dl.sourceforge.net/project/findbugs/findbugs%20eclipse%20plugin/1.3.9/edu.umd.cs.findbugs.plugin.eclipse_1.3.9.20090821.zip
c.Bytecode Outline plugin for Eclipse 2.2.10
来源: http://asm.ow2.org/eclipse/index.html
下载地址:
http://download.forge.objectweb.org/asm/asm-3.2.jar(Bytecode Outline依赖的jar)

http://download.forge.objectweb.org/asm/de.loskutov.BytecodeOutline_2.2.10.jar

3.把插件集中放到一个地方:
解压site-1.6.8.zip到D:\Java\ecplugin\subclipse
解压edu.umd.cs.findbugs.plugin.eclipse_1.3.9.20090821.zip到:D:\Java\ecplugin\findbugs\eclipse\plugins
确保D:\Java\ecplugin\findbugs\eclipse下面有features和plugins目录
复制asm-3.2.jar和de.loskutov.BytecodeOutline_2.2.10.jar到D:\Java\ecplugin\bytecode\eclipse\plugins
确保D:\Java\ecplugin\bytecode\eclipse下面有features和plugins目录

4.安装插件

新建目录 D:\Java\ec352\eclipse\links

在D:\Java\ec352\eclipse\links下面,新建subeclipse.link,内容为:path=D:\\Java\\ecplugin\\subeclipse
在D:\Java\ec352\eclipse\links下面,新建findbugs.link,内容为:path=D:\\Java\\ecplugin\\findbugs
在D:\Java\ec352\eclipse\links下面,新建bytecode.link,内容为:path=D:\\Java\\ecplugin\\bytecode
上面这样操作太麻烦,可以用下面的bat双击一次搞定
set ECPLUGIN_LINK=D:\Java\ec352\eclipse\links
mkdir %ECPLUGIN_LINK%
echo path=D:\\Java\\ecplugin\\subeclipse > %ECPLUGIN_LINK%\subeclipse.link
echo path=D:\\Java\\ecplugin\\findbugs > %ECPLUGIN_LINK%\findbugs.link
echo path=D:\\Java\\ecplugin\\bytecode > %ECPLUGIN_LINK%\bytecode.link

5.配置maven
maven已经装好
运行
mvn eclipse:configure-workspace -Declipse.workspace=D:\javadev\workspace
Tags: Bytecode, eclipse, findbugs, smplayer, Subclipse

Related posts

使用Google Email Uploader备份Microsoft Office Outlook邮件到google apps里的邮箱。

2010年02月25日
由于Microsoft Office Outlook收到的邮件太多,加上电脑上启动了一堆程序,导致Outlook启动很慢很卡,
使用搜索功能的时候,更是卡得要命。
曾经分别尝试过安装Google Desktop Search和Windows Desktop Search 4.0,但是效果更差,
让本来就卡的电脑更卡了,全文搜索功能等同废品。
迫于无奈,于是另寻其它方法。

目前GMail和QQMail都支持了邮件全文搜索,
如果能够将邮件阅读之后转发到GMail或QQMail里进行备份,然后利用搜索功能查找邮件,那将是很美好的事情。

可惜遇到了第一个难题,就是Exchage Server限制了邮件自动转发,
要求发给其它邮箱的邮件,件标题都必须加上指定的前缀。
outlook的邮件规则支持自动转发,但是不支持配置前缀,
在每次阅读邮件后手工加前缀再转发,没法活了。
于是想到的第一个方法,就是找outlook插件,肯定早就有人就类似需求了,
通过google搜索“outlook addons forward”,确实找到了很多免费的或收费的插件,
下载下来试用后发现,支持自动转发邮件的配置里,还是没法在转发前对邮件标题等信息进行修改,
也就不可能加固定前缀。

此路不通,再找其它方法:
QQMail和Gmail都提供了IMAP方式访问邮箱,
用google搜索得知使用foxmail客户端可以将邮件通过imap方式快速上传到服务器。
(google搜索“foxmail  imap 备份”可以了解详细操作),但再仔细一看,Foxmail不支持Exchange Server方式访问,
而Exchange Server更是没有提供imap和pop3访问方式。也就是说没有用foxmail来备份了。

接下来再想办法:
Gmail支持imap,Microsoft Office Outlook呢,支持复制邮件到本地文件夹,
自动将每个邮件导成一个单独的文件。
如果手工批量复制邮件到指定文件夹,再写个程序,用javamail解析文件,
然后再调用imap接口上传,看上去也还凑合。
于是动手实验,发现Microsoft Office Outlook导出的文件是不明格式的msg文件,
只有Outlook Express 备份出来的文件是方便解析的eml格式,
而Outlook Express 不用imap和pop3是没法连Exchage Server的,这个方法也就没戏。

还曾幻想自己写vba脚本来实现转发功能,但是一番google搜索之后,发现这个太难了,还是放弃这个想法。

万般无奈之下之下,想想总应该有可以导入邮件到gmail的工具吧,这些工具或许有导入outlook邮件的方法,
于是搜索"gmail import email",找到了
Google Email Uploader(http://mail.google.com/mail/help/email_uploader.html),
一看到页面上介绍支持Microsoft Outlook,总算看到希望了。

飞快下载安装文件,安装后运行Google Email Uploader,提示Microsoft Office Outlook正在运行,不能操作。
于是关掉Microsoft Office Outlook后再启动Google Email Uploader,
看到说支持Microsoft Outlook,Outlook Express,Thunderbird这三种邮件客户端,
点 next,看到gmail账号的输入框。
输入gmaila账号信息,点sing in,结果提示如下:

---------------------------
Uploading email to gmail.com or googlemail.com is not enabled
---------------------------
Your account is not allowed to upload email.
You must login to GMail for your domain before you can use this feature.
 Also, your domain administrator may need to enable email migration for your domain.
---------------------------
确定
---------------------------

晕倒,还是不支持Gmail啊,继续google搜索“Uploading email to gmail.com or googlemail.com is not enabled”。
在网上找到了答案,原来这个只支持google apps里的电子邮件导入。
很幸运,Google apps开放的时候,正好通过网页代理去激活了618119.com的电子邮件服务,虽然密码早就忘了。
于是快速找回相关密码,重新登录账号,确认@618119.com的电子邮件可用。

再接下来就很顺利了,邮件顺利的导入到了google apps托管的电子邮箱里,
第一次导入的邮件非常多,等了一两个小时才搞定。
Microsoft Office Outlook本地的邮件也可以放心大胆删除并压缩pst文件了,
清理之后,本地邮件的文件夹大小控制在100M左右。
找邮件的时候登录gmail进行搜索。

还可以下载Google apps的桌面小程序,实际就是chrome浏览器的简单包装了快捷方式。
618119.com Email的启动命令如下。

"C:\Program Files\Google\Google Apps\googleapps.exe" --domain=618119.com --mail.google.com

几次备份之后,“目前您已经使用 7421 MB 中的 680 MB (9%)。”,按这个速度,备份空间是足够的了。

上面说了那么多废话,简而言之就一句话,如果你有已经激活google apps的电子邮件服务的独立域名邮箱,
就可以使用Google Email Uploader快速备份outlook邮件到gmail。享受轻松的全文检索服务。

已经向QQMail提过支持邮件导入的建议了,不过不知道QQMail会不会实现这样的功能。
Tags: 618119, gmail, google apps, mail import, Outlook, QQmail

Related posts