#!/usr/bin/perl -w

# Write out a hash as text, read in and parse text files. Internal usage only.

# (c) Copyright by Tels 1999-2000. All Rights Reserved. Do NOT distribute.
# Updates and support on bloodgate.com/thief

# 2000-01-18 v0.0.1 - from Bunch
# 2000-01-28 v0.0.2 - added asText

package Cowgen::Text;
require Exporter;
@ISA     = qw(Exporter);
@EXPORT  = qw(
           );
@EXPORT_OK = qw(getStyle setStyle fromText asText);

use strict 'vars';

#my $open = "%s%n\n%s%i{\n"; 
# other variant: 
my $open = "%s%n {\n";
my $close = "%s%i}\n";
my $comment = ' # ';
my $indend = '  ';
my $error = "";

sub fromText
  {
  # read one (some) bunch(s) from a textfile and merge them to meself
  # used as Cowgen::Bunch->create("name","textfile");
  my ($file,$levels,$map) = @_;
  my ($text,$top,$stack,$state,$key,@refstack);
  my $line = 0;
  return Error ("No file specified",0) if (!defined $file);
  $stack = 0; $state = 2; $key = "";
  my $ref = {}; $top = $ref;
  $error = "";
  open (FILE, $file) || return Error ("Cant open file $file $!",0);
  while (<FILE>)
    {
    chomp; $text = $_;
    $text =~ s/\|//g;      # remove pipes (special char)
    $text =~ s/("[^"]*")/ nospace($1); /eg;
    $text =~ s/#.*//g;     # remove comments and spaces
    $text =~ s/\s+/ /g; $text =~ s/\s=\s/=/g; $text =~ s/\s?,\s?/,/g; 
    $text = " ".$text." ";
    $text =~ s/([A-Za-z0-9"\|={}\_\-,\.]+)/ 
     parse($1,$line,\$key,$levels,$map,\$ref,\$state,\$stack,\@refstack); /eg;
    $line ++;
    print "\rReading line $line" if (($line & 0xFF) == 0);
    return if ($error ne "");
    }
  close(FILE);
  return Error ("Unexpected end of file, '}' missing",$line) if ($stack > 0);
  return $top;
  }

sub nospace { my $t = shift; $t =~ s/\s/\|/g; $t; }

sub parse
  {
  my ($k,$line,$key,$levels,$map,$ref,$state,$stack,$refstack) = @_;
  my ($name,$val,$t,$r,@values,@names,$i,$n);
  if ($k =~ /([a-zA-Z0-9]+)=([a-z\|A-Z0-9\_\-",\.]+)/o)
    {
    $t = $k; 
    $t =~ s/([a-zA-Z0-9]+)=([a-z\|A-Z0-9\_\-",\.]+)/ 
      $name = lc($1); $val = $2; /oeg;
    return Error ("Undefined value for $name",$line) if (!defined $val);
    return Error ("Undefined name for $val",$line) if (!defined $name);
    $val =~ s/\"//g; $val =~ s/\|/ /g; @values = split (",",$val);
    @names = split(",",$map->{$name}) if (defined $map->{$name});
    $i = 0;
    foreach (@values)
      { 
      $n = $names[$i] || $name; $$ref->{$n} = $_; $i++;
      }
    return $k;
    }
  if ($k =~ /[a-z0-9A-Z]/o)
    {
    my $s = $$stack;
    $s = 0 if (!exists $levels->{$$stack});
    return Error ("Unexpected key '$k'",$line) 
     if (($$state != 2) || (!defined $levels->{$s}->{$k}));
    $$state = 1; $$key = lc($k); 
    return $k;
    }
  if ($k eq "{")
    {
    return Error ("Need { after keyword '$$key'",$line) if ($$state != 1);
    $$stack++; $$state = 2; $r = {}; # create new anonymous hash
    $name = $levels->{$$stack}->{$$key} || $levels->{0}->{$$key};
    return Error ("Unknown keyword '$$key'",$line) if (!defined $name);
    if (!defined $$ref->{$name})
      {
      $$ref->{$name} = [];
      }
    push (@{$$ref->{$name}}, $r); push (@{$refstack}, $$ref); $$ref = $r;
    return $k;
    }
  if ($k eq "}")
    {
    return Error ("Unexpected }",$line) if (($$state != 2) || ($$stack == 0));
    $$stack--; $$state = 2; $$ref = pop(@{$refstack});
    return $k; 
    }
  return Error ("Syntax error: unknown token '$k'",$line);
  }

sub Error
  {
  my ($txt,$line) = @_;
  $error = "\nError: $txt in line $line\n";
  print $error; # if ($debug);
  return "";
  }

sub getStyle 
  {
  # return list of opening, closing, comment
  return ($open,$close,$comment,$indend); 
  }

sub setStyle 
  { 
  # set opening, closing, comment
  # "%s" will be replaced by spaces according to actual level
  # "%n" by name (this MUST be included somewhere in $open!)
  # %i will be replaced by amount of spaced indicated by $indend
  $open = shift || "%s%n\n%s%i{\n";
  $close = shift || "%s%i}\n";
  $comment = shift || ' # '; 
  $indend = shift || '  '; 
  }

# put a struct to text

sub asText
  {
  # input: ref (to put), name, flags (1 = put names NOT listed in put, 0
  # put ONLY names listed in put), put (see flags),  
  # remap (hash to remap names), level (indendlevel, optional)
  my $r = shift || return;
  my $name = shift || "unnamed";
  my $flags = shift || 0;
  my $put = shift || {};
  my $remap = shift || {};
  my $level = shift || 0;
 
   print "astext $name $remap $put $r\n"; 
  my $space = " " x $level;
  my $t = $open; $t =~ s/%s/$space/g;
  $t =~ s/%n/$name/g;
  $t =~ s/%i/$indend/g;
  my ($e,$n,$a,$h);
  $level += length($indend);
  # put simple elements first
  foreach $e (sort keys %$r)
    {
    next if (($flags == 1) && (exists $put->{$e}));
    next if (($flags == 0) && (!exists $put->{$e}));
    $n = $e; 
    $n = $remap->{$e} if (exists $remap->{$e}); 
    $h = $e; $h = $r->{$e} if (ref($r) eq 'HASH');
    next if (!defined $h);
    next if (ref($h) ne "");
    print "$level: $e $n $h\n";
    $h = "\"$h\"" if ($h !~ /^[0-9\.\-]*$/);
    $t .= "$space$indend$n = $h\n";
    }
  foreach $e (sort keys %$r)
    {
    next if (($flags == 1) && (exists $put->{$e}));
    next if (($flags == 0) && (!exists $put->{$e}));
    $n = $e; $n = $remap->{$e} if (exists $remap->{$e}); 
    $h = $e; $h = $r->{$e} if (ref($r) eq 'HASH');
    print "$level: $e $n $h\n";
    next if (!defined $h);
    if (ref($h) eq 'HASH')
      {
      foreach $a (keys %$h)
        {
        $t .= asText ($h->{$a},$n,$flags,$put,$remap,$level);
        }
      }
    elsif (ref($h) eq 'ARRAY')
      {
      foreach $a (@$h)
        {     
        print "array $a $h $n\n";
        $t .= asText ($a,$n,$flags,$put,$remap,$level);
        }
      }
    elsif (ref($h) eq 'code')
      {
      $t .= $h->asText();
      }
    }
  my $c = $close; $c =~ s/%s/$space/g;
  $c =~ s/%i/$indend/g;
  $t .= $c;
  }


1;
