package Lire::XMLSpecParser;
use strict;
use base qw/Lire::DocBookParser Lire::Config::Parser/;
use Lire::ReportSpec;
use Lire::FilterSpec;
use Lire::FilterExpr;
use Lire::Average;
use Lire::First;
use Lire::Last;
use Lire::Min;
use Lire::Max;
use Lire::Sum;
use Lire::Count;
use Lire::Group;
use Lire::GroupField;
use Lire::Rangegroup;
use Lire::Records;
use Lire::Timegroup;
use Lire::Timeslot;
use Lire::Param;
use Lire::Config::ListSpec;
use Lire::Config::ChartSpec;
=pod
=head1 NAME
Lire::XMLSpecParser - Creates Lire::XMLSpecContainer object form XML files.
=head1 SYNOPSIS
use Lire::XMLSpecParser;
my $parser = new Lire::XMLSpecParser;
my $spec = $parser->parsefile( 'top-files.xml' );
=head1 DESCRIPTION
This is a Lire::XMLParser subclass that can be used to creates
Lire::ReportSpec and Lire::FilterSpec objects from XML files.
=cut
my @aggregators_mix = qw/lrsml:group lrsml:rangegroup lrsml:records
lrsml:timegroup lrsml:timeslot/;
my @aggregates_mix = qw/lrsml:sum lrsml:avg lrsml:min lrsml:max
lrsml:first lrsml:last lrsml:count/;
my @scalar_filters_mix = qw/lrsml:eq lrsml:ne lrsml:gt lrsml:ge
lrsml:lt lrsml:le lrsml:match lrsml:value/;
my @compound_filters_mix = qw/lrsml:and lrsml:or lrsml:not/;
my @meta_infos_mix = qw/lrsml:title lrsml:description
lrsml:display-spec lrsml:param-spec/;
my %spec = (
'lrsml:report-spec' =>
{ 'start' => 'spec_start',
'end' => 'spec_end',
'content' => [ @meta_infos_mix, 'lrsml:filter-spec',
'lrsml:chart-configs',
'lrsml:report-calc-spec' ],
},
'lrsml:global-filter-spec' =>
{ 'start' => 'spec_start',
'end' => 'spec_end',
'content' => [ @meta_infos_mix, 'lrsml:filter-spec' ],
},
'lrsml:title' => { 'start' => 'collector_start',
'char' => 'collector_char',
'end' => 'title_end',
'content' => [ 'PCDATA' ], },
'lrsml:description' => { 'start' => 'dbk_init',
'char' => 'dbk_element_char',
'end' => 'description_end',
'content' => [ 'para' ], },
'lrsml:display-spec' => [ 'lrsml:title', 'lrsml:description' ],
'lrsml:param-spec' => [ 'lrsml:param' ],
'lrsml:param' => { 'start' => 'lrsml_param_start',
'end' => 'lrsml_param_end',
'content' => [ 'lrsml:description' ] },
'lrsml:filter-spec' => [ @compound_filters_mix,
@scalar_filters_mix ],
'lrsml:chart-configs' => [ 'lrcml:param' ],
'lrsml:report-calc-spec' => [ @aggregators_mix ],
'lrsml:group' => [ 'lrsml:field', @aggregators_mix,
@aggregates_mix ],
'lrsml:field' => [],
'lrsml:not' => { 'start' => 'compound_filter_start',
'end' => 'not_end',
'content' => [ @compound_filters_mix,
@scalar_filters_mix ] },
'lrsml:avg' => [],
'lrsml:count' => [],
'lrsml:first' => [],
'lrsml:last' => [],
'lrsml:records' => [],
);
foreach my $aggr ( ( @aggregates_mix ) ) {
$spec{$aggr} = { 'start' => 'aggregate_start', 'content' => [] }
unless exists $spec{$aggr};
}
foreach my $aggr ( @aggregators_mix ) {
$spec{$aggr} = { 'start' => 'aggregator_start',
'end' => 'aggregator_end',
'content' => [ @aggregators_mix, @aggregates_mix ] }
unless exists $spec{$aggr};
}
foreach my $filter ( @scalar_filters_mix ) {
$spec{$filter} = { 'start' => 'scalar_filter_start',
'content' => [] }
unless exists $spec{$filter};
}
foreach my $filter ( @compound_filters_mix ) {
$spec{$filter} = { 'start' => 'compound_filter_start',
'end' => 'compound_filter_end',
'content' => [ @compound_filters_mix,
@scalar_filters_mix ] }
unless exists $spec{$filter};
}
sub namespaces {
my $self = $_[0];
my $ns = { %{$self->Lire::Config::Parser::namespaces()} };
$ns->{'http://www.logreport.org/LRSML/'} = 'lrsml';
return $ns;
}
sub elements_spec {
my $self = $_[0];
return {
%{$self->Lire::Config::Parser::elements_spec()},
%{$self->Lire::DocBookParser::elements_spec()},
%spec,
};
}
sub parse_start {
my $self = $_[0];
$self->init_stack( 'config_spec' );
$self->init_stack( 'config_value' );
return;
}
sub parse_end {
return $_[0]{'_spec'};
}
my %factories =
( 'lrsml:avg' => 'Lire::Average',
'lrsml:count', => 'Lire::Count',
'lrsml:first' => 'Lire::First',
'lrsml:last' => 'Lire::Last',
'lrsml:min' => 'Lire::Min',
'lrsml:max' => 'Lire::Max',
'lrsml:sum' => 'Lire::Sum',
'lrsml:group' => 'Lire::Group',
'lrsml:rangegroup' => 'Lire::Rangegroup',
'lrsml:timegroup' => 'Lire::Timegroup',
'lrsml:timeslot' => 'Lire::Timeslot',
'lrsml:records' => 'Lire::Records',
'lrsml:le' => 'Lire::FilterExpr::Le',
'lrsml:lt' => 'Lire::FilterExpr::Lt',
'lrsml:ge' => 'Lire::FilterExpr::Ge',
'lrsml:gt' => 'Lire::FilterExpr::Gt',
'lrsml:eq' => 'Lire::FilterExpr::Eq',
'lrsml:ne' => 'Lire::FilterExpr::Ne',
'lrsml:value' => 'Lire::FilterExpr::Value',
'lrsml:match' => 'Lire::FilterExpr::Match',
'lrsml:and' => 'Lire::FilterExpr::And',
'lrsml:or' => 'Lire::FilterExpr::Or',
'lrsml:not' => 'Lire::FilterExpr::Not',
'lrsml:report-spec' => 'Lire::ReportSpec',
'lrsml:global-filter-spec' => 'Lire::FilterSpec',
'lrsml:field' => 'Lire::GroupField',
'lrsml:param' => 'Lire::Param',
);
sub spec_start {
my ( $self, $name, $attr ) = @_;
$self->{'_spec'} = $factories{$name}->new();
die "missing 'id' attribute\n"
unless exists $attr->{'id'};
$self->{'_spec'}->id( $attr->{'id'} );
die "missing 'superservice' attribute\n"
unless exists $attr->{'superservice'};
$self->{'_spec'}->superservice( $attr->{'superservice'} );
$self->{'_spec'}->schema( $attr->{'schema'})
if exists $attr->{'schema'};
$self->{'_spec'}->joined_schemas( [ split /\s+/, $attr->{'joined-schemas'} ] )
if exists $attr->{'joined-schemas'};
$self->{'_spec'}->charttype( $attr->{'charttype'} )
if exists $attr->{'charttype'};
return;
}
sub spec_end {
my ( $self, $name ) = @_;
# Check that display-spec isn't missing
die "$name is missing a title element\n"
unless ( defined $self->{'_spec'}->title() );
die "$name is missing a description element\n"
unless ( defined $self->{'_spec'}->description() );
die "$name is missing a display title\n"
unless ( defined $self->{'_spec'}->display_title() );
return;
}
sub chart_configs_start {
my $self = $_[0];
my $spec = new Lire::Config::ListSpec( 'name' => 'chart_configs' );
$spec->add( new Lire::Config::ChartSpec( 'name' => 'chart' ) );
$self->stack_push( 'config_spec', $spec );
$self->stack_push( 'config_value', $spec->instance() );
return;
}
sub chart_configs_end {
my $self = $_[0];
$self->stack_pop( 'config_spec' );
foreach my $cfg ( @{$self->stack_pop( 'config_value' )->as_value()}) {
$self->{'_spec'}->add_chart_config( $cfg );
}
return;
}
sub title_end {
my ( $self, $name ) = @_;
my $title = $self->get_collector( 'lrsml:title' );
if ( $self->within_element( 'lrsml:display-spec' ) ) {
$self->{'_spec'}->display_title( $title );
} else {
$self->{'_spec'}->title( $title );
}
return;
}
sub description_end {
my ( $self, $name ) = @_;
if ( $self->in_element( 'lrsml:display-spec' )) {
$self->{'_spec'}->display_description( $self->dbk_string() );
} elsif ( $self->in_element( 'lrsml:param' ) ) {
$self->{'_curr_param'}->description( $self->dbk_string() );
} else {
$self->{'_spec'}->description( $self->dbk_string() );
}
return;
}
sub lrsml_param_start {
my ( $self, $name, $attr ) = @_;
die "$name missing 'name' attribute\n"
unless exists $attr->{'name'};
die "$name is missing 'type' attribute\n"
unless exists $attr->{'type'};
$self->{'_curr_param'} =
$factories{$name}->new( 'i18n_domain' =>
'lire-'.$self->{'_spec'}->superservice(),
%$attr );
$self->{'_spec'}->param( $self->{'_curr_param'}->name(),
$self->{'_curr_param'} );
return;
}
sub lrsml_param_end {
my ( $self, $name ) = @_;
delete $self->{'_curr_param'};
return;
}
sub filter_spec_start {
my ( $self, $name, $attr ) = @_;
$self->init_stack( 'filter-spec' );
$self->stack_push( 'filter-spec', [] );
return;
}
sub filter_spec_end {
my ( $self, $name ) = @_;
my $expr = $self->stack_pop( 'filter-spec' );
die "filter-spec can contains only one expression"
if @$expr > 1;
die "filter-spec must contains one expression"
if @$expr == 0;
$self->{'_spec'}->filter_spec( $expr->[0] );
return;
}
sub compound_filter_start {
my ( $self, $name, $attr ) = @_;
my $parent_content = $self->stack_peek( 'filter-spec' );
push @$parent_content,
$factories{$name}->new( %$attr,
'container' => $self->{'_spec'} );
$self->stack_push( 'filter-spec', [] );
return;
}
sub compound_filter_end {
my ( $self, $name ) = @_;
my $expr = $self->stack_pop( 'filter-spec' );
die "$name expression must contains at leat one expression\n"
unless @$expr;
$self->stack_peek( 'filter-spec' )->[-1]->expr( $expr );
return;
}
sub not_end {
my ( $self, $name ) = @_;
my $expr = $self->stack_pop( 'filter-spec' );
die "$name element must contains one expression\n"
unless @$expr == 1;
$self->stack_peek( 'filter-spec' )->[-1]->expr( $expr->[0] );
return;
}
sub scalar_filter_start {
my ( $self, $name, $attr ) = @_;
my $content = $self->stack_peek( 'filter-spec' );
push @$content, $factories{$name}->new( %$attr,
'container' => $self->{'_spec'} );
return;
}
sub report_calc_spec_start {
my ($self, $name, $attr ) = @_;
$self->init_stack( 'calc-spec' );
$self->init_stack( 'group-sort-fields' );
$self->stack_push( 'calc-spec', [] );
return;
}
sub report_calc_spec_end {
my ( $self, $name ) = @_;
my $curr_calc = $self->stack_pop( 'calc-spec' );
die "$name must contains one aggregator (", join( ", ", @aggregators_mix ), ")\n"
unless @$curr_calc == 1;
$self->{'_spec'}->calc_spec( $curr_calc->[0] );
return;
}
sub group_start {
my ($self, $name, $attr ) = @_;
# Sort fields attributes can only verified after fields and
# operations are specified
$self->stack_push( 'group-sort-fields', $attr->{'sort'} || '' );
$self->aggregator_start( $name, $attr );
return;
}
sub group_end {
my ( $self, $name ) = @_;
my $content = $self->stack_pop( 'calc-spec' );
my @fields = grep { UNIVERSAL::isa( $_, "Lire::GroupField" ) } @$content;
my @ops = grep { UNIVERSAL::isa( $_, "Lire::ReportOperator" ) } @$content;
die "$name must contains at least one field\n"
unless @fields;
die "$name must contains at least one aggregate\n"
unless @ops;
die "$name must only contains field and report operators elements\n"
unless @fields + @ops == @$content;
my $group = $self->stack_peek( 'calc-spec' )->[-1];
$group->group_fields( \@fields );
$group->ops( \@ops );
$group->sort_fields( [ split /\s+/,
$self->stack_pop( 'group-sort-fields' ) ] );
return;
}
sub aggregator_start {
my ( $self, $name, $attr ) = @_;
my $parent_content = $self->stack_pop( 'calc-spec' );
my $parent;
$parent = $self->stack_peek( 'calc-spec' )->[-1]
unless $self->is_stack_empty( 'calc-spec' );
push @$parent_content,
$factories{$name}->new( %$attr, 'report_spec' => $self->{'_spec'},
'parent' => $parent );
$self->stack_push( 'calc-spec', $parent_content );
$self->stack_push( 'calc-spec', [] );
return;
}
sub aggregator_end {
my ( $self, $name ) = @_;
my $content = $self->stack_pop( 'calc-spec' );
my @ops = grep { UNIVERSAL::isa( $_, "Lire::ReportOperator" ) } @$content;
die "$name must contains at least one aggregate.\n"
unless @ops;
die "$name must only contains aggregates\n"
unless @ops == @$content;
$self->stack_peek( 'calc-spec' )->[-1]->ops( \@ops );
return;
}
sub field_start {
my ( $self, $name, $attr ) = @_;
my $content = $self->stack_peek( 'calc-spec' );
push @$content,
$factories{$name}->new( %$attr,
'i18n_domain' => 'lire-' . $self->{'_spec'}->superservice(),
'report_spec' => $self->{'_spec'} );
return;
}
sub aggregate_start {
my ( $self, $name, $attr ) = @_;
my $parent_content = $self->stack_pop( 'calc-spec' );
my $parent = $self->stack_peek( 'calc-spec' )->[-1];
push @$parent_content,
$factories{$name}->new( %$attr,
'report_spec' => $self->{'_spec'},
'parent' => $parent,
);
$self->stack_push( 'calc-spec', $parent_content );
return;
}
sub avg_start {
my ( $self, $name, $attr ) = @_;
$attr->{'by-fields'} = [ split /\s+/, $attr->{'by-fields'}]
if exists $attr->{'by-fields'};
$self->aggregate_start( $name, $attr );
return;
}
sub first_start {
my ( $self, $name, $attr ) = @_;
$attr->{'sort_fields'} = [split /\s+/, $attr->{'sort'}]
if exists $attr->{'sort'};
$self->aggregate_start( $name, $attr );
return;
}
sub last_start {
my ( $self, $name, $attr ) = @_;
$attr->{'sort_fields'} = [split /\s+/, $attr->{'sort'}]
if exists $attr->{'sort'};
$self->aggregate_start( $name, $attr );
return;
}
sub count_start {
my ( $self, $name, $attr ) = @_;
$attr->{'fields'} = [split /\s+/, $attr->{'fields'}]
if exists $attr->{'fields'};
$self->aggregate_start( $name, $attr );
return;
}
sub records_start {
my ( $self, $name, $attr ) = @_;
$attr->{'fields'} = [ split /\s+/, $attr->{'fields'} ]
if exists $attr->{'fields'};
$self->aggregate_start( $name, $attr );
return;
}
1;
__END__
=pod
=head1 SEE ALSO
Lire::XMLParser(3pm, Lire::ReportSpec(3pm), Lire::FilterSpec(3pm),
Lire::XMLSpecContainer(3pm).
=head1 AUTHOR
Francis J. Lacoste <flacoste@logreport.org>
=head1 VERSION
$Id: XMLSpecParser.pm,v 1.5 2006/07/23 13:16:30 vanbaal Exp $
=head1 COPYRIGHT
Copyright (C) 2001-2004 Stichting LogReport Foundation LogReport@LogReport.org
This file is part of Lire.
Lire is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program (see COPYING); if not, check with
http://www.gnu.org/copyleft/gpl.html.
=cut
syntax highlighted by Code2HTML, v. 0.9.1