6.3. qweave.cc
Produce LATEX or HTML documentation from a QefDoc file.
TODO: allow multiple files to be easily turned into a single .tex file.
TODO: use popt for the option handling.
TODO: allow the filename which will be typeset to be specified with a command
line arg. The filename for error messages and the 2nd line of TEX
comments should still be the real one, but it might be useful to change the
one that appears in the final output so that the file's position in the
source tree can be shown even if `qweave' is being run on it from the
directory its in.
The following error codes can be returned:
- 0 - everything was fine and dandy.
- 1 - bad command line arguments.
- 2 - I/O error reading or writing file.
- 3 - syntax error in the input file.
- 4 - semantic error in input.
- 5 - parse error in index file.
#include <string>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cassert>
#define QWEAVE_NO_EXTERNS
#include "qweave.hh"
const char *style_sheet_name = "qprettyl.css";
const char *language_names[] =
{
"Plain text",
"C++",
"Perl",
"Lex/Flex",
"Python",
"Sed"
};
Prototypes for the interfaces to the source code pretty printers.
const string &pretty_print_cxx (const string &source);
const string &pretty_print_python (const string &source);
const string &pretty_print_text (const string &source);
Prototypes for functions defined below.
void parse_index (const char *filename);
void parse (QefDocFile &file);
void parse_error (const char *msg, int code = 3);
bool read_line (FILE *ins, string &line);
void inline_command (const string &com, const string &arg, string &s);
bool arg_error (const string &com, bool args_wanted);
void new_chunk (QefDocFile &file, const string &doc, const string &src);
void output_header (FILE *ous, const string &title);
void output_toc ();
bool output_file (QefDocFile &file, int refn, int chnsec, int chnfile);
void output_footer (FILE *ous);
const string &pretty_print (Language lang, const string &src);
FILE *open_input_file (const char *filename);
void close_files ();
void trim_end (string &s);
Pointers to the input and output files, respectively. If filenames are not
specified on the command line then ins will point to `stdin' and
ous to `stdout'.
FILE *ous = 0;
This is true if the output should be in HTML. It is set to `true'
if the option `--html' is given.
bool use_html = false;
Set to `true' if the option `--no-header' is given. When set the
starting and finishing code will not be output, only the code for the
actual chunks. This allows the resulting code to be included into a larger
document.
bool option_no_header = false;
This will be `true' if `--index' is specified, in which case the
input file is used as the index (.qex) file which lists the actual
QefDoc source files to be used.
bool option_index = false;
This option only applies when `--html' is also used. It causes the
documentation for each file to be output to a separate HTML file.
bool option_split = false;
If specified this will suppress the table of contents, which is generated by
default if an index file is used. TODO
bool option_no_contents = false;
Used in the parser to keep track of the current line number and file name of
the input file. This is useful for printing error messages in
`parse_error'.
int line_num = 1;
const char *current_filename = 0;
string main_title;
The list of QefDoc source files to read. If `--index' is given on the
command line then these will be read from the input file, otherwise the
input file itself is the only one.
list <QefDocFile *> files;
typedef list <QefDocFile *>::iterator QefDocFileIt;
int
main (int argc, char **argv)
{
const char *inpfilename = 0;
Decode any command line options.
int argi = 1;
while (argv[argi][0] == '-')
{
if (strcmp (argv[argi], "--html") == 0)
use_html = true;
else if (strcmp (argv[argi], "--no-header") == 0)
option_no_header = true;
else if (strcmp (argv[argi], "--index") == 0)
option_index = true;
else if (strcmp (argv[argi], "--split") == 0)
option_split = true;
else
{
fprintf (stderr, "%s: unrecognized option `%s'.\n",
argv[0], argv[argi]);
exit (1);
}
++argi;
}
Try to open the input file, or use `stdin' if none was specified.
if (argc - argi ≥ 1)
inpfilename = argv[argi++];
Try to open the output file, or use `stdout' if one wasn't specified.
if (argc - argi ≥ 1)
{
ous = fopen (argv[argi++], "w");
if (!ous)
{
fprintf (stderr, "Error opening output file `%s'.\n", argv[argi - 1]);
return 2;
}
}
else
ous = stdout;
if (option_index)
parse_index (inpfilename);
else
files.push_back (new QefDocFile (inpfilename, "", 0, 0));
Parse the QefDoc files and store the documentation and source strings in
the files objects.
for (QefDocFileIt i = files.begin(); i ≠ files.end(); ++i)
parse (**i);
Output the TEX or HTML source, first for the table of contents and
then for each file in turn.
XXX: obviously the title shouldn't be hardcoded in.
output_header (ous, "Final Year Project Code");
if (files.size() > 1 && !option_no_contents)
output_toc();
int n = 1;
for (QefDocFileIt i = files.begin(); i ≠ files.end(); ++i, ++n)
{
if (!output_file (**i, n, (*i)->secnum, (*i)->filenum))
return 2;
}
output_footer (ous);
close_files();
return 0;
}
QefDocFile::QefDocFile (const string &name, const string &sec, int secn,
int file)
: filename (name), section (sec), secnum (secn), filenum (file)
{
if (filename.size() > 4 && filename.substr (filename.size() - 4) == ".qef")
preproc_filename = filename.substr (0, filename.size() - 4);
else
preproc_filename = filename;
language = LANG_Text;
}
A function to parse the simple index files.
void
parse_index (const char *filename)
{
FILE *ins = open_input_file (filename);
string line, secname;
int secn = 0, filen = 1;
while (read_line (ins, line))
{
if (line.empty() || line[0] == '#')
;
else if (line.find ("section ") == 0)
{
secname = line.substr (8);
if (secname.empty())
parse_error ("missing section name", 5);
++secn;
filen = 1;
}
else if (line.find ("qefdoc ") == 0)
{
string inpfilename = line.substr (7);
if (inpfilename.empty())
parse_error ("missing file name", 5);
files.push_back (new QefDocFile (inpfilename, secname, secn, filen));
++filen;
}
else if (line.find ("title ") == 0)
{
string title = line.substr (6);
if (!main_title.empty())
parse_error ("title specified more than once", 4);
if (title.empty())
parse_error ("missing title", 5);
main_title = title;
}
else
parse_error ("bad line", 5);
line = "";
++line_num;
}
if (ins ≠ stdin)
fclose (ins);
}
An enumeration of the states through which the parser below can pass.
`S_HEADER_MAGIC' is the initial state. Each state is explained in the
switch statement in `parse'.
enum States
{
S_HEADER_MAGIC,
S_HEADER_LANG,
S_HEADER_SKIP,
S_BEFORE_CHUNK_DOC,
S_BEFORE_CHUNK_SRC,
S_NEW_CHUNK_AT,
S_DOC_LINE,
S_SOURCE,
S_SOURCE_AT,
S_INLINE_COMMAND,
S_COMMAND_NAME,
S_COMMAND_ARG,
S_SKIP_LINE
};
Parse the file one character at a time and output TEX commands to present
it.
void
parse (QefDocFile &file)
{
Open the input file, or use `stdin' if none was specified.
fprintf (stderr, "Parsing `%s'.\n", file.filename.c_str());
FILE *ins = open_input_file (file.filename.c_str());
string id, arg, doc, src;
States state = S_HEADER_MAGIC;
while (true)
{
Read a single character from the input file and check for errors or
EOF.
int c_int = fgetc (ins);
unsigned char c = c_int;
if (c_int == EOF)
{
if (ferror (ins))
parse_error ("error reading input file", 2);
if (!src.empty() || !doc.empty())
new_chunk (file, doc, src);
if (ins ≠ stdin)
fclose (ins);
return;
}
if (c == '\n')
++line_num;
switch (state)
{
Read the identifier at the start of the file, which should be
`qefdoc'. If its not then the file is of the wrong type.
The magic identifier `qefdoc' is followed by a `:' if there is
information after it which needs to be processed, or by `\n'
otherwise.
case S_HEADER_MAGIC:
if (c == ':' || c == '\n')
{
if (id ≠ "qefdoc")
parse_error ("this does not seem to be a QefDoc file");
id = "";
state = (c == ':' ? S_HEADER_LANG : S_BEFORE_CHUNK_DOC);
}
else
id += c;
break;
Now we read in the name of the language which the source code is in.
It might be prefixed by whitespace, and it might not exist. If it
is `-' then we leave it unaltered.
case S_HEADER_LANG:
if (c == '\n')
{
if (!id.empty())
file.set_language (id);
id = "";
state = S_BEFORE_CHUNK_DOC;
}
else if (isspace (c))
{
if (!id.empty())
{
file.set_language (id);
id = "";
state = S_HEADER_SKIP;
}
}
else
id += c;
break;
A kludge to ignore the stuff after `qefdoc:'. Eventually we will
need to take more notice of it.
case S_HEADER_SKIP:
if (c == '\n')
state = S_BEFORE_CHUNK_DOC;
break;
In this state we expect either a line of source code or a line of
documentation. Either way, it will be the start of a new chunk.
Bits of whitespace are stored in id so that the indentation
will be known if a line of source is found.
case S_BEFORE_CHUNK_DOC:
if (c == '\n')
{
id = "";
if (!doc.empty())
state = S_BEFORE_CHUNK_SRC;
}
else
{
id += c;
if (c == '@')
{
id = "";
state = S_NEW_CHUNK_AT;
}
else if (!isspace (c))
{
src = id;
id = "";
state = S_SOURCE;
}
}
break;
This state is entered after a line of source code.
From here there will either be more source or a new chunk will
begin with `@'.
case S_BEFORE_CHUNK_SRC:
if (c == '@')
{
new_chunk (file, doc, src);
id = doc = src = "";
state = S_NEW_CHUNK_AT;
}
else if (isspace (c))
id += c;
else
{
src += id;
src += c;
id = "";
state = S_SOURCE;
}
break;
This state is entered just after an `@' at the start of a new
chunk.
case S_NEW_CHUNK_AT:
if (c == '\n')
{
if (!doc.empty())
doc += '\n';
state = S_BEFORE_CHUNK_DOC;
}
else if (isspace (c))
{
if (!doc.empty())
{
if (use_html && doc[doc.size() - 1] == '\n')
doc += "<p>";
else
doc += '\n';
}
state = S_DOC_LINE;
}
else
{
id += c;
state = S_SKIP_LINE;
}
break;
Read a line of documentation after a `@' into doc.
case S_DOC_LINE:
if (c == '\n')
state = S_BEFORE_CHUNK_DOC;
else if (c == '@')
state = S_INLINE_COMMAND;
else if (isalnum (c))
doc += c;
else
{
format_text (c, id);
doc += id;
id = "";
}
break;
While in this state a line of source code is being read into
src.
case S_SOURCE:
if (c == '\n')
{
src += '\n';
state = S_BEFORE_CHUNK_SRC;
}
else if (c == '@')
state = S_SOURCE_AT;
else
src += c;
break;
Parse whatever follows `@' in source code.
case S_SOURCE_AT:
if (c == '@')
{
src += '@';
state = S_SOURCE;
}
else
parse_error ("stray `@' found in source");
break;
Decode an `@' command which appears in a line of documentation or
source code. We only stay in this state for one character.
case S_INLINE_COMMAND:
if (c == '@')
{
doc += '@';
state = S_DOC_LINE;
}
else if (c == '<')
{
parse_error ("there must be nothing but whitespace on a line "
"before `@<'");
}
else if (isalpha (c) || c == '_')
{
id = c;
state = S_COMMAND_NAME;
}
break;
Read the name of an inline `@' command.
case S_COMMAND_NAME:
if (isalnum (c) || c == '_')
id += c;
else if (c == '<')
{
arg = "";
state = S_COMMAND_ARG;
}
else
{
ungetc (c, ins);
inline_command (id, "", doc);
id = "";
state = S_DOC_LINE;
}
break;
Read the argument of a command, between the `<' and `>'.
case S_COMMAND_ARG:
if (c == '>')
{
inline_command (id, arg, doc);
id = "";
state = S_DOC_LINE;
}
else
arg += c;
break;
This is a hack.
case S_SKIP_LINE:
if (c == '\n')
{
id = "";
state = S_BEFORE_CHUNK_DOC;
}
break;
This will crash the program with an error message, but it should
never happen.
default:
assert (false);
}
}
}
Print an error message with the filename and line number on which it was
detected.
void
parse_error (const char *msg, int code)
{
fprintf (stderr, "%s:%d: %s.\n",
(current_filename ? current_filename : "<stdin>"), line_num, msg);
exit (code);
}
bool
read_line (FILE *ins, string &line)
{
int c = fgetc (ins);
while (c ≠ '\n' && c ≠ EOF)
{
line += (char) c;
c = fgetc (ins);
}
return !(c == EOF && line.empty());
}
void
inline_command (const string &com, const string &arg, string &s)
{
Typeset the TEX logo.
if (com == "TeX")
{
arg.empty() || arg_error (com, false);
s += use_html ? "T<sub><big>E</big></sub>X" : "\\TeX{}";
}
Typeset the LATEX logo.
else if (com == "LaTeX")
{
arg.empty() || arg_error (com, false);
s += use_html ? "L<sup>A</sup>T<sub><big>E</big></sub>X" :
"\\LaTeX{}";
}
else if (com == "code")
{
arg.empty() && arg_error (com, true);
s += style_start (STYLE_CODE);
s += format_text (arg);
s += style_end (STYLE_CODE);
}
else if (com == "var")
{
arg.empty() && arg_error (com, true);
s += style_start (STYLE_VAR);
s += format_text (arg);
s += style_end (STYLE_VAR);
}
else if (com == "val")
{
arg.empty() && arg_error (com, true);
s += style_start (STYLE_VAL);
s += format_text (arg);
s += style_end (STYLE_VAL);
}
else if (com == "func" || com == "prog" || com == "reffun" || com == "type")
{
arg.empty() && arg_error (com, true);
s += style_start (STYLE_FUNC);
s += format_text (arg);
s += style_end (STYLE_FUNC);
}
else if (com == "ac")
{
arg.empty() && arg_error (com, true);
s += style_start (STYLE_SMALL_CAPS);
if (!use_html)
{
string lcname = arg;
for (unsigned int i = 0; i < lcname.size(); ++i)
lcname[i] = tolower (lcname[i]);
s += format_text (lcname);
}
else
s += format_text (arg);
s += style_end (STYLE_SMALL_CAPS);
}
else if (com == "path" || com == "file")
{
arg.empty() && arg_error (com, true);
s += style_start (STYLE_PATH);
s += format_text (arg);
s += style_end (STYLE_PATH);
}
else if (com == "dots")
{
arg.empty() || arg_error (com, false);
s += use_html ? "…" : "\\dots{}";
}
Typeset a list of bullet points or an item such a the list.
else if (com == "list")
{
arg.empty() || arg_error (com, false);
s += use_html ? "<ul>" : "\\begin{itemize}";
}
else if (com == "listend")
{
arg.empty() || arg_error (com, false);
s += use_html ? "</ul>" : "\\end{itemize}";
}
else if (com == "item")
{
arg.empty() || arg_error (com, false);
s += use_html ? "<li>" : "\\item ";
}
Print an error message if any other command is used.
else
fprintf (stderr, "%s:%d: unknown command `@%s' ignored.\n",
current_filename, line_num, com.c_str());
}
Set the source language to that specified by name. The names are
case-insensitive. If the name is empty or is `-' or `text' then we switch
to source language independent mode.
void
QefDocFile::set_language (const string &name)
{
string lcname = name;
for (unsigned int i = 0; i < name.size(); ++i)
lcname[i] = tolower (lcname[i]);
if (lcname.empty() || lcname == "-" || lcname == "text")
language = LANG_Text;
else if (lcname == "c++" || lcname == "c")
language = LANG_Cxx;
else if (lcname == "lex" || lcname == "flex")
language = LANG_Lex;
else if (lcname == "perl")
language = LANG_Perl;
else if (lcname == "python")
language = LANG_Python;
else if (lcname == "sed")
language = LANG_Sed;
else
{
fprintf (stderr, "%s:%d: the language `%s' is unrecognized.\n",
current_filename, line_num, name.c_str());
exit (4);
}
}
Print an error message to indicate the presence of a parameter to a command
when there should not be one (if args_wanted is `true'), or
the absence of a parameter when one is required (if args_wanted is
`false').
The return value is always `false', to allow `arg_error' to be used
in kludgy ways.
bool
arg_error (const string &com, bool args_wanted)
{
const char *reason;
if (args_wanted)
reason = "ignored because it lacks a parameter";
else
reason = "should not have a parameter";
fprintf (stderr, "%s:%d: command `@%s' %s.\n",
current_filename, line_num, com.c_str(), reason);
return false;
}
Add a chunk of documentation and source to the list.
void
new_chunk (QefDocFile &file, const string &doc, const string &src)
{
string new_doc = doc, new_src = src;
trim_end (new_doc);
trim_end (new_src);
file.chunks.push_back (new Chunk (new_doc, new_src));
}
QefDocFile *
find_file_from_index (const string &filename)
{
for (QefDocFileIt i = files.begin(); i ≠ files.end(); ++i)
if ((*i)->preproc_filename == filename)
return *i;
return 0;
}
TODO: don't do DOCTYPE if option_no_header.
void
output_header (FILE *ous, const string &title)
{
Output some helpful comments so that people reading the output file know
where it came from.
if (use_html)
fputs ("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\""
" \"http://www.w3.org/TR/html4/strict.dtd\">\n"
"<!-- This is HTML output from qweave, version " VERSION
". -->\n\n", ous);
else
fputs ("% This is LaTeX output from qweave, version " VERSION ".\n\n",
ous);
if (!option_no_header)
{
if (use_html)
fprintf (ous, "<html>\n<head>\n <title>%s</title>\n"
" <link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">\n"
"</head>\n<body>\n", title.c_str(), style_sheet_name);
else
fputs ("\\documentclass[oneside]{report}\n"
"\\usepackage{path}\n"
"\\begin{document}\n", ous);
}
if (!use_html)
fputs ("\\font\\ttbf=cmttb10\\def\\textttbf#1{{\\ttbf{}#1}}\n"
"\\font\\slbf=cmbxsl10\\def\\textslbf#1{{\\slbf{}#1}}\n"
"\\def\\tilde{\\lower 0.3em\\hbox{\\~{}}}\n"
"\\font\\imrten=qefdocimr10\n"
"\\font\\imrtenit=qefdocimti10\n"
"\\def\\thorn{{\\imrten\\char\"1C}}\n"
"\\def\\Thorn{{\\imrten\\char\"1E}}\n"
"\\def\\eth{{\\imrten\\char\"1F}}\n"
"\\def\\Eth{{\\imrten\\char\"20}}\n"
"\\def\\ithorn{{\\imrtenit\\char\"1C}}\n"
"\\def\\iThorn{{\\imrtenit\\char\"1E}}\n"
"\\def\\ieth{{\\imrtenit\\char\"1F}}\n"
"\\def\\iEth{{\\imrtenit\\char\"20}}\n"
"\\def\\line#1{\\hbox to\\hsize{#1}}\n\n", ous);
}
void
output_toc ()
{
if (use_html)
{
fputs ("<h1>Table of Contents</h1>\n<ul>\n", ous);
int n = 1;
string secname;
bool in_ul = false;
for (QefDocFileIt i = files.begin(); i ≠ files.end(); ++i, ++n)
{
if ((*i)->secnum && (*i)->section ≠ secname)
{
secname = (*i)->section;
if (in_ul)
fputs (" </ul>\n", ous);
fprintf (ous, " <li>%d. %s\n <ul>\n", (*i)->secnum,
secname.c_str());
in_ul = true;
}
char chunknum[256];
chunknum[0] = '\0';
if ((*i)->secnum)
sprintf (chunknum, "%d.", (*i)->secnum);
sprintf (chunknum + strlen (chunknum), "%d.", (*i)->filenum);
if (option_split)
fprintf (ous, " <li>%s <a href=\"%s.html\">%s</a>\n", chunknum,
(*i)->preproc_filename.c_str(),
(*i)->preproc_filename.c_str());
else
fprintf (ous, " <li>%s <a href=\"#F%d\">%s</a>\n", chunknum, n,
(*i)->preproc_filename.c_str());
}
if (in_ul)
fputs (" </ul>\n", ous);
fputs ("</ul>\n", ous);
}
else
fputs ("\\tableofcontents\\clearpage\n", ous);
}
This function prints all of the actual TEX or HTML code for a single
input file. By the time its called the input file will have been
completely read and digested. chnsec is the section number this file
is part of, or `0' if it isn't in one.
bool
output_file (QefDocFile &file, int refn, int chnsec, int chnfile)
{
Choose the file to output to.
FILE *ous = ::ous;
if (use_html && option_split)
{
string htmloutfile = file.preproc_filename + ".html";
ous = fopen (htmloutfile.c_str(), "w");
if (!ous)
{
fprintf (stderr, "qweave: couldn't open output file `%s'.\n",
htmloutfile.c_str());
return false;
}
output_header (ous, file.preproc_filename);
}
char chunknum[256];
chunknum[0] = '\0';
if (chnsec)
sprintf (chunknum, "%d.", chnsec);
if (chnfile)
sprintf (chunknum + strlen (chunknum), "%d.", chnfile);
If this is a new section then output its title.
static int oldsec = -1;
if (chnsec ≠ oldsec && !option_no_header)
{
if (use_html)
fprintf (ous, "<h1>%d. %s</h1>\n", chnsec, file.section.c_str());
else
fprintf (ous, "\\chapter{%s}\n", file.section.c_str());
}
oldsec = chnsec;
The title of the file and the anchor/label for it to link to.
string title = (file.title.empty() ? file.preproc_filename : file.title);
if (use_html)
fprintf (ous, "\n<!-- Start of file `%s'. -->\n"
"<h3><a name=\"F%d\"></a>%s %s</h3>\n",
file.filename.c_str(), refn, chunknum, title.c_str());
else
fprintf (ous, "\n%% Start of file `%s'.\n"
"\\section{%s}\n",
file.filename.c_str(), format_text (title, false).c_str());
Output the chunks of documentation and/or code, one by one. They are
numbered by section (chnsec, if specified), file (chnfile)
and chunk within file (chn) starting from `1.1' or
`1.1.1'. Before each chunk a comment is printed into the output
file for debugging purposes.
int chn = 1;
list <const Chunk *>::const_iterator i;
for (i = file.chunks.begin(); i ≠ file.chunks.end(); ++i, ++chn)
{
if (use_html)
fprintf (ous, "\n<!-- Chunk %s%d. -->\n"
"<table width=\"100%%\" border=0><tr><td width=\"70%%\">\n"
" <td width=\"30%%\">\n"
" <table width=\"100%%\" border=0><tr><td><hr noshade>\n"
" <td width=\"10%%\">%s%d</table>\n"
" </table>\n", chunknum, chn, chunknum, chn);
else
fprintf (ous, "\n%% Chunk %s%d.\n"
"\\vskip 5pt\\line{\\hfill\\hbox to 0.23\\hsize{"
"\\hrulefill~%s%d}}\\vskip 5pt\n",
chunknum, chn, chunknum, chn);
Output the documentation, if there was any in this chunk.
if (!(*i)->doc.empty())
{
fputs (use_html ? "<p>" : "\\par{}", ous);
fputs ((*i)->doc.c_str(), ous);
if (!(*i)->src.empty())
fputs (use_html ? "" : "\\vskip 5pt\n", ous);
}
Output the source code, if there was any in this chunk. Before being
printed it is run through the appropriate pretty printer.
if (!(*i)->src.empty())
{
const string &s = pretty_print (file.language, (*i)->src);
if (use_html)
fputs ("<pre>", ous);
else
fputs ("\\begin{flushleft}{\\tt\\obeylines\\hsize=23cm%\n", ous);
fputs (s.c_str(), ous);
if (use_html)
fputs ("</pre>", ous);
else
fputs ("\n}\\end{flushleft}", ous);
}
fputs ("\n", ous);
}
if (ous ≠ ::ous)
{
output_footer (ous);
fclose (ous);
}
return true;
}
Finish off the output file.
void
output_footer (FILE *ous)
{
if (!option_no_header)
fputs (use_html ? "\n\n</body>\n</html>\n"
: "\n\n\\end{document}\n", ous);
fputs (use_html ? "<!--" : "%", ous);
fputs (" Here ends the output of qweave.", ous);
fputs (use_html ? " -->\n" : "\n", ous);
}
Use whichever pretty printer is suitable for the source code language
lang to process the code in src.
const string &
pretty_print (Language lang, const string &src)
{
if (lang == LANG_Cxx)
return pretty_print_cxx (src);
else if (lang == LANG_Python)
return pretty_print_python (src);
else if (lang == LANG_Text)
return pretty_print_text (src);
else
{
return pretty_print_text (src);
}
}
TextStyleTypes provides indices into the array style_code.
style_code is a table of scraps of formating code in TEX and
HTML, which are used by the function below.
TODO: use a style sheet, maybe, for the HTML.
const char *style_code[][4] =
{
{ "<span class=\"keyword\">", "\\textttbf{", "</span>", "}" },
{ "<span class=\"constant\">", "\\textit{", "</span>", "}" },
{ "<span class=\"comment\">", "\\textsl{", "</span>", "}" },
{ "<code>", "\\texttt{", "</code>", "}" },
{ "`<span class=\"val\">", "`\\textit{", "</span>'", "\\/}'" },
{ "<var>", "\\textit{", "</var>", "\\/}" },
{ "`<span class=\"func\">", "`\\textbf{", "</span>'", "}'" },
{ "<span class=\"preproc\">", "", "</span>", "" },
{ "<span class=\"incfile\">", "", "</span>", "" },
{ "<span class=\"typeword\">", "\\textttbf{", "</span>", "}" },
{ "<span class=\"string\">", "", "</span>", "" },
{ "<span class=\"path\">", "\\path|", "</span>", "|" },
{ "<span class=\"commenthl\">", "\\textslbf{", "</span>", "}" },
{ "<span class=\"stringesc\">", "\\textbf{", "</span>", "}" },
{ "<acronym>", "\\textsc{", "</acronym>", "}" }
};
These functions return strings containing the appropriate TEX or HTML
code to start or end different types of text style. It looks the relevent
strings up in the table above.
const char *
style_start (TextStyleTypes style)
{
return style_code[style - 1][use_html ? 0 : 1];
}
const char *
style_end (TextStyleTypes style)
{
return style_code[style - 1][use_html ? 2 : 3];
}
Wrap text style code round a string and return it.
string
style_text (TextStyleTypes style, const string &text)
{
string s = style_start (style);
s += text;
s += style_end (style);
return s;
}
Set enc to the TEX or HTML code necessary to typeset the
character c.
void
format_text (char c, string &enc, bool source_mode)
{
if (use_html)
{
switch (c)
{
case '<': enc = "<"; break;
case '>': enc = ">"; break;
case '&': enc = "&"; break;
default: enc = c;
}
}
else
{
switch (c)
{
case ' ': enc = source_mode ? "\\ " : " "; break;
case '$': enc = "\\$"; break;
case '#': enc = "\\#"; break;
case '%': enc = "\\%"; break;
case '&': enc = "\\&"; break;
case '^': enc = "\\char\"5E{}"; break;
case '_': enc = "\\_"; break;
case '{': enc = "$\\lbrace$"; break;
case '}': enc = "$\\rbrace$"; break;
case '~': enc = "\\tilde{}"; break;
case '<': enc = "$<$"; break;
case '>': enc = "$>$"; break;
case '\\': enc = "$\\backslash$"; break;
These are some kludges for Icelandic characters. XXX: use the
alternate ones for italic, or something.
case 'ö': enc = "\\\"{o}"; break;
case 'ø': enc = "\\o{}"; break;
case 'ð': enc = "\\eth{}"; break;
case 'Ð': enc = "\\Eth{}"; break;
case 'þ': enc = "\\thorn{}"; break;
case 'Þ': enc = "\\Thorn{}"; break;
default: enc = c;
}
}
}
Convert a whole string to TEX or HTML code and return it.
string
format_text (const string &s, bool source_mode)
{
string ch, tex;
for (unsigned int i = 0; i < s.size(); ++i)
{
format_text (s[i], ch, source_mode);
tex += ch;
}
return tex;
}
FILE *
open_input_file (const char *filename)
{
FILE *ins = stdin;
if (filename)
{
ins = fopen (filename, "r");
if (ins == NULL)
{
fprintf (stderr, "Error opening input file `%s'.\n", filename);
exit (2);
}
}
::current_filename = filename;
line_num = 1;
return ins;
}
Close the input and output files if they are not stdin and stdout.
void
close_files ()
{
if (ous && ous ≠ stdout)
fclose (ous);
}
Strip off trailing whitespace from s.
void
trim_end (string &s)
{
while (s.size() > 0 && isspace (s[s.size() - 1]))
s.resize (s.size() - 1);
}
int
find_word (const Word *words, const char *w)
{
const Word *i = words;
for (; i->word; ++i)
{
if (strcmp (w, i->word) == 0)
return i->type;
}
return 0;
}