Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  GAPDoc2Text.gi   Sprache: unbekannt

 
#############################################################################
##
#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 3,  section 2,  subsection 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) > 5 then
      Add(r.six, [Concatenation(a[4], " ", a[6]), a[3], a[5]]);
    elif Length(a[2]) > 0 then
      # subkey given as attribute, no markup
      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, lb, 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
      lb := NormalizedWhitespace(r.attributes.Label);
      r.root.labels.(lb) := num;
      r.root.labeltexts.(lb) := 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.(NormalizedWhitespace(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, " ) ");
  else
    Append(s, " ");
  fi;
  # label (if not given, the default is the Name)
  if IsBound(r.attributes.Label) then
    lab := NormalizedWhitespace(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;
    lab := NormalizedWhitespace(lab);
    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;
    lab := NormalizedWhitespace(lab);
    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 := NormalizedWhitespace(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 := NormalizedWhitespace(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, lb,  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
    lb := NormalizedWhitespace(r.attributes.Label);
    r.root.labels.(lb) := num;
    r.root.labeltexts.(lb) := 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.(NormalizedWhitespace(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
--> --------------------

--> maximum size reached

--> --------------------

[ zur Elbe Produktseite wechseln0.81Quellennavigators  Analyse erneut starten  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge