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

# $Id: CGIApp.pm,v 1.118 2007/03/11 02:19:05 eric Exp $

use strict;
require Exporter;

$W3C::Rdf::CGIApp::REVISION = '$Id: CGIApp.pm,v 1.118 2007/03/11 02:19:05 eric Exp $ ';


package W3C::Rdf::CGIApp::AppException;
use W3C::Util::Exception;
use vars qw(@ISA);
@ISA = qw(W3C::Util::ResourceException);
sub new {
    my ($proto, @parms) = @_;
    my $class = ref($proto) || $proto;
    my $self = $class->SUPER::new(@parms);
    $self->missingParm('-status') if (!$self->{-status});
    $self->missingParm('-presenter') if (!$self->{-presenter});
    $self->fillInStackTrace;
    return $self;
}
sub getSpecificMessage {return 'Error on resource: "'.$_[0]->{-uri}.'"';}
sub toString {
    my ($self) = @_;
    return $self->{-presenter}->flush($self->{-status});
}

package W3C::Rdf::CGIAppErrorHandler;
use W3C::XML::ErrorHandlerImpl;
use vars qw(@ISA);
@ISA = qw(W3C::XML::ErrorHandlerImpl);

sub makeString {
    my ($self, $exception) = @_;
    my $ret = $exception->getMessage;
    if ($exception->isa('W3C::Rdf::RdfDBException')) {
	$ret .= ' at '.$self->{-documentLocator}->getPublicId.':'.
	    $self->{-documentLocator}->getLineNumber.'.'.
		$self->{-documentLocator}->getColumnNumber;
    }
    $DB::single = 1;
    return $ret;
}

sub warning {
    my ($self, $exception) = @_;
    $self->{-CGIApp}{PRESENTER}->warning($self->makeString($exception));
}

sub error {
    my ($self, $exception) = @_;
    $self->{-CGIApp}{PRESENTER}->error($self->makeString($exception));
}

@W3C::Rdf::CGIApp::QueryObject::ISA = qw(W3C::Util::NamedParmObject);

package W3C::Rdf::CGIApp;
use vars qw($VERSION $DSLI @ISA @EXPORT @EXPORT_OK);
@ISA = qw(W3C::Util::NamedParmObject Exporter);

use W3C::Util::Exception;
use W3C::Http::Message;
use W3C::Http::Exception;
use URI;
use W3C::Rdf::RdfApp;
use vars qw($CGIApp_DeleteMarker);
$CGIApp_DeleteMarker = '$CGIApp_DeleteMarker';
@EXPORT = qw();
@EXPORT_OK = qw($CGIApp_DeleteMarker);
$VERSION = 0.94;
$DSLI = 'adpO';

$W3C::Rdf::CGIApp::USE_EPHEMERAL_DB = "\$W3C::Rdf::CGIApp::USE_EPHEMERAL_DB\n";

sub new {
    my ($proto, $read, @initParms) = @_;
    my $class = ref($proto) || $proto;
    my $self = $class->SUPER::new(@initParms);
    bless ($self, $class);

    # constructor parameters
    $self->{READ} = $read;
    $self->{WRITE} = new CGI({});
    $self->{PRE_MESSAGES} = []; # passed to presenter for rendering

    # import CGI parameters
    $self->importCGIParms();

    $self->{ERROR_HANDLER} = new W3C::Rdf::CGIAppErrorHandler(-CGIApp => $self);

    $self->{-atomDictionary} = new W3C::Rdf::Atoms();
    eval {
	die $W3C::Rdf::CGIApp::USE_EPHEMERAL_DB if ($self->{EPHEMERAL_DB});
	my $props = $self->{-properties} || new W3C::Util::Properties('CGIApp.prop');
	use W3C::Rdf::SqlDB;
	$self->{RDF_DB} = new W3C::Rdf::SqlDB(-errorHandler => $self->{ERROR_HANDLER}, 
					      -lazyReification => 1, 
					      -properties => $props, 
					      -atomDictionary => $self->{-atomDictionary});
    }; if ($@) {
	if (my $ex = &catch('W3C::Util::Exception')) {
	    push (@{$self->{PRE_MESSAGES}}, $ex->getMessage);
	    push (@{$self->{PRE_MESSAGES}}, 'switching to ephemeral DB');
	} elsif ($@ ne $W3C::Rdf::CGIApp::USE_EPHEMERAL_DB) {
	    push (@{$self->{PRE_MESSAGES}}, "got die($@)");
	    push (@{$self->{PRE_MESSAGES}}, 'switching to ephemeral DB');
	}
	$self->{RDF_DB} = $self->{RDF_DB} = new W3C::Rdf::RdfDB(-errorHandler => $self->{ERROR_HANDLER}, 
								-lazyReification => 1, 
								-atomDictionary => $self->{-atomDictionary});
    }

    $self->{-context} = new W3C::Rdf::RdfApp(-atomDictionary => $self->{-atomDictionary}, 
					     -rdfDB => $self->{RDF_DB});
    $self->{-queryHandler} = $self->{-context}->getQueryHandler();

    return $self;
}

sub makePresenter {
    my ($self, $presenterTypes, $ns, %flags) = @_;
    my ($accept, $presenterPackage) = $self->getPresenterClass($presenterTypes, %flags);
    if (!defined $presenterPackage) {
	&throw(new W3C::Util::Exception(-message => "no presentation class"));
    }
    my $out = $self->{WRITE};
    my $sessionId = $self->{READ}->getSessionId;
    my $presenter;
    my $evalMe = "\$presenter = new $presenterPackage(\$self, \$out, \$sessionId, \$accept, \$ns, -atomDictionary => \$self->{-atomDictionary});";
    eval $evalMe;
    if ($@) {
	my $msg = "failed to execute $evalMe: $!";
	if (my $ex = &catch('W3C::Util::Exception')) {
	    &throw(new W3C::Util::Exception(-message => $msg, 
					    -chainedException => $ex));
	} else {
	    &throw(new W3C::Util::Exception(-message => $msg));
	}
    }
    if (!$presenter) {
	&throw(new W3C::Util::Exception(-message => "\"$evalMe\" failed to make a presenter"));
    }
    return $presenter;
}

