The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package SemanticWeb::OAI::ORE::N3;
#$Id: N3.pm,v 1.14 2010-12-06 14:44:15 simeon Exp $

=head1 NAME

SemanticWeb::OAI::ORE::N3 - Parse/serialize OAI-ORE Resource Maps in N3 format

=head1 SYNPOSIS

Class to parse and serialize OAI-ORE ReMs in N3 format.

=head1 DESCRIPTION

Follows N3 specification defined by L<http://www.w3.org/DesignIssues/Notation3.html>.

=cut

use strict;
use warnings;
use Carp;

use SemanticWeb::OAI::ORE::ReM;
use SemanticWeb::OAI::ORE::Constant qw(:all);

use RDF::Notation3::RDFCore;
use RDF::Notation3::Triples;
use RDF::Core::Storage::Memory;
use RDF::Core::Model;
use RDF::Core::Resource;
use RDF::Core::Enumerator;
use Data::Dumper;

=head1 METHODS

=head2 new()

=cut

sub new {
  my $class=shift;
  my $self={'strict'=>'warn',
            @_};
  bless $self, (ref($class) || $class);
  return($self);
}


=head2 parse($src,$uri_rem)

Parse $src which is either a string containing the N3 serialization of 
the ReM with URI $uri_rem, or a filehandle.

=cut

sub parse {
  my $self=shift;
  my ($src,$uri_rem)=@_;  

  my $rdf = RDF::Notation3::RDFCore->new();
  $rdf->set_storage(RDF::Core::Storage::Memory->new());
  my $model;
  if (ref($src) eq 'IO::File') {
    $model = $rdf->parse_file($src); #takes IO::File or path
  } elsif (ref($src)) {
    croak("Attempt to parse N3 with a ".ref($src)." object supplied");
  } else {
    $model = $rdf->parse_string($src);
  }

  # $model is an RDF::Core::Model
  # Now we look for statements that are special to OAI-ORE
  #print "got $model ".ref($model)."\n";
  #print Dumper($model);  
  return($model);
}


=head3 serialize()

We could do this by simply converting all data to triples
and then dumping them as N3. However, we attempt to do a
nice "pretty print" specific to ORE, with a few comments for 
the parts.

=cut

sub serialize {
  my $self=shift;
  my $out='';
  my $rem=$self->{rem};
  if (ref($rem) and $rem->isa('SemanticWeb::OAI::ORE::ReM')) {
    # Get the info from the ReM
    my $uri_r=$rem->uri;
    my $uri_a=$rem->aggregation;
    my @rem_and_agg=();
    my @agg_res=();
    my @lines=();
    $self->_prefixify_setup;
    foreach my $statement (@{$rem->model->as_array()}) {
      my ($subject,$predicate,$object,$is_literal)=@$statement;
      my $line=$self->_make_n3_line(@$statement);
      if ($subject eq $uri_r and $predicate eq DESCRIBES) {
        push(@rem_and_agg,$line);
      } elsif ($subject eq $uri_a and $predicate eq AGGREGATES) {
        push(@agg_res,$line);
      } else {
        push(@lines,$line);
      }
      #FIXME
    }
    # Now print it
    $out.="### OAI-ORE Resource Map ($uri_r)\n";
    $out.=$self->_prefixes_section;
    $out.="\n# Resource Map and Aggregation\n";
    $out.=join('',(sort @rem_and_agg)) if (@rem_and_agg);
    $out.="\n# Aggregated resources\n";
    $out.=join('',(sort @agg_res)) if (@agg_res);
    $out.="\n# Relations\n";
    $out.=join('',(sort @lines)) if (@lines);
    $out.="\n### End Resource Map\n";
  } else {
    carp "Can't serialize something that isn't a rem: $rem, ".ref($rem)."\n";
  }
  return($out);
}


