#!/usr/bin/perl -w
use strict;

=head1 NAME

Info - Object to manage a topic information.

=head1 SYNOPSIS

use Info;

  @handouts = Info->get_all();

  $info = Info->new($name);
  $info = Info->import($name, $file);

=head1 DESCRIPTION

An info class makes an object for all handout
information.

=head1 USAGE

Isolate all handouts through this object.

=head1 AUTHOR

David Scott L

=head1 METHODS

These methods suppport Info objects:

=over 4

=cut

package Info;
use fields qw( name dir file topic data );

# Libraries used to build a handout
use Carp;
use CGI qw/:standard *table/;
use CSV;
use constant {
	NULL  => "\0",
};

# Global name space
our $VERSION = '1.0';
our $AUTOLOAD;
our @TOPIC  = ( '?', 'A' .. 'Z', 'a' .. 'z' );	# MAX_TOPICS = 54

=item @handouts = Info->get_all();

Returns a list of all handouts.

=item $info->get_all()

Returns a list of all handouts.

=cut

sub get_all {
    my $self  = shift;

    my @handouts = ();

    my $dir = get_dir();
    @handouts = File::Slurp::read_dir($dir);

    return @handouts;
}

=item SUPER::new($name);

Info can be inherited, use this call to
properly initialize the base object.

=item $info = Info->new($name);

Returns an info object that manages the current handout environment.

=cut

sub new {
    my $self  = shift;
    my $name  = shift || return undef;

    unless (ref $self) {
        $self = fields::new($self);
    }
    return $self->init($name);
}

=item $info = Info->import($name, $file);

Create a new info file from an import of a text file
from a workstation or from a local file.

Local files are read into memory and written back out.
Large files would require a file copy.

=cut

sub import {
    my $self  = shift;
    my $name  = shift || return undef;
    my $file  = shift || return undef;

    # setup directory if not there
    my $dir = get_dir();

    # file reference can come from anywhere
    my $txt = "${dir}/${name}";
    if ( ref($file) ) {
	# import contents in file
	open FILE, ">$txt" or die "Unable to write $txt: $!";
	while (<$file>) { 
	    # remove LineFeed to make a proper UNIX file
	    # CAUTION: may only be valid for text conversion
	    # CAUTION: tested on XP only
	    s/\r\n$/\n/;
	    print FILE;
	}
	close FILE;
    # simple name must be a filename
    } else {
	# copy contents in file
	my @lines = File::Slurp::read_file($file);
	File::Slurp::write_file($txt, @lines);
    }
    unless (ref $self) {
        $self = fields::new($self);
    }

    $self->init($name);

    return $self;
}

=item Info fields

  name  - name of the Info must be unique for a user
  dir   - name of directory
  file  - name of file
  topic - topic data
  data  - topic info data

=cut
  
# my $info = $self->init($name);
# initialize all variables & return $self or undef
sub init {
    my $self  = shift;

    # initialize variables
    $self->{name} = shift;
    $self->{dir} = get_dir();
    $self->{file} = "$self->{dir}/$self->{name}";
    $self->parse;
    return $self;
}


# $dir = get_dir();
# setup directory if not there
sub get_dir {

    my $dir = "data";
    mkdir $dir unless -d $dir; 
    $dir .= "/handout";
    mkdir $dir unless -d $dir;
    return $dir;
}