sub getPresenterClass {
    my ($self, $presenterTypes, %flags) = @_;
    my $htmlPresenterClass = $presenterTypes->{-htmlPresenter} || 'W3C::Rdf::CGIApp::HTMLPresenter';
    my $rdfPresenterClass = $presenterTypes->{-rdfPresenter} || 'W3C::Rdf::CGIApp::RdfPresenter';
    my $srxPresenterClass = $presenterTypes->{-srxPresenter} || 'W3C::Rdf::CGIApp::SrxPresenter';
    my $n3PresenterClass = $presenterTypes->{-rdfPresenter} || 'W3C::Rdf::CGIApp::N3Presenter';
    my $csvPresenterClass = $presenterTypes->{-rdfPresenter} || 'W3C::Rdf::CGIApp::ArrayPresenter';
    if ($self->{FORCE_PRESENTATION} eq 'text/html') {
	return ('text/html', $htmlPresenterClass);
    }
    if ($self->{FORCE_PRESENTATION} eq 'application/rdf+xml') {
	return ('application/rdf+xml', $rdfPresenterClass);
    }
    if ($self->{FORCE_PRESENTATION} eq 'application/srx+xml') {
	return ('application/srx+xml', $srxPresenterClass);
    }
    if ($self->{FORCE_PRESENTATION} eq 'text/x-n3') {
	return ('text/x-n3', $n3PresenterClass);
    }
    if ($self->{FORCE_PRESENTATION} eq 'text/x-csv') {
	return ('text/x-csv', $csvPresenterClass);
    }
    $flags{-default} ||= 'text/html';
    return $self->{READ}->maxAccept(['text/html' => $htmlPresenterClass, 
				     'text/rdf' => $rdfPresenterClass, 
				     'text/x+rdf' => $rdfPresenterClass, 
				     'text/xml' => $rdfPresenterClass, 
				     'text/*+xml' => $rdfPresenterClass, 
				     'application/rdf' => $rdfPresenterClass, 
				     'application/x-rdf' => $rdfPresenterClass, 
				     'application/xml' => $rdfPresenterClass, 
				     'application/srx+xml' => $srxPresenterClass, 
				     'application/*+xml' => $rdfPresenterClass, 
				     '*/*' => $htmlPresenterClass], 
				    %flags);
}

sub htmlReply {
    my ($self, $status, $contentType, $title, $message, $headers) = @_;
    my $sessionId = $self->{READ}->getSessionId;
    my $data = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"
    \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">
<!-- sessionId: $sessionId -->
<html><head><title>$title</title></head>
      <body><h1>$title</h1>
            <p>$message</p>
      </body>
</html>\n";
    my $contentLength = length $data;
    my $httpMessage = $self->simpleReply($status, $contentType, $contentLength, $data, $headers);
    return new W3C::Http::HttpMessageException(-httpMessage => $httpMessage);
}

sub simpleReply {
    my ($self, $status, $contentType, $contentLength, $data, $headers) = @_;
    my $headers = [['Content-type', $contentType], 
		   ['Content-length', $contentLength], 
		   ['Session-Id', $self->{READ}->getSessionId], 
		   map {[$_, $headers->{$_}]} keys %$headers];
    return new W3C::Http::Message(-statusCode => $status, -headers => $headers, -body => $data);
}

# create and decompose "hosted" ids.
sub generateUid {
    my ($self, $type, $sessionId) = @_;
    return $self->getSelfUri.'/'.$type.'/'.$sessionId;
}
# call with a scalar context to just get $2 (the session id).
sub decomposeUid {
    my ($self, $uri) = @_;
    my $selfUri = $self->getSelfUri;
    if ($uri =~ m/^$selfUri\/([^\/]+)\/([\d\.]+)$/) {
	return ($1, $2);
    }
    return (undef, undef);
}

sub checkId {
    my ($self, $checkMe, $type, $overrideId, $dbs) = @_;
    my $newIdStr = $self->generateUid($type, $overrideId ? $overrideId : $self->{READ}->getSessionId);
    my $newId = $self->{-atomDictionary}->getUri($newIdStr);
    my $ret = $checkMe;
    if (UNIVERSAL::isa($checkMe, 'W3C::Rdf::BNode')) {# || 
#NUKE	($checkMe->isa('W3C::Rdf::RdfObjects::RdfIds') && $checkMe->_getBNode)) {
	my $oldIdString = $checkMe->toString;
#NUKE	$self->{RDF_DB}->changeId(undef, $checkMe, $newId);
	foreach my $db (@$dbs) {
	    $db->changeId(undef, $checkMe, $newId);
	}
	my $newIdString = $newId->toString;
	$ret = $newId;
	$self->{PRESENTER}->printInfo($self->{READ}->escapeHTML("$type $oldIdString changed to $newIdString"));
    } elsif ($overrideId && $checkMe != $newId) {
	my $idUri = $checkMe->getUri;
	&throw(new W3C::Util::Exception(-message => "non-anonymous node \"$idUri\" does not match expected \"$overrideId\"."));
    }
    return $ret;
}

sub parse {
    my ($self, $text, $db, $attrib) = @_;
    my $type = 'rdf';

    # Assign a URI to the text RDF_INPUT and parse it.
    use W3C::XML::InputSource;
    my $inputSource = new W3C::XML::InputSource(\ $text);
    $inputSource->setPublicId($self->{BASE_URI});
    my $bytes = $inputSource->getByteStream();
    my $location = $inputSource->getPublicId();

    use W3C::Rdf::RdfApp;
    my $context = new W3C::Rdf::RdfApp(-atomDictionary => $self->{-atomDictionary}, 
				       -rdfDB => $db);

    use W3C::XML::NamespaceHandler;
    my $namespaceHandler = new W3C::XML::NamespaceHandler(-useSAX2 => 1);

    use W3C::Rdf::Algae2;
    my $algae2 = new W3C::Rdf::Algae2(-atomDictionary => $self->{-atomDictionary}, 
				      -namespaceHandler => $namespaceHandler, 
				      -sources => {'' => $0}, 
				      -rdfApp => $self, 
				      -sourceAttribution => $attrib, 
				      -rdfDB => $db);

    $context->parse($bytes, $location, $type, $algae2);
    return $algae2;
}

