#############################################################################
##
#W  GAPDoc2Text.gi                 GAPDoc                        Frank Lübeck
##
#H  @(#)$Id: GAPDoc2Text.gi,v 1.32 2008/04/08 23:41:32 gap Exp $
##
#Y  Copyright (C)  2000,  Frank Lübeck,  Lehrstuhl D für Mathematik,  
#Y  RWTH Aachen
##
##  The  files GAPDoc2Text.g{d,i}  contain  a  conversion program  which
##  produces from a  GAPDoc XML-document a text version  for viewing the
##  document on screen (GAP online help).
##  

##  
##  We add a link to the document root to all recursively called functions
##  by adding .root entries.    The toc-, index- and  bib-information   is
##  collected in the root.
##  
##  The set of elements is partitioned into two subsets - those which
##  contain whole paragraphs and those which don't. 
##  
##  The     handler  of  a    paragraph       containing  element     (see
##  GAPDoc2TextProcs.ParEls below) gets a list as argument to which it adds
##  entries pairwise: the first of  such a pair  is the paragraph  counter
##  (like [3,2,1,5] meaning Chap.3, Sec.2, Subsec.1, Par.5) and the second
##  is the formatted text of this paragraph.
##  
##  Some   handlers  of paragraph   containing  elements do the formatting
##  themselves (e.g., .List), the others are handled in the main recursion
##  function 'GAPDoc2TextContent'.
##  
##  We produce  a full version of  the document  in text format, including
##  title   page, abstract and  other front   matter,  table of  contents,
##  bibliography (via   BibTeX-data  files) and  index.  Highlighting with
##  colors for ANSI color sequence capable terminals is included.
##  
##  In   this text converter  we also   produce  the manual.six  data. For
##  getting page  numbers in .dvi  and .pdf  versions, the LaTeX-converter
##  and LaTeX must be run first. This produces a .pnr file.
##  

InstallValue(GAPDoc2TextProcs, rec());

##  Some text attributes for display on ANSI terminals
##  We do all the formatting with not used escape sequences of form
##  <ESC>[<num>X. These are substituted by user configurable sequences
##  before an actual display. 
##  
##  <#GAPDoc Label="SetGAPDocTextTheme">
##  <ManSection >
##  <Func Arg="[optrec]" Name="SetGAPDocTextTheme" />
##  <Returns>nothing</Returns>
##  <Description>
##  With this function can readers of the screen version of &GAP; manuals 
##  which are generated by the &GAPDoc; package configure the color and
##  attribute layout of the displayed text. There is a default which can 
##  be reset by calling this function without argument. <P/>
##  
##  As an abbreviation the argument <A>optrec</A> can be a string for
##  the known name of a  theme. Currently, there is only <C>"none"</C>
##  which displays just the plain text without any markup. <P/>
##  
##  Otherwise, <A>optrec</A> must be a record. Its entries overwrite the 
##  corresponding entries in the default. To construct valid markup you 
##  can use <Ref Var="TextAttr"/>. The following components are recognized:
##  
##  <List>
##  <Mark><C>reset</C></Mark><Item>reset to default, don't change this </Item>
##  <Mark><C>Heading</C></Mark><Item>chapter and (sub-)section headings </Item>
##  <Mark><C>Func</C></Mark><Item>function, operation, ... names </Item>
##  <Mark><C>Arg</C></Mark><Item>argument names in descriptions</Item>
##  <Mark><C>Example</C></Mark><Item>example code</Item>
##  <Mark><C>Package</C></Mark><Item>package names</Item>
##  <Mark><C>Returns</C></Mark><Item>Returns-line in descriptions</Item>
##  <Mark><C>URL</C></Mark><Item>URLs</Item>
##  <Mark><C>Mark</C></Mark><Item>Marks in description lists</Item>
##  <Mark><C>K</C></Mark><Item>&GAP; keywords</Item>
##  <Mark><C>C</C></Mark><Item>code or text to type</Item>
##  <Mark><C>F</C></Mark><Item>file names</Item>
##  <Mark><C>B</C></Mark><Item>buttons</Item>
##  <Mark><C>Emph</C></Mark><Item>emphasized text</Item>
##  <Mark><C>Ref</C></Mark><Item>reference text</Item>
##  <Mark><C>BibReset</C></Mark><Item>reset for bibliography, 
##           don't change</Item>
##  <Mark><C>BibAuthor</C></Mark><Item>author names in bibliography</Item>
##  <Mark><C>BibTitle</C></Mark><Item>titles in bibliography</Item>
##  <Mark><C>BibJournal</C></Mark><Item>journal names in bibliography</Item>
##  <Mark><C>BibVolume</C></Mark><Item>volume number in bibliography</Item>
##  <Mark><C>BibLabel</C></Mark><Item>labels for bibliography entries</Item>
##  </List>
##  
##  <Example>
##  gap> # change display of headings to bold green
##  gap> SetGAPDocTextTheme(rec(
##  >              Heading:=Concatenation(TextAttr.bold, TextAttr.2)));
##  </Example>
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##  
# used fields
GAPDoc2TextProcs.TextAttrFields := [ "reset", "Heading", "Func", "Arg",
            "Example", "Package", "Returns", "URL", "Mark", "K", "C", "F", 
            "B", "Emph", "Ref", "BibReset", "BibAuthor", "BibTitle", 
            "BibJournal", "BibVolume", "BibLabel" ];
GAPDoc2TextProcs.TextAttr := rec();
GAPDoc2TextProcs.TextAttr.f := function()
  local i;
  for i in [1..Length(GAPDoc2TextProcs.TextAttrFields)] do
    GAPDoc2TextProcs.TextAttr.(GAPDoc2TextProcs.TextAttrFields[i]) := 
        Concatenation(TextAttr.CSI, String(i-1), "X");
  od;
end;
GAPDoc2TextProcs.TextAttr.f();
Unbind(GAPDoc2TextProcs.TextAttr.f);

GAPDoc2TextProcs.DefaultTheme := rec();
GAPDoc2TextProcs.DefaultTheme.reset := TextAttr.reset;
GAPDoc2TextProcs.DefaultTheme.Heading := Concatenation(TextAttr.bold,
                                            TextAttr.underscore, TextAttr.1);
GAPDoc2TextProcs.DefaultTheme.Func := Concatenation(TextAttr.bold, TextAttr.4);
GAPDoc2TextProcs.DefaultTheme.Arg := Concatenation(TextAttr.normal, TextAttr.4);
GAPDoc2TextProcs.DefaultTheme.Example := 
                                     Concatenation(TextAttr.normal, TextAttr.5);
GAPDoc2TextProcs.DefaultTheme.Package := TextAttr.bold;
GAPDoc2TextProcs.DefaultTheme.Returns := TextAttr.bold;
GAPDoc2TextProcs.DefaultTheme.URL := TextAttr.4;
GAPDoc2TextProcs.DefaultTheme.Mark := Concatenation(TextAttr.bold, TextAttr.3);
GAPDoc2TextProcs.DefaultTheme.K := Concatenation(TextAttr.normal, TextAttr.2);
GAPDoc2TextProcs.DefaultTheme.C := Concatenation(TextAttr.normal, TextAttr.2);
GAPDoc2TextProcs.DefaultTheme.F := Concatenation(TextAttr.bold, "");
GAPDoc2TextProcs.DefaultTheme.B := Concatenation(TextAttr.bold, TextAttr.b6);
GAPDoc2TextProcs.DefaultTheme.Emph := 
                                     Concatenation(TextAttr.normal, TextAttr.6);
GAPDoc2TextProcs.DefaultTheme.Ref := TextAttr.bold;
GAPDoc2TextProcs.DefaultTheme.BibReset := TextAttr.reset;
GAPDoc2TextProcs.DefaultTheme.BibAuthor := 
                                       Concatenation(TextAttr.bold, TextAttr.1);
GAPDoc2TextProcs.DefaultTheme.BibTitle := TextAttr.4;
GAPDoc2TextProcs.DefaultTheme.BibJournal := "";
GAPDoc2TextProcs.DefaultTheme.BibVolume := TextAttr.4;
GAPDoc2TextProcs.DefaultTheme.BibLabel := TextAttr.3;
GAPDoc2TextProcs.OtherThemes := rec( default :=
                                GAPDoc2TextProcs.DefaultTheme, none := rec() );
GAPDoc2TextProcs.f := function()
  local a;
  for a in RecFields(GAPDoc2TextProcs.DefaultTheme) do
    GAPDoc2TextProcs.OtherThemes.none.(a) := "";
  od;
end;
GAPDoc2TextProcs.f();
Unbind(GAPDoc2TextProcs.f);

