#Copyright Massachusetts Institute of technology, 1998.
#Written by Eric Prud'hommeaux for the World Wide Web Consortium

# $Id: XmlSerializer.pm,v 1.37 2007-07-18 23:13:44 eric Exp $

use strict;
require Exporter;
#require AutoLoader;

use W3C::Rdf::EmitterInterface;

$W3C::Rdf::RdfDB::REVISION = '$Id: XmlSerializer.pm,v 1.37 2007-07-18 23:13:44 eric Exp $ ';

package W3C::Rdf::XmlSerializer;
use vars qw($VERSION $DSLI @ISA @TODO @EXPORT_OK);
@ISA = qw(W3C::Util::NamedParmObject Exporter); # AutoLoader);
@TODO = qw();
@EXPORT_OK = qw();
$VERSION = 0.95;
$DSLI = 'adpO';

use W3C::Util::Exception;
use W3C::XML::XmlSerializer;
use W3C::Rdf::Atoms qw($RDF_SCHEMA_URI $ATTRIBUTION_SCHEMA_URI);

sub new {
    my ($proto, @parms) = @_;
    my $class = ref($proto) || $proto;
    my $self = $class->SUPER::new(@parms);
    my $rdfNS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
    $self->{-rdfTag} = $self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'RDF')->getUri;
    if (!defined $self->{-serializer}) {
	$self->{-serializer} = new W3C::XML::UriXmlSerializer(-prettyPrint => 1, 
							      -namespaceFactory => sub {return $self->_namespaceHandlerFactory(@_);}, 
							      -namespaceCreativity => $self->{-namespaceCreativity}, 
							      -namespaceForce => [$rdfNS], 
							      -noDefaultAttrNamespace => 1, 
							      -namespaceElementSweep => 5, 
							      -namespaceElementTrip => 2, 
							      -importMap => $self->{-importMap});
    }
    return $self;
}

sub _namespaceHandlerFactory {
    my ($self, $importMap, $parent) = @_;
    return $self->{-createNamespaces} ? 
	new W3C::Util::NamespaceInventor(-importMap => $importMap, -copyHandler => $parent) : 
	new W3C::Util::NamespaceHandler(-importMap => $importMap, -copyHandler => $parent);
}

sub nest {
    return 1;
}

sub collection {
    return 1;
}

sub getText {
    my ($self) = @_;
    return $self->{-serializer}->getText();
}

sub startDocument {
    my ($self) = @_;
    $self->{-serializer}->startDocument();
    if (!$self->{-noRdfTag}) {
	$self->{-serializer}->startElement($self->{-rdfTag}, undef, {-addNamespaces => $self->{-neededNamespaces}});
    }
}

sub endDocument {
    my ($self) = @_;
    if ($self->{-attributions}) {
	$self->{-attributions} = 0;
	foreach my $set (@{$self->{REIFICATION_STATEMENTS}}) {
	    my ($statement, $iterator) = @$set;
	    $self->serializeStatements([$statement], $iterator, 1);
	}
	$self->{-attributions} = 1;
    }
    $self->{-serializer}->endDocument();
    if (!$self->{-noRdfTag}) {
	$self->{-serializer}->endElement($self->{-rdfTag});
    }
}

use vars qw(%BNodesOut);
%BNodesOut = ();

sub getBNodeOut {
    my ($self, $subjectNode) = @_;
    my ($created, $outAtom) = (0, $BNodesOut{$subjectNode});
    if (!$outAtom) {
	$outAtom = $self->{-atomDictionary}->createBNode($self->{-resource});
	$BNodesOut{$subjectNode} = $outAtom;
	$BNodesOut{$outAtom} = $outAtom;
	$created = 1;
    }
    return ($created, $outAtom);
}