sub arrayHasDiffs {
    my ($a, $b) = @_;
    return 1 if ($#$a != $#$b);
    my (%a, %b);
    map {$a{$_} = undef} @$a;
    map {return 1 if (!exists $a{$_})} @$b;
    return 0;
}

# CGI inputs:

sub importCGIParms {
    my ($self) = @_;
    my $selfUri = $self->getSelfUri;

    if (my $parmMapping = $self->{-parmMapping}) {
	# read the inputs from the query
	my @params = $self->{READ}->param;
	foreach my $param (@params) {
	    # entry into the rdf script through a specified resource and optional wildcards
	    my $value = $self->{READ}->param($param);
	    if (my $mapping = $parmMapping->{$param}) {
		$self->{$mapping} = $value;
	    } elsif ($value == \ $CGIApp_DeleteMarker) {
	    } else {
		&throw($self->htmlReply(500, 'text/html', "bad parameter: $param", 
					"$selfUri was unable to interpret parameter $param: $value"));
	    }
	}
    }

    if (my $path = $ENV{'PATH_INFO'}) {
	if (my $pathMapping = $self->{-pathMapping}) {
	    if ($path =~ m/^\/(\w+)\/([^\/]+)$/) {
		my ($name, $value) = ($1, $2);
		if (my $mapping = $pathMapping->{$name}) {
		    $self->{$mapping} = $value;
		} else {
		    &throw($self->htmlReply(500, 'text/html', "bad path parameter: $name", "$selfUri was unable to interpret parameter $name: $value"));
		}
	    } elsif ($path eq '/') {
	    } else {
		&throw($self->htmlReply(500, 'text/html', "bad path: $path", "$selfUri was unable to interpret path $path"));
	    }
	}
    }

    $self->{BASE_URI} = $self->generateUid('attribution', $self->{READ}->getSessionId) if (!$self->{BASE_URI});
    return 1;
}

sub getSelfUri {
    my ($self) = @_;
    return $self->{-properties}->getI('script.url') || $self->{READ}->url;
}

sub unknownException {
    my ($self, $exception) = @_;
    my $exceptionText = $self->{WRITE}->escapeHTML($exception->toString);
#    my $exceptionText;
#    $exception->printStackTrace(\ $exceptionText);
    my $response = <<EOF
Status: 200 OK
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">
<HTML><HEAD>
<TITLE>$self->{SHORT_TITLE}: Unknown Exception</TITLE>
</HEAD><BODY>
<H1>$self->{SHORT_TITLE}: Unknown Exception</H1>
$self->{SHORT_TITLE} blew its mind.<br>
<PRE>$exceptionText</PRE>
<P></BODY></HTML>
EOF
    ;
    return $response;
}

sub requireAuth {
    my ($self, $realm, $needed) = @_;
#    my $resource = join(' ', @{$self->{RESOURCE_LIST}});
    my $resource = $self->{BASE_URI};
    my $authResponse = <<EOF
Status: 401 Authorization Required
WWW-Authenticate: Basic realm=\"$realm\"
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">
<HTML><HEAD>
<TITLE>401 Authorization Required</TITLE>
</HEAD><BODY>
<H1>Authorization Required</H1>
You need $needed access to $resource to perform the requested opperation.<P>
EOF
    ;
    return $authResponse;
}

sub confFileMissing {
    my ($self, $filename) = @_;
    my $confResponse = <<EOF
Status: 200 OK
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">
<HTML><HEAD>
<TITLE>$self->{SHORT_TITLE}: Configuration File Missing</TITLE>
</HEAD><BODY>
<H1>$self->{SHORT_TITLE}: Configuration File Missing</H1>
$self->{SHORT_TITLE} needs the file \"$filename\" to opperate.<P></BODY></HTML>
EOF
    ;
    return $confResponse;
}

#####
# Front end to W3C::Util::ShowSource module

sub showMySources {
    my ($query) = @_;
    require W3C::Util::ShowSource;
    my @sources = ('W3C::Rdf::CGIApp.pm', 'W3C::Database::DBIInterface.pm', 
		   'W3C::Util::Properties.pm', 'W3C::Util::ShowSource.pm', 'W3C::Rdf::Parser.pm', 
		   'W3C::XML::XmlParser.pm', 'W3C::XML::InputSource.pm', 'W3C::XML::AttributeListImpl.pm', 
		   'W3C::XML::HandlerBase.pm', 'W3C::XML::SAXException.pm', 'W3C::XML::SAXParseException.pm', 
		   'W3C::XML::HandlerBase.pm', 'W3C::XML::XmlElement.pm');
    my %paths = ('W3C::Rdf::CGIApp.pm' => 'CGIApp', 
		 'W3C::Database::DBIInterface.pm' => 'DBIInterface',
		 'W3C::Util::Properties.pm' => 'Properties', 
		 'W3C::Util::ShowSource.pm' => 'ShowSource', 
		 'W3C::Rdf::Parser.pm' => 'Parser', 
		 'W3C::XML::XmlParser.pm' => 'XmlParser', 
		 'W3C::XML::InputSource.pm' => 'InputSource', 
		 'W3C::XML::AttributeListImpl.pm' => 'AttributeListImpl', 
		 'W3C::XML::HandlerBase.pm' => 'HandlerBase', 
		 'W3C::XML::SAXException.pm' => 'SAXException', 
		 'W3C::XML::SAXParseException.pm' => 'SAXParseException', 
		 'W3C::XML::HandlerBase.pm' => 'HandlerBase', 
		 'W3C::XML::XmlElement.pm' => 'XmlElement');

    my ($useColor, $funcLinks, $funcList) = ($query->param('w3c_useColor'), 
					     $query->param('w3c_funcLinks'), 
					     $query->param('w3c_funcList'));
    #####
    # Start the page
    print $query->header(-expires=>'+4h');
    print $query->start_html(-title=>'Rdf Editor Sources', -BGCOLOR=>"#FFFFFF", -TEXT=>"#000000", -LINK=>"#0000ee", -VLINK=>"#551a8b");
    print <<EOF;
<h1>Rdf Editor Sources</h1>
    <p>
    <a href='#CGIApp'>CGIApp</a> presents the the current 
    ACLs for a resource and creates forms to manipulate them. 
    <a href='#CGIApp'>CGIApp</a> then handles 
    the output from the forms. Both are layered on top of 
    <a href='#DBIInterface'>W3C::Database::DBIInterface.pm</a>.
    <p>
    RDF parsing facilities are handled by a stack of parser:
    <pre>
    <a href='#Parser'>W3C::Rdf::Parser</a>
       |
    <a href='#XmlParser'>W3C::XML::XmlParser</a>
    </pre> The <a href='#XmlParser'>W3C::XML::XmlParser</a> is an
    implementation of a SAX interface ported to perl. It uses:
    <ul>
	<li><a href='#InputSource'>W3C::XML::InputSource</a>
	<li><a href='#AttributeListImpl'>W3C::XML::AttributeListImpl</a>
	<li><a href='#HandlerBase'>W3C::XML::HandlerBase</a>
	<li><a href='#SAXException'>W3C::XML::SAXException</a>
	<li><a href='#SAXParseException.'>W3C::XML::SAXParseException.</a>
	<li><a href='#HandlerBase'>W3C::XML::HandlerBase</a>
	<li><a href='#XmlElement'>W3C::XML::XmlElement</a>
    </ul>
    <p>
    &lt;brought to you by <a href='#ShowSource'>W3C::Util::ShowSource.pm</a>&gt;.
EOF
    ;

    #####
    # Print source
    my $showSource = W3C::Util::ShowSource->new(\@sources, \%paths, $query);
    $showSource->present($useColor, $funcLinks, $funcList);
    print $query->end_html;
}

package W3C::Rdf::CGIApp::QueryObject;
use W3C::Util::Exception;
use W3C::Rdf::Algae2;
use W3C::Util::NamespaceHandler;

sub new {
    my ($proto, $atomDictionary, $attrib, $defaultDB, @parms) = @_;
    my $class = ref($proto) || $proto;
    my $self = $class->SUPER::new(@parms);

    my $namespaceHandler = new W3C::Util::NamespaceHandler(); # -separator => '::');
    $self->{QHANDLER} = new W3C::Rdf::Algae2(-atomDictionary => $atomDictionary,
					     -namespaceHandler => $namespaceHandler, 
					     -sources => {'' => $self}, 
					     -sourceAttribution => $attrib, 
					     -rdfDB => $defaultDB);
    return $self;
}

sub algaeReq {
    my ($self, $rdfDB, $presenter, $query, $auth, $lang) = @_;
    $lang ||= $QL_ALGAE;
    # temporary measure till i can work out repeated property management
    # my ($results, $selects, $messages, $statements) = $queryHandler->interpret($query, undef, $QL_ALGAE, 0x00, {-group => 1, -groupBy => ['a.id']});
    my $queryHandler = $rdfDB->getAlgaeInterface;
    my ($results, $selects, $messages, $statements) = $self->{QHANDLER}->interpret($query, undef, $lang, 0x00, {-group => 1, -uniqueResults => 1});
    $self->{QHANDLER}->clear();
    if (@$statements) {
	$self->showQueryResults($rdfDB, $results, $selects, $messages, $statements, $presenter, 'algae query', $query);
    } else {
	my $encQuery = CGI::escapeHTML($query);
	$presenter->printEmptyQuery("found no query results on \"$encQuery\".", $query);
    }
}

sub algaeLink {
    my ($self, $queryTemplate, $algaeURL) = @_;
    my @queries;
    my $query; eval '$query = "'.$queryTemplate.'"'; if ($@) {&throw()}
    my @urlStrs = ();
    my @htmlStrs = ();
    while ($query =~ m|\G(.*?)\n|gcxs) {
	my $line = $1;
	push (@urlStrs, CGI::escape("$line\n"));
	push (@htmlStrs, CGI::escapeHTML($line)."<br />\n");
    }
    if ($query =~ m|\G(.*)$|gcxs) {
	my $line = $1;
	push (@urlStrs, CGI::escape($line));
	push (@htmlStrs, CGI::escapeHTML($line));
    }
    my $url = join ('', @urlStrs);
    my $html = join ('', @htmlStrs);
    return "For more info, see <a href=\"$algaeURL?w3c_rdfQuery=$url\">$html</a>";
}

sub templateReq {
    my ($self, $rdfDB, $queryTemplate) = @_;
    my $query; eval '$query = "'.$queryTemplate.'"'; if ($@) {&throw()}
    my $queryHandler = $rdfDB->getAlgaeInterface;
    my @ret = $self->{QHANDLER}->interpret($query, undef, $QL_ALGAE, 0x00, {-group => 1, -uniqueResults => 1});
    $self->{QHANDLER}->clear();
    return @ret;
}


sub genericReq {
    my ($self, $rdfDB, $presenter, $queryTemplate, $what, $title, $noneFoundString, $moreInfo, $auth) = @_;
    my ($results, $selects, $messages, $statements, $query) = $self->templateReq($rdfDB, $queryTemplate);
    my $whatStr = "<a href=\"$what\">$what</a>";
    if (@$results) {
	$self->showQueryResults($rdfDB, $results, $selects, $messages, $statements, $presenter, "$title $whatStr", $query);
    } else {
	$presenter->printEmptyQuery("$noneFoundString $whatStr", $query, $moreInfo);
    }
}

sub showQueryResults {
    my ($self, $rdfDB, $results, $selects, $messages, $statements, $presenter, $title, $description) = @_;

    # statements is an array of arrays of proofs for each result. These may be
    # repeated if the same statements is used multiple times. For instance,
    #   (p1 ?s o1)(?p ?s ?o) -> [[p1 s1 o1][p0 s1 o1][p1 s1 o1][p2 s1 o1]]
    # We may care about these for pairing proofs with query constraints when
    # displaying them as proofs, but for just rendering the resulting triples,
    # we only care about unique Statements.
    my $aggregate = new W3C::Rdf::RdfDB(-atomDictionary => $rdfDB->{-atomDictionary});
    foreach my $db (@$statements) {
	$aggregate->copyTriples($db);
    }

    my $filter = $self->{-filter};
    if ($filter) {
	&$filter($aggregate);
    }
    $presenter->showQueryResults($aggregate, $results, $selects, $messages, $statements, $title, $description);
    return;

    my %uniques = (map {map {$_, $_} @$_} @$statements);
    my @uniqueStatements = ();
    foreach my $proof (@$statements) {
	my %proofUniques = (map {$_, $_} @$proof);
	push (@uniqueStatements, [values %proofUniques]);
    }

    $rdfDB->setCurrentView([@{$rdfDB->getCurrentView}, values %uniques]);

    # Some apps, like the annotation script, add data to the graph based on 
    # hard-wired info in the model.
    my $filter = $self->{-filter};
    if ($filter && @$statements) {
	my $tmpDB = $rdfDB->getRdfDB();
	my $attrib = $statements->[0][0]->getAttribution;
	my $annotation = $statements->[0][0]->getSubject;
	&$filter($tmpDB, $attrib, $annotation, \@uniqueStatements);
	#&$filter($tmpDB, $attrib, $annotation, $statements);
    }
    $presenter->showQueryResults($rdfDB, $results, $selects, $messages, \@uniqueStatements, $title, $description);
    #$presenter->showQueryResults($rdfDB, $results, $selects, $messages, $statements, $title, $description);
}

package W3C::Rdf::AnnotatedXmlSerializer;
use vars qw($VERSION $DSLI @ISA @EXPORT @EXPORT_OK);
use W3C::XML::HandlerBase;
@ISA = qw(W3C::XML::UriXmlSerializer); # Exporter AutoLoader);
@EXPORT = qw();
@EXPORT_OK = qw();
$VERSION = 0.10;
$DSLI = 'adpO';

sub print {
    my ($self, $toPrint) = @_;
    if ($self->{-printFilter}) {
	$toPrint = &{$self->{-printFilter}}($toPrint);
    }
    $self->SUPER::print($toPrint);
}

sub printAnnotation {
    my ($self, $annotation) = @_;
#    $self->{ANNOTATION} .= $annotation;
    $self->flush;
    $self->SUPER::print($annotation);
#    if ($self->{ANNOTATION}) {
#	$self->SUPER::print($self->{ANNOTATION});
#	$self->{ANNOTATION} = '';
#    }
}

package W3C::Rdf::CGIApp::Presenter;
use W3C::XML::XmlSerializer;
use vars qw(%RespStrings);
use W3C::Rdf::Atoms qw($RDF_SCHEMA_URI);

sub new {
    my ($proto, $engine, $renderer, $sessionId, $accept, %flags) = @_;
    my $class = ref($proto) || $proto;
    my $self = {%flags, ENGINE => $engine, RENDERER => $renderer, SESSION_ID => $sessionId, ACCEPT => $accept, OK_QUEUE => []};
    bless ($self, $class);
    return $self;
}

# queue text for output
sub printOK {
    my ($self, $printMe) = @_;
    push (@{$self->{OK_QUEUE}}, $printMe);
}

# most derived presenters don't show errors
sub printError {
    my ($self, $printMe) = @_;
#    push (@{$self->{OK_QUEUE}}, $printMe);
}

%RespStrings = {200 => 'OK', 
		201 => 'Created', 
		404 => 'Not Found'};

sub flush {
    my ($self, $status) = @_;
    my $respString = $RespStrings{$status};
    # my $statusLine = "HTTP/1.0 $status $respString";
    my $statusLine = "Status: $status $respString";
    my $mimeType = $self->getMimeType;
    my $body = join ('', @{$self->{OK_QUEUE}});
    my $length = length($body);
    my $ret =<<EOF
$statusLine
Server: W3C Annotation Server
Connection: close
Content-Type: $mimeType; charset=utf-8
Content-Length: $length
Pragma: no-cache
Cache-control: no-cache

EOF
    ;
    return $ret.$body;
}

sub getMimeType {
    return 'text/html';
}

# W3C::Rdf::CGIApp::RdfSerializerPresenter adds a {SERIALIZER} which stores the rendering in the {OK_QUEUE}

package W3C::Rdf::CGIApp::RdfSerializerPresenter;
use W3C::XML::XmlSerializer;
use vars qw(@ISA %RespStrings);
@ISA = qw(W3C::Rdf::CGIApp::Presenter);
use W3C::Rdf::Atoms qw($RDF_SCHEMA_URI);

sub new {
    my ($proto, $engine, $renderer, $sessionId, $accept, $nsMap, %flags) = @_;
    my $class = ref($proto) || $proto;
    my $self = $class->SUPER::new($engine, $renderer, $sessionId, $accept, %flags);
    $self->{OK_QUEUE} = [];
    $self->{SERIALIZED_STRING} = undef;

    my $importMap = new W3C::Util::NamespaceHandler();
    my $prefixes = [keys %$nsMap];
    foreach my $prefix (@$prefixes) {
	$importMap->addNamespace($prefix, $nsMap->{$prefix}, -collide => $W3C::Util::NamespaceHandler::NS_FAIL);
    }

    $self->makeSerializer(\ $self->{SERIALIZED_STRING}, $importMap);
    $self->startRdf($prefixes);
    return $self;
}

sub makeSerializer {
    my ($self, $target, $importMap, $serializerArgs) = @_;
    $serializerArgs ||= {};
    my $publicId = $self->{ENGINE}{BASE_URI};
    my $serializer = new W3C::Rdf::AnnotatedXmlSerializer(-target => $target, 
						    -namespaceElementSweep => 5, 
						    -namespaceElementTrip => 2, 
						    -namespaceForce => [$RDF_SCHEMA_URI],
						    # -namespaceHandler => $namespaceHandler
						    -defaultAttrNamespace => 0, 
						    -createNamespaces => 1, 
						    -prettyPrint => 1, 
						    -importMap => $importMap, 
						    %$serializerArgs);
    $self->{SERIALIZER} = $serializer;
    $serializer->addXmlDecl(-version => '1.0');
    $serializer->comment("session-id $self->{SESSION_ID}\"");

    use W3C::Rdf::XmlSerializer;
    my $xmlSerializer = new W3C::Rdf::XmlSerializer(-serializer => $serializer, 
						    -atomDictionary => $self->{ENGINE}{-atomDictionary}, 
						    -noRdfTag => 1, 
						    -RdfDB => $self->{ENGINE}{RDF_DB}, 
						    -allowAnonymousRefs => 1, 
						    -ignoreReifications => 1, 
						    -attributions => 0, 
						    -ignoreAnonymousReifications => 1, 
						    -neededNamespaces =>[qw(r)]);
    $self->{STATEMENT_SERIALIZER} = $xmlSerializer;
}

# Most apps present aggregate of all statements in all solutions.
# Others must overload <presenter>::showQueryResults.
sub showQueryResults {
    my ($self, $rdfDB, $results, $selects, $messages, $statements, $title, $description) = @_;
    if ($title || $description) {
	$self->printAnnotation($title, $description);
    }
    $self->serialize($rdfDB, undef);
}

sub serialize {
    my ($self, $rdfDB, $statements) = @_;
    my $iterator = $rdfDB->makeSerializerIterator($statements, $self->{ENGINE}{-queryHandler}, 
						  -scheme => 'guess', -sort => 1, 
						  # -neededNamespaces =>[qw(r a xlink http d)],
						  -ignoreReifications => 1, 
						  -allowAnonymousRefs => 1, 
						  -ignoreAnonymousReifications => 1, 
						  -serializeTypelessNodes => 0,# generate handy (illegal) [http:...#45] style uris
						   -serializeExpandResources => 0, 
						  -serializeNoNodeAttrs => 1, 
						  -serializeNoNesting => 0);
    if ($self->{SERIALIZER}->can('setIterator')) {
	$self->{SERIALIZER}->setIterator($iterator);
    }
    $iterator->iterate($self->{STATEMENT_SERIALIZER});
}

# these functions output one mimetype which is used by many presenters

sub startRdf {
    my ($self, $nsMap) = @_;
    $self->{SERIALIZER}->startDocument();
##    my $namespaceHandler = new W3C::Util::NamespaceHandler(-createNamespaces => 1);
#    $namespaceHandler->addNamespace('r', $RDF_SCHEMA_URI);
#    foreach my $ns (keys %$nsMap) {
#	$namespaceHandler->addNamespace($ns, $nsMap->{$ns}.'foo');
#    }

    $self->{SERIALIZER}->startElement("${RDF_SCHEMA_URI}RDF", {}, {-addNamespaces => $nsMap});
}

sub endRdf {
    my ($self) = @_;
    $self->{SERIALIZER}->endElement("${RDF_SCHEMA_URI}RDF");
    $self->{SERIALIZER}->endDocument();
}

sub printStatements {
    my ($self, $rdfDB, $statements) = @_;
    my $statementCount = scalar(@$statements);
    $self->serialize($rdfDB, $statements);
}

package W3C::Rdf::CGIApp::HTMLPresenter;
use W3C::Util::Exception;
use vars qw(@ISA);
@ISA = qw(W3C::Rdf::CGIApp::RdfSerializerPresenter);
use vars qw($Doctype $Style $Meta);
$Doctype = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"
    \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">";
$Style = "<link rel=\"stylesheet\" type=\"text/css\" href=\"CGIApp.css\" />";
$Meta = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />";

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = $class->SUPER::new(@_);
    bless ($self, $class);
    $self->{INFO} = [];
    return $self;
}

