package org.fanhongtao.tools.chmbuilder;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import org.fanhongtao.log.LogUtils;
import org.fanhongtao.log.RunLogger;
/**
* 将指定目录下的 .htm/.html 文件生成可以供 Microsoft HTML Help WorkShop 使用的 .hhp、.hhc、.hhk 文件。<br>
* 然后调用 Microsoft HTML Help WorkShop 中的 hhc.exe 生成CHM文件。<br>
*
* @author Dharma
* @created 2008-11-12
*/
public class ChmBuilder
{
/** HTML文件所在目录 */
private String htmlPath = null;
/** CHM文件启动时显示的页面(含路径) */
private String defaultPage = null;
/** 要生成的CHM文件名(含路径) */
private String chmFileName = null;
/** 要生成的CHM文件的标题 */
private String chmTitle = null;
/** .hhp文件的文件名(含路径) */
private String hhpFileName = null;
/** .hhc文件的文件名(含路径) */
private String hhcFileName = null;
/** .hhk文件的文件名(含路径) */
private String hhkFileName = null;
/**
*
* @param chmFileName :
* @param htmlPath
* @param defaultPage
*/
public ChmBuilder(String chmFileName, String htmlPath, String defaultPage, String chmTitle)
{
super();
this.chmFileName = new File(chmFileName).getAbsolutePath();
this.htmlPath = new File(htmlPath).getAbsolutePath();
this.defaultPage = new File(defaultPage).getAbsolutePath();
this.chmTitle = chmTitle;
// 生成相应的文件名
String basePath = new File(htmlPath).getParent();
String baseName = new File(chmFileName).getName();
if (baseName.endsWith(".chm"))
{
baseName = baseName.substring(0, baseName.length() - 4);
}
if (this.defaultPage.startsWith(basePath + "\\"))
{
this.defaultPage = this.defaultPage.substring(basePath.length() + 1);
}
hhpFileName = basePath + "\\" + baseName + ".hhp";
hhcFileName = basePath + "\\" + baseName + ".hhc";
hhkFileName = basePath + "\\" + baseName + ".hhk";
RunLogger.info(".hhp : " + hhpFileName);
RunLogger.info(".hhc : " + hhcFileName);
RunLogger.info(".hhk : " + hhkFileName);
RunLogger.info(".chm : " + chmFileName);
}
public void run() throws IOException
{
RunLogger.info("Generate .hhp");
generateHHP();
RunLogger.info("Generate .hhc");
generateHHC();
RunLogger.info("Generate .hhk");
generateHHK();
RunLogger.info("Generate .chm");
generateCHM();
RunLogger.info("Build success! CHM file: " + chmFileName);
}
/**
* 生成 .hhp 文件
* @throws IOException
*/
private void generateHHP() throws IOException
{
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(hhpFileName)));
pw.println("[OPTIONS]");
pw.println("Compatibility=1.1 or later");
pw.println("Compiled file=" + chmFileName);
pw.println("Contents file=" + hhcFileName);
pw.println("Default Window=win1");
pw.println("Default topic=" + defaultPage);
pw.println("Display compile progress=Yes");
pw.println("Full-text search=Yes");
pw.println("Index file=" + hhkFileName);
pw.println("Language=0x804 中文(中国)");
if ((chmTitle != null) && (chmTitle.trim().length() > 0))
{
pw.println("Title=" + chmTitle);
}
pw.println("");
// 指定Windows属性,主要是为了实现 “书签”功能
pw.println("[WINDOWS]");
StringBuffer sb = new StringBuffer(1024);
sb.append("win1=,\"");
sb.append(hhcFileName);
sb.append("\",\"");
sb.append(hhkFileName);
sb.append("\",\"");
sb.append(defaultPage); // default page
sb.append("\",\"");
sb.append(defaultPage); // home page
sb.append("\",,,,,0x23520,,0x104e,,,,,,,,0");
pw.println(sb.toString());
pw.println("");
pw.println("[FILES]");
printHHC(pw, htmlPath);
pw.close();
}
private void printHHC(PrintWriter pw, String path) throws IOException
{
File[] files = new File(path).listFiles();
Arrays.sort(files);
for (int i = 0; i < files.length; i++)
{
File f = files[i];
if (f.isDirectory())
{
printHHC(pw, f.getCanonicalPath());
}
else
{
// String fileName = f.getName().toLowerCase();
// if (fileName.endsWith(".htm") || fileName.endsWith(".html"))
{
pw.println(f.getCanonicalPath());
}
}
}
}
/**
* 生成 .hhc 文件
* @throws IOException
*/
private void generateHHC() throws IOException
{
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(hhcFileName)));
pw.println("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">");
pw.println("<HTML>");
pw.println("<HEAD>");
pw.println("<!-- Dharma -->");
pw.println("</HEAD><BODY>");
pw.println("<OBJECT type=\"text/site properties\">");
pw.println("\t<param name=\"ImageType\" value=\"Folder\">");
pw.println("</OBJECT>");
printHHC(pw, htmlPath, 0);
pw.println("</BODY></HTML>");
pw.close();
}
private String getIndent(int indentNum)
{
StringBuffer sb = new StringBuffer(64);
for (int i = 0; i < indentNum; i++)
{
sb.append(" ");
}
return sb.toString();
}
private void printHHC(PrintWriter pw, String path, int identNum) throws IOException
{
pw.println(getIndent(identNum) + "<UL>");
File[] files = new File(path).listFiles();
Arrays.sort(files);
for (int i = 0; i < files.length; i++)
{
File f = files[i];
if (f.isDirectory())
{
pw.println(getIndent(identNum + 1) + "<LI> <OBJECT type=\"text/sitemap\">");
pw.println(getIndent(identNum + 2) + "<param name=\"Name\" value=\"" + f.getName() + "\">");
pw.println(getIndent(identNum + 2) + "</OBJECT>");
printHHC(pw, f.getCanonicalPath(), identNum + 1);
}
else
{
String fileName = f.getName().toLowerCase();
if (fileName.endsWith(".htm") || fileName.endsWith(".html"))
{
pw.println(getIndent(identNum + 1) + "<LI> <OBJECT type=\"text/sitemap\">");
pw.println(getIndent(identNum + 2) + "<param name=\"Name\" value=\"" + f.getName() + "\">");
pw.println(getIndent(identNum + 2) + "<param name=\"Local\" value=\"" + f.getCanonicalPath()
+ "\">");
pw.println(getIndent(identNum + 2) + "</OBJECT>");
}
}
}
pw.println(getIndent(identNum) + "</UL>");
}
/**
* 生成 .hhk 文件
* @throws IOException
*/
private void generateHHK() throws IOException
{
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(hhkFileName)));
pw.println("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">");
pw.println("<HTML>");
pw.println("<HEAD>");
pw.println("<!-- Dharma -->");
pw.println("</HEAD><BODY>");
pw.println("<UL>");
printHHK(pw, htmlPath, 0);
pw.println("</UL>");
pw.println("</BODY></HTML>");
pw.close();
}
private void printHHK(PrintWriter pw, String path, int identNum) throws IOException
{
File[] files = new File(path).listFiles();
Arrays.sort(files);
for (int i = 0; i < files.length; i++)
{
File f = files[i];
if (f.isDirectory())
{
printHHK(pw, f.getCanonicalPath(), identNum + 1);
}
else
{
String fileName = f.getName().toLowerCase();
if (fileName.endsWith(".htm") || fileName.endsWith(".html"))
{
String title = getTitle(f);
pw.println(getIndent(1) + "<LI> <OBJECT type=\"text/sitemap\">");
pw.println(getIndent(2) + "<param name=\"Name\" value=\"" + title + "\">");
pw.println(getIndent(2) + "<param name=\"Name\" value=\"" + title + "\">");
pw.println(getIndent(2) + "<param name=\"Local\" value=\"" + f.getCanonicalPath() + "\">");
pw.println(getIndent(2) + "</OBJECT>");
}
}
}
}
/**
* 获取HTML文件的<title>属性
* @param file
* @return
* @throws IOException
*/
private String getTitle(File file) throws IOException
{
String title = "";
BufferedReader br = new BufferedReader(new FileReader(file));
String line = null;
int findStatus = 0;
while (((line = br.readLine()) != null) && (findStatus < 2))
{
int startIndex = 0;
String temp = line.toLowerCase();
if (findStatus == 0) // 还没有找到过 <title>
{
startIndex = temp.indexOf("<title>");
if (startIndex != -1)
{
startIndex += 7;
findStatus = 1;
int stopIndex = temp.indexOf("</title>", startIndex);
if (stopIndex != -1) // 一行中同时有<title>和</title>
{
findStatus = 2;
title = line.substring(startIndex, stopIndex);
break;
}
title = line.substring(startIndex); // 有可能<title>后面有内容
}
}
else if (findStatus == 1) // 已经找到过<title>
{
startIndex = temp.indexOf("</title>");
if (startIndex != -1)
{
findStatus = 2;
title = title + line.substring(0, startIndex);
}
else
{
title = title + line;
}
}
}
br.close();
title = title.trim();
if ((findStatus != 2) || (title.equals("")))
{
title = file.getName();
}
return title;
}
/**
* 生成 .chm 文件
* @throws IOException
*/
private void generateCHM() throws IOException
{
Runtime rt = Runtime.getRuntime();
String[] cmd = new String[2];
cmd[0] = "hhc.exe";
cmd[1] = hhpFileName;
Process proc = rt.exec(cmd);
new StreamGobbler(proc.getErrorStream(), "ERROR").start();
new StreamGobbler(proc.getInputStream(), "OUTPUT").start();
int exitValue;
try
{
exitValue = proc.waitFor();
}
catch (InterruptedException e)
{
e.printStackTrace();
throw new IOException("Meet InterruptedException");
}
if (exitValue != 1)
{
throw new IOException("Complied failed. exitValue is : " + exitValue);
}
}
public String getChmFileName()
{
return chmFileName;
}
/**
* @param args
*/
public static void main(String[] args)
{
if (args.length != 4)
{
System.out.println("Usage: ChmBuilder chm-file-name html-path default-page chm-title");
System.out.println("For example, ");
System.out.println(" ChmBuilder d:\\temp\\test.chm d:\\temp\\html d:\\temp\\html\\index.html test");
return;
}
LogUtils.initBasicLog();
try
{
new ChmBuilder(args[0], args[1], args[2], args[3]).run();
}
catch (IOException e)
{
e.printStackTrace();
System.out.println("Failed to create CHM file. " + e.getLocalizedMessage());
}
}
}
class StreamGobbler extends Thread
{
InputStream is;
String type;
OutputStream os;
StreamGobbler(InputStream is, String type)
{
this(is, type, null);
}
StreamGobbler(InputStream is, String type, OutputStream redirect)
{
this.is = is;
this.type = type;
this.os = redirect;
}
public void run()
{
try
{
PrintWriter pw = null;
if (os != null)
pw = new PrintWriter(os);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null)
{
if (pw != null)
pw.println(line);
RunLogger.info(type + ">" + line);
}
if (pw != null)
pw.flush();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}