An Internationalized Software Project With Auto Tools
Prev Adding HTML Help Documentation Next

Adding HTML Help Documentation

Integration Into The Build System

The main problem adding html help to the gnu autotools is, that there is no template available. In other words: there is no htmltoolize, which does all the necessary steps. So they have to be performed manually. First the creation of the required files.
# mkdir doc
# touch doc/Makefile.am
# touch doc/testproj.xsl
# touch doc/testproj.xml
# touch doc/test_chapter.xml
# touch doc/apicture.png
# touch doc/generated_files.mk
All the documentation goes into the doc directory. The top level Makefile.am has to point into that directory:

Makefile.am

SUBDIRS = src po doc

ACLOCAL_AMFLAGS = -I m4

...

force-update-doc:
	cd doc && $(MAKE) $(AM_MAKEFLAGS) force-update-doc

.PHONY: check-gettext update-po update-gmo force-update-gmo force-update-doc
The additional target force-update-doc will force the rebuild of the documentation.

configure.ac needs more changes: A Makefile must be created in the doc directory, the installation directory of the documentation must be configurable and the required xsltprog and docbook stylesheets must be seeked or checked for:

configure.ac

AC_PREREQ(2.59)
AC_INIT(testproj, 0.1, j.t.kirk@ncc-1701.ufp)
AC_CONFIG_SRCDIR([src/main.cpp])
AC_CONFIG_HEADER([config.h])
AM_INIT_AUTOMAKE
AC_LIBTOOL_DLOPEN
AC_PROG_LIBTOOL
AM_GNU_GETTEXT([external])

AC_ARG_WITH(helpdir,[AC_HELP_STRING([--with-helpdir=dir],
	[Installation directory of the html help [${datadir}/doc/testproj]])],
	[helpdir="$withval"],[helpdir='${datadir}/doc/${PACKAGE}'])
AC_SUBST(helpdir)

# Checks for programs.
AC_PROG_CXX

can_build_documentation=yes