sub makeSerializer {
    my ($self, $target, $importMap) = @_;
    my $pf = sub {
	if ($self->{-keepClean}) {
	    return shift;
	}
	my $a = shift;
	# <r:Description
        #  about="http://example.com/foo#genid1">

	# This code HTML encodes the XML for HTML display. URLs are encased in
	# invalid URL characters ([]s), the XML is  HTML encoded, and the URLs
	# are removed from their protective cases. It is important to encase the
	# URLs before escaping them as the delimiting quotes are turned into
	# "&quot;" so there is no way to know when to end the URL.
	$a =~ s|([a-z]+://[\w:=%\./\~\?\#+;@\(\)\&,\-]*[\w:=%\./\~\?\#+;@\&\-])|[$1]|gi;
	# <r:Description
        #  about="[http://example.com/foo#genid1]">

	$a = $self->{RENDERER}->escapeHTML($a);
	# &lt;r:Description
        #  about=&quot;[http://example.com/foo#genid1]&quot;&gt;

	$a =~ s|\[([a-z]+://[\w:=%\./\~\?\#+;@\(\)\&,\-]*[\w:=%\./\~\?\#+;@\&\-])\]|<a href=\"$1\">$1</a>|gi;
	# &lt;r:Description
        #  about=&quot;http://example.com/foo#genid1&quot;&gt;

	return $a;
    };
    $self->SUPER::makeSerializer($target, $importMap, {-printFilter => $pf});
}

# queue text for output
sub printError {
    my ($self, $printMe) = @_;
    my $str = CGI::escapeHTML($printMe);
    push (@{$self->{OK_QUEUE}}, "<pre class=\"printError\">$str</pre>\n");
}

sub printHead {
    my ($self, $preMessages, $title) = @_;
    my $statusLine = "Status: 200 OK";
    if (!$title) {
	$title = 'RDF CGI results';
	$title .= ' for: '.$self->{BASE_URI} if ($self->{BASE_URI});
    }
    my $selfUri = $self->{ENGINE}->getSelfUri;
    my $infoStr = @{$self->{INFO}} ? join("\n\n", "<h1 class=\"info\">Info</h1>", 
					  (map {"<p class=\"info\">$_</p>"} @{$self->{INFO}}), undef) : '';
    my $tmp =<<EOF
<?xml version="1.0" encoding="utf-8"?>
$Doctype
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>$title</title>
  $Style
  $Meta
</head>
<body>
<form method="post" action="$selfUri" enctype="application/x-www-form-urlencoded">
  <p>session-id: <input name=\"w3c_rerun\" value=\"$self->{SESSION_ID}\" /></p>
</form>
$infoStr
<h1>Query Results</h1>
EOF
    ;
    $self->printOK($tmp);
    if (@$preMessages) {
	$self->printOK("<hr />\n<pre class=\"messages\">");
	$self->printOK(join ("\n", map {$self->{RENDERER}->escapeHTML($_)} @$preMessages));
	$self->printOK("</pre><hr />\n");
    }
}

sub printAnnotation {
    my ($self, $title, $desc, $plainText) = @_;
    my $algaeURL = new URI('algae')->abs(new URI($self->{ENGINE}->getSelfUri));
    my $descURL = $self->{RENDERER}->escape($desc);
    my @descStrs = ();
    while ($desc =~ m|\G(.*?)([a-z]+://[\w:=%\./\~\?\#+;@\(\)\&,\-]*[\w:=%\./\~\?\#+;@\&\-])|gcxs) {
	my ($chars, $url) = ($self->{RENDERER}->escapeHTML($1), $2);
	$chars =~ s/\bask\b/<a href=\"$algaeURL?w3c_rdfQuery=$descURL\">ask<\/a>/;
	push (@descStrs, "$chars<a href=\"$url\">$url</a>");
    }
    if ($desc =~ m|\G(.*)$|gcxs) {
	my $chars = $1;
	$chars =~ s/\bask\b/<a href=\"$algaeURL?w3c_rdfQuery=$descURL\">ask<\/a>/;
	push (@descStrs, $chars);
    }
    my $descStr = @descStrs ? join ('', "\n<pre class=\"printAnnotation_desc\">", @descStrs, "</pre>") : '';
    $self->{SERIALIZER}->printAnnotation("</pre>\n<h3>$title</h3>$descStr\n<pre class=\"code annotation\">$plainText");
}

sub warning {
    my ($self, $message) = @_;
    $self->printAnnotation('warning: ', $message);
}

sub error {
    my ($self, $message) = @_;
    $self->printAnnotation('error: ', $message);
}

sub printInfo {
    my ($self, @info) = @_;
    push (@{$self->{INFO}}, @info);
}

sub printEmptyQuery {
    my ($self, $message, $query, $plainText) = @_;
    $self->printAnnotation('warning: '.$message, $query, $plainText);
#    $self->printOK("<h3>warning</h3><p>$message</p>\n");
}

sub printStatements {
    my ($self, $statements) = @_;
    $self->printOK("<pre class=\"printStatements\">");
    $self->printOK(join ("\n", map {$self->{RENDERER}->escapeHTML($_)} @$statements));
    $self->printOK("</pre>\n");
}

sub printQueryResults {
    my ($self, $results) = @_;
    $self->printOK("<pre class=\"printQueryResults\">");
    $self->printOK(join ("\n", map {'('.join (' ', map {$_->toString} @$_).')'} @$results));
    $self->printOK("</pre>\n");
}

sub queryAtom { # static
    my ($pos, $statement) = @_;
    my ($term, $val);
    if ($pos eq 'p') {
	$term = $statement->getPredicate;
    } elsif ($pos eq 's') {
	$term = $statement->getSubject;
    } elsif ($pos eq 'o') {
	$term = $statement->getObject;
    } else {&throw(new W3C::Util::ProgramFlowException)}
    if ($term->isa('W3C::Rdf::BNode')) {
	$val = "?$pos";
    } else {
	$val = $term->toString();
    }
    return $val;
}

sub showStatement {
    my ($self, $statement, $colspan) = @_;
    my $id = $self->{ENGINE}{RDF_DB}->getStatementId($statement); # $statement->getSubclass($self->{ENGINE}{RDF_DB})->getPrimaryKey;
    my ($p, $s, $o) = map {&queryAtom($_, $statement)} qw(p s o);
    my $queryRef = $self->{RENDERER}->escapeHTML($self->_selfRef(['w3c_rdfQuery', "ask ($s $p $o {?a = %ATTRIB}) collect (?a)"]));
    my $statementIdRef = "<a href=\"algae?statements=$id\">$id</a><a href=\"$queryRef\">?</a>";
    my $string;
    eval {
	$string = $statement->toString(undef, undef, 
				       -showAttributions => 1, 
				       -noReifications => 1, 
				       -href => ['a', 'href']);
    }; if ($@) {if (my $ex = &catch('W3C::Util::Exception')) {
	my $exString = $ex->toString;
	$exString =~ s/\n/<br \/>\n/gs;
	$string = "<span class=\"warning\">$exString</span>";
    } else {
	$string = "<span class=\"warning\">$@</span>";
    }}
    $self->printOK("<tr><th style=\"code\">$statementIdRef</th><td colspan='$colspan' style=\"code\">$string</td></tr>\n");
}

package W3C::Rdf::CGIApp::RdfStatementPresenter;
use vars qw(@ISA);
@ISA = qw(W3C::Rdf::CGIApp::RdfSerializerPresenter);
use W3C::Util::Exception;

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = $class->SUPER::new(@_);
    return $self;
}

sub printHead {
    my ($self, $preMessages, $title) = @_;
}

sub printAnnotation {
    my ($self, $title, $desc) = @_;
}

sub warning {
    my ($self, $exception) = @_;
}

sub error {
    my ($self, $exception) = @_;
}

sub printInfo {
    my ($self, @info) = @_;
}

sub printEmptyQuery {
    my ($self, $message, $query, $plainText) = @_;
    my $errorPresenter = new W3C::Rdf::CGIApp::HTMLPresenter($self->{ENGINE}, $self->{RENDERER}, $self->{SESSION_ID});
    $errorPresenter->printOK("<html><head><title>No Results</title></head>\n<body><pre>");
    $errorPresenter->printAnnotation('error: '.$message, $query, $plainText);
    $errorPresenter->printOK($errorPresenter->{SERIALIZED_STRING});
    $errorPresenter->printOK("</pre></body></html>\n");
    &throw(new W3C::Rdf::CGIApp::AppException(-uri => '???', -status => 404, -presenter => $errorPresenter));
}

sub printFoot {
    my ($self, $opaque) = @_;
    $self->endRdf;
    $self->printOK($self->{SERIALIZED_STRING});
}

package W3C::Rdf::CGIApp::RdfPresenter;
use vars qw(@ISA);
@ISA = qw(W3C::Rdf::CGIApp::RdfStatementPresenter);
use W3C::Util::Exception;

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = $class->SUPER::new(@_);
    return $self;
}

sub getMimeType {
    my ($self) = @_;
    return $self->{ACCEPT};
}

package W3C::Rdf::CGIApp::N3Presenter;
use vars qw(@ISA);
@ISA = qw(W3C::Rdf::CGIApp::RdfStatementPresenter);
use W3C::Util::Exception;
use W3C::Rdf::Atoms qw($RDF_SCHEMA_URI);

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = $class->SUPER::new(@_);
    return $self;
}

sub makeSerializer {
    my ($self, $target, $nsMap, $serializerArgs) = @_;
    $serializerArgs ||= {};
    my $publicId = $self->{ENGINE}{BASE_URI};

    require W3C::Rdf::N3Serializer;
    $self->{SERIALIZER} = new W3C::Rdf::N3Serializer(# -serializer => undef, 
						    -noRdfTag => 1, 
						    -RdfDB => $self->{ENGINE}{RDF_DB}, 
						    -allowAnonymousRefs => 1, 
						    -ignoreReifications => 1, 
						    -ignoreAnonymousReifications => 1, 
						    -neededNamespaces =>[qw(r)]);
    $self->{STATEMENT_SERIALIZER} = $self->{SERIALIZER};
}

sub startRdf {
    my ($self, $nsMap) = @_;
    my $nsh = $self->{STATEMENT_SERIALIZER}->getNamespaceHandler;
    $nsh->addNamespace('r', $RDF_SCHEMA_URI);
    foreach my $ns (keys %$nsMap) {
	$nsh->addNamespace($ns, $nsMap->{$ns});
    }
}

sub endRdf {
    my ($self) = @_;
}

sub getMimeType {
    my ($self) = @_;
    return 'text/x-n3';
}

# most derived presenters don't show errors
sub printError {
    my ($self, $printMe) = @_;
    push (@{$self->{OK_QUEUE}}, "<> <http://dev.w3.org/cvsweb/perl/modules/W3C/Rdf/CGIApp.pm#hadError> \"\"\" $printMe \"\"\".\n");
}

package W3C::Rdf::CGIApp::ArrayPresenter;
use vars qw(@ISA);
@ISA = qw(W3C::Rdf::CGIApp::RdfSerializerPresenter);
use W3C::Util::Exception;

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = $class->SUPER::new(@_);
    bless ($self, $class);
    return $self;
}