GAPDocTextTheme := 0;
SetGAPDocTextTheme := function(arg)
  local r, res, af, v, i;
  if Length(arg) = 0 then
    r := rec();
  else
    r := arg[1];
  fi;
  if IsString(r) then
    if not IsBound(GAPDoc2TextProcs.OtherThemes.(r)) then
      Print("Only the following named text themes are available:\n     ",
            RecFields(GAPDoc2TextProcs.OtherThemes), "\n");
      return;
    else
      r := GAPDoc2TextProcs.OtherThemes.(r);
    fi;
  fi;

  res := [[], []];
  af := GAPDoc2TextProcs.TextAttrFields;
  for i in [1..Length(af)] do
    if IsBound(r.(af[i])) then
      v := r.(af[i]);
    else
      v := GAPDoc2TextProcs.DefaultTheme.(af[i]);
    fi;
    Add(res[1], Concatenation(String(i-1), "X"));
    Add(res[2], v);
  od;
  SortParallel(res[1], res[2]);
  GAPDocTextTheme := res;
end;
SetGAPDocTextTheme(rec());


GAPDoc2TextProcs.ParEls := 
[ "Display", "Example", "Log", "Listing", "List", "Enum", "Item", "Table",
"TitlePage", "Address", "TitleComment", "Abstract", "Copyright",
"Acknowledgements", "Colophon", "TableOfContents", "Bibliography", "TheIndex",
"Subsection", "ManSection", "Description", "Returns", "Section", "Chapter",
"Appendix", "Body", "Book", "WHOLEDOCUMENT", "Attr", "Fam", "Filt", "Func",
"Heading", "InfoClass", "Meth", "Oper", "Prop", "Var", "Verb" ];

##  arg: a list of strings
##  nothing for now, may be enhanced and documented later. 
SetGapDocTxtOptions := function(arg)    
  local   gdp;
  gdp := GAPDoc2TextProcs;
  return;  
end;

##  here we collect paragraphs to whole chapters and remember line numbers
##  of subsections for the .six information
GAPDoc2TextProcs.PutFilesTogether := function(l, six)
  local   countandshift,  concat, files,  n,  i,  p,  a, tmp;
  
  # count number of lines in txt and add 2 spaces in the beginning of
  # each  line, returns [newtxt, nrlines]
  countandshift := function(txt)
    local   new,  ind,  n,  p,  pos;
    # sometimes there may occur paragraph elements inside text elements
    # (like list in list item) - we concatenate the text here.
    concat := function(txt)
      local new, a;
      if not IsString(txt) then
        new := "";
        for a in txt do
          if IsChar(a) then
            Add(new, a);
          elif IsList(a) then
            Append(new, concat(a));
          fi;
        od;
      else
        new := txt;
      fi;
      ConvertToStringRep(new);
      return new;
    end;
    txt := concat(txt);
    new := "  ";
    ind := "  ";
    n := 0;
    p := 0;
    pos := Position(txt, '\n');
    while pos <> fail do
      Append(new, txt{[p+1..pos]});
      if pos < Length(txt) then
        Append(new, ind);
      fi;
      n := n+1;
      p := pos;
      pos := Position(txt, '\n', p);
    od;
    if p < Length(txt) then
      Append(new, txt{[p+1..Length(txt)]});
    fi;
    return [new, n];
  end;

  # putting the paragraphs together (one string (file) for each chapter)
  files := rec();
  for n in Set(List([2,4..Length(l)], i-> l[i-1][1])) do
    files.(n) := rec(text := "", ssnr := [], linenr := [], len := 0);
  od;
  for i in [2,4..Length(l)] do
    n := files.(l[i-1][1]);
    p := countandshift(l[i]);
    if Length(n.ssnr)=0 or l[i-1]{[1..3]} <> n.ssnr[Length(n.ssnr)] then
      Add(n.ssnr, l[i-1]{[1..3]});
      Add(n.linenr, n.len+1);
    fi;
    Append(n.text, p[1]);
    n.len := n.len + p[2];
  od;
  
  # - add line numbers to six information
  # - add simplified strings for searching
  # - add hash strings of the latter for links in HTML and PDF version
  #   (such that links remain stable as long as the (sub)section exists
  #   and stays in the same chapter)
  Info(InfoGAPDoc, 1, "#I Producing simplified search strings and labels ",
                      "for hyperlinks . . .\n");
  tmp := ShallowCopy(six);
  SortParallel(List([1..Length(tmp)], i-> [tmp[i][3],i]), tmp);
  for i in [1..Length(six)] do
    a := tmp[i];
    p := Position(files.(a[3][1]).ssnr, a[3]);
    if p = fail then
      Error("don't find subsection ", a[3], " in text documention");
    fi;
    Add(a, files.(a[3][1]).linenr[p]);
    a[6] := SIMPLE_STRING(StripEscapeSequences(a[1]));
    NormalizeWhitespace(a[6]);
    # the 'X' is to start with a proper letter, since this will be used
    # for ID type attributes; we use the same label for all entries with
    # the same subsection number
    if i > 1 and a[3] = tmp[i-1][3] then
      a[7] := tmp[i-1][7];
    else
      a[7] := Concatenation("X", HexStringInt(CrcText(a[6])+2^31), 
                          HexStringInt(CrcText(Reversed(a[6]))+2^31));
    fi;
  od;
  
  return files;
end;

##  <#GAPDoc Label="GAPDoc2Text">
##  <ManSection >
##  <Func Arg="tree[, bibpath][, width]" Name="GAPDoc2Text" />
##  <Returns>record  containing  text  files  as  strings  and  other
##  information</Returns>
##  <Description>
##  The   argument  <A>tree</A>   for   this  function   is  a   tree
##  describing  a   &GAPDoc;  XML   document  as  returned   by  <Ref
##  Func="ParseTreeXMLString"  /> (probably  also  checked with  <Ref
##  Func="CheckAndCleanGapDocTree" />). This function produces a text
##  version of  the document  which can be  used with  &GAP;'s online
##  help (with  the <C>"screen"</C>  viewer, see  <Ref BookName="Ref"
##  Func="SetHelpViewer"  />). It  includes title  page, bibliography
##  and  index. The  bibliography  is made  from BibXMLext or &BibTeX;  
##  databases, see <Ref Chap="ch:bibutil"/>.
##  Their location must be given with the argument <A>bibpath</A> (as
##  string or directory object).<P/>
##  
##  The  output is  a  record  with one  component  for each  chapter
##  (with  names   <C>"0"</C>,  <C>"1"</C>,  ...,   <C>"Bib"</C>  and
##  <C>"Ind"</C>).  Each  such  component  is  again  a  record  with
##  the following components:
##  
##  <List >
##  <Mark><C>text</C></Mark>
##  <Item>the text of the whole chapter as a string</Item>
##  <Mark><C>ssnr</C></Mark>
##  <Item>list of subsection numbers in  this chapter (like <C>[3, 2,
##  1]</C>  for  chapter&nbsp;3,  section&nbsp;2,  subsection&nbsp;1)
##  </Item>
##  <Mark><C>linenr</C></Mark>
##  <Item>corresponding list  of line  numbers where  the subsections
##  start</Item>
##  <Mark><C>len</C></Mark>
##  <Item>number of lines of this chapter</Item>
##  </List>
##  
##  The  result can  be  written  into files  with  the command  <Ref
##  Func="GAPDoc2TextPrintTextFiles" />.<P/>
##  
##  As   a   side   effect    this   function   also   produces   the
##  <F>manual.six</F>  information which  is  used  for searching  in
##  &GAP;'s  online help.  This is  stored in  <C><A>tree</A>.six</C>
##  and   can  be   printed  into   a  <F>manual.six</F>   file  with
##  <Ref   Func="PrintSixFile"  />   (preferably  after   producing  a
##  &LaTeX;  version  of   the  document  as  well   and  adding  the
##  page  number  information  to  <C><A>tree</A>.six</C>,  see  <Ref
##  Func="GAPDoc2LaTeX"   />   and  <Ref   Func="AddPageNumbersToSix"
##  />).<P/>
## 
##  The  text  produced by  this  function  contains some  markup  via
##  ANSI  escape  sequences.  The  sequences  used  here  are  usually
##  ignored by  terminals. But the  &GAP; help system  will substitute
##  them  by  interpreted  color  and attribute  sequences  (see  <Ref
##  Var="TextAttr"/>)  before  displaying  them. There  is  a  default
##  markup  used  for this  but  it  can  also  be configured  by  the
##  user, see <Ref  Func="SetGAPDocTextTheme"/>. Furthermore, the text
##  produced is in UTF-8 encoding.  The encoding is also translated on
##  the fly,  if <C>GAPInfo.TermEncoding</C>  is set to  some encoding
##  supported  by <Ref  Func="Encode"/>, e.g.,  <C>"ISO-8859-1"</C> or
##  <C>"latin1"</C>.<P/>
##  
##  With the optional argument <A>width</A> a different length of the
##  output text lines can be chosen.  The default is 76 and all lines
##  in the resulting text start with two spaces. This looks good on a
##  terminal with a standard width  of 80 characters and you probably
##  don't want to use this argument.
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##  
# the basic call, used recursively with a result r from GetElement 
# and a string str or list l to which the output should be appended
# arg: r[, linelength]       (then a list is returned, only for whole document)
# or:  r, str[, linelength]  (then the output is appended to string or
#                             list str)
InstallGlobalFunction(GAPDoc2Text, function(arg)
  local   r,  str,  linelength,  name;
  r := arg[1];
  if Length(arg)=2 and (IsList(arg[2]) or (r.name = "WHOLEDOCUMENT" and
                                                 IsDirectory(arg[2]))) then
    str := arg[2];
    linelength := 76;
  elif Length(arg)=2 and IsInt(arg[2]) then
    linelength := arg[2];
    str := "";
  elif Length(arg)=3 then
    str := arg[2];
    linelength := arg[3];
  else
    str := "";
    linelength := 76;
  fi;
  
  if r.name = "WHOLEDOCUMENT" then
    r.linelength := linelength;
    r.indent := "";
    if IsDirectory(str) then
      r.bibpath := str;
    else
      if Length(str) = 0 then
        str := ".";
      fi;
      r.bibpath := Directory(str);
    fi;
    str := "";
  fi;
  
  name := r.name;
  if not IsBound(GAPDoc2TextProcs.(name)) then
    Info(InfoGAPDoc, 1, "#W WARNING: Don't know how to process element ", name, 
          " ---- ignored\n");
  else
    GAPDoc2TextProcs.(r.name)(r, str);
  fi;
  
  if r.name = "WHOLEDOCUMENT" then
    # generate sorted list of counts from .six entries, makes LaTeX and HTML
    # converter much faster on large documents
    r.sixcount := List(r.six, a-> a[3]);
    r.sixindex := [1..Length(r.six)];
    SortParallel(r.sixcount, r.sixindex);

    # put final record together and return it, also add line numbers to
    # .six entries
    return GAPDoc2TextProcs.PutFilesTogether(str, r.six);  
  fi;

  return str;
end);

