program imgalt(orihtml, imgaltp,
               imagereport, imagenames, althtml, altstrings,
               stop, output);
(* imgalt: image alt detection

  Thomas D. Schneider, Ph.D.
  toms@alum.mit.edu
  https://alum.mit.edu/www/toms 

 *)

label 1; (* end of program *)

const
(* begin module version *)
   version = 2.40; (* of imgalt.p 2017 Apr 06
2017 Apr 06, 2.40: update link to rules-of-alt
2011 Aug 15, 2.39: put default images into imagenames too! (no change)
2011 Aug 14, 2.37: detect zero length file names soon and report location
2011 Aug 14, 2.36: revert to 2.30 - remove noquotesrc mechanism
2011 Aug 14, 2.35: trigger for noquotesrc not right -duplicate alts!
2011 Aug 14, 2.34: can't tell when src ends if no quotes in <img
2011 Aug 14, 2.33: no blanks in src strings
2011 Aug 12, 2.32: zero length file crash - can it be handled?  YES!
2011 Aug 11, 2.31: zero length file crash - ok - a SRC without quotes!
2011 Aug 11, 2.30: don't cr after <img ...>
2011 Aug 11, 2.29: bus error on empty imgaltp
2011 Aug 10, 2.28: clean up
2011 Aug 10, 2.27: crash: src without quoted => zero length file name
2011 Jul 28, 2.26: list associated scripts
2011 Jul 27, 2.25: testfortrigger cannot handle multiple dashes. - solved
2011 Jul 27, 2.24: why mmoptimal-icon.jpg not tagged? <!-----> is a
                   run-on comment!
                   http://htmlhelp.com/reference/wilbur/misc/comment.html
                   says <!---->  is legal.  That also is run-on.
2011 Jul 27, 2.23: clean up
2011 Jul 27, 2.22: allow blank lines in imgaltp list of pairs
2011 Jul 14, 2.21: closing image '>' on same line
2011 Jul 08, 2.20: fix documentation
2011 Jun 30, 2.19: brush up
2011 Jun 30, 2.18: Two spaces after periods in alt tag sentences.
2011 Jun 30, 2.17: eliminate holdimagenames
2011 Jun 30, 2.16: why is imagenames not written correctly?
2011 Jun 15, 2.15: document rembla
2011 Jun 15, 2.14: parameter retported to stop
2011 Jun 15, 2.13: empty alt strings in imgaltp must be transferred!
2011 Jun 14, 2.12: empty alt strings must be left alone
2011 Jun 14, 2.11: clean up
2011 Jun 14, 2.10: testing.  althtml file put back to orihtml gets spaces
                   at end of line => remove blanks from end of buffero.
2011 Jun 14, 2.09: duplicate problem solved
2011 Jun 14, 2.08: duplicate problem - addaltstrings called multiple times
2011 Jun 14, 2.07: testing: duplicate problem again
2011 Jun 14, 2.06: add space when joining lines if needed outside quote!
2011 Jun 14, 2.05: add space in wrap when joining lines if needed inside quote
2011 Jun 14, 2.04: testing
2011 Jun 14, 2.03: DUPLICATE ddd - solved
2011 Jun 14, 2.02: DUPLICATE ddd - why?
2011 Jun 14, 2.01: clean up; one copy of file is duplicate - why?
2011 Jun 14, 2.00: wrap debugging - solved, clean up
2011 Jun 14, 1.99: wrap debugging - solve final word break bug?
2011 Jun 13, 1.98: wrap debugging - final word break bug
2011 Jun 13, 1.97: wrap debugging - fixed!
2011 Jun 13, 1.96: wrap: i ALWAYS steps, to avoid infinite loops
2011 Jun 13, 1.95: orihtml7 test
2011 Jun 12, 1.94: crash on home.html! why?
2011 Jun 09, 1.93: clean up
2011 Jun 09, 1.93: wrap alt lines (running nicely!)
2011 Jun 09, 1.92: wrap alt lines (nearly there)
2011 Jun 09, 1.91: wrap alt lines
2011 Jun 08, 1.90: clean up
2011 Jun 07, 1.89: wrap alt lines is next
2011 Jun 07, 1.88: suppress multiple spaces while inside img
2011 Jun 07, 1.87: don't write blank lines while inside img
2011 Jun 07, 1.86: test home.html - crash fixed
2011 Jun 07, 1.85: why turned off after img? - got it
2011 Jun 07, 1.84: why img not written out? - solved - clean img/src/alt
2011 Jun 06, 1.83: testing
2011 Jun 06, 1.82: clean up
2011 Jun 06, 1.81: process to put alt and src at end of img. Success!
2011 Jun 06, 1.80: Implement output buffer so that alt and src can be
                   captured and output only at end of <img >
                   Successful: alt and src suppressed inside img.
2011 Jun 03, 1.79: src before alt
2011 Jun 02, 1.78: stophold file message mechanism
2011 Jun 02, 1.77: require src to be before alt!
2011 Jun 01, 1.76: clean and extend rebuilt method
                   We need the file name to be first to see what to do
                   with the alt tag!
2011 Jun 01, 1.75: rebuild method:  Detect alt or src,
                   but process only at end of img - now functional
2011 Jun 01, 1.74: fix parsing of anybrowser.gif on home.html (cont)
2011 Jun 01, 1.73: fix parsing of anybrowser.gif on home.html (cont)
2011 Jun 01, 1.72: fix parsing of anybrowser.gif on home.html
2011 Jun 01, 1.71: give line numbers when parsing
2011 Jun 01, 1.70: showparse
2011 Jun 01, 1.69: report quote/comment status
2011 Jun 01, 1.68: Debug imgaltp reading problem (cont) - skip comments
2011 May 27, 1.67: Debug imgaltp reading problem
2011 May 27, 1.66: write a stop file if there is a halt.
2011 May 27, 1.65: Debug home.html crash:
                    needed to emptystring(currentquote); at each new line
2011 May 16, 1.64: clean up
2011 May 16, 1.63: bus error for missing pairs in imgaltp - start new auto list
2011 May 16, 1.62: more clean up
2011 May 16, 1.61: clean up
2011 May 16, 1.60: alt tags in orihtml must be added to altstrings too
2011 May 16, 1.59: find why duplicate file names are not removed from orihtml.
2011 May 16, 1.58: why duplicate file names are removed from imgaltp.
2011 May 15, 1.57: figure out why duplicate file names not removed.
2011 May 15, 1.56: allow blanks in imgaltp image list
2011 May 03, 1.55: clean
2011 May 03, 1.54: fix end of list
2011 May 03, 1.53: Debug duplicate removal
2011 May 03, 1.52: Debug list
2011 May 03, 1.51: cleanup; bad string fixed
2011 May 03, 1.50: cleanup - deleting duplicates works
2011 May 03, 1.49: proceed ...
2011 May 03, 1.48: emptystring in prgmod clearstring and onetoken!
2011 May 03, 1.47: detect duplicate files
(dropped versions)
2011 Apr 29, 1.43: report number of alts read in from imgaltp
2011 Apr 27, 1.42: fix documentation
2011 Apr 27, 1.41: fix documentation
2011 Apr 27, 1.40: altstrings file introduced
2011 Apr 26, 1.39: clean/document
2011 Apr 26, 1.38: clean/document
2011 Apr 26, 1.37: handle case of no file names given
2011 Apr 26, 1.36: user sets default string
2011 Apr 22, 1.35: check function
2011 Apr 07, 1.34: finish work on correct counts
2011 Apr 07, 1.33: more work on correct counts
2011 Apr 07, 1.32: work on correct counts
2011 Apr 07, 1.31: cleanup
2011 Apr 07, 1.30: alt = "" is not being substituted when requested
2011 Apr 05, 1.29: use 4 cases for counting
2011 Apr 05, 1.28: use 4 cases for counting
2011 Apr 05, 1.27: document 4 cases
2011 Apr 05, 1.26: functional!
2011 Apr 05, 1.25: replace alt with requested one only if no instruction!
2011 Apr 04, 1.24: replace alt with requested one
2011 Apr 04, 1.23: replace alt with requested one - almost there!
2011 Apr 04, 1.22: <IMG SRC=" missed - allow upper case
2011 Mar 30, 1.21: document and clean
2011 Mar 30, 1.20: read in file alt pairs; automatic upgrade!
2011 Mar 29, 1.19: clean up
2011 Mar 29, 1.18: get exact locations identified correctly
2011 Mar 29, 1.17: straighten out names
2011 Mar 29, 1.16: straighten out names
2011 Mar 29, 1.15: review/inspect/clean up
2011 Mar 24, 1.14: report line numbers in althtml too
2011 Mar 24, 1.13: avoid extra cr before alt text in althtml
2011 Mar 24, 1.12: clean up
2011 Mar 24, 1.11: althtml has alt inserted!
2011 Mar 24, 1.10: althtml duplicates html (check!)
2011 Mar 24, 1.09: create list of image names 
2011 Mar 24, 1.08: report only missing alts!
2011 Mar 24, 1.07: identifies missing alts!
2011 Mar 24, 1.06: stp and quotes handled
2011 Mar 24, 1.05: stp identified
2011 Mar 21, 1.04: not identifying stp correctly
2011 Mar 21, 1.03: revamp for using triggers
2011 Mar 21, 1.02: proper tracking all '<img'
2011 Mar 21, 1.01: identifies '<img'!
2011 Mar 21, 1.00: origin (reads html)
*)
updateversion = 1.70; (* defines lowest acceptable current parameter file *)
(* end module version *)

