File:  [LON-CAPA] / loncom / xml / lontable.pm
Revision 1.7: download - view: text, annotated - select for diffs
Mon Dec 29 11:57:37 2008 UTC (15 years, 4 months ago) by foxr
Branches: MAIN
CVS tags: HEAD
Add code to support horizontal alignment, correct bordering, and
table width.

    1: # The LearningOnline Network with CAPA
    2: #  Generating TeX tables.
    3: #
    4: # $Id: lontable.pm,v 1.7 2008/12/29 11:57:37 foxr Exp $
    5: # 
    6: #
    7: # Copyright Michigan State University Board of Trustees
    8: #
    9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   10: #
   11: # LON-CAPA is free software; you can redistribute it and/or modify
   12: # it under the terms of the GNU General Public License as published by
   13: # the Free Software Foundation; either version 2 of the License, or
   14: # (at your option) any later version.
   15: #
   16: # LON-CAPA is distributed in the hope that it will be useful,
   17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   19: # GNU General Public License for more details.
   20: #
   21: # You should have received a copy of the GNU General Public License
   22: # along with LON-CAPA; if not, write to the Free Software
   23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   24: #
   25: # /home/httpd/html/adm/gpl.txt
   26: #
   27: # http://www.lon-capa.org/
   28: ## Copyright for TtHfunc and TtMfunc by Ian Hutchinson. 
   29: # TtHfunc and TtMfunc (the "Code") may be compiled and linked into 
   30: # binary executable programs or libraries distributed by the 
   31: # Michigan State University (the "Licensee"), but any binaries so 
   32: # distributed are hereby licensed only for use in the context
   33: # of a program or computational system for which the Licensee is the 
   34: # primary author or distributor, and which performs substantial 
   35: # additional tasks beyond the translation of (La)TeX into HTML.
   36: # The C source of the Code may not be distributed by the Licensee
   37: # to any other parties under any circumstances.
   38: #
   39: 
   40: # This module is a support packkage that helps londefdef generate
   41: # LaTeX tables using the LaTeX::Table package.  A prerequisite is that
   42: # the print generator must have added the following to the LaTeX 
   43: #
   44: #  \usepackage{xtab}
   45: #  \usepackage{booktabs}
   46: #  \usepackage{array}
   47: #  \usepackage{colortbl}
   48: #  \usepackage{xcolor}
   49: #
   50: #  These packages are installed in the packaged LaTeX distributions we know of as of
   51: #  11/24/2008
   52: #
   53: 
   54: 
   55: 
   56: package Apache::lontable;
   57: use strict;
   58: use LaTeX::Table;
   59: 
   60: 
   61: =pod
   62: 
   63: =head1  lontable Table generation assistant for the LaTeX target
   64: 
   65: This module contains support software for generating tables in LaTeX output mode 
   66: In this implementation, we use the LaTeX::Table package to do the actual final formatting.
   67: Each table creates a new object.  Table objects can have global properties configured.
   68: The main operations on a table object are:
   69: 
   70: =over 3
   71: 
   72: =item start_row  
   73: 
   74: Opens a new table row.
   75: 
   76: =item end_row
   77: 
   78: Closes a table row.
   79: 
   80: =item configure_row
   81: 
   82: Modifies a configuration item in the currently open row.
   83: 
   84: =item generate
   85: 
   86: Returns the generated table string.
   87: 
   88: =item configure
   89: 
   90: Configures a table's global configuration.
   91: 
   92: =item add_cell
   93: 
   94: Add and configure a cell to the current row.6
   95: 
   96: =back
   97: 
   98: =cut
   99: 
  100: =pod
  101: 
  102: =head2 new - create a new object.
  103: 
  104: Create a new table object.  Any of the raw table configuration items can be
  105: modified by this.  These configuration items include:
  106: 
  107:   my $table = lontable::new(\%config_hash)
  108: 
  109: =over3
  110: 
  111: 
  112: =item alignment
  113: 
  114: Table alignment.  Some table styles support this but not all.
  115: 
  116: =item tableborder
  117: 
  118: If true, a border is drawn around the table.
  119: 
  120: =item cellborder
  121: 
  122: If true, borders are drawn around the cells inside a table.
  123: 
  124: =item caption
  125: 
  126: The table caption text.
  127: 
  128: =item theme
  129: 
  130: The theme of the table to use.  Defaults to Zurich.  Themes we know about are:
  131: NYC, NYC2, Zurich, Berlin, Dresden, Houston, Miami, plain, Paris.  Other themes can be added
  132: to the LaTeX::Table package, and they will become supported automatically, as theme names are
  133: not error checked.  Any use of a non-existent theme is reported by the LaTeX::Table package
  134: when the table text is generated.
  135: 
  136: =item width
  137: 
  138: The width of the table.  This can be expressed as fractions of full width, or in any
  139: TeX unit measure e.g. 0.75 for 75% of the width, or 10.8cm  This forces the table to the
  140: tabularx environment.
  141: 
  142: =back
  143: 
  144: =head3 Member data
  145: 
  146: The object hash has the following members:
  147: 
  148: =over 3
  149: 
  150: =item column_count 
  151: 
  152: Maintained internally, the number of colums in the widest row.
  153: 
  154: =item alignment
  155: 
  156: Table alignment (configurable) "left", "center", or "right".
  157: 
  158: =item outer_border
  159: 
  160: True if a border should be drawn around the entire table (configurable)
  161: 
  162: =item inner_borders
  163: 
  164: True if a border should be drawn around all cells (configurable).
  165: 
  166: =item caption
  167: 
  168: Table caption (configurable).
  169: 
  170: =item theme
  171: 
  172: Theme desired (configurable).
  173: 
  174: =item width
  175: 
  176: If defined, the width of the table (should be supplied
  177: in fraction of column width e.g. .75 for 75%.
  178: 
  179: =item row_open 
  180: 
  181: True if a row is open and not yet closed.
  182: 
  183: =item rows
  184: 
  185: Array of row data. This is an array of hashes described below.
  186: 
  187: =back
  188: 
  189: =head3 Row data.
  190: 
  191: Each row of table data is an element of the rows hash array.  Hash elements are
  192: 
  193: =over 3
  194: 
  195: 
  196: =item default_halign 
  197: 0
  198: Default horizontal alignment for cells in this row.
  199: 
  200: =item default_valign
  201: 
  202: Default vertical alignment for cells in this row (may be ignored).
  203: 
  204: =item cell_width
  205:  
  206: The width of the row in cells.  This is the sum of the column spans 
  207: of the cells in the row.
  208: 
  209: =item cells
  210: 
  211: Array of hashes where each element represents the data for a cell.
  212: The contents of each element of this hash are described below:
  213: 
  214: =over 3
  215: 
  216: =item header
  217: 
  218: If present, the row is a 'header' that is it was made via the
  219: <th> tag.
  220: 
  221: =item halign
  222: 
  223: If present, overrides the row default horizontal alignment.
  224: 
  225: =item valign
  226: 
  227: if present, override the row default vertical alignment.
  228: 
  229: =item rowspan
  230: 
  231: If present, indicates the number of rows this cell spans.
  232: 
  233: =item colspan
  234: 
  235: If present indicates the number of columns this cell spans.
  236: Note that a cell can span both rows and columns.
  237: 
  238: =item start_col
  239: 
  240: The starting column of the cell in the table grid.
  241: 
  242: =item contents
  243: 
  244: The contents of the cell.
  245: 
  246: =back
  247: 
  248: =back
  249: 
  250: =cut
  251: 
  252: sub new {
  253:     my ($class, $configuration) = @_;
  254: 
  255:     #  Initialize the object member data with the default values
  256:     #  then override with any stuff in $configuration.
  257: 
  258:     my $self = {
  259: 	alignment      => "left",
  260: 	outer_border   => 0,
  261: 	inner_border  => 0,
  262: 	caption        => "",
  263: 	theme          => "Zurich",
  264: 	column_count   => 0,
  265: 	row_open       => 0,
  266: 	rows           => [],
  267:     };
  268: 
  269:     foreach my $key (keys %$configuration) {
  270: 	$self->{$key} = $$configuration{$key};
  271:     }
  272: 
  273:     bless($self, $class);
  274: 
  275:     return $self;
  276: }
  277: 
  278: 
  279: #-------------------------------------------------------------------------
  280: #
  281: #  Methods that get/set table global configuration.
  282: #
  283: 
  284: =pod
  285: 
  286: =head2 Gets/set alignment.  
  287: 
  288: If the method is passed a new alignment value, that replaces the current one.
  289: Regardless, the current alignment is used:
  290: 
  291: =head3 Examples:
  292: 
  293:  my $align = $table->alignment(); # Return current alignment
  294:  $table->alignment("center");     # Attempt centered alignment.
  295: 
  296: =cut
  297: 
  298: sub alignment {
  299:     my ($self, $new_value) = @_;
  300: 
  301:     if (defined($new_value)) {
  302: 	$self->{'alignment'} = $new_value;
  303:     }
  304:     return $self->{'alignment'};
  305: }
  306: 
  307: =pod
  308: 
  309: =head2 table_border
  310: 
  311: Set or get the presence of an outer border in the table.
  312: If passed a parameter, that parameter replaces the current request
  313: for or not for an outer border. Regardless, the function returns
  314: the final value of the outer_border request.
  315: 
  316: =head3 Examples:
  317: 
  318:   $table->table_border(1);      # Request an outer border.
  319:   my $outer_requested = $table->table_border();
  320: 
  321: =cut
  322: 
  323: sub table_border {
  324:     my ($self, $new_value) = @_;
  325: 
  326:     if (defined($new_value)) {
  327: 	$self->{'outer_border'} = $new_value;
  328:     }
  329:     return $self->{'outer_border'};
  330: }
  331: 
  332: 
  333: =pod
  334: 
  335: =head2 cell_border
  336: 
  337: Set or get the presence of a request for cells to have borders
  338: drawn around them.  If a paramter is passed, it will be treated as
  339: a new value for the cell border configuration.  Regardless,the final
  340: value of that configuration parameter is returned.
  341: 
  342: =head3 Examples:
  343: 
  344:  my $cell_border = $table->cell_border(); # ask if cell borders are requested.
  345:  $table->cell_border(1);	# Request cell borders.
  346: 
  347: =cut
  348: 
  349: sub cell_border {
  350:     my ($self, $new_value) = @_;
  351: 
  352:     if (defined($new_value)) {
  353: 	$self->{'inner_border'} = $new_value;
  354:     }
  355:     return $self->{'inner_border'};
  356: }
  357: 
  358: =pod
  359: 
  360: =head2 caption
  361: 
  362: Gets and/or sets the caption string for the table.  The caption string appears to label
  363: the table.  If a parameter is supplied it will become the new caption string.k
  364: 
  365: =head3 Examples:
  366: 
  367: 
  368:   $my caption = $table->caption();
  369:   $table->caption("This is the new table caption");
  370: 
  371: =cut
  372: 
  373: sub caption {
  374:     my ($self, $new_value) = @_;
  375: 
  376:     if (defined($new_value)) {
  377: 	$self->{'caption'} = $new_value;
  378:     }
  379: 
  380:     return $self->{'caption'};
  381: }
  382: 
  383: =pod
  384: 
  385: =head2 theme
  386: 
  387: Gets and optionally sets the table theme.  The table theme describes how the
  388: table will be typset by the table package.  If a parameter is supplied it
  389: will be the new theme selection.
  390: 
  391: =head3 Examples:
  392: 
  393:   my $theme = $table->theme();
  394:   $table->theme("Dresden");
  395: 
  396: =cut
  397: 
  398: sub theme {
  399:     my ($self, $new_value) = @_;
  400: 
  401:     if (defined($new_value)) {
  402: 	$self->{'theme'} = $new_value;
  403:     }
  404:     return $self->{'theme'};
  405: }
  406: 
  407: =pod
  408: 
  409: =head 2 width
  410: 
  411: Gets and optionally sets the width of the table.
  412: 
  413: =head 3 Examples:
  414: 
  415:  $table->width("0.8");    # 80% of the column width.
  416:  my $newwidth = $table->width("10cm");   # 10cm width returns "10cm".
  417: 
  418: =cut
  419: sub width {
  420:     my ($self, $new_value) = @_;
  421:     if (defined($new_value)) {
  422: 	$self->{'width'} = $new_value;
  423:     }
  424:     return $self->{'width'}; 	# Could be undef.
  425: }
  426: 
  427: =pod
  428: 
  429: =head2 start_row
  430: 
  431: Begins a new row in the table.  If a row is already open, that row is
  432: closed off prior to starting the new row.  Rows can have the following attributes
  433: which are specified by an optional hash passed in to this function.
  434: 
  435: =over 3
  436: 
  437: =item default_halign
  438: 
  439: The default horizontal alignment of the row. This can be "left", "center", or "right"
  440: 
  441: =item default_valign
  442: 
  443: The default vertical alignment of the row.  This can be "top", "center", or "bottom"
  444: 
  445: =back
  446: 
  447: =head3 Examples:
  448: 
  449:   $table_start_row();                  # no attributes.
  450:   $table_start({default_halign => "center",
  451:                 default_valign => "bottom"}); # Create setting the attrbutes.
  452: 
  453: =cut
  454: 
  455: sub start_row {
  456:     my ($self, $config) = @_;
  457: 
  458:     if ($self->{'row_open'}) { 
  459: 	$self->end_row();
  460:     }
  461:     my $row_hash = {
  462: 	default_halign => "left",
  463: 	default_valign => "top",
  464: 	cell_width     =>  0,
  465: 	cells          => []
  466:     };
  467: 
  468:     # Override the defaults if the config hash is present:
  469: 
  470:     if (defined($config)) {
  471: 	foreach my $key  (keys %$config) {
  472: 	    $row_hash->{$key} = $config->{$key};
  473: 	}
  474:     }
  475: 
  476:     
  477:     my $rows = $self->{'rows'};
  478:     push(@$rows, $row_hash);
  479: 
  480:     $self->{"row_open"} = 1;	# Row is now open and ready for business.
  481: }
  482: 
  483: =pod
  484: 
  485: =head2  end_row 
  486: 
  487: Closes off a row.  Once closed, cells cannot be added to this row again.
  488: 
  489: =head3 Examples:
  490: 
  491:    $table->end_row();
  492: 
  493: 
  494: =cut
  495: 
  496: sub end_row {
  497:     my ($self) = @_;
  498: 
  499:     if ($self->{'row_open'}) {
  500: 	
  501: 	# Mostly we need to determine if this row has the maximum
  502: 	# cell count of any row in existence in the table:
  503: 
  504: 	my $row        = $self->{'rows'}->[-1];
  505: 	my $cells      = $row->{'cells'};
  506: 
  507: 	if ($row->{'cell_width'} > $self->{'column_count'}) {
  508: 	    $self->{'column_count'} = $row->{'cell_width'};
  509: 	}
  510: 
  511: 	$self->{'row_open'} = 0;;
  512:     }
  513: }
  514: 
  515: =pod
  516: 
  517: =head2 configure_row
  518: 
  519: Modify the configuration of a row.   If a row is not open, a new one will be opened.
  520: 
  521: =head3 Parameters:
  522: 
  523: config_hash - A hash that contains new values for the set of row confiuguration 
  524: items to be modified.  There is currently no check/penalty for items that are not in
  525: the set of defined configuration properties which are:
  526: 
  527: =over 2
  528: 
  529: =item default_halign
  530: 
  531: The default horizontal alignment for text in  cells in the row.  This can be any of:
  532: "left", "right" or "center".
  533: 
  534: =item default_valign
  535: 
  536: The default vertical alignment for text in cells in the row.  This can be any of:
  537: 
  538: "top", "bottom" or "center"
  539: 
  540: =back 
  541: 
  542: =cut
  543: 
  544: sub configure_row {
  545:     my ($self, $config) = @_;
  546: 
  547:     if (!$self->{'row_open'}) {
  548: 	$self->start_row();
  549:     }
  550:     
  551:     my $row = $self->{'rows'}[-1];
  552:     foreach my $config_item (keys %$config) {
  553: 	$row->{$config_item} = $config->{$config_item};
  554:     }
  555: }
  556: 
  557: 
  558: =pod
  559: 
  560: =head2 add_cell
  561: 
  562: Add a new cell to a row.  If there is a row above us, we need to 
  563: watch out for row spans that may force additional blank cell entries
  564: to fill in the span. 
  565: 
  566: =head3 Parameters:
  567: 
  568: =over 2
  569: 
  570: =item text
  571: 
  572: Text to put in the cell.
  573: 
  574: =item cell_config
  575: 
  576: Hash of configuration options that override the defaults.   The recognized options,
  577: and their defaults are:
  578: 
  579: =over 2
  580: 
  581: =item halign 
  582: 
  583: If nonblank overrides the row's default for the cell's horizontal alignment.
  584: 
  585: =item valign
  586: 
  587: If nonblank, overrides the row's default for the cdell's vertical alignment.
  588: 
  589: =item rowspan
  590: 
  591: Number of rows the cell spans.
  592: 
  593: =item colspan
  594: 
  595: Number of columns the cell spans.
  596: 
  597: =back
  598: 
  599: =cut
  600: 
  601: sub add_cell {
  602:     my ($self, $text, $config) = @_;
  603: 
  604:     # If a row is not open, we must open it:
  605: 
  606:     if (!$self->{'row_open'}) {
  607: 	$self->start_row();
  608:     }
  609:     my $rows          = $self->{'rows'};
  610:     my $current_row   = $rows->[-1];
  611:     my $current_cells = $current_row->{'cells'}; 
  612:     my $last_coord    = $current_row->{'cell_width'};
  613: 
  614:     #  We have to worry about row spans if there is a prior row:
  615: 
  616:     if (scalar(@$rows) > 1) {
  617: 
  618: 	my $last_row = $rows->[-2];
  619: 	if ($last_coord < $last_row->{'cell_width'}) {
  620: 	    my $prior_coord       = 0;
  621: 	    my $prior_cell_index  = 0;
  622: 	    while ($prior_coord <= $last_coord) {
  623: 		
  624: 		# Pull a cell down if it's coord matches our start coord
  625: 		# And there's a row span > 1.
  626: 		# Having done so, we adjust our $last_coord to match the
  627: 		# end point of the pulled down cell.
  628: 
  629: 		my $prior_cell = $last_row->{'cells'}->[$prior_cell_index];
  630: 		if (($prior_cell->{'start_col'} == $last_coord) &&
  631: 		    ($prior_cell->{'rowspan'}  > 1)) {
  632: 		    
  633: 		    #  Need to drop the cell down
  634: 
  635: 		    my %dropped_down_cell = %$prior_cell;
  636: 		    $dropped_down_cell{'rowspan'}--;
  637: 		    $dropped_down_cell{'contents'} = '';
  638: 
  639: 		    push(@$current_cells, \%dropped_down_cell);
  640: 		    $last_coord += $dropped_down_cell{'colspan'};
  641: 		    $current_row->{'cell_width'} = $last_coord;
  642: 		    
  643: 		}
  644: 		$prior_coord += $prior_cell->{'colspan'};
  645: 		$prior_cell_index++;
  646: 	    }
  647: 	}
  648: 
  649:     }
  650: 
  651:     #
  652:     # Now we're ready to build up our cell:
  653: 
  654:     my $cell = {
  655: 	rowspan    => 1,
  656: 	colspan    => 1,
  657: 	start_col  => $last_coord,
  658: 	contents   => $text
  659:     };
  660:     
  661:     if (defined($config)) {
  662: 	foreach my $key (keys(%$config)) {
  663: 	    $cell->{$key} = $config->{$key};
  664: 	}
  665:     }
  666:     $current_row->{'cell_width'} += $cell->{'colspan'};
  667: 
  668:     push(@$current_cells, $cell);
  669: }
  670: 
  671: =pod
  672: 
  673: =head2 generate
  674: 
  675: Call this when the structures for the table have been built.
  676: This will generate and return the table object that can be used
  677: to generate the table.  Returning the table object allows for
  678: a certain amount of testing to be done on the generated table.
  679: The caller can then ask the table object to generate LaTeX.
  680: 
  681: =cut
  682: sub generate {
  683:     my ($this) = @_;
  684: 
  685:     my $table = LaTeX::Table->new();
  686: 
  687:     # Add the caption if supplied.
  688: 
  689:     if ($this->{'caption'} ne "") {
  690: 	$table->set_caption($this->caption);
  691:     }
  692: 
  693:     
  694:     # Set the width if defined:
  695: 
  696:     if (defined ($this->{'width'})) {
  697: 	$table->set_width($this->{'width'});
  698: 	$table->set_width_environment('tabularx');
  699:     }
  700: 
  701:     # Build up the data:
  702: 
  703:     my @data;
  704:     my $rows      = $this->{'rows'};
  705:     my $row_count = scalar(@$rows);
  706:     my $inner_border = $this->{'inner_border'};
  707:     my $outer_border = $this->{'outer_border'};
  708:     my $column_count = $this->{'column_count'};
  709: 
  710:     for (my $row = 0; $row < $row_count; $row++) {
  711: 	my @row;
  712: 	my $cells      = $rows->[$row]->{'cells'};
  713: 	my $def_halign = $rows->[$row]->{'default_halign'};
  714: 	my $cell_count = scalar(@$cells);
  715: 	my $startcol   = 1;
  716: 	my @underlines;		# Array of \cline cells if cellborder on.
  717: 
  718: 	for (my $cell  = 0; $cell < $cell_count; $cell++) {
  719: 	    my $contents = $cells->[$cell]->{'contents'};
  720: 
  721: 	    #
  722: 	    #  Cell alignment is the default alignment unless
  723: 	    #  explicitly specified in the cell.
  724: 	    #  NOTE: at this point I don't know how to do vert alignment.
  725: 	    #
  726: 
  727: 	    my $halign   = $def_halign;
  728: 	    if (defined ($cells->[$cell]->{'halign'})) {
  729: 		$halign = $cells->[$cell]->{'halign'};
  730: 	    }
  731: 
  732: 	    # Create the horizontal alignment character:
  733: 
  734: 	    my $col_align = 'l';
  735: 	    if ($halign eq 'right') {
  736: 		$col_align = 'r';
  737: 	    }
  738: 	    if ($halign eq 'center') {
  739: 		$col_align = 'c';
  740: 	    }
  741: 	    if ($inner_border || ($outer_border && ($cell == 0))) {
  742: 		$col_align = '|'.$col_align;
  743: 	    }
  744: 	    if ($inner_border || ($outer_border && ($cell == ($cell_count -1)))) {
  745: 		$col_align = $col_align.'|';
  746: 	    }
  747: 
  748: 	    #factor in spans:
  749: 
  750: 	    my $cspan    = $cells->[$cell]->{'colspan'};
  751: 	    my $nextcol  = $startcol + $cspan;
  752: 	    $contents = '\multicolumn{'.$cspan.'}{'.$col_align.'}{'.$contents.'}';
  753: 	    if ($inner_border && ($cells->[$cell]->{'rowspan'} == 1)) {
  754: 		my $lastcol = $nextcol -1;
  755: 		push(@underlines, "\\cline{$startcol-$lastcol}");
  756: 	    }
  757: 	    $startcol = $nextcol;
  758: 	    # Rowspans should take care of themselves.
  759: 	    
  760: 
  761: 	    push(@row, $contents);
  762: 
  763: 	}
  764: 	push(@data, \@row);
  765: 	if ($inner_border) {
  766: 	    for (my $i =0; $i < scalar(@underlines); $i++) {
  767: 		push(@data, [$underlines[$i]]);
  768: 	    }
  769: 	}
  770: 
  771:     }
  772:     $table->set_data(\@data);
  773:     
  774:     my $coldef = "";
  775:     if ($outer_border || $inner_border) {
  776: 	$coldef .= '|';
  777:     }
  778:     for (my $i =0; $i < $column_count; $i++) {
  779: 	$coldef .= 'l';
  780: 	if ($inner_border || 
  781: 	    ($outer_border && ($i == $column_count-1))) {
  782: 	    $coldef .= '|';
  783: 	}
  784:     }
  785:     $table->{'coldef'} = $coldef;
  786: 
  787:     # Return the table:
  788: 
  789:     return $table;
  790: 
  791: }
  792: #----------------------------------------------------------------------------
  793: # The following methods allow for testability.
  794: 
  795: 
  796: sub get_object_attribute {
  797:     my ($self, $attribute) = @_;
  798:     return $self->{$attribute};
  799: }
  800: 
  801: sub get_row {
  802:     my ($self, $row) = @_;
  803:     my $rows = $self->{'rows'};	  # ref to an array....
  804:     return $rows->[$row];         # ref to the row hash for the selected row.
  805: }
  806: #   Mandatory initialization.
  807: BEGIN{
  808: }
  809: 
  810: 1;
  811: __END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>