#!/usr/bin/perl -w # NAME: sortcss.pl # AIM: Read a file of CSS, in this case taken from my site, # and 'sort' it - that is put each element together # 02/08/2013 - have no default input file # 11/07/2010 - checkout, and added input file name - parse_arg and # output the tags with width formatting... # 05/07/2008 geoff mclane http://geoffair.net/mperl specialised # Decode of CSS file # Skip C comments - /* ... */ # Starts with *, ., # or character is start of tag # other tag names can follow after comma (,) # definition begins with '{', and ends with '}' ... use strict; use warnings; use File::Basename; # split path ($name,$dir,$ext) = fileparse($file [, qr/\.[^.]*/] ) use Cwd; my $os = $^O; my $perl_dir = '/home/geoff/bin'; my $PATH_SEP = '/'; my $temp_dir = '/tmp'; if ($os =~ /win/i) { $perl_dir = 'C:\GTools\perl'; $temp_dir = $perl_dir; $PATH_SEP = "\\"; } unshift(@INC, $perl_dir); require 'lib_utils.pl' or die "Unable to load 'lib_utils.pl' Check paths in \@INC...\n"; # log file stuff our ($LF); my $pgmname = $0; if ($pgmname =~ /(\\|\/)/) { my @tmpsp = split(/(\\|\/)/,$pgmname); $pgmname = $tmpsp[-1]; } my $outfile = $temp_dir.$PATH_SEP."temp.$pgmname.txt"; open_log($outfile); # user variables my $VERS = "0.0.2 2013-08-02"; my $uin_file = ''; my $out_file = $temp_dir.$PATH_SEP.'tempcss2.css'; my $max_tag_width = 20; my $load_log = 0; # features my $out_nice_txt = 0; my $out_repeats = 0; # ### DEBUG ### my $debug_on = 0; my $def_file = 'C:\HOMEPAGE\GA\page2.css'; # DEBUG my $dbg1 = 0; # show start and end of C comments my $dbg2 = 0; # show "$i:$lnnum:$colm:$tagnum: Got tag [$tag] ... CLOSED my $dbg3 = 0; # show "$tagset { $tagtxt } ($lnnum) my $dbg4 = 0; # show "File = $actfile [$comtxt] ... my $dbg5 = 0; # show "NO MATCH [$tag] ne [$ttag] - VERY, VERY NOISY!!! my $dbg6 = 0; # show "Searching for tag [$tag] ...\n" ) my $verbosity = 0; ### program variables my @warnings = (); sub VERB1() { return $verbosity >= 1; } sub VERB2() { return $verbosity >= 2; } sub VERB5() { return $verbosity >= 5; } sub VERB9() { return $verbosity >= 9; } sub show_warnings($) { my ($val) = @_; if (@warnings) { prt( "\nGot ".scalar @warnings." WARNINGS...\n" ); foreach my $itm (@warnings) { prt("$itm\n"); } prt("\n"); } else { prt( "\nNo warnings issued.\n\n" ) if (VERB9()); } } sub pgm_exit($$) { my ($val,$msg) = @_; if (length($msg)) { $msg .= "\n" if (!($msg =~ /\n$/)); prt($msg); } show_warnings($val); close_log($outfile,$load_log); exit($val); } sub prtw($) { my ($tx) = shift; $tx =~ s/\n$//; prt("$tx\n"); push(@warnings,$tx); } # expect /\s*\w\s*:\s*.+;{0,1}/ repeated sub nice_tag_text($) { my ($tt) = shift; $tt = trim_all($tt); my $len = length($tt); # got for a simple thing ... my @arr = split(';',$tt); my $asz = scalar @arr; my $ntt = ''; for (my $k = 0; $k < $asz; $k++) { my $ti = trim_all($arr[$k]); $ntt .= "\n" if length($ntt); $ntt .= " $ti;"; } return $ntt; } sub set_line_col($$$) { my ($ch1, $colm1, $lnnum1) = @_; $$colm1++; if ($ch1 eq "\n") { $$colm1 = 0; $$lnnum1++; } } sub trim_array_items($) { my (@ar) = @_; my $l = scalar @ar; for (my $p = 0; $p < $l; $p++) { $ar[$p] = trim_all($ar[$p]); } return @ar; } # usage: $wmsg = compare_done_tags($tag, $tagtxt, @tagtxtdone); sub compare_done_tags($$$) { my ($tg, $tt, @done) = @_; my $sz = scalar @done; my @arr1 = split(';',$tt); my $al1 = scalar @arr1; my ($m, $n, $p, $q, $same, $itm1, $itm2); @arr1 = trim_array_items(@arr1); for ($m = 0; $m < $sz; $m++) { my $ttg = $done[$m][0]; ###if ($tg eq $ttg) { if (lc($tg) eq lc($ttg)) { my $ttt = $done[$m][1]; my @arr2 = split(';',$ttt); my $al2 = scalar @arr2; @arr2 = trim_array_items(@arr2); $same = 0; for ($n = 0; $n < $al1; $n++) { $itm1 = $arr1[$n]; # for each attribute in passed tag text my @arr3 = trim_array_items(split(':',$itm1)); # split attribute 'margin:0' my $al3 = scalar @arr3; for ($p = 0; $p < $al2; $p++) { # now of done itme $itm2 = $arr2[$p]; # extract tag text item, split on ';' my @arr4 = trim_array_items(split(':',$itm2)); # get split on ':'; my $al4 = scalar @arr4; if ($al3 == $al4) { # if two splits are the same for ($q = 0; $q < $al3; $q++) { # try to correct case, at least on some, like color if ($q == 1) { if (substr($arr3[$q],0,1) eq '#') { $arr3[$q] = lc($arr3[$q]); } if (substr($arr4[$q],0,1) eq '#') { $arr4[$q] = lc($arr4[$q]); } } if ($arr3[$q] ne $arr4[$q]) { last; } } if ($q == $al3) { $same++; } } } } if ($same == $al1) { return "repeat"; } } # tags are equal } return ""; } sub process_in_file($) { my $in_file = shift; my @lines = (); my $line = ''; my $lncnt = 0; my %taglist = (); my @tagarray = (); my $wmsg = ''; my @tagtxtdone = (); my @tagoutput = (); my ($txt, $i, $ch, $len, $pch, $incom, $lnnum); my ($colm, $tag, $intag, $tagtxt, $tagset, $tagnum); my ($bgnln, $nch, $comtxt, $actfile, $tagp2); if (open INF, "<$in_file") { @lines = ; close INF; $lncnt = scalar @lines; $txt = join('', @lines); $len = length($txt); prt( "Got $lncnt lines to process ... $len characters ... from $in_file\n" ); $pch = ''; $incom = 0; $lnnum = 1; $colm = 0; $tag = ''; $comtxt = ''; $actfile = ''; for ($i = 0; $i < $len; $i++) { $ch = substr($txt,$i,1); set_line_col($ch, \$colm, \$lnnum); if ($incom) { $comtxt .= $ch; if ($ch eq '/') { if ($pch eq '*') { $incom = 0; prt( "$i:$lnnum:$colm: Exit comment ...\n" ) if ($dbg1); ###if ($comtxt =~ /\w{1}:[\\\/]\w+/) { if ($comtxt =~ /\/\*.*(\w{1}:[\\\/]\w+.*)\*\//) { $actfile = $1; prt( "File = $actfile [$comtxt] ...\n" ) if ($dbg4); } } } } else { if ($ch eq '*') { if ($pch eq '/') { $incom = 1; prt( "$i:$lnnum:$colm: Entered comment ...\n" ) if ($dbg1); $comtxt = $pch.$ch; } } elsif ($ch =~ /[\w#\.\*]/) { $tag = $ch; $i++; $intag = 1; $tagset = ''; $tagnum = 0; $bgnln = $lnnum; while ($intag) { $tagnum++; for (; $i < $len; $i++) { $ch = substr($txt,$i,1); set_line_col($ch, \$colm, \$lnnum); if ($ch =~ /[\s,\{]/) { $i++; last; } $tag .= $ch; } while (($i < $len)&&($ch =~ /\s/)) { # notes SPACE can also separate tags, so got to next non-space, but ... # EAT space while (($ch =~ /\s/)&&($i < $len)) { $ch = substr($txt,$i,1); set_line_col($ch, \$colm, \$lnnum); $i++; } if (($ch ne ',') && ($ch ne '{') && !($ch =~ /\s/) ) { # ok, it seems this BELONGS to the tag ??? $tag .= ' '.$ch; for (; $i < $len; $i++) { $ch = substr($txt,$i,1); set_line_col($ch, \$colm, \$lnnum); if ($ch =~ /[\s,\{]/) { $i++; last; } $tag .= $ch; } } } if ($ch eq '{') { prt( "$i:$lnnum:$colm:$tagnum: Got tag [$tag] ... CLOSED \n" ) if ($dbg2); } else { prt( "$i:$lnnum:$colm:$tagnum: Got tag [$tag] ... ($ch)\n" ) if ($dbg2); } if (defined $taglist{$tag}) { $taglist{$tag}++; } else { $taglist{$tag} = 1; } $tagset .= '|' if length($tagset); $tagset .= $tag; $tag = ''; # if we have a COMMA, eat spaces until next non-space char if (($ch eq ',')&&($i < $len)) { $nch = substr($txt,$i,1); while (($i < $len)&&($nch =~ /\s/)) { $i++; $nch = substr($txt,$i,1); set_line_col($nch, \$colm, \$lnnum); } } $intag = 0 if ($ch eq '{'); } $tagtxt = ''; if ($ch eq '{') { for (; $i < $len; $i++) { $ch = substr($txt,$i,1); set_line_col($ch, \$colm, \$lnnum); if ($ch eq '}') { last; } $tagtxt .= $ch; $pch = $ch; } $tagtxt =~ s/\n/ /g; $tagtxt = trim_all($tagtxt); prt( "Stored: [$tagset] { $tagtxt } ($lnnum)\n" ) if ($dbg3); push(@tagarray, ["$tagset", $tagtxt, $bgnln, 0, $actfile]); } else { mydie( "$i:$lnnum:$colm: No close tag ...\n" ); } } } $pch = $ch; } } else { prt( "ERROR: Can NOT open $in_file ... check name. location \n" ); } if (@tagarray) { my (@tags, $tgcnt, $j, $fnd, $ttag,$min,$len,$ctag); $tagnum = scalar @tagarray; $lnnum = scalar keys(%taglist); prt( "***** RESULTS **** Options: out_nice_txt=$out_nice_txt, out_repeats=$out_repeats\n" ); prt( "Got $tagnum tags to sort ... $lnnum different tags ...\n" ); $min = 0; foreach $tag (keys %taglist) { $len = length($tag); $min = $len if ($len > $min); last if ($min > $max_tag_width); } $min = $max_tag_width if ($min > $max_tag_width); foreach $tag (sort keys %taglist) { $fnd = 0; $ctag = $tag; $ctag .= ' ' while (length($ctag) < $min); prt( "Searching for tag [$tag] ...\n" ) if ($dbg6); for ($i = 0; $i < $tagnum; $i++) { $tagset = $tagarray[$i][0]; $tagtxt = $tagarray[$i][1]; $bgnln = $tagarray[$i][2]; $actfile = $tagarray[$i][4]; @tags = split(/\|/,$tagset); $tgcnt = scalar @tags; for ($j = 0; $j < $tgcnt; $j++) { $ttag = $tags[$j]; if ($ttag eq $tag) { $fnd++; $tagarray[$i][3]++; $wmsg = compare_done_tags($tag, $tagtxt, @tagtxtdone); push(@tagtxtdone, [$tag, $tagtxt, $actfile]); push(@tagoutput, [$tag, $tagtxt, $actfile]) if ($wmsg ne 'repeat'); if ($out_nice_txt) { if ($out_repeats) { prt( "$tag {\n".nice_tag_text($tagtxt)."\n} /* $bgnln $actfile $wmsg*/\n" ); } elsif ($wmsg ne 'repeat') { prt( "$tag {\n".nice_tag_text($tagtxt)."\n} /* $bgnln $actfile $wmsg*/\n" ); } } else { if ($out_repeats) { prt( "$ctag { $tagtxt } /* $bgnln $actfile $wmsg */ \n" ); } elsif ($wmsg ne 'repeat') { prt( "$ctag { $tagtxt } /* $bgnln $actfile $wmsg */ \n" ); } } } else { prt( "NO MATCH [$tag] ne [$ttag]\n" ) if ($dbg5); } } } if (!$fnd) { $wmsg = "WARNING: tag [$tag] NOT FOUND!"; prt( "$wmsg\n" ); } } $fnd = 0; for ($i = 0; $i < $tagnum; $i++) { if ($tagarray[$i][3] == 0) { $fnd++; } } if ($fnd) { prt( "\nWARNING: It appears $fnd items NOT utilised ...\n" ); for ($i = 0; $i < $tagnum; $i++) { if ($tagarray[$i][3] == 0) { $tagset = $tagarray[$i][0]; $tagtxt = $tagarray[$i][1]; $bgnln = $tagarray[$i][2]; $actfile = $tagarray[$i][4]; prt( "[$tagset] { $tagtxt } /* $bgnln $actfile */ \n" ); } } prt( "CHECK THESE!!!\n" ); } else { prt( "Appears ALL entries in tagarray covered ... as they should be ...\n" ); } } ###my $out_file = 'tempcss2.css'; if (@tagoutput) { my $cnt = scalar @tagoutput; my $lastfile = ''; if (open OUTF, ">$out_file") { print OUTF "/* CSS SUMMARY - ".scalar localtime(time()). " */\n"; $tagnum = scalar @tagoutput; $lastfile = ''; for ($i = 0; $i < $tagnum; $i++) { $tagset = $tagoutput[$i][0]; next if ($tagset =~ /#/); next if (!($tagset =~ /^\./)); next if (length($tagset) > 2); $tagtxt = $tagoutput[$i][1]; $actfile = $tagoutput[$i][2]; $wmsg = ''; $wmsg = "/* $actfile */" if length($actfile) && ($lastfile ne $actfile); print OUTF "$tagset {\n".nice_tag_text($tagtxt)."\n} $wmsg\n\n"; $lastfile = $actfile; } $lastfile = ''; for ($i = 0; $i < $tagnum; $i++) { $tagset = $tagoutput[$i][0]; next if ($tagset =~ /#/); next if (($tagset =~ /^\./)&&(length($tagset) <= 2)); $actfile = $tagoutput[$i][2]; $tagtxt = $tagoutput[$i][1]; $wmsg = ''; $wmsg = "/* $actfile */" if length($actfile) && ($lastfile ne $actfile); print OUTF "$tagset {\n".nice_tag_text($tagtxt)."\n} $wmsg\n\n"; $lastfile = $actfile; } $lastfile = ''; for ($i = 0; $i < $tagnum; $i++) { $tagset = $tagoutput[$i][0]; next if !($tagset =~ /#/); next if (($tagset =~ /^\./)&&(length($tagset) <= 2)); $tagtxt = $tagoutput[$i][1]; $wmsg = ''; $wmsg = "/* $actfile */" if length($actfile) && ($lastfile ne $actfile); print OUTF "$tagset {\n".nice_tag_text($tagtxt)."\n} $wmsg\n\n"; $lastfile = $actfile; } print OUTF "/* eof - $out_file */\n"; close OUTF; prt( "Written file $out_file ...\n" ); } } } ################################ # ==================================== # MAIN # ==================================== parse_args(@ARGV); process_in_file($uin_file); pgm_exit(0,""); # ===================================== sub need_arg { my ($arg,@av) = @_; pgm_exit(1,"ERROR: [$arg] must have a following argument!\n") if (!@av); } sub parse_args { my (@av) = @_; my ($arg,$sarg); while (@av) { $arg = $av[0]; if ($arg =~ /^-/) { $sarg = substr($arg,1); $sarg = substr($sarg,1) while ($sarg =~ /^-/); if (($sarg =~ /^h/i)||($sarg eq '?')) { give_help(); pgm_exit(0,"Help exit(0)"); } elsif ($sarg =~ /^v/) { if ($sarg =~ /^v.*(\d+)$/) { $verbosity = $1; } else { while ($sarg =~ /^v/) { $verbosity++; $sarg = substr($sarg,1); } } prt("Verbosity = $verbosity\n") if (VERB1()); } elsif ($sarg =~ /^l/) { if ($sarg =~ /^ll/) { $load_log = 2; } else { $load_log = 1; } prt("Set to load log at end. ($load_log)\n") if (VERB1()); } elsif ($sarg =~ /^o/) { need_arg(@av); shift @av; $sarg = $av[0]; $out_file = $sarg; prt("Set out file to [$out_file].\n") if (VERB1()); } else { pgm_exit(1,"ERROR: Invalid argument [$arg]! Try -?\n"); } } else { $uin_file = $arg; prt("Set input to [$uin_file]\n") if (VERB1()); } shift @av; } if ($debug_on) { prtw("WARNING: DEBUG is ON!\n"); if (length($uin_file) == 0) { $uin_file = $def_file; prt("Set DEFAULT input to [$uin_file]\n"); } } if (length($uin_file) == 0) { pgm_exit(1,"ERROR: No input files found in command!\n"); } if (! -f $uin_file) { pgm_exit(1,"ERROR: Unable to find in file [$uin_file]! Check name, location...\n"); } } sub give_help { prt("$pgmname: version $VERS\n"); prt("Usage: $pgmname [options] in-css-file\n"); prt("Options:\n"); prt(" --help (-h or -?) = This help, and exit 0.\n"); prt(" --verb[n] (-v) = Bump [or set] verbosity. def=$verbosity\n"); prt(" --load (-l) = Load LOG at end. ($outfile)\n"); prt(" --out (-o) = Write output to this file.\n"); } # eof - sortcss.pl