AC_PATH_PROG([xsltproc],[xsltproc])
if test -z "$xsltproc" ; then
	AC_MSG_WARN([xsltproc was not found. If you want to change and compile the documentation, \
please install libxslt (http://xmlsoft.org/XSLT/)])
	can_build_documentation=no
fi

AC_CHECK_FILE([doc/docbook/htmlhelp/htmlhelp.xsl], , [AC_MSG_WARN([htmlhelp sytle sheet was not found. \
If you want to change and compile the documentation, please install docbook-xsl \
(http://docbook.sourceforge.net/) and create a symlink in doc]) ; can_build_documentation=no])

AC_MSG_CHECKING([whether documentation can be changed and compiled])
AC_MSG_RESULT($can_build_documentation)
AC_SUBST(can_build_documentation)

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.
AC_CONFIG_FILES([Makefile src/Makefile src/testmodule/Makefile po/Makefile.in m4/Makefile \
	doc/Makefile])
AC_OUTPUT
The first change provides a variable helpdir, which defaults to ${datadir}/doc/${PACKAGE}, which in this case is usually /usr/share/doc/testproj. The user can override it with the configure switch --with-helpdir.

The second change seeks xsltproc and the html help docbook style sheet. The result is shown in a line "Checking whether documentation can be changed and compiled".

The last change makes sure, that the Makefile in the doc directory is created as well.

To use the helpdir variable in the program, all corresponding Makefile's must pass it to the compiler. Here the variable is passed to top level source files in the src directory only:

src/Makefile.am

...

localedir = $(datadir)/locale
DEFS = -DLOCALEDIR=\"$(localedir)\" -DHELPDIR=\"$(helpdir)\" @DEFS@
As an example, the variable is used to print out the urls of two help files:

src/main.cpp

#include <stdio.h>
#include <locale.h>
#include "testmodule/testfunc.h"
#include "i18n.h"

const char * helpdir = HELPDIR;

...

        // multi plural test. Usefull, if the target language has other plural forming rules.
        printf(i18nP("You have won one point!\n", "You have won %d points!\n", n), n);

        // reference to english html help
        printf(i18n("\nHelp: %s/index.html\n"), helpdir);
        printf(i18n("Test chapter: %s/test_chapter.html\n"), helpdir);
}
The Makefile template is quite long and therefore split:

doc/Makefile.am

SUBDIRS =

@AMDEP_TRUE@@am__include@ @am__quote@generated_files.mk@am__quote@

pictures = apicture.png
source_tarball_files = $(PACKAGE).xsl $(PACKAGE).xml test_chapter.xml 

htmlhelpdir = $(helpdir)
htmlhelp_created_files = $(PACKAGE).hhc $(PACKAGE).hhk $(PACKAGE).hhp $(TESTPROJ_DOCFILES)
htmlhelp_DATA = $(pictures) $(TESTPROJ_DOCFILES)

EXTRA_DIST = $(source_tarball_files) $(pictures) generated_files.mk $(htmlhelp_created_files)

DISTCLEANFILES = Makefile

check_can_build_documentation:
	@if test x$(can_build_documentation) != "xyes" ; then echo "Missing libxslt." \
	"Rerun configure and check for 'checking whether documentation can be changed and compiled... yes'!" ; \
	exit 1 ; fi

The most obscure line is the line starting with @AMDEP_TRUE@. It is copied unchanged into Makefile.in. configure converts it into the line: 'include generated_files.mk'. If make is started, this line includes generated_files.mk, which gives the variable TESTPROJ_DOCFILES a value (see below): a list of all .html files created here. A similar magic is applied in the src directory, where the c compiler additionally creates dependency files, which are included into the Makefile as well.

The next line just define some variables. "pictures" contains all files, which will be directly copied to the documentation directory, usually pictures. source_tarball_files lists all documentation source files. The html help files will be generated from it. htmlhelpdir is the installation directory. It be set by configure and can be changed with the switch --with-helpdir.

htmlhelp_created_files lists all files, which will be created during the documentation. htmlhelp_DATA list the files to be installed. EXTRA_DIST lists all files, which will go into the source tarball. DISTCLEANFILES finally controlls, which files to be delete if "make distclean" is called. Here just the Makefile has to be deleted.

The target check_can_build_documentation checks, if configure has found all prerequisites to build the documentation. If not, an error is printed out to point the user into the right direction.

doc/Makefile.am

...

generated_files_include:
	@echo -n "TESTPROJ_DOCFILES = "> $(srcdir)/generated_files.mk2 
	@find . -maxdepth 1 -type f -name "*.html" | sort | $(AWK) '{printf(" \\\n\t%s", $$0)}' \
		>> $(srcdir)/generated_files.mk2
	@if diff $(srcdir)/generated_files.mk $(srcdir)/generated_files.mk2 >/dev/null 2>&1 ; then \
		echo "generated_files.mk is unchanged" ; \
		rm -f $(srcdir)/generated_files.mk2 ; \
	else \
		mv $(srcdir)/generated_files.mk2 $(srcdir)/generated_files.mk ; \
	fi

all: $(htmlhelp_created_files)

force-update-doc: doc-clean $(htmlhelp_created_files)

$(htmlhelp_created_files): $(source_tarball_files)
	@ $(MAKE) check_can_build_documentation
	$(MAKE) doc-clean
	$(xsltproc) $(srcdir)/$(PACKAGE).xsl $(srcdir)/$(PACKAGE).xml
	@echo "creating generated_files.mk"
	$(MAKE) generated_files_include

maintainer-clean: doc-clean maintainer-clean-recursive

doc-clean:
	-rm -f *.hhp
	-rm -f *.html
	-rm -f *.hhk
	-rm -f *.hhc

.PHONY: check_can_build_documentation generated_files_include force-update-doc doc-clean
The generated_files_include target creates a list of created html files. To do so a file generated_files.mk2 is created and the line "TESTPROJ_DOCFILES = " written into it. Next 'find' is used to list all html files in the doc directory. The list is sorted, processed by awk and added to generated_files.mk2. The awk processing ensures, that generated_files.mk2 becomes a valid Makefile command, which gives the variable TESTPROJ_DOCFILES a value: the a list of all created files. Afterwards this newly created generated_files.mk2 is diff'ed against the old version in generated_files.mk. If nothing has changed, generated_files.mk2 is deleted. Otherwise generated_files.mk is replaced. If the generated_files_include target is done, generated_files.mk is a file, that contains a valid Makefile command, which assigns the variable TESTPROJ_DOCFILES to a list of all generated html files.

The "all" target depends on all htmlhelp_created_files and therefore builds or rebuilds all these files. force-update-doc first delets all generated files and then rebuilds them.

The htmlhelp_created_files target (which is a list of all created files) depend on all source files, stored in the variable source_tarball_files, which is maintained manually. If any of the source files is newer than the any created file, the $(htmlhelp_created_files) target is processed. It must update all created files, otherwise this target is invoked for each target file. As this works only, if libxslt is installed, this is checked first. Afterwards doc-clean is called, which deletes all target files. Afterwards xsltproc is called, which newly creates all target files.

maintainer-clean deletes all created files (through doc-clean) and afterwards calls the built in maintainer-clean-recursive target, which ensures, that maintainer-clean is called on all sub directories and that distclean is called as well.

The usage of this Makefile.am should be clear now: All pictures or other files, which might be changed without changing the created files are added to the 'pictures' variable. All files, whose changes will change the created files (like the xml sources) are added to the source_tarball_files variable. Both go into the source tarball. The created files are installed on 'make install' and listed in a file generated_files.mk, which will be included in the source tarball as well. It allows to track changes in the sources, which requires a rebuild of the documentation.

HTML Help Source Files

As seen above, xsltproc requires two parameters. The first is an xsl file:

doc/testproj.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
  <xsl:import href="docbook/htmlhelp/htmlhelp.xsl"/>
  <xsl:param name="htmlhelp.use.hhk" select="1"/>
  <xsl:param name="htmlhelp.button.next" select="1"/>
  <xsl:param name="htmlhelp.button.previous" select="1"/>
  <xsl:param name="htmlhelp.button.back" select="1"/>
  <xsl:param name="htmlhelp.button.forward" select="1"/>
  <xsl:param name="htmlhelp.hhc.show.root" select="0"/>
  <xsl:param name="htmlhelp.show.favorites" select="1"/>
  <xsl:param name="htmlhelp.show.advanced.search" select="1"/>
  <xsl:param name="htmlhelp.show.menu" select="1"/>
  <xsl:param name="htmlhelp.hhp" select="'testproj.hhp'"/>
  <xsl:param name="htmlhelp.hhc" select="'testproj.hhc'"/>
  <xsl:param name="htmlhelp.hhk" select="'testproj.hhk'"/>
  <xsl:param name="htmlhelp.chm" select="'testproj.chm'"/>
  <xsl:param name="use.id.as.filename" select="1"/>
</xsl:stylesheet>
It mainly defines the target file format (here: html help) and sets some attributes. Most of them customize the layout of the chm help file, if it is created later under windows.

It requires the style sheet to be present in a sub directory:
ln -s /usr/local/share/xsl/docbook/ doc/docbook
The actual location of this style sheet depends on the installation. This soft link has to added to createFromCvs:

createFromCvs.freebsd

...

ln -s /usr/local/share/gettext/gettext.h src
ln -s /usr/local/share/xsl/docbook/ doc/docbook

createFromCvs.gentoo

...

ln -s /usr/share/gettext/gettext.h src
ln -s /usr/share/sgml/docbook/xsl-stylesheets-1.68.1 doc/docbook
The other xsltproc prorameter is the file nameof the root xml document, which contains all or parts of the documentation:

doc/testproj.xml

<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
	<!ENTITY test_chapter SYSTEM "test_chapter.xml">
	<!ENTITY directions_right "right">
	<!ENTITY results_right "right ">
	]>
<book lang="en">
	<bookinfo>
		<legalnotice>
			<para>
				Legal notice ...
			</para>
		</legalnotice>
		<author>
			<firstname>J.T.</firstname>
			<surname>Kirk</surname>
		</author>
		<copyright>
			<year>2005</year>
			<year>2006</year>
			<holder>J.T. Kirk</holder>
		</copyright>
	</bookinfo>
	<title>testproj</title>
	<preface id="overview"><title>Preface to testproj</title>
		<para>
			<indexterm><primary>testproj</primary></indexterm>
			<indexterm><primary>Preface</primary></indexterm>
			This is the preface. It contains of a paragrapgh, 
			a list and a link. This paragraph contains useless 
			words only. Its mere purpose is to have 
			some text in order to see the paragraphs end.</para>
		<para>
			A small list:
			<variablelist><itemizedlist>
					<listitem>list item 1</listitem>
					<listitem>list item 2</listitem>
				</itemizedlist></variablelist>
		</para>
		<para>
			Demonstration of amigous translations:
			<informaltable>
				<tgroup cols='2'>
					<tbody>
						<row>
							<entry>Possible directions</entry>
							<entry>Possible results</entry>
						</row>
						<row>
							<entry>&directions_right;</entry>
							<entry>&results_right;</entry>
						</row>
						<row>
							<entry>left</entry>
							<entry>wrong</entry>
						</row>
					</tbody>
				</tgroup>
			</informaltable>
		</para>
		<para> Link:
			<link linkend="test_chapter">
				Jump to test chapter.
			</link>
		</para>
	</preface>
&test_chapter;
</book>
In the header, the entity test_chapter ist defined, which points to the file test_chapter.xml. This allows to split the documentation into seperate files. It furthermore defines the two entities directions_right and results_right, which will in the nect chapter demonstrate the translation of ambigous strings. Here it is very important to note, that results_right is defined as the string "right" with an additional space it its end, which distiguishes it from directions_right.

The root xml tag of the documentation is "book". It has the attribute "lang", which is set to en. It is highly recommended to set this language here, but only here! The reason is, that this string will be locallized later as well and setting it to another lanuage code in other languages will automatically translate some strings provided by xsltproc (like "chapter").

The "book" tag contains numerous sub-tags, which are mainly quite self explaining. Prefaces and chapters carry an id, which allow to link to them (see link tag). Both are structured in paragraphs. One paragraph contains a table, which contains the ambigous string "right" twice, but through the entities defined above. It does not harm, that wrong_left if defined as the string "right" with a trailing blank, as it is folowed by an xml tag. Otherwise there would be not ambiguity.

At the end, the test_chapter entity is 'invoked', so that the file test_chapter.xml is logically inserted here.

doc/test_chapter.xml

<chapter id="test_chapter"><title>Test Chapter</title>
	<para><indexterm><primary>Test chapter</primary></indexterm>
		This is the first and only chapter. It contains a note, a picture and a table.
	</para>
	<para> This paragraph contains a picture
		<mediaobject><imageobject><imagedata fileref="apicture.png" format="png"/>
		</imageobject></mediaobject>
		<emphasis>within</emphasis> the paragraph, which is surrounded by the text.
		<note>This is a small note</note>
	</para>
	<para>
		Finally a table with some xml tags:
		<informaltable>
			<tgroup cols='2'>
				<tbody>
					<row>
						<entry>xml tag</entry>
						<entry>result</entry>
					</row>
					<row>
						<entry><para>command</para></entry>
						<entry><para><command>command</command>
							</para></entry>
					</row>
					<row>
						<entry><para>replaceable</para></entry>
						<entry><para><replaceable>replaceable</replaceable>
							</para></entry>
					</row>
					<row>
						<entry><para>prompt</para></entry>
						<entry><para><prompt>prompt</prompt></para></entry>
					</row>
				</tbody>
			</tgroup>
		</informaltable>
	</para>
</chapter>
This test chapter demonstrates an inserted picture and a small table.

Usage

# gmake
cd . && aclocal19 -I m4
 cd . && automake19 --gnu
cd . && autoconf259
/bin/sh ./config.status --recheck
running /bin/sh ./configure   --no-create --no-recursion

...

checking for xsltproc... /usr/local/bin/xsltproc
checking for doc/docbook/htmlhelp/htmlhelp.xsl... yes
checking whether documentation can be changed and compiled... yes

...

gmake[3]: Entering directory `/usr/home/he/develop/testproj/src'
if g++ -DLOCALEDIR=\"/usr/local/share/locale\" -DHELPDIR=\"/usr/local/share/doc/testproj\"
    -DHAVE_CONFIG_H -I. -I. -I..   -I/usr/local/include  -g -O2 -MT main.o -MD -MP -MF 
    ".deps/main.Tpo" -c -o main.o main.cpp; \
then mv -f ".deps/main.Tpo" ".deps/main.Po"; else rm -f ".deps/main.Tpo"; exit 1; fi

...

Making all in doc
gmake[2]: Entering directory `/usr/home/he/develop/testproj/doc'
gmake[3]: Entering directory `/usr/home/he/develop/testproj/doc'
gmake[3]: Nothing to be done for `all-am'.
gmake[3]: Leaving directory `/usr/home/he/develop/testproj/doc'
gmake doc-clean
gmake[3]: Entering directory `/usr/home/he/develop/testproj/doc'
rm -f *.hhp
rm -f *.html
rm -f *.hhk
rm -f *.hhc
gmake[3]: Leaving directory `/usr/home/he/develop/testproj/doc'
/usr/local/bin/xsltproc testproj.xsl testproj.xml
Writing overview.html for preface(overview)
Writing test_chapter.html for chapter(test_chapter)
Writing index.html for book
Writing testproj.hhp
Writing testproj.hhc
Writing testproj.hhk
creating generated_files.mk
gmake generated_files_include
gmake[3]: Entering directory `/usr/home/he/develop/testproj/doc'
gmake[3]: Leaving directory `/usr/home/he/develop/testproj/doc'
gmake[2]: Leaving directory `/usr/home/he/develop/testproj/doc'
gmake[2]: Entering directory `/usr/home/he/develop/testproj'
gmake[2]: Leaving directory `/usr/home/he/develop/testproj'
gmake[1]: Leaving directory `/usr/home/he/develop/testproj'
As most autotool files have been touched, the autotools are called automatically to recreate the Makefiles and the configure script. Next configure is called again. This time it additionally checks for xsltproc. The result is printed after "checking whether documentation can be changed and compiled". Afterwards make is invoked. During the recompilation of main.cpp, the HELPDIR is passed as compiler option.

Later the doc directory is entered. Here first all generated files are deleted. Next xsltproc is called, which generates the help files. Finally generated_files.mk is created:
# cat doc/generated_files.mk
TESTPROJ_DOCFILES =  \
        ./index.html \
        ./overview.html \
        ./test_chapter.html
It contains all generated html files, as described above. During the installation, these files are copied to /usr/local/share/doc/testproj, as no other target was specified at the "configure" run.

As two new internationalized strings have been added to main.cpp, these have to be translated first:

po/de.po

...

#: src/main.cpp:34
#, c-format
msgid ""
"\n"
"Help: %s/index.html\n"
msgstr ""
"\n"
"Hilfe: %s/index.html\n"

#: src/main.cpp:35
#, c-format
msgid "Test chapter: %s/test_chapter.html\n"
msgstr "Test Kapitel: %s/test_chapter.html\n"

...
Now the translation can be updated and the project installed:
# gmake update-gmo
...
# gmake install

...

Making install in doc
gmake[1]: Entering directory `/usr/home/he/develop/testproj/doc'
gmake[2]: Entering directory `/usr/home/he/develop/testproj/doc'
gmake[3]: Entering directory `/usr/home/he/develop/testproj/doc'
gmake[3]: Nothing to be done for `install-exec-am'.
test -z "/usr/local/share/doc/testproj" || /usr/local/bin/bash ../mkinstalldirs "/usr/local/share/doc/testproj"
mkdir /usr/local/share/doc/testproj
 /usr/bin/install -c -m 644 'apicture.png' '/usr/local/share/doc/testproj/apicture.png'
 /usr/bin/install -c -m 644 './index.html' '/usr/local/share/doc/testproj/index.html'
 /usr/bin/install -c -m 644 './overview.html' '/usr/local/share/doc/testproj/overview.html'
 /usr/bin/install -c -m 644 './test_chapter.html' '/usr/local/share/doc/testproj/test_chapter.html'
gmake[3]: Leaving directory `/usr/home/he/develop/testproj/doc'
gmake[2]: Leaving directory `/usr/home/he/develop/testproj/doc'
gmake[1]: Leaving directory `/usr/home/he/develop/testproj/doc'
gmake[1]: Entering directory `/usr/home/he/develop/testproj'
gmake[2]: Entering directory `/usr/home/he/develop/testproj'
gmake[2]: Nothing to be done for `install-exec-am'.
gmake[2]: Nothing to be done for `install-data-am'.
gmake[2]: Leaving directory `/usr/home/he/develop/testproj'
gmake[1]: Leaving directory `/usr/home/he/develop/testproj'
If started, testproj will print out these documentation directories, as they are passed to it during compilation
# testproj
Hello world!
Press a key

Possible directions: right, left
Possible results: right, wrong
Enter a number: 3
You have won 3 points!

Help: /usr/local/share/doc/testproj/index.html
Test chapter: /usr/local/share/doc/testproj/test_chapter.html
To change the documentation directory, "configure" has to be called again. In this test "make clean" is necessary in order to enforce recompilation. In an end user scenario, this is the first gmake after "configure", so an end user will not have to call "make clean".
# gmake uninstall

...

Making uninstall in doc
gmake[1]: Entering directory `/usr/home/he/develop/testproj/doc'
gmake[2]: Entering directory `/usr/home/he/develop/testproj/doc'
 rm -f '/usr/local/share/doc/testproj/apicture.png'
 rm -f '/usr/local/share/doc/testproj/index.html'
 rm -f '/usr/local/share/doc/testproj/overview.html'
 rm -f '/usr/local/share/doc/testproj/test_chapter.html'
gmake[2]: Leaving directory `/usr/home/he/develop/testproj/doc'
gmake[1]: Leaving directory `/usr/home/he/develop/testproj/doc'
gmake[1]: Entering directory `/usr/home/he/develop/testproj'
gmake[1]: Nothing to be done for `uninstall-am'.
gmake[1]: Leaving directory `/usr/home/he/develop/testproj'
# ./configure --help

...

Optional Packages:

...

  --with-helpdir=dir      Installation directory of the html help
                          [${datadir}/doc/testproj]

Some influential environment variables:

...

# ./configure --with-helpdir=/usr/local/share/doc/a_test

...

# gmake clean

...

# gmake

...

gmake[3]: Entering directory `/usr/home/he/develop/testproj/src'
if g++ -DLOCALEDIR=\"/usr/local/share/locale\" -DHELPDIR=\"/usr/local/share/doc/a_test\"
    -DHAVE_CONFIG_H -I. -I. -I..   -I/usr/local/include  -g -O2 -MT main.o -MD -MP -MF 
    ".deps/main.Tpo" -c -o main.o main.cpp; \
then mv -f ".deps/main.Tpo" ".deps/main.Po"; else rm -f ".deps/main.Tpo"; exit 1; fi

...

# gmake install

...

gmake[3]: Entering directory `/usr/home/he/develop/testproj/doc'
gmake[3]: Nothing to be done for `install-exec-am'.
test -z "/usr/local/share/doc/a_test" || /usr/local/bin/bash ../mkinstalldirs "/usr/local/share/doc/a_test"
mkdir /usr/local/share/doc/a_test
 /usr/bin/install -c -m 644 'apicture.png' '/usr/local/share/doc/a_test/apicture.png'
 /usr/bin/install -c -m 644 './index.html' '/usr/local/share/doc/a_test/index.html'
 /usr/bin/install -c -m 644 './overview.html' '/usr/local/share/doc/a_test/overview.html'
 /usr/bin/install -c -m 644 './test_chapter.html' '/usr/local/share/doc/a_test/test_chapter.html'

...

# testproj

...

Help: /usr/local/share/doc/a_test/index.html
Test chapter: /usr/local/share/doc/a_test/test_chapter.html

The generated help files can be viewed here.
# gmake uninstall clean

...

# ./configure

...

The result source code can be downloaded here.

Next the documentation will be internationalized.
Prev Home Next
Documentation Overview Internationalization of the Documentation