sub serializeStatements {
    my ($self, $withThisSubject, $iterator, $forceAbout) = @_;
    my $subjectNode = $withThisSubject->[0]->getSubject;
    my %attrs;

    # Add reificiation attributes. @@@ this is in flux.
    if ($self->{-serializeReificationsFoo}) {
	if (my $reifiedIn = $withThisSubject->[0]->getReifiedFoo) {
	    if (!$self->{-ignoreAnonymousReifications}) {
		$attrs{$self->{-atomDictionary}->getUri($ATTRIBUTION_SCHEMA_URI.'collectionID')->getUri} = $reifiedIn->toString;
	    }
	}
    }

    if ($subjectNode->isa('W3C::Rdf::String')) {
	&throw(new W3C::Rdf::RdfDBException('can\'t serialize subject node of type string'));
    } else {
	if ($subjectNode->isa('W3C::Rdf::BNode')) {
	    if ($forceAbout && $self->{-allowAnonymousRefs}) {
		my $outAtom = $self->getBNodeOut($subjectNode);
		$attrs{$self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'nodeID')->getUri} = '_'.$outAtom->getId;
	    }
	} else {
	    $attrs{$self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'about')->getUri} = $subjectNode->getUri;
	}
    }

    # stick what we can into the attributes
    if (!$self->{-serializeNoNodeAttrs} && !$self->{-attributions}) {
	for (my $i = 0; $i < @$withThisSubject; $i++) {
	    my $statement = $withThisSubject->[$i];
	    next if ($statement->isa('W3C::Rdf::ListStatement'));
	    my $attr = $statement->getPredicate->getUri;
	    next if ((grep {$_->getPredicate->getUri eq $attr} @$withThisSubject) > 1); # 2 or more foaf:nick arcs all serialized in <nick>...</nick> form.

	    if (!$self->{-ignoreReifications} && $withThisSubject->[0]->getReifiedAs) {
		next;
	    }

	    my $object = $statement->getObject;
	    if ($object->isa('W3C::Rdf::String') && $object->getEncoding eq 'PLAIN' && !$object->getDatatype) {
		$attrs{$attr} = $object->getString;
		splice (@$withThisSubject, $i, 1); # eliminate that entry
		$i--;
	    }
	}
    }

    # serialize the element
    my $type = $self->getTypedNodeTag("$subjectNode", $withThisSubject);
    $self->{-serializer}->startElement($type, \%attrs);

    # any nested statements
    foreach my $statement (@$withThisSubject) {
	my %attrs;
	my $predicate = $statement->getPredicate->getUri;
	if ($self->{-attributions}) {
	    my $attributedTo = $statement->getAttribution->getUri;
	    my ($created, $reifiedIn) = $self->getBNodeOut($attributedTo->getUri);
	    if ($created) {
		my $pred = $self->{-atomDictionary}->getUri($ATTRIBUTION_SCHEMA_URI.'attribution');
		my $statement = new W3C::Rdf::MappedTriple($self->{-RdfDB}, $pred, $reifiedIn, $attributedTo, $self->{-db}{-sourceAttribution});
		push (@{$self->{REIFICATION_STATEMENTS}}, [$statement, $iterator]);
	    }
	    $attrs{$self->{-atomDictionary}->getUri($ATTRIBUTION_SCHEMA_URI.'collectionID')->getUri} = $reifiedIn->getId;
	}

	# add reificiation attributes
	if (!$self->{-ignoreReifications}) {
	    if (my $reifiedAs = $withThisSubject->[0]->getReifiedAs) {
		$attrs{$self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'ID')->getUri} = $reifiedAs;
	    }
	}

	# First check for Collections - they're handled differently.
	if ($statement->isa('W3C::Rdf::ListStatement')) {
	    $attrs{$self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'parseType')->getUri} = "Collection";
	    $self->{-serializer}->startElement($predicate, \%attrs);
	    my $objects = $statement->getObjects;
	    foreach my $object (@$objects) {
		if (!$iterator->handleNestedStatements($object, 0)) {
		    # Was just a list item with no arcs from it so we must serialize it ourselves.
		    $self->_emptyElt($object, $forceAbout);
		}
	    }
	    $self->{-serializer}->endElement($predicate);
	    next;
	}

	# Not a Collection...

	# get statement object
	my $object = $statement->getObject;
	my $objectHashId = "$object"; # name used in BY{SU,O}BJECT hashes

	# may reify as an RDF:resource if it's a URI
	if ($object->isa('W3C::Rdf::Uri') && 
	    # and we are not told to expand resources
	    !$self->{-serializeExpandResources} && 
	    # and we've been told not to nest or there are no statements to nest
	    ($self->{-serializeNoNesting} || 
	     !$iterator->bySubject($objectHashId) || 
	     !@{$iterator->bySubject($objectHashId)})) {

	    # Serialize as <predicate rdf:resource="object" />.

	    $attrs{$self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'resource')->getUri} = $object->getUri;
	    $self->{-serializer}->startElement($predicate, \%attrs);
	} else {
	    if ($object->isa('W3C::Rdf::String')) {
		if ($object->getEncoding eq 'PLAIN') {

		    # Serialize as <predicate>object</predicate>

		    if (my $datatype = $object->getDatatype) {
			$attrs{$self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'datatype')->getUri} = $datatype->getUri;
		    }
		    $self->{-serializer}->startElement($predicate, \%attrs);
		    $self->{-serializer}->characters($object->getString);
		} elsif ($object->getEncoding eq 'XML') {

		    # Serialize as <predicate rdf:parsetype="Literal">object</predicate>
		    
		    $attrs{$self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'parseType')->getUri} = 'Literal';
		    $self->{-serializer}->startElement($predicate, \%attrs);
		    $self->{-serializer}->characters($object->getString, {-dontEncode => 1});
		} else {
		    &throw(new W3C::Util::ProgramFlowException(-state => '$object->getEncoding: "'.$object->getEncoding.'"'));
		}
	    } else {

		# Serialize as <predicate>
		#                 <typedNode...>

		my $forceAbout = 0;
		if ($object->isa('W3C::Rdf::BNode')) {
		    if (@{$iterator->byObject($object)} > 0) {
			if ($self->{-allowAnonymousRefs}) {
			    $forceAbout = 1;
			} else {
			    # we have no legal way to serialize annonymous nodes
			    &throw(new W3C::Rdf::RdfDBException('can\'t reference anonymous node more than once '.
								$object->toString));
			}
		    }
		}

		$self->{-serializer}->startElement($predicate, \%attrs);
		# start nested description if we are allowed to nest
		if (!$self->{-serializeNoNesting} && 
		    $iterator->handleNestedStatements($statement->getObject, $forceAbout)) {
		} else {
		    # just create a nested description without going into it's predicates.

		    # Could calculate a new $withThisSubject, but this would
		    # require tedious extra interaction wiht the iterator and
		    # is handled well enough by handleNestedStatements.
		    $self->_emptyElt($object, $forceAbout);
		}
	    }
	}
	$self->{-serializer}->endElement($predicate);
    }
    $self->{-serializer}->endElement($type);
}