##  recursion through the tree and formatting paragraphs
BindGlobal("GAPDoc2TextContent", function(r, l)
  local tmp, par, cont, count, s, a;
  
  # inline needed Alt elements
  if IsList(r.content) and 
                 ForAny(r.content, a-> IsRecord(a) and a.name = "Alt") then
    tmp := r.content;
    r := ShallowCopy(r);
    r.content := [];
    for a in tmp do
      if IsRecord(a) and a.name = "Alt" then
        if GAPDoc2TextProcs.AltYes(a) then
          Append(r.content, a.content);
        fi;
      else
        Add(r.content, a);
      fi;
    od;
  fi;

  # utility: append counter and formatted paragraph to l
  par := function(s, name)
    local sn, ss, pos;
    # extra call for each part until a forced line break
    pos := Position(s, '\004');
    sn := "";
    while pos <> 0 do
      if pos = fail then
        ss := s;
        pos := 0;
      else
        ss := s{[1..pos-1]};
        s := s{[pos+1..Length(s)]};
        pos := Position(s, '\004');
      fi;
      Append(sn, FormatParagraph(ss, r.root.linelength - Length(r.root.indent),
                               "both", [r.root.indent, ""], WidthUTF8String));
    od;
    if Length(sn)>0 then
      GAPDoc2TextProcs.P(0, sn);
      Add(l, count);
      Add(l, sn);
    fi;
  end;

  # if not containing paragraphs, then l is string to append to
  if not r.name in GAPDoc2TextProcs.ParEls then
    for a in r.content do
      GAPDoc2Text(a, l);
    od;
    return;
  fi;
  
  # otherwise we have to collect text and paragraphs
  cont := r.content;
  count := r.count;
  s := "";
  for a in cont do
    if a.count <> count  then
      par(s, a.name);
      count := a.count;
      s := "";
    fi;
    if a.name in GAPDoc2TextProcs.ParEls then
      # recursively collect paragraphs
      GAPDoc2Text(a, l);
    else 
      # collect text for current paragraph
      GAPDoc2Text(a, s);
    fi;
  od;
  if Length(s)>0 then
    par(s, 0);
  fi;
end);

  
##  write head and foot of Txt file.
GAPDoc2TextProcs.WHOLEDOCUMENT := function(r, par)
  local i, pi, t, el, str, dat, datbt, bib, b, keys, need, labels, 
        tmp, pos, j, diff, text, stream, a, ansi;
  
  ##  add paragraph numbers to all nodes of the document
  AddParagraphNumbersGapDocTree(r);
  
  ##  add a link .root to the root of the document to all nodes
  ##  (then we can collect information about indexing and so on 
  ##  there)
  AddRootParseTree(r);
  r.index := [];
  r.toc := "";
  r.labels := rec();
  r.labeltexts := rec();
  r.bibkeys := [];
  # and the .six information for the online help index
  # will contain pairs [string, [chap, sect, subsect]]
  r.six := [];
  
  ##  checking for processing instructions before the book starts
  ##  example:  <?Txt option1="value1" ?>
  i := 1;
  pi := rec();
  while not r.content[i].name = "Book" do
    if r.content[i].name = "XMLPI" then
      t := r.content[i].content;
      if Length(t) > 3 and t{[1..4]} = "Txt " then
        el := GetSTag(Concatenation("<", t, ">"), 2);
        for a in NamesOfComponents(el.attributes) do
          pi.(a) := el.attributes.(a);
        od;
      fi;
    fi;
    i := i+1;
  od;
  
  ##  Now the actual work starts, we give the processing instructions found
  ##  so far to the Book handler.
  ##  We call the Book handler twice and produce index, bibliography, toc
  ##  in between.
  Info(InfoGAPDoc, 1, "#I First run, collecting cross references, ",
        "index, toc, bib and so on . . .\n");
  # with this flag we avoid unresolved references warnings in first run
  GAPDoc2TextProcs.FirstRun := true;
  GAPDoc2TextProcs.Book(r.content[i], "", pi);
  GAPDoc2TextProcs.FirstRun := false;
  
  # now the toc is ready
  Info(InfoGAPDoc, 1, "#I Table of contents complete.\n");
  r.toctext := r.toc;
  
  # .index has entries of form [sorttext, subsorttext, numbertext, 
  #  entrytext, count[, subtext]]
  Info(InfoGAPDoc, 1, "#I Producing the index . . .\n");
  Sort(r.index);
  str := "";
  for a in r.index do
    Append(str, a[4]);
    if IsBound(a[6]) then
      Append(str, ", ");
      Append(str, a[6]);
    elif Length(a[2])>0 then
      Append(str, ", ");
      Append(str, a[2]);
    fi;
    Append(str, "  ");
    Append(str, a[3]);
    Add(str, '\n');
  od;
  r.indextext := str;
  
  if Length(r.bibkeys) > 0 then
    GAPDocAddBibData(r);
    Info(InfoGAPDoc, 1, "#I Writing bibliography . . .\n");
    need := List(r.bibentries, a-> RecBibXMLEntry(a, "Text", r.bibstrings));
    # copy the unique labels
    for a in [1..Length(need)] do
      need[a].printedkey := r.biblabels[a];
    od;
    text := "";
    ansi := rec(
               Bib_author := GAPDoc2TextProcs.TextAttr.BibAuthor,
               Bib_title := GAPDoc2TextProcs.TextAttr.BibTitle,
               Bib_journal := GAPDoc2TextProcs.TextAttr.BibJournal,
               Bib_volume := GAPDoc2TextProcs.TextAttr.BibVolume,
               Bib_Label := GAPDoc2TextProcs.TextAttr.BibLabel,
               Bib_reset := GAPDoc2TextProcs.TextAttr.BibReset);
    for a in need do
      Append(text, StringBibAsText(a, ansi));
    od;
    r.bibtext := text;
  fi;
  
  # second run
  r.six := [];
  r.index := [];
  Info(InfoGAPDoc, 1, "#I Second run through document . . .\n");
  GAPDoc2TextProcs.Book(r.content[i], par, pi);
  # adding .six entries from index
  for a in r.index do
    if Length(a[2]) > 0 then
      Add(r.six, [Concatenation(a[4], " ", a[2]), a[3], a[5]]);
    else
      Add(r.six, [a[4],  a[3], a[5]]);
    fi;
  od;
  
  ##  remove the links to the root  ???
##    RemoveRootParseTree(r);
end;

##  comments and processing instructions are in general ignored
GAPDoc2TextProcs.XMLPI := function(r, str)
  return;
end;
GAPDoc2TextProcs.XMLCOMMENT := function(r, str)
  return;
end;

# do nothing with Ignore
GAPDoc2TextProcs.Ignore := function(arg)
end;

# just process content ??? putting together here
GAPDoc2TextProcs.Book := function(r, par, pi)
  r.root.Name := r.attributes.Name;
  GAPDoc2TextContent(r, par);
end;

##  Body is sectioning element
GAPDoc2TextProcs.Body := GAPDoc2TextContent;

