User:Lar/ClassificationTableGen/Backlev
Appearance
Perl code: This code generated User:Lar/Sandbox2 (version 6).. There is a lot of work to do on it yet but if you stumble across this, feedback welcome. Not ready for public release yet (if ever).
Updated as of ++Lar: t/c 05:06, 27 March 2006 (UTC)
#!/usr/bin/perl -w #---------------------------------------------------------------------------# # process files and generate a category table # Author: Larry Pieniazek (IBM/Ascential Software) as hobby project # Adapted from stuff I cribbed from all over. # (c)Larry Pieniazek 2006. This library is free software; you can redistribute it # and/or modify it under the same terms as Perl itself. # additionally, can be redistributed and modified under GFDL or CC-SA as you choose # # Abstract: # This perlscript is designed to parse category SQL dumps from wikipedia # which are found here: http://download.wikimedia.org/enwiki/ # For example the 23 March dump is called # http://download.wikimedia.org/enwiki/20060323/enwiki-20060323-categorylinks.sql.gz # # The parsing is to generate article classification tables such as those found at # http://en.wikipedia.org/wiki/Wikipedia_talk:WikiProject_The_Beatles/Article_Classification # # In addition to the dump (currently must have been converted to linefeed delimited tuples) # the other input is a list of categories of interest, one per line. # #---------------------------------------------------------------------------# use strict; use Data::Dumper; use Getopt::Std; # things we may want to use at some point # use File::Spec::Functions; #---------------------------------------------------------------------------# # Subroutine prototypes: # #---------------------------------------------------------------------------# # setup sub Usage; # print info message about how to use this sub ProcessOptions; # Process Command Line Options. # utility sub ScoreToBlank; # underscores to blanks sub BlankToScore; # blanks to underscores sub FlipComma; # reverse a reversed comma string. "Lennon, John" -> "John Lennon" sub UnEscape; # remove escapes with a clever rexp # general sub ReadCatFile; # read the category file into the catArray and is_catHash sub ParseSQL; # parse the big SQL file and build the article data (hashref $collect) sub WriteTable; # create the output sub WriteTableHeader; # used by above, create header sub WriteTableSecBreak; # used by above, create a section break (when the leading char changes) # ------ option switches and related --------- my(%options); # hash of switches, values # ----- logging NOT IMPLEMENTED YET (ever?) -- my($logging); # Flag to denote we are writing to log. my($log_dir); # Log Directory. my($lfh); # Log File Handle. my($LOG_FILE_NAME); # Name of Log File to be written -l value or default # my($verbose); # -v Flag to denote verbose messaging. my($debug); # -d Flag to denote REALLY verbose messaging. my($sqlFileName); # -q <file name of SQL file to parse> (or 'enwiki-20060303-categorylinks.sql') my($catFileName); # -c <file name of categories> (or 'categoryList.txt') my($tableFileName); # -o <table file to create> (or 'tables.txt') # ------ Data structures --------------------- my $inCats=(); # what cats is the article in? my $nameVersions=(); # what are the versions of the name (lex orders) my $rec={}; # ref to one article's record my $collect={}; # ref to all the articles keyed on the $artKey var # what the data will look like # my $rec={ # key => "178234", # numeric key from first tuple value (article key, believed unique) # artLink => "link text" # text to use for link not same as sort # sortKey => "sort text" # sort text (what order should article come out) # inCats => [@inCats], # array of categories the article is in # nameVersions => [@nameVersions] # array of version of the name of the article # # this one may not be used for anything # }; # one article's record # # my %collect={ # key => $rec # }; # all the articles keyed on the $artKey var # ------ work vars ---------------------------- my @catList; my @catArray; my %is_catHash; # file handles my $sqlH; my $tableH; my $cfH; #---------------------------------------------------------------------------# # Usage - Print Usage Information and exit. #---------------------------------------------------------------------------# sub Usage { print <<END_USAGE; Usage: $0 [-h] [-v] [-d] [-q <sqlFile>] [-c <catFile>] [-o <tableFile>] Switch meanings: -h --help print this help message. -v --version print version message. -d <0|1|2|3> debug: 0: quiet 1: Verbose Mode 2: REALLY verbose mode 3: Every frigging detail. File switches: -q <file name of SQL file to parse> (or 'enwiki-20060303-categorylinks_sample.sql' by default) -c <file name of categories> (or 'categoryList.txt' by default) -o <table file to create> (or 'tables.txt' by default) END_USAGE print "Status: 99\n"; exit(99); } # End of Usage. sub Version { print "\nfilterCategories version 0.04 - 26 March 2006, Larry Pieniazek." ." \n -- released under GFDL and CC-SA -- \n\n"; # really should print something else } # this stuff isn't quite right at the moment # required for getopts to support --help and --version sub HELP_MESSAGE{ &Usage(); } # required for getopts to support --help and --version sub VERSION_MESSAGE{ &Version(); } #---------------------------------------------------------------------------# # ProcessOptions - Process Command Line Options. #---------------------------------------------------------------------------# sub ProcessOptions { &Version if ($options{'v'}); &Usage if ($options{'h'}); my %debugHash = ( '0'=>"silent" , '1'=>"normal trace", '2'=>"very chatty", '3'=>"insanely chatty" ); if (defined $options{'d'}) { $debug=$options{'d'}; if ($debugHash{$debug}) { print"...debug switch was ".$options{'d'}." giving setting: ".$debugHash{$debug}."\n" unless 0 == $options{'d'} ; # if 0, then REALLY quiet } else { $debug=1; print"...debug switch was ".$options{'d'}." defaulting debug to 1 - normal trace\n"; } # recognised option } else { # default, no switch $debug=1; print"...debug switch not found, defaulting debug to 1 - normal trace\n"; } if (defined $options{'q'}) { $sqlFileName=$options{'q'}; } else { $sqlFileName="enwiki-20060303-categorylinks_sample.sql"; } if (defined $options{'c'}) { $catFileName=$options{'c'}; } else { $catFileName="categoryList.txt"; } if (defined $options{'o'}) { $tableFileName=$options{'o'}; } else { $tableFileName="tables.txt"; } } # End of ProcessOptions. #---------------------------------------------------------------------------# # ReadCatFile - read in categories to build article tracking tables for #---------------------------------------------------------------------------# sub ReadCatFile { my $rc=0; # $catFileName = $_[0]; # now set processOptions() if ($debug>2) { stat($catFileName); print "Exists\n" if -e _; print "Readable\n" if -r _; print "Writable\n" if -w _; print "Executable\n" if -x _; print "Setuid\n" if -u _; print "Setgid\n" if -g _; print "Sticky\n" if -k _; print "Text\n" if -T _; print "Binary\n" if -B _; } if (( -e $catFileName ) && ( -r $catFileName )) { if (!open $cfH, "<", $catFileName){ warn "can't open ".$catFileName."\n"; $rc=99; return $rc; } } else { print "error with ".$catFileName." ... does not exist or not readable \n"; $rc= 99; return $rc; } %is_catHash = (); if ($debug>0) {print "reading ".$catFileName."\n";} # @catList=<$cfH>; my $catListItem; for (;;) { undef $!; unless (defined( $catListItem = <$cfH> )) { die $! if $!; last; # reached EOF } chomp $catListItem; $catListItem=ScoreToBlank($catListItem); push @catList, $catListItem; # set up searchable hash... $is_catHash{$catListItem} = 1; } if ($debug>0) { print "\nCategories to process: \n"; for my $fe(@catList) {print( $fe."\n");}; print "\n"; } if ($debug>1) { print "\n\n... corresponding hash values: \n"; while (my ($key, $value) = each %is_catHash) { print "$key = $value\n"; } print "\n"; } # end chatty trace $rc=0; return $rc; } #---------------------------------------------------------------------------# # ScoreToBlank - convert underscores to blanks #---------------------------------------------------------------------------# sub ScoreToBlank { my $str=$_[0]; if ($debug>3) {print "ScoreToBlank \$str IN: $str\n";} $str=~ s/_/ /g; if ($debug>3) {print "ScoreToBlank \$str OUT: $str\n";} return $str; } # there #---------------------------------------------------------------------------# # BlankToScore - convert blanks to underscores #---------------------------------------------------------------------------# sub BlankToScore { my $str=$_[0]; $str=~ s/ /_/g; return $str; } # and back again #---------------------------------------------------------------------------# # FlipComma - take a phrase with comma (and 1 blank) and flip it, # "Lennon, John" -> "John Lennon" #---------------------------------------------------------------------------# sub FlipComma { my $str=$_[0]; my ($first,$second)= split(/, /,$str,2); if (length($second)>0) { # there is something there to flip $str=$second." ".$first; } return $str; } # round and round we go #---------------------------------------------------------------------------# # StripLeadTrail - strip leading s/^\s+// and trailing s/\s+$// blanks #---------------------------------------------------------------------------# sub StripLeadTrail { my $str=$_[0]; $str=~ s/^\s+//; $str=~ s/\s+$//; return $str; } # and back again #---------------------------------------------------------------------------# # UnEscape - remove escape chars unless they're escaped # this code lifted from John Alden's Escape Delimiters # http://search.cpan.org/src/JOHNA/Text-EscapeDelimiters-1.004/lib/Text/EscapeDelimiters.pm # Text::EscapeDelimiters v1.004 # (c) John Alden 2005. This library is free software; you can redistribute it # and/or modify it under the same terms as Perl itself. #---------------------------------------------------------------------------# sub UnEscape { my($string) = $_[0]; my $eseq = "\\"; return $string unless($eseq); #no-op #Remove escape characters apart from double-escapes $string =~ s/\Q$eseq\E(?!\Q$eseq\E)//gs; #Fold double-escapes down to single escapes $string =~ s/\Q$eseq$eseq\E/$eseq/gs; return $string; } #---------------------------------------------------------------------------# # ParseSQL - read through the SQL file and build the data structures # - read in one tuple at a time (currently one line but change to # buffered read later) # - for each tuple parse out the pieces we need # - add or update record in $collect hash, recording category and lexical key # (if we find a comma reversed version of the article name, it's probably # a better lexical key than we have so take it.) # - update lexical key, article name, category seen # - possibly strip blanks, change _ to blanks, remove \ escapes, # and reverse comma fields. (future: use list of articles with commas # in their names as refinement) #---------------------------------------------------------------------------# sub ParseSQL { my $rc=0; if (( -e $sqlFileName ) && ( -r $sqlFileName )) { open ($sqlH, "<", $sqlFileName) or die "can't open ".$sqlFileName." for reading \n"; } else { print "error with ".$sqlFileName." ... does not exist or not readable \n"; $rc=99; return $rc; } if ($debug>0) {print "reading ".$sqlFileName."\n"; } my $sqlLine; my $sqlLC=0; $Data::Dumper::Indent = 2; # pretty print (3 is with array indices $Data::Dumper::Useqq = 1; # print strings in double quotes $Data::Dumper::Pair = " : "; # specify hash key/value separator $Data::Dumper::Purity = 1; # fill in the holes for eval $Data::Dumper::Maxdepth = 3; # no deeper than 3 refs down $Data::Dumper::Deepcopy = 1; # deep copy for (;;) { undef $!; unless (defined( $sqlLine = <$sqlH> )) { die $! if $!; last; # reached EOF } # we have to process lines that look like any of these # (12731,'Catholics_not_in_communion_with_Rome','George Harrison',20060228150212), # ordinary # (12731,'Deaths_by_lung_cancer','Harrison, George',20050904074730), # sort order is different (the article name is probably the first 12731 that doesn't # have a comma in the article name # (12731,'George_Harrison','',20060303000936), # self ref... the category contains an article named the same thing # (2246703,'The_Beatles_songs','Don\'t Pass Me By',20050719071328), # embedded escaped ' will screw up parse if not careful. # safe to process line as we got a line $sqlLC++; if ($debug>2) { print "line ".$sqlLC." was ".$sqlLine."\n"; } chomp $sqlLine; my($firstP, $secondP) = split(/',/, $sqlLine,2); if ($debug>2) { print "firstP: >".$firstP."< secondP: >".$secondP."< \n";} my($artKey, $catName) = split(/,'/,$firstP,2); $artKey=substr($artKey,1); $catName=ScoreToBlank($catName); if ($debug>2) { print "artKey: >".$artKey."< catName: >".$catName."< \n"; } my($artName, $timeStamp)=split(/',/,substr($secondP,1),2); # $timeStamp=split(/),/,$timeStamp,1); $timeStamp=substr($timeStamp,0,-2); if ($debug>2) {print "artName: >".$artName."< timeStamp: >".$timeStamp."< \n";} if (0==length($artName)) { # empty, this is the case of matching art/cat names $artName=$catName; } else { $artName=StripLeadTrail(UnEscape($artName)); } my $sortKey=""; my $skHasComma=0; my $anHasComma=0; if (exists($is_catHash{$catName}) ) { if ($debug>1) { print "artName: >".$artName. "<\n timeStamp: >".$timeStamp. "<\n artKey: >".$artKey. "<\n catName: >".$catName."< \n"; print " ... one of our cats! \n"; } if (exists($collect->{$artKey}) ) { if ($debug>1) { print " ... and we have the article already\n"; }; $rec = $collect->{$artKey}; # get ref to existing one $inCats= $rec->{inCats}; # and to the arrays it carries $nameVersions = $rec->{nameVersions}; } else { $rec={}; # make an empty one $rec->{key}=$artKey; # uses same key $inCats=(); $nameVersions=(); } $inCats->{$catName}=1; $nameVersions->{$artName}=1; $rec->{'inCats'}=$inCats; $rec->{'nameVersions'}=$nameVersions; # put logic to handle making sure name of article for link is non comma $anHasComma= ( $artName =~/,/ ); my $artNameSave=$artName; if ($anHasComma) { # if article has comma flip it and save that as name $artNameSave=FlipComma($artName); } if (!(exists($rec->{artLink}))) { $rec->{artLink}=$artNameSave; } if ($debug>1) {print "\$artName: $artName \$artNameSave: $artNameSave \n"} # put logic for sort key here if (exists($rec->{sortKey})) { $sortKey=$rec->{sortKey}; if ($debug>1) {print "sortKey: $sortKey\n"; } if ($sortKey ne $artName) { # If the keys are the same do nothing $skHasComma= ( $sortKey =~/,/ ); if ($debug>1) {print "anHasComma: $anHasComma skHasComma: $skHasComma\n";} if ($anHasComma eq $skHasComma) { # if neither has a comma, or both have a comma take whichever one is earlier in the alphabet if ($sortKey gt $artName) { $rec->{sortKey}= $artName; } # else not needed because sortKey already earlier, leave it. } else { # If the new key has a comma in it, use that one, it's probably the sort key if ($anHasComma) { $rec->{sortKey}= $artName; } # else not needed, leave as is } if ($debug>1) {print "sortKey now is ".$rec->{sortKey}."\n"; } } # end of handling different keys } else { # we don't have it, save it away $rec->{sortKey}=$artName; # since it's new, the sort key is the name we found if ($debug>1) {print "added sortKey: $rec->{sortKey}\n"; } } # end if sortKey does/doesn't exist $collect->{$artKey}=$rec; } # end if category is one we care about } # end for (;;) (the read loop) if ($debug>0) { print "...collect: \n"; print Dumper($collect); } if ($debug>0) {print "finished parsing SQL\n"; } return $rc; } # end ParseSQL #---------------------------------------------------------------------------# # WriteTableHeader - create output table header #---------------------------------------------------------------------------# sub WriteTableHeader { # assumes that $tableH is open and valid print $tableH <<END_TABLEH; {| |valign=top| {| width="100%" border="1" cellpadding="2" cellspacing="0" style="margin: 1em 1em 1em 0; background: #f9f9f9; border: 1px #aaa solid; border-collapse: collapse; font-size: 85%;" |- !width=20%|Article !width=15%|Categories !width=7%|Assessed !width=7%|Status !width=5%|Uses Infobox !width=37%|Comments and Pending tasks !width=8%|Assessed by END_TABLEH return 0; } #---------------------------------------------------------------------------# # WriteTableSecBreak - create output table break between sections #---------------------------------------------------------------------------# sub WriteTableSecBreak { my $headChar=$_[0]; print $tableH "|-\n|colspan=\"7\" align=\"left\" style=\"background:white; font-size: 200%;" ." font-weight:bold; border-bottom:4px solid grey; \"| \n" ."====".$headChar."====\n"; return 0; } # end WriteTableSecBreak #---------------------------------------------------------------------------# # WriteTable - create output table # - sort the data structure by the sort keys (which are the lexical # (sometimes comma inverted) article names) ... these keys are inside the # structure # - using the sorted array of keys, iterate the hash in sort order # - every time the first letter of the key changes, write out a SecBreak #---------------------------------------------------------------------------# sub WriteTable { my $rc=0; if ($debug>2) { print" statting: ".$tableFileName."\n"; stat($tableFileName); print "Exists\n" if -e _; print "Readable\n" if -r _; print "Writable\n" if -w _; print "Executable\n" if -x _; print "Setuid\n" if -u _; print "Setgid\n" if -g _; print "Sticky\n" if -k _; print "Text\n" if -T _; print "Binary\n" if -B _; } open ($tableH, '>', $tableFileName) or die "can't open ".$tableFileName." for writing \n"; $rc=&WriteTableHeader(); if ($rc) { die "error building table header\n"; } # we want to create line pairs of the form # (with the pipe in col 1) # |- # |[[Abbey Road (album)]]||[[:category:The Beatles albums|]]|| ||{{/Unknown}}||unknown|| || # |- # |[[Anthology 1]]||[[:category:The Beatles albums|]]|| ||{{/Unknown}}||unknown|| || # # in sorted order # make an array of the keys to the hash # (the article keys, which are not in any particular alpha) my @keys = sort { $collect->{$a}->{sortKey} # custom sort spec, use the lexical key cmp # (which is embedded in the rec) $collect->{$b}->{sortKey} } keys %{$collect}; my $firstLet=chr(00); # has to be lower than any other character val! # iterate in sorted order foreach my $artKey ( @keys ) { $rec = $collect->{$artKey}; # get easy access to the record $inCats= $rec->{inCats}; # and to the category array it carries my $artLink=$rec->{artLink}; my $trialFirst=substr($rec->{sortKey},0,1); # get first char if ($trialFirst ne $firstLet) { $firstLet=$trialFirst; if ($debug>1) {print "Switching to new first letter: $firstLet \n";} &WriteTableSecBreak($firstLet); } # end if new first letter in lexical order my ($catStr,$catV,$catK); $catStr=""; while (($catK, $catV) = each %{$inCats}) { if ("" ne $catStr) { $catStr.="<br>"; } $catStr.="[[:category:".$catK."|]]"; } # loop through the categories we saw if ($debug>0) { print "key: ".$artKey." rec key ".$rec->{key}." article Link text ".$artLink."\n"; } print $tableH "|-\n||[[".$artLink."]]||".$catStr."||| ||" ."{{Wikipedia:WikiProject The Beatles/Article Classification/Unknown}}" ."||unknown|| || \n"; } # end of iteration through the hash in sorted order # finish off table print $tableH "\n|}"; return $rc; } #---------------------------------------------------------------------------# # Main routine - # process options # read in categories desired # build hash of articles by parsing SQL file # write out table file using hash #---------------------------------------------------------------------------# # main my $rc=0; # print "prior to getopts\n"; getopts('hvd:q:c:o:', \%options) or &Usage; # debug also d # print "post getopts, pre process\n"; &ProcessOptions(); if ($debug>1) { print "post process, pre read cat\n"; } $rc=&ReadCatFile(); if ($rc) { die "error reading category list\n"; } $rc=&ParseSQL(); if ($rc) { die "error reading SQL or building structure\n"; } $rc=&WriteTable(); if ($rc) { die "error building table\n"; } exit 0;