# Create a single N3 line from subject,predicate,object labels with 
# appropriate escaping.
#
sub _make_n3_line {
  my $self=shift;
  my ($subject,$predicate,$object,$object_is_literal)=@_;
  my $line='';
  if (my $psubject=$self->_prefixify($subject)) {
    $line.=$psubject.' ';
  } else {
    $line.='<'.$subject.'> ';
  }
  if (my $ppredicate=$self->_prefixify_predicate($predicate)) {
    $line.=$ppredicate.' ';
  } else {
    $line.='<'.$predicate.'> ';
  }
  if ($object_is_literal) {
    $line.='"'._n3_escape($object).'"';
  } elsif (my $pobject=$self->_prefixify($object)) {
    $line.=$pobject;
  } else {
    $line.='<'.$object.'>';
  }
  return($line.".\n");
}


# Escape a string for use in N3 quoted string.
# See: http://www.w3.org/DesignIssues/Notation3
#
sub _n3_escape {
  my ($sstr)=@_;
  my $rstr='';
  foreach my $c (split(//,$sstr)) {
    if ($c eq '\\') {
      $c='\\\\';
    } elsif ($c eq '"') {
      $c='\\"';
    } elsif ($c eq "\n") {
      $c='\\n';
    } elsif ($c eq "\r") {
      $c='\\r';
    } elsif ($c eq "\t") {
      $c='\\t';
    }
    $rstr.=$c;
  }
  return($rstr);
}


sub _parse_warning {
  my $self=shift;
  my $msg=shift;
  print "_parse_warning: $msg\n";
}


sub _parse_error { 
  my $self=shift;
  my $msg=shift;
  if ($self->{strict} eq 'warn') {
    return($self->_parse_warning($msg));
  }
  croak "_parse_error: $msg\n";
}


# Setup ready to use _prefixify.
#
# WARNING - have to be carefull that constants do not get quoted
# in the hash. See Caveats in http://perldoc.perl.org/constant.html
#
sub _prefixify_setup {
  my $self=shift;
  $self->{prefixes_known}={
    ORE_NS() => ORE_PREFIX(),
    DC_NS() => DC_PREFIX(),
    DCT_NS() => DCT_PREFIX(),
    RDF_NS() => RDF_PREFIX() };
  $self->{prefixes_used}={};    
}


# Return prefixes section of N3 output.
#
sub _prefixes_section {
  my $self=shift;
  my $out='';
  if (scalar(keys %{$self->{prefixes_used}})) {
    $out.="\n# Namespace prefixes\n";
    foreach my $prefix (sort keys %{$self->{prefixes_used}}) {
      $out.="\@prefix $prefix: <".$self->{prefixes_used}{$prefix}.">.\n";
    }
  }
  return($out);
}


# Takes input $uri, return possible prefixed $uri having added
# prefix to list of known used prefixes.
#
sub _prefixify {
  my $self=shift;
  my ($uri)=@_;
  foreach my $prefix (keys %{$self->{prefixes_known}}) {
    if ($uri=~s%^$prefix%%) {
      $self->{prefixes_used}{$self->{prefixes_known}{$prefix}}=$prefix;
      return($self->{prefixes_known}{$prefix}.':'.$uri);
    }
  }
  # Nothing found, return nothing
  return();
}


# Special support for predicates where N3 has certain shorthands
#
sub _prefixify_predicate {
  my $self=shift;
  my ($uri)=@_;
  if ($uri eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type') {
    return('a');
  } elsif ($uri eq 'http://www.w3.org/2002/07/owl#sameAs') {
    return('=');
  } elsif ($uri eq 'http://www.w3.org/2000/10/swap/log#implies') {
    return('=>');
  }
  # No specials for predicate found, try normal _prefixify
  return($self->_prefixify($uri));
}


=head1 SEE ALSO

L<SemanticWeb::OAI::ORE> and associated modules.

=head1 AUTHORS

Simeon Warner

=head1 LICENSE AND COPYRIGHT

Copyright 2007-2010 Simeon Warner.

This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. See L<perlartistic>.

=cut

1;