##  the title page,  the most complicated looking function
GAPDoc2TextProcs.TitlePage := function(r, par)
  local   strn,  l,  s,  a,  aa,  cont,  ss, st, stmp, ind, len;
  
  strn := "\n\n";
  
  # the .six entry 
  Add(r.root.six, [GAPDocTexts.d.Titlepage, 
          GAPDoc2TextProcs.SectionNumber(r.count, "Subsection"),
          r.count{[1..3]}]);
  
  # title
  l := Filtered(r.content, a-> a.name = "Title");
  s := "";
  GAPDoc2TextContent(l[1], s);
  s := FormatParagraph(WrapTextAttribute(s, GAPDoc2TextProcs.TextAttr.Heading),
                       r.root.linelength, "center", WidthUTF8String);
  Append(strn, s);
  Append(strn, "\n\n");
  
  # subtitle
  l := Filtered(r.content, a-> a.name = "Subtitle");
  if Length(l)>0 then
    s := "";
    GAPDoc2TextContent(l[1], s);
    s := FormatParagraph(WrapTextAttribute(s,GAPDoc2TextProcs.TextAttr.Heading),
                     r.root.linelength, "center", WidthUTF8String);
    Append(strn, s);
    Append(strn, "\n\n");
  fi;
  
  # version
  l := Filtered(r.content, a-> a.name = "Version");
  if Length(l)>0 then
    s := "";
    GAPDoc2TextContent(l[1], s);
    while Length(s)>0 and s[Length(s)] in  WHITESPACE do
      Unbind(s[Length(s)]);
    od;
    s := FormatParagraph(s, r.root.linelength, "center", WidthUTF8String);
    Append(strn, s);
    Append(strn, "\n\n");
  fi;

  # date
  l := Filtered(r.content, a-> a.name = "Date");
  if Length(l)>0 then
    s := "";
    GAPDoc2TextContent(l[1], s);
    s := FormatParagraph(s, r.root.linelength, "center", WidthUTF8String);
    Append(strn, s);
    Append(strn, "\n\n");
  fi;

  # author name(s)
  l := Filtered(r.content, a-> a.name = "Author");
  for a in l do
    s := "";
    aa := ShallowCopy(a);
    aa.content := Filtered(a.content, b-> 
                  not b.name in ["Email", "Homepage", "Address"]);
    GAPDoc2TextContent(aa, s);
    s := FormatParagraph(s, r.root.linelength, "center", WidthUTF8String);
    Append(strn, s);
    Append(strn, "\n");
  od;
  Append(strn, "\n\n");
  
  # short comment for front page
  l := Filtered(r.content, a-> a.name = "TitleComment");
  if Length(l)>0 then
    # format narrower than later text
    st := "";
    r.root.linelength := r.root.linelength - 10;
    s := r.root.indent;
    r.root.indent := Concatenation(s, "          ");
    GAPDoc2TextContent(l[1], st);
    r.root.indent := s;
    r.root.linelength := r.root.linelength + 10;
    Append(strn, st);
    Append(strn, "\n\n");
  fi;
  
  # email and WWW-homepage of author(s), if given
  l := Filtered(r.content, a-> a.name = "Author");
  for a in l do
    cont := List(a.content, b-> b.name);
    if "Email" in cont or "Homepage" in cont then
      s := "";
      aa := ShallowCopy(a);
      aa.content := Filtered(a.content, b-> not b.name in 
                            ["Email", "Homepage", "Address"]);
      GAPDoc2TextContent(aa, s);
      NormalizeWhitespace(s);
      Append(strn, s);
      
      if "Email" in cont then
        Append(strn, Concatenation("\n    ", GAPDocTexts.d.Email, ":    "));
        GAPDoc2Text(a.content[Position(cont, "Email")], strn);
      fi;
      if "Homepage" in cont then
        Append(strn, "\n");
        Append(strn, Concatenation("    ", GAPDocTexts.d.Homepage, ": "));
        GAPDoc2Text(a.content[Position(cont, "Homepage")], strn);
      fi;
      if "Address" in cont then
        Append(strn, "\n");
        stmp := "";
        ind := a.root.indent;
        len := Length(GAPDocTexts.d.Address);
        a.root.indent := Concatenation(ind, RepeatedString(' ', len+7));
        GAPDoc2TextContent(a.content[Position(cont, "Address")], stmp);
        a.root.indent := ind;
        stmp[2]{Length(ind)+[1..len+7]} := Concatenation("    ", 
                                         GAPDocTexts.d.Address, ":  ");
        Append(strn, stmp);
      fi;
      Append(strn, "\n");
    fi;
  od;

  # if an address is given outside the <Author> element(s)
  l := Filtered(r.content, a-> a.name = "Address");
  if Length(l) > 0 then
    Append(strn, "\n\n");
    stmp := "";
    ind := r.root.indent;
    len := Length(GAPDocTexts.d.Address);
    r.root.indent := Concatenation(ind, RepeatedString(' ', len+2));
    GAPDoc2TextContent(l[1], stmp);
    r.root.indent := ind;
    stmp[2]{Length(ind)+[1..len+2]} := Concatenation(GAPDocTexts.d.Address, 
                                                     ": ");
    Append(strn, stmp);
  fi; 
  
  Append(strn, "\n-------------------------------------------------------\n");
  
  Add(par, r.count);
  Add(par, strn);
  
  # abstract, copyright page, acknowledgements, colophon
  for ss in ["Abstract", "Copyright", "Acknowledgements", "Colophon" ] do
    l := Filtered(r.content, a-> a.name = ss);
    if Length(l)>0 then
      # the .six entry 
      Add(r.root.six, [GAPDocTexts.d.(ss), 
              GAPDoc2TextProcs.SectionNumber(l[1].count, "Subsection"),
              l[1].count{[1..3]}]);
      Add(par, l[1].count);
      Add(par, Concatenation(WrapTextAttribute(GAPDocTexts.d.(ss),
                 GAPDoc2TextProcs.TextAttr.Heading), "\n"));
      GAPDoc2TextContent(l[1], par);
      Append(par[Length(par)], 
             "\n-------------------------------------------------------\n");
    fi;
  od;
end;

##  these produce text for an URL
##  arg:  r, str[, pre]
GAPDoc2TextProcs.Link := GAPDoc2TextContent;
GAPDoc2TextProcs.LinkText := GAPDoc2TextContent;
GAPDoc2TextProcs.URL := function(arg)
  local r, str, pre, rr, txt, s;
  r := arg[1];
  str := arg[2];
  if Length(arg)>2 then
    pre := arg[3];
  else
    pre := "";
  fi;
  rr := First(r.content, a-> a.name = "LinkText");
  if rr <> fail then
    txt := "";
    GAPDoc2TextContent(rr, txt);
    rr := First(r.content, a-> a.name = "Link");
    if rr = fail then
      Info(InfoGAPDoc, 1, "#W missing <Link> element for text ", txt, "\n");
      s := "MISSINGLINK";
    else
      s := "";
      GAPDoc2TextContent(rr, s);
    fi;
  else
    s := "";
    GAPDoc2TextContent(r, s);
    if IsBound(r.attributes.Text) then
      txt := r.attributes.Text;
    else
      txt := s;
    fi;
  fi;
  NormalizeWhitespace(s);
  NormalizeWhitespace(txt);
  pre := WrapTextAttribute(Concatenation(pre, s),
                             GAPDoc2TextProcs.TextAttr.URL);
  if txt=s then
    Append(str, pre);
  else
    Append(str, Concatenation(txt, " (", pre, ")"));
  fi;
end;

GAPDoc2TextProcs.Homepage := GAPDoc2TextProcs.URL;

GAPDoc2TextProcs.Email := function(r, str)
  # we add the 'mailto://' phrase
  GAPDoc2TextProcs.URL(r, str, "mailto:");
end;

GAPDoc2TextProcs.Address := function(r, str)
  # just process the text
  GAPDoc2TextContent(r, str);
end;

##  utility: generate a chapter or (sub)section-number string 
GAPDoc2TextProcs.SectionNumber := function(count, sect)
  local   res;
  res := "";
  if IsString(count[1]) or count[1]>0 then
    Append(res, String(count[1]));
  else
    res := "";
  fi;
  if sect="Chapter" or sect="Appendix" then
    return res;
  fi;
  Add(res, '.');
  if count[2]>0 then
    Append(res, String(count[2]));
  fi;
  if sect="Section" then
    return res;
  fi;
  if count[3]>0 then
    Append(res, Concatenation("-", String(count[3])));
  fi;
  if sect="Par" then
    Append(res, Concatenation(".", String(count[4])));
    return res;
  fi;
  # default is SubSection or ManSection number
  return res;
