#!/usr/bin/perl
require
5.008001;
our
$VERSION
=
"3.32"
;
(
my
$CMD
= $0) =~ s{.*[\/]}{};
my
$is_grep
= $0 =~ m/
grep
$/;
sub
usage {
my
$err
=
shift
and
select
STDERR;
my
$p
=
$is_grep
?
" pattern"
:
""
;
print
join
"\n"
=>
"usage: $CMD\t[-s <sep>] [-L] [-n] [-A] [-u] [Selection]$p file.xls"
,
" \t[-c | -m] [-u] [Selection]$p file.xls"
,
" \t -i [-S sheets]$p file.xls"
,
" Generic options:"
,
" -v[#] Set verbose level (xlscat/xlsgrep)"
,
" -d[#] Set debug level (Spreadsheet::Read)"
,
" --list Show supported spreadsheet formats and exit"
,
" -u Use unformatted values"
,
" --strip[=#] Strip leading and/or traing spaces of all cells"
,
" # & 01 = leading, # & 02 = trailing, 3 = default"
,
" --clip=# Clip cells to max length #"
,
" --noclip Do not strip empty sheets and"
,
" trailing empty rows and columns"
,
" --no-empty Skip empty rows"
,
" --no-nl[=R] Replace all newlines in cells with R (default space)"
,
" -e <enc> Set encoding for input and output"
,
" -b <enc> Set encoding for input"
,
" -a <enc> Set encoding for output"
,
" -U Set encoding for output to utf-8 (short for -a utf-8)"
,
" Input CSV:"
,
" --in-sep=c Set input sep_char for CSV (c can be 'TAB')"
,
" Input XLS:"
,
" --dtfmt=fmt Specify the default date format to replace 'm-d-yy'"
,
" the default replacement is 'yyyy-mm-dd'"
,
" --passwd=pw Specify the password for workbook"
,
" if pw = -, read password from keyboard"
,
" --formulas Show the formula instead of the value"
,
" Output Text (default):"
,
" -s <sep> Use separator <sep>. Default '|', \\n allowed"
,
" Overrules ',' when used with --csv"
,
" -L Line up the columns"
,
" -B --box Like -L but also add outer frame"
,
" -n [skip] Number lines (prefix with column number)"
,
" optionally skip <skip> (header) lines"
,
" -A Show field attributes in ANSI escapes"
,
" -h[#] Show # header lines"
,
" -D Dump each record with Data::Peek or Data::Dumper"
,
" --hash Like -D but as hash with first row as keys"
,
" Output CSV:"
,
" -c Output CSV, separator = ','"
,
" -m Output CSV, separator = ';'"
,
$is_grep
? (
" Grep options:"
,
" -i Ignore case"
,
" -w Match whole words only"
) : (
" Output Index only:"
,
" -i Show sheet names and size only"
),
" Output HTML:"
,
" -H Output HTML"
,
" Selection:"
,
" -S <sheets> Only print sheets <sheets>. 'all' is a valid set"
,
" Default only prints the first sheet"
,
" -R <rows> Only print rows <rows>. Default is 'all'"
,
" Ranges and lists supported as 2,4-7,8-"
,
" Trailing - is to end of data"
,
" Negative rows count from tail -8--2 is allowed"
,
" --head[=n] Alias for -R1..n where n defaults to 10"
,
" --tail[=n] Alias for -R-n- where n defaults to 10"
,
" --first=C Only show rows where column C is a 'new' value"
,
" (suppress rows where value in column C is seen"
,
" before in column C). That includes empty fields"
,
" -C <cols> Only print columns <cols>. Default is 'all'"
,
" -F <flds> Only fields <flds> e.g. -FA3,B16"
,
" Ordering (column numbers in result set *after* selection):"
,
" --sort=spec Sort output (e.g. --sort=3,2r,5n,1rn+2)"
,
" +# - first # lines do not sort (header)"
,
" # - order on column # lexical ascending"
,
" #n - order on column # numeric ascending"
,
" #r - order on column # lexical descending"
,
" #rn - order on column # numeric descending"
,
""
,
"Examples:"
,
" xlscat -i foo.xls"
,
" xlscat --in-sep=: --sort=3n -L /etc/passwd"
,
" xlsgrep pattern file.ods"
,
""
;
@_
and
print
join
"\n"
,
@_
,
""
;
exit
$err
;
}
use
Encode
qw( encode decode )
;
use
Getopt::Long
qw(:config bundling noignorecase passthrough)
;
my
$opt_c
;
my
$opt_F
=
""
;
my
$opt_v
= 0;
my
$opt_d
= 0;
my
$opt_h
= 0;
my
$opt_D
= 0;
my
$clip
= 1;
my
$enc_bom
;
my
$enc_i
;
my
$enc_o
;
GetOptions (
"help|?"
=>
sub
{ usage (0); },
"V|version"
=>
sub
{
print
"$CMD [$VERSION] Spreadsheet::Read [$Spreadsheet::Read::VERSION]\n"
;
exit
0; },
"list"
=>
sub
{ list_parsers (); },
"c|csv"
=>
sub
{
$opt_c
=
","
},
"m|ms"
=>
sub
{
$opt_c
=
";"
},
"insepchar"
.
"|in-sep"
.
"|in-sep-char=s"
=> \
my
$sep
,
"dtfmt"
.
"|date-format=s"
=> \
my
$dtfmt
,
"f|formulas!"
=> \
my
$opt_f
,
"password=s"
=> \
my
$passwd
,
"i|index"
.
"|ignore-case!"
=> \
my
$opt_i
,
"s|separator|sep"
.
"|outsepchar"
.
"|out-sep"
.
"|out-sep-char=s"
=> \
my
$opt_s
,
"S|sheets=s"
=> \
my
$opt_S
,
"R|rows=s"
=> \
my
$opt_R
,
"head:10"
=> \
my
$opt_head
,
"tail:10"
=> \
my
$opt_tail
,
"first=s"
=> \
my
$opt_first
,
"C|columns=s"
=> \
my
$opt_C
,
"F|fields=s"
=> \
$opt_F
,
"L|fit|align!"
=> \
my
$opt_L
,
"B|box|frame!"
=> \
my
$opt_B
,
"P|pivot!"
=> \
my
$pivot
,
"n|number:0"
=> \
my
$opt_n
,
"A|ansi|color!"
=> \
my
$opt_A
,
"u|unformatted!"
=> \
my
$opt_u
,
"v|verbose:1"
=> \
$opt_v
,
"d|debug:1"
=> \
$opt_d
,
"D|dump!"
=> \
$opt_D
,
"hash!"
=>
sub
{
$opt_D
= 2 },
"H|html:1"
=> \
my
$opt_H
,
"noclip"
=>
sub
{
$clip
= 0 },
"strip:3"
=> \
my
$strip
,
"clip=i"
=> \
my
$clip_len
,
"sort=s"
=> \
my
$sort_order
,
"no-empty"
=> \
my
$skip_empty
,
"N|no-nl:s"
=> \
my
$opt_N
,
"e|encoding=s"
=>
sub
{
$enc_i
=
$enc_o
=
$_
[1] },
"b|encoding-in=s"
=> \
$enc_i
,
"a|encoding-out=s"
=> \
$enc_o
,
"U|utf-8|utf8"
=>
sub
{
$enc_o
=
"utf-8"
},
"w|word!"
=> \
my
$opt_w
,
"h|header:1"
=> \
$opt_h
,
) or usage 1,
"GetOpt: $@"
;
sub
list_parsers {
my
$err
=
shift
|| 0;
print
"Ext Parser module Req Has Def\n"
;
print
"-------- ------------------------- ----- ----- ---\n"
;
for
(Spreadsheet::Read::parsers ()) {
$_
->{ext} =~ m/^zzz/ and
next
;
printf
"%-8s %-25s %5s %5s %s\n"
,
$_
->{ext},
$_
->{mod},
$_
->{min},
$_
->{vsn},
$_
->{def} ?
"<=="
:
""
;
}
exit
$err
;
}
$opt_head
and
$opt_R
=
"1-$opt_head"
;
$opt_tail
and
$opt_R
=
"-$opt_tail-"
;
$opt_B
and
$opt_L
++;
unless
(
$is_grep
) {
$opt_i
&&
$opt_L
and usage 1,
"Options i and L are mutually exclusive"
;
$opt_i
&&
$opt_s
and usage 1,
"Options i and s are mutually exclusive"
;
$opt_i
&&
$opt_c
and usage 1,
"Options i and c are mutually exclusive"
;
$opt_i
&&
$opt_u
and usage 1,
"Options i and u are mutually exclusive"
;
$opt_i
&&
$opt_S
and usage 1,
"Options i and S are mutually exclusive"
;
$opt_i
&&
$opt_R
and usage 1,
"Options i and R are mutually exclusive"
;
$opt_i
&&
$opt_C
and usage 1,
"Options i and C are mutually exclusive"
;
$opt_i
&&
$opt_F
and usage 1,
"Options i and F are mutually exclusive"
;
$opt_i
&&
$opt_H
and usage 1,
"Options i and H are mutually exclusive"
;
}
$opt_c
&&
$opt_H
and usage 1,
"Options c and H are mutually exclusive"
;
$opt_s
&&
$opt_H
and usage 1,
"Options s and H are mutually exclusive"
;
my
%e
= (
a
=>
"\a"
,
b
=>
"\b"
,
e
=>
"\e"
,
f
=>
"\f"
,
n
=>
"\n"
,
r
=>
"\r"
,
t
=>
"\t"
);
$opt_s
and
$opt_s
=~ s/\\+([abefnrt])/
$e
{$1}/g;
defined
$opt_S
or
$opt_S
=
$opt_i
||
$is_grep
?
"all"
:
"1"
;
defined
$opt_N
&& !
length
$opt_N
and
$opt_N
=
" "
;
$opt_i
&& !
$is_grep
&&
$opt_v
< 1 and
$opt_v
= 1;
$opt_f
and
$opt_A
= 1;
if
(
$opt_c
) {
$opt_L
= 0;
if
(
$opt_s
) {
$opt_c
=
$opt_s
;
$opt_s
=
undef
;
}
$opt_c
=~ m/^1?$/ and
$opt_c
=
","
;
$opt_c
= Text::CSV_XS->new ({
binary
=> 1,
sep_char
=>
$opt_c
,
eol
=>
"\r\n"
,
});
}
sub
ddumper {
$dp
? Data::Peek::DDumper (
@_
)
:
print
STDERR Dumper (
@_
);
}
}
my
@RDarg
;
for
(
reverse
0 ..
$#ARGV
) {
$ARGV
[
$_
] =~ m/^--([-\w]+)(?:=(.*))?$/ or
next
;
push
@RDarg
, $1,
defined
$2 ? $2 : 1;
$RDarg
[-2] =~
tr
/-/_/;
splice
@ARGV
,
$_
, 1;
}
my
$pattern
;
if
(
$is_grep
) {
$pattern
=
shift
or usage 1;
$opt_w
and
$pattern
=
"\\b$pattern\\b"
;
$opt_i
and
$pattern
=
"(?i:$pattern)"
;
$pattern
=
qr{$pattern}
;
$opt_v
> 1 and
warn
"Matching on $pattern\n"
;
}
my
$file
=
shift
;
if
(
defined
$file
and
$file
ne
"-"
) {
$opt_v
> 1 and
warn
"Using $file as input\n"
;
-f
$file
or usage 1,
"the file argument is not a regular file"
;
-s
$file
or usage 1,
"the file is empty"
;
if
(
$file
=~ m/\.csv$/i and
open
my
$fh
,
"<"
,
$file
) {
my
$l
= <
$fh
>;
close
$fh
;
if
(
$l
=~ s/^\x00\x00\xfe\xff//) {
$enc_bom
=
"utf-32be"
}
elsif
(
$l
=~ s/^\xff\xfe\x00\x00//) {
$enc_bom
=
"utf-32le"
}
elsif
(
$l
=~ s/^\xfe\xff//) {
$enc_bom
=
"utf-16be"
}
elsif
(
$l
=~ s/^\xff\xfe//) {
$enc_bom
=
"utf-16le"
}
elsif
(
$l
=~ s/^\xef\xbb\xbf//) {
$enc_bom
=
"utf-8"
}
elsif
(
$l
=~ s/^\xf7\x64\x4c//) {
$enc_bom
=
"utf-1"
}
elsif
(
$l
=~ s/^\xdd\x73\x66\x73//) {
$enc_bom
=
"utf-ebcdic"
}
elsif
(
$l
=~ s/^\x0e\xfe\xff//) {
$enc_bom
=
"scsu"
}
elsif
(
$l
=~ s/^\xfb\xee\x28//) {
$enc_bom
=
"bocu-1"
}
elsif
(
$l
=~ s/^\x84\x31\x95\x33//) {
$enc_bom
=
"gb-18030"
}
elsif
(
$l
=~ s/^\x{feff}//) {
$enc_bom
=
""
}
if
(
$enc_bom
and
open
$fh
,
"<:encoding($enc_bom)"
,
$file
) {
$opt_v
> 1 and
warn
"Opened $file with encoding $enc_bom after BOM detection\n"
;
read
$fh
, (
my
$bom
), 1;
$file
=
$fh
;
push
@RDarg
,
parser
=>
"CSV"
;
}
}
}
else
{
$opt_v
> 1 and
warn
"Working as a pipe\n"
;
$file
=
*ARGV
;
}
if
(
$opt_c
) {
Spreadsheet::Read::parses (
"csv"
) or
die
"No CSV module found\n"
;
eval
q{use Text::CSV_XS}
;
}
if
(
$opt_H
) {
$enc_o
=
"utf-8"
;
$opt_H
=
sub
{
$_
[0]; };
eval
q{
use HTML::Entities;
$opt_H = sub {
encode_entities (Encode::is_utf8 ($_[0]) ? $_[0] :
decode ("utf-8", $_[0]));
}
;
};
}
if
(
$sep
) {
my
%sep
= (
tab
=>
"\t"
,
pipe
=>
"|"
,
colon
=>
":"
,
semicolon
=>
";"
,
comma
=>
","
,
);
$sep
=
$sep
{
lc
$sep
} ||
$sep
;
}
push
@RDarg
,
debug
=>
$opt_d
,
clip
=>
$clip
;
$opt_A
and
push
@RDarg
,
attr
=> 1;
defined
$sep
and
push
@RDarg
,
sep
=>
$sep
,
parser
=>
"csv"
;
defined
$dtfmt
and
push
@RDarg
,
dtfmt
=>
$dtfmt
;
$strip
and
push
@RDarg
,
strip
=>
$strip
;
$pivot
and
push
@RDarg
,
pivot
=>
$pivot
;
if
(
$passwd
) {
if
(
$passwd
eq
"-"
) {
print
STDERR
"Password: "
;
eval
"use Term::ReadKey;"
;
eval
{ ReadMode (
"noecho"
); };
chomp
(
$passwd
= <STDIN>);
eval
{ ReadMode (
"echo"
); };
}
push
@RDarg
,
passwd
=>
$passwd
;
}
$opt_v
> 4 and
warn
"ReadData ($file, @RDarg);\n"
;
my
$xls
=
eval
{ ReadData (
$file
,
@RDarg
) };
$opt_v
> 7 and ddumper (
$xls
);
unless
(
$xls
) {
warn
"cannot read $file\n"
;
if
($@) {
my
$e
= $@;
$e
=~ s{\s+at\s+\S+\s+line\s.*}{}s;
$e
=~ s{\s+\(perhaps you forgot to load.*}{}s;
warn
"$e\n"
;
}
if
(
my
@e
=
sort
grep
m/^SPREADSHEET_READ_/ =>
keys
%ENV
) {
warn
"\n"
;
warn
" $_ is set to $ENV{$_}\n"
for
@e
;
warn
"\n"
;
}
if
($@ =~ m/cannot|can't/) {
print
"Supported parsers per spreadsheet:\n"
;
list_parsers (-1);
}
exit
-1;
}
if
(
$xls
->[0]{parser} =~ m/Spreadsheet::XLSX/) {
warn
"You parse with Spreadsheet::XLSX, which is deprecated\n"
;
warn
"Please use Spreadsheet::ParseXLSX for more reliable results\n"
;
}
my
$sc
=
$xls
->[0]{sheets} or
die
"No sheets in $file\n"
;
$opt_v
and
warn
$opt_v
> 1
?
join
" "
=>
"Spreadsheet::Read-$Spreadsheet::Read::VERSION"
,
"parsed $file with $sc sheets"
,
"using $xls->[0]{parser}-$xls->[0]{version}\n"
:
"Parsed $file with $sc sheets\n"
;
$opt_S
eq
"all"
and
$opt_S
=
"1..$sc"
;
$opt_S
=~ s/-$/-
$sc
/;
$opt_S
=~ s/-/../g;
my
%print
;
eval
"%{\$print{sheet}} = map { \$_ => 1 } $opt_S"
;
my
$v_fmt
=
$opt_C
||
$opt_R
||
$opt_F
?
""
:
"%6d x %6d%s"
;
sub
color_reduce {
my
(
$rgb
,
$base
) =
@_
;
defined
$rgb
or
return
""
;
my
(
$r
,
$g
,
$b
) =
map
{
hex
>> 7 }
(
$rgb
=~ m/^\s*
$base
+ 4 *
$b
+ 2 *
$g
+
$r
;
}
sub
ansi_color {
my
(
$fg
,
$bg
,
$bold
,
$ul
) =
@_
;
my
$attr
=
join
";"
, 0,
grep
{ /\S/ }
$bold
? 1 :
""
,
$ul
? 4 :
""
,
color_reduce (
$fg
, 30),
color_reduce (
$bg
, 40);
"\e[${attr}m"
;
}
sub
css_color {
my
(
$fg
,
$bg
,
$bold
,
$ul
,
$ha
) =
@_
;
my
@css
;
$bold
and
push
@css
,
"font-weight: bold"
;
$ul
and
push
@css
,
"text-decoration: underline"
;
$fg
and
push
@css
,
"color: $fg"
;
$bg
and
push
@css
,
"background: $bg"
;
$ha
and
push
@css
,
"text-align: $ha"
;
local
$
" = "
; ";
@css
?
qq{ style="@css"}
:
""
;
}
binmode
STDERR,
":encoding(utf-8)"
;
$enc_o
and
binmode
STDOUT,
":encoding($enc_o)"
;
if
(
$opt_H
) {
print
<<"EOH";
<?xml version="1.0" encoding="utf-8"?>
<head>
<title>$file</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="Author" content="xlscat $VERSION" />
<style type="text/css">
body, h2,
td, th { font-family: "Nimbus Sans L", "DejaVu Sans",
Helvetica, Arial, sans; }
table { border-spacing: 2px;
border-collapse: collapse; }
td, th { vertical-align: top;
padding: 4px; }
table > tbody > tr > th,
table > tr > th {
background: #e0e0e0; }
table > tbody > tr > td:not([class]),
table > tr > td:not([class]) {
background: #f0f0f0; }
.odd { background: #e0e0e0; }
</style>
</head>
<body>
EOH
}
my
%first
;
if
(
$opt_first
) {
$opt_first
=~ m/^([a-zA-Z]+)[0-9]*$/ and
$opt_first
= (cell2cr (
uc
$1 .
"1"
))[0];
$opt_first
=~ m/^[0-9]+$/ &&
$opt_first
> 0 or
die
"--first=C requires a numeric column index or a column index letter\n"
;
}
my
$name_len
= 30;
if
(
$opt_i
) {
my
$nl
= 0;
foreach
my
$sn
(
keys
%{
$xls
->[0]{sheet}}) {
length
(
$sn
) >
$nl
and
$nl
=
length
$sn
;
}
$nl
and
$name_len
=
$nl
;
}
my
@opt_F
=
split
m/[^A-Z\d]+/ =>
$opt_F
;
foreach
my
$si
(1 ..
$sc
) {
my
@data
;
exists
$print
{sheet}{
$si
} or
next
;
$opt_v
> 1 and
warn
"Opening sheet $si ...\n"
;
my
$s
=
$xls
->[
$si
] or
next
;
$opt_v
> 5 and ddumper (
$s
);
my
@r
= (1,
$s
->{maxrow});
my
@c
= (1,
$s
->{maxcol});
my
(
$sn
,
$nr
,
$nc
) = (
$s
->{label},
$r
[-1],
$c
[-1]);
defined
$nr
or
next
;
defined
$nc
or
next
;
defined
$sn
or
$sn
=
""
;
my
$active
=
$s
->{active} ?
" (Active)"
:
""
;
my
$hidden
=
$s
->{hidden} ?
" (Hidden)"
:
""
;
my
$eolt
=
$s
->{eolt} ?
", EOL = $s->{eolt}"
:
""
;
$eolt
=~ s/\n/\\n/g;
$eolt
=~ s/\r/\\r/g;
$eolt
=~ s/\t/\\t/g;
$eolt
=~ s/([\x00-\x1f])/
sprintf
"\\x{%02x}"
,
ord
$1/ge;
$opt_v
and
printf
STDERR
"%s - %02d: [ %-*s ] %3d Cols, %5d Rows%s%s%s\n"
,
$file
,
$si
,
$name_len
,
$sn
,
$nc
,
$nr
,
$active
,
$hidden
,
$eolt
;
$opt_i
&& !
$is_grep
and
next
;
if
(
@opt_F
) {
foreach
my
$fld
(
@opt_F
) {
$is_grep
&&
defined
$s
->{
$fld
} &&
$s
->{
$fld
} !~
$pattern
and
next
;
print
"$fld:"
,
$s
->{
$fld
},
"\n"
;
}
next
;
}
if
(
my
$rows
=
$opt_R
) {
$rows
eq
"all"
and
$rows
=
"1..$nr"
;
$rows
=~ s/--(\d+)/-(
$nr
+1-$1)/ge;
$rows
=~ s/-(\d+)(?=-)/(
$nr
+1-$1)/ge;
$rows
=~ s/-$/-
$nr
/;
$rows
=~ s/-/../g;
eval
"%{\$print{row}} = map { \$_ => 1 } $rows"
;
}
if
(
my
$cols
=
$opt_C
) {
$cols
eq
"all"
and
$cols
=
"1..$nc"
;
if
(
$cols
=~ m/[A-Za-z]/) {
my
%ct
=
map
{
my
(
$cc
,
$rr
) = cell2cr (
uc
"$_"
.1);
(
$_
=>
$cc
)
} (
$cols
=~ m/([a-zA-Z]+)/g);
$cols
=~ s/([A-Za-z]+)/
$ct
{$1}/g;
}
$cols
=~ s/-$/-
$nc
/;
$cols
=~ s/-/../g;
eval
"\$print{col} = [ map { \$_ - 1 } $cols ]"
;
$nc
= @{
$print
{col}};
}
$opt_v
>= 8 and ddumper (\
%print
);
$opt_H
and
print
qq{<h2>$sn</h2>\n\n<table border="1">\n}
;
my
$undef
=
$opt_v
> 2 ?
"-- undef --"
:
""
;
my
(
$h
,
@w
) = (0, (0) x
$nc
);
my
@align
= (
""
) x
$nc
;
foreach
my
$r
(
$r
[0] ..
$r
[1]) {
exists
$print
{row} && !
exists
$print
{row}{
$r
} and
next
;
my
@att
;
my
@row
=
map
{
my
$cell
= cr2cell (
$_
,
$r
);
my
(
$uval
,
$fval
) =
map
{
defined
$_
?
$enc_i
? decode (
$enc_i
,
$_
) :
$_
:
$undef
}
$s
->{cell}[
$_
][
$r
],
$s
->{
$cell
};
if
(
defined
$opt_N
) {
s/\n/
$opt_N
/go
for
grep
{
defined
}
$uval
,
$fval
;
}
if
(
$clip_len
) {
$_
=
substr
$_
, 0,
$clip_len
- 1
for
grep
{
length
>
$clip_len
}
$uval
,
$fval
;
}
$opt_v
> 2 and
warn
"$_:$r '$uval' / '$fval'\n"
;
$opt_A
and
push
@att
, [ @{
$s
->{attr}[
$_
][
$r
]}{
qw( fgcolor bgcolor bold uline halign )
} ];
$opt_f
&&
$s
->{attr}[
$_
][
$r
]{formula}
?
"="
.
$s
->{attr}[
$_
][
$r
]{formula}
:
defined
$s
->{cell}[
$_
][
$r
] ?
$opt_u
?
$uval
:
$fval
:
""
;
}
$c
[0] ..
$c
[1];
$r
== 1 &&
$row
[0] &&
defined
$enc_bom
and
$row
[0] =~ s/^\N{BOM}//;
exists
$print
{col} and
@row
=
@row
[
grep
{
$_
<
@row
}@{
$print
{col}}];
$is_grep
&&
$r
>
$opt_h
&&
! first {
defined
$_
&&
$_
=~
$pattern
}
@row
and
next
;
$skip_empty
&& ! first {
length
}
@row
and
next
;
if
(
$opt_first
) {
@row
>=
$opt_first
&&
$first
{
$row
[
$opt_first
- 1]}++ and
next
;
}
if
(
$opt_D
) {
ddumper (
$opt_D
== 1 ? \
@row
:
{
map
{
$s
->{cell}[
$_
+ 1][1] =>
$row
[
$_
] } 0 ..
$#row
});
next
;
}
if
(
$opt_H
) {
print
" <tr>"
;
if
(
defined
$opt_n
) {
my
$x
=
$r
-
$opt_n
;
$x
<= 0 and
$x
=
""
;
my
$c
=
$r
% 2 ?
qq{ class="odd"}
:
""
;
print
qq{<td style="text-align: right" $c>$x</td>}
;
}
foreach
my
$c
(0 ..
$#row
) {
my
$css
= css_color (@{
$att
[
$c
]});
$r
% 2 and
$css
.=
qq{ class="odd"}
;
my
$td
=
$opt_H
->(
$row
[
$c
]);
print
"<td$css>$td</td>"
;
}
print
"</tr>\n"
;
next
;
}
if
(
defined
$opt_n
) {
unshift
@row
,
$r
;
unshift
@att
, [
"#ffffff"
,
"#000000"
, 0, 0 ];
}
if
(
$opt_L
||
$sort_order
) {
push
@data
, [ [
@row
], [
@att
] ];
next
;
}
if
(
$opt_c
) {
$opt_c
->
print
(
*STDOUT
, \
@row
) or
die
$opt_c
->error_diag;
next
;
}
if
(
$opt_A
) {
foreach
my
$c
(0 ..
$#row
) {
$row
[
$c
] =
ansi_color (@{
$att
[
$c
]}).
$row
[
$c
] .
"\e[0m"
;
}
}
line (0,
$opt_s
=>
@row
);
}
continue
{
++
$h
% 100 == 0 &&
$opt_v
&&
$v_fmt
and
printf
STDERR
$v_fmt
,
$nc
,
$h
,
"\r"
;
}
$opt_H
and
print
" </table>\n\n"
;
$v_fmt
&&
$v_fmt
and
printf
STDERR
$v_fmt
,
$nc
,
$h
,
"\n"
;
if
(
$sort_order
) {
my
@o
;
my
@h
;
$sort_order
=~ s/\+([0-9]+)\b// and
@h
=
splice
@data
, 0, $1;
for
(
$sort_order
=~ m/([0-9]+[rn]*)/g) {
m/^([0-9]+)(.*)/;
push
@o
, {
col
=> $1 - 1,
map
{
$_
=> 1 }
split
m// => $2 };
}
my
$sort
=
sub
{
my
$d
= 0;
foreach
my
$o
(
@o
) {
my
(
$A
,
$B
) =
map
{
$_
->[0][
$o
->{col}] || 0 }
$a
,
$b
;
$d
=
$o
->{n}
?
$o
->{r} ?
$B
<=>
$A
:
$A
<=>
$B
:
$o
->{r} ?
$B
cmp
$A
:
$A
cmp
$B
and
return
$d
;
}
return
$d
;
};
@data
= (
@h
,
sort
$sort
@data
);
}
if
(
$opt_c
&&
@data
) {
$opt_c
->
print
(
*STDOUT
,
$_
->[0])
for
@data
;
next
;
}
$opt_L
||
$sort_order
or
next
;
if
(
defined
$opt_n
) {
unshift
@w
,
length
$data
[-1][0][0];
unshift
@align
,
""
;
}
$opt_n
= 0;
if
(
$opt_L
) {
foreach
my
$r
(0 ..
$#data
) {
my
$R
=
$data
[
$r
][0];
foreach
my
$c
(0 ..
$#$R
) {
my
$d
=
$R
->[
$c
];
my
$l
=
length
$d
;
$l
>
$w
[
$c
] and
$w
[
$c
] =
$l
;
$r
&&
$d
=~ m/\S/ or
next
;
$d
=~ m/^(?:-|\s*(?:-\s*)?[0-9][0-9 .,]*)$/ or
$align
[
$c
] =
"-"
;
}
}
if
(
$skip_empty
) {
foreach
my
$i
(
reverse
0 ..
$#w
) {
$w
[
$i
] and
next
;
splice
@w
,
$i
, 1;
splice
@align
,
$i
, 1;
splice
@$_
,
$i
, 1
for
grep
{
$#$_
>=
$i
}
map
{ @{
$_
} }
@data
;
}
}
}
$opt_B
and line (1,
$opt_s
=>
map
{
"-"
x
$w
[
$_
] } 0..$
for
(
@data
) {
my
(
$row
,
$att
) =
@$_
;
my
@row
=
@$row
;
for
(0 ..
$#row
) {
my
$l
=
length
$row
[
$_
];
my
$w
=
$l
<
$w
[
$_
] ?
" "
x (
$w
[
$_
] -
$l
) :
""
;
if
(
$align
[
$_
]) {
$_
<
$#row
||
$opt_B
and
$row
[
$_
] .=
$w
;
}
else
{
substr
$row
[
$_
], 0, 0,
$w
;
}
if
(
$opt_A
) {
substr
$row
[
$_
], 0, 0, ansi_color (@{
$att
->[
$_
]});
$row
[
$_
] .=
"\e[0m"
;
}
}
line (0,
$opt_s
=>
@row
);
++
$opt_n
==
$opt_h
and
line (2,
$opt_s
=>
map
{
"-"
x
$w
[
$_
] } 0..
$#row
);
}
$opt_B
and line (3,
$opt_s
=>
map
{
"-"
x
$w
[
$_
] } 0..$
}
$opt_H
and
print
"</body>\n</html>\n"
;
sub
line {
my
$uh
=
"\x{2500}"
;
my
$uv
=
"\x{2502}"
;
my
@tpl
= (
$uv
,
"\x{250c}"
,
"\x{251c}"
,
"\x{2514}"
);
my
@tpm
= (
$uv
,
"\x{252c}"
,
"\x{253c}"
,
"\x{2534}"
);
my
@tpr
= (
$uv
,
"\x{2510}"
,
"\x{2524}"
,
"\x{2518}"
);
my
$type
=
shift
;
my
$sep
=
shift
||
" $uv "
;
my
@dta
=
@_
;
if
(
$opt_L
) {
$type
> 0 and
$sep
=~ s/ /-/g;
if
(
$sep
=~ m/
$uv
/) {
$sep
=~ s/-/
$uh
/g;
$sep
=~ s/
$uv
/
$tpm
[
$type
]/;
if
(
$type
> 0) {
s/-/
$uh
/g
for
grep
{
length
}
@dta
;
}
}
elsif
(
$type
> 0) {
$sep
=~ s/\|/+/;
}
}
my
$line
=
join
$sep
=>
@dta
;
if
(
$opt_B
) {
(
my
$s1
=
$sep
) =~ s/^(?:\s|
$uh
)[\x{2500}-\x{25ff}]/
$tpl
[
$type
]/;
substr
$line
, 0, 0,
$s1
;
(
my
$s2
=
$sep
) =~ s/[\x{2500}-\x{25ff}](?:\s|
$uh
)$/
$tpr
[
$type
]/;
$line
.=
$s2
;
}
!
$enc_o
&& Encode::is_utf8 (
$line
) and
$line
= encode (
"utf-8"
,
$line
);
print
$line
;
print
"\n"
;
}
END {
if
(
$opt_v
>= 7) {
my
%seen
;
print
"\nNon-CORE modules loaded:\n"
,
"-"
x 25,
" "
,
"--------\n"
;
foreach
my
$mod
(
sort
keys
%INC
) {
my
$path
=
$INC
{
$mod
};
$mod
=~ s{\.pm$}{} or
next
;
$mod
=~ s{/}{::}g;
$path
=~ s{.*/site_perl/}{} or
next
;
grep
{
$mod
=~ m/^${_}::/ }
keys
%seen
and
next
;
$seen
{
$mod
}++;
my
$v
=
$mod
->VERSION () ||
eval
"\$${mod}::VERSION"
||
"?"
;
printf
"%-25s %s\n"
,
$mod
,
$v
;
}
}
}