CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

| Download

GAP 4.8.9 installation with standard packages -- copy to your CoCalc project to get it

Views: 418346
#############################################################################
##
#W  GAPDoc2Text.gi                 GAPDoc                        Frank Lübeck
##
##
#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. 
##  
##       the actual themes are in the file TextThemes.g
##  
##  <#GAPDoc Label="SetGAPDocTextTheme">
##  <ManSection >
##  <Func Arg="[optrec1[, optrec2] ...]" Name="SetGAPDocTextTheme" />
##  <Returns>nothing</Returns>
##  <Description>
##  This utility function is for readers  of the screen version of &GAP;
##  manuals which  are generated by  the &GAPDoc; package. It  allows to
##  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 arguments <A>optrec1</A> and so on can be 
##  strings for the  known  name  of  a  theme.  Information about valid 
##  names is shown with <C>SetGAPDocTextTheme("");</C>. <P/>
##  
##  Otherwise, <A>optrec1</A> and so on must be a record. Its entries 
##  overwrite the corresponding entries in the default and in previous 
##  arguments.  To construct valid markup you
##  can  use <Ref  Var="TextAttr"/>.  Entries must  be  either pairs  of
##  strings, which are  put before and after the  corresponding text, or
##  as an  abbreviation it can be  a single string. In  the latter case,
##  the  second string  is implied;  if  the string  contains an  escape
##  sequence the  second string is <C>TextAttr.reset</C>,  otherwise the
##  given string is used. The following components are recognized:
##  
##  <List>
##  <Mark><C>flush</C></Mark><Item><C>"both"</C> for left-right justified
##           paragraphs, and <C>"left"</C> for ragged right ones</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>M</C></Mark><Item>simplified math elements</Item>
##  <Mark><C>Math</C></Mark><Item>normal math elements</Item>
##  <Mark><C>Display</C></Mark><Item>displayed math elements</Item>
##  <Mark><C>Emph</C></Mark><Item>emphasized text</Item>
##  <Mark><C>Q</C></Mark><Item>quoted text</Item>
##  <Mark><C>Ref</C></Mark><Item>reference text</Item>
##  <Mark><C>Prompt</C></Mark><Item>&GAP; prompt in examples</Item>
##  <Mark><C>BrkPrompt</C></Mark><Item>&GAP; break prompt in examples</Item>
##  <Mark><C>GAPInput</C></Mark><Item>&GAP; input in examples</Item>
##  <Mark><C>reset</C></Mark><Item>reset to default, don't change this </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>
##  <Mark><C>BibReset</C></Mark><Item>reset for bibliography, 
##           don't change</Item>
##  <Mark><C>ListBullet</C></Mark><Item>bullet for simple lists (2 
##           visible characters long)</Item>
##  <Mark><C>EnumMarks</C></Mark><Item>one visible character before and
##           after the number in enumerated lists</Item>
##  <Mark><C>DefLineMarker</C></Mark><Item>marker before function and variable
##           definitions (2 visible characters long)</Item>
##  <Mark><C>FillString</C></Mark><Item>for filling in definitions and
##           example separator lines</Item>
##  </List>
##  
##  <Example>
##  gap> # use no colors for GAP examples and 
##  gap> # change display of headings to bold green
##  gap> SetGAPDocTextTheme("noColorPrompt", 
##  >            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", "Q", "M", "Math", 
            "Display", "Prompt", "BrkPrompt", "GAPInput", "GAPOutput", 
            "DefLineMarker", "ListBullet", "EnumMarks", 
            "FillString", "format", "flush" ];
##  We produce a text document with markup that is ignored by terminals,
##  this can be substituted on the fly before display according to the
##  current GAPDocTextTheme.
GAPDoc2TextProcs.TextAttr := rec();
GAPDoc2TextProcs.TextAttr.f := function()
  local i, l, fs;
  fs := GAPDoc2TextProcs.TextAttrFields;
  l := Length(fs);
  # begin and end markers ESC[(i)X and ESC[(i+100)X
  for i in [1..l] do
    GAPDoc2TextProcs.TextAttr.(fs[i]) := 
        [Concatenation(TextAttr.CSI, String(i-1), "X"),
         Concatenation(TextAttr.CSI, String(100+i-1), "X")];
  od;
end;
GAPDoc2TextProcs.TextAttr.f();
Unbind(GAPDoc2TextProcs.TextAttr.f);

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", "Constr", "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);