end;

  
##  the sectioning commands are just translated and labels are
##  generated, if given as attribute
GAPDoc2TextProcs.ChapSect := function(r, par, sect)
  local   num,  posh,  s,  ind, strn, sm;
  
  # section number as string
  num := GAPDoc2TextProcs.SectionNumber(r.count, sect);
  
  # the heading
  posh := Position(List(r.content, a-> a.name), "Heading");
  if posh <> fail then      
    s := "";
    # first the .six entry
    GAPDoc2TextProcs.Heading1(r.content[posh], s);
    # reset to heading markup where overwritten
    sm := SubstitutionSublist(s, GAPDoc2TextProcs.TextAttr.reset,
               Concatenation(GAPDoc2TextProcs.TextAttr.reset,
                             GAPDoc2TextProcs.TextAttr.Heading));
    Add(r.root.six, [NormalizedWhitespace(FormatParagraph(sm,
                 r.root.linelength, [GAPDoc2TextProcs.TextAttr.Heading,
                 GAPDoc2TextProcs.TextAttr.reset], WidthUTF8String)), 
                 num, r.count{[1..3]}]);
    
    # label entry, if present
    if IsBound(r.attributes.Label) then
      r.root.labels.(r.attributes.Label) := num;
      r.root.labeltexts.(r.attributes.Label) := s;
    fi;
  
    # the heading text
    sm := Concatenation(num, " ", sm);
    Add(par, r.count);
    # here we assume that r.indent = ""
    Add(par, Concatenation("\n", FormatParagraph(sm,
                 r.root.linelength, [GAPDoc2TextProcs.TextAttr.Heading,
                   GAPDoc2TextProcs.TextAttr.reset], WidthUTF8String), "\n"));
    
    # table of contents entry
    if sect="Section" then 
      ind := "  ";
    elif sect="Subsection" then
      ind := "    ";
    else
      ind := "";
    fi;
    # here s without heading markup
    Append(r.root.toc, FormatParagraph(Concatenation(num, " ", s),
            r.root.linelength-Length(ind), "left", [ind, ""],
            WidthUTF8String));
  fi;
  
  # the actual content
  GAPDoc2TextContent(r, par);
end;

##  this really produces the content of the heading
GAPDoc2TextProcs.Heading1 := function(r, str)
  local a;
  a := "";
  GAPDoc2TextContent(r, a);
  Append(str, a[2]);
end;
##  and this ignores the heading (for simpler recursion)
GAPDoc2TextProcs.Heading := function(r, str)
end;

GAPDoc2TextProcs.Chapter := function(r, par)
  GAPDoc2TextProcs.ChapSect(r, par, "Chapter");
end;

GAPDoc2TextProcs.Appendix := function(r, par)
  GAPDoc2TextProcs.ChapSect(r, par, "Appendix");
end;

GAPDoc2TextProcs.Section := function(r, par)
  GAPDoc2TextProcs.ChapSect(r, par, "Section");
end;

GAPDoc2TextProcs.Subsection := function(r, par)
  GAPDoc2TextProcs.ChapSect(r, par, "Subsection");
end;

##  table of contents, just puts "TOC" in first run
GAPDoc2TextProcs.TableOfContents := function(r, par)
  # the .six entry 
  Add(r.root.six, [GAPDocTexts.d.TableofContents, 
          GAPDoc2TextProcs.SectionNumber(r.count, "Subsection"),
          r.count{[1..3]}]);

  Add(par, r.count);
  if IsBound(r.root.toctext) then
    Add(par, Concatenation("\n\n", GAPDoc2TextProcs.TextAttr.Heading,
        GAPDocTexts.d.Contents," (", r.root.Name, ")", 
        GAPDoc2TextProcs.TextAttr.reset, "\n\n", r.root.toctext,
        "\n\n-------------------------------------------------------\n"));
  else
    Add(par,"TOC\n-----------\n");
  fi;
end;

##  bibliography, just "BIB" in first run, store databases in root
GAPDoc2TextProcs.Bibliography := function(r, par)
  local   s;
  # .six entries
  s := GAPDoc2TextProcs.SectionNumber(r.count, "Chapter");
  Add(r.root.six, [GAPDocTexts.d.Bibliography, s, r.count{[1..3]}]);
  if GAPDocTexts.d.Bibliography <> GAPDocTexts.d.References then
    Add(r.root.six, [GAPDocTexts.d.References, s, r.count{[1..3]}]);  
  fi;
  
  r.root.bibdata := r.attributes.Databases;
  Add(par, r.count);
  if IsBound(r.root.bibtext) then
    Add(par, Concatenation("\n\n", GAPDoc2TextProcs.TextAttr.Heading,
          GAPDocTexts.d.References, GAPDoc2TextProcs.TextAttr.reset, 
          "\n\n", r.root.bibtext,
          "\n\n-------------------------------------------------------\n"));
  else
    Add(par,"BIB\n-----------\n");
  fi;
end;

##  inside <M> element we don't want this filtering
GAPDoc2TextProcs.PCDATANOFILTER := function(r, str)
  Append(str, r.content);
end;

## default is with filter ???changed???
GAPDoc2TextProcs.PCDATAFILTER := GAPDoc2TextProcs.PCDATANOFILTER;
GAPDoc2TextProcs.PCDATA := GAPDoc2TextProcs.PCDATAFILTER;

##  end of paragraph (end with double newline)
GAPDoc2TextProcs.P := function(r, str)
  local   l,  i;
  l := Length(str);
  if l>0 and str[l] <> '\n' then
    Append(str, "\n\n");
  elif l>1 and str[l-1] <> '\n' then
    Add(str, '\n');
  else
    # remove too many line breaks
    i := l-2;
    while i>0 and str[i] = '\n' do
      Unbind(str[i+2]);
      i := i-1;
    od;
  fi;
end;

##  end of line, same as with .P, but no empty line 
GAPDoc2TextProcs.Br := function(r, str)
  # we use character \004 to mark forced line breaks, used in 'par' above
  Add(str, '\004');
##    GAPDoc2TextProcs.P(r, str);
##    if Length(str) > 0 and str[Length(str)] = '\n' then
##      Unbind(str[Length(str)]);
##    fi;
end;

##  wrapping text attributes
GAPDoc2TextProcs.WrapAttr := function(r, str, a)
  local   s;
  s := "";
  GAPDoc2TextContent(r, s);
  Append(str, WrapTextAttribute(s, GAPDoc2TextProcs.TextAttr.(a)));
end;

##  GAP keywords 
GAPDoc2TextProcs.K := function(r, str)
  GAPDoc2TextProcs.WrapAttr(r, str, "K");
end;

##  verbatim GAP code
GAPDoc2TextProcs.C := function(r, str)
  GAPDoc2TextProcs.WrapAttr(r, str, "C");
end;

##  file names
GAPDoc2TextProcs.F := function(r, str)
  GAPDoc2TextProcs.WrapAttr(r, str, "F");
end;

##  argument names (same as Arg)
GAPDoc2TextProcs.A := function(r, str)
  GAPDoc2TextProcs.WrapAttr(r, str, "Arg");
end;

##  simple maths, here we try to substitute TeX command to something which
##  looks ok in text mode
GAPDoc2TextProcs.M := function(r, str)
  local s;
  s := "";
  GAPDoc2TextContent(r, s);
  s := TextM(s);
  Append(str, s);
end;


##  in Txt this is shown in TeX format
GAPDoc2TextProcs.Math := function(r, str)
  Add(str, '$');
  GAPDoc2TextProcs.PCDATA := GAPDoc2TextProcs.PCDATANOFILTER;
  GAPDoc2TextContent(r, str);
  GAPDoc2TextProcs.PCDATA := GAPDoc2TextProcs.PCDATAFILTER;
  Add(str, '$');
end;

##  displayed maths (also in TeX format, but printed as  paragraph enclosed
##  by "\[" and "\]")
GAPDoc2TextProcs.Display := function(r, par)
  local   s;
  s := "";
  GAPDoc2TextProcs.PCDATA := GAPDoc2TextProcs.PCDATANOFILTER;
  GAPDoc2TextContent(r, s);
  GAPDoc2TextProcs.PCDATA := GAPDoc2TextProcs.PCDATAFILTER;
  s := Concatenation(Filtered(s, IsString));
#  s := StripBeginEnd(s, "\n");
#  s := Concatenation("\\[\n", s, "\n\\]\n\n");
  s := FormatParagraph(s, r.root.linelength - 10 - Length(r.root.indent), 
          "left", [Concatenation(r.root.indent, "     "), ""], WidthUTF8String);
##    s := Concatenation("\\[\n", s, "\\]\n\n");
  s := Concatenation("\n", s, "\n\n");
  Add(par, r.count);
  Add(par, s);
end;

##  emphazised text
GAPDoc2TextProcs.Emph := function(r, str)
  GAPDoc2TextProcs.WrapAttr(r, str, "Emph");
end;

##  quoted text
GAPDoc2TextProcs.Q := function(r, str)
  Append(str, "\"");
  GAPDoc2TextContent(r, str);
  Append(str, "\"");
end;