# $self->parse;
# Parsing engine for handout data
# topic = array reference: by topic: 0, 1, ... n
# data  = hash reference: by topic: 0, 1, ... n
#   each hash is an array of info
#     info: [  [ [] |  ] ... ]
#           [ "[rows|columns] , [list] ... ]"
sub parse {
    my $self = shift;

    open FILE, $self->file || die "Unable to open $self-file: $!";
    my $it = -1;	# iterate 0, 1, ... by topic
    my ($info, $topic, $table) = (0,0,0);	# gathering flags
    my @topics = ();
    my %info = ();	# a hash of arrays organized by 0, 1, ...
    my @data = ();	# the array to save
    while (  ) {
	chomp;
        next if /^\s*$/;	# ignore blank lines
	next if /^\s/;		# ignore lines with leading space
	# look for keywords
	if ( /^topic\s+(.*)$/ ) {
	    $info = $table = 0 if $it == -1;	# ignore leading info /table
	    push @{$info{$it}}, [ @data ] if $info || $table;
	    $topic = 1;
	    $info = $table = 0;
	    ++$it;
	    $info{$it} = [];
	    @data = ();
	    push @topics, $1;
	} elsif ( /^info\s+(.*)$/ ) {
	    push @{$info{$it}}, [ @data ] if $info || $table;
	    $info = 1;
	    $topic = $table = 0;
	    @data = ();
	    push @data, $1;	# ( $text )
	} elsif ( /^table\srows\s+(.*)$/ ) {
	    push @{$info{$it}}, [ @data ] if $info || $table;
	    $table = 1;
	    $topic = $info = 0;
	    @data = ();
	    push @data, "rows $1";	# ( "rows $text" )
	    $_ =  || die "Table $1 not defined completely";
	    chomp;
	    /,/ || die "Table $data[0] not defined completely";
	    s/, /,/g;
	    push @data, [ split(/,/, $_) ];
	    $_ =  || die "Table $1 not defined completely";
	    chomp;
	    /,/ || die "Table $data[0] not defined completely";
	    s/, /,/g;
	    push @data, [ split(/,/, $_) ];
	} elsif ( /^table\scolumns\s+(.*)$/ ) {
	    push @{$info{$it}}, [ @data ] if $info || $table;
	    $table = 1;
	    $topic = $info = 0;
	    @data = ();
	    push @data, "columns $1";	# ( "columns $text" )
	    $_ =  || die "Table $1 not defined completely";
	    chomp;
	    /,/ || die "Table $data[0] not defined completely";
	    s/, /,/g;
	    push @data, [ split(/,/, $_) ];
	    $_ =  || die "Table $1 not defined completely";
	    chomp;
	    /,/ || die "Table $data[0] not defined completely";
	    s/, /,/g;
	    push @data, [ split(/,/, $_) ];
	# gather for keywords
        } elsif ( $info ) {
	    push @data, $_;		# ( $text, $text, ... )
#	    if ( /\.\s*$/ ) {
#		push @data, $_;		# ( $text, $text, ... )
#	    } elsif ( /,/ ) {
#		push @data, [ split(/,/, $_) ]; # ( $text, [ $item, ...] )
#	    } else {
#		push @data, $_;		# ( $text, $text, ... )
#            } 
        } elsif ( $table ) {
	    chomp;
	    /,/ || die "Table $data[0] not defined completely";
	    s/, /,/g;
	    push @data, [ split(/,/, $_) ];	# $( $text, [ $item, ...] )
        } elsif ( $topic ) {
	# ignore extra topic lines for now
        }
    }
    # End of File may catch the last info or table
    push @{$info{$it}}, [ @data ] if $info || $table;

    # save topics and info data
    $self->{topic} = \@topics;
    $self->{data}  = \%info;
}

=item $info->delete($product_name)

Remove a handout list completely.

=cut

sub delete {
    my $self = shift;
    my $info = $self->name;

    my $dir = $self->dir;

    # Delete the current info file
    unlink "$dir/$info";

    # Clean up current object
    $self->{data} = undef;
}

=item $self->{data} = $info->restore();

Restore a information.

=cut

sub restore {
    my $self  = shift;
}

# $self->add_name()
# add a name to the products file, create it if it doesn't exist
sub add_name {
    my $self = shift;

    my $name = $self->name;

    # Verify handout info name is within the "handouts" file
    my $info_file = get_info_file();
    my @handouts = ();
    if ( -e $info_file ) {
	@handouts = File::Slurp::read_file($info_file);
    }

    # Add name if it isn't already in the products file
    if ( $name && ! grep ( /^$name$/, @handouts ) ) {
	push @handouts, "$name\n";
	File::Slurp::write_file($info_file, @handouts);
    }
}

=back 4

=head1 Object methods

Info objects return information needed for display.

=over 4

=item topics

Return a list of topic fields.

=cut

sub topics {
    my $self = shift;

    die "invalid method call" unless ref($self);

    my @topics;
    foreach my $count ( 1 .. scalar @{$self->{topic}} ) {
	push @topics, "topic_" . $count;
    }

    return @topics;
}

=item $options_ref = $info->topic_options;

Return a list of options for checkbox selection
of topics.

=cut

sub topic_options {
    my $self = shift;

    die "invalid method call" unless ref($self);

    my $item;
    my %topics = ();
    foreach my $count ( 1 .. scalar @{$self->{topic}} ) {
	$item = "topic_" . $count;
	$topics{$item} = [ $self->{topic}->[$count-1] ];
    }

    return \%topics;
}

