├── ext ├── hha.dll └── hhc.exe ├── lib └── jsoup-1.7.3.jar ├── README.md ├── src └── com │ └── chenfei │ └── chm │ └── helper │ ├── connection │ └── Connection.java │ ├── service │ ├── parser │ │ ├── HHKParser.java │ │ └── impl │ │ │ └── RegexHHKParser.java │ ├── HHKService.java │ ├── HHPService.java │ └── HHCService.java │ ├── listener │ ├── DefaultListener.java │ └── Listener.java │ ├── bean │ ├── HHKItem.java │ ├── HHCItem.java │ └── CHM.java │ ├── utils │ ├── FileUtils.java │ └── HTMLUtils.java │ ├── creater │ ├── CHMCreater.java │ ├── HHKCreater.java │ ├── HHPCreater.java │ └── HHCCreater.java │ ├── Main.java │ └── parser │ └── Parser.java └── LICENSE /ext/hha.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenfeicqq/CHM-Helper/HEAD/ext/hha.dll -------------------------------------------------------------------------------- /ext/hhc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenfeicqq/CHM-Helper/HEAD/ext/hhc.exe -------------------------------------------------------------------------------- /lib/jsoup-1.7.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenfeicqq/CHM-Helper/HEAD/lib/jsoup-1.7.3.jar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CHM-Helper 2 | ========== 3 | 4 | 帮助创建CHM文件 5 | 6 | 仅运行于Windows下 7 | 8 | Demo:com.chenfei.chm.helper.Main 9 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/connection/Connection.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.connection; 2 | 3 | public interface Connection { 4 | 5 | /** 6 | * 获取T 7 | * 8 | * @return 返回一个T 9 | */ 10 | T get(); 11 | 12 | /** 13 | * 判断是否结束 14 | * 15 | * @return true:结束;false:没有结束; 16 | */ 17 | boolean isEnded(); 18 | } 19 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/service/parser/HHKParser.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.service.parser; 2 | 3 | import java.nio.file.Path; 4 | import java.util.List; 5 | 6 | import com.chenfei.chm.helper.bean.HHKItem; 7 | 8 | public interface HHKParser { 9 | 10 | /** 11 | * HHK解析器 12 | * 13 | * @param relativize 文件相对路径 14 | * @param file 索引文件 15 | * @return 16 | */ 17 | List parse(String relativize, Path file); 18 | } 19 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/listener/DefaultListener.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.listener; 2 | 3 | import java.nio.file.Path; 4 | 5 | public class DefaultListener implements Listener { 6 | 7 | @Override 8 | public void file(String relativize, Path file) { 9 | } 10 | 11 | @Override 12 | public void directoryIn(String relativize, Path directory) { 13 | } 14 | 15 | @Override 16 | public void directoryOut(String relativize, Path directory) { 17 | } 18 | 19 | @Override 20 | public void end() { 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/bean/HHKItem.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.bean; 2 | 3 | public class HHKItem { 4 | 5 | private String key; 6 | 7 | private String value; 8 | 9 | public HHKItem(String key, String value) { 10 | this.key = key; 11 | this.value = value; 12 | } 13 | 14 | public String getKey() { 15 | return key; 16 | } 17 | 18 | public void setKey(String key) { 19 | this.key = key; 20 | } 21 | 22 | public String getValue() { 23 | return value; 24 | } 25 | 26 | public void setValue(String value) { 27 | this.value = value; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return "HHKItem [key=" + key + ", value=" + value + "]"; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/listener/Listener.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.listener; 2 | 3 | import java.nio.file.Path; 4 | 5 | public interface Listener { 6 | 7 | /** 8 | * 文件 9 | * 10 | * @param relativize 文件相对路径 11 | * @param file 文件路径 12 | */ 13 | void file(String relativize, Path file); 14 | 15 | /** 16 | * 进入文件夹 17 | * 18 | * @param relativize 文件夹相对路径 19 | * @param directory 文件夹路径 20 | */ 21 | void directoryIn(String relativize, Path directory); 22 | 23 | /** 24 | * 退出文件夹 25 | * 26 | * @param relativize 文件夹相对路径 27 | * @param directory 文件夹路径 28 | */ 29 | void directoryOut(String relativize, Path directory); 30 | 31 | /** 32 | * 监听结束 33 | */ 34 | void end(); 35 | } 36 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.utils; 2 | 3 | public class FileUtils { 4 | 5 | /** 6 | * 分割文件名 7 | * 8 | * @param fileName 文件名 9 | * @return 10 | */ 11 | public static String[] split(String fileName) { 12 | String[] fileNameInfos = new String[2]; 13 | 14 | int splitPoint = fileName.lastIndexOf("."); 15 | 16 | if (-1 == splitPoint) { 17 | fileNameInfos[0] = fileName; 18 | fileNameInfos[1] = null; 19 | } else { 20 | fileNameInfos[0] = fileName.substring(0, splitPoint); 21 | fileNameInfos[1] = fileName.substring(splitPoint); 22 | } 23 | 24 | return fileNameInfos; 25 | } 26 | 27 | private FileUtils() { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 chenfeicqq \< chenfeicqq@gmail.com \> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/utils/HTMLUtils.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.utils; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | public class HTMLUtils { 10 | 11 | public static String getTitle(File file, String charset) { 12 | 13 | String title = ""; 14 | 15 | try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file))) { 16 | 17 | // 读取前512字节 18 | byte[] buffer = new byte[512]; 19 | 20 | bufferedInputStream.read(buffer); 21 | 22 | // 转换为字符串 23 | String string = new String(buffer, charset); 24 | 25 | Pattern pattern = Pattern.compile("()([^<(]*)", Pattern.CASE_INSENSITIVE); 26 | 27 | Matcher matcher = pattern.matcher(string); 28 | 29 | if (matcher.find()) { 30 | title = matcher.group(2).trim(); 31 | } 32 | 33 | } catch (Throwable e) { 34 | e.printStackTrace(); 35 | } 36 | 37 | return title; 38 | } 39 | 40 | private HTMLUtils() { 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/bean/HHCItem.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.bean; 2 | 3 | import java.io.File; 4 | import java.nio.file.Path; 5 | 6 | public class HHCItem { 7 | 8 | private String relativize; 9 | 10 | private Path path; 11 | 12 | private Action action; 13 | 14 | public HHCItem(String relativize, Path path, Action action) { 15 | this.relativize = relativize; 16 | this.path = path; 17 | this.action = action; 18 | } 19 | 20 | public boolean isDirectory() { 21 | return null != this.action; 22 | } 23 | 24 | public boolean isIn() { 25 | return Action.IN == this.action; 26 | } 27 | 28 | public boolean isOut() { 29 | return Action.OUT == this.action; 30 | } 31 | 32 | public String getRelativize() { 33 | return relativize; 34 | } 35 | 36 | public File getFile() { 37 | return path.toFile(); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "HHCItem [relativize=" + relativize + ", path=" + path + ", action=" + action + "]"; 43 | } 44 | 45 | public enum Action { 46 | /** 进入 */ 47 | IN, 48 | /** 退出 */ 49 | OUT; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/creater/CHMCreater.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.creater; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.file.Files; 6 | import java.nio.file.Paths; 7 | import java.util.concurrent.CountDownLatch; 8 | 9 | import com.chenfei.chm.helper.bean.CHM; 10 | 11 | /** 12 | * 类CHMCreater.java的实现描述:创建 CHM 文件的线程 13 | * @author chenfei.chenf 2014-5-4 上午10:41:47 14 | */ 15 | public class CHMCreater implements Runnable { 16 | 17 | private CHM chm; 18 | 19 | private CountDownLatch countDownLatch; 20 | 21 | public CHMCreater(CHM chm, CountDownLatch countDownLatch) { 22 | this.chm = chm; 23 | this.countDownLatch = countDownLatch; 24 | } 25 | 26 | @Override 27 | public void run() { 28 | try { 29 | this.countDownLatch.await(); 30 | 31 | this.create(); 32 | 33 | Files.delete(Paths.get(this.chm.getHHPPath())); 34 | Files.delete(Paths.get(this.chm.getHHCPath())); 35 | Files.delete(Paths.get(this.chm.getHHKPath())); 36 | 37 | } catch (Throwable e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | 42 | private void create() throws IOException, InterruptedException { 43 | 44 | Process process = Runtime.getRuntime().exec(this.getCommand()); 45 | 46 | try (InputStream inputStream = process.getInputStream()) { 47 | while (true) { 48 | if (inputStream.read() == -1) { 49 | break; 50 | } 51 | } 52 | } 53 | } 54 | 55 | private String getCommand() { 56 | StringBuilder commandStringBuilder = new StringBuilder(); 57 | commandStringBuilder.append("ext\\hhc.exe \""); 58 | commandStringBuilder.append(this.chm.getHHPPath().replace("/", "\\")); 59 | commandStringBuilder.append("\""); 60 | return commandStringBuilder.toString(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/service/HHKService.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.service; 2 | 3 | import java.nio.file.Path; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | 7 | import com.chenfei.chm.helper.bean.HHKItem; 8 | import com.chenfei.chm.helper.connection.Connection; 9 | import com.chenfei.chm.helper.listener.DefaultListener; 10 | import com.chenfei.chm.helper.service.parser.HHKParser; 11 | 12 | public class HHKService extends DefaultListener implements Connection<HHKItem> { 13 | 14 | /** 键值对列表 */ 15 | private LinkedList<HHKItem> hhkItems = new LinkedList<>(); 16 | 17 | /** 索引解析器 */ 18 | private HHKParser hhkParser; 19 | 20 | /** 监听是否结束 */ 21 | private boolean ended = false; 22 | 23 | public HHKService(HHKParser hhkParser) { 24 | this.hhkParser = hhkParser; 25 | } 26 | 27 | @Override 28 | public HHKItem get() { 29 | synchronized (this.hhkItems) { 30 | while (this.hhkItems.isEmpty()) { 31 | 32 | if (this.ended) { 33 | return null; 34 | } 35 | 36 | try { 37 | this.hhkItems.wait(100); 38 | } catch (InterruptedException e) { 39 | e.printStackTrace(); 40 | } 41 | } 42 | return this.hhkItems.removeFirst(); 43 | } 44 | } 45 | 46 | @Override 47 | public boolean isEnded() { 48 | return this.ended && this.hhkItems.isEmpty(); 49 | } 50 | 51 | @Override 52 | public void file(String relativize, Path file) { 53 | synchronized (this.hhkItems) { 54 | List<HHKItem> hhkItem = this.hhkParser.parse(relativize, file); 55 | 56 | if (null != hhkItem) { 57 | this.hhkItems.addAll(hhkItem); 58 | } 59 | } 60 | } 61 | 62 | @Override 63 | public void end() { 64 | this.ended = true; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/Main.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper; 2 | 3 | import java.io.IOException; 4 | import java.util.concurrent.CountDownLatch; 5 | 6 | import com.chenfei.chm.helper.bean.CHM; 7 | import com.chenfei.chm.helper.creater.CHMCreater; 8 | import com.chenfei.chm.helper.creater.HHCCreater; 9 | import com.chenfei.chm.helper.creater.HHKCreater; 10 | import com.chenfei.chm.helper.creater.HHPCreater; 11 | import com.chenfei.chm.helper.parser.Parser; 12 | import com.chenfei.chm.helper.service.HHCService; 13 | import com.chenfei.chm.helper.service.HHKService; 14 | import com.chenfei.chm.helper.service.HHPService; 15 | import com.chenfei.chm.helper.service.parser.impl.RegexHHKParser; 16 | import com.chenfei.chm.helper.service.parser.impl.RegexHHKParser.Regex; 17 | 18 | public class Main { 19 | 20 | public static void main(String[] args) throws IOException { 21 | // 文件名中不要出现空格 22 | CHM chm = new CHM("c:/Users/chenfei.chenf/Desktop/api", "JDK-1.8", "JDK 1.8 API by chenfei", "index.html"); 23 | 24 | CountDownLatch countDownLatch = new CountDownLatch(3); 25 | 26 | new Thread(new CHMCreater(chm, countDownLatch)).start(); 27 | 28 | Parser parser = new Parser(chm); 29 | 30 | HHPService hhpService = new HHPService(null); 31 | HHPCreater hhpCreater = new HHPCreater(chm, hhpService, countDownLatch); 32 | new Thread(hhpCreater).start(); 33 | parser.addListeners(hhpService); 34 | 35 | HHCService hhcService = new HHCService("(index-files|resources|script|stylesheet).*"); 36 | HHCCreater hhcCreater = new HHCCreater(chm, hhcService, countDownLatch); 37 | new Thread(hhcCreater).start(); 38 | parser.addListeners(hhcService); 39 | 40 | HHKService hhkService = new HHKService(new RegexHHKParser("index-files\\\\.*", "GB2312", new Regex("dl>dt>a", "span text()", "@href"))); 41 | HHKCreater hhkCreater = new HHKCreater(chm, hhkService, countDownLatch); 42 | new Thread(hhkCreater).start(); 43 | parser.addListeners(hhkService); 44 | 45 | parser.parse(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/service/HHPService.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.service; 2 | 3 | import java.nio.file.Path; 4 | import java.util.LinkedList; 5 | import java.util.regex.Pattern; 6 | 7 | import com.chenfei.chm.helper.connection.Connection; 8 | import com.chenfei.chm.helper.listener.DefaultListener; 9 | 10 | public class HHPService extends DefaultListener implements Connection<String> { 11 | 12 | /** 文件列表 */ 13 | private LinkedList<String> hhpItems = new LinkedList<>(); 14 | 15 | /** 排除文件表达式 */ 16 | private Pattern excludes; 17 | 18 | /** 监听是否结束 */ 19 | private boolean ended = false; 20 | 21 | public HHPService(String excludes) { 22 | if (null != excludes) { 23 | this.excludes = Pattern.compile(excludes, Pattern.CASE_INSENSITIVE); 24 | } 25 | } 26 | 27 | /** 28 | * 根据 excludes 表达式 过滤 文件/目录 29 | * @param relativize 文件/目录 的 相对路径 30 | * @return true:过滤;false:不过滤; 31 | */ 32 | private boolean isFiltered(String relativize) { 33 | // excludes 为空直接返回 false 34 | if (null == this.excludes) { 35 | return false; 36 | } 37 | // 返回 excludes 匹配结果 38 | return this.excludes.matcher(relativize).matches(); 39 | } 40 | 41 | @Override 42 | public String get() { 43 | synchronized (this.hhpItems) { 44 | while (this.hhpItems.isEmpty()) { 45 | 46 | if (this.ended) { 47 | return null; 48 | } 49 | 50 | try { 51 | this.hhpItems.wait(100); 52 | } catch (InterruptedException e) { 53 | e.printStackTrace(); 54 | } 55 | } 56 | return this.hhpItems.removeFirst(); 57 | } 58 | } 59 | 60 | @Override 61 | public boolean isEnded() { 62 | return this.ended && this.hhpItems.isEmpty(); 63 | } 64 | 65 | @Override 66 | public void file(String relativize, Path file) { 67 | 68 | // 排除文件 69 | if (this.isFiltered(relativize)) { 70 | return; 71 | } 72 | 73 | synchronized (this.hhpItems) { 74 | this.hhpItems.add(relativize); 75 | } 76 | } 77 | 78 | @Override 79 | public void end() { 80 | this.ended = true; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/bean/CHM.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.bean; 2 | 3 | /** 4 | * 类CHM.java的实现描述:CHM 文件相关信息 5 | * @author chenfei.chenf 2014-5-4 上午10:41:08 6 | */ 7 | public class CHM { 8 | 9 | /** CHM文件标题 */ 10 | private String title; 11 | 12 | /** CHM文件首页 */ 13 | private String index; 14 | 15 | /** 根目录 */ 16 | private String root; 17 | 18 | /** CHM文件名 */ 19 | private String name; 20 | 21 | /** HHP文件名 */ 22 | private String hhpName; 23 | 24 | /** HHC文件名 */ 25 | private String hhcName; 26 | 27 | /** HHK文件名 */ 28 | private String hhkName; 29 | 30 | /** 31 | * @param root 根目录 32 | * @param name CHM文件名 33 | * @param title 标题 34 | * @param index CHM文件首页 35 | */ 36 | public CHM(String root, String name, String title, String index) { 37 | this.title = title; 38 | this.index = index; 39 | this.root = root; 40 | this.name = name + ".chm"; 41 | this.hhpName = name + ".hhp"; 42 | this.hhcName = name + ".hhc"; 43 | this.hhkName = name + ".hhk"; 44 | } 45 | 46 | public boolean isAboutFile(String path) { 47 | return path.endsWith(".chm") || path.endsWith(".hhp") || path.endsWith(".hhc") || path.endsWith(".hhk"); 48 | } 49 | 50 | public String getTitle() { 51 | return this.title; 52 | } 53 | 54 | public String getIndex() { 55 | return this.index; 56 | } 57 | 58 | public String getRoot() { 59 | return root; 60 | } 61 | 62 | public String getName() { 63 | return this.name; 64 | } 65 | 66 | public String getHHPName() { 67 | return this.hhpName; 68 | } 69 | 70 | public String getHHCName() { 71 | return this.hhcName; 72 | } 73 | 74 | public String getHHKName() { 75 | return this.hhkName; 76 | } 77 | 78 | public String getHHPPath() { 79 | return this.root + "/" + this.hhpName; 80 | } 81 | 82 | public String getHHCPath() { 83 | return this.root + "/" + this.hhcName; 84 | } 85 | 86 | public String getHHKPath() { 87 | return this.root + "/" + this.hhkName; 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return "CHM [title=" + title + ", index=" + index + ", root=" + root + ", name=" + name + ", hhpName=" + hhpName + ", hhcName=" + hhcName 93 | + ", hhkName=" + hhkName + "]"; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/creater/HHKCreater.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.creater; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.OutputStreamWriter; 7 | import java.util.concurrent.CountDownLatch; 8 | 9 | import com.chenfei.chm.helper.bean.CHM; 10 | import com.chenfei.chm.helper.bean.HHKItem; 11 | import com.chenfei.chm.helper.connection.Connection; 12 | 13 | /** 14 | * 类HHKCreater.java的实现描述:HHK 文件的创建线程 15 | * @author chenfei.chenf 2014-5-4 上午10:42:26 16 | */ 17 | public class HHKCreater implements Runnable { 18 | 19 | private CHM chm; 20 | 21 | private Connection<HHKItem> hhkItems; 22 | 23 | private CountDownLatch countDownLatch; 24 | 25 | public HHKCreater(CHM chm, Connection<HHKItem> hhkItems, CountDownLatch countDownLatch) { 26 | this.chm = chm; 27 | this.hhkItems = hhkItems; 28 | this.countDownLatch = countDownLatch; 29 | } 30 | 31 | @Override 32 | public void run() { 33 | 34 | try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.chm.getHHKPath()), "GB2312"))) { 35 | 36 | this.writeHead(bufferedWriter); 37 | 38 | while (!this.hhkItems.isEnded()) { 39 | 40 | HHKItem hhkItem = this.hhkItems.get(); 41 | 42 | if (null == hhkItem) { 43 | break; 44 | } 45 | 46 | this.writeItem(bufferedWriter, hhkItem); 47 | } 48 | 49 | this.writeFoot(bufferedWriter); 50 | 51 | } catch (Throwable e) { 52 | e.printStackTrace(); 53 | } 54 | 55 | this.countDownLatch.countDown(); 56 | } 57 | 58 | private void writeHead(BufferedWriter bufferedWriter) throws IOException { 59 | bufferedWriter.write("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">"); 60 | bufferedWriter.newLine(); 61 | bufferedWriter.write("<HTML><HEAD></HEAD><BODY><UL>"); 62 | bufferedWriter.newLine(); 63 | } 64 | 65 | private void writeItem(BufferedWriter bufferedWriter, HHKItem hhkItem) throws IOException { 66 | 67 | bufferedWriter.write("<LI><OBJECT type=\"text/sitemap\">"); 68 | bufferedWriter.newLine(); 69 | 70 | bufferedWriter.write("<param name=\"Name\" value=\"" + hhkItem.getKey() + "\">"); 71 | bufferedWriter.newLine(); 72 | 73 | bufferedWriter.write("<param name=\"Local\" value=\"" + hhkItem.getValue() + "\">"); 74 | bufferedWriter.newLine(); 75 | 76 | bufferedWriter.write("</OBJECT></LI>"); 77 | bufferedWriter.newLine(); 78 | } 79 | 80 | private void writeFoot(BufferedWriter bufferedWriter) throws IOException { 81 | bufferedWriter.write("</UL></BODY></HTML>"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/creater/HHPCreater.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.creater; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.OutputStreamWriter; 7 | import java.util.concurrent.CountDownLatch; 8 | 9 | import com.chenfei.chm.helper.bean.CHM; 10 | import com.chenfei.chm.helper.connection.Connection; 11 | 12 | /** 13 | * 类HHPCreater.java的实现描述:HHP 文件的创建线程 14 | * @author chenfei.chenf 2014-5-4 上午10:42:36 15 | */ 16 | public class HHPCreater implements Runnable { 17 | 18 | private CHM chm; 19 | 20 | private Connection<String> hhpItems; 21 | 22 | private CountDownLatch countDownLatch; 23 | 24 | public HHPCreater(CHM chm, Connection<String> hhpItems, CountDownLatch countDownLatch) { 25 | this.chm = chm; 26 | this.hhpItems = hhpItems; 27 | this.countDownLatch = countDownLatch; 28 | } 29 | 30 | @Override 31 | public void run() { 32 | 33 | try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.chm.getHHPPath()), "GB2312"))) { 34 | 35 | this.writeHead(bufferedWriter); 36 | 37 | while (!this.hhpItems.isEnded()) { 38 | 39 | String hhcItem = this.hhpItems.get(); 40 | 41 | if (null == hhcItem) { 42 | break; 43 | } 44 | 45 | this.writeItem(bufferedWriter, hhcItem); 46 | } 47 | 48 | } catch (Throwable e) { 49 | e.printStackTrace(); 50 | } 51 | 52 | this.countDownLatch.countDown(); 53 | } 54 | 55 | private void writeHead(BufferedWriter bufferedWriter) throws IOException { 56 | bufferedWriter.write("[OPTIONS]"); 57 | bufferedWriter.newLine(); 58 | bufferedWriter.write("Compatibility=1.1 or later"); 59 | bufferedWriter.newLine(); 60 | bufferedWriter.write("Language=0x804 中文(中国)"); 61 | bufferedWriter.newLine(); 62 | bufferedWriter.write("Compiled file=" + this.chm.getName()); 63 | bufferedWriter.newLine(); 64 | bufferedWriter.write("Default topic=" + this.chm.getIndex()); 65 | bufferedWriter.newLine(); 66 | bufferedWriter.write("Title=" + this.chm.getTitle()); 67 | bufferedWriter.newLine(); 68 | bufferedWriter.write("Contents file=" + this.chm.getHHCName()); 69 | bufferedWriter.newLine(); 70 | bufferedWriter.write("Index file=" + this.chm.getHHKName()); 71 | bufferedWriter.newLine(); 72 | bufferedWriter.write("[FILES]"); 73 | bufferedWriter.newLine(); 74 | } 75 | 76 | private void writeItem(BufferedWriter bufferedWriter, String hhcItem) throws IOException { 77 | bufferedWriter.write(hhcItem); 78 | bufferedWriter.newLine(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/service/HHCService.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.service; 2 | 3 | import java.nio.file.Path; 4 | import java.util.LinkedList; 5 | import java.util.regex.Pattern; 6 | 7 | import com.chenfei.chm.helper.bean.HHCItem; 8 | import com.chenfei.chm.helper.bean.HHCItem.Action; 9 | import com.chenfei.chm.helper.connection.Connection; 10 | import com.chenfei.chm.helper.listener.DefaultListener; 11 | 12 | public class HHCService extends DefaultListener implements Connection<HHCItem> { 13 | 14 | /** 文件列表 */ 15 | private LinkedList<HHCItem> hhcItems = new LinkedList<>(); 16 | 17 | /** 排除文件表达式 */ 18 | private Pattern excludes; 19 | 20 | /** 监听是否结束 */ 21 | private boolean ended = false; 22 | 23 | public HHCService(String excludes) { 24 | if (null != excludes) { 25 | this.excludes = Pattern.compile(excludes, Pattern.CASE_INSENSITIVE); 26 | } 27 | } 28 | 29 | /** 30 | * 根据 excludes 表达式 过滤 文件/目录 31 | * @param relativize 文件/目录 的 相对路径 32 | * @return true:过滤;false:不过滤; 33 | */ 34 | private boolean isFiltered(String relativize) { 35 | // excludes 为空直接返回 false 36 | if (null == this.excludes) { 37 | return false; 38 | } 39 | // 返回 excludes 匹配结果 40 | return this.excludes.matcher(relativize).matches(); 41 | } 42 | 43 | @Override 44 | public HHCItem get() { 45 | synchronized (this.hhcItems) { 46 | while (this.hhcItems.isEmpty()) { 47 | 48 | if (this.ended) { 49 | return null; 50 | } 51 | 52 | try { 53 | this.hhcItems.wait(100); 54 | } catch (InterruptedException e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | return this.hhcItems.removeFirst(); 59 | } 60 | } 61 | 62 | @Override 63 | public boolean isEnded() { 64 | return this.ended && this.hhcItems.isEmpty(); 65 | } 66 | 67 | @Override 68 | public void file(String relativize, Path file) { 69 | 70 | // 排除文件 71 | if (this.isFiltered(relativize)) { 72 | return; 73 | } 74 | 75 | synchronized (this.hhcItems) { 76 | this.hhcItems.add(new HHCItem(relativize, file, null)); 77 | } 78 | } 79 | 80 | @Override 81 | public void directoryIn(String relativize, Path directory) { 82 | 83 | // 排除路径 84 | if (this.isFiltered(relativize)) { 85 | return; 86 | } 87 | 88 | synchronized (this.hhcItems) { 89 | this.hhcItems.add(new HHCItem(relativize, directory, Action.IN)); 90 | } 91 | } 92 | 93 | @Override 94 | public void directoryOut(String relativize, Path directory) { 95 | 96 | // 排除路径 97 | if (this.isFiltered(relativize)) { 98 | return; 99 | } 100 | 101 | synchronized (this.hhcItems) { 102 | this.hhcItems.add(new HHCItem(relativize, directory, Action.OUT)); 103 | } 104 | } 105 | 106 | @Override 107 | public void end() { 108 | this.ended = true; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/parser/Parser.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.parser; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.FileVisitResult; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | import java.nio.file.SimpleFileVisitor; 9 | import java.nio.file.attribute.BasicFileAttributes; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import com.chenfei.chm.helper.bean.CHM; 14 | import com.chenfei.chm.helper.listener.Listener; 15 | 16 | public class Parser { 17 | 18 | /** 监听器 */ 19 | private List<Listener> listeners = new ArrayList<Listener>(); 20 | 21 | private CHM chm; 22 | 23 | public Parser(CHM chm) { 24 | this.chm = chm; 25 | } 26 | 27 | public void parse() throws IOException { 28 | 29 | Path rootPath = Paths.get(chm.getRoot()); 30 | 31 | recursiveParse(rootPath); 32 | 33 | // 触发结束 34 | triggerEnd(); 35 | } 36 | 37 | private void recursiveParse(final Path rootPath) throws IOException { 38 | 39 | Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() { 40 | 41 | private String relativize(Path path) { 42 | return rootPath.relativize(path).toString(); 43 | } 44 | 45 | @Override 46 | public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attrs) throws IOException { 47 | 48 | String relativize = this.relativize(directory); 49 | 50 | if (!"".equals(relativize)) { 51 | Parser.this.triggerDirectoryIn(relativize, directory); 52 | } 53 | 54 | return FileVisitResult.CONTINUE; 55 | } 56 | 57 | @Override 58 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 59 | 60 | String relativize = this.relativize(file); 61 | 62 | if (!"".equals(relativize) && !Parser.this.chm.isAboutFile(relativize)) { 63 | Parser.this.triggerFile(relativize, file); 64 | } 65 | 66 | return FileVisitResult.CONTINUE; 67 | } 68 | 69 | @Override 70 | public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException { 71 | 72 | String relativize = this.relativize(directory); 73 | 74 | if (!"".equals(relativize)) { 75 | Parser.this.triggerDirectoryOut(relativize, directory); 76 | } 77 | 78 | return FileVisitResult.CONTINUE; 79 | } 80 | 81 | }); 82 | } 83 | 84 | private void triggerFile(String relativize, Path file) { 85 | for (Listener listener : listeners) { 86 | listener.file(relativize, file); 87 | } 88 | } 89 | 90 | private void triggerDirectoryIn(String relativize, Path directory) { 91 | for (Listener listener : listeners) { 92 | listener.directoryIn(relativize, directory); 93 | } 94 | } 95 | 96 | private void triggerDirectoryOut(String relativize, Path directory) { 97 | for (Listener listener : listeners) { 98 | listener.directoryOut(relativize, directory); 99 | } 100 | } 101 | 102 | private void triggerEnd() { 103 | for (Listener listener : listeners) { 104 | listener.end(); 105 | } 106 | } 107 | 108 | public void addListeners(Listener listener) { 109 | this.listeners.add(listener); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/creater/HHCCreater.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.creater; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.OutputStreamWriter; 7 | import java.util.concurrent.CountDownLatch; 8 | 9 | import com.chenfei.chm.helper.bean.CHM; 10 | import com.chenfei.chm.helper.bean.HHCItem; 11 | import com.chenfei.chm.helper.connection.Connection; 12 | import com.chenfei.chm.helper.utils.FileUtils; 13 | import com.chenfei.chm.helper.utils.HTMLUtils; 14 | 15 | /** 16 | * 类HHCCreater.java的实现描述:HHC 文件的创建线程 17 | * @author chenfei.chenf 2014-5-4 上午10:42:11 18 | */ 19 | public class HHCCreater implements Runnable { 20 | 21 | private CHM chm; 22 | 23 | private Connection<HHCItem> hhcItems; 24 | 25 | private CountDownLatch countDownLatch; 26 | 27 | public HHCCreater(CHM chm, Connection<HHCItem> hhcItems, CountDownLatch countDownLatch) { 28 | this.chm = chm; 29 | this.hhcItems = hhcItems; 30 | this.countDownLatch = countDownLatch; 31 | } 32 | 33 | @Override 34 | public void run() { 35 | 36 | try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.chm.getHHCPath()), "GB2312"))) { 37 | 38 | this.writeHead(bufferedWriter); 39 | 40 | while (!this.hhcItems.isEnded()) { 41 | 42 | HHCItem hhcItem = this.hhcItems.get(); 43 | 44 | if (null == hhcItem) { 45 | break; 46 | } 47 | 48 | if (hhcItem.isDirectory()) { 49 | if (hhcItem.isIn()) { 50 | this.writeDirectoryIn(bufferedWriter, hhcItem); 51 | } 52 | if (hhcItem.isOut()) { 53 | this.writeDirectoryOut(bufferedWriter, hhcItem); 54 | } 55 | } else { 56 | this.writeFile(bufferedWriter, hhcItem); 57 | } 58 | } 59 | 60 | this.writeFoot(bufferedWriter); 61 | 62 | } catch (Throwable e) { 63 | e.printStackTrace(); 64 | } 65 | 66 | this.countDownLatch.countDown(); 67 | } 68 | 69 | private void writeHead(BufferedWriter bufferedWriter) throws IOException { 70 | bufferedWriter.write("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">"); 71 | bufferedWriter.newLine(); 72 | bufferedWriter.write("<HTML><HEAD></HEAD><BODY>"); 73 | bufferedWriter.newLine(); 74 | bufferedWriter.write("<OBJECT type=\"text/site properties\"><param name=\"Window Styles\" value=\"0x800025\"/></OBJECT>"); 75 | bufferedWriter.newLine(); 76 | bufferedWriter.write("<UL>"); 77 | bufferedWriter.newLine(); 78 | } 79 | 80 | private void writeFile(BufferedWriter bufferedWriter, HHCItem hhcItem) throws IOException { 81 | String title = ""; 82 | 83 | String[] fileNameInfos = FileUtils.split(hhcItem.getFile().getName()); 84 | String extension = fileNameInfos[1]; 85 | 86 | if (null != extension && (".html".equalsIgnoreCase(extension) || ".htm".equalsIgnoreCase(extension))) { 87 | title = HTMLUtils.getTitle(hhcItem.getFile(), "GB2312"); 88 | } 89 | 90 | if (title.isEmpty()) { 91 | title = fileNameInfos[0]; 92 | } 93 | 94 | if (!title.isEmpty()) { 95 | StringBuilder stringBuilder = new StringBuilder(); 96 | stringBuilder.append("<LI><OBJECT type=\"text/sitemap\"><param name=\"Name\" value=\""); 97 | stringBuilder.append(title); 98 | stringBuilder.append("\"/><param name=\"Local\" value=\""); 99 | stringBuilder.append(hhcItem.getRelativize()); 100 | stringBuilder.append("\"/></OBJECT></LI>"); 101 | 102 | bufferedWriter.write(stringBuilder.toString()); 103 | bufferedWriter.newLine(); 104 | } 105 | } 106 | 107 | private void writeDirectoryIn(BufferedWriter bufferedWriter, HHCItem hhcItem) throws IOException { 108 | bufferedWriter.write("<LI><OBJECT type=\"text/sitemap\"><param name=\"Name\" value=\"" + hhcItem.getFile().getName() + "\"/></OBJECT></LI>"); 109 | bufferedWriter.newLine(); 110 | bufferedWriter.write("<UL>"); 111 | bufferedWriter.newLine(); 112 | } 113 | 114 | private void writeDirectoryOut(BufferedWriter bufferedWriter, HHCItem hhcItem) throws IOException { 115 | bufferedWriter.write("</UL>"); 116 | bufferedWriter.newLine(); 117 | } 118 | 119 | private void writeFoot(BufferedWriter bufferedWriter) throws IOException { 120 | bufferedWriter.write("</UL>"); 121 | bufferedWriter.newLine(); 122 | bufferedWriter.write("</BODY></HTML>"); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/com/chenfei/chm/helper/service/parser/impl/RegexHHKParser.java: -------------------------------------------------------------------------------- 1 | package com.chenfei.chm.helper.service.parser.impl; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Path; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | import org.jsoup.Jsoup; 11 | import org.jsoup.nodes.Document; 12 | import org.jsoup.nodes.Element; 13 | import org.jsoup.select.Elements; 14 | 15 | import com.chenfei.chm.helper.bean.HHKItem; 16 | import com.chenfei.chm.helper.service.parser.HHKParser; 17 | 18 | public class RegexHHKParser implements HHKParser { 19 | 20 | /** 索引文件表达式 */ 21 | private Pattern includes; 22 | 23 | /** 索引文件编码 */ 24 | private String charset; 25 | 26 | /** 索引解析表达式 */ 27 | private Regex[] regexes; 28 | 29 | /** 30 | * @param includes 索引文件表达式 31 | * @param charset 索引文件编码 32 | * @param regexes HHKItem 匹配规则 33 | */ 34 | public RegexHHKParser(String includes, String charset, Regex... regexes) { 35 | this.includes = Pattern.compile(includes, Pattern.CASE_INSENSITIVE); 36 | this.charset = charset; 37 | this.regexes = regexes; 38 | } 39 | 40 | @Override 41 | public List<HHKItem> parse(String relativize, Path file) { 42 | // 不匹配,则结束 43 | if (!this.includes.matcher(relativize).matches()) { 44 | return null; 45 | } 46 | 47 | List<HHKItem> hhkItems = new LinkedList<>(); 48 | 49 | try { 50 | Document document = Jsoup.parse(file.toFile(), this.charset); 51 | 52 | for (Regex regex : this.regexes) { 53 | hhkItems.addAll(this.parseRegex(document, regex)); 54 | } 55 | 56 | } catch (IOException e) { 57 | e.printStackTrace(); 58 | } 59 | 60 | return hhkItems; 61 | } 62 | 63 | private List<HHKItem> parseRegex(Document document, Regex regex) { 64 | 65 | List<HHKItem> hhkItems = new LinkedList<>(); 66 | 67 | Elements hhkItemElements = document.select(regex.getRegex()); 68 | 69 | for (int index = 0, size = hhkItemElements.size(); index < size; index++) { 70 | Element hhkItemElement = hhkItemElements.get(index); 71 | 72 | String key = getFieldValue(hhkItemElement, regex.getKeyRegex()); 73 | 74 | if (null == key) { 75 | continue; 76 | } 77 | 78 | String value = getFieldValue(hhkItemElement, regex.getValueRegex()); 79 | 80 | if (null == value) { 81 | continue; 82 | } 83 | 84 | if (value.startsWith("../")) { 85 | value = value.substring(3); 86 | } 87 | 88 | hhkItems.add(new HHKItem(key, value)); 89 | } 90 | 91 | return hhkItems; 92 | } 93 | 94 | private String getFieldValue(Element element, FieldRegex regex) { 95 | 96 | if (!regex.getValue().isEmpty()) { 97 | element = element.select(regex.getValue()).first(); 98 | } 99 | 100 | if (null == element) { 101 | return null; 102 | } 103 | 104 | if (regex.isAttr()) { 105 | return element.attr(regex.getAttr()).trim(); 106 | } 107 | 108 | if (regex.isText()) { 109 | return element.text().trim(); 110 | } 111 | 112 | return null; 113 | } 114 | 115 | /** 116 | * 类RegexHHKParser.java的实现描述:HHK匹配的正则信息 117 | * @author chenfei.chenf 2014-4-23 下午5:40:12 118 | */ 119 | public static class Regex { 120 | 121 | /** HHKItem 正则表达式 根节点 */ 122 | private String regex; 123 | 124 | /** HHKItem 正则表达式 Key */ 125 | private FieldRegex keyRegex; 126 | 127 | /** HHKItem 正则表达式 Value */ 128 | private FieldRegex valueRegex; 129 | 130 | public Regex(String regex, String keyRegex, String valueRegex) { 131 | this.regex = regex; 132 | this.keyRegex = FieldRegex.getRegex(keyRegex); 133 | this.valueRegex = FieldRegex.getRegex(valueRegex); 134 | } 135 | 136 | public String getRegex() { 137 | return regex; 138 | } 139 | 140 | public FieldRegex getKeyRegex() { 141 | return keyRegex; 142 | } 143 | 144 | public FieldRegex getValueRegex() { 145 | return valueRegex; 146 | } 147 | 148 | @Override 149 | public String toString() { 150 | return "Regex [keyRegex=" + keyRegex + ", valueRegex=" + valueRegex + "]"; 151 | } 152 | 153 | } 154 | 155 | private static class FieldRegex { 156 | 157 | /** 表达式值 */ 158 | private String value; 159 | 160 | /** 属性名 */ 161 | private String attr; 162 | 163 | private FieldRegex(String value, String attr) { 164 | this.value = value; 165 | this.attr = attr; 166 | } 167 | 168 | public boolean isText() { 169 | return null == this.attr; 170 | } 171 | 172 | public boolean isAttr() { 173 | return null != this.attr; 174 | } 175 | 176 | public String getValue() { 177 | return value; 178 | } 179 | 180 | public String getAttr() { 181 | return attr; 182 | } 183 | 184 | public static FieldRegex getRegex(String regexStr) { 185 | // 文本节点 text() 186 | Pattern pattern = Pattern.compile("^(.*)text\\(\\)$", Pattern.CASE_INSENSITIVE); 187 | 188 | Matcher matcher = pattern.matcher(regexStr); 189 | 190 | if (matcher.find()) { 191 | return new FieldRegex(matcher.group(1).trim(), null); 192 | } 193 | 194 | // 属性节点 @xxx 195 | pattern = Pattern.compile("^(.*)@(\\S*)$", Pattern.CASE_INSENSITIVE); 196 | 197 | matcher = pattern.matcher(regexStr); 198 | 199 | if (matcher.find()) { 200 | return new FieldRegex(matcher.group(1).trim(), matcher.group(2)); 201 | } 202 | 203 | return new FieldRegex(regexStr, null); 204 | } 205 | 206 | @Override 207 | public String toString() { 208 | return "FieldRegex [value=" + value + ", attr=" + attr + "]"; 209 | } 210 | } 211 | } 212 | --------------------------------------------------------------------------------