##  Package names
GAPDoc2TextProcs.Package := function(r, str)
  GAPDoc2TextProcs.WrapAttr(r, str, "Package");
end;

##  menu items
GAPDoc2TextProcs.B := function(r, str)
  GAPDoc2TextProcs.WrapAttr(r, str, "B");
end;

GAPDoc2TextProcs.ExampleLike := function(r, par, label)
  local   str,  cont,  a,  s, len, l1;
  len := r.root.linelength - Length(r.root.indent) - 10;
  str := Concatenation(r.root.indent, GAPDoc2TextProcs.TextAttr.Example);
  if Length(label) = 0 then
    Append(str, RepeatedString('-', len));
  else
    l1 := RepeatedString('-', Int((len - 4 - Length(label)) / 2));
    Append(str, Concatenation(l1, "  ", label, "  ", l1));
    while Length(str) < len + Length(GAPDoc2TextProcs.TextAttr.Example) do
      Add(str, '-');
    od;
  fi;
  Append(str, GAPDoc2TextProcs.TextAttr.reset);
  Add(str, '\n');
  cont := "";
  for a in r.content do 
    # here we try to avoid reformatting
    if IsString(a.content) then
      Append(cont, a.content); 
    else
      s := "";
      GAPDoc2Text(a, s);
      Append(cont, s);
    fi;
  od;
  cont := SplitString(cont, "\n", "");
  # delete first line, if whitespace only
  if Length(cont) > 0 and ForAll(cont[1], x-> x in WHITESPACE) then
    cont := cont{[2..Length(cont)]};
  fi;
  cont := Concatenation(List(cont, a-> Concatenation(r.root.indent, 
                        "  ", GAPDoc2TextProcs.TextAttr.Example, a, 
                        GAPDoc2TextProcs.TextAttr.reset, "\n")));
  Append(str, cont);
  Append(str, Concatenation(r.root.indent,
                            GAPDoc2TextProcs.TextAttr.Example, 
                            RepeatedString('-', len), 
                            GAPDoc2TextProcs.TextAttr.reset, "\n\n"));
  Add(par, r.count);
  Add(par, str);
end;

##  log of session and GAP code is typeset the same way as <Example>
GAPDoc2TextProcs.Example := function(r, par)
  GAPDoc2TextProcs.ExampleLike(r, par, GAPDocTexts.d.Example);
end;
GAPDoc2TextProcs.Log := function(r, par)
  GAPDoc2TextProcs.ExampleLike(r, par, GAPDocTexts.d.Log);
end;
GAPDoc2TextProcs.Listing := function(r, par)
  if IsBound(r.attributes.Type) then
    GAPDoc2TextProcs.ExampleLike(r, par, r.attributes.Type);
  else
    GAPDoc2TextProcs.ExampleLike(r, par, "");
  fi;
end;

##  Verb is without any formatting
GAPDoc2TextProcs.Verb := function(r, par)
  local cont, s, pos, a;
  cont := "";
  for a in r.content do 
    # here we try to avoid reformatting
    if IsString(a.content) then
      Append(cont, a.content); 
    else
      s := "";
      GAPDoc2Text(a, s);
      Append(cont, s);
    fi;
  od;
  # delete first line if it contains only whitespace
  pos := Position(cont, '\n');
  if pos <> fail and ForAll(cont{[1..pos]}, x-> x in WHITESPACE) then
    cont := cont{[pos+1..Length(cont)]};
  fi;
  # adjust trailing newlines
  GAPDoc2TextProcs.P(0, cont);
  Append(par, [r.count, cont]);
end;

##  explicit labels
GAPDoc2TextProcs.Label := function(r, str)
  r.root.labels.(r.attributes.Name) :=
    GAPDoc2TextProcs.SectionNumber(r.count, "Subsection");
end;

##  citations
GAPDoc2TextProcs.Cite := function(r, str)
  local   key,  pos;
  key := r.attributes.Key;
  pos := Position(r.root.bibkeys, key);
  if pos = fail then
    Add(r.root.bibkeys, key);
    Append(str, Concatenation("[?", key, "?]"));
  elif  not IsBound(r.root.biblabels) then
    Append(str, Concatenation("[?", key, "?]"));
  else
    Append(str, Concatenation("[", r.root.biblabels[pos]));
    if IsBound(r.attributes.Where) then
      Append(str, ", ");
      Append(str, r.attributes.Where);
    fi;
    Add(str, ']');
  fi;
end;

##  explicit index entries
GAPDoc2TextProcs.Subkey := GAPDoc2TextContent;
GAPDoc2TextProcs.Index := function(r, str)
  local s, sub, entry, a;
  
  s := "";
  sub := "";
  for a in r.content do
    if a.name = "Subkey" then
      GAPDoc2Text(a, sub);
    else
      GAPDoc2Text(a, s);
    fi;
  od;
  NormalizeWhitespace(s);
  NormalizeWhitespace(sub);
  if IsBound(r.attributes.Key) then
    entry := [STRING_LOWER(r.attributes.Key)];
  else
    entry := [STRING_LOWER(StripEscapeSequences(s))];
  fi;
  if IsBound(r.attributes.Subkey) then
    Add(entry, r.attributes.Subkey);
  else
    Add(entry, STRING_LOWER(StripEscapeSequences(sub)));
  fi;
  Add(entry, GAPDoc2TextProcs.SectionNumber(r.count, "Subsection"));
  Add(entry, s);
  Add(entry, r.count{[1..3]});
  if Length(sub) > 0 then
    Add(entry, sub);
  fi;
  Add(r.root.index, entry);
end;
      
##  this produces an implicit index entry and a label entry
GAPDoc2TextProcs.LikeFunc := function(r, par, typ)
  local   str,  s,  name,  lab,  i;
  s := Concatenation(GAPDoc2TextProcs.TextAttr.Func, "> ", r.attributes.Name);
  if IsBound(r.attributes.Arg) then
    Append(s, "( "); 
    Append(s, GAPDoc2TextProcs.TextAttr.reset);
    Append(s, GAPDoc2TextProcs.TextAttr.Arg);
    Append(s, NormalizedArgList(r.attributes.Arg));
    Append(s, GAPDoc2TextProcs.TextAttr.reset);
    Append(s, GAPDoc2TextProcs.TextAttr.Func);
    Append(s, " ) ");
  fi;
  # label (if not given, the default is the Name)
  if IsBound(r.attributes.Label) then
    lab := Concatenation(" (", r.attributes.Label, ")");
  else
    lab := "";  
  fi;
  GAPDoc2TextProcs.Label(rec(count := r.count, attributes := rec(Name
              := Concatenation(r.attributes.Name, lab)), root := r.root), par); 
  # index entry
  name := r.attributes.Name;
  Add(r.root.index, [STRING_LOWER(name), "", 
          GAPDoc2TextProcs.SectionNumber(r.count, "Subsection"), 
          Concatenation(GAPDoc2TextProcs.TextAttr.Func, name, 
                        GAPDoc2TextProcs.TextAttr.reset, lab),
          r.count{[1..3]}]);
  # some hint about the type of the variable
  for i in [WidthUTF8String(StripEscapeSequences(s))+1 .. 
          r.root.linelength - Length(typ)] do
    Add(s, '_');
  od;
  Append(s, Concatenation(GAPDoc2TextProcs.TextAttr.reset, typ, "\n"));
  Add(par, r.count);
  Add(par, s);
end;

GAPDoc2TextProcs.Func := function(r, str)
  GAPDoc2TextProcs.LikeFunc(r, str, GAPDocTexts.d.Func);
end;

GAPDoc2TextProcs.Oper := function(r, str)
  GAPDoc2TextProcs.LikeFunc(r, str, GAPDocTexts.d.Oper);
end;

GAPDoc2TextProcs.Meth := function(r, str)
  GAPDoc2TextProcs.LikeFunc(r, str, GAPDocTexts.d.Meth);
end;

GAPDoc2TextProcs.Filt := function(r, str)
  # r.attributes.Type could be "representation", "category", ...
  if IsBound(r.attributes.Type) then
    GAPDoc2TextProcs.LikeFunc(r, str, r.attributes.Type);
  else
    GAPDoc2TextProcs.LikeFunc(r, str, GAPDocTexts.d.Filt);
  fi;
end;

GAPDoc2TextProcs.Prop := function(r, str)
  GAPDoc2TextProcs.LikeFunc(r, str, GAPDocTexts.d.Prop);
end;

GAPDoc2TextProcs.Attr := function(r, str)
  GAPDoc2TextProcs.LikeFunc(r, str, GAPDocTexts.d.Attr);
end;

GAPDoc2TextProcs.Var := function(r, str)
  GAPDoc2TextProcs.LikeFunc(r, str, GAPDocTexts.d.Var);
end;

GAPDoc2TextProcs.Fam := function(r, str)
  GAPDoc2TextProcs.LikeFunc(r, str, GAPDocTexts.d.Fam);
end;

