All formats

chm-lib

LanguagePHP 5.3.3+ (works on PHP 7 and 8)
LicenseMIT, with parts under MS-PL
Repositorygithub.com/mlocati/chm-lib
DocumentationREADME
Depends onnothing (pure PHP, own LZX decoder)
Read / writeyes / no

chm-lib parses the CHM container (ITSF, ITSP, PMGL/PMGI directory chunks), decompresses LZX content, and parses the sitemap HTML of the table of contents and index into trees of items with names and page paths. It can also merge tables of contents of documentation split into several CHM files. The author tested it on about 400 CHM files.

It’s slower than C libraries (LZX decompression in PHP), but needs no extension, so it runs on any PHP host. The latest release is 1.3.0 (2020).

Installation#

Shell
composer require mlocati/chm-lib

Without Composer, include CHMLib.php from the repository.

Basic usage: list files and read their contents#

PHP
<?php
require 'vendor/autoload.php';

use CHMLib\CHM;
use CHMLib\Entry;

$chm = CHM::fromFile('help.chm');
foreach ($chm->getEntries(Entry::TYPE_FILE) as $entry) {
    echo $entry->getPath(), ' (', $entry->getLength(), " bytes)\n";
}

$page = $chm->getEntryByPath('/index.html');   // case-insensitive by default; null if missing
if ($page !== null) {
    echo $page->getContents();
}

How to extract a CHM to a folder#

PHP
<?php
require 'vendor/autoload.php';

use CHMLib\CHM;
use CHMLib\Entry;

$outDir = 'output';
$chm = CHM::fromFile('help.chm');
foreach ($chm->getEntries(Entry::TYPE_FILE) as $entry) {
    $rel = ltrim($entry->getPath(), '/');
    if (in_array('..', explode('/', $rel), true)) {
        continue;                         // skip paths that escape the folder
    }
    $dest = $outDir . '/' . $rel;
    if (!is_dir(dirname($dest))) {
        mkdir(dirname($dest), 0777, true);
    }
    file_put_contents($dest, $entry->getContents());
}

How to read the table of contents and index#

getTOC() parses the .hhc file and getIndex() the .hhk file; both return a tree, or null if the CHM has none:

PHP
<?php
require 'vendor/autoload.php';

use CHMLib\CHM;

function printTree($tree, $level)
{
    if ($tree === null) {
        return;
    }
    foreach ($tree->getItems() as $item) {
        echo str_repeat('  ', $level), $item->getName(), ' -> ', $item->getLocal(), "\n";
        printTree($item->getChildren(), $level + 1);
    }
}

$chm = CHM::fromFile('help.chm');
printTree($chm->getTOC(), 0);
printTree($chm->getIndex(), 0);

$item->findEntry() returns the Entry of the page an item points to.

How to merge a table of contents split across CHM files#

Large documentation sets put a master table of contents in one CHM that links to others:

PHP
use CHMLib\CHM;
use CHMLib\Map;

$main = CHM::fromFile('main.chm');
$map = new Map();
$map->add('sub1.chm', CHM::fromFile('sub1.chm'));
$map->add('sub2.chm', CHM::fromFile('sub2.chm'));

$toc = $main->getTOC();
$toc->resolve($map);   // items now include the entries of sub1.chm and sub2.chm

Tips#