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


Quelle  Text.gi   Sprache: unbekannt

 
#############################################################################
##
#W  Text.gi                      GAPDoc                          Frank Lübeck
##
##
#Y  Copyright (C)  2000,  Frank Lübeck,  Lehrstuhl D für Mathematik,  
#Y  RWTH Aachen
##
##  The files Text.g{d,i}  contain some utilities for  dealing with text
##  strings.
##  

##  
##  <#GAPDoc Label="CharsColls">
##  <ManSection>
##  <Var Name="WHITESPACE" />
##  <Var Name="CAPITALLETTERS" />
##  <Var Name="SMALLLETTERS" />
##  <Var Name="LETTERS" />
##  <Var Name="DIGITS" />
##  <Var Name="HEXDIGITS" />
##  <Var Name="BOXCHARS" />
##  <Description>
##  These variables contain sets of characters which are useful for
##  text processing. They are defined as follows.<P/>
##  <List >
##  <Mark><C>WHITESPACE</C></Mark>
##  <Item><C>" \n\t\r"</C></Item>
##  <Mark><C>CAPITALLETTERS</C></Mark>
##  <Item><C>"ABCDEFGHIJKLMNOPQRSTUVWXYZ"</C></Item>
##  <Mark><C>SMALLLETTERS</C></Mark>
##  <Item><C>"abcdefghijklmnopqrstuvwxyz"</C></Item>
##  <Mark><C>LETTERS</C></Mark>
##  <Item>concatenation of <C>CAPITALLETTERS</C> and <C>SMALLLETTERS</C></Item>
##  <Mark><C>DIGITS</C></Mark><Item><C>"0123456789"</C></Item>
##  <Mark><C>HEXDIGITS</C></Mark><Item><C>"0123456789ABCDEFabcdef"</C></Item>
##  <Mark><C>BOXCHARS</C></Mark>
##     <Item><Alt Not="LaTeX"><C>"─│┌┬┐├┼┤└┴┘━┃┏┳┓┣╋┫┗┻┛═║╔╦╗╠╬╣╚╩╝"</C></Alt>
##     <Alt Only="LaTeX"><C>Encode(Unicode(9472 + [ 0, 2, 12, 44, 16, 28,
##     60, 36, 20, 52, 24, 1, 3, 15, 51, 19, 35, 75, 43, 23, 59, 27, 80, 81,
##     84, 102, 87, 96, 108, 99, 90, 105, 93 ]), "UTF-8")</C></Alt>, 
##  these are  in UTF-8 encoding,  the <C>i</C>-th unicode  character is
##  <C>BOXCHARS{[3*i-2..3*i]}</C>.</Item>
##  </List>
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##  
InstallValue(WHITESPACE, " \n\t\r");
InstallValue(CAPITALLETTERS, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
IsSet(CAPITALLETTERS);
InstallValue(SMALLLETTERS, "abcdefghijklmnopqrstuvwxyz");
IsSet(SMALLLETTERS);
InstallValue(LETTERS, Concatenation(CAPITALLETTERS, SMALLLETTERS));
IsSet(LETTERS);
InstallValue(DIGITS, "0123456789");
InstallValue(HEXDIGITS, "0123456789ABCDEFabcdef");
InstallValue(BOXCHARS, "─│┌┬┐├┼┤└┴┘━┃┏┳┓┣╋┫┗┻┛═║╔╦╗╠╬╣╚╩╝");

MakeImmutable(WHITESPACE);
MakeImmutable(CAPITALLETTERS);
MakeImmutable(SMALLLETTERS);
MakeImmutable(LETTERS);
MakeImmutable(DIGITS);
MakeImmutable(HEXDIGITS);
MakeImmutable(BOXCHARS);

# utilities to find lines
InstallGlobalFunction(PositionLinenumber, function(str, nr)
  local pos, i;
  pos := 0;
  i := 1;
  while i < nr and pos <> fail do
    pos := Position(str, '\n', pos);
    i := i+1;
  od;
  if i = nr and IsInt(pos) then
    return pos+1;
  else
    return fail;
  fi;
end);
InstallGlobalFunction(NumberOfLines, function(str)
  local pos, i;
  if Length(str) = 0 then
    return 0;
  fi;
  pos := 0;
  i := 1;
  while pos <> fail do
    pos := Position(str, '\n', pos);
    i := i+1;
  od;
  if str[Length(str)] = '\n' then
    return i-2;
  else
    return i-1;
  fi;
end);

##  
##  <#GAPDoc Label="TextAttr">
##  <ManSection>
##  <Var Name="TextAttr" />
##  <Description>
##  The  record  <Ref Var="TextAttr"/>  contains  strings  which can  be
##  printed  to   change  the  terminal  attribute   for  the  following
##  characters. This  only works  with terminals which  understand basic
##  ANSI escape sequences.  Try the following example to see  if this is
##  the case for the terminal you are  using. It shows the effect of the
##  foreground and background color  attributes and of the <C>.bold</C>,
##  <C>.blink</C>, <C>.normal</C>, <C>.reverse</C> and <C>.underscore</C>
##  which can partly be mixed.
##  
##  <Listing Type="Example">
##  extra := ["CSI", "reset", "delline", "home"];;
##  for t in Difference(RecNames(TextAttr), extra) do
##    Print(TextAttr.(t), "TextAttr.", t, TextAttr.reset,"\n");
##  od;
##  </Listing>
##  
##  The suggested defaults for colors <C>0..7</C> are black, red, green,
##  brown, blue,  magenta, cyan,  white. But this  may be  different for
##  your terminal configuration.<P/>
##  
##  The  escape  sequence <C>.delline</C>  deletes  the  content of  the
##  current line and  <C>.home</C> moves the cursor to  the beginning of
##  the current line.
##  
##  <Listing Type="Example">
##  for i in [1..5] do 
##    Print(TextAttr.home, TextAttr.delline, String(i,-6), "\c"); 
##    Sleep(1); 
##  od;
##  </Listing>
##  
##  <Index>UseColorsInTerminal</Index> 
##  Whenever  you  use  this  in   some  printing  routines  you  should
##  make  it optional.  Use  these attributes  only  when 
##  <C>UserPreference("UseColorsInTerminal");</C> returns <K>true</K>.
##  </Description>
##  </ManSection>
##  
##  <#/GAPDoc>
##  
InstallValue(TextAttr, rec());
TextAttr.CSI := "\033[";
TextAttr.reset := Concatenation(TextAttr.CSI, "0m");
TextAttr.normal := Concatenation(TextAttr.CSI, "22m");
TextAttr.bold := Concatenation(TextAttr.CSI, "1m");
TextAttr.underscore := Concatenation(TextAttr.CSI, "4m");
TextAttr.blink := Concatenation(TextAttr.CSI, "5m");
TextAttr.reverse := Concatenation(TextAttr.CSI, "7m");
# foreground colors 0..7 (default: black, red, green, brown, blue, magenta,
# cyan, white
TextAttr.0 := Concatenation(TextAttr.CSI, "30m");
TextAttr.1 := Concatenation(TextAttr.CSI, "31m");
TextAttr.2 := Concatenation(TextAttr.CSI, "32m");
TextAttr.3 := Concatenation(TextAttr.CSI, "33m");
TextAttr.4 := Concatenation(TextAttr.CSI, "34m");
TextAttr.5 := Concatenation(TextAttr.CSI, "35m");
TextAttr.6 := Concatenation(TextAttr.CSI, "36m");
TextAttr.7 := Concatenation(TextAttr.CSI, "37m");
# background colors 0..7
TextAttr.b0 := Concatenation(TextAttr.CSI, "40m");
TextAttr.b1 := Concatenation(TextAttr.CSI, "41m");
TextAttr.b2 := Concatenation(TextAttr.CSI, "42m");
TextAttr.b3 := Concatenation(TextAttr.CSI, "43m");
TextAttr.b4 := Concatenation(TextAttr.CSI, "44m");
TextAttr.b5 := Concatenation(TextAttr.CSI, "45m");
TextAttr.b6 := Concatenation(TextAttr.CSI, "46m");
TextAttr.b7 := Concatenation(TextAttr.CSI, "47m");

TextAttr.delline := Concatenation(TextAttr.CSI, "2K");
TextAttr.home := Concatenation(TextAttr.CSI, "1G");

MakeImmutable(TextAttr);

##  <#GAPDoc Label="RepeatedString">
##  <ManSection >
##  <Func Arg="c, len" Name="RepeatedString" />
##  <Func Arg="c, len" Name="RepeatedUTF8String" />
##  <Description>
##  Here <A>c</A> must be either a  character or a string and <A>len</A>
##  is a non-negative number. Then <Ref Func="RepeatedString" /> returns
##  a string of length <A>len</A> consisting of copies of <A>c</A>.
##  <P/>
##  In the variant <Ref Func="RepeatedUTF8String" /> the argument <A>c</A>
##  is considered as string in UTF-8 encoding, and it can also be specified
##  as unicode string or character, see <Ref Oper="Unicode" />. The result is 
##  a string in UTF-8 encoding which has visible width <A>len</A> as explained
##  in <Ref Func="WidthUTF8String"/>. 
##  <Example>
##  gap> RepeatedString('=',51);
##  "==================================================="
##  gap> RepeatedString("*=",51);
##  "*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*"
##  gap> s := "bäh";;
##  gap> enc := GAPInfo.TermEncoding;;
##  gap> if enc <> "UTF-8" then s := Encode(Unicode(s, enc), "UTF-8"); fi;
##  gap> l := RepeatedUTF8String(s, 8);;
##  gap> u := Unicode(l, "UTF-8");;
##  gap> Print(Encode(u, enc), "\n");
##  bähbähbä
##  </Example>
##  </Description>
##  </ManSection>
##  
##  <#/GAPDoc>
InstallGlobalFunction(RepeatedString, function(s, n)
  local res, i;
  res := EmptyString(n+1);
  if n = 0 then
    return res;
  elif IsString(s) and Length(s) > 0 then
    if Length(s) > n then
      for i in [1..n] do
        res[i] := s[i];
      od;
      return s;
    else
      Append(res, s);
    fi;
  elif IsChar(s) then
    res[1] := s;
  else
    Error("First argument must be character or non-empty string\n");
  fi;
  while 2*Length(res) <= n do
    Append(res, res);
  od;
  Append(res, res{[1..n-Length(res)]});
  return res;
end);
InstallGlobalFunction(RepeatedUTF8String, function(s, n)
  local res, w, r, u, tail, i;
  if IsUnicodeCharacter(s) then
    # need to check this first because s is also in IsChar
    s := Encode(Unicode([Int(s)]),"utf8");
  elif IsChar(s) then
    res := "";
    Add(res,s);
    s := res;
  elif IsUnicodeString(s) then
    s := Encode(s, "utf8");
  elif not IsString(s) then
    Error("RepeatedUTF8String: First argument must be character, string \
or unicode \ncharacter or string.\n"); 
  fi;
  w := WidthUTF8String(s);
  if w = 0 then
    if n = 0 then 
      return "";
    else
      Error("RepeatedUTF8String: First argument has width 0.\n"); 
    fi;
  fi;
  r := QuotientRemainder(n, w);
  if r[2] <> 0 then
    u := Unicode(s, "utf8");
    tail := "";
    i := 1;
    while WidthUTF8String(tail) < r[2] do
      Append(tail, Encode(u{[i]}, "utf8"));
      i := i+1;
    od;
  else
    tail := "";
  fi;
  if r[1] = 0 then
    return tail;
  fi;
  r := r[1]*Length(s)+Length(tail);
  res := EmptyString(r);
  Append(res, s);
  while 2*Length(res) <= r do
    Append(res, res);
  od;
  Append(res, res{[1..r-Length(tail)-Length(res)]});
  Append(res, tail);
  return res;
end);


##  <#GAPDoc Label="PositionMatchingDelimiter">
##  <ManSection >
##  <Func Arg="str, delim, pos" Name="PositionMatchingDelimiter" />
##  <Returns>position as integer or <K>fail</K></Returns>
##  <Description>
##  Here <A>str</A> must  be a string and <A>delim</A>  a string with
##  two  different characters.  This function  searches the  smallest
##  position   <C>r</C>  of   the  character   <C><A>delim</A>[2]</C>
##  in   <A>str</A>   such  that   the   number   of  occurrences  of
##  <C><A>delim</A>[2]</C>    in    <A>str</A>   between    positions
##  <C><A>pos</A>+1</C>  and  <C>r</C> is  by  one  greater than  the
##  corresponding number of occurrences of <C><A>delim</A>[1]</C>.<P/>
##  
##  If such an <C>r</C> exists, it is returned. Otherwise <K>fail</K>
##  is returned.
##  
##  <Example>
##  gap> PositionMatchingDelimiter("{}x{ab{c}d}", "{}", 0);
##  fail
##  gap> PositionMatchingDelimiter("{}x{ab{c}d}", "{}", 1);
##  2
##  gap> PositionMatchingDelimiter("{}x{ab{c}d}", "{}", 6);
##  11
##  </Example>
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
InstallGlobalFunction(PositionMatchingDelimiter, function(str, delim, pos)
  local   b,  e,  p,  l,  level;

  b := delim[1];
  e := delim[2];
  
  p := pos+1;
  l := Length(str);
  level := 0;
  while true do
    if p > l then
      return fail;
    elif str[p] = b then
      level := level+1;
    elif str[p] = e then
      if level = 0 then 
        return p;
      else
        level := level-1;
      fi;
    fi;
    p := p+1;
  od;
end);

##  <#GAPDoc Label="SubstitutionSublist">
##  <ManSection >
##  <Func Arg="list, sublist, new[, flag]" Name="SubstitutionSublist" />
##  <Returns>the changed list</Returns>
##  <Description>
##  This function looks for (non-overlapping) occurrences of a sublist
##  <A>sublist</A> in a list <A>list</A> (compare <Ref BookName="ref"
##  Oper="PositionSublist"  />) and  returns a  list where  these are
##  substituted with the list <A>new</A>.<P/>
##  
##  The  optional argument  <A>flag</A>  can  either be  <C>"all"</C>
##  (this is the default if not given) or <C>"one"</C>. In the second
##  case only  the first occurrence of <A>sublist</A> is substituted.
##  <P/>
##  
##  If <A>sublist</A> does not  occur in <A>list</A> then <A>list</A>
##  itself is returned (and not a <C>ShallowCopy(list)</C>).
##  
##  <Example>
##  gap> SubstitutionSublist("xababx", "ab", "a");
##  "xaax"
##  </Example>
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
InstallGlobalFunction(SubstitutionSublist, function(arg)
  local   str,  substr,  lss,  subs,  all,  p,  s, off;
  str := arg[1];
  substr := arg[2];
  lss := Length(substr);
  subs := arg[3];
  if Length(arg)>3 then
    all := arg[4]="all";
  else
    all := true;
  fi;
  
  p := PositionSublist(str, substr);
  if p = fail then 
    # return original object in case of no substitution
    return str; 
  fi;
  s := str{[]};
  off := 1-lss;
  while p<>fail do
    Append(s, str{[off+lss..p-1]});
    Append(s, subs);
##      str := str{[p+lss..Length(str)]};
    off := p;
    if all then
      p := PositionSublist(str, substr, p+lss-1);
    else
      p := fail;
    fi;
    if p=fail then
      Append(s, str{[off+lss..Length(str)]});
    fi;
  od;
  return s;
end);
    

##  <#GAPDoc Label="NumberDigits">
##  <ManSection >
##  <Func Arg="str, base" Name="NumberDigits" />
##  <Returns>integer</Returns>
##  <Func Arg="n, base" Name="DigitsNumber" />
##  <Returns>string</Returns>
##  <Description>
##  The argument  <A>str</A> of  <Ref Func="NumberDigits" />  must be
##  a  string  consisting  only  of an  optional  leading  <C>'-'</C>
##  and characters  in  <C>0123456789abcdefABCDEF</C>,  describing an
##  integer  in  base <A>base</A>  with  <M>2  \leq <A>base</A>  \leq
##  16</M>. This function returns the corresponding integer.<P/>
##  
##  The function <Ref Func="DigitsNumber" /> does the reverse.
##  
##  <Example>
##  gap> NumberDigits("1A3F",16);
##  6719
##  gap> DigitsNumber(6719, 16);
##  "1A3F"
##  </Example>
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##  
InstallGlobalFunction(NumberDigits, function(str, base)
  local   res,  x,  nr, sign;
  res := 0;
  sign := 1;
  for x in str do
    nr := INT_CHAR(x) - 48;
    if nr = -3 then 
      # '-'
      sign := -sign;
    else
      if nr>48 then 
        nr := nr - 39;
      elif nr>15 then
        nr := nr - 7;
      fi;
      res := res*base + nr;
    fi;
  od;
  return sign*res;
end);

InstallGlobalFunction(DigitsNumber, function(n, base)
  local str, s;
  s := "";
  if n<0 then
    Add(s, '-');
    n := -n;
  fi;
  str := "";
  while n <> 0 do
    Add(str, HEXDIGITS[(n mod base) + 1]);
    n := QuoInt(n, base);
  od;
  return Concatenation(s, Reversed(str));
end);

##  <#GAPDoc Label="LabelInt">
##  <ManSection >
##  <Func Arg="n, type, pre, post" Name="LabelInt" />
##  <Returns>string</Returns>
##  <Description>
##  The argument <A>n</A> must be an integer in the range from 1 to 5000,
##  while <A>pre</A> and <A>post</A> must be strings.
##  <P/>
##  The argument <A>type</A> can be one of <C>"Decimal"</C>,
##  <C>"Roman"</C>, <C>"roman"</C>, <C>"Alpha"</C>, <C>"alpha"</C>.
##  <P/>
##  The function returns a string that starts with <A>pre</A>, followed by
##  a decimal, respectively roman number or alphanumerical number literal
##  (capital, respectively small letters), followed by <A>post</A>.
##  <P/>
##  <Example>
##  gap> List([1,2,3,4,5,691], i-> LabelInt(i,"Decimal","","."));
##  [ "1.", "2.", "3.", "4.", "5.", "691." ]
##  gap> List([1,2,3,4,5,691], i-> LabelInt(i,"alpha","(",")"));
##  [ "(a)", "(b)", "(c)", "(d)", "(e)", "(zo)" ]
##  gap> List([1,2,3,4,5,691], i-> LabelInt(i,"alpha","(",")"));
##  [ "(a)", "(b)", "(c)", "(d)", "(e)", "(zo)" ]
##  gap> List([1,2,3,4,5,691], i-> LabelInt(i,"Alpha","",".)"));
##  [ "A.)", "B.)", "C.)", "D.)", "E.)", "ZO.)" ]
##  gap> List([1,2,3,4,5,691], i-> LabelInt(i,"roman","","."));
##  [ "i.", "ii.", "iii.", "iv.", "v.", "dcxci." ]
##  gap> List([1,2,3,4,5,691], i-> LabelInt(i,"Roman","",""));
##  [ "I", "II", "III", "IV", "V", "DCXCI" ]
##  </Example>
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##  
InstallGlobalFunction(LabelInt, function(n, type, pre, post)
  local l1, l2, l3, l, res, r, i;
  if not IsInt(n) or n < 1 or n>5000 then
    return fail;
  fi;
  if type="roman" then
    l1 := ["","i","ii","iii","iv","v","vi","vii","viii","ix"];
    l2 := ["","x","xx","xxx","xl","l","lx","lxx","lxxx","xc"];
    l3 := ["","c","cc","ccc","cd","d","dc","dcc","dccc","cm","m"];
  elif type="Roman" then
    l1 := ["","I","II","III","IV","V","VI","VII","VIII","IX"];
    l2 := ["","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"];
    l3 := ["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM","M"];
  fi;
  if type="alpha" then
    l := LETTERS{[27..52]};
  elif type="Alpha" then
    l := LETTERS{[1..26]};
  fi;
  if type="Decimal" then
    res := String(n);
  elif type in ["roman","Roman"] then
    res := "";
    for i in [1..QuoInt(n,1000)] do
      Append(res, l3[11]);
    od;
    Append(res, l3[QuoInt(n,100) mod 10 + 1]);
    Append(res, l2[QuoInt(n,10) mod 10 + 1]);
    Append(res, l1[n mod 10 + 1]);
  elif type in ["alpha", "Alpha"] then
    if n < 27 then
      res := l{[n]};
    elif n <= 26*27 then
      res := l{[QuoInt(n-1,26),((n-1) mod 26)+1]};
    else
      res := l{[QuoInt(n-27,26*26),
                QuoInt((n-27) mod 26^2-1, 26)+1,((n-1) mod 26)+1]};
    fi;
  fi;
  return Concatenation(pre, res, post);
end);
 
##  <#GAPDoc Label="StripBeginEnd">
##  <ManSection >
##  <Func Arg="list, strip" Name="StripBeginEnd" />
##  <Returns>changed string</Returns>
##  <Description>
##  Here <A>list</A>  and <A>strip</A>  must be lists.  This function
##  returns the  sublist of list  which does not contain  the leading
##  and trailing  entries which are  entries of <A>strip</A>.  If the
##  result  is  equal  to  <A>list</A>  then  <A>list</A>  itself  is
##  returned.
##  
##  <Example>
##  gap> StripBeginEnd(" ,a, b,c,   ", ", ");
##  "a, b,c"
##  </Example>
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##  
InstallGlobalFunction(StripBeginEnd, function(str, chars)
  local   pb,  l,  pe;
  pb := 1;
  l := Length(str);
  while  pb <= l and str[pb] in chars do
    pb := pb + 1;
  od;
  pe := l;
  while pe > 0 and str[pe] in chars do
    pe := pe - 1;
  od;
  if pb > 1 or pe < l then
    return str{[pb..pe]};
  else
    return str;
  fi;
end);


##  <#GAPDoc Label="NormalizedWhitespace">
##  <ManSection >
##  <Func Arg="str" Name="NormalizedWhitespace" />
##  <Returns>new string with white space normalized</Returns>
##  <Description>
##  This  function  gets  a  string  <A>str</A>  and  returns  a  new
##  string  which  is a  copy  of  <A>str</A> with  normalized  white
##  space.  Note  that  the   library  function  <Ref  BookName="ref"
##  Func="NormalizeWhitespace"  />  works in place  and  changes  its
##  argument.
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##  
# moved into GAP library
##  InstallGlobalFunction(NormalizedWhitespace, function(str)
##    local   res;
##    res := ShallowCopy(str);
##    NormalizeWhitespace(res);
##    return res;
##  end);



##  <#GAPDoc Label="WrapTextAttribute">
##  <ManSection >
##  <Func Arg="str, attr" Name="WrapTextAttribute" />
##  <Returns>a string with markup</Returns>
##  <Description>
##  The argument <A>str</A> must be a text as &GAP; string, possibly with 
##  markup by escape sequences as in <Ref Var="TextAttr" />. This function
##  returns a string which is wrapped by the escape sequences <A>attr</A>
##  and <C>TextAttr.reset</C>. It takes care of markup in the given string
##  by appending <A>attr</A> also after each given <C>TextAttr.reset</C> in
##  <A>str</A>.
##  <Example>
##  gap> str := Concatenation("XXX",TextAttr.2, "BLUB", TextAttr.reset,"YYY");
##  "XXX\033[32mBLUB\033[0mYYY"
##  gap> str2 := WrapTextAttribute(str, TextAttr.1);
##  "\033[31mXXX\033[32mBLUB\033[0m\033[31m\027YYY\033[0m"
##  gap> str3 := WrapTextAttribute(str, TextAttr.underscore);
##  "\033[4mXXX\033[32mBLUB\033[0m\033[4m\027YYY\033[0m"
##  gap> # use Print(str); and so on to see how it looks like.
##  </Example>
##  </Description>
##  </ManSection>
##  
##  <#/GAPDoc>
InstallGlobalFunction(WrapTextAttribute, function(str, attr)
  if IsList(attr) and Length(attr) > 0 and IsString(attr[1]) and
     Length(attr[1]) > 1 and attr[1]{[1,2]} = TextAttr.CSI and
                            attr[2] = TextAttr.reset then
    attr := attr[1];
  fi;
  if IsString(attr) and Length(attr) > 1 and attr{[1,2]} = TextAttr.CSI then
    # we mark inner attribute starters by appending a char 23
    str := SubstitutionSublist(str, TextAttr.reset, Concatenation(
                                              TextAttr.reset, attr, "\027"));
    str := Concatenation(attr, str, TextAttr.reset);
  elif IsString(attr) then
    str := Concatenation(attr,str,attr);
  elif IsList(attr) and Length(attr) = 2 and IsString(attr[1]) and
    IsString(attr[2]) then
    str := Concatenation(attr[1], str, attr[2]);
  else
    Error("WrapTextAttribute: argument attr must be string or list of two strings.\n");
  fi;
  return str;
end);

##  <#GAPDoc Label="FormatParagraph">
##  <ManSection >
##  <Func Arg="str[, len][, flush][, attr][, widthfun]" 
##      Name="FormatParagraph" />
##  <Returns>the formatted paragraph as string</Returns>
##  <Description>
##  This function formats a text given  in the string <A>str</A> as a
##  paragraph. The optional arguments have the following meaning:
##  
##  <List >
##  <Mark><A>len</A></Mark>
##  <Item>the length of  the lines of the formatted  text, default is
##  <C>78</C> (counted without a visible length of the strings
##  specified in the <A>attr</A> argument)</Item>
##  <Mark><A>flush</A></Mark>
##  <Item>can  be <C>"left"</C>,  <C>"right"</C>, <C>"center"</C>  or
##  <C>"both"</C>, telling that lines should be flushed left, flushed
##  right, centered or left-right justified, respectively, default is
##  <C>"both"</C></Item>
##  <Mark><A>attr</A></Mark>
##  <Item>is a  list of two strings;  the first is prepended  and the
##  second  appended to  each line  of  the result  (can for  example
##  be  used  for  indenting,  <C>["  ",  ""]</C>,  or  some  markup,
##  <C>[TextAttr.bold,   TextAttr.reset]</C>,   default  is   <C>["",
##  ""]</C>)</Item>
##  <Mark><A>widthfun</A></Mark>
##  <Item>must be a function which returns the display width of text in 
##  <A>str</A>. The default is <C>Length</C> assuming that each byte 
##  corresponds to a character of width one. If <A>str</A> is given in 
##  <C>UTF-8</C> encoding one can use <Ref Func="WidthUTF8String"/> here.
##  </Item>
##  </List>
##  
##  This function  tries to handle  markup with the  escape sequences
##  explained in <Ref Var="TextAttr"/> correctly.
##  
##  <Example>
##  gap> str := "One two three four five six seven eight nine ten eleven.";;
##  gap> Print(FormatParagraph(str, 25, "left", ["/* ", " */"]));           
##  /* One two three four five */
##  /* six seven eight nine ten */
##  /* eleven. */
##  </Example>
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##  
BindGlobal("SPACESTRINGS", [" "]);
# only relevant for HPCGAP, to allow for formatting of help pages in any thread
if IsBound(HPCGAP) then
  MakeThreadLocal("SPACESTRINGS");                                             
fi;                                                                            

InstallGlobalFunction(FormatParagraph, function(arg)
  local   str,  len,  flush,  attr, width, i, words, esc, l, j, k, lw,  
          lines,  s,  ss,  nsp,  res,  a,  new,  qr,  b;
  str := arg[1];
  # default line length
  len := 78;
  # default flush (flush left and right)
  flush := "both";
  # default attribute (empty)
  attr := false;
  # default width function assumes that one byte is one character
  width := Length;
  # scan further arg's
  for i in [2..Length(arg)] do
    if IsInt(arg[i]) then
      len := arg[i];
    elif arg[i] in ["both", "left", "right", "center"] then
      flush := arg[i];
    elif IsList(arg[i]) then
      attr := arg[i];
    elif IsFunction(arg[i]) then
      width := arg[i];
    else
      Error("wrong argument", arg[i]);
    fi;
  od;
  for i in [Length(SPACESTRINGS)+1..len] do
    SPACESTRINGS[i] := Concatenation(SPACESTRINGS[i-1], " ");
  od;
  # we scan the string
  words := [];
  i := 1;
  esc := CHAR_INT(27);
  l := Length(str);
  while i<=l do
    if str[i] in WHITESPACE then
      # delete leading whitespace
      if Length(words)>0 then
        Add(words, 1);
      fi;
      i := i+1;
      while i<=l and str[i] in WHITESPACE do
        i := i+1;
      od;
    elif str[i] = esc then
      # sequences starting with ESC and stopping with the first letter
      # afterwards are not changed and considered to have length zero
      j := i+1;
      while j<=l and not str[j] in SMALLLETTERS and not str[j] in
        CAPITALLETTERS do
        j := j+1;
      od;
      if j>l then
        Error("string end inside escape sequence");
      else
        Add(words, [0, [i..j]]);
      fi;
      i := j+1;
    else
      j := i+1;
      while j<=l and not (str[j] in WHITESPACE or str[j]=esc) do
        j := j+1;
      od;
      if ForAll([i..j-1], k-> IsChar(str[k])) then
        Add(words, [width(str{[i..j-1]}), [i..j-1]]);
      else
        Add(words, [j-i, [i..j-1]]);
      fi;
      i := j;
    fi;
  od;
  # remove trailing white space
  lw := Length(words);
  if lw>0 and IsInt(words[lw]) then
    Unbind(words[lw]);
  fi;
  
  # split into lines
  lines := [];
  i := 1;
  lw := Length(words);
  while i <= lw do
    # make sure that at least first chunk without space is added to
    # next line (even, if too long)
    s := words[i][1];
    k := i+1;
    while k <= lw and IsList(words[k]) do
      s := s+words[k][1];
      k := k+1;
    od;
    j := k;
    nsp := 0;
    while j <= lw and s+nsp < len do
      if IsInt(words[j]) then
        nsp := nsp+1;
        j := j+1;
      else
        # line breaks only at white space
        ss := s+nsp;
        k := j;
        while k <= lw and IsList(words[k]) do
          ss := ss+words[k][1];
          k := k+1;
        od;
        if s=0 or ss <= len then
          s := ss-nsp;
          j := k;
        else
          break;
        fi;
      fi;
    od;
    if IsInt(words[j-1]) then
      Add(lines, [s,nsp-1,[i..j-2]]);
      i := j;
    else
      Add(lines, [s,nsp,[i..j-1]]);
      i := j+1;
    fi;
  od;
  
  # format lines
  res := "";
  for i in [1..Length(lines)] do
    a := lines[i];
    new := words{a[3]};
    # now fill with spaces
    nsp := len - a[1] - a[2];
    if nsp > 0 then
      if flush = "right" then
        new := Concatenation([nsp], new);
      elif flush = "both" and a[2] > 0 and i < Length(lines) then
        qr := QuotientRemainder(nsp, a[2]);
        for j in [1..Length(new)] do
          if IsInt(new[j]) then
            if qr[2]>0 then
              new[j] := new[j]+qr[1]+1;
              qr[2] := qr[2]-1;
            else
              new[j] := new[j]+qr[1];
            fi;
          fi;
        od;
      elif flush = "center" and nsp > 1 then
        new := Concatenation([QuoInt(nsp,2)], new);
      fi;
    fi;
    # add text attribute begin
    if attr <> false and Length(new)>0 then
      if IsInt(new[1]) then
        new := Concatenation([new[1]], [attr[1]], new{[2..Length(new)]});
      else
        Append(res, attr[1]);
      fi;
    fi;
    s := "";
    for b in new do
      if IsInt(b) then
        Append(s, SPACESTRINGS[b]);
      elif IsString(b) then
        Append(s, b);
      else # range
        Append(s, str{b[2]});
      fi;
    od;
    # add text attribute begin after each text attribute reset (if it
    # is an escape sequence) 
    # and the end attribute
    if attr <> false then
      if Length(attr[1])>2 and attr[1]{[1,2]} = TextAttr.CSI then
        s := SubstitutionSublist(s, TextAttr.reset, attr[1]);
      fi;
      Append(s, attr[2]);
    fi;
    Add(s, '\n');
    Append(res, s);
  od;
##  if PositionSublist(res,"\033[33X")<> fail and PositionSublist(res,"\033[133X")= fail then Error("FP"); fi;
##  if PositionSublist(res,"Emph")<> fail then Error("FP"); fi;
  return res;
end);

##  <#GAPDoc Label="StripEscapeSequences">
##  <ManSection >
##  <Func Arg="str" Name="StripEscapeSequences" />
##  <Returns>string without escape sequences</Returns>
##  <Description>
##  This  function  returns  the  string one  gets  from  the  string
##  <A>str</A> by  removing all escape sequences  which are explained
##  in <Ref Var="TextAttr"/>.  If <A>str</A> does not  contain such a
##  sequence then <A>str</A> itself is returned.
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##  
InstallGlobalFunction(StripEscapeSequences, function(str)
  local   esc,  res,  i,  ls,  p;
  esc := CHAR_INT(27);
  res := "";
  i := 1;
  ls := Length(str);
  while i <= ls do
    if str[i] = esc then
      i := i+1;
      while not str[i] in LETTERS do
        i := i+1;
      od;
      # first letter is last character of escape sequence
      i := i+1; 
      # remove \027 marker of inner escape sequences as well
      if IsBound(str[i]) and str[i] = '\027' then
        i := i+1;
      fi;
    else
      p := Position(str, esc, i);
      if p=fail then
        if i=1 then
          # don't copy if no escape there
          return str;
        else
          Append(res, str{[i..ls]});
          return res;
        fi;
      else
        Append(res, str{[i..p-1]});
        i := p;
      fi;
    fi;
  od;
  return res;
end);
InstallGlobalFunction(SubstituteEscapeSequences, function(str, subs)
  local orig, special, hash, esc, res, i, ls, seq, pos, p, b, e, width, 
        cont, nb, ne, flush, pY, indlen, par, j, row, a, l, n, k, widthfun;

  # maybe we need to simplify some substitution strings because
  # of the current encoding, we cache the result
  if GAPInfo.TermEncoding <> "UTF-8" then
    if IsBound(subs.(GAPInfo.TermEncoding)) then
      subs := subs.(GAPInfo.TermEncoding);
    else
      orig := subs;
      subs := ShallowCopy(subs);
      for a in RecNames(subs) do
        if IsList(subs.(a)) then
          subs.(a) := [subs.(a)[1], List(subs.(a)[2], x-> 
                                Encode(
                                SimplifiedUnicodeString(Unicode(x, "UTF-8"), 
                                GAPInfo.TermEncoding),
                                GAPInfo.TermEncoding))];
        fi;
      od;
      orig.(GAPInfo.TermEncoding) := subs;
    fi;
  fi;
  # we need a special handling of tags to reformat paragraphs and to
  # fill lines
  special := [];
  for a in ["format", "FillString"] do
    Add(special, Position(subs.hash[1], subs.(a)[1][1]));
    Add(special, Position(subs.hash[1], subs.(a)[1][2]));
  od;
  hash := subs.hash;
  esc := CHAR_INT(27);
  res := "";
  i := 1;
  ls := Length(str);
  while i <= ls do
    if str[i] = esc and IsBound(str[i+1]) and str[i+1] = '[' then
      seq := "";
      i := i+1;
      while not str[i] in LETTERS do
        i := i+1;
        Add(seq, str[i]);
      od;
      # first letter is last character of escape sequence
      i := i+1; 
      if IsBound(str[i]) and str[i] = '\027' then
        cont := true;
        i := i+1;
      else
        cont := false;
      fi;
      pos := PositionSet(hash[1], seq);
      if pos <> fail and not pos in special then
        if cont and (Length(hash[2][pos]) = 0 or hash[2][pos][1] <> esc) then
          seq := "";
        else
          seq := hash[2][pos];
        fi;
      else
        Add(res, esc);
        Add(res, '[');
      fi;
      Append(res, seq);
    else
      p := Position(str, esc, i);
      if p=fail then
        if i=1 then
          # don't copy if no escape there
          return str;
        else
          Append(res, str{[i..ls]});
          i := ls+1;
        fi;
      else
        Append(res, str{[i..p-1]});
        i := p;
      fi;
    fi;
  od;
  # now we reformat paragraphs
  if GAPInfo.TermEncoding = "UTF-8" then
    widthfun := WidthUTF8String;
  else
    widthfun := Length;
  fi;
  str := res;
  res := "";
  pos := 0;
  b := Concatenation(TextAttr.CSI, subs.format[1][1]);
  e := Concatenation(TextAttr.CSI, subs.format[1][2]);
  width := SizeScreen()[1] - 2;
  while pos <> fail do
    nb := PositionSublist(str, b, pos);
    if nb = fail then
      Append(res, str{[pos+1..Length(str)]});
      pos := fail;
    else
      ne := PositionSublist(str, e, nb);
      # find flush mode
      if str[nb+Length(b)+2] = '0' then
        flush := subs.flush[2][1];
      elif str[nb+Length(b)+2] = '1' then
        flush := "left";
      elif str[nb+Length(b)+2] = '2' then
        flush := "center";
      else
        flush := "both";
      fi;
      Append(res, str{[pos+1..nb-1]});
      # find indent
      pY := Position(str, 'Y', nb);
      # the +2 because all help text has additional indentation of 2
      indlen := Int(str{[nb+Length(b)+4..pY-1]}) + 2;
      par := FormatParagraph(str{[pY+1..ne-1]}, width - indlen, flush,
                          [RepeatedString(" ", indlen), ""], widthfun);
      # remove leading blanks if there is already something on this line
      # (e.g., initial indentation or a list mark)
      i := Length(res);
      while i > 0 and res[i] <> '\n' do
        i := i-1;
      od;
      i := widthfun(StripEscapeSequences(res{[i+1..Length(res)]}));
      while i > 0 and par[1] = ' ' do
        Remove(par,1);
        i := i-1;
      od;
      if Length(par) > 1 and par[Length(par)] = '\n' then
        Unbind(par[Length(par)]);
      fi;
      Append(res, par);
      pos := ne + Length(e) - 1;
    fi;
  od;
  # a finally we expand the fill strings
  b := Concatenation(TextAttr.CSI, subs.FillString[1][1]);
  if PositionSublist(res, b) <> fail then
    str := res;
    res := "";
    pos := 0;
    nb := PositionSublist(str, b, pos);
    while nb <> fail do
      # find row
      i := nb-1;
      while i>0 and str[i] <> '\n' do
        i := i-1;
      od;
      j := nb+1;
      while Length(str) >= j and str[j] <> '\n' do
        j := j+1;
      od;
      Append(res, str{[pos+1..i]});
      # split row into pieces to fill
      row := [str{[i+1..nb-1]}, str{[nb+Length(b)..j-1]}];
      nb := PositionSublist(row[Length(row)], b);
      while nb <> fail do
        a := row[Length(row)];
        row[Length(row)] := a{[1..nb-1]};
        Add(row, a{[nb+Length(b)..Length(a)]});
        nb := PositionSublist(row[Length(row)], b);
      od;
      # lengths of the fillings
      l := width - Sum(row, a-> widthfun(StripEscapeSequences(a)));
      n := Length(row)-1;
      ls := [];
      for k in [1..n] do
        Add(ls, QuoInt(l,n));
      od;
      k := n;
      while Sum(ls) < l do
        ls[k] := ls[k]+1;
        k := k-1;
      od;
      # cannot do much when line already too long
      if l < 0 then
        ls := 0*ls;
      fi;
      for i in [1..n] do
        Append(res, row[i]);
        Append(res, RepeatedUTF8String(subs.FillString[2][1], ls[i]));
      od;
      Append(res, row[n+1]);
      pos := j-1;
      nb := PositionSublist(str, b, pos);
    od;
    Append(res, str{[pos+1..Length(str)]});
  fi;
  return res;
end);



##  <#GAPDoc Label="WordsString">
##  <ManSection >
##  <Func Arg="str" Name="WordsString" />
##  <Returns>list of strings containing the words</Returns>
##  <Description>
##  This returns  the list of  words of a  text stored in  the string
##  <A>str</A>. All non-letters are considered as word boundaries and
##  are removed.
##  <Example>
##  gap> WordsString("one_two \n    three!?");
##  [ "one", "two", "three" ]
##  </Example>
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
##  
InstallGlobalFunction(WordsString, function(str)
  local   nonletters, wds;
  nonletters := Set("0123456789 \n\r\t\b+*~^\\\"#'`'/?-_.:,;<>|=()[]{}&%$§!");
  wds := SplitString(str, "", nonletters);
  return wds;
end);

# The GAP library will contain a new function CrcString. To make GAPDoc
# running with current/older versions of GAP we use the following helper,
# if necessary with a simple fallback.
if IsBoundGlobal("CrcString") then
  InstallGlobalFunction(CrcText, CrcString); 
else
  InstallGlobalFunction(CrcText, function(s)
    local n, res;
    n := "guckCRCXQWYNVOH";
    FileString(n, s);
    res := CrcFile(n);
    RemoveFile(n);
    return res;
  end);
fi;

##  <#GAPDoc Label="Base64String">
##  <ManSection >
##  <Func Arg="str" Name="Base64String" />
##  <Func Arg="bstr" Name="StringBase64" />
##  <Returns>a string</Returns>
##  <Description>
##  The  first  function  translates  arbitrary   binary  data  given  as  a
##  GAP  string  into   a  <E>base  64</E>  encoded   string.  This  encoded
##  string  contains  only  printable  ASCII   characters  and  is  used  in
##  various  data  transfer  protocols  (<C>MIME</C>  encoded  emails,  weak
##  password   encryption,  ...).   We   use  the   specification  in   <URL
##  Text="RFC 2045">https://www.rfc-editor.org/rfc/rfc2045</URL>.<P/>
##  
##  The second function  has the reverse functionality. Here  we also accept
##  the characters  <C>-_</C> instead of  <C>+/</C> as last  two 
##  characters.  Whitespace is ignored.
##  
##  <Example>
##  gap> b := Base64String("This is a secret!");
##  "VGhpcyBpcyBhIHNlY3JldCEA="
##  gap> StringBase64(b);                       
##  "This is a secret!"
##  </Example>
##  </Description>
##  </ManSection>
##  <#/GAPDoc>
BindGlobal("Base64LETTERS",
          "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
BindGlobal("Base64REVERSE",
[,,,,,,,,,-1,,,-1,,,,,,,,,,,,,,,,,,,-1,,,,,,,,,,,62,,62,,63,52,53,54,55,56,57,58,59,60,61,,,,-2,,,,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,,,,,63,,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51]);

MakeImmutable(Base64LETTERS);
MakeImmutable(Base64REVERSE);

InstallGlobalFunction(Base64String, function(str)
  local istr, pad, i, res, a, d, c, b;
  istr := INTLIST_STRING(str, 1);
  pad := (-Length(istr)) mod 3;
  for i in [1..pad] do
    Add(istr,0);
  od;
  i := 1;
  res := "";
  while i < Length(istr) do
    if i > 1 and i mod 57 = 1 then
      Add(res, '\n');
    fi;
    a := (istr[i]*256+istr[i+1])*256+istr[i+2];
    d := RemInt(a, 64);
    a := (a-d)/64;
    c := RemInt(a, 64);
    a := (a-c)/64;
    b := RemInt(a, 64);
    a := (a-b)/64;
    Append(res, Base64LETTERS{[a,b,c,d]+1});
    i := i+3;
  od;
  if i mod 57 = 1 and pad > 0 then
    Add(res, '\n');
  fi;
  for i in [1..pad] do
    Add(res, '=');
  od;
  return res;
end);
 
InstallGlobalFunction(StringBase64, function(bstr)
  local istr, res, j, n, d, c, a;
  istr := Base64REVERSE{INTLIST_STRING(bstr, 1)};
  res := [];
  j := 0;
  n := 0;
  for a in istr do
    if a <> -1 then
      if a = -2 then
        Unbind(res[Length(res)]);
      else
        n := n*64+a;
        j := j+1;
        if j = 4 then
          d := RemInt(n, 256);
          n := (n-d)/256;
          c := RemInt(n, 256);
          n := (n-c)/256;
          Append(res, [n,c,d]);
          j := 0;
          n := 0;
        fi;
      fi;
    fi;
  od;
  return STRING_SINTLIST(res);
end);



[ zur Elbe Produktseite wechseln0.68Quellennavigators  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