GAPDoc2TextProcs.InfoClass := function(r, str)
  GAPDoc2TextProcs.LikeFunc(r, str, GAPDocTexts.d.InfoClass);
end;

##  using the HelpData(.., .., "ref") interface
GAPDoc2TextProcs.ResolveExternalRef := function(bookname,  label, nr)
  local info, match, res;
  info := HELP_BOOK_INFO(bookname);
  if info = fail then
    return fail;
  fi;
  match := Concatenation(HELP_GET_MATCHES(info, SIMPLE_STRING(label), true));
  if Length(match) < nr then
    return fail;
  fi;
  res := HELP_BOOK_HANDLER.(info.handler).HelpData(info, match[nr][2], "ref");
  res[1] := SubstitutionSublist(res[1], " (not loaded): ", ": ", "one");
  return res;
end;

GAPDoc2TextProcs.Ref := function(r, str)
  local   funclike,  int,  txt,  ref,  lab,  sectlike;
  
  # function like cases
  funclike := [ "Func", "Oper", "Meth", "Filt", "Prop", "Attr", "Var", 
                "Fam", "InfoClass" ];
  int := Intersection(funclike, NamesOfComponents(r.attributes));
  if Length(int)>0 then
    txt := r.attributes.(int[1]);
    if IsBound(r.attributes.Label) then
      lab := Concatenation(txt, " (", r.attributes.Label, ")");
    else
      lab := txt;
    fi;
    if IsBound(r.attributes.BookName) then
      ref := GAPDoc2TextProcs.ResolveExternalRef(r.attributes.BookName, lab, 1);
      if ref = fail then
        if GAPDoc2TextProcs.FirstRun <> true then
          Info(InfoGAPDoc, 1, "#W WARNING: non resolved reference: ",
                            r.attributes, "\n");
        fi;
        ref := Concatenation(lab, "???");
      else
        # the search text for online help including book name
        ref := ref[1];
      fi;
    else
      if IsBound(r.root.labels.(lab)) then
        ref := r.root.labels.(lab);
      else
        if GAPDoc2TextProcs.FirstRun <> true then
          Info(InfoGAPDoc, 1, "#W WARNING: non resolved reference: ",
                            r.attributes, "\n");
        fi;
        ref := Concatenation("???", lab, "???");
      fi;
    fi;
    Append(str, WrapTextAttribute(txt, GAPDoc2TextProcs.TextAttr.Func));
    # add reference by subsection number or text if external, 
    # but only if it does not point to current subsection
    if GAPDoc2TextProcs.SectionNumber(r.count, "Subsection") <> ref then
      Append(str, Concatenation(" (", WrapTextAttribute(ref, 
                   GAPDoc2TextProcs.TextAttr.Ref), ")"));
    fi;
    return;
  fi;
  
  # section like cases
  sectlike := ["Chap", "Sect", "Subsect", "Appendix"];
  int := Intersection(sectlike, NamesOfComponents(r.attributes));
  if Length(int)>0 then
    txt := r.attributes.(int[1]);
    if IsBound(r.attributes.Label) then
      lab := Concatenation(txt, r.attributes.Label);
    else
      lab := txt;
    fi;
    if IsBound(r.attributes.BookName) then
      ref := GAPDoc2TextProcs.ResolveExternalRef(r.attributes.BookName, lab, 1);
      if ref = fail then
        if GAPDoc2TextProcs.FirstRun <> true then
          Info(InfoGAPDoc, 1, "#W WARNING: non resolved reference: ",
                            r.attributes, "\n");
        fi;
        ref := Concatenation(lab, "???");
      else
        # the search text for online help including book name
        ref := Concatenation("'", StripBeginEnd(ref[1], " "), "'");
      fi;
    else
      # with sectioning references Label must be given
      lab := r.attributes.(int[1]);
      # default is printing section number, but we allow a Style="Text"
      # attribute
      if IsBound(r.attributes.Style) and r.attributes.Style = "Text" and
         IsBound(r.root.labeltexts.(lab)) then
        ref := Concatenation("'", StripBeginEnd(
                                  r.root.labeltexts.(lab), WHITESPACE), "'"); 
      elif IsBound(r.root.labels.(lab)) then
        ref := r.root.labels.(lab);
      else
        if GAPDoc2TextProcs.FirstRun <> true then
          Info(InfoGAPDoc, 1, "#W WARNING: non resolved reference: ",
                            r.attributes, "\n");
        fi;
        ref := Concatenation("???", lab, "???");
      fi;
    fi;
    Append(str, WrapTextAttribute(ref, GAPDoc2TextProcs.TextAttr.Ref));
    return;
  fi;
  
  # neutral reference to a label
  lab := r.attributes.Label;
  if IsBound(r.attributes.BookName) then
    ref := GAPDoc2TextProcs.ResolveExternalRef(r.attributes.BookName, lab, 1);
    if ref = fail then
      ref := Concatenation(lab, "???");
    else
      # the search text for online help including book name
      ref := ref[1];
    fi;
  else
    if IsBound(r.root.labels.(lab)) then
      ref := r.root.labels.(lab);
    else
      ref := Concatenation("???", lab, "???");
    fi;
  fi;
  Append(str, WrapTextAttribute(ref, GAPDoc2TextProcs.TextAttr.Ref));
  return;
end;

GAPDoc2TextProcs.Description := function(r, par)
  local l, tmp;
  l := "";
  GAPDoc2TextContent(r, l);
  # Add an empty line in front if not yet there
  if Length(par) > 0 and Length(par[Length(par)]) > 1 then
    tmp := par[Length(par)];
  else
    tmp := "";
  fi;
  if tmp[Length(tmp)-1] <> '\n' then
    Add(tmp, '\n');
  fi;
  Append(par, l);
end;

GAPDoc2TextProcs.Returns := function(r, par)
  local ind, l;
  l := "";
  ind := r.root.indent;
  r.root.indent := Concatenation(ind, "          ");
  GAPDoc2TextContent(r, l);
  if Length(l) > 0 then
    l[2] := Concatenation(l[2]{[1..Length(r.root.indent) - 10]},
              GAPDoc2TextProcs.TextAttr.Returns, GAPDocTexts.d.Returns,":", 
              GAPDoc2TextProcs.TextAttr.reset,
              l[2]{[Length(r.root.indent)-1..Length(l[2])]});
    Append(par, l);
  fi;
  r.root.indent := ind;
end;


GAPDoc2TextProcs.ManSection := function(r, par)
  local   funclike,  i,  num,  s, strn;
  
  # if there is a Heading then handle as subsection
  if ForAny(r.content, a-> IsRecord(a) and a.name = "Heading") then
    GAPDoc2TextProcs.ChapSect(r, par, "Subsection");
    return;
  fi;
  strn := "";
  # function like elements
  funclike := [ "Func", "Oper", "Meth", "Filt", "Prop", "Attr", "Var", 
                "Fam", "InfoClass" ];
  
  # heading comes from name of first function like element
  i := 1;
  while not r.content[i].name in funclike do
    i := i+1;
  od;
  
  num := GAPDoc2TextProcs.SectionNumber(r.count, "Subsection");
  s := Concatenation(num, " ", r.content[i].attributes.Name);
  Add(par, r.count);
  Add(par, Concatenation(WrapTextAttribute(s, 
            GAPDoc2TextProcs.TextAttr.Heading), "\n\n"));
  # append to TOC as subsection
  Append(r.root.toc, Concatenation("    ", s, "\n"));
  GAPDoc2TextContent(r, par);
end;

GAPDoc2TextProcs.Mark := function(r, str)
  GAPDoc2TextProcs.WrapAttr(r, str, "Mark");
  Append(str, "\n");
end;

GAPDoc2TextProcs.Item := function(r, str)
  local   s;
#  s := "";
  s := r.root.indent;
  r.root.indent := Concatenation(s, "      ");
  GAPDoc2TextContent(r, str);
  r.root.indent := s;
#  s:= FormatParagraph(s, r.root.linelength-6, "both", ["      ", ""]);
#  Append(str, s);
end;

# must do the complete formatting 
GAPDoc2TextProcs.List := function(r, par)
  local   s,  a,  ss;
  if "Mark" in List(r.content, a-> a.name) then
    for a in r.content do
      if a.name = "Mark" then
        s := "";
        GAPDoc2TextProcs.Mark(a, s);
        Append(par, [a.count, s]);
      elif a.name = "Item" then
        GAPDoc2TextProcs.Item(a, par);
      fi;
    od;
  else
    for a in Filtered(r.content, a-> a.name = "Item") do
      ss := "";
      GAPDoc2TextProcs.Item(a, ss);
      ss[2]{[1,2]} := "--";
      Append(par, ss);
    od;
  fi;
end;

GAPDoc2TextProcs.Enum := function(r, par)
  local   s,  i,  a,  ss,  num;
  i := 1;
  for a in Filtered(r.content, a-> a.name = "Item") do
    ss := "";
    GAPDoc2TextProcs.Item(a, ss);
    num := Concatenation("(", String(i), ")");
    ss[2]{[1..Length(num)]} := num;
    Append(par, ss);
    i := i+1;
  od;