=item @fields = $info->info( @topics );

Return a list of topic and info fields.

=cut

sub info {
    my ($self, @topics) = @_;

    die "invalid method call" unless ref($self);

    my @fields = ();
    my $item;
    my $data;
    foreach my $topic ( @topics ) {
	push @fields, "topic_" . $topic;
	$data = $self->data->{$topic-1};
        foreach my $count ( 1 .. scalar @$data ) {
	    $item = $data->[$count-1];
	    if ( $item->[0] =~ /^rows (.*)$/ ) {
		push @fields, "table_" . $topic . "_" . $count;
	    } elsif ( $item->[0] =~ /^columns (.*)$/ ) {
		push @fields, "table_" . $topic . "_" . $count;
	    } else {
		push @fields, "info_" . $topic . "_" . $count;
	    }
	}
    }
    return @fields;
}

=item $info->info_options( @topics );

Return the options of info for display.

All topics will be static picks.

All tables, and info items return only the
first banner.  Call info_fields to get the
display of other lines.

=cut

sub info_options {
    my ($self, @topics) = @_;

    die "invalid method call" unless ref($self);

    my %fields = ();
    my $field = "";
    my $index;
    my $data;
    foreach my $topic ( @topics ) {
	$field = "topic_" . $topic;
	$fields{$field} = [ $self->{topic}->[$topic-1] ];
	$data = $self->data->{$topic-1};
        foreach my $count ( 1 .. scalar @$data ) {
	    $index = $count - 1;
	    if ( $data->[$index]->[0] =~ /^rows (.*)$/ ) {
		$field = "table_" . $topic . "_" . $count;
                $fields{$field} = [ $1 ];
	    } elsif ( $data->[$index]->[0] =~ /^columns (.*)$/ ) {
		$field = "table_" . $topic . "_" . $count;
                $fields{$field} = [ $1 ];
	    } else {
		$field = "info_" . $topic . "_" . $count;
	        $fields{$field} = [ $data->[$index]->[0] ];
	    }
#	  }
	}
    }
    return \%fields;
}

=item $info->info_data( @topics );

Return the data of info for testing.

=cut

sub info_data {
    my ($self, @topics) = @_;

    die "invalid method call" unless ref($self);

    my %fields = ();
    my $field = "";
    my $index;
    my $data;
    foreach my $topic ( @topics ) {
	$field = "topic_" . $topic;
	$fields{$field} = [ $self->{topic}->[$topic-1] ];
	$data = $self->data->{$topic-1};
        foreach my $count ( 1 .. scalar @$data ) {
	    $index = $count - 1;
	    if ( $data->[$index]->[0] =~ /^rows (.*)$/ ) {
		$field = "table_" . $topic . "_" . $count;
	        $fields{$field} = $data->[$index];
                $fields{$field}->[0] = $1;
	    } elsif ( $data->[$index]->[0] =~ /^columns (.*)$/ ) {
		$field = "table_" . $topic . "_" . $count;
	        $fields{$field} = $data->[$index];
                $fields{$field}->[0] = $1;
	    } else {
		$field = "info_" . $topic . "_" . $count;
	        $fields{$field} = $data->[$index];
	    }
	}
    }
    return \%fields;
}

=item $info->info_fields( $form, @topics );

Return the field info for for info selection.

=cut

