The last time I had to build a brand new help file was some time ago — maybe even ten years ago — and in the world of software, that’s an age.
For the past few months I’ve been working hard on a new release of iDefrag, version 5, and as part of this I’m rewriting the documentation. Rather than using hand-written HTML like I did before, I’ve chosen this time around to use a documentation generator, Sphinx. The advantages of this approach include:
-
Built-in support for indexing and cross-referencing.
-
The ability to write the documententation in plain text.
-
Keeps the presentation details separate from the content (via theming and templates).
-
Supports multiple output formats, not just HTML.
The current version of Sphinx doesn’t directly support building Apple Help Books, but I’ve submitted a pull request to fix that so hopefully by the time you read this you’ll be able to do
$ sphinx-quickstart
fill in some fields and then do
$ make applehelp
to generate a help book.
(If you do do that, you’ll want to edit your conf.py
file quite a bit, and
you probably don’t want to use the default theme either.)
Anyway, all of the Sphinx related stuff was fine, and worked as documented. Unlike Apple Help, which doesn’t. I spent an entire day struggling to make a help book that actually worked, and most of that is because of problems with the documentation.
Let’s start with the Info.plist. Apple gives this not particularly helpful table:
Key | Exact or sample value |
---|---|
CFBundleDevelopmentRegion | en_us |
CFBundleIdentifier | com.mycompany.surfwriter.help |
CFBundleInfoDictionaryVersion | 6.0 |
CFBundleName | SurfWriter |
CFBundlePackageType | BNDL |
CFBundleShortVersionString | 1 |
CFBundleSignature | hbwr |
CFBundleVersion | 1 |
HPDBookAccessPath | SurfWriter.html |
HPDBookIconPath | shrd/SurfIcn.png |
HPDBookIndexPath | SurfWriter.helpindex |
HPDBookKBProduct | surfwriter1 |
HPDBookKBURL | https://mycompany.com/kbsearch.py?p='product'&q='query'&l='lang' |
HPDBookRemoteURL | https://help.mycompany.com/snowleopard/com.mycompany.surfwriter.help/r1 |
HPDBookTitle | SurfWriter Help |
HPDBookType | 3 |
HPDBookTopicListCSSPath | sty/topiclist.css |
HPDBookTopicListTemplatePath | sty/topiclist.xquery |
There are two serious problems with the table above. The first is that some of it is wrong(!), and the second is that it doesn’t indicate which values are sample values and which are required.
Here’s what you actually need:
Key | Value |
---|---|
CFBundleDevelopmentRegion | en-us |
CFBundleIdentifier | your help bundle identifier |
CFBundleInfoDictionaryVersion | 6.0 |
CFBundlePackageType | BNDL |
CFBundleShortVersionString | your short version string - e.g. 1.2.3 (108) |
CFBundleSignature | hbwr |
CFBundleVersion | your version - e.g. 108 |
HPDBookAccessPath | _access.html (see below) |
HPDBookIndexPath | the name of your help index file |
HPDBookTitle | the title of your help file |
HPDBookType | 3 |
The first thing to note is that CFBundleDevelopmentRegion
should have a
hyphen, not an underscore. Apple’s utilities generate this properly, but
the documentation is wrong.
The second thing to note is that in spite of the documentation implying that
you can use your help bundle identifier to refer to your help bundle (which
would, admittedly, make sense), you can’t. You need to use the HPDBookTitle
value. Oh, and ignore any references to AppleTitle
meta tags. You don’t
need those.
The third thing relates to HPDBookAccessPath
. The file referred to there
must be a valid XHTML file. In particular, it cannot be an HTML5 document
— that will simply not work, and the error messages you get on the system
console are completely uninformative.
The best solution I’ve come up with for this particular problem, as I want to
generate modern HTML output, is to make a file called _access.html
and put
the following in it:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Title Goes Here</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="robots" content="noindex" />
<meta http-equiv="refresh" content="0;url=index.html" />
</head>
<body>
</body>
</html>
This means that both helpd
and the help indexer (hiutil
) are happy, and I
can write my index page using modern HTML. Incidentally, Apple appears to be
using a similar trick in the help for the current version of Mail. Obviously
you can change the index.html
in the above to whatever you need.
In your application bundle, you need to fill in the following keys
Key | Value |
---|---|
CFBundleHelpBookFolder | The path of your help book relative to Resources - e.g. SurfWriter.help |
CFBundleHelpBookName | The value from HPDBookTitle, above |
Note that while the HPDBookTitle
is displayed to the user, it can be
localised using InfoPlist.strings
. Note also that you absolutely cannot,
contrary to what the documentation implies, give a bundle ID here. It just
doesn’t work. You could however, if you wanted, write an
InfoPlist.strings
file like this:
HPDBookTitle = "SurfWriter Help"
then put the bundle ID in as the HPDBookTitle
in the Info.plist
.
Oh, and if you think you’re going to be able to double-click a help book to
preview it, think again. That won’t work. Instead, you need either to use it
from within your application, or you can put it in
~/Library/Documentation/Help
(you might have to make that folder) and
double-click it in there. Why? Because help files are indexed and you can
only open them if they’re registered in the index.
One other thing that isn’t really documented at all is what exactly the
HPDBookRemoteURL
will do for you. There’s some handwaving about being able
to offer remote content updates, but how the URL is used is skirted over.
Well, if you do set HPDBookRemoteURL
, Help Viewer will essentially expect
it to point at a copy of the Resources
folder of your bundle; so if you have
HPDBookRemoteURL
set to http://example.com/foo/bar/
, then you’re going to
get requests like http://example.com/foo/bar/en.lproj/index.html
(and so
on).
Useful update (Feb 29th 2016)
You may have noticed that Help Viewer has a button to toggle the table of contents in your help file. Matt Shepherd did a bit of work looking into this and it turns out that it’s controlled by a Javascript API — see Matt’s gist for more information.