end;

GAPDoc2TextProcs.TheIndex := function(r, par)
  local   s;
  # .six entry
  s := GAPDoc2TextProcs.SectionNumber(r.count, "Chapter");
  Add(r.root.six, [GAPDocTexts.d.Index, s, r.count{[1..3]}]);
  
  # the text, if available
  Add(par, r.count);
  if IsBound(r.root.indextext) then
    Add(par, Concatenation("\n\n", GAPDoc2TextProcs.TextAttr.Heading,
          GAPDocTexts.d.Index, GAPDoc2TextProcs.TextAttr.reset, 
          "\n\n", r.root.indextext,
          "\n\n-------------------------------------------------------\n"));
  else
    Add(par,"INDEX\n-----------\n");
  fi;
end;

GAPDoc2TextProcs.AltYes := function(r)
  if (not IsBound(r.attributes.Only) and not IsBound(r.attributes.Not)) or
     (IsBound(r.attributes.Only) and 
      "Text" in SplitString(r.attributes.Only, "", " ,"))  or
     (IsBound(r.attributes.Not) and 
     not "Text" in SplitString(r.attributes.Not, "", " ,")) then
    return true;
  else
    return false;
  fi;
end;

GAPDoc2TextProcs.Alt := function(r, str)
  if GAPDoc2TextProcs.AltYes(r) then
    GAPDoc2TextContent(r, str);
  fi;
end;

# copy a few entries with two element names
GAPDoc2TextProcs.E := GAPDoc2TextProcs.Emph;
GAPDoc2TextProcs.Keyword := GAPDoc2TextProcs.K;
GAPDoc2TextProcs.Code := GAPDoc2TextProcs.C;
GAPDoc2TextProcs.File := GAPDoc2TextProcs.F;
GAPDoc2TextProcs.Button := GAPDoc2TextProcs.B;
GAPDoc2TextProcs.Arg := GAPDoc2TextProcs.A;
GAPDoc2TextProcs.Quoted := GAPDoc2TextProcs.Q;
GAPDoc2TextProcs.Par := GAPDoc2TextProcs.P;

# like PCDATA
GAPDoc2TextProcs.EntityValue := GAPDoc2TextProcs.PCDATA;

GAPDoc2TextProcs.Table := function(r, str)
  local cap, align, i, j, z, a, b, t, l, s, d, m;
  if not GAPDoc2TextProcs.AltYes(r) then
    return;
  fi;
  # head part of table and tabular
  if IsBound(r.attributes.Label) then
    r.root.labels.(r.attributes.Label) :=
                    GAPDoc2TextProcs.SectionNumber(r.count, "Subsection");
  fi;
  
  # add spaces as separators of colums if no "|" is given
  a := r.attributes.Align;
  align := "";
  for i in [1..Length(a)-1] do
    if a[i] in "crl" then
      Add(align, a[i]);
      if a[i+1] <> '|' then
        Add(align, ' ');
      fi;
    elif a[i] = '|' then
      Add(align, '|');
    fi;
  od;
  Add(align, a[Length(a)]);
  # make all odd positions separator descriptions
  if not align[1] in " |" then
    align := Concatenation(" ", align);
  fi;
  
  # collect entries
  t := [];
  # the rows of the table
  for a in r.content do 
    if a.name = "Row" then
      z := [];
      b := Filtered(a.content, x-> x.name = "Item");
      for i in [1..Length(align)] do
        if i mod 2 = 1 then
          Add(z, Concatenation(" ", align{[i]}, " "));
        elif IsBound(b[i/2]) then
          l := "";
          GAPDoc2TextProcs.Item(b[i/2], l);
          s := Concatenation(l{[2,4..Length(l)]});
          NormalizeWhitespace(s);
          Add(z, s);
        else
          Add(z, "");
        fi;
      od;
      Add(t, z);
    elif a.name = "HorLine" then
      Add(t, List(align, x-> ""));
    fi;
  od;

  # equalize width of entries in columns
  for i in [2,4..2*QuoInt(Length(align), 2)] do
    a := List(t, b-> WidthUTF8String(StripEscapeSequences(b[i])));
    m := Maximum(a);
    z := "";
    for b in [1..m] do 
      Add(z, ' ');
    od;
    if align[i] = 'r' then
      for j in [1..Length(t)] do
        t[j][i] := Concatenation(z{[1..m-a[j]]}, t[j][i]);
      od;
    elif align[i] = 'l' then
      for j in [1..Length(t)] do
        t[j][i] := Concatenation(t[j][i], z{[1..m-a[j]]});
      od;
    else
      for j in [1..Length(t)] do
        d := m - a[j];
        t[j][i] := Concatenation(z{[1..QuoInt(d, 2)]}, t[j][i], 
                                              z{[1..d - QuoInt(d, 2)]});
      od;
    fi;
  od;

  # put lines together
  for i in [1..Length(t)] do
    if Length(t[i][1])=0 then
      t[i] := ["-"];
    fi;
  od;
  t := List(t, Concatenation);
  a := Maximum(List(t, x-> WidthUTF8String(StripEscapeSequences(x))));
  z := "    ";
  for b in [2..a-1] do 
    Add(z, '-');
  od;
  Add(z, '\n');
  for i in [1..Length(t)] do
    if t[i][1] = '-' then
      t[i] := z;
    else
      t[i] := Concatenation("   ", t[i], "\n");
    fi;
  od;
  t := Concatenation(t);
  Add(t, '\n');

  # the caption, if given
  cap := Filtered(r.content, a-> a.name = "Caption");
  if Length(cap) > 0 then
    s := "";
    GAPDoc2TextProcs.Caption1(cap[1], s);
    Append(t, s);
    Append(t, "\n\n");
  fi;
  Add(str, r.count);
  Add(str, t);
end;

# do nothing, we call .Caption1 directly in .Table
GAPDoc2TextProcs.Caption := function(r, str)
  return;
end;

# here the caption for a table text is produced
GAPDoc2TextProcs.Caption1 := function(r, str)
  local s;
  s := "";
  Append(s, Concatenation(GAPDoc2TextProcs.TextAttr.Heading, 
              GAPDocTexts.d.Table,":",
              GAPDoc2TextProcs.TextAttr.reset, " "));
  GAPDoc2TextContent(r, s);
  Append(str, FormatParagraph(s, r.root.linelength - 10, 
                                "both", ["     ", ""], WidthUTF8String));
end;

##  
##  <#GAPDoc Label="GAPDoc2TextPrintTextFiles">
##  <ManSection >
##  <Func Arg="t[, path]" Name="GAPDoc2TextPrintTextFiles" />
##  <Returns>nothing</Returns>
##  <Description>
##  The  first   argument  must   be  a   result  returned   by  <Ref
##  Func="GAPDoc2Text"/>. The second argument is a path for the files
##  to write, it can be given as string or directory object. The text
##  of  each  chapter is  written  into  a  separate file  with  name
##  <F>chap0.txt</F>,  <F>chap1.txt</F>, ...,  <F>chapBib.txt</F>, and
##  <F>chapInd.txt</F>.<P/>
##  
##  If you want to make your  document accessible via the &GAP; online
##  help  you must  put  at least  these files  for  the text  version
##  into  a  directory,  together  with  the  file  <F>manual.six</F>,
##  see  <Ref Func="PrintSixFile"  />. Then  specify the  path to  the
##  <F>manual.six</F> file in  the packages <F>PackageInfo.g</F> file,
##  see  <Ref  Sect="The PackageInfo.g  File"  BookName="extending"/>.
##  <P/>
##  
##  Optionally you can add the <C>dvi</C>- and <C>pdf</C>-versions of
##  the  document which  are produced  with <Ref  Func="GAPDoc2LaTeX"
##  />   to  this   directory.  The   files  must   have  the   names
##  <F>manual.dvi</F>   and  <F>manual.pdf</F>,   respectively.  Also
##  you  can  add  the  files  of  the  HTML  version  produced  with
##  <Ref   Func="GAPDoc2HTML"  />   to  this   directory,  see   <Ref
##  Func="GAPDoc2HTMLPrintHTMLFiles"  />.  The handler  functions  in
##  &GAP;  for this  help format  detect automatically  which of  the
##  optional formats of a book are actually available.
##  
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##  
# arg: t (result of GAPDoc2Text)[, directory]
InstallGlobalFunction(GAPDoc2TextPrintTextFiles, function(arg)
  local   t, dir, a;
  t := arg[1];
  if Length(arg)>1 then
    dir := arg[2];
    if IsString(dir) then
      dir := Directory(dir);
    fi;
  else
    dir := Directory("");
  fi; 
  
  for a in NamesOfComponents(t) do
    FileString(Filename(dir,Concatenation("chap",a,".txt")), t.(a).text);
  od;
end);