##  to format paragraph which can be reformatted before display
GAPDoc2TextProcs.MarkAndFormat := function(str, flush, indlen, len)
  local f, fnr, res, par;
  # only handle non-empty strings
  if flush = "auto" then
    f := "both";
    fnr := 0;
  elif flush = "left" then
    f := flush;
    fnr := 1;
  elif flush = "center" then
    f := flush;
    fnr := 2;
  fi;
  par := FormatParagraph(str, len - indlen, f,
                        [RepeatedString(" ", indlen), ""], WidthUTF8String);
  if Length(par) = 0 then
    return "";
  fi;
  res := Concatenation(TextAttr.CSI,String(fnr),";",String(indlen),"Y",
          par{[indlen+1..Length(par)-1]});
  res := Concatenation(par{[1..indlen]}, WrapTextAttribute(res,
            GAPDoc2TextProcs.TextAttr.format), "\n");
  return res;
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, GAPDoc2TextProcs.MarkAndFormat(ss, "auto", 
                                  Length(r.root.indent), r.root.linelength));
    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, k, l, ind;
  
  ##  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");
  SortBy(r.index, a-> [a[1],STRING_LOWER(a[2]),
                       List(SplitString(a[3],".-",""), Int)]);
  str := "";
  ind := r.index;
  k := 1;
  while k <= Length(ind) do
    if k > 1 and ind[k][4] = ind[k-1][4] then
      Append(str, "    ");
    else
      Append(str, ind[k][4]);
    fi;
    if IsBound(ind[k][6]) then
      if k = 1 or ind[k][4] <> ind[k-1][4] then
        Append(str, ", ");
      fi;
      Append(str, ind[k][6]);
    elif Length(ind[k][2]) > 0 then
      if k = 1 or ind[k][4] <> ind[k-1][4] then
        Append(str, ", ");
      fi;
      Append(str, ind[k][2]);
    fi;
    l := k;
    while l <= Length(ind) and ind[l][4] = ind[k][4] and
          ((IsBound(ind[k][6]) and IsBound(ind[l][6])
            and ind[k][6] = ind[l][6]) or
           (not IsBound(ind[k][6]) and not IsBound(ind[l][6])
           and ind[k][2] = ind[l][2]))  do
      Append(str, Concatenation(" ", ind[l][3], " "));
      l := l+1;
    od;
    Append(str, "\n");
    k := l;
  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].key := 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[1]);
    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, sms, t;
  
  # 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 if overwritten by inner elements
    sm := s;
    if PositionSublist(s{[5..Length(s)-8]}, "\033[1") <> fail then
      for t in [ "K", "B", "M", "C", "F", "Func", "Math", "Emph", "Q", 
                 "Package", "Arg" ] do
        sm := SubstitutionSublist(sm, GAPDoc2TextProcs.TextAttr.(t)[2],
               Concatenation(GAPDoc2TextProcs.TextAttr.(t)[2],
                             GAPDoc2TextProcs.TextAttr.Heading[2],"\027",
                             GAPDoc2TextProcs.TextAttr.Heading[1],"\027"));
      od;
    fi;
    # in six entry we don't want the extra indent on reformatting
    sms := SubstitutionSublist(sm, "\033[0;0Y", "\033[0;-2Y");
    Add(r.root.six, [WrapTextAttribute(NormalizedWhitespace(sms),
                 GAPDoc2TextProcs.TextAttr.Heading),
                 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, 
                 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, where;
  a := "";
  GAPDoc2TextContent(r, a);
  if a = "" then
    if IsBound(r.root) and IsBound(r.root.inputorigins) then
      where := OriginalPositionDocument(r.root.inputorigins,r.start);
      where := Concatenation("(file ",where[1],
                             ", line ",String(where[2]),")");
    else
      where := "";
    fi;
    Error("Empty <Heading> element is not supported ",where);
    return;
  fi;
  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)
  local s;
  # 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
    s := WrapTextAttribute(Concatenation(GAPDocTexts.d.Contents," (",
         r.root.Name, ")"), GAPDoc2TextProcs.TextAttr.Heading);
    Add(par, Concatenation("\n\n", s, "\n\n", r.root.toctext,
        "\n\n", GAPDoc2TextProcs.TextAttr.FillString[1],"\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;
  if IsBound(r.attributes.Style) then
    r.root.bibstyle := r.attributes.Style;
  fi;
  Add(par, r.count);
  if IsBound(r.root.bibtext) then
    Add(par, Concatenation("\n\n", WrapTextAttribute(GAPDocTexts.d.References, 
          GAPDoc2TextProcs.TextAttr.Heading), 
          "\n\n", r.root.bibtext,
          "\n\n", GAPDoc2TextProcs.TextAttr.FillString[1], "\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.WrapAppend := function(str, s, a)
  Append(str, WrapTextAttribute(s, GAPDoc2TextProcs.TextAttr.(a)));
end;
GAPDoc2TextProcs.WrapAttr := function(r, str, a)
  local   s;
  s := "";
  GAPDoc2TextContent(r, s);
  GAPDoc2TextProcs.WrapAppend(str, s, a);
end;

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

##  verbatim GAP code
GAPDoc2TextProcs.C := function(r, str)
  local s;
  s := "";
  GAPDoc2TextContent(r, s);
  # <A> elements are allowed inside <C>, so maybe we need to repeat the markup
  # (with a char 23 such that it can be ignored in case of visible markup
  # substitution)
  s := SubstitutionSublist(s, GAPDoc2TextProcs.TextAttr.Arg[2], 
                               Concatenation(GAPDoc2TextProcs.TextAttr.Arg[2],
                                      GAPDoc2TextProcs.TextAttr.C[1],"\027"));
  GAPDoc2TextProcs.WrapAppend(str, s, "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);
  GAPDoc2TextProcs.WrapAppend(str, s, "M");
end;

##  in Txt this is shown in TeX format
GAPDoc2TextProcs.Math := function(r, str)
  local s;
  s := "";
  GAPDoc2TextProcs.PCDATA := GAPDoc2TextProcs.PCDATANOFILTER;
  GAPDoc2TextContent(r, s);
  GAPDoc2TextProcs.PCDATA := GAPDoc2TextProcs.PCDATAFILTER;
  GAPDoc2TextProcs.WrapAppend(str, s, "Math");
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));
  if IsBound(r.attributes.Mode) and r.attributes.Mode = "M" then
    s := TextM(s);
  fi;
  s := WrapTextAttribute(s, GAPDoc2TextProcs.TextAttr.Display);
  # change the indentation value in formatting escape sequence
  s := SubstitutionSublist(s, "\033[0;0Y", "\033[0;6Y", "one");
  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)
  GAPDoc2TextProcs.WrapAttr(r, str, "Q");
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.AddColorPromptMarkup := function(cont)
  local res, pos, s;
  res := [];
  for s in cont do
    if Length(s) > 4 and s{[1..5]} = "gap> " then
      Add(res, Concatenation(WrapTextAttribute("gap>", 
                             GAPDoc2TextProcs.TextAttr.Prompt),
                             " ", WrapTextAttribute(s{[6..Length(s)]},
                             GAPDoc2TextProcs.TextAttr.GAPInput)));
    elif Length(s) > 1 and s{[1,2]} = "> " then
      Add(res, Concatenation(WrapTextAttribute(">", 
                             GAPDoc2TextProcs.TextAttr.Prompt),
                             " ", WrapTextAttribute(s{[3..Length(s)]},
                             GAPDoc2TextProcs.TextAttr.GAPInput)));
    elif Length(s) > 2 and s{[1..3]} = "brk" then
      pos := Position(s, ' ');
      if pos <> fail then
        Add(res, Concatenation(WrapTextAttribute(s{[1..pos-1]}, 
                             GAPDoc2TextProcs.TextAttr.BrkPrompt),
                             " ", WrapTextAttribute(s{[pos+1..Length(s)]},
                             GAPDoc2TextProcs.TextAttr.GAPInput)));
      else
        Add(res, WrapTextAttribute(s, GAPDoc2TextProcs.TextAttr.BrkPrompt));
      fi;
    else
      Add(res, WrapTextAttribute(s, GAPDoc2TextProcs.TextAttr.GAPOutput));
    fi;
  od;
  return res;
end;

GAPDoc2TextProcs.ExampleLike := function(r, par, label)
  local   str,  cont,  a,  s, len, l1;
  if Length(label) = 0 then
    str := Concatenation(r.root.indent, 
                         GAPDoc2TextProcs.TextAttr.FillString[1]);
  else
    str := Concatenation(r.root.indent,
                         GAPDoc2TextProcs.TextAttr.FillString[1],
                         "  ", label, "  ",
                         GAPDoc2TextProcs.TextAttr.FillString[1]);
  fi;
  str := WrapTextAttribute(str, GAPDoc2TextProcs.TextAttr.Example);
  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;
  # color prompt markup in <Example> and <Log> elements
  if label in [GAPDocTexts.d.Example, GAPDocTexts.d.Log] then
    cont := GAPDoc2TextProcs.AddColorPromptMarkup(cont);
  fi;
  cont := Concatenation(List(cont, a-> Concatenation(r.root.indent, 
                       "  ", 
                       WrapTextAttribute(a, GAPDoc2TextProcs.TextAttr.Example),
                       "\n")));
  Append(str, cont);
  Append(str, Concatenation(r.root.indent,
                 WrapTextAttribute(GAPDoc2TextProcs.TextAttr.FillString[1], 
                 GAPDoc2TextProcs.TextAttr.Example), 
                 "\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;
      
##  helper to add markup to the args
GAPDoc2TextProcs.WrapArgs := function(argstr)
  local res, noletter, c;
  res := "";
  noletter := true;
  for c in argstr do
    if noletter then
      if not c in ", []" then
        noletter := false;
        Append(res, GAPDoc2TextProcs.TextAttr.Arg[1]);
      fi;
    elif c in ", []" then
      noletter := true;
      Append(res, GAPDoc2TextProcs.TextAttr.Arg[2]);
    fi;
    Add(res, c);
  od;
  if not noletter then
    Append(res, GAPDoc2TextProcs.TextAttr.Arg[2]);
  fi;
  return res;
end;

##  this produces an implicit index entry and a label entry
GAPDoc2TextProcs.LikeFunc := function(r, par, typ)
  local   str,  s,  name,  lab, comma, i, entry;
  # for very long lines we allow left flushed paragraph formatting
  s := Concatenation(GAPDoc2TextProcs.TextAttr.format[1], "\033[1;0Y", 
         GAPDoc2TextProcs.TextAttr.DefLineMarker[1],
         WrapTextAttribute(r.attributes.Name, GAPDoc2TextProcs.TextAttr.Func));
  if IsBound(r.attributes.Arg) then
    Append(s, "( "); 
    Append(s, GAPDoc2TextProcs.WrapArgs(NormalizedArgList(r.attributes.Arg)));
    Append(s, " ) ");
  fi;
  # label (if not given, the default is the Name)
  if IsBound(r.attributes.Label) then
    lab := r.attributes.Label;
    comma := ", ";
  else
    lab := "";  
    comma := "";
  fi;
  GAPDoc2TextProcs.Label(rec(count := r.count, attributes := rec(Name
       := Concatenation(r.attributes.Name, comma, lab)), root := r.root), par);
  # index entry
  name := r.attributes.Name;
  entry := [STRING_LOWER(name), "", 
            GAPDoc2TextProcs.SectionNumber(r.count, "Subsection"), 
            WrapTextAttribute(name, GAPDoc2TextProcs.TextAttr.Func),
            r.count{[1..3]}];
  if Length(lab) > 0 then
    entry[2] := STRING_LOWER(StripEscapeSequences(lab));
    Add(entry, lab);
  fi;
  Add(r.root.index, entry);
  # some hint about the type of the variable
  Append(s, GAPDoc2TextProcs.TextAttr.FillString[1]);
  Append(s, Concatenation(" ",typ,GAPDoc2TextProcs.TextAttr.format[2],"\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.Constr := function(r, str)
  GAPDoc2TextProcs.LikeFunc(r, str, GAPDocTexts.d.Constr);
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 := GetHelpDataRef(info, match[nr][2]);
  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", "Constr", "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 l, ind, lr, pos;
  l := "";
  ind := r.root.indent;
  r.root.indent := Concatenation(ind, "          ");
  GAPDoc2TextContent(r, l);
  if Length(l) > 0 then
    lr := Length(GAPDocTexts.d.Returns)+1;
    pos := PositionSublist(l[2], RepeatedString(" ",lr), Length(ind));
    l[2] := Concatenation(l[2]{[1..pos-1]},
              WrapTextAttribute(Concatenation(GAPDocTexts.d.Returns,":"),
                 GAPDoc2TextProcs.TextAttr.Returns),
              l[2]{[pos+lr..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", "Constr", "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"));

  # 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;

  GAPDoc2TextContent(r, par);
end;

GAPDoc2TextProcs.Mark := function(r, str)
  local s;
  s := "";
  GAPDoc2TextProcs.WrapAttr(r, s, "Mark");
  # allow for <C> and <A> elements in <Mark>
  s := SubstitutionSublist(s, GAPDoc2TextProcs.TextAttr.Arg[2], 
                               Concatenation(GAPDoc2TextProcs.TextAttr.Arg[2],
                               GAPDoc2TextProcs.TextAttr.Mark[1],"\027"));
  s := SubstitutionSublist(s, GAPDoc2TextProcs.TextAttr.C[2], 
                               Concatenation(GAPDoc2TextProcs.TextAttr.C[2],
                               GAPDoc2TextProcs.TextAttr.Mark[1],"\027"));
  Append(str, r.root.indent);
  Append(str, NormalizedWhitespace(s));
  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, ss, pos, a, i, start;
  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);
      if ss <> "" then # ignore empty <Item>
        # insert bullet
        start := ss[2]{[1..Length(r.root.indent)]};
        ss[2] := ss[2]{[Length(r.root.indent)+1..Length(ss[2])]};
        for i in [1..2] do
          Remove(ss[2], Position(ss[2],' '));
        od;
        ss[2] := Concatenation(start,
                               GAPDoc2TextProcs.TextAttr.ListBullet[1], ss[2]);
        Append(par, ss);
      fi;
    od;
  fi;
end;

GAPDoc2TextProcs.Enum := function(r, par)
  local i, ss, num, a, j, start;
  i := 1;
  for a in Filtered(r.content, a-> a.name = "Item") do
    ss := "";
    GAPDoc2TextProcs.Item(a, ss);
    # merge in the counter
    start := ss[2]{[1..Length(r.root.indent)]};
    ss[2] := ss[2]{[Length(r.root.indent)+1..Length(ss[2])]};
    for j in [1..Length(String(i))+2] do
      Remove(ss[2], Position(ss[2],' '));
    od;
    num := WrapTextAttribute(String(i), GAPDoc2TextProcs.TextAttr.EnumMarks);
    ss[2] := Concatenation(start, num, ss[2]);
    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", 
          WrapTextAttribute(GAPDocTexts.d.Index, 
                          GAPDoc2TextProcs.TextAttr.Heading), 
          "\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 a, align, bc, t, z, b, l, s, pos, lens, m, d, ind, hline, cap, i, j;
  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
  # first, add a dummy character at the end of the input to 
  # simplify handling of the last position
  a := Concatenation(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;
  # make all odd positions separator descriptions
  if not align[1] in " |" then
    align := Concatenation(" ", align);
  fi;
  
  # box characters
  bc := List([1..11], i-> BOXCHARS{[3*i-2..3*i]});
  # 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
          if align[i] = '|' then
            Add(z, Concatenation(" ", bc[2], " "));
          else
            Add(z, Concatenation(" ", align{[i]}, " "));
          fi;
        elif IsBound(b[i/2]) then
          l := "";
          GAPDoc2TextProcs.Item(b[i/2], l);
          s := Concatenation(l{[2,4..Length(l)]});
          NormalizeWhitespace(s);
          # do not reformat paragraphs later
          pos := PositionSublist(s, GAPDoc2TextProcs.TextAttr.format[1]);
          if pos = 1 then
            s := s{[Position(s, 'Y')+1..PositionSublist(s, 
                                   GAPDoc2TextProcs.TextAttr.format[2])-1]};
          fi;
          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
  lens := [];
  for i in [2,4..2*QuoInt(Length(align), 2)] do
    a := List(t, b-> WidthUTF8String(StripEscapeSequences(
              SubstituteEscapeSequences(b[i], GAPDocTextTheme))));
    m := Maximum(a);
    lens[i] := m;
    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);
  hline := function(t,l,m,r)
    local z, i, j;
    # Process first column
    z := "    ";
    if align[1] = '|' then
      Append(z, l);
      Append(z, t);
    else
      Append(z, "  ");
    fi;
    # Process all columns excluding the first and last one
    for i in [2..Length(align)-1] do
      if i mod 2 = 0 then
        for j in [1..lens[i]] do
          Append(z, t);
        od;
      elif align[i] = '|' then
        Append(z, t);
        Append(z, m);
        Append(z, t);
      else
        Append(z, "   ");
      fi;
    od;
    # Process last column
    if align[Length(align)] = '|' then
      Append(z, t);
      Append(z, r);
    else
      Append(z, "  ");
    fi;
    Add(z, '\n');
    return z;
  end;
  for i in [1..Length(t)] do
    if t[i][1] = '-' then
      if i = 1 then
        t[i] := hline(bc[1],bc[3],bc[4],bc[5]);
      elif i = Length(t) then
        t[i] := hline(bc[1],bc[9],bc[10],bc[11]);
      else
        t[i] := hline(bc[1],bc[6],bc[7],bc[8]);
      fi;
    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 := Concatenation(GAPDocTexts.d.Table,":");
  s := WrapTextAttribute(s,GAPDoc2TextProcs.TextAttr.Heading);
  Append(s, " ");
  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="reference"/>.
##  <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);