sub info_fields {
    my ($self, $form, @topics) = @_;

    die "invalid method call" unless ref($self);

    # predeclared for efficiency
    my ($field, $index, $data, $comment, $item, $list, $max);

    # Set each topic to static
    foreach my $topic ( @topics ) {
	$field = "topic_" . $topic;
        $form->field( -name => $field, -type => 'static' );
	$data = $self->data->{$topic-1};
	# Add data to each selection as a comment
        foreach my $count ( 1 .. scalar @$data ) {
	    $index = $count - 1;
	    if ( $data->[$index]->[0] =~ /^rows (.*)$/ ) {
		$field = "table_" . $topic . "_" . $count;
		$list = get_row_list( $data->[$index] ); 
                $comment = "

" . table({-border => undef}, caption($1), Tr({-align => 'CENTER', -valign => 'TOP'}, $list) ); $form->field( -name => $field, -comment => $comment ); } elsif ( $data->[$index]->[0] =~ /^columns (.*)$/ ) { $field = "table_" . $topic . "_" . $count; $list = get_col_list( $data->[$index] ); $comment = "

" . table({-border => undef}, caption($1), Tr({-align => 'CENTER', -valign => 'TOP'}, $list) ); $form->field( -name => $field, -comment => $comment ); } else { $field = "info_" . $topic . "_" . $count; $comment = "

    "; foreach $item ( 1 .. $#{$data->[$index]} ) { $comment .= "
  • " . $data->[$index]->[$item] . "
  • "; } $comment .= "

"; $form->field( -name => $field, -comment => $comment ); } } } } =item @fields = $info->fields( $form, \%label, @topics ); Return a list of topic and handout fields. This will return only those info fields that have been picked. Returns labels for simple numbering of info items. =cut sub fields { my ($self, $form, $labels, @topics) = @_; die "invalid method call" unless ref($self); my @fields = (); my ($data, $item, $id, $info_count, $label); my $topic_count = 1; foreach my $topic ( @topics ) { push @fields, $TOPIC[$topic_count]; $data = $self->data->{$topic-1}; $info_count = 1; foreach my $count ( 1 .. scalar @$data ) { $item = $data->[$count-1]; if ( $item->[0] =~ /^rows (.*)$/ ) { $id = "table_" . $topic . "_" . $count; } elsif ( $item->[0] =~ /^columns (.*)$/ ) { $id = "table_" . $topic . "_" . $count; } else { $id = "info_" . $topic . "_" . $count; } if ( $form->field($id) ) { $label = $TOPIC[$topic_count] . "_" . $info_count; push @fields, $label; $labels->{$label} = $info_count; ++ $info_count; } } ++ $topic_count; } return @fields; } =item $info->show_fields( $picks, $form, @topics ); Return the field info for a handout, in this case all fields should be set to static. This is based on form "picks" from "select_info". =cut sub show_fields { my ($self, $picks, $form, @topics) = @_; die "invalid method call" unless ref($self); # predeclared for efficiency my ($field, $index, $data, $comment, $item, $list, $max); my ($info_count, $id); my $topic_count = 1; # Set each topic to static foreach my $topic ( @topics ) { $id = "topic_" . $topic; $field = $TOPIC[$topic_count]; $form->field( -name => $field, -type => 'static' ); $data = $self->data->{$topic-1}; $info_count = 1; # Add data to each selection as a comment, set to static foreach my $count ( 1 .. scalar @$data ) { $index = $count - 1; $field = $TOPIC[$topic_count] . "_" . $info_count; if ( $data->[$index]->[0] =~ /^rows (.*)$/ ) { $id = "table_" . $topic . "_" . $count; $list = get_row_list( $data->[$index] ); $comment = "

" . table({-border => undef}, caption($1), Tr({-align => 'CENTER', -valign => 'TOP'}, $list) ); } elsif ( $data->[$index]->[0] =~ /^columns (.*)$/ ) { $id = "table_" . $topic . "_" . $count; $list = get_col_list( $data->[$index] ); $comment = "

" . table({-border => undef}, caption($1), Tr({-align => 'CENTER', -valign => 'TOP'}, $list) ); } else { $id = "info_" . $topic . "_" . $count; $comment = "

    "; foreach $item ( 1 .. $#{$data->[$index]} ) { $comment .= "
  • " . $data->[$index]->[$item] . "
  • "; } $comment .= "

"; } if ( $picks->field($id) ) { $form->field( -name => $field, -comment => $comment, -type => 'static' ); ++ $info_count; } } ++ $topic_count; } } =item $info->show_options( $form, @topics ); Return the options of info for display. All topic and info items will be static displays for printing. All tables, and info items return only the first banner. Call show_fields to get the display of other lines. =cut sub show_options { my ($self, $form, @topics) = @_; die "invalid method call" unless ref($self); my %fields = (); my $field = ""; my ($index, $data, $id, $info_count); my $topic_count = 1; foreach my $topic ( @topics ) { $id = "topic_" . $topic; $field = $TOPIC[$topic_count]; $fields{$field} = [ $self->{topic}->[$topic-1] ]; $data = $self->data->{$topic-1}; $info_count = 1; foreach my $count ( 1 .. scalar @$data ) { $index = $count - 1; $field = $TOPIC[$topic_count] . "_" . $info_count; if ( $data->[$index]->[0] =~ /^rows (.*)$/ ) { $id = "table_" . $topic . "_" . $count; $fields{$field} = [ $1 ] if $form->field($id); } elsif ( $data->[$index]->[0] =~ /^columns (.*)$/ ) { $id = "table_" . $topic . "_" . $count; $fields{$field} = [ $1 ] if $form->field($id); } else { $id = "info_" . $topic . "_" . $count; $fields{$field} = [ $data->[$index]->[0] ] if $form->field($id); } ++ $info_count if $form->field($id); } ++ $topic_count; } return \%fields; } # $list = get_row_list( $data->[$index] ); # returns a list of rows in row order for display sub get_row_list { my ($list) = @_; my @row_list = (); my @header = @{$list->[1]}; my $max_header = scalar @header; my ($count, $item); # create rows with header items foreach my $index ( 0 .. $max_header-1 ) { push @row_list, [ "" . $header[$index] . "" ]; } # find maximum list item my $max = scalar @{$list->[2]}; foreach $item ( 3 .. scalar @$list - 1 ) { $count = scalar @{$list->[$item]}; $max = $count if $count > $max; } # populate rows $count = 0; foreach $item ( 2 .. scalar @$list - 1 ) { push @{$row_list[$count]}, @{$list->[$item]}; ++ $count; } # create table rows my $the_list = []; foreach $item ( 0 .. $#row_list ) { push @$the_list, td($row_list[$item]); } return $the_list; } # $list = get_col_list( $data->[$index] ); # returns a list of cols in col order for display # th(['Vegetable', 'Breakfast','Lunch','Dinner']), # td(['Tomatoes' , 'no', 'yes', 'yes']), # td(['Broccoli' , 'no', 'no', 'yes']), # td(['Onions' , 'yes','yes', 'yes']) sub get_col_list { my ($list) = @_; my @col_list = (); my @header = @{$list->[1]}; my $max_header = scalar @header; my ($count, $item); # find maximum list item my $max = scalar @{$list->[2]}; foreach $item ( 3 .. scalar @$list - 1 ) { $count = scalar @{$list->[$item]}; $max = $count if $count > $max; } # create cols foreach my $index ( 0 .. $max_header-1 ) { push @col_list, [ ]; } # populate cols $count = 0; foreach $item ( 2 .. scalar @$list - 1 ) { foreach $count ( 0 .. $max - 1 ) { if ( $count < scalar @{$list->[$item]} ) { push @{$col_list[$count]}, $list->[$item]->[$count]; } else { push @{$col_list[$count]}, ""; } } } # create table cols my $the_list = []; push @$the_list, th($list->[1]); foreach $item ( 0 .. $#col_list ) { push @$the_list, td($col_list[$item]); } return $the_list; } =item $output = $info->show( $form, $topics ); Return the handout screen of info for display. Pass in the topic array and the current screen response. Information is numbered and indented at that number level. Tables are just indented. So the handout is in outline format. =cut sub show { my ($self, $form, $topics) = @_; die "invalid method call" unless ref($self); my %fields = (); my $field = ""; my $item; my $data; foreach my $topic ( @$topics ) { $field = "topic_" . $topic; $fields{$field} = [ $self->{topic}->[$topic-1] ]; $data = $self->data->{$topic-1}; foreach my $count ( 1 .. scalar @$data ) { $item = $data->[$count-1]; if ( $item->[0] =~ /^rows (.*)$/ ) { $field = "table_" . $topic . "_" . $count; $fields{$field} = [ $1 ]; } elsif ( $item->[0] =~ /^columns (.*)$/ ) { $field = "table_" . $topic . "_" . $count; $fields{$field} = [ $1 ]; } else { $field = "info_" . $topic . "_" . $count; $fields{$field} = [ $item->[0] ]; } } } return \%fields; } =item AUTOLOAD Return or set any product info by default. Put a new value for query of this product: $product->query($query); Return the query value for this product: $product->query); =cut sub AUTOLOAD { my $self = shift; my $type = ref($self) or croak "$self is not an object"; my $name = $AUTOLOAD; $name =~ s/.*://; # strip fully-qualified portion croak "Can't access `$name' field in class $type" unless (exists $self->{$name}); if (@_) { return $self->{$name} = shift; } else { return $self->{$name}; } } # explicit destroy required because of AUTOLOAD sub DESTROY { } 1; # all packages must return 1 for sucessful loading __END__
Hosted by www.Geocities.ws

1