sub getMimeType {
    return 'text/plain';
}

sub printHead {
    my ($self, $preMessages, $title) = @_;
}

sub showQueryResults {
    my ($self, $rdfDB, $nodes, $selects, $messages, $proofs, $presenter, $title, $description) = @_;
    $self->printOK(join ("\n", map {join (',', map {$_->toString} @$_)} @$nodes)."\n");
}

sub printFoot {
    my ($self) = @_;
}

sub printStatements {
    my ($self, $rdfDB, $statements) = @_;
    $self->printOK(join ("\n", map {join (',', map {$_->toString} ($_->getPredicate, $_->getSubject, $_->getObject))} @$statements)."\n");
}

package W3C::Rdf::CGIApp::SrxPresenter;
use vars qw(@ISA);
@ISA = qw(W3C::Rdf::CGIApp::Presenter);
use W3C::Util::Exception;
use W3C::Rdf::SPARQLXmlRenderer;
use W3C::Util::NamespaceHandler;

sub new {
    my ($proto, $engine, $renderer, $sessionId, $accept, $nsMap, %flags) = @_;
    my $class = ref($proto) || $proto;
    my $self = $class->SUPER::new($engine, $renderer, $sessionId, $accept, %flags);
    bless ($self, $class);
    return $self;
}

sub getMimeType {
    return 'application/srx+xml';
}

