Biblio-COUNTER
view release on metacpan or search on metacpan
lib/Biblio/COUNTER/Report.pm view on Meta::CPAN
use constant YTD_TOTAL => 'ytd';
# Metrics
use constant REQUESTS => 'requests';
use constant SEARCHES => 'searches';
use constant SESSIONS => 'sessions';
use constant TURNAWAYS => 'turnaways';
# Field matching
use constant MAY_BE_BLANK => 0;
use constant NOT_BLANK => 1;
use constant EXACT_MATCH => 2;
# Useful constants
use constant INVALID => 0;
use constant VALID => 1;
use constant FIXED => 2;
# --- Variables
my %mon2num = qw(
jan 01
feb 02
mar 03
apr 04
may 05
jun 06
jul 07
aug 08
sep 09
oct 10
nov 11
dec 12
);
my @num2mon = qw(
---
jan
feb
mar
apr
may
jun
jul
aug
sep
oct
nov
dec
);
my $rx_mon = qr/(?i)jan|feb|mar|apr|may|june?|july?|aug|sept?|oct|nov|dec|0[1-9]|1[0-2]/;
my $rx_year = qr/(?:2[012])?\d\d/; # Good through 2299
# ------------------------------------------------------------ PUBLIC METHODS --
sub new {
my ($cls, %args) = @_;
bless {
'treat_blank_counts_as_zero' => 0,
'change_not_available_to_blank' => 0,
'dont_reread_next_row' => 0,
%args,
}, $cls;
}
sub process {
my ($self) = @_;
$self->begin_file
->begin_report
->process_header
->process_body
->end_report
->end_file;
}
# ---------------------------------------------- TOP-LEVEL STRUCTURAL METHODS --
sub begin_file {
my ($self) = @_;
$self->trigger_callback('begin_file', $self->{'file'});
}
sub end_file {
my ($self) = @_;
$self->trigger_callback('end_file', $self->{'file'});
}
sub begin_report {
my ($self) = @_;
$self->trigger_callback('begin_report');
$self->_orient;
}
sub end_report {
my ($self) = @_;
$self->{'is_valid'} = !$self->{'errors'};
$self->trigger_callback('end_report');
undef $self->{'fh'};
return $self;
}
sub process_header {
my ($self) = @_;
$self->begin_header;
$self->process_header_rows;
$self->end_header;
}
sub process_header_rows {
die "Every report must have its own header-processing code";
}
sub process_body {
my ($self) = @_;
$self->_in_scope(RECORD);
$self->begin_body;
while (!$self->_eof) {
$self->begin_record;
$self->process_record;
$self->end_record;
lib/Biblio/COUNTER/Report.pm view on Meta::CPAN
$self->_in_field(LABEL)->_must_match($str, $rx);
}
sub begin_header {
my ($self) = @_;
my $hdr = $self->{'container'} = $self->{'header'} = {
'name' => $self->canonical_report_name,
'description' => $self->canonical_report_description,
'code' => $self->canonical_report_code,
'release' => $self->release_number,
};
$self->trigger_callback('begin_header', $hdr);
return $self;
}
sub end_header {
my ($self) = @_;
my $hdr = $self->{'header'};
$self->trigger_callback('end_header', $hdr);
return $self;
}
sub begin_record {
my ($self) = @_;
my $rec = $self->{'container'} = $self->{'record'} = {};
$self->trigger_callback('begin_record', $rec);
return $self;
}
sub end_record {
my ($self) = @_;
my $rec = $self->{'record'};
push @{ $self->{'records'} ||= [] }, $rec;
$self->trigger_callback('end_record', $rec);
return $self;
}
# ----------------------------------------------------------- PRIVATE METHODS --
# --- Record field checking methods
sub _check_field {
my ($self, $field, $check) = @_;
$self->_in_field($field);
my $container = $self->{'container'};
my $cur = $self->_ref_to_cur_cell;
$self->_trim($cur);
if ($check->($self, $field, $cur)) {
$container->{$field} = $$cur;
}
return $self;
}
sub _check_free_text_field {
my ($self, $field, $mode, $str) = @_;
if ($mode == EXACT_MATCH) {
$str = '' unless defined $str;
$self->_check_field($field, _exact_match_sub($str));
}
elsif ($mode == NOT_BLANK) {
$self->_check_field($field, \&_is_not_blank);
}
else {
$self->_check_field($field, \&_is_anything);
}
$self->_next;
}
sub _not_available {
my ($self) = @_;
if ($self->{'change_not_available_to_blank'}) {
$self->_fix('');
}
else {
$self->_cant_fix('<count>');
}
return $self;
}
sub _check_count {
my ($self, $field, $period) = @_;
my $cur = $self->_ref_to_cur_cell;
$self->_trim($cur);
my $val = $$cur;
my $container = $self->{'container'};
if (defined $period) {
# Usage for a particular period
my ($result, $normalized_period);
($result, $period, $normalized_period) = $self->parse_period($period);
if ($val =~ /^\d+$/) {
if ($result != INVALID) {
$container->{'count'}->{$normalized_period}->{$field} = $val;
$self->trigger_callback('count', $self->{'scope'}, $field, $period, $val);
}
}
elsif ($val eq '') {
if ($self->{'treat_blank_counts_as_zero'}) {
$container->{'count'}->{$normalized_period}->{$field} = $val;
$self->trigger_callback('count', $self->{'scope'}, $field, $period, 0);
}
}
elsif ($val =~ m{^n/a$}i) {
$self->_not_available;
}
else {
$self->_cant_fix('<count>');
}
}
else {
# YTD usage
if ($val =~ /^\d+$/) {
$container->{'count'}->{$field} = $val;
$self->trigger_callback("count_$field", $self->{'scope'}, $field, $val);
}
elsif ($val eq '') {
if ($self->{'treat_blank_counts_as_zero'}) {
$container->{'count'}->{$field} = $val;
$self->trigger_callback("count_$field", $self->{'scope'}, $field, 0);
}
}
elsif ($val =~ m{^n/a$}i) {
$self->_not_available;
}
else {
$self->_cant_fix('<count>');
}
}
$self->_next;
}
sub _exact_match_sub {
# Return a ref to code that compares the current cell's value to the given string
my ($str) = @_;
return sub {
my ($self, $field, $cur) = @_;
$cur ||= $self->_ref_to_cur_cell;
if ($$cur eq $str) {
$self->_ok($cur);
}
else {
$self->_cant_fix($str);
}
return $self;
};
}
sub _force_exact_match_sub {
# Return a ref to code that forces the current cell's value to the given string
my ($str) = @_;
return sub {
my ($self, $field, $cur) = @_;
if ($$cur eq $str) {
$self->_ok($cur);
}
else {
$self->_fix($str);
}
return $self;
};
}
sub _is_yyyymmdd {
my ($self) = @_;
my $cur = $self->_ref_to_cur_cell;
my $val = $$cur;
if ($val =~ /^(\d\d\d\d)-(\d\d)-(\d\d)$/) {
# Nothing to do
return $self->_ok($cur);
}
elsif ($val =~ m{^(\d\d?)/(\d\d?)/(\d\d)?(\d\d)$}) {
# Ack! Try to fix
if ($1 < 13 && $2 >= 13) {
# mm/dd/(cc)?yy
return $self->_fix(sprintf('%02d%02d-%02d-%02d', $3 || 20, $4, $1, $2));
}
elsif ($2 < 13 && $1 >= 13) {
# dd/mm/(cc)?yy
return $self->_fix(sprintf('%02d%02d-%02d-%02d', $3 || 20, $4, $2, $1));
}
}
return $self->_cant_fix('<yyyy-mm-dd>');
}
sub _is_anything {
my ($self) = @_;
$self->_trim;
}
sub _is_issn {
my ($self, $field, $cur) = @_;
$self->_trim;
my $val = $$cur;
if (length $val) {
if ($val =~ /^\d{4}-\d{3}[\dX]$/) {
$self->_ok($cur);
}
elsif ($val =~ /^(\d{3,4})-?(\d{3})([\dXx])$/) {
$self->_fix(sprintf("%04d-%03d%s", $1, $2, lc $3));
}
else {
$self->_cant_fix('<issn>');
}
}
return $self;
}
sub _is_count {
my ($self, $field, $cur) = @_;
if ($$cur =~ /^\d+$/) {
$self->_ok($cur);
}
else {
$self->_cant_fix('<count>');
}
return $self;
}
sub _is_not_blank {
my ($self, $field, $cur) = @_;
if ($$cur eq '') {
$self->_cant_fix('<not blank>');
return;
}
else {
$self->_ok($cur);
}
return $self;
}
sub _must_match {
my ($self, $str, $rx) = @_;
$rx ||= _str2rx($str);
my $cur = $self->_ref_to_cur_cell;
$self->_trim($cur);
if ($$cur eq $str) {
$self->_ok($cur);
}
elsif ($$cur =~ /$rx/) {
$self->_fix($str);
}
else {
$self->_cant_fix($str);
}
$self->_next;
}
sub _read_next_line {
my ($self) = @_;
# Fetch the next line
my $fh = $self->{'fh'};
my $line = <$fh>;
return unless defined $line;
chomp $line;
$self->trigger_callback('line', $.);
$self->trigger_callback('input', $line);
return $line;
}
sub _read_next_row {
my ($self) = @_;
if ($self->{'dont_reread_next_row'}) {
$self->{'dont_reread_next_row'} = 0;
return $self->{'row'};
}
my $line = $self->_read_next_line;
return unless defined $line;
$line =~s/\x0d$//; # Strip CR at end of line
my $begin_row = $self->{'row'} = [ $self->_parse_line($line) ];
push @{ $self->{'rows'} }, $begin_row;
$self->{'r'}++;
$self->{'c'} = 'A';
return $begin_row;
}
sub _parse_line {
my ($self, $line) = @_;
chomp $line;
if ($line =~ /\t/) {
( run in 1.170 second using v1.01-cache-2.11-cpan-aadc1410aed )