(* begin module describe.imgalt *)
(*
name

   imgalt: html image alt detection and upgrading for 508 requirements

synopsis
   imgalt(orihtml: in, imgaltp: in,
          imagereport: out, imagenames: out,
          althtml: out, altstrings: out,
          stop: out,
          output: out)

files

   orihtml:  the original html file to be analyzed.

   imgaltp:  parameters to control the program.  The file must contain the
      following parameters, one per line:

      parameterversion: The version number of the program.  This allows the
         user to be warned if an old parameter file is used.

      Second line: showconversion (charcter),
                   showparse (charcter):

         If the first character is a 'c', then each conversion name
         and alt tag is shown as it is read in.  This could be a long
         list.  They look like: 'new.gif -> "new"'.

         If the second character is an 's', then the orihtml file is
         copied to the output with parsing information displayed. 
         Each line number being examined is given and then a set of
         characters show the parsing information:

                     not        '-'
                     in comment 'C'
                     in quote   'Q'
                     in image   'I'

                     found src  's'
                     found src= '='

                     found alt  'a'
                     found alt= '='

      Third line: temporarytag (string): This string is the default
      'temporary tag' alt string to provide if the user does not
      specify one and there is no alt tag.  Suggestion:
         ALTERNATE_TEXT

      The following lines of the file contain pairs consisting of a
      file name (e.g. colorbar.gif) and an alt string in double quotes
      to use for that file name (e.g. "a color bar").

   imagereport:  report of the results

   imagenames:  names of images.

      The first line begins with '*' and identifies the program.

      The second line begins with '*' and identified the columns.  The
      rest of the file has these columns:

      1. type: If the line begins with

         'alt ' then the image already has an alt and the alt is not
         listed in imgaltp.

         '--- ' then the image does not have an alt and none is listed
         in imgaltp.

         '+++ ' then the image alt will be supplied from imgaltp in
         creating althtml from orihtml.

      2. url: The image url is given as reported in the orihtml.  Note
      that this may not be a full url.

      3. Ourl: The line in the orihtml file where the image url is
      given.

      4. Oalt: The line in the orihtml file where the alternative text
      string is.

      5. Aurl: The line in the althtml file where the image url is
      given.  This is different from the lines in the orihtml file
      because extra lines are added to althtml to put each alternative
      text string on a fresh line.

      6. Aalt: The line in the althtml file where the alternative text
      string is.

   althtml: a copy of orihtml with alt strings inserted as needed.

      There are four cases to handle depending on whether or not there
      already is an alt tag and whether or not what to do is specified
      in the imgaltp.

      --- no alt tag, alternative not supplied: fill in with temporary tag

      +++ no alt tag, alternative     supplied: use tag supplied in imgaltp

      alt    alt tag, alternative not supplied: leave alone

      +++    alt tag, alternative     supplied: use tag supplied in imgaltp

      For the first case, when a temporary tag is generated, it will
      contain the string specified by the user in imgaltp, for example
      "ALTERNATE_TEXT".

      Note that an empty alt tag is replaced with data from imgaltp
      only if there is a file name match.  This preserves empty alt
      tags.

      In this version of imgalt, the <img ... > is rewritten in
      althtml so that src and alt are at the end of the <img ... >. 
      Both the src and alt are on their own lines.  Also, the alt
      string is wrapped and the current indentation is retained.

      Any spaces on the ends of the orihtml are removed from lines of
      althtml.  This allows comparison of the results.  If the imgaltp
      is not changed, then moving althtml back to orihtml and running
      imgalt should, in theory, make orihtml and althtml identical.

   altstrings: image file names and alt strings in quotes, one per line
       These are taken from both imgaltp and orihtml and duplicates
       are removed.  Priority is given to the first file name/alt
       string found in the list so that what a user defines overrides
       values in the orihtml.

   stop: The stop file is written only if there is a program error. 
       This allows other programs to know that imgalt has crashed and
       handle the situation gracefully.

   output: messages to the user

description

   The 508 requirements require that every image (img) in an html page
   have an alternative (alt) description tag.  This program, imgalt,
   reads an html page (orihtml), detects the images, identifies the
   source of the image and whether there is an alt tag.  It rewrites
   the page so that the image has the source followed by alt tags that
   the user specifies in a master list (provided in imgaltp).  The
   revised master list is created (altstrings).  If there is a
   problem, the stop file is created which can be used to halt
   recursive reading through directories (see the tree script in my
   toolbox).  Other files give information about the image tags.

   In an html web page, orihtml, identify images that do not have
   "alt" (alternative) descriptions.  Create a revised html, called
   althtml, that inserts alts with tags that can be fixed.  The
   initial list of alt tags to use is given in the parameter file
   imgaltp.

   The program outputs a list of the images, called imagenames, marked
   with whether or not they have an alt description.  This list gives
   the line numbers for the url and alt tag in the both the orihtml
   and althtml files.

   NOTE: only the actual file name, following all slashes ('/') in the
   URL is used to identify the image name in orihtml, but the whole
   URL is passed to althtml.

   The program also generates a list all the file names with their alt
   tags into the altstrings file.  The list starts with pairs given by
   imgaltp and is followed by ones found in the file.  This list can
   be appended to the end of the imgaltp parameter file.  Duplicate
   file names are removed from the altstrings list.  Duplicates that
   are further down the list are removed, so priority is given to the
   top of the list.  This allows a user to define a new alt tag for a
   given file, and this will override all cases subsequently found.

   The program does a single pass through the html file.  To replace
   an alt tag, the file has to be identified before the alt tag is
   given.  One approach would be to requre that the src string tag be
   before the alt tag.  However, the program is smarter than that. 
   When it encounters an img that is not inside a comment, it copies
   the contents but skips and remembers the alt and src.  Then when
   the end of the img is found, it prints them on their own lines with
   the same indentation as the original code.  The src is given before
   the alt.

examples

Example imgaltp file:

1.36  version of imgalt that this parameter file is designed for.
sn    's' means show the conversion lines as read in
ALTERNATE_TEXT
colorbar.gif "a color bar"
donor.gif "sequence logo for human donor splice junctions"

documentation

see also

   {google search for 'html alt'}
   http://www.google.com/search?hl=en&source=hp&q=html+alt

   {A useful tutorial, "The Rules of ALT":}
   http://html.com/images/rules-of-alt/

   {A useful tutorial, "Guidelines on alt texts in img elements":}
   http://www.cs.tut.fi/~jkorpela/html/alt.html

   {Program that removes blanks from ends of lines: } rembla.p

   {**** Related Scripts ****}
   mh {sets up for correcting alts in a single html file}
   alt {called by my or directly to correct alts}
   mkalttags {uses tree to analyze all htmls in a directory}
   mkalttagsfunction {- called by mkalttags through the tree script}
   tree {general recursive directory processing}
   masteralt {provides the alt string for a given image}

author

   Thomas Dana Schneider

bugs

technical notes

   The program imgalt reads through the original html (orihtml) and
   copies to the altered html (althtml).  When it is inside an image
   '<img' it copies until it sees a source file 'src=' and it then
   captures that.  Likewise it captures an alt string 'alt='.  Case of
   the src and alt and spacing to the equals do not matter.  These are
   captured but not sent to the output.  Instead, they are held until
   the end of the img is found at '>'.  At this point the src is
   output followed by the alt.  So if the src is after the alt, the
   order will be reversed.

*)
(* end module describe.imgalt *)

   pagewidth = 60; (* page width for display and line wrapping *)

(* begin module string.const *)
      maxstring = 2000; (* the maximum string *)
(* end module string.const version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module filler.const *)
      fillermax = 50; (* the size of the filler array for a string *)
