# Parse.pm
#
# Copyright (c) 2002 Cunningham & Cunningham, Inc.
# Released under the terms of the GNU General Public License version 2 or later.
#
# Perl translation by Dave W. Smith <dws@postcognitive.com>

package Parse;

use strict;

use vars qw(@tags);

@tags = qw(table tr td);

sub from {
    my $pkg = shift;
    my($tag,$body,$parts,$more) = @_;

    bless {
	leader => "\n",
        tag => "<$tag>",
        body => $body,
	parts => $parts,
	end => "</$tag>",
	more => $more,
        trailer => ""
    }, $pkg;
}

sub new {
    my $pkg = shift;

    my $self = bless { }, $pkg;
    $self->_parse($_[0], @tags) if @_ == 1;
    $self->_parse(@_) if @_ >= 2;
    return $self;
}

sub _parse {
    my $self = shift;
    my($string, @tags) = @_;
    my $t = $tags[0];

    my($leader,$tag,$body,$end,$trailer) =
        $string =~ m!^(.*?)(<$t[^>]*>)(.*?)(</$t>)(.*)$!is
	    or die "Can't find tag: $t\n";

    $self->{'leader'} = $leader;
    $self->{'tag'} = $tag;
    if ( @tags == 1 ) {
        $self->{'body'} = $body;
    } else {
        $self->{'parts'} = new Parse($body, @tags[1..$#tags]);
    }
    $self->{'end'} = $end;
    if ( $trailer =~ m!<$t!i ) {
	$self->{'more'} = new Parse($trailer, @tags);
    } else {
	$self->{'trailer'} = $trailer;
    }
}

sub size {
    my $self = shift;

    $self->more() ? $self->more()->size() + 1 : 1;
}

sub last {
    my $self = shift;

    $self->more() ? $self->more()->last() : $self;
}

sub leaf {
    my $self = shift;

    $self->parts() ? $self->parts()->leaf() : $self;
}

sub at {
    my $self = shift;

    return $self->_at3(@_) if 3 == @_;
    return $self->_at2(@_) if 2 == @_;
    return ($_[0] == 0 || not defined($self->more()))
	? $self
	: $self->more()->at($_[0] - 1);
}

sub _at2 {			#TBD revisit this
    my $self = shift;

    return $self->at($_[0])->parts()->at($_[1]);
}

sub _at3 {			#TBD revisit this
    my $self = shift;

    return $self->_at2($_[0], $_[1])->parts()->at($_[2]);
}

sub text {
    my $self = shift;

    my $s = unescape(unformat($self->body()));
    $s =~ s/^\s*//;
    $s =~ s/\s*$//;
    return $s;
}

sub unescape {
    my $s = shift;

    $s =~ s/\&lt;/</g;
    $s =~ s/\&gt;/>/g;
    $s =~ s/\&nbsp;/ /g;
    $s =~ s/\&amp;/&/g;
    return $s;
}

sub unformat {
    my $s = shift;

    $s =~ s/<[^>]+>//g;
    return $s;
}

sub addToTag {
    my $self = shift;
    my($string) = @_;

    $self->{'tag'} =~ s/>$/$string>/;
}

sub addToBody {
    my $self = shift;
    my($string) = @_;

    $self->{'body'} .= $string;
}

sub asString {
    my $self = shift;

    my $s = $self->leader() . $self->tag();
    if ( $self->parts() ) {
	$s .= $self->parts()->asString();
    } else {
	$s .= $self->body();
    }
    $s .= $self->end();
    if ( $self->more() ) {
        $s .= $self->more()->asString();
    } else {
	$s .= $self->trailer();
    }
    return $s;
}


sub leader {
    $_[0]->{'leader'}
}

sub tag {
    $_[0]->{'tag'}
}

sub body {
    $_[0]->{'body'}
}

sub parts {
    $_[0]->{'parts'}
}

sub end {
    $_[0]->{'end'}
}

sub trailer {
    $_[0]->{'trailer'}
}

sub more {
    my $self = shift;
    $self->{'more'} = $_[0] if @_;
    return $self->{'more'};
}

1;

=for Java
package fit;

// Copyright (c) 2002 Cunningham & Cunningham, Inc.
// Released under the terms of the GNU General Public License version 2 or later.

import java.io.*;
import java.text.ParseException;

public class Parse {

    public String leader;
    public String tag;
    public String body;
    public String end;
    public String trailer;

    public Parse more;
    public Parse parts;

    public Parse (String tag, String body, Parse parts, Parse more) {
        this.leader = "\n";
        this.tag = "<"+tag+">";
        this.body = body;
        this.end = "</"+tag+">";
        this.trailer = "";
        this.parts = parts;
        this.more = more;
    }

    public static String tags[] = {"table", "tr", "td"};

    public Parse (String text) throws ParseException {
        this (text, tags, 0, 0);
    }

    public Parse (String text, String tags[]) throws ParseException {
        this (text, tags, 0, 0);
    }

    public Parse (String text, String tags[], int level, int offset) throws ParseException {
        String lc = text.toLowerCase();
        int startTag = lc.indexOf("<"+tags[level]);
        int endTag = lc.indexOf(">", startTag) + 1;
        int startEnd = lc.indexOf("</"+tags[level], endTag);
        int endEnd = lc.indexOf(">", startEnd) + 1;
        int startMore = lc.indexOf("<"+tags[level], endEnd);
        if (startTag<0 || endTag<0 || startEnd<0 || endEnd<0) {
            throw new ParseException ("Can't find tag: "+tags[level], offset);
        }

        leader = text.substring(0,startTag);
        tag = text.substring(startTag, endTag);
        body = text.substring(endTag, startEnd);
        end = text.substring(startEnd,endEnd);
        trailer = text.substring(endEnd);

        if (level+1 < tags.length) {
            parts = new Parse (body, tags, level+1, offset+endTag);
            body = null;
        }

        if (startMore>=0) {
            more = new Parse (trailer, tags, level, offset+endEnd);
            trailer = null;
        }
    }

    public int size() {
        return more==null ? 1 : more.size()+1;
    }

    public Parse last() {
        return more==null ? this : more.last();
    }

    public Parse leaf() {
        return parts==null ? this : parts.leaf();
    }

    public Parse at(int i) {
        return i==0 || more==null ? this : more.at(i-1);
    }

    public Parse at(int i, int j) {
        return at(i).parts.at(j);
    }

    public Parse at(int i, int j, int k) {
        return at(i,j).parts.at(k);
    }

    public String text() {
        return unescape(unformat(body)).trim();
    }

    public static String unformat(String s) {
        int i=0, j;
        while ((i=s.indexOf('<',i))>=0) {
            if ((j=s.indexOf('>',i+1))>0) {
                s = s.substring(0,i) + s.substring(j+1);
            } else break;
        }
        return s;
    }

    public static String unescape(String s) {
        int i=-1, j;
        while ((i=s.indexOf('&',i+1))>=0) {
            if ((j=s.indexOf(';',i+1))>0) {
                String from = s.substring(i+1, j).toLowerCase();
                String to = null;
                if ((to=replacement(from)) != null) {
                    s = s.substring(0,i) + to + s.substring(j+1);
                }
            }
        }
        return s;
    }

    public static String replacement(String from) {
        if (from.equals("lt")) return "<";
        else if (from.equals("gt")) return ">";
        else if (from.equals("amp")) return "&";
        else if (from.equals("nbsp")) return " ";
        else return null;
    }


    public void addToTag(String text) {
        int last = tag.length()-1;
        tag = tag.substring(0,last) + text + ">";
    }

    public void addToBody(String text) {
        body = body + text;
    }


    public void print(PrintWriter out) {
        out.print(leader);
        out.print(tag);
        if (parts != null) {
            parts.print(out);
        } else {
            out.print(body);
        }
        out.print(end);
        if (more != null) {
            more.print(out);
        } else {
            out.print(trailer);
        }
    }
}
=cut