sub printHead {
    my ($self, $preMessages, $title) = @_;
}

sub showQueryResults {
    my ($self, $rdfDB, $nodes, $selects, $messages, $proofs, $presenter, $title, $description) = @_;
#    my $renderer = new W3C::Rdf::SPARQLXmlRenderer(-atomDictionary => $self->{-atomDictionary}, 
#						   -namespaceHandler => new W3C::Util::NamespaceInventor(-copyHandler => $self->{-namespaceHandler}));
    my $importMap = new W3C::Util::NamespaceHandler();
    $importMap->addNamespace('', 'http://www.w3.org/2005/sparql-results#');
    $importMap->addNamespace('r', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
    my $renderer = new W3C::Rdf::SPARQLXmlRenderer(-atomDictionary => $self->{-atomDictionary}, 
						   -createNamespaces => 1, -importMap => $importMap);
    $renderer->addHeaders($selects);
    $renderer->addData(@$nodes);
    $self->printOK($renderer->toString());
}

sub printFoot {
    my ($self) = @_;
}

sub printStatements {
    my ($self, $rdfDB, $statements) = @_;
    # $self->printOK(join ("\n", map {join (',', map {$_->toString} ($_->getPredicate, $_->getSubject, $_->getObject))} @$statements)."\n");
}

package W3C::Rdf::CGIApp;

1;

__END__

=head1 NAME

W3C::Rdf::CGIApp - Common CGI functions for RDF apps

=head1 SYNOPSIS

    use W3C::Rdf::CGIApp;
    package testRdfParser;
    @testRdfParser::ISA = qw(W3C::Rdf::CGIApp);

    my $flags = {-parmMapping => \%ParmMapping, -pathMapping => \%PathMapping});
    my $tester = new testRdfParser($CGIinput, $flags);
    $tester->execute();

    sub render {
	my ($self) = @_;
	$query = "ask (?x http://www.w3.org/schema/certHTMLv1/access \"acc1\" .
?x ?y \"http://user1\")
collect (?x)" if (!$query);
	my $queryHandler = $self->{RDF_DB}->getAlgaeInterface;
	my ($nodes2, $messages) = $queryHandler->algae($query, $self->{RDF_PARSER}->{REVISION});
    }

=head1 DESCRIPTION

Common handlers and CLI for RDF apps. This is the basis for the Algae and
Annotation (and Bookmarks) CGI scripts.

This module is part of the W3C::RDF CPAN module suite.

=head1 ARGUMENTS

=item B<@<file>> - read arguments from <file>

=item B<...>

=head1 AUTHOR

Eric Prud\'hommeaux <eric@w3.org>

=head1 SEE ALSO

W3C::Rdf::Parser(3) W3C::Rdf::RdfDB(3) W3C::XML::XmlParser(3) perl(1).

=cut