(* end module filler.const version = 5.48; (@ of prgmod.p 2011 May 03 *)

type

(* begin module string.type *)
   stringptr = ^string; (* pointer to a string *)
   string = record (* a string of characters *)
      letters: array[1..maxstring] of char; (* the letters in the string *)
      length: integer; (* the number of characters in the string *)
      current: integer; (* the letter we are working on *)
      previous: stringptr; (* the previous string in a series *)
      next: stringptr; (* the next string in a series *)
   end;
(* end module string.type version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module filler.type *)
      (* the following is an array used to fill a string.
      it is convenient to have it much shorter than the maxstring, so that
      it is easy to fill the string using procedure fillstring.
      the user must declare the value of constant fillermax. *)
      filler = packed array[1..fillermax] of char;
(* end module filler.type version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module trigger.type *)
      trigger = record (* an object to be searched for *)
         seek: string; (* the characters looked for *)
         state: integer; (* how close to triggering we are *)
         skip: boolean; (* trigger not found- skip the line *)
         found: boolean (* the trigger was found *)
      end;
(* end module trigger.type version = 5.48; (@ of prgmod.p 2011 May 03 *)

var
   orihtml, (* file used by this program *)
   imgaltp, (* file used by this program *)
   imagereport, (* file used by this program *)
   imagenames, (* file used by this program *)
   altstrings, (* file used by this program *)
   althtml, (* file used by this program *)
   stop: text; (* file used by this program *)
   stophold: text; (* Internal file for holding messages for halting.
                      The messages are transferred to the stop file. *)

(* ************************************************************************ *)

procedure copyfileversion(var fin, fout: text);
(* copy file fin to fout *)
begin
   reset(fin);
   rewrite(fout);
   writeln(fout, '* imgalt ', version:4:2);
   while not eof(fin) do begin
      while not eoln(fin) do begin
         fout^ := fin^;
         put(fout);
         get(fin)
      end;
      readln(fin);
      writeln(fout);
   end;
   writeln(fout,'program halt.');
end;

(* LOCKED begin module halt *)
procedure halt;
(* stop the program.  the procedure performs a goto to the end of the
   program.  you must have a label:
      label 1;
   declared, and also the end of the program must have this label:
      1: end.
   examples are in the module libraries.
   this is the only goto in the delila system. *)
{
google:
pascal internal file
http://www.gnu-pascal.de/gpc/Reset.html
}
begin
      copyfileversion(stophold, stop);
      copyfileversion(stophold, output);
      goto 1
end;
(* LOCKED end module halt version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module package.trigger *)
(* ************************************************************************ *)

(* begin module clearstring *)
(* These modules clear strings in various ways *)
(* ---- *)
procedure emptystring(var ribbon: string);
(* empty the contents of the string but do NOT
remove the pointer.  This is useful for clearing
one string within a linked list of them. *)
var   index: integer; (* to the ribbon *)
begin (* clearstring *)
   with ribbon do begin
      for index := 1 to maxstring do letters[index] := ' ';
      length := 0;
      current := 0;
   end
end; (* emptystring *)
(* ---- *)
procedure clearstring(var ribbon: string);
(* empty the string and remove the pointer.
As of 2010 Dec 04 there is a previous pointer that
allows proper removal of the linked list using disposestring.
This clearstring routine is for the initial clearing
of the variables. *)
begin (* clearstring *)
   with ribbon do begin
      emptystring(ribbon);
      previous := nil;
      next := nil;
   end
end; (* clearstring *)
(* ---- *)
procedure disposestring(var ribbon: stringptr);
(* remove the linked list from the string in revere order *)
var
   t: stringptr; (* a link in the string *)
begin
   t := ribbon;
   (* find the end *)
   while t^.next <> nil do t := t^.next;
   (* move backwards and dispose links *)
   while t^.previous <> nil do begin
      t := t^.previous;
      dispose(t^.next);
   end;
   dispose(t);
end; (* disposestring *)
(* ---- *)
procedure initializestring(var ribbon: string);
(* start the string with a nil pointer.   This routine
should be called before doing linked list work.  This allows
the standard string routines to clear the string without
killing the pointer.  This is now deprecated, do not use it
since clearstring still clears the next pointer. *)
begin (* initializestring *)
   writeln(output,'remove initializestring routine!');
   writeln(output,'replace it with clearstring routine!');
   halt; (* to force deprecation *)
   clearstring(ribbon);
   ribbon.next := nil;
end; (* initializestring *)
(* end module clearstring version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module writestring *)
procedure writestring(var tofile: text; var s: string);
(* write the string s to file tofile, no writeln *)
var   i: integer; (* index to s *)
begin (* writestring *)
      with s do for i := 1 to length do write(tofile, letters[i])
end; (* writestring *)
(* end module writestring version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module filler.fillstring *)
procedure fillstring(var s: string; a: filler);
(* this procedure makes it reasonably easy to fill the string s with
characters.  one calls the procedure as: *)
(*                           1         2         3         4         5 *)
(*                  12345678901234567890123456789012345678901234567890 *)
(*   fillstring(s, 'this-is-the-string                                ');
the two comments make it easy to line the characters up. also, for this
example, it was assumed that the length of filler as defined by the
constant fillermax was 50. *)
var
      length: integer; (* of the string without trailing blanks *)
      index: integer; (* of s *)
begin (* fillstring *)
      clearstring(s);
      length := fillermax;
      while (length > 1) and (a[length] = ' ') do length := pred(length);
      if (length = 1) and (a[length] = ' ') then begin
         writeln(output, 'fillstring: the string is empty');
         halt
      end;

      for index := 1 to length do s.letters[index] := a[index];
      s.length := length;
      s.current := 1
end; (* fillstring *)
(* end module filler.fillstring version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module filler.filltrigger *)
procedure filltrigger(var t: trigger;
                      a: filler);
(* fill the trigger t *)
begin (* filltrigger *)
         fillstring(t.seek,a)
end; (* fillstring *)
(* end module filler.filltrigger version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module trigger.proc *)
(* this module allows one to scan a series of characters, as from
an array or a file, and to "trigger" or detect a simple string
in the series.  the advantage of the trigger is that several triggers
can "observe" a stream of characters at once, each looking for a
different thing.
some other modules required: interact.const, interact.type *)

procedure resettrigger(var t: trigger);
(* reset the trigger to ground state *)
begin (* resettrigger *)
      with t do begin
         state := 0;
         skip := false;
         found := false
      end
end; (* resettrigger *)

procedure testfortrigger(ch: char; var t: trigger);
(* look at the character ch.
   if it is part of the trigger (at the current trigger state),
       then the trigger state goes higher.
   if it is not part of the trigger then the trigger state is reset,
      skip is true and one should skip onward to find the trigger.
   if the trigger is found, found is true.
1996 Sep 12: Bug found!  In the case of a trigger "ab", the program
used to miss it for situations like "aab".  This was because at the
first a it would step up.  Then it would see the second a and recognize
that was not part of ab.  It would fail to realize that it could be
the start of a new one.  The code now accounts for that possibility. *)
begin (* testfortrigger *)
      with t do begin
         state := succ(state);
{
writestring(list,seek);
writeln(list,'testfortrigger seek.letters[',state:1,']:',
seek.letters[state],' ch:',ch);
}
         if seek.letters[state] = ch
         then begin
            skip := false;
            if state = seek.length then found := true
                                   else found := false
         end
         else begin
            (* it failed.  But wait!  It could be the beginning
            of a NEW trigger string! *)
            if seek.letters[1] = ch then begin
               state := 1;
               skip := false;
               found := false
            end
            else begin (* reset trigger *)
               state := 0;
               skip := true;
               found := false
            end
         end
      end
end; (* testfortrigger *)
(* end module trigger.proc version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module skipblanks *)

(* 2003 July 31: tab is considered a blank character *)

function isblank(c: char): boolean;
(* is the character c blank or tab? *)
const
   tab = 9; (* tab character *)
begin
   isblank := (c = ' ') or (ord(c) = tab)
end;

procedure skipblanks(var thefile: text);
(* skip over blanks until a non-blank, or end of line, is found *)
begin
   while isblank(thefile^) and not eoln(thefile) do get(thefile);
end;

procedure skipnonblanks(var thefile: text);
(* skip over nonblanks until a blank, or end of line, is found *)
begin
   while (not isblank(thefile^)) and not eoln(thefile) do get(thefile);
end;

procedure skipcolumn(var thefile: text);
(* skip over a data column *)
begin
   skipblanks(thefile); skipnonblanks(thefile)
end;
(* end module skipblanks version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* ************************************************************************ *)
(* end module package.trigger version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module interact.getstring *)
procedure getstring(var afile: text; var buffer: string;
                    var gotten: boolean);
(* get a line (as a string) from a file not using string calls.  this lets
one obtain lines from a file without interactive prompts *)
var   index: integer; (* of buffer *)
begin (* getstring *)
      clearstring(buffer);
      if eof(afile)
      then gotten := false
      else begin
         index := 0;
         while (not eoln(afile)) and (index < maxstring) do begin
            index := succ(index);
            read(afile, buffer.letters[index])
         end;
         if not eoln(afile) then begin
            writeln(output, ' getstring: a line exceeds maximum string size (',
                            maxstring:1,')');
            halt
         end;
         buffer.length := index;
         buffer.current := 1;
         readln(afile);
         gotten := true
      end
end; (* getstring *)
(* end module interact.getstring version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module onetoken *)
procedure onetoken(var afile: text; var buffer: string;
                  var gotten: boolean);
(* Get a string from a file not using string calls.  This lets one
obtain lines from a file without interactive prompts.  if end of file
is found, gotten is false. *)
var   index: integer; (* of buffer *)
      done: boolean;
begin (* onetoken *)
   skipblanks(afile);
   emptystring(buffer);
   done := false;
   if eof(afile)
   then gotten := false
   else begin
      index := 0;
      while (not eoln(afile)) and (index < maxstring)
         and not done
      do begin
         index := succ(index);
         read(afile, buffer.letters[index]);
         if buffer.letters[index] = ' ' then begin
            done := true;
            index := pred(index);
         end
      end;
      buffer.length := index;
      buffer.current := 1;
      gotten := true
   end
end;
(* end module onetoken version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module readquotestring *)
procedure readquotestring(var afile: text; var s: string);
(* read the quoted string from afile, remove the quotes.  Trailing
blanks are converted to tab characters. *)
const
   tabcharacter = 9; (* the ordinal of the tab character *)
var
   c: char; (* a character read *)
   i: integer; (* index to the string s *)
begin
   emptystring(s);
   skipblanks(afile);
   with s do begin
      read(afile,c);
      if c <> '"' then begin
         writeln(output, 'quote string expected but " missing');
         writeln(output, 'character found instead:"',c,'"');
         halt
      end;
      if eoln(afile) then begin
         writeln(output, 'missing end of quote string " string');
         halt
      end;
      repeat
         read(afile,c);
         if c <> '"' then begin
            length := succ(length);
            letters[length] := c;
         end;
      until (c = '"')
            or eoln(afile) or (length = maxstring);
      if length = maxstring then begin
         writeln(output, 'A quote string exceeded ',maxstring:1,
                         ' characters.');
         writeln(output, 'Use a shorter string or increase constant',
                         ' maxstring.');
         halt
      end;
      if c <> '"' then begin
         writeln(output, 'The second " mark of a string is missing.');
         halt
      end;
      (* convert all blanks to tab characters *)
      for i := 1 to length do begin
         if letters[i] = ' '
         then letters[i] := chr(tabcharacter); (* set to tab character *)
      end;
   end;
end;
(* end module readquotestring version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module detabstring *)
procedure detabstring(var s: string);
(* Tabs are converted to blanks.  This allows the tab to be a special "solid"
kind of blank, but the string written won't vary with position on the line
depending on the tabstops. *)
const
   tabcharacter = 9; (* the ordinal of the tab character *)
var
   c: char; (* a character write out *)
   i: integer; (* index to the string s *)
begin
   with s do for i := 1 to length do begin
      c := letters[i];
{
for testing:
      if ord(c) = tabcharacter then c := 'T'; (* tab converted to blank *)
}
      if ord(c) = tabcharacter then c := ' '; (* tab converted to blank *)
      letters[i] := c
   end;
end;
(* end module detabstring version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module writequotestring *)
procedure writequotestring(var afile: text; s: string);
(* Write the string s to afile, surrounded by double quote marks.  Tabs are
converted to blanks.  This allows the tab to be a special "solid" kind of
blank, but the string written won't vary with position on the line depending
on the tabstops. *)
begin
   detabstring(s);
   write(afile, '"');
   writestring(afile, s);
   write(afile, '"');
end;
(* end module writequotestring version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module decapitalize *)
function decapitalize(c: char): char; 
(* convert the character c to lower case *) 
var 
   n: integer; (* c is the n'th letter of the alphabet *)
begin 
   n := ord(c); 
   if (n >= ord('A')) and 
      (n <= ord('Z'))   
   then c := chr( n - ord('A') + ord('a'))
   else c := chr(n);
   decapitalize := c
end; 
(* end module decapitalize version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module decapstring *)
procedure decapstring(var t: string);
(* convert capital letters in t to lower case *)
var
   i: integer; (* index to position in t *)
begin
   with t do for i := 1 to length
   do letters[i] := decapitalize(letters[i]);
end;
(* end module decapstring version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module copyaline *)
procedure copyaline(var fin, fout: text);
(* copy a line from file fin to file fout *)
begin (* copyaline *)
      while not eoln(fin) do begin
         fout^ := fin^;
         put(fout);
         get(fin)
      end;
      readln(fin);
      writeln(fout);
end; (* copyaline *)
(* end module copyaline version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module equalstring *)
function equalstring(a, b: string): boolean;
(* Test for equality between two strings at current positions.
NOTE:  A compiler bug results if one directly tests this way:
   if thedefinition^.nametag = aname
The reason is completely not clear!  I showed that the parts of the
strings were identical, but the whole was not by this test.
For this reason it is *critical to test strings with equalstring.
*)
var   index: integer; (* index to both strings *)
      equal: boolean; (* are letters in a and b the same? *)
begin (* equalstring *)
      if a.length = b.length
      then begin
         index := 1;
         repeat
            equal := (a.letters[index] = b.letters[index]);
            index := succ(index)
         until (not equal) or (index > a.length);
         equalstring := equal
      end
      else equalstring := false
end; (* equalstring *)
(* end module equalstring version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* begin module copystring *)
procedure copystring(a: string; var b: string);
(* copy string a to b.  The length, characters and
where the string points to are all copied. *)
var l: integer; (* index to the string *)
begin
   b.length := a.length;
   for l := 1 to a.length do b.letters[l] := a.letters[l];
   b.current := a.current;
   b.next := a.next;
end;
(* end module copystring version = 5.48; (@ of prgmod.p 2011 May 03 *)

(* ************************************************************************ *)
(* ************************************************************************ *)

(* begin module writewrapquotestring *)
procedure writewrapquotestring(var f: text;
                               indentation: string;
                               startindent, linewidth: integer;
                               thestring: string);
(* Write the string in quotes with wrapping and indentation to file f.
The line is assumed to already have been started using the indentation
string.  An additional startindent are assumed to have been written. 
The algorithm breaks the lines at spaces so that printing does not
exceed linewidth.  The simpler call
   writequotestring(f, thestring);
would write the string out without wrapping.
Suppress extra spaces in thestring UNLESS proceeded by a '. '
   (ie the end of a previous sentence).
No final carriage return is written.
---
To avoid infinite loops, this revised algorithm ALWAYS steps the index
i across the entire string, guaranteed.  Also guaranteed is that the
letter at i is written out except when it is at a space that is a
break.
*)
var
   column: integer; (* current column of output *)
   i: integer; (* index to what's actually been written of the string *)
   break: integer; (* next point of thestring to break at *)
begin
   column := indentation.length + startindent;
   i := 0;
   write(f,'"'); (* beginning quote *)
   break := linewidth - column + 1; (* +1 for the quote *)
   while i < thestring.length do begin
      i := i + 1; (* guarantees stepping through the string! *)
      (* move break back to first space or the current print location. *)
      (* But only move back if the next break is inside the string. *)
      if break < thestring.length then begin
         while (thestring.letters[break] <> ' ') and
               (break > i)
         do begin
            break := break - 1;
         end;
      end;
      if (i = break) and
         (thestring.letters[i] = ' ')
      then begin
         writeln(f);
         writestring(f,indentation);
         (* For break we have already accounted for indentation.length *)
         break := i + linewidth;  (* if this goes past thestring.length,
            then we won't break anymore - as it should be *)
{yyy}
         (* don't put a space out when at the break *)
         if thestring.letters[i] <> ' ' then write(f, thestring.letters[i]);
      end
      else write(f, thestring.letters[i]);
   end;
{ppp}
   write(f,'"'); (* ending quote *)
end; (* writewrapquotestring *)
(* end module writewrapquotestring *)

(* begin module imgalt.themain *)
procedure themain(var orihtml, imgaltp,
                      imagereport, imagenames, althtml, altstrings, stop: text);
(* the main procedure of the program *)
const
   tabcharacter = 9; (* the ordinal of the tab character *)
   debug = false; (* set true to get some debug output *)

var
   altstring: string; (* the current alt string inside an image *)
   srcstring: string; (* the current src string inside an image *)
   auto: stringptr; (* automatic file name changes, a list.
      NOTE: the list consists of file name followed by alt string;
      this alternates through the list. *)
   autoindex: stringptr; (* pointer into auto *)
   autofile: stringptr; (* pointer into auto, current file name *)
{zq}
   dupindex: stringptr; (* pointer for finding duplicates *)
   b: integer; (* index to the buffer *)
   buffer: string; (* a string in file orihtml *)
   buffero: string; (* a string to output to file althtml *)
   c: char; (* a character in the orihtml *)
   countpairs: integer; (* number of alt pairs listed in imgaltp *)
   removed: integer; (* number of duplicate alt pairs removed *)
   d: char; (* c decapitalized *)
   done: boolean; (* done with a loop *)
   foundfile: boolean; (* found the file named in variable filename *)
   filename: string; (* currently read file name *)
   gotten: boolean; (* got a string from file orihtml *)
   gotimg: integer; (* got a img; coordinate in buffer *)
   gotalt: integer; (* got an alt; coordinate in buffer *)
   gotstp: integer; (* got a  stp; coordinate in buffer *)
   linewidth: integer; (* maximum width of printed lines *)
   indentation: string; (* current indentation in the buffer *)

   temporarytag: string; (* the string to use for alternate texts,
         usually ALTERNATE_TEXT *)
   imagesALT: integer; (* number of images with alts *)
   imagesFixedFromTemporaryTag: integer; (* number of images fixed
                                            using temporary tag *)
   imagesFixedFromFile: integer; (* number of images fixed from file *)
{zzz}
   (* alt tag possibilities and names for counting: *)
   (*    no alt tag, alternative not supplied: fill in with temporary tag *)
   NAYaltNAYsup: integer;
   (*    no alt tag, alternative     supplied: use tag supplied in imgaltp *)
   NAYaltYEAsup: integer;
   (*       alt tag, alternative not supplied: leave alone *)
   YEAaltNAYsup: integer;
   (*       alt tag, alternative     supplied: use tag supplied in imgaltp *)
   YEAaltYEAsup: integer;
   imagesSUM: integer; (* sum of the NAY YEA variables *)
   imagesTOTAL: integer; (* total number of images *)

   linenumberO: integer;   (* current line number in the orihtml file O *)
   linenumberOurl: integer; (* line number of url in the orihtml file O *)
   linenumberOalt: integer; (* line number of alt in the orihtml file O *)
   linenumberA: integer;   (* current line number in the althtml file A *)
   linenumberAurl: integer; (* line number of url in the althtml file A *)
   linenumberAalt: integer; (* line number of alt in the althtml file A *)
   lind: integer; (* line indent for numbering lines *)
   showedbuffer: integer; (* the last linenumber which was shown *)
   showconversion: boolean; (* set true to show the conversions *)
   parameterversion: real; (* parameter version number *)

   imgtrigger: trigger; (* '<img' *)
   alttrigger: trigger; (* 'alt' *)
   stptrigger: trigger; (* '>' end of image *)
   quotrigger: trigger; (* '"' quote *)
   srctrigger: trigger; (* 'src' quote *)
   cbgtrigger: trigger; (* comment begin *)
   cedtrigger: trigger; (* comment end *)

   foundalt: boolean; (* we found an alt tag inside an image *)
   foundsrc: boolean; (* we found a src tag inside an image *)
   foundsrceq: boolean; (* we found a src tag followed by an equals *)
   foundalteq: boolean; (* we found a alt tag followed by an equals *)

   inimage: boolean; (* we are inside the image tag <img ... > *)
   fusedlines: boolean; (* lines are fused inside images; add a space
      for the first one *)
{fff}
   lastbuffero: char; (* the last character in the previous buffer,
   was it a space? *)
   inquote: boolean; (* we found are inside a quote string and should
      ignore varous things *)
   skipquote: boolean; (* skip the contents of a quote! *)
   skipalt: boolean; (* skip the contents of alt! *)
   skipsrc: boolean; (* skip the contents of src! *)
   processed: boolean; (* we just processed the alt and src *)
   currentquote: string; (* current quote string *)
   notdoublespace: boolean; (* two spaces were NOT found in a row in the
      orihtml.  If two spaces ARE found, only put one into currentquote *)
   replacedstring: boolean; (* true if we have replaced the alt string *)
   incomment: boolean; (* true if we are inside and skipping a comment *)
   showparse: boolean; (* true if we are showing the html parsing *)
   parsefail: boolean; (* still inside parse elements at end of file *)

   hold: text; (* internal file for holding results *)

procedure showbuffer(var f: text);
(* show the buffer to file f if we haven't already above *)
begin
   if showedbuffer < linenumberO then begin
      write(f, linenumberO:lind, ' ');
      writestring(f, buffer);
      writeln(f);
      showedbuffer := linenumberO;
   end;
end;

procedure uparrow(var f: text; column: integer);
(* show an up arrow in f at the given column.
Use global lind for line indentation. *)
begin
write(f,' ':(column+lind),'^ ',column:1,' ');
end;

procedure makefilename(var srcstring, filename: string);
(* extract the last part of the url source string srcstring
following a '/' to get the raw file name *)
var
   srcstringindex: integer; (* index to url *)
begin
   (* determine the file name from the url *)
   srcstringindex := 1;
   clearstring(filename);
   while srcstringindex <= srcstring.length do begin
{uuu}
      if srcstring.letters[srcstringindex] = '/'
      then begin
         filename.length := 0;
      end
      else begin
         filename.length := succ(filename.length);
         filename.letters[filename.length] := srcstring.letters[srcstringindex];
      end;
      srcstringindex := succ(srcstringindex);
   end;
{
write(output,'url: ');      writestring(output,srcstring); writeln(output);
write(output,'filename: '); writestring(output,filename);  writeln(output);
}
end;

function checkforfile(auto: stringptr; filename: string): boolean;
(* Check for the file name (derived from the url) in the auto list.
Result is in boolean foundfile.  If the filename is found in the auto list,
report the location in the global autoindex *)
const debug = false;
var
   foundfile: boolean; (* the file name was found *)
begin
if debug then writeln(output, 'checkforfile =================== BEGIN');
   autoindex := auto;
   done := false;
   foundfile := false;
   while not done do begin
      if autoindex = nil
      then done := true
      else begin
if debug then begin
write(output,'CHECKING: ');
writestring(output,autoindex^);
write(output,' against filename ');
writequotestring(output,filename);
end;
         if equalstring(filename, autoindex^) then begin
            (* found the file name! *)
if debug then writeln(output,' - FOUND IT!');
            foundfile := true;
            done := true;
            autoindex^.current := succ(autoindex^.current);
if debug then begin
write(output,' with alt string:');
writequotestring(output,autoindex^.next^);
writeln(output,' <<<<<<<<<<<<<<<<');
end;
         end
         else begin
if debug then writeln(output,' - NOT FOUND');
            autoindex := autoindex^.next;
            if autoindex <> nil then begin
if debug then writestring(output,autoindex^);
if debug then writeln(output);
               autoindex := autoindex^.next;
            end
            else begin
               writeln(stophold,'checkforfile: link error 2');
               halt;
            end
         end;
      end
   end;
   checkforfile := foundfile;
if debug then writeln(output, 'checkforfile =================== END');
end; (* checkforfile *)

procedure writealtstrings(var altstrings: text; filename, qstring: string);
(* write the file name and string to altstrings *)
begin
   writestring(altstrings,filename);
   write(altstrings,' ');
   writequotestring(altstrings,qstring);
   writeln(altstrings);
end;

procedure showauto(var f: text; var auto: stringptr);
(* Show the auto list to file f *)
var
   a: stringptr; (* pointer into auto *)
   count: integer; (* count of pointers *)
   fail: boolean; (* did the output fail? *)
begin
   a := auto;
   fail := false;
   count := 0;
   while a <> nil do begin
      count := count + 1;
      writestring(f,a^);
      if a^.length = 0 then begin
         writeln(stophold,'ERROR: ZERO LENGTH FILE NAME!');
         writeln(stophold,'Are you missing quotes around your src string?');
         fail := true;
         a := a^.next;
         if a <> nil then begin
            writeln(stophold,'The next string (#',count:1,
                             ') in the auto list is:');
{sss}
            writequotestring(stophold,a^);
            writeln(stophold);
         end
         else begin
            writeln(stophold, 'no string name');
         end;
         halt;
      end;
{zzz}
      write(f,' ');
      a := a^.next;
      if a = nil then begin
         writeln(stophold,'showauto: strings not in pairs, program error');
         halt;
      end;
      writequotestring(f,a^);
      writeln(f);
      a := a^.next;
   end;
{qqq}
end;

procedure addaltstrings(var auto: stringptr; filename, qstring: string);
(* add the file name and string to the auto list *)
const debug = false;
var
   current: stringptr; (* pointer into auto *)
   last: stringptr; (* the most recent 'current' stringptr *)
   done: boolean; (* search for the filename string is done *)
   duplicate: boolean; (* the filename was already in the auto list *)
begin
if debug then begin
writeln(output);
writeln(output,'addaltstrings BEGIN part <<<<<<<<<<<<<<<<<<<< 0000000000');
write  (output,'filename: ');
writequotestring(output,filename);
writeln(output);
write  (output,' qstring: ');
writequotestring(output,qstring);
writeln(output);
end;
{ yyy }
   if auto = nil then begin (* start a new list! *)
if debug then begin
writeln(output,'addaltstrings first part <<<<<<<<<<<<<<<<<<<< 1111111111');
end;
      new(auto);
      clearstring(auto^);
      copystring(filename, auto^);
      new(auto^.next);
      current := auto^.next;
      copystring(qstring, current^);
      current^.previous := auto;
   end
   else begin
if debug then begin
writeln(output,'addaltstrings       part <<<<<<<<<<<<<<<<<<<< 2222222222');
end;
      (* see if the filename string is there in the auto list already *)
      current := auto;
      last := current;
      done := false;
      duplicate := false;
      while (current <> nil) and (not done) do begin
         if equalstring(current^, filename) then begin
            done := true;
            duplicate := true;
            write(output,'DUPLICATE: ');
{ddd}
            writestring(output,filename);
            writeln(output);
         end;
         last := current;
         current := current^.next;
      end;
      if not duplicate then begin
if debug then begin
writeln(output,'NOT DUPLICATE');
end;
         (* add that to the end of the auto list! *)
         if last^.next <> nil then begin
            writeln(stophold,'program error in addaltstrings');
            halt;
         end;
         (* build first link *)
         new(last^.next);
         current := last^.next;
         emptystring(current^);
         copystring(filename, current^);
         current^.current := 1; (* record that it HAS a tag *)
         current^.previous := last;
         (* build second link *)
         new(current^.next);
         current := current^.next;
         emptystring(current^);
         copystring(qstring, current^);
         current^.next := nil;
         current^.previous := last;
{qqq}
      end;
   end
end; (* addaltstrings *)

procedure showlist;
(* show the current auto list *)
var
   a: stringptr; (* pointer into the list *)
   indentation: string; (* non-indented *)
   spaces: integer; (* spaces to separate the previous *)
begin
   clearstring(indentation);
   a := auto;
   done := false;
   while not done do begin
      if a = nil
      then done := true
      else begin
         write(output,'LIST: "');
         writestring(output,a^);
         write(output,'" ',a^.length:2);
         (* display previous string in the list *)
         spaces := pagewidth-a^.length;
         if spaces < 1 then spaces := 1;
         if a^.previous = nil then begin
            write(output,' ':spaces,' ------ previous is nil');
         end;
         writeln(output);
         a := a^.next;
      end;
   end;
end; (* showlist *)

procedure replacealtstring;
(* Replace the altstring of orihtml if one is already given in imgaltp
(indicated by foundfile).  Otherwise leave the original.  If the file
for altstring was not found, insert the default one, temporarytag.  We
need the foundfile variable to know what to do with the alt tag.  Use
the location in the auto list given by autoindex. *)
begin
   if foundfile
   then begin (* replace altstring with the one from imgaltp *)
        copystring(autoindex^.next^,altstring);
        imagesFixedFromFile := succ(imagesFixedFromFile);
   end
   else begin
        if not foundalt (* use the temporary tag only if there is
                           no alt tag at all.  If the alt tag is there but
                           empty, leave it alone. *)
        then begin
           copystring(temporarytag,altstring);
           imagesFixedFromTemporaryTag := succ(imagesFixedFromTemporaryTag);
        end;
        addaltstrings(auto,filename,altstring);
   end;
   replacedstring := true;
end; (* replacealtstring *)

procedure readfilealtpairs;
(* Read in file alt pairs from imgaltp *)
var
   aii: integer; (* index to autoindex *)
begin
   countpairs := 0;
   removed := 0;
   new(auto);
   emptystring(auto^);
   writeln(output,'Reading file/alt pairs from imgaltp:');
   auto^.previous := nil; (* no previous item *)
   auto^.next := nil; (* no next item yet *)
   autoindex := auto;
   while not eof(imgaltp) do begin
      if eoln(imgaltp) then begin
         readln(imgaltp); (* allow blank lines *)
         if eof(imgaltp) then begin
            (* the last link is empty - remove it *)
            (* unearth this code to see that it is empty:
            write(output,'autoindex: ');
            writequotestring(output,autoindex^);
            writeln(output);
            *)
            if autoindex^.length <> 0 then begin
               write(output,'autoindex should be empty but is not: ');
               writequotestring(output,autoindex^);
               writeln(output);
               halt;
            end;
            if autoindex^.previous <> nil
            then autoindex^.previous^.next := nil;
{
}
            dispose(autoindex);
         end;
      end
      else begin
         onetoken(imgaltp, autoindex^, gotten); (* read file name *)
         if gotten then begin
            if showconversion then begin
               write(output,'   ');
               writestring(output,autoindex^);
               write(output,' -> ');
            end;
            new(autoindex^.next);
            emptystring(autoindex^.next^);
            autoindex^.next^.previous := autoindex; (* back pointer *)
            autoindex^.next^.next := nil; (* nothing beyond this yet *)
            autoindex^.current := 0; (* used to count number of uses *)
            autofile := autoindex; (* current file name *)
            autoindex := autoindex^.next; (* current tag string *)
            readquotestring(imgaltp, autoindex^); (* read alt string *)
            readln(imgaltp);
            (* readquotestring puts tabs in for spaces.
               This is not appropriate for this program, so undo it: *)
            with autoindex^ do for aii := 1 to length do begin
               if letters[aii] = chr(tabcharacter)
               then letters[aii] := ' ' (* revert to space *)
            end;
            if showconversion then begin
               writequotestring(output,autoindex^);
               writeln(output);
            end;
            countpairs := succ(countpairs);
            if autoindex^.previous <> auto then begin
               (* check whether this is a duplicate *)
               dupindex := auto;
               done := false;
               while not done do begin
                  if equalstring(dupindex^,autofile^)
                  then begin
                     write(output,' ':autofile^.length,' => ');
                     writeln(output,'DUPLICATE string in imgaltp - REMOVED');
                     autoindex := autoindex^.previous^.previous;
                     dispose(autoindex^.next^.next);
                     dispose(autoindex^.next);
                     autoindex^.next := nil;
                     done := true;
                     removed := succ(removed);
                  end
                  else begin
{zq}
                     if dupindex^.next = nil then begin
                        writeln(stophold,'broken list');
                        halt;
                     end;
                     dupindex := dupindex^.next^.next; (* skip to next file name *)
                     if dupindex = autofile then begin
                        done := true;
                     end
                  end;
               end;
            end
            else begin
               if debug
               then writeln(output,'skip duplicate check since it is first');
            end;
            if not eof(imgaltp) then begin (* prepare to read the next one *)
               new(autoindex^.next);
               emptystring(autoindex^.next^);
               autoindex^.next^.previous := autoindex; (* back pointer *)
               autoindex^.next^.next := nil; (* end of list *)
               autoindex := autoindex^.next;
               autoindex^.current := 0; (* used to count number of uses *)
            end;
         end
         else begin
            writeln(stophold,'could not get file name from parameters');
            halt;
         end;
      end;
   end;
end; (* readfilealtpairs *)

procedure showparsedata(var f: text);
(* show the current parse information to file f *) 
begin
   write(f,linenumberO:4,' ');
   if incomment then write(f,'C')
                else write(f,'-');
   if inquote   then write(f,'Q')
                else write(f,'-');
   if inimage   then write(f,'I')
                else write(f,'-');
   write(f,' ');
   if foundsrc   then write(f,'s')
                 else write(f,'-');
   if foundsrceq then write(f,'=')
                 else write(f,'-');
   if foundalt   then write(f,'a')
                 else write(f,'-');
   if foundalteq then write(f,'=')
                 else write(f,'-');
end;

procedure processimage;
(* We are at the end '>' of the '<img',
so we have at this point the src and the alt strings.
Now is the time to process them. *)
var
   blanks: integer; (* counting blanks on start of string *)
begin
{uuu}
{
write(output,'**** srcstring:'); (* BUBBA *)
writequotestring(output, srcstring); (* BUBBA *)
write(output,' altstring:'); (* BUBBA *)
writequotestring(output, altstring); (* BUBBA *)
writeln(output,  '*********'); (* BUBBA *)
}
{zy}
   (* Determine the file name (without slashes) of the src *)
   makefilename(srcstring, filename);
   (* locate the file in the auto list *)
   foundfile := checkforfile(auto, filename);
   (* now autoindex should point to the file if foundfile is true *)
   replacealtstring;

   gotstp := b;
   imagesTOTAL := succ(imagesTOTAL);

   (* determine indentation *)
   (* count blanks to mirror indentation!! *)
   blanks := 0;
   clearstring(indentation);
   while (blanks <= buffer.length) and
          ((buffer.letters[blanks+1] = ' ')
            or
          (buffer.letters[blanks+1]
            = chr(tabcharacter)))
   do begin
      {write(althtml,buffer.letters[blanks+1]);}
      indentation.length := succ(indentation.length);
      indentation.letters[indentation.length] := buffer.letters[blanks+1];
      blanks := blanks + 1;
   end;

   if foundalt then begin
      imagesALT := succ(imagesALT);
      if foundfile
      then YEAaltYEAsup := succ(YEAaltYEAsup)
      else YEAaltNAYsup := succ(YEAaltNAYsup);
   end
   else begin
      if foundfile
      then NAYaltYEAsup := succ(NAYaltYEAsup)
      else NAYaltNAYsup := succ(NAYaltNAYsup);
      showbuffer(hold);
      uparrow(hold,gotstp); writeln(hold,'stp');
      write(hold,' NO ');
      writestring(hold,temporarytag);
      writeln(hold,' FOR THIS IMAGE');
      (* dump the hold data now *)
      reset(hold);
      while not eof (hold) do begin
         copyaline(hold, imagereport);
      end;
   end;
   rewrite(hold);
   inimage := false;
   (* handle source names at end of img *)
   foundsrc   := false;
   foundsrceq := false;
   foundalteq := false;

  (* write out the src on its own line *)
   writeln(althtml); (* start new line *)
   writestring(althtml,indentation); (* copy indentation of '>' *)
   write(althtml, 'src=');  (* use indentation of '>' *)
   writequotestring(althtml, srcstring);
   writeln(althtml);

{ppp}

   (* write out the alt on its own line *)
   writestring(althtml,indentation); (* copy indentation of '>' *)
   write(althtml,'alt='); (* start the alternate text, startindentation *)

{
writeln(althtml);
write(althtml,'raw altstring: ');
writequotestring(althtml, altstring);
writeln(althtml);
writeln(althtml);
}

   writewrapquotestring(althtml, indentation, 4, linewidth, altstring);
{* don't indent for the '>'!
   writeln(althtml);
   writestring(althtml,indentation); (* indentation for next line *)
*}

   (* put the stop trigger after the src and alt: *)
   writestring(althtml,stptrigger.seek);

{
write(output,'buffer: |');
writestring(output,buffer);
writeln(output,'|');
write(output,'buffer: |');
writeln(output,'^':b,' ',b:1);
}

   (* If there is more to the line after the '>', then start a new
      indented line.  If there isn't more, don't do this because if we
      did we would add a new carriage return each time! *)
{
   if buffer.length > b then begin
      (* move to next line: *)
      writeln(althtml);
      (* indentation for next line: *)
      writestring(althtml,indentation);
   end;
}

   (* Record imagenames *)
   (* add an alt tag if there wasn't one *)
   if not foundalt then begin
      linenumberA := succ(linenumberA);
      linenumberAalt := linenumberA;
      if foundfile
      then write(imagenames,'+++ ')  (* ............. 1 *)
      else write(imagenames,'--- '); (* ............. 1 *)
   end
   else begin
      write(imagenames,     'alt '); (* ............. 1 *)
{qqq}
   end;
{iii}
   writestring(imagenames,srcstring); (* the URL .... 2 *)
   writeln(imagenames, 
                           ' ', linenumberOurl:1,  (* 3 *)
                           ' ', linenumberOalt:1,  (* 4 *)
                           ' ', linenumberAurl:1,  (* 5 *)
                           ' ', linenumberAalt:1); (* 6 *)

{ppp}
end; (* processimage *)

procedure bufferout(var f: text; var buffero: string);
(* Remove trailing spaces and then output the buffero string to f *)
var
   done: boolean; (* done shortening buffero *)
begin
   done := false;
   while not done do begin
     if buffero.length <= 0
     then done := true
     else if buffero.letters[buffero.length] = ' '
          then buffero.length := buffero.length - 1
          else done := true
   end;
   writestring(f, buffero);
end;

begin (* themain *)
   writeln(output,'imgalt ',version:4:2);
   rewrite(stophold);

   reset(imgaltp);
   readln(imgaltp, parameterversion);
   if round(100*parameterversion) < round(100*updateversion) then begin
      writeln(stophold, 'You have an old parameter file!');
      halt
   end;
   if not eof(imgaltp) then begin
      showconversion := (imgaltp^ = 'c');
      get(imgaltp);
      if not eoln(imgaltp)
      then showparse := (imgaltp^ = 'p')
      else showparse := false;
      readln(imgaltp);
   end
   else begin
      writeln(stophold,'imgaltp is missing line 2');
      halt;
   end;

   if not eof(imgaltp) then begin
      getstring(imgaltp, temporarytag, gotten);
      write(output,'temporary tag: ');
      writestring(output, temporarytag);
      writeln(output);
   end
   else begin
      writeln(stophold,'imgaltp missing is line 3');
      halt;
   end;

   (* Read in file alt pairs from imgaltp *)
   readfilealtpairs;

   if countpairs = 0 then begin
      (* the user didn't supply any pairs in imgaltp; clear out *)

      (* dispose(auto); no need to do this - it's gone already in
      readfilealtpairs *)
      auto := nil;
   end;

   writeln(output,'counted pairs in imgaltp: ',countpairs:1);
   writeln(output,'removed pairs in imgaltp: ',removed:1);

   if debug then begin
      showlist;
   end;

(*                                   1         2         3         4         5 *)
(*                          12345678901234567890123456789012345678901234567890 *)
   filltrigger(imgtrigger, '<img                                              ');
   filltrigger(alttrigger, 'alt                                               ');
   filltrigger(srctrigger, 'src                                               ');
   filltrigger(stptrigger, '>                                                 ');
   filltrigger(quotrigger, '"                                                 ');
   filltrigger(cbgtrigger, '<!--                                              ');
(*
   filltrigger(cedtrigger, '-->                                               ');
*)
   (* testfortrigger cannot handle two dashes.  So do the test separately *)
   filltrigger(cedtrigger, '->                                                ');
   resettrigger(imgtrigger);
   resettrigger(alttrigger);
   resettrigger(srctrigger);
   resettrigger(stptrigger);
   resettrigger(quotrigger);
   resettrigger(cbgtrigger);
   resettrigger(cedtrigger);

   clearstring(currentquote);
   clearstring(altstring);
   clearstring(srcstring);

   linenumberO := 0;
   linenumberOurl := 0;
   linenumberOalt := 0;
   linenumberA := 0;
   linenumberAurl := 0;
   linenumberAalt := 0;
   linewidth := pagewidth; (* hardwired for now *)
   lind := 3;
   showedbuffer := 0;
   foundalt := false;
   replacedstring := false;
   skipquote := false;
   skipalt := false;
   skipsrc := false;
   processed := false;
   filename.current := 0;

   inimage := false;
   inquote := false;
   incomment := false;
   fusedlines := false;
{fff}
   lastbuffero := '_'; (* NOT a space previously *)

   imagesALT    := 0;
   imagesFixedFromTemporaryTag := 0;
   imagesFixedFromFile := 0;
   imagesTOTAL  := 0;
   NAYaltNAYsup := 0;
   NAYaltYEAsup := 0;
   YEAaltNAYsup := 0;
   YEAaltYEAsup := 0;

   reset(orihtml);
   rewrite(althtml);
   rewrite(imagereport);
   rewrite(imagenames);
   writeln(imagenames, '* imgalt ', version:4:2);
   writeln(imagenames, '* 1: alt/---;',
                        ' 2: url;',
                        ' 3: orihtml url;',
                        ' 3: orihtml alt;',
                        ' 4: althtml url;',
                        ' 5: althtml alt');
   rewrite(hold);

   if showparse then begin
      writeln(output,'PARSE of the orihtml:');
      writeln(output);
   end;

   (* read the original html (orihtml) to detect an <img.
      Then detect the srcstring and altstring (if it exists). *)
   while not eof(orihtml) do begin (* each line of the file *)
      linenumberO := succ(linenumberO);
      linenumberA := succ(linenumberA);
      resettrigger(imgtrigger);
      resettrigger(alttrigger);
      resettrigger(stptrigger);
      if eoln(orihtml) then begin
         readln(orihtml);
         writeln(althtml);
         if showparse then begin
            showparsedata(output);
            writeln(output);
         end;
      end
      else begin (* non-empty line *)
         clearstring(buffero);
         getstring(orihtml, buffer, gotten); (* does a readln *)
         if gotten then begin
            if showparse then begin
               showparsedata(output);
               write(output,' ');
               writestring(output,buffer);
               writeln(output);
            end;
{zzz}

            b := 1;
            while b <= buffer.length do begin (* read across the line *)
               c := buffer.letters[b]; (* a letter from the current line *)
               (* triggers are insensitive to capitalization by using d: *)
               d := decapitalize(c);

               (* Most of file is written from the following.
                  Insert the character c into the output buffer
                  if it's not in a quote, alt or src. *)

               if (not skipquote) and (not skipalt) and (not skipsrc)
               then begin

                  if inimage then begin
                     (* We are at the start of a line inside an image
                     and fusing lines, then add a space *)
                     if (lastbuffero <> ' ') and (b = 1) then begin
                        buffero.length := succ(buffero.length);
                        buffero.letters[buffero.length] := ' ';
                     end;
                  end;
                  buffero.length := succ(buffero.length);
                  buffero.letters[buffero.length] := c;

                  (* If inside an img and the previous character was a
                  space, suppress another space *)
                  if inimage
                  then if c = ' '
                  then if buffero.length > 1
                  then if buffero.letters[buffero.length-1] = ' '
                  then buffero.length := pred(buffero.length);

{fff}

               end;

               (* Determine if we are inside a comment *)
               testfortrigger(d,cbgtrigger);
{
if (linenumberO > 6) and (linenumberO < 9) then begin
writeln(output,'BUBBA 0, d=',d);
with cedtrigger do begin
writeln(output,'cedtrigger.state=',state);
writeln(output,'cedtrigger.skip=',skip);
writeln(output,'cedtrigger.found=',found);
end
end;
}
               if cbgtrigger.found then begin
                  incomment := true;
               end;
               testfortrigger(d,cedtrigger);
               if cedtrigger.found then begin
                  (* We have found '->'.  testfortrigger can't handle
                  multiple dashes of variable length.  So was there a
                  dash in front of this? *)
                  if b >= 3 then begin (* Is there space for a dash? *)
                     if buffer.letters[b-2] = '-' then begin

{
writestring(output,buffer);(* BUBBA *)
writeln(output,' b = ',b:1,' BUBBA 0.6931');
writeln(output,'buffer.letters[b-0]=', buffer.letters[b-0]);
writeln(output,'buffer.letters[b-1]=', buffer.letters[b-1]);
writeln(output,'buffer.letters[b-2]=', buffer.letters[b-2]);
}
                        incomment := false;
                     end;
                  end;
               end;

               (* Look for an image if we are not in a comment or image *)
               if (not inimage) and (not incomment) then begin
                  testfortrigger(d,imgtrigger);
                  if imgtrigger.found then begin
{bbb}
                     gotimg := b;
                     inimage := true;
                     foundsrc := false;
                     foundsrceq := false;
                     foundalt := false;
                     processed := false;
                     foundalteq := false;
                     resettrigger(quotrigger);
                     inquote := false; (* ignore imbalanced quotes outside img! *)
                     skipquote := false;
                     (* NB: clearstring removes pointers.  However,
                        these strings are not part of the auto linked
                        list, they get copied in. *)
                     clearstring(currentquote);
                     clearstring(altstring);
                     clearstring(srcstring);
                     replacedstring := false;
                     writeln(hold,'===========');
                     showbuffer(hold);
                     uparrow(hold,gotimg); writeln(hold,'img');
                     linenumberOalt := 0; (* default is there is none *)
                     filename.length := 0;
                     fusedlines := false;
                     lastbuffero := '_'; (* outside image; no last buffero *)
{fff}
                  end;
               end;

               if inimage and (not incomment) then begin (* in the <img ... > *)

                  if inquote and (c = '"') then begin
                     (* we are at the end of a quote *)
                     if skipalt then skipalt := false;
                     if skipsrc then skipsrc := false;
                  end;

                  (* simpler test than trigger: *)
                  if c = '"' then inquote := not inquote;
                  showbuffer(hold); (* show all parts of the img *)

                  if inquote
                  then begin (* inquote { *)
                     (* quotes start with a quote mark, skip it *)
                     if c <> '"' then begin
                        (* capture the current string *)

                        if b = 1 then begin
                           (* Inside a quote continued from a previous line! 
                              Add a preceeding space! *)
                           currentquote.length := succ(currentquote.length);
                           currentquote.letters[currentquote.length] := ' ';
                        end;

                        (* Suppress multiple spaces! *)
                        if currentquote.length > 0 then begin
                           if not ((c = ' ') and
                                   (currentquote.letters[currentquote.length]
                                      = ' '))
                              then notdoublespace := true
                              else notdoublespace := false;
                        end
                        else notdoublespace := true;

                        (* If the previous two letters were '. ', then
                        that's the end of a sentence - using the
                        official two spaces between sentences, so
                        don't suppress the two spaces! *)
                        if (currentquote.length > 3)
                           and (not notdoublespace) then begin
                           if currentquote.letters[currentquote.length-1] = '.'
                           then notdoublespace := true;
                        end;

                        if notdoublespace then begin
                           currentquote.length := succ(currentquote.length);
                           currentquote.letters[currentquote.length] := c;
                        end;
                     end
                  end (* inquote } *)
                  else begin (* not inquote { *)
                     skipquote := false; (* don't skip anymore after quote! *)

                     testfortrigger(d,alttrigger); (* look for alt tag *)
                     if alttrigger.found then begin
                        gotalt := b;
                        showbuffer(hold);
                        uparrow(hold,gotalt); writeln(hold,'alt');
                        writeln(hold,'alt');
                        linenumberOalt := linenumberO; (* record O alt *)
                        linenumberAalt := linenumberA; (* record A alt *)
                        foundalt := true;
                        skipalt := true;
                        if not skipsrc (* if skipsrc, then it was *)
                        then buffero.length := buffero.length (* remove 'alt' *)
                                               - alttrigger.seek.length;
                        (* remove space before 'alt' if there is one *)
                        if buffero.length > 0
                        then if buffero.letters[buffero.length] = ' '
                        then buffero.length := pred(buffero.length);
                     end;
                     if foundalt then begin
                        if c = '=' then begin
                           foundalteq := true;
                        end;
                     end;

                     testfortrigger(d,srctrigger); (* look for src tag *)
                     if srctrigger.found then begin
                        showbuffer(hold);
                        uparrow(hold,b); writeln(hold,'src');
                        linenumberOurl := linenumberO;
                        linenumberAurl := linenumberA;
                        foundsrc := true;
                        skipsrc := true;
                        if not skipalt
                        then buffero.length := buffero.length (* remove 'src' *)
                                               - srctrigger.seek.length;
                        (* remove space before 'src' if there is one *)
                        if buffero.length > 0
                        then if buffero.letters[buffero.length] = ' '
                        then buffero.length := pred(buffero.length);
                     end;
                     if foundsrc then begin
                        if c = '=' then begin
                           foundsrceq := true;
                        end;
                     end;

                     (* if there was a quote string, attach it to the
                        altstring or srcstring *)
                     if currentquote.length > 0 then begin
                        if foundsrceq and (srcstring.length = 0) then begin
                           copystring(currentquote, srcstring);

                           if showparse then begin
                              write(output,'srcstring ');
                              writequotestring(output,srcstring);
                              writeln(output);
                           end;
                        end;
                        if foundalteq and (altstring.length = 0) then begin
                           copystring(currentquote, altstring);
                           if showparse then begin
                              write(output,'altstring ');
                              writequotestring(output,altstring);
                              writeln(output);
                           end;
                        end;
                        (* wipe the current string even if it had stuff *)
                        clearstring(currentquote);
                     end;

                     testfortrigger(d,stptrigger); (* look for end of img *)
                     if stptrigger.found then begin
{ppp}
                        (* remove the stop trigger: *)
                        buffero.length := buffero.length-stptrigger.seek.length;
                        (* dump the current buffero out *)
                        bufferout(althtml, buffero);
{fff}
                        if buffero.length > 0
                        then lastbuffero := buffero.letters[buffero.length]
                        else lastbuffero := '_';
                        clearstring(buffero);
{bbb}


                        if srcstring.length = 0 then begin
                           writeln(stophold,'ERROR: ZERO LENGTH FILE NAME!');
                           writeln(stophold,'Are you missing quotes around',
                                            ' your src string?');
                           writeln(stophold,'At line ', linenumberO:1,
                                            ' of orihtml.');
{sss}
                        halt;
                        end;
                        (* now process the image with the srcstring
                        and altstring: *)
                        processimage;

                        processed := true;
                        foundsrc := false;
                        foundsrceq := false;
                        foundalt := false;
                        foundalteq := false;
                        skipalt := false;
                        skipsrc := false;
                     end
                     else processed := false;
                  end; (* not inquote } *)

               end; (* in image and not in comment *)

               b := succ(b);
            end; (* end reading through the buffer characters *)

            bufferout(althtml, buffero);
            if (not skipalt) and (not skipsrc)
               and not inimage
            then writeln(althtml);

{
writeln(output,'BUBBA: --------- end of string state:');
writeln(output,'BUBBA: skipquote', skipquote);
writeln(output,'BUBBA: skipalt  ', skipalt);
writeln(output,'BUBBA: skipsrc  ', skipsrc);
writeln(output,'BUBBA: processed', processed);
writeln(output,'BUBBA: ------------------------------');
}

         end (* buffer string was gotten *)
         else begin
            writeln(stophold,'buffer string not gotten');
            halt;
         end
      end (* non-empty line *)
   end; (* end of reading through the orihtml *)

   writeln(output,'--- Done reading orihtml ---');

   if showparse then begin
      writeln(output);
   end;
   writeln(output, imagesALT:2,' orihtml images had alt tags');
   writeln(output, imagesFixedFromFile:2,' orihtml images fixed');
   writeln(output, imagesFixedFromTemporaryTag:2,
                   ' orihtml images fixed using temporary tag');
{zzz}

   writeln(output, '-----------------------------');
   writeln(output,
   NAYaltNAYsup:2,
     ' no alt tag, alternative not supplied: fill in with temporary tag');
   writeln(output,
   NAYaltYEAsup:2,
     ' no alt tag, alternative     supplied: use tag supplied in imgaltp');
   writeln(output,
   YEAaltNAYsup:2,
     '    alt tag, alternative not supplied: leave alone');
   writeln(output,
   YEAaltYEAsup:2,
     '    alt tag, alternative     supplied: use tag supplied in imgaltp');
   imagesSUM := NAYaltNAYsup+NAYaltYEAsup+YEAaltNAYsup+YEAaltYEAsup;
   writeln(output, '-----------------------------');
   writeln(output, imagesSUM:2,' images total');
   if imagesSUM <> imagesTOTAL
   then begin
      writeln(output,'COUNT ERROR: total images counted directly: ',
                     imagesTOTAL:1);
   end;
   write(output,'The temporary tag is "');
   writestring(output,temporarytag);
   writeln(output,'".');

   writeln(output,'--- number of alt tags inserted for each file name: ----');
   if countpairs > 0 then begin
      autoindex := auto;
      while autoindex <> nil do begin
         if autoindex^.current > 0 then begin (* reduce output! *)
            write(output, '* ',autoindex^.current:1,' ');
            writestring(output,autoindex^);
            if autoindex^.current = 0 then begin
               write(output,' -------------- in imgaltp but not used in orihtml');
            end;
            writeln(output);
         end;
         autoindex := autoindex^.next;
         if autoindex <> nil then begin
            autoindex := autoindex^.next;
         end
         else begin
            writeln(stophold,'themain: link error 3');
            halt;
         end
      end;
   end
   else begin
      writeln(output,'NONE');
   end;

   (* detect run-on parsing errors *)
   parsefail := false;
   if inimage then begin
      writeln(stophold, 'Still inside image at end of file');
      parsefail := true;
   end;
   if inquote then begin
      writeln(stophold, 'Still inside quote at end of file');
      parsefail := true;
   end;
   if incomment then begin
      writeln(stophold, 'Still inside comment at end of file');
      parsefail := true;
   end;
   if parsefail then begin
      writeln(stophold,' - PARSING FAILED - ERROR IN HTML');
      halt;
   end;

   (* put out the revised list of file/alt pairs: *)
   (* 2011 Aug 11: do this AFTER the parse checks for safety! *)
   rewrite(altstrings);
   showauto(altstrings, auto);

end;
(* end module imgalt.themain *)

begin
   themain(orihtml, imgaltp,
           imagereport, imagenames, althtml, altstrings, stop);
1: end.
