Commits

Toby Inkster committed 2741090

Cleaned up this old parser; getting it ready for CPAN.

  • Participants

Comments (0)

Files changed (9)

+#############################################################
+
+@prefix :        <http://usefulinc.com/ns/doap#> .
+@prefix dcs:     <http://ontologi.es/doap-changeset#> .
+@prefix dc:      <http://purl.org/dc/terms/> .
+@prefix foaf:    <http://xmlns.com/foaf/0.1/> .
+@prefix my:      <http://purl.org/NET/cpan-uri/dist/Example-Example/> .
+@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix toby:    <http://tobyinkster.co.uk/#> .
+@prefix xsd:     <http://www.w3.org/2001/XMLSchema#> .
+
+#############################################################
+
+<>
+
+	dc:title         "Changes" ;
+	dc:description   "Revision history for Perl extension Example::Example."@en ;
+	dc:subject       my:project ;
+	dc:creator       toby:i .
+
+#############################################################
+
+my:v_0-01
+
+	a               :Version ;
+	dc:issued       "2000-01-01"^^xsd:date ;
+	:revision       "0.01"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/Example-Example-0.01.tar.gz> ;
+	rdfs:comment    "Original version"@en .
+
+#############################################################
+
+my:project
+
+	a               :Project ;
+	:name           "Example-Example" ;
+	:shortdesc      "Example Project"@en ;
+	:programming-language  "Perl" ;
+	:homepage       <http://search.cpan.org/dist/Example-Example/> ;
+	:download-page  <http://search.cpan.org/dist/Example-Example/> ;
+	:bug-database   <http://rt.cpan.org/Dist/Display.html?Queue=Example-Example> ;
+	:repository     [ a :SVNRepository ; :browse <http://goddamn.co.uk/viewvc/perlmods/Example-Example/> ] ;
+	:maintainer     toby:i ;
+	:developer      toby:i ;
+	:documenter     toby:i ;
+	:tester         toby:i ;
+	:created        "2000-01-01"^^xsd:date ;
+	:license        <http://dev.perl.org/licenses/> ;
+	:release        my:v_0-01 .
+
+#############################################################
+
+toby:i
+
+	a               foaf:Person ;
+	foaf:name       "Toby Inkster" ;
+	foaf:homepage   <http://tobyinkster.co.uk/> ;
+	foaf:page       <http://search.cpan.org/~tobyink/> ;
+	foaf:mbox       <mailto:tobyink@cpan.org> .
+
+#############################################################
+Changes
+Changes.ttl
+Changes.xml
+Makefile.PL
+MANIFEST
+MANIFEST.SKIP
+README
+META.yml
+SIGNATURE
+
+t/00sig.t
+t/01basic.t

File MANIFEST.SKIP

+^Makefile$
+^blib/
+^pm_to_blib
+^blibdirs
+\.svn
+^example.*\.pl$
+^[^/]+\.(tar\.gz|tar\.bz2|tgz|tbz2|tbz|zip|tar)$
+^MYMETA..yml
+use strict;
+use warnings;
+
+use inc::Module::Install;
+
+my $dist = 'Example-Example';
+my $fn   = "lib/$dist.pm"; $fn =~ s#-#/#g;
+
+name                $dist;
+perl_version_from   $fn;
+version_from        $fn;
+abstract_from       $fn;
+readme_from         $fn;
+author              'Toby Inkster <tobyink@cpan.org>';
+license             'perl';
+
+test_requires       'Test::More' => '0.61';
+
+requires            'Carp'               => '1.00';
+requires            'DateTime'           => 0;
+requires            'RDF::Trine'         => '0.112';
+requires            'XML::LibXML'        => '1.60';
+requires            'URI'                => '1.30';
+
+# install_script 'fingerw';
+
+resources(
+	'homepage'   => "http://search.cpan.org/dist/$dist/",
+	'repository' => "http://goddamn.co.uk/viewvc/perlmods/$dist/",
+	'bugtracker' => "http://rt.cpan.org/Dist/Display.html?Queue=$dist",
+	);
+	
+write_doap_changes;
+write_doap_changes_xml;
+
+include 'Test::Signature';
+auto_install;
+WriteAll(
+	'meta' => 1,
+	'sign' => 1,
+	);
+use 5.010;
+use lib "lib";
+use RDF::MicroTurtle::Parser;
+use JSON;
+
+my $p    = RDF::MicroTurtle::Parser->new;
+my $mttl = 'lalala #mttl <#me> has #person foaf:name "Bob" ; is foaf:member of @blah ; foaf:page http://example.net/ .';
+
+$p->parse(
+	'http://example.net/',
+	$mttl,
+	sub { say $_[0]->as_string; }
+	);

File lib/RDF/MicroTurtle/Context.pm

+package RDF::MicroTurtle::Context;
+
+use 5.010;
+use common::sense;
+use utf8;
+use CGI::Util qw'escape';
+use RDF::TrineShortcuts qw':nodes rdf_statement';
+use Data::UUID;
+
+sub new
+{
+	my ($class, %args) = @_;
+	
+	my $uuid = Data::UUID->new->create_str;
+	$args{'agent_uri'}  ||= "widget://$uuid/Agents#\%s";
+	$args{'tag_uri'}    ||= "widget://$uuid/Tags#\%s";
+	$args{'me_uri'}     ||= "widget://$uuid/Me";
+	
+	return bless \%args, $class;
+}
+
+sub agent_uri
+{
+	my ($self, $account_name) = @_;
+	
+	return sprintf($self->{'agent_uri'}, escape($account_name));
+}
+
+sub tag_uri
+{
+	my ($self, $tag) = @_;
+	
+	(my $canon_tag = lc $tag) =~ s/\-\_\.//g;
+	return sprintf($self->{'tag_uri'}, escape($canon_tag));
+}
+
+sub me_uri
+{
+	my ($self) = @_;
+	
+	return $self->{'me_uri'};
+}
+
+sub agent_triples
+{
+	my ($self, $account_name) = @_;
+	
+	return (
+		rdf_statement(
+			rdf_resource($self->agent_uri($account_name)),
+			rdf_resource('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
+			rdf_resource('http://xmlns.com/foaf/0.1/Agent'),
+			),
+		rdf_statement(
+			rdf_resource($self->agent_uri($account_name)),
+			rdf_resource('http://xmlns.com/foaf/0.1/nick'),
+			rdf_literal($account_name),
+			),
+		);
+}
+
+sub tag_triples
+{
+	my ($self, $tag) = @_;
+	
+	return (
+		rdf_statement(
+			rdf_resource($self->tag_uri($tag)),
+			rdf_resource('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
+			rdf_resource('http://www.holygoat.co.uk/owl/redwood/0.1/tags/Tag'),
+			),
+		rdf_statement(
+			rdf_resource($self->tag_uri($tag)),
+			rdf_resource('http://www.holygoat.co.uk/owl/redwood/0.1/tags/name'),
+			rdf_literal($tag),
+			),
+		);
+}
+
+sub tagging_triples
+{
+	my ($self, $thing, $tag) = @_;
+	
+	return (
+		rdf_statement(
+			rdf_node($thing),
+			rdf_resource('http://www.holygoat.co.uk/owl/redwood/0.1/tags/taggedWithTag'),
+			rdf_resource($self->tag_uri($tag)),
+			),
+		);
+}
+
+
+1;

File lib/RDF/MicroTurtle/Parser.pm

+package RDF::MicroTurtle::Parser;
+
+use 5.010;
+use base qw'RDF::Trine::Parser';
+use common::sense;
+use utf8;
+use Digest::SHA1 qw'sha1_hex';
+use LWP::Simple;
+use RDF::MicroTurtle::Context;
+use RDF::TrineShortcuts qw':nodes rdf_statement';
+use Text::Balanced qw(extract_bracketed extract_delimited);
+use URI;
+
+our $VERSION = '0.001';
+
+BEGIN
+{
+	our $count = 0;
+	
+	# Certain prefixes are hard-coded. This list is part of the MicroTurtle spec.
+	# (Or it will be if a spec gets written.) The list has a number of good,
+	# general-purpose vocabs, plus a number which have been chosen because they
+	# seem good matches for the type of topics frequently discussed in microblogs.
+	#
+	# For other prefixes, prefix.cc's service is used, but that is not a very
+	# stable way of doing things.
+	our $expand = {
+		'bio'     => 'http://purl.org/vocab/bio/0.1/' ,
+		'cc'      => 'http://creativecommons.org/ns#' ,
+		'ccold'   => 'http://web.resource.org/cc/' ,
+		'ccrel'   => 'http://creativecommons.org/ns#' ,
+		'dc'      => 'http://purl.org/dc/terms/' ,
+		'dc11'    => 'http://purl.org/dc/elements/1.1/' ,
+		'dcterms' => 'http://purl.org/dc/terms/' ,
+		'doac'    => 'http://ramonantonio.net/doac/0.1/#' ,
+		'doap'    => 'http://usefulinc.com/ns/doap#' ,
+		'foaf'    => 'http://xmlns.com/foaf/0.1/' ,
+		'geo'     => 'http://www.w3.org/2003/01/geo/wgs84_pos#' , 
+		'ical'    => 'http://www.w3.org/2002/12/cal/icaltzd#' ,
+		'lac'     => 'http://laconi.ca/ont/' ,
+		'like'    => 'http://ontologi.es/like#' ,
+		'log'     => 'http://www.w3.org/2000/10/swap/log#' ,
+		'mo'      => 'http://purl.org/ontology/mo/' ,
+		'ov'      => 'http://open.vocab.org/terms/' ,
+		'owl'     => 'http://www.w3.org/2002/07/owl#' ,
+		'rdf'     => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' ,
+		'rdfg'    => 'http://www.w3.org/2004/03/trix/rdfg-1/' ,
+		'rdfs'    => 'http://www.w3.org/2000/01/rdf-schema#' ,
+		'rel'     => 'http://purl.org/vocab/relationship/' ,
+		'rev'     => 'http://purl.org/stuff/rev#' ,
+		'rss'     => 'http://purl.org/rss/1.0/' ,
+		'sioc'    => 'http://rdfs.org/sioc/ns#' ,
+		'skos'    => 'http://www.w3.org/2004/02/skos/core#' ,
+		'tags'    => 'http://www.holygoat.co.uk/owl/redwood/0.1/tags/' ,
+		'vcard'   => 'http://www.w3.org/2006/vcard/ns#' ,
+		'wot'     => 'http://xmlns.com/wot/0.1/' ,
+		'xfn'     => 'http://vocab.sindice.com/xfn#' ,
+		'xhv'     => 'http://www.w3.org/1999/xhtml/vocab#' ,
+		'xsd'     => 'http://www.w3.org/2001/XMLSchema#' ,
+		};
+	
+	$RDF::Trine::Parser::parser_names{'mttl'}              = __PACKAGE__;
+	$RDF::Trine::Parser::parser_names{'microturtle'}       = __PACKAGE__;
+	$RDF::Trine::Parser::media_types{'text/x-microturtle'} = __PACKAGE__;
+	$RDF::Trine::Parser::media_types{'text/microturtle'}   = __PACKAGE__;
+}
+
+sub new
+{
+	my ($class, %args) = @_;
+	$args{'parsing_context'} ||= RDF::MicroTurtle::Context->new;
+	
+	my $self = bless \%args, $class;
+	
+	return $self;
+}
+
+sub parse
+{
+	my ($proto, $base_uri, $rdf, $handler) = @_;
+	$proto = $proto->new unless ref $proto;
+	
+	my $data = $proto->microturtle($base_uri, $rdf);
+	
+	foreach my $triple (@{ $data->{triples} })
+	{
+		$handler->( $triple );
+	}
+
+	foreach my $agent (@{ $data->{agents} })
+	{
+		foreach my $triple ($proto->{'parsing_context'}->agent_triples($agent))
+		{
+			$handler->( $triple );
+		}
+	}
+	
+	my $tags_used = {};
+	while (my ($uri, $tags) = each %{ $data->{tags} })
+	{
+		foreach my $tag (@$tags)
+		{
+			foreach my $triple ($proto->{'parsing_context'}->tagging_triples($uri, $tag))
+			{
+				$handler->( $triple );
+			}
+			$tags_used->{$tag}++;
+		}
+	}
+	foreach my $tag (keys %$tags_used)
+	{
+		foreach my $triple ($proto->{'parsing_context'}->tag_triples($tag))
+		{
+			$handler->( $triple );
+		}
+	}
+}
+
+sub microturtle
+{
+	my ($self, $base, $text) = @_;
+	
+	my ($comment, $mttl);
+	
+	if ($text =~ /^(.*)#m?ttl(.+)$/)
+	{
+		($comment, $mttl) = ($1, $2);
+	}
+	else
+	{
+		($comment, $mttl) = (undef, $text);		
+	}
+	
+	$base ||= sprintf('widget://%s.microturtle/self', sha1_hex($text));
+	
+	my $parsed  = $self->_mttl_parse($mttl, $base);
+	my @triples = map { _trine_statement($_) } @{ $parsed->{'triples'} };
+	
+	$parsed->{'triples'} = \@triples;
+	
+	return $parsed;
+}
+
+sub _trine_statement
+{
+	my ($triple) = @_;
+	my ($s, $p, $o, $rev) = @$triple;
+	
+	($s, $o) = ($o, $s) if $rev;
+	
+	my ($subject, $predicate, $object);
+
+	if ($s =~ /^<(.*)>$/)
+	{
+		$subject = rdf_resource($1);
+	}
+	else
+	{
+		$subject = rdf_node($s);
+	}
+
+	if ($p =~ /^<(.*)>$/)
+	{
+		$predicate = rdf_resource($1);
+	}
+	else
+	{
+		$predicate = rdf_node($p);
+	}
+
+	if (ref $o && $o->isa('RDF::MicroTurtle::Parser::Literal'))
+	{
+		$object = rdf_literal($o->{value}, lang=>$o->{lang}, datatype=>$o->{dturi});
+	}
+	elsif ($o =~ /^<(.*)>$/)
+	{
+		$object = rdf_resource($1);
+	}
+	else
+	{
+		$object = rdf_node($o);
+	}
+	
+	return rdf_statement($subject, $predicate, $object);
+}
+
+sub _mttl_parse
+{
+	my ($self, $mttl, $base) = @_;
+	
+	my $agents = [];
+	my @raw    = $self->_mttl_tokenize($mttl);
+	my @tokens = $self->_mttl_expand($base, $agents, @raw);
+	
+	my $triples;
+	my $tags = {};
+	my $current_triple;
+	my $isof = 0;
+	
+	while (my $t = shift @tokens)
+	{
+		if ((ref $t) && $t->isa('RDF::MicroTurtle::Parser::Literal'))
+		{
+			push @$current_triple, $t;
+		}
+		elsif ($t =~ /^#(\S+)$/)
+		{
+			#hashtags 
+			if (defined $current_triple->[0])
+			{
+				push @{$tags->{$current_triple->[0]}}, $1;
+			}
+			else
+			{
+				push @{$tags->{'<'.$base.'>'}}, $1;
+			}
+		}
+		elsif ($t eq '.')
+		{
+			if (defined $current_triple->[2] && !defined $current_triple->[3])
+			{
+				push @$current_triple, 'REV' if $isof;
+				push @$triples, $current_triple;
+				$isof = 0;
+			}
+			$current_triple = [];
+		}
+		elsif ($t eq ';')
+		{
+			if (defined $current_triple->[2] && !defined $current_triple->[3])
+			{
+				push @$current_triple, 'REV' if $isof;
+				push @$triples, $current_triple;
+				$isof = 0;
+			}
+			$current_triple = [$current_triple->[0]];
+		}
+		elsif ($t eq ',')
+		{
+			if (defined $current_triple->[2] && !defined $current_triple->[3])
+			{
+				push @$current_triple, 'REV' if $isof;
+				push @$triples, $current_triple;
+			}
+			$current_triple = [$current_triple->[0] , $current_triple->[1]];
+		}
+		elsif ($t eq '[]')
+		{
+			push @$current_triple, '_:'.$self->_random_bnode;
+		}
+		elsif ($t eq 'has')
+		{
+			# noop
+		}
+		elsif ($t eq 'is')
+		{
+			# noop
+		}
+		elsif ($t eq 'of')
+		{
+			$isof = 1;
+		}
+		elsif ($t eq '[')
+		{
+			my $anon = '_:'.$self->_random_bnode;
+			my $r = $self->_mttl_parse_bracketted(\@tokens, $anon, $triples, $tags);
+			foreach my $triple (@{ $r->{'triples'} })
+			{
+				push @$triples, $triple;
+			}
+			push @$current_triple, $anon;
+		}
+		else
+		{
+			push @$current_triple, $t;
+		}
+	}
+	
+	if (defined $current_triple->[2] && !defined $current_triple->[3])
+	{
+		push @$current_triple, 'REV' if $isof;
+		push @$triples, $current_triple;
+		$isof = 0;
+	}
+	
+	return {
+		'triples' => $triples ,
+		'tags'    => $tags ,
+		'agents'  => $agents ,
+		};
+}
+
+
+sub _mttl_parse_bracketted
+{
+	my ($self, $tokens, $subject, $triples, $tags) = @_;
+	
+	my $current_triple = [$subject];
+	my $isof = 0;
+	
+	while (my $t = shift @$tokens)
+	{
+		last if (!ref $t) && ($t eq ']');
+	
+		if ((ref $t) && $t->isa('RDF::MicroTurtle::Parser::Literal'))
+		{
+			push @$current_triple, $t;
+		}
+		elsif ($t =~ /^#(\S+)$/)
+		{
+			push @{$tags->{$subject}}, $1;  #hashtags
+		}
+		elsif ($t eq ';')
+		{
+			if (defined $current_triple->[2] && !defined $current_triple->[3])
+			{
+				push @$current_triple, 'REV' if $isof;
+				push @$triples, $current_triple;
+				$isof = 0;
+			}
+			$current_triple = [$subject];
+		}
+		elsif ($t eq ',')
+		{
+			if (defined $current_triple->[2] && !defined $current_triple->[3])
+			{
+				push @$current_triple, 'REV' if $isof;
+				push @$triples, $current_triple;
+			}
+			$current_triple = [$subject, $current_triple->[1]];
+		}
+		elsif ($t eq '[]')
+		{
+			push @$current_triple, '_:'.$self->_random_bnode;
+		}
+		elsif ($t eq '[')
+		{
+			my $anon = '_:'.$self->_random_bnode;
+			my $r = $self->_mttl_parse_bracketted($tokens, $anon, $triples, $tags);
+			foreach my $triple (@{ $r->{'triples'} })
+			{
+				push @$triples, $triple;
+			}
+			push @$current_triple, $anon;
+		}
+		else
+		{
+			push @$current_triple, $t;
+		}
+	}
+	
+	if (defined $current_triple->[2] && !defined $current_triple->[3])
+	{
+		push @$current_triple, 'REV' if $isof;
+		push @$triples, $current_triple;
+		$isof = 0;
+	}
+		
+	return {
+		'triples' => $triples ,
+		'tags'    => $tags
+		};
+}
+
+sub _mttl_tokenize
+{
+	my ($self, $ts) = @_;
+	my @rv;
+	
+	if ($ts =~ /^[\s\r\n]*\{.*\}[\s\r\n]*$/s)
+	{
+		$ts =~ s/^([\s\r\n]*\{)|(\}[\s\r\n]*$)//gs;
+	}
+	
+	while (length $ts)
+	{
+		$ts =~ s/^[\s\r\n]+//s;
+		
+		if ($ts =~ /^([\-\+]?([0-9]+\.[0-9]*e[\-\+]?[0-9]+))/i
+		||     $ts =~ /^([\-\+]?(\.[0-9]+e[\-\+]?[0-9]+))/i
+		||     $ts =~ /^([\-\+]?([0-9]+e[\-\+]?[0-9]+))/i)
+		{
+			push @rv, RDF::MicroTurtle::Parser::Literal->new($1, undef, 'xsd:double');
+			$ts = substr($ts, length $1);
+		}
+		elsif ($ts =~ /^([\-\+]?([0-9]+\.[0-9]*))/
+		||     $ts =~ /^([\-\+]?(\.[0-9]+))/)
+		{
+			push @rv, RDF::MicroTurtle::Parser::Literal->new($1, undef, 'xsd:decimal');
+			$ts = substr($ts, length $1);
+		}
+		elsif ($ts =~ /^([\-\+]?([0-9]+))/)
+		{
+			push @rv, RDF::MicroTurtle::Parser::Literal->new($1, undef, 'xsd:integer');
+			$ts = substr($ts, length $1);
+		}
+		elsif ($ts =~ /^([\.\,\;])/)
+		{
+			push @rv, $1;
+			$ts = substr($ts, length $1);
+		}
+# Don't have placeholder variables.
+#		elsif ($ts =~ /^([\?\$]\S+)/)
+#		{
+#			push @rv, $1;
+#			$ts = substr($ts, length $1);
+#		}
+# But have hashtags...
+		elsif ($ts =~ /^([\#][A-Za-z0-9_\.-]+)/)
+		{
+			push @rv, $1;
+			$ts = substr($ts, length $1);
+		}
+		elsif ($ts =~ /^(<[^>]*>)/)
+		{
+			push @rv, $1;
+			$ts = substr($ts, length $1);
+		}
+		elsif ($ts =~ m'^(https?://\S+)'i)
+		{
+			push @rv, '<'.$1.'>';
+			$ts = substr($ts, length $1);
+		}
+		elsif ($ts =~ /^["']/)
+		{
+			my ($string, $lang, $dt, $dturi);
+			($string, $ts) = extract_delimited($ts, substr($ts, 0, 1));
+			
+			if ($ts =~ /^(
+				\@
+				(
+					(([A-Z]{2,8})|i|x)		# primary
+					(\-[A-Z]{4})?			# script
+					(\-([A-Z]{2})|([0-9]{3}))?	# region
+					(\-[A-Z0-9]+)*			# other bits
+				)
+				)/ix)
+			{
+				$lang = lc $2;
+				$ts = substr($ts, length $1);
+			}
+			if ($ts =~ /^(\^\^<([^>]*)>)/)
+			{
+				$dturi = $2;
+				$ts = substr($ts, length $1);
+			}
+			elsif ($ts =~ /^(\^\^([^:]*:[^\s\,\;]*))/)
+			{
+				$dt = $2;
+				$ts = substr($ts, length $1);
+			}
+			
+			$string = substr($string, 1, (length $string)-2);
+			
+			push @rv, RDF::MicroTurtle::Parser::Literal->new($string, $lang, $dt, $dturi);
+		}
+		elsif ($ts =~ /^(_:[^\s\,\;]*)/)
+		{
+			push @rv, $1;
+			$ts = substr($ts, length $1);
+		}
+		elsif ($ts =~ /^([^\s\,\;]*:[^\s\,\;]*)/)
+		{
+			push @rv, $1;
+			$ts = substr($ts, length $1);
+		}
+		elsif ($ts =~ /^a\b/)
+		{
+			push @rv, '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>';
+			$ts = substr($ts, 1);
+		}
+		elsif ($ts =~ /^of\b/)
+		{
+			push @rv, 'of';
+			$ts = substr($ts, 2);
+		}
+		elsif ($ts =~ /^is\b/)
+		{
+			push @rv, 'is';
+			$ts = substr($ts, 2);
+		}
+		elsif ($ts =~ /^has\b/)
+		{
+			push @rv, 'has';
+			$ts = substr($ts, 3);
+		}
+		elsif ($ts =~ /^=>/)
+		{
+			push @rv, '<http://www.w3.org/2000/10/swap/log#implies>';
+			$ts = substr($ts, 2);
+		}
+		elsif ($ts =~ /^<=/)
+		{
+			push @rv, ('is', '<http://www.w3.org/2000/10/swap/log#implies>', 'of');
+			$ts = substr($ts, 2);
+		}
+		elsif ($ts =~ /^at\b/)
+		{
+			push @rv, '<http://www.w3.org/2003/01/geo/wgs84_pos#location>';
+			$ts = substr($ts, 2);
+		}
+		elsif ($ts =~ /^says\b/)
+		{
+			push @rv, '<http://open.vocab.org/terms/quote>';
+			$ts = substr($ts, 4);
+		}
+		elsif ($ts =~ /^gist\b/)
+		{
+			push @rv, '<http://purl.org/dc/terms/abstract>';
+			$ts = substr($ts, 4);
+		}
+		elsif ($ts =~ /^=/)
+		{
+			push @rv, '<http://www.w3.org/2002/07/owl#sameAs>';
+			$ts = substr($ts, 1);
+		}
+		elsif ($ts =~ /^\s*([→])/)
+		{
+			push @rv, '<http://xmlns.com/foaf/0.1/homepage>';
+			$ts = substr($ts, length($1));
+		}
+		elsif ($ts =~ /^\s*([←])/)
+		{
+			push @rv, ('<http://xmlns.com/foaf/0.1/primaryTopic>');
+			$ts = substr($ts, length($1));
+		}
+		elsif ($ts =~ /^\s*([♥♡❤])/)
+		{
+			push @rv, '<http://ontologi.es/like#likes>';
+			$ts = substr($ts, length($1));
+		}
+		elsif ($ts =~ /^(true|false)\b/i)
+		{
+			push @rv, RDF::MicroTurtle::Parser::Literal->new($1, undef, 'xsd:boolean');
+			$ts = substr($ts, length $1);
+		}
+		elsif ($ts =~ /^(\@[A-Za-z0-9][A-Za-z0-9-]*)\b/)
+		{
+			push @rv, "<$1>";
+			$ts = substr($ts, length $1);
+		}
+		elsif ($ts =~ /^(\[\s*\])/)
+		{
+			push @rv, '[]';
+			$ts = substr($ts, length $1);
+		}
+		elsif ($ts =~ /^([\[\]\(\)])/)
+		{
+			push @rv, $1;
+			$ts = substr($ts, 1);
+		}
+		else
+		{
+			warn "Possible Turtle tokenisation problem!\n";
+			push @rv, "???ERRORCOND???";
+			return @rv;
+		}
+	}
+	
+	return @rv;
+}
+
+sub _mttl_expand
+{
+	my ($self, $base, $agent_list, @terms) = @_;
+	my $uri   = URI->new($base);
+	my @rv;
+	
+	while (my $term = shift @terms)
+	{
+		if ((ref $term) && $term->isa('RDF::MicroTurtle::Parser::Literal'))
+		{
+			push @rv, $term;
+		}
+		elsif ($term =~ /^<\@(.+)>$/)
+		{
+			push @$agent_list, $1;
+			push @rv, $self->{'parsing_context'}->agent_uri($1);
+		}
+		elsif ($term =~ /^<#me>$/)
+		{
+			push @rv, $self->{'parsing_context'}->me_uri;
+		}
+		elsif ($term =~ /^([A-Za-z0-9_-]+):(\S+)$/
+		&&     $term !~ /^_:/)
+		{
+			my ($prefix, $suffix) = ($1, $2);
+			if (!defined $RDF::MicroTurtle::Parser::expand->{$prefix})
+			{
+				my $txt = &get("http://prefix.cc/$prefix.txt.plain");
+				chomp $txt;
+				my (undef, $uri) = split /\s/, $txt;
+				$RDF::MicroTurtle::Parser::expand->{$prefix} = $uri;
+			}
+			push @rv, '<' . $RDF::MicroTurtle::Parser::expand->{$prefix} . $suffix . '>';
+		}
+		elsif ($term =~ /^<(.+)>$/)
+		{
+			my $termuri = URI->new_abs($1, $uri);
+			push @rv, "<$termuri>";
+		}
+		elsif ($term =~ /^<>$/)
+		{
+			push @rv, "<$uri>";
+		}
+		else
+		{
+			push @rv, $term;
+		}
+	}
+	
+	return @rv;
+}
+
+sub _random_bnode
+{
+	return sprintf('b%08d', $RDF::MicroTurtle::count++);
+}
+
+1;
+
+package RDF::MicroTurtle::Parser::Literal;
+
+use overload '""' => sub { return $_[0]->{'value'}; } ;
+use LWP::Simple;
+
+sub new
+{
+	my $class = shift;
+	my $value = shift;
+	my $lang  = shift;
+	my $dt    = shift;
+	my $dturi = shift;
+	
+	if (!$dturi && $dt =~ /^([^:]+):(.*)$/)
+	{
+		my ($prefix, $suffix) = ($1, $2);
+		if (!defined $RDF::MicroTurtle::Parser::expand->{$prefix})
+		{
+			my $txt = &get("http://prefix.cc/$prefix.txt.plain");
+			chomp $txt;
+			my (undef, $uri) = split /\s/, $txt;
+			$RDF::MicroTurtle::expand->{$prefix} = $uri;
+		}
+		
+		$dturi = $RDF::MicroTurtle::expand->{$prefix} . $suffix;
+	}
+	
+	my $self = {
+		'value' => $value ,
+		'lang'  => (lc $lang) ,
+		'dt'    => $dt ,
+		'dturi' => $dturi ,
+		};
+	
+	bless $self, $class;
+}
+
+1;
+use lib 'inc';
+use Test::More tests => 1;
+use Test::Signature;
+signature_ok();
+use Test::More tests => 1;
+BEGIN { use_ok('Example::Example') };
+