#!/usr/bin/perl use Data::Dumper; my %OPT; my @HDRLIST = (); foreach my $arg ( @ARGV ) { if ( $arg =~ /^\-+(.+?)\=(.*)/ ) { $OPT{$1} = $2; } elsif ( $arg =~ /^\-+(.+)/ ) { $OPT{$1} = -1; } else { push ( @HDRLIST, $arg ); } } my $DEBUGGING = $OPT{'debug'} || 0; my $INMODE = lc($OPT{'mode'}) || lc($OPT{'in'}) || 'auto'; # supported modes: tab, csv, hash, xml, json, lf/load/loadfile $INMODE = 'json' if ( defined ( $OPT{'j'} ) || defined ( $OPT{'json'} ) ); $INMODE = 'lf' if ( $INMODE eq 'load' ); $INMODE = 'lf' if ( $INMODE eq 'loadfile' ); my $OUMODE = lc($OPT{'out'}) || $INMODE; my $DELIMITER = $OPT{'delimiter'} || $OPT{'delim'}; $OPT{'noheader'} ||= $OPT{'noheaders'}; $OPT{'header'} ||= $OPT{'headers'}; exit &main(); sub main { my $buffer; my $headers_ar; my %keepers = (); foreach my $k ( @HDRLIST ) { $keepers{$k} = 1; } &parse_headers(\$buffer, \$headers_ar); # also sets $INMODE if "all". print ( "#0450# ".Dumper($headers_ar) ) if ( $DEBUGGING ); $OUMODE = $INMODE if ( $OUMODE eq 'auto' ); &load_modules(); # based on $INMODE and $OUMODE, try to load needed modules. This late binding permits us to use jselect with degraded functionality when insufficient modules are installed on the local machine to support every mode. if ( $OUMODE eq 'tab' || $OUMODE eq 'csv' || $OUMODE eq 'lf' ) { my $caret = ''; $caret = '^' if ( $OUMODE eq 'lf' ); my $out_delim = ','; $out_delim = "\t" if ( $OUMODE eq 'tab' ); $out_delim = '^|^' if ( $OUMODE eq 'lf' ); if ( defined ( $OPT{'all'} ) ) { # zzapp -- FIXME: How to generate headers when input is headerless and output is headerful? Punting for now, only generating headers if $headers_ar is populated. if ( scalar(@{$headers_ar}) > 0 ) { if ( $OUMODE eq 'csv' ) { my $x = ''; foreach my $hdr ( @{$headers_ar} ) { $x .= to_plaintext ( $hdr ) . ","; } chop ( $x ); print ( "$x\n" ); } else { print ( $caret . join ( $out_delim, @{$headers_ar} ) . $caret . "\n" ) if ( scalar(@{$headers_ar}) > 0 ); } } } else { # not 'all' if ( $OUMODE eq 'csv' ) { my $x = ''; foreach my $hdr ( @HDRLIST ) { $x .= to_plaintext ( $hdr ) . ","; } chop ( $x ); print ( "$x\n" ); } else { print ( $caret . join ( $out_delim, @HDRLIST ) . $caret . "\n" ); } } # END of if 'all' } # END of if $OUMODE tab, csv, lf ### BEGIN MAIN LOOP: while ( 1 ) { my $x; if ( defined($buffer) ) { $x = $buffer; undef $buffer; } else { $x = ; $x = $1 if ( $x =~ /(.*?)[\r\n]+$/ ); # chomp() doesn't dtrt when data originates from MS-DOS or MacOS users. } last unless ( defined ( $x ) ); my $hr = &text_to_href ( $x, $headers_ar ); my $needs_to_be_flattened = 0; foreach my $k ( keys ( %{$hr} ) ) { if ( defined ( $OPT{'all'} ) || defined ( $keepers{$k} ) ) { $needs_to_be_flattened = 1 if ( ref($hr->{$k}) ); } else { delete $hr->{$k} unless ( defined ( $OPT{'all'} ) || defined ( $keepers{$k} ) ); } } next if ( defined($OPT{'where'} ) && !where_filter ( $OPT{'where'}, $hr ) ); next if ( defined($OPT{'wheref'}) && !where_filter ( rd_f($OPT{'wheref'}), $hr ) ); $hr = flatten_complex_structure ( $hr ) if ( $needs_to_be_flattened && ( $OUMODE ne 'json' ) && ( $OUMODE ne 'xml' ) && ( $OUMODE ne 'dumper' ) ); if ( $OUMODE eq 'dumper' ) { $x = Dumper ( $hr ); } else { $x = &href_to_text ( $hr, $headers_ar, $needs_to_be_flattened ); # use $needs_to_be_flattened to determine if partial matching on $headers_ar/@HDRLIST is necessary. } print ( "$x\n" ) unless ( $x eq '' ); } ### END OF MAIN LOOP return 0; } sub where_filter { my ( $full_expression, $hr ) = @_; # TODO: parse parenthesized expressions $full_expression = join ( ' ', split ( /[\r\n]+/s, $full_expression ) ); foreach my $and_clause ( split ( /(\&\&)|(AND)/, $full_expression ) ) { my $resolution = 0; next if ( $and_clause eq '&&' ); next if ( $and_clause eq 'AND' ); next if ( $and_clause =~ /^\s*$/ ); foreach my $ex ( split ( /(\|\|)|(OR)/, $and_clause ) ) { next if ( $ex eq '||' ); next if ( $ex eq 'OR' ); next if ( $ex =~ /^\s*$/ ); if ( $ex =~ /\s*([^\s]+)\s*([^\s]+)\s*([^\s]+)\s*$/ ) { my ( $term1, $op, $term2 ) = ( $1, $2, $3 ); $term1 = '$hr->{"'.$term1.'"}' if ( defined ( $hr->{$term1} ) ); $term2 = '$hr->{"'.$term2.'"}' if ( defined ( $hr->{$term2} ) ); my $perl_expr = "$term1 $op $term2"; $res = eval($perl_expr); $resolution ||= $res; # print ("## '$perl_expr' = $res\n".Dumper($hr)."\n"); } else { die ("parse error in --where expression: [$ex]"); } } return 0 unless ( $resolution ); } return 1; } sub flatten_complex_structure { my ( $r ) = @_; my $ret_hr = {}; if ( ref($r) eq 'HASH' ) { foreach my $k ( keys ( %{$r} ) ) { if ( !ref($r->{$k}) ) { $ret_hr->{$k} = $r->{$k}; } else { my $t_hr = flatten_complex_structure ( $r->{$k} ); foreach my $subkey ( %{$t_hr} ) { $ret_hr->{"$k.$subkey"} = $t_hr->{$subkey}; } } # END of if..else ref() } # END of foreach $k } # END of if HASH elsif ( ref($r) eq 'ARRAY' ) { for ( my $i = 0; $i < scalar(@{$r}); $i++ ) { if ( !ref($r->[$i]) ) { $ret_hr->{$i} = $r->[$i]; } else { my $t_hr = flatten_complex_structure ( $r->[$i] ); foreach my $subkey ( %{$t_hr} ) { $ret_hr->{"$i.$subkey"} = $t_hr->{$subkey}; } } # END of if..else ref() } # END of for $i } # END of if ARRAY return ( $ret_hr ); } sub parse_headers { # if $INMODE warrants, read first line of input and parse out the headers (if any) and set $INMODE if "auto". my ( $buffer_sr, $headers_arr ) = @_; return if ( $INMODE eq 'hash' ); # No headers. return if ( $INMODE eq 'xml' ); # No headers. return if ( $INMODE eq 'json' ); # No headers. return if ( defined($OPT{'noheader'}) ); my $buf = ; my $got_headers = 0; chomp ( $buf ); if ( $INMODE eq 'auto' ) { # JUST guessing mode here, not exhaustively parsing ( $INMODE, $got_headers, $buf ) = &best_guess ( $buf ); print ("#0800# INMODE=$INMODE got_headers=$got_headers buf=$buf\n") if ( $DEBUGGING ); } if ( $INMODE eq 'unk' ) { print ( "ERROR\tcannot guess input format\n"); exit(1); } if ( $INMODE eq 'tab' ) { # MAY or MAY NOT have headers. Leave \$headers_arr undef if no headers and push data back into \$buffer_sr. # Unquoted tab-delimited text format is HARD to guess about! Headers may look exactly like the data. Making wild-ass guesses here. The wise user will use --header or --noheader for tab-delimited input. $got_headers = defined($OPT{'header'}) || ( $buf =~ /^\d+\t/ ) || ( $buf =~ /\t\d+$/ ) || ( $buf =~ /\t\d+\t/ ); $got_headers = $got_headers && ( $buf !~ /[ \?]/ ) && ( $buf !~ /\ttrue\t/i ) && ( $buf !~ /\tfalse\t/i ); if ( $got_headers ) { $$headers_arr = []; push ( @{$$headers_arr}, split ( /\t/, $buf, -1 ) ); } } elsif ( $INMODE eq 'csv' ) { # MAY or MAY NOT have headers. Leave \$headers_arr undef if no headers and push data back into \$buffer_sr. # Another hard one. Punting like a wimp. FIXME dammit. $got_headers = 1; if ( $got_headers ) { $$headers_arr = []; push ( @{$$headers_arr}, &tokenize_csv ( $buf, ($DELIMITER || ','), 1 ) ); } } elsif ( $INMODE eq 'lf' ) { # MUST have headers. my $caret = substr ( $buf, 0, 1 ); my $rex_caret = $caret; $rex_caret = '\136' if ( $caret eq '^' ); if ( $buf !~ /^$rex_caret[^$rex_caret]+$rex_caret(.)$rex_caret/ ) { print ("ERROR\tcannot determine loadfile delimiter\n"); exit(1); } $DELIMITER = "$caret$1$caret"; if ( $buf !~ /^$rex_caret(.+?)$rex_caret$/ ) { print ("ERROR\tmalformed loadfile header\n"); exit(1); } $buf = $1; $$headers_arr = []; my $esc_DELIMITER = "\\" . join ( "\\", split ( //, $DELIMITER ) ); push ( @{$$headers_arr}, split ( $esc_DELIMITER, $buf, -1 ) ); $got_headers = 1; } $$buffer_sr = $buf unless ( $got_headers || defined ( $$buffer_sr ) ); # $$buffer_sr might have been set above, in which case we do not want to clobber it. return; } sub tokenize_csv { my ( $raw, $delim, $strip_quotes ) = @_; $strip_quotes = 0 unless ( defined ( $strip_quotes ) ); $delim = $delim || ' '; my %delh = (); print ("tokenize_csv: delim=$delim raw=$raw\n") if ( $DEBUGGING > 1 ); foreach my $c ( split ( //, $delim, -1 ) ) { $delh{$c} = 1; } my @d = split ( //, $raw, -1 ); push ( @d, ' ' ); my @toklist = (); my $buf = ''; my $state = 0; # 0: tween strings, 1: in ' string, 2: in " string, 3: in non-string for ( my ( $i, $k ) = ( 0, scalar(@d)-1 ); $i < $k; $i++ ) { if ( $state == 0 ) { my $c = $d[$i]; next if ( defined ( $delh{$c} ) ); if ( $c eq "'" ) { $buf .= $c; $state = 1; } elsif ( $c eq '"' ) { $buf .= $c; $state = 2; } else { $buf .= $c; $state = 3; } } elsif ( $state == 1 ) { my $c = $d[$i]; if ( ( $c eq '\\' ) && ( ($i+1) >= $k ) ) { $buf .= $c; } elsif ( ( $c eq "'" ) && ( $d[$i+1] eq "'" ) ) { $i++; $buf .= $d[$i]; } elsif ( $c eq '\\' ) { $i++; $buf .= slashcoded ( $d[$i] ); } elsif ( $c eq "'" ) { $buf .= $c; push ( @toklist, $buf ); $buf = ''; $state = 0; } else { $buf .= $c; } } elsif ( $state == 2 ) { my $c = $d[$i]; if ( ( $c eq '\\' ) && ( ($i+1) >= $k ) ) { $buf .= $c; } elsif ( ( $c eq '"' ) && ( $d[$i+1] eq '"' ) ) { $i++; $buf .= $d[$i]; } elsif ( $c eq '\\' ) { $i++; $buf .= $d[$i]; } elsif ( $c eq '"' ) { $buf .= $c; push ( @toklist, $buf ); $buf = ''; $state = 0; } else { $buf .= $c; } } elsif ( $state == 3 ) { my $c = $d[$i]; if ( defined ( $delh{$c} ) ) { push ( @toklist, $buf ); $buf = ''; $state = 0; } else { $buf .= $c; } } } if ( $strip_quotes ) { for ( my $i = 0; $i < scalar(@toklist); $i++ ) { # print ("stripping ($strip_quotes) '$toklist[$i]'\n"); $toklist[$i] = $1 if ($toklist[$i] =~ /^[\"\'](.*?)[\"\']$/ ); } } else { # print ("not stripping ($strip_quotes)\n"); } return ( @toklist ); } sub slashcoded { my ( $c ) = @_; ### THESE ARE NOT TYPOS! We do not actually want these characters embedded in our output text! return '\n' if ( $c eq 'n' ); return '\r' if ( $c eq 'r' ); return '\t' if ( $c eq 't' ); ### END of not typos. If you see something suspicious below this line, I likely screwed the pooch. return $c; } # ( $INMODE, $got_headers, $buf ) = &best_guess ( $buf ); # overwrite $$buffer_sr if we have to read *another* line from STDIN. # supported modes: tab, csv, hash, xml, json, lf/load/loadfile sub best_guess { my ( $buf ) = @_; while ( defined($buf) && ( $buf =~ /^\s*$/ ) ) { $buf = ; chomp ( $buf ) if ( defined ( $buf ) ); } return ( 'hash', 0, $buf ) if ( $buf =~ /\t([^\=]+)\=/ ); return ( 'json', 0, $buf ) if ( $buf =~ /^\s*[\{\[]/ ); return ( 'json', 0, $buf ) if ( $buf =~ /^\s*\/\// ); return ( 'lf', 1, $buf ) if ( ( $buf =~ /^\^/ ) && ( $buf =~ /\^\|\^/ ) ); return ( 'lf', 1, $buf ) if ( ( $buf =~ /^\376/ ) && ( $buf =~ /\376\024\376/ ) ); return ( 'tab', $OPT{'header'}, $buf ) if ( $buf =~ /\t[^\t\=\<]+\t/ ); # zzapp -- improve on this. return ( 'csv', $OPT{'header'}, $buf ) if ( $buf =~ /\,[^\,\=\<]+\,/ ); # zzapp -- improve on this. return ( 'xml', 0, $buf ) if ( $buf =~ /\<\?xml/i ); return ( 'xml', 0, $buf ) if ( $buf =~ /\{$keylist[0]}; } if ( ref($hr) eq 'ARRAY' ) { my $ar = $hr; $hr = {}; for ( my $i = 0; $i < scalar(@{$ar}); $i++ ) { $hr->{$i} = $ar->[$i]; } } return $hr; } # END of xml if ( $INMODE eq 'json' ) { $text = &unescape_string ( $text ) if ( defined ( $OPT{'unescape'} ) ); if ( $JSON::VERSION < 2 ) { $hr = jsonToObj ( $text ); } else { $hr = from_json ( $text ); } if ( ref($hr) eq 'ARRAY' ) { my $ar = $hr; $hr = {}; for ( my $i = 0; $i < scalar(@{$ar}); $i++ ) { $hr->{$i} = $ar->[$i]; } } return $hr; } # END of json if ( $INMODE eq 'lf' ) { my $caret = substr ( $DELIMITER, 0, 1 ); my $rex_caret = $caret; $rex_caret = '\136' if ( $caret eq '^' ); $text = &unescape_string ( $text ) if ( defined ( $OPT{'unescape'} ) ); $text = $1 if ( $text =~ /^$rex_caret(.+?)$rex_caret[\r\n]*$/s ); my $esc_DELIMITER = "\\" . join ( "\\", split ( //, $DELIMITER ) ); populate_href_from_array ( $hr, $headers_ar, split ( $esc_DELIMITER, $text, -1 ) ); return $hr; } return undef; } # supported modes: tab, csv, hash, xml, json, lf/load/loadfile sub populate_href_from_array { my ( $hr, $headers_ar, @tok ) = @_; my $i = 0; foreach my $v ( @tok ) { my $k = $i; $k = $headers_ar->[$i] if ( defined ( $headers_ar ) ); print ( "#0300# i=$i k=$k v=$v\n") if ( $DEBUGGING ); if ( defined ( $hr->{$k} ) ) { if ( ref($hr->{$k}) ) { push ( @{$hr->{$k}}, $v ); } else { my $t = $hr->{$k}; $hr->{$k} = []; push ( @{$hr->{$k}}, $t, $v ); } } else { $hr->{$k} = $v; } $i++; } return; } sub string_to_hash { my ( $str, $septok, $unescape, $list_separator, $default_name ) = @_; $list_separator = ' ' unless ( defined ( $list_separator ) ); $default_name = 'name' unless ( defined ( $default_name ) ); $septok = "\t" unless ( defined ( $septok ) ); $unescape = 0 unless ( defined ( $unescape ) ); $str = "name=$str" if ( $str =~ /^[^\=]+\t/ ); my $hr = {}; foreach my $x ( split ( /$septok+/s, $str ) ) { my ( $k, $v ) = ( $x, 1 ); ( $k, $v ) = ( $1, $2 ) if ( $x =~ /(.+?)\=(.+)/ ); ( $k, $v ) = ( unescape_string($k), unescape_string($v) ) if ( $unescape != 0 ); if ( defined ( $hr->{$k} ) ) { if ( ref ( $hr->{$k} ) ) { push ( @{$hr->{$k}}, $v ); } else { my $t = $hr->{$k}; $hr->{$k} = []; push ( @{$hr->{$k}}, $t, $v ); } } else { $hr->{$k} = $v; } } return $hr; } sub hash_to_string_given_headers { my ( $hr, $septok, $escape, $list_separator, $header_ar ) = @_; $list_separator = ' ' unless ( defined ( $list_separator ) ); $septok = ' ' if ( !defined($septok) ); $escape = 0 if ( !defined($escape) ); my $str = ''; foreach my $key ( @{$header_ar} ) { next unless ( defined ( $hr->{$key} ) ); my $value = $hr->{$key}; next if ( ref($value) && ( ref($value) ne 'ARRAY' ) ); $value = join ( $list_separator, @{$value} ) if ( ref($value) && ( ref($value) eq 'ARRAY' ) ); $value = &escape_string($value) if ( $escape != 0 ); $key = &escape_string($key) if ( $escape != 0 ); $str .= "$key=$value$septok"; } for ( my $i = 0; $i < length($septok); $i++ ) { chop ( $str ); } return ( $str ); } # supported modes: tab, csv, hash, xml, json, lf/load/loadfile # $text = &href_to_text ( $hr, $headers_ar ); sub href_to_text { my ( $hr, $headers_ar ) = @_; my $xml_capsule = $OPT{'xml-delimiter'} || 'row'; my $json_begin = '{'; $json_begin = '[' if ( defined ( $hr->{0} ) ); my $json_end = '}'; $json_end = ']' if ( $json_begin eq '[' ); my $encapsulate_xml = 1; $encapsulate_xml = 0 if ( scalar(keys(%{$hr})) < 2 ); my $text = ''; $text = '^' if ( $OUMODE eq 'lf' ); $text = "\<$xml_capsule\>" if ( $encapsulate_xml && ( $OUMODE eq 'xml' ) ); $text = $json_begin if ( $OUMODE eq 'json' ); if ( defined($OPT{'all'}) ) { if ( scalar(@{$headers_ar}) > 0 ) { foreach my $k ( @{$headers_ar} ) { $text .= construct_one_field ( $hr, $k, $json_begin ) if ( defined ( $hr->{$k} ) ); } } else { # zzapp -- FIXME: this implementation could potentially produce a different number of fields per line, which will break some formats (tab, csv, lf). foreach my $k ( sort keys ( %{$hr} ) ) { $text .= construct_one_field ( $hr, $k, $json_begin ); } } # END of if scalar() .. else } # END of if all else { # user has specified on the command line which fields to emit foreach my $k ( @HDRLIST ) { $text .= construct_one_field ( $hr, $k, $json_begin ); } } # END of if !all if ( $OUMODE eq 'tab' || $OUMODE eq 'csv' || $OUMODE eq 'hash' ) { chop ( $text ); } if ( $OUMODE eq 'lf' ) { for ( my $i = 0; $i < ( length($DELIMITER) || 3 ) - 1; $i++ ) { chop ( $text ); } } $text .= "\<\/$xml_capsule\>" if ( $encapsulate_xml && ( $OUMODE eq 'xml' ) ); chop ( $text ) if ( $OUMODE eq 'json' ); $text .= $json_end if ( $OUMODE eq 'json' ); return $text; } sub to_plaintext { my ( $text ) = @_; return ref($text) if ( ref($text) ); if ( defined ( $OPT{'escape'} ) ) { $text = &escape_string ( $text ); } else { $text = join ( ' ', split ( /\n+/, $text, -1 ) ) if ( defined ( $OPT{'eat-lines'} ) ); $text = join ( ' ', split ( /\n/, $text, -1 ) ); $text = join ( '', split ( /\r/, $text, -1 ) ); $text = join ( '\t', split ( /\t/, $text, -1 ) ); $text = join ( ' ', split ( /\s+/, $text, -1 ) ) if ( defined ( $OPT{'eat-spaces'} ) ); } if ( $OUMODE eq 'csv' ) { $text = join ( "''", split ( /\'/, $text, -1 ) ); $text = join ( '""', split ( /\"/, $text, -1 ) ); $text = '"'.$text.'"' unless ( $text =~ /^\-?\d+\.?\d*$/ ); } elsif ( $OUMODE eq 'xml' ) { # gawd, xml @#$%^& sucks $text = join ( '&', split ( /\&/, $text, -1 ) ); $text = join ( '"', split ( /\"/, $text, -1 ) ); $text = join ( ''', split ( /[\'\`]/, $text, -1 ) ); $text = join ( '<', split ( /\/, $text, -1 ) ); } elsif ( $OUMODE eq 'tab' ) { # la la la! tab-delimited anarchy! } else { $text = join ( "\\'", split ( /\'/, $text, -1 ) ); $text = join ( '\"', split ( /\"/, $text, -1 ) ); } return $text; } sub construct_one_field { my ( $hr, $k, $json_begin ) = @_; my $text = ''; print ("#0200# k=$k hr=$hr->{$k}\n") if ( $DEBUGGING ); if ( $OUMODE eq 'tab' ) { if ( defined ( $hr->{$k} ) ) { $text .= to_plaintext ( $hr->{$k} ) . "\t"; } else { $text .= $DELIMITER; } } elsif ( $OUMODE eq 'csv' ) { if ( defined ( $hr->{$k} ) ) { $text .= to_plaintext ( $hr->{$k} ) . ( $OPT{'csv-delimiter'} || "," ); } else { $text .= $DELIMITER; } } elsif ( $OUMODE eq 'hash' ) { if ( defined ( $hr->{$k} ) ) { $text .= "$k=" . to_plaintext ( $hr->{$k} ) . "\t"; } } elsif ( $OUMODE eq 'xml' ) { if ( !ref($hr->{$k}) ) { $text .= "\<$k\>".to_plaintext($hr->{$k})."\<\/$k\>"; } elsif ( ref($hr->{$k}) eq 'HASH' ) { $text .= "\<$k\>".to_complextext ( XMLout ( $hr->{$k} ) )."\<\/$k\>"; } elsif ( ref($hr->{$k}) eq 'ARRAY' ) { my $t_hr = { $k => $hr->{$k} }; $text .= to_complextext ( XMLout ( $t_hr->{$k} ) ); } } elsif ( $OUMODE eq 'json' ) { $text .= "\"$k\":" if ( $json_begin eq '{' ); if ( ref($hr->{$k}) ) { $text .= to_complextext ( struct2json ( $hr->{$k} ) ); } else { $text .= to_complextext ( $hr->{$k} ); } $text .= ","; } elsif ( $OUMODE eq 'lf' ) { $text .= to_plaintext ( $hr->{$k} ); $text .= '^|^'; } return $text; } sub json2struct { my ( $text ) = @_; if ( $JSON::VERSION < 2 ) { return jsonToObj ( $text ); } else { return from_json ( $text ); } } sub struct2json { my ( $hr ) = @_; if ( $JSON::VERSION < 2 ) { return objToJson ( $hr ); } else { return to_json ( $hr ); } } sub to_complextext { my ( $text ) = @_; $text = join ( ' ', split ( /\n+/, $text, -1 ) ) if ( defined ( $OPT{'eat-lines'} ) ); $text = join ( ' ', split ( /\n/, $text, -1 ) ); $text = join ( '', split ( /\r/, $text, -1 ) ); $text = join ( '\t', split ( /\t/, $text, -1 ) ); $text = join ( ' ', split ( /\s+/, $text, -1 ) ) if ( defined ( $OPT{'eat-spaces'} ) ); $text = '"'.$text.'"' if ( ( $OUMODE eq 'json' ) && ( $text !~ /^\-?\d+\.?\d*$/ ) ); return $text; } sub rd_f { my ( $fn ) = @_; my ( $fh, $buf, $aline ); open ( $fh, "<$fn" ) or return ("ERROR cannot open '$fn'"); $buf = ''; while ( defined ( $aline = <$fh> ) ) { $buf .= $aline; } close ( $fh ); return ( $buf ); }