All formats

jchmlib

LanguageJava 6+
LicenseApache-2.0
Repositorygithub.com/chimenchen/jchmlib
DocumentationREADME, javadoc (gradle javadoc) and the sample programs in src/app/java/org/jchmlib/app
Depends onnothing
Read / writeyes / 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:

Kotlin
repositories {
    maven { url = uri("https://jitpack.io") }
}
dependencies {
    implementation("com.github.chimenchen:jchmlib:v0.5.4")
}

Maven:

XML
<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#

Java
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:

Java
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#

Java
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#

Java
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
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#