jchmlib
| Language | Java 6+ |
|---|---|
| License | Apache-2.0 |
| Repository | github.com/chimenchen/jchmlib |
| Documentation | README, javadoc (gradle javadoc) and the sample programs in src/app/java/org/jchmlib/app |
| Depends on | nothing |
| Read / write | yes / no |
jchmlib reads CHM files in pure Java, so it works on any JVM without CHMLib. Beyond listing and reading files, it parses the table of contents into a ChmTopicsTree, searches with the built-in full-text index (or builds its own index when a file has none), and detects and fixes wrong encodings in non-English files.
The project hasn’t changed since 2017 (version 0.5.4). It still works for reading, but don’t expect fixes. Builds are served by JitPack.
Installation#
Gradle:
repositories {
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation("com.github.chimenchen:jchmlib:v0.5.4")
}
Maven:
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.chimenchen</groupId>
<artifactId>jchmlib</artifactId>
<version>v0.5.4</version>
</dependency>
Basic usage: open a CHM and read the home page#
import org.jchmlib.ChmFile;
import org.jchmlib.ChmUnitInfo;
import java.io.IOException;
public class ReadChm {
public static void main(String[] args) throws IOException {
ChmFile chm = new ChmFile("help.chm");
System.out.println("title: " + chm.getTitle());
System.out.println("home: " + chm.getHomeFile());
System.out.println("encoding: " + chm.getEncoding());
ChmUnitInfo ui = chm.resolveObject(chm.getHomeFile()); // null if not found
if (ui != null) {
String html = chm.retrieveObjectAsString(ui); // decoded with the CHM's encoding
System.out.println(html.substring(0, Math.min(200, html.length())));
}
}
}
How to list all files in a CHM#
enumerate calls your ChmEnumerator for every entry:
import org.jchmlib.ChmFile;
ChmFile chm = new ChmFile("help.chm");
chm.enumerate(ChmFile.CHM_ENUMERATE_USER, ui ->
System.out.println(ui.getLength() + " " + ui.getPath()));
CHM_ENUMERATE_USER selects normal files and directories; CHM_ENUMERATE_ALL also returns the internal files. enumerateDir("/images/", what, enumerator) lists one folder. To stop early, throw ChmStopEnumeration from the callback.
How to extract all files#
import org.jchmlib.ChmFile;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class ExtractChm {
public static void main(String[] args) throws IOException {
ChmFile chm = new ChmFile("help.chm");
Path outDir = Paths.get("out").toAbsolutePath().normalize();
chm.enumerate(ChmFile.CHM_ENUMERATE_NORMAL | ChmFile.CHM_ENUMERATE_FILES, ui -> {
if (ui.getLength() == 0 || !ui.getPath().startsWith("/")) return;
Path dest = outDir.resolve(ui.getPath().substring(1)).normalize();
if (!dest.startsWith(outDir)) return; // skip paths that escape the folder
ByteBuffer buf = chm.retrieveObject(ui);
if (buf == null) return;
byte[] bytes = new byte[buf.remaining()];
buf.get(bytes);
try {
Files.createDirectories(dest.getParent());
Files.write(dest, bytes);
} catch (IOException e) {
System.err.println("cannot write " + dest + ": " + e);
}
});
}
}
How to read the table of contents#
import org.jchmlib.ChmFile;
import org.jchmlib.ChmTopicsTree;
public class Toc {
static void print(ChmTopicsTree node, int level) {
for (ChmTopicsTree child : node.children) {
System.out.println(" ".repeat(level) + child.title + " -> " + child.path);
print(child, level + 1);
}
}
public static void main(String[] args) throws Exception {
ChmFile chm = new ChmFile("help.chm");
ChmTopicsTree root = chm.getTopicsTree(); // parsed from the .hhc file
if (root != null) print(root, 0);
}
}
How to search a CHM#
indexSearch uses the full-text index compiled into the CHM:
java.util.HashMap<String, String> hits = chm.indexSearch("install", true /*whole words*/, false /*titles only*/);
if (hits != null) {
hits.forEach((url, title) -> System.out.println(title + " " + url));
}
Tips#
getDetectedLCID()andgetEncoding()tell which language and charset the file uses; jchmlib uses them when it decodes text.getTitleOfObject(path)returns the title of a page from the table of contents.ChmFilehas noclose()method; it keeps the file open until garbage collection.