sub _emptyElt {
    my ($self, $object, $forceAbout) = @_;
    my $objType = $self->getTypedNodeTag($object, []);
    my %attrs;
    if ($object->isa('W3C::Rdf::Uri')) {
	$attrs{$self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'about')->getUri} = $object->getUri;
    }
    if ($forceAbout && $self->{-allowAnonymousRefs}) {
	my $outAtom = $self->getBNodeOut($object);
	$attrs{$self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'nodeID')->getUri} = '_'.$outAtom->getId;
    }
    $self->{-serializer}->startElement($objType, \%attrs);
    $self->{-serializer}->endElement($objType);
}

# Get a type arc suitable for use in the typedNode production in RDFXML.
#  Remove the arc from @$withThisSubject so we don't serialize it later.
sub getTypedNodeTag {
    my ($self, $node, $withThisSubject) = @_;
    my $tag = $self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'Description');
    if (!$self->{-serializeTypelessNodes}) {
	for (my $i = 0; $i < @$withThisSubject; $i++) {
	    my $statement = $withThisSubject->[$i];
	    if ($statement->getPredicate == $self->{-atomDictionary}->getUri($RDF_SCHEMA_URI.'type') && 
		$statement->getObject->isa('W3C::Rdf::Uri')) {
		$tag = $statement->getObject;
		splice (@$withThisSubject, $i, 1); # eliminate type entry
		last;
	    }
	}
    }
    return $tag->getUri;
}

1;

__END__

=head1 NAME

W3C::Rdf::XmlSerializer - serialize RDF graphs as RDF/XML

=head1 SYNOPSIS

  use W3C::Rdf::Atoms;
  use W3C::Rdf::RdfDB;
  require W3C::Rdf::XmlSerializer;
  my $atoms = new W3C::Rdf::Atoms();
  my $rdfDB = new W3C::Rdf::RdfDB(-atomDictionary => $atoms);
  my $algae2 = new W3C::Rdf::Algae2(-atomDictionary => $atoms);
  my $serializer = new W3C::Rdf::XmlSerializer(-atomDictionary => $atoms);
  my $iterator = $rdfDB->makeSerializerIterator($statements, $algae2);
  $iterator->iterate($serializer);
  print $serializer->getText();

=head1 DESCRIPTION

C<W3C::Rdf::RdfDB>'s C<W3C::Rdf::RdfDB::SerializerIterator> calls
C<W3C::Rdf::StatementsSerializer> to generate RDF/XML.

This module is part of the W3C::Rdf CPAN module.

=head1 CONSTRUCTOR

=over 4

=item new ( ATOMS [, FLAGS] )

Creates an C<W3C::Rdf::XmlSerializer>.  This must be passed an Atoms dictionary.
Attitional flags:

=back

=head1 METHODS

=over 4

=item collection()

Return whether this serializer has special code for collections. If so,
C<serializeStatements> may be called with a C<W3C::Rdf::Atoms::ListStatement>.

=item nest()

Return whether this serializer can express nested descriptions.

=item startDocument()

Follow SAX convention except there is no document locator (the serializer is
the actual owner of the document).

=item endDocument()

Follow SAX convention.

=item serializeStatements( STATEMENTS, [ ITERATOR ] )

Take a set of statements to be serialized. The C<ITERATOR> is only used if the
serializer attempts to serialize nested (statements with a subject of the
current object). The C<STATENTS> may also be ListStatemens, in which case
special code serializes a collection.

=item getText()

Return a scalar with the serialized RDF. The caller will likely write this to
a file or to a network stream in response to an HTTP GET of some virtual document.

=back

=head1 AUTHOR

Eric Prud'hommeaux <eric@w3.org>

=head1 SEE ALSO

L<W3C::Rdf::RdfDB>

=cut
