=head1 NAME

JSONLD - A toolkit for transforming JSON-LD data.

=head1 VERSION

This document describes JSONLD version 0.001.

=head1 SYNOPSIS

  use v5.14;
  use JSON;
  use JSONLD;
  
  my $infile = 'test.jsonld';
  open(my $fh, '<', $infile) or die $!;
  my $content	= do { local($/); <$fh> };
  my $data = JSON->new()->boolean_values(0, 1)->decode($content);
  
  my $jld = JSONLD->new();
  my $expanded	= $jld->expand($data);

=head1 DESCRIPTION

This module implements part of the JSON-LD 1.1 standard for manipulating JSON
data as linked data.

This version (0.001) provides full support for the JSON-LD 1.1 "Expansion" and
"toRdf" transformations (the latter primarily being useful through a subclass
of JSON-LD, such as that provided by L<AtteanX::Parser::JSONLD>).
Partial support for the "Compaction" transformation is provided, but it
contains many known deficiencies. Full support for "Compaction" will be
forthcoming in a future release.
No other JSON-LD transformation are supported at this time.

=head1 METHODS

=over 4

=cut

package JSONLD {
	use v5.14;
	use autodie;
	our $VERSION	= '0.001';
	use utf8;
	use Moo;
	use LWP;
	use List::Util qw(all any);
	use JSON;
	use JSON qw(decode_json);
	use IRI;
	use FindBin qw($Bin);
	use File::Spec;
	use File::Glob qw(bsd_glob);
	use Encode qw(encode decode_utf8);
	use Debug::ShowStuff qw(indent println);
	use Data::Dumper;
	use Clone 'clone';
	use Carp qw(confess);
	use B qw(svref_2object SVf_IOK SVf_POK SVf_NOK SVf_IOK);
	use namespace::clean;
	
	has 'base_iri' => (is => 'rw', required => 0, default => sub { IRI->new('http://example.org/') });
	has 'processing_mode' => (is => 'ro', default => 'json-ld-1.1');
	has 'max_remote_contexts' => (is => 'rw', default => 10);
	has 'parsed_remote_contexts' => (is => 'rw', default => sub { +{} });
	has 'rdf_direction' => (is => 'rw');
	has 'identifier_map' => (is => 'rw', default => sub { +{} });
	has 'next_identifier_id' => (is => 'rw', default => 0);
	has 'default_language' => (is => 'rw');
	has 'default_base_direction' => (is => 'rw');
	
	our $debug		= 0;
	my %keywords	= map { $_ => 1 } qw(: @base @container @context @direction @graph @id @import @included @index @json @language @list @nest @none @prefix @propagate @protected @reverse @set @type @value @version @vocab);
	
=item C<< compact( $data, [$context] ) >>

Returns the JSON-LD compaction of C<< $data >>, using the optionally supplied
C<< $context >>.

NOTE: Support for JSON-LD Compaction is not fully supported in this version.

=cut

	sub compact {
		my $self	= shift;
		my $d		= shift;
		my $context	= shift // {};
		my %args	= @_;
		
		my $expanded_input	= do {
			local($debug)	= 0;
			$self->expand($d, ordered => 0);
		};
		println(Data::Dumper->Dump([$context, $expanded_input], [qw(context expanded_input)])) if $debug;
		
		my $ctx		= {
			'@base' => $self->base_iri->abs, # TODO: not sure this follows the spec, but it's what makes test t0089 pass
		};
		if (ref($context) eq 'HASH' and exists $context->{'@context'}) {
			local($debug)	= 0;
			$ctx	= $self->_4_1_2_ctx_processing($ctx, $context->{'@context'});
		}
		
		my $inverseCtx	= $self->_4_3_inverse_context_creation($ctx);
		
# 		warn "Compacting...";
		my $c	= $self->_compact($ctx, $inverseCtx, undef, $expanded_input, %args);
		if (scalar(%{ $context->{'@context'} || {} })) {
			$c->{'@context'}	= $context->{'@context'};
		}
		return $c;
	}
	
=item C<< expand( $data, [expandContext => $ctx] ) >>

Returns the JSON-LD expansion of C<< $data >>.

If an C<< expandContext >> value is supplied, it is used to construct the
initial active context for the expansion process.

=cut

	sub expand {
		my $self	= shift;
		my $d		= shift;
		my %args	= @_;
		
		my $ctx		= {
			'@base' => $self->base_iri->abs, # TODO: not sure this follows the spec, but it's what makes test t0089 pass
		};
		if (my $ec = $args{expandContext}) {
			if (ref($ec) eq 'HASH' and exists $ec->{'@context'}) {
				$ec	= $ec->{'@context'};
			}
			$ctx	= $self->_4_1_2_ctx_processing($ctx, $ec);
		}
# 		warn "Expanding...";
		return $self->_expand($ctx, undef, $d);
	}
	
=item C<< to_rdf( $data, [expandContext => $ctx] ) >>

Returns the dataset generated by turning the JSON-LD expansion of C<< $data >>
into RDF.

If an C<< expandContext >> value is supplied, it is passed to the C<< expand >>
function call that takes place internally prior to generating RDF.

Note: this method must be called on a C<< JSONLD >> subclass which implements
the RDF-related methods:

=over 4

=item * C<< default_graph() >>
	
=item * C<< new_dataset() >>
	
=item * C<< new_triple($s, $p, $o) >>
	
=item * C<< new_quad($s, $p, $o, $g) >>
	
=item * C<< new_iri($value) >>

=item * C<< new_graphname($value) >>
	
=item * C<< new_blank( [$id] ) >>
	
=item * C<< new_lang_literal($value, $lang) >>
	
=item * C<< new_dt_literal($value, $datatype) >>

=item * C<< add_quad($quad, $dataset) >>

=back

See L<AtteanX::Parser::JSONLD> for an API that provides this functionality.

=cut

	sub to_rdf {
		my $self	= shift;
		my $obj		= shift;
		my $expandedInput	= do {
			local($debug)	= 0;
			$self->expand($obj, @_);
		};
		println "to rdf " . Data::Dumper->Dump([$expandedInput], ['expandedInput']) if $debug;
		my $dataset	= $self->new_dataset;
		my $map		= {};
		$self->identifier_map({});
		$self->next_identifier_id(0);
		$self->_7_2_2_nodemap_generation($expandedInput, $map);
		println(Data::Dumper->Dump([$map], ['node_map'])) if $debug;
		$self->_8_1_2_to_rdf($map, $dataset);
		return $dataset;
	}

	sub _compact {
		my $self		= shift;
		my $activeCtx	= shift;
		my $inverseCtx	= shift;
		my $activeProp	= shift;
		my $element		= shift;
		my $result		= $self->_6_1_2_compaction($activeCtx, $inverseCtx, $activeProp, $element, @_);

		if (ref($result) eq 'ARRAY') {
			# https://github.com/w3c/json-ld-api/issues/318
			if (scalar(@$result) == 0) {
				$result	= {};
			} else {
				my $key	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, '@graph');
				$result	= { $key => $result };
			}
		}
		
		return $result;
	}

	sub _expand {
		my $self	= shift;
		my $ctx		= shift // {};
		my $prop	= shift;
		my $d		= shift;
		my $expanded_output	= $self->_5_1_2_expansion($ctx, $prop, $d, @_);
		if (ref($expanded_output) eq 'HASH') {
			my @keys	= keys %$expanded_output;
			if (scalar(@keys) == 1 and $keys[0] eq '@graph') {
				println "_expand 7.1" if $debug;
				$expanded_output	= $expanded_output->{'@graph'};
			}
		}
		
		unless (defined($expanded_output)) {
			println "_expand 7.2" if $debug;
			$expanded_output	= [];
		}
		
		if (ref($expanded_output) ne 'ARRAY') {
			println "_expand 7.3" if $debug;
			$expanded_output	= [$expanded_output];
		}
		
		return $expanded_output;
	}
	
	sub _values_from_scalar_or_array {
		my $value	= shift;
		if (ref($value) eq 'ARRAY') {
			return @$value;
		} else {
			return $value;
		}
	}
	
	sub _is_prefix_of {
		my $v	= shift;
		my $big	= shift;
		return 0 unless defined($v);
		return 0 unless defined($big);
		return 0 if (length($v) > length($big));
		return (substr($big, 0, length($v)) eq $v);
	}

	sub _is_scalar {
		my $v	= shift;
		return 1 unless (ref($v));
		return 1 if JSON::is_bool($v);
		return 0;
	}
	
	sub _is_string {
		my $v	= shift;
		return 0 unless defined($v);
		return 0 if ref($v);
		my $sv	= svref_2object(\$v);
		my $flags	= $sv->FLAGS;
		my $is_string	= $flags & SVf_POK;
		return $is_string;
	}
	
	sub _is_integer {
		my $v	= shift;
		return 0 unless (_is_numeric($v));
		my $i	= int($v);
		my $f	= $v - $i;
		return ($f == 0);
	}

	sub _is_numeric {
		my $v	= shift;
		return 0 unless defined($v);
		return 0 if ref($v);
		my $sv	= svref_2object(\$v);
		my $flags	= $sv->FLAGS;
		my $is_num	= (($flags & SVf_NOK) or ($flags & SVf_IOK));
		return $is_num;
	}
	
	sub _is_abs_iri {
		my $self	= shift;
		my $value	= shift;
		return 0 unless (length($value));
		my $i		= eval { IRI->new($value) };
		unless ($i) {
			return 0;
		}
		my $is_abs = (defined($i->scheme) and $value eq $i->abs);
		return $is_abs;
	}
	
	sub _is_iri {
		my $self	= shift;
		my $value	= shift;
		my $i		= eval { IRI->new(value => $value) };
		my $err		= $@;
		my $is_iri	= not($@);
		return $is_iri;
	}

	sub _make_relative_iri {
		my $base	= shift;
		my $rel		= shift;
		println "Make relative IRI from: " . Data::Dumper->Dump([$base->abs, $rel->abs], [qw(base rel)]) if $debug;
		if (($base->scheme // '') ne ($rel->scheme // '')) {
			return $rel->abs;
		}
		
		my @afields	= qw(host port user);
		while (scalar(@afields)) {
			my $f	= $afields[0];
			no warnings 'uninitialized';
			if ($base->$f() ne $rel->$f()) {
				last;
			}
			shift(@afields);
		}
		
		if (scalar(@afields)) {
			my $i	= IRI->new($rel->abs);
			$i->scheme('');
			return $i->_abs; # bug in IRI prevents ->abs from working after changing components
		}
		
		my $i	= IRI->new();
		if ($rel->path eq $base->path) {
			if ($rel->query eq $base->query) {
				if ($rel->fragment eq $base->fragment) {
					# <>
				} else {
					$i->fragment($rel->fragment);
				}
			} else {
				if (defined($rel->query)) {
					$i->query($rel->query);
				}
				if (defined($rel->fragment)) {
					$i->fragment($rel->fragment);
				}
			}
		} else {
			# TODO: determine correct relative path
			$i->path($rel->path);
			if (defined($rel->query)) {
				$i->query($rel->query);
			}
			if (defined($rel->fragment)) {
				$i->fragment($rel->fragment);
			}
		}
		return $i->_abs;
	}
	
	sub _load_document {
		my $self	= shift;
		my $url		= shift;
		my $profile	= shift;
		my $req_profile	= shift;
		my $ua		= LWP::UserAgent->new();
		my $resp	= $ua->get($url);
		return $resp;
	}
	
	sub _cm_contains {
		my $self	= shift;
		my $container_mapping	= shift;
		my $value	= shift;
		if (ref($container_mapping)) {
			Carp::cluck unless (ref($container_mapping) eq 'ARRAY');
			foreach my $m (@$container_mapping) {
				return 1 if ($m eq $value);
			}
		} else {
			return (defined($container_mapping) and $container_mapping eq $value);
		}
	}

	sub _cm_contains_any {
		my $self	= shift;
		my $container_mapping	= shift;
		my @values	= @_;
		foreach my $value (@values) {
			return 1 if ($self->_cm_contains($container_mapping, $value));
		}
		return 0;
	}
	
	sub _ctx_term_defn {
		my $self	= shift;
		my $ctx		= shift;
		my $term	= shift;
		confess "Bad context type in _ctx_term_defn: " . ref($ctx) unless (ref($ctx) eq 'HASH');
		no warnings 'uninitialized';
		return $ctx->{'terms'}{$term};
	}

	sub _ctx_contains_protected_terms {
		my $self	= shift;
		my $ctx		= shift;
		my @prot	= $self->_ctx_protected_terms($ctx);
		return scalar(@prot);
	}
	
	sub _ctx_protected_terms {
		my $self	= shift;
		my $ctx		= shift;
		my @protected;
		foreach my $term (keys %{ $ctx->{'terms'} }) {
			push(@protected, $term) if $ctx->{'terms'}{$term}{'protected'};
		}
		return @protected;
	}
	
	sub _is_node_object {
		my $self	= shift;
		my $value	= shift;
		return 0 unless (ref($value) eq 'HASH');
		foreach my $p (qw(@value @list @set)) {
			return 0 if (exists $value->{$p});
		}
		# TODO: check that value "is not the top-most map in the JSON-LD document consisting of no other entries than @graph and @context."
		return 1;
	}
	
	sub _is_value_object {
		my $self	= shift;
		my $value	= shift;
		return 0 unless (ref($value) eq 'HASH');
		return (exists $value->{'@value'});
	}

	sub _is_default_object {
		my $self	= shift;
		my $value	= shift;
		return 0 unless (ref($value) eq 'HASH');
		return (exists $value->{'@default'});
	}

	sub _is_list_object {
		my $self	= shift;
		my $value	= shift;
		return 0 unless (ref($value) eq 'HASH');
		return (exists $value->{'@list'});
	}
	
	sub _is_graph_object {
		my $self	= shift;
		my $value	= shift;
		return 0 unless (ref($value) eq 'HASH');
		return (exists $value->{'@graph'});
	}
	
	sub _is_simple_graph_object {
		my $self	= shift;
		my $value	= shift;
		return 0 unless ($self->_is_graph_object($value));
		return (not exists $value->{'@id'});
	}
	
	sub _add_value {
		my $self	= shift;
		my $object	= shift;
		my $key		= shift;
		my $value	= shift;
		unless (defined($value)) {
			Carp::cluck "undefined value in add_value";
		}
		my %args	= @_;
		my $as_array	= $args{'as_array'} // 0;
		if ($as_array and ref($value) ne 'ARRAY') {
			$value	= [$value];
		}
		
		if (not exists $object->{$key}) {
			$object->{$key}	= $value;
		} else {
			if (ref($object->{$key}) ne 'ARRAY') {
				$object->{$key}	= [$object->{$key}];
			}
			
			if (ref($value) eq 'ARRAY') {
				push(@{ $object->{$key} }, @$value);
			} else {
				push(@{ $object->{$key} }, $value);
			}
		}
	}
	
	sub _is_well_formed_graph_node {
		my $self	= shift;
		my $value	= shift;
		return 1 if $self->_is_well_formed_iri($value);
		return 1 if ($value =~ /^_:/);
		return 0;
	}
	
	sub _is_well_formed_language {
		my $self	= shift;
		my $value	= shift;
		my $ok	= ($value =~ m/^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/);
		if (not $ok) {
			println "not a well-formed language: $value\n" if $debug;
		}
		return $ok;
	}
	
	sub _is_well_formed_datatype {
		my $self	= shift;
		my $value	= shift;
		return 1 if ($value eq '@json');
		return 1 if $self->_is_well_formed_iri($value);
		return 0;
	}
	
	sub _is_well_formed_iri {
		my $self	= shift;
		my $value	= shift;
		my $ok	= ($self->_is_abs_iri($value));
		if (not $ok) {
			println "not a well-formed IRI: $value\n" if $debug;
		}
		return $ok;
	}
	
	sub _is_well_formed_graphname {
		my $self	= shift;
		my $value	= shift;
		my $ok	= ($self->_is_abs_iri($value) or ($value eq '@default') or ($value =~ /^_:(\w+)$/));
		if (not $ok) {
			println "not a well-formed graph name: $value\n" if $debug;
		}
		return $ok;
	}
	
	sub _is_well_formed {
		my $self	= shift;
		my $value	= shift;
		println "TODO: _is_well_formed: $value" if $debug;
		return 1;
	}

	sub _4_1_2_ctx_processing {
		println "ENTER    =================> _4_1_2_ctx_processing" if $debug;
		my $__indent	= indent();
		my $self		= shift;
		my $activeCtx	= shift;
		my $localCtx	= shift;
		local($Data::Dumper::Indent)	= 0;
		println(Data::Dumper->Dump([$activeCtx], ['*activeCtx'])) if $debug;
		println(Data::Dumper->Dump([$localCtx], ['*localCtx'])) if $debug;
		my %args		= @_;
		my $propagate	= $args{propagate} // 1;
		my $remote_contexts	= $args{remote_contexts} // [];
		my $override_protected	= $args{override_protected} // 0;
		my $base_iri	= $args{base_iri} // $self->base_iri->abs;

		println "1" if $debug;
		my $result	= clone($activeCtx); # 1
		confess "Bad active context type in _4_1_2_ctx_processing: " . Dumper($activeCtx) unless (ref($activeCtx) eq 'HASH');
		if (ref($localCtx) eq 'HASH' and exists $localCtx->{'@propagate'}) {
			println "2" if $debug;
			$propagate	= (!!$localCtx->{'@propagate'}); # 2
		}
		
		if (not($propagate) and not exists $result->{'previous_context'}) {
			println "3" if $debug;
			$result->{'previous_context'}	= $activeCtx; # 3
		}
		
		if (ref($localCtx) ne 'ARRAY') {
			println "4" if $debug;
			$localCtx = [$localCtx]; # 4
		}
		
		println "5" if $debug;
		foreach my $context (@$localCtx) {
			my $__indent	= indent();
			println '-----------------------------------------------------------------' if $debug;
			println "5 loop for each local context item" if $debug;
			println(Data::Dumper->Dump([$context], ['*context'])) if $debug;
			if (not(defined($context))) {
				# 5.1
				println "5.1" if $debug;
				if (not($override_protected) and $self->_ctx_contains_protected_terms($activeCtx)) {
					my @prot	= $self->_ctx_protected_terms($activeCtx);
					println "5.1.1 " . Data::Dumper->Dump([\@prot], ['protected_terms']) if $debug;
					die 'invalid context nullification'; # 5.1.1
				} else {
					println "5.1.2 moving to next context" if $debug;
					my $prev	= $result;
					$result	= {
						'@base' => $self->base_iri->abs, # TODO: not sure this follows the spec, but it's what makes test t0089 pass
					};
					if ($propagate) {
						$result->{'previous_context'}	= $prev;
					}
					next;
				}
			}

			if (not(ref($context))) {
				println "5.2 $context" if $debug;
				
				println "5.2.1" if $debug;
				$context	= IRI->new(value => $context, base => $base_iri)->abs;
				
				if (scalar(@$remote_contexts) > $self->max_remote_contexts) {
					println "5.2.2" if $debug;
					die 'context overflow';
				}
				
				my $context_url		= $context;
				if (my $c = $self->parsed_remote_contexts->{$context}) {
					println "5.2.3" if $debug;
					$context	= $c;
				} else {
					println "5.2.4 loading context from: $context_url" if $debug;
					my $resp	= $self->_load_document($context_url, 'http://www.w3.org/ns/json-ld#context', 'http://www.w3.org/ns/json-ld#context');
					if (not $resp->is_success) {
						println "5.2.5 " . $resp->status_line if $debug;
						die 'loading remote context failed';
					}
					my $content	= $resp->decoded_content;
					$context		= eval { decode_json(encode('UTF-8', $content))->{'@context'} };
					if ($@) {
						println "5.2.5 $@" if $debug;
						die 'loading remote context failed';
					}
					$self->parsed_remote_contexts->{$context_url}	= $context;
				}

				println "5.2.6" if $debug;
				$result	= $self->_4_1_2_ctx_processing($result, $context, remote_contexts => clone($remote_contexts), base_iri => $context_url);

				println "5.2.7 moving to next context" if $debug;
				next;
			}

			if (ref($context) ne 'HASH') {
				println "5.3" if $debug;
				die "invalid_local_context"; # 5.3
			}
			
			println "5.4" if $debug; # no-op

			if (exists $context->{'@version'}) {
				println "5.5" if $debug;
				my $v	= $context->{'@version'};
				if ($v ne '1.1') {
					println "5.5.1" if $debug;
					die 'invalid @version value'; # 5.5.1
				}
				if ($self->processing_mode eq 'json-ld-1.0') {
					println "5.5.2" if $debug;
					die 'processing mode conflict';
				}
			}

			if (exists $context->{'@import'}) {
				println "5.6" if $debug;
				if ($self->processing_mode eq 'json-ld-1.0') {
					println "5.6.1" if $debug;
					die 'invalid context entry';
				}
				
				my $value	= $context->{'@import'};
				if (ref($value)) {
					println "5.6.2" if $debug;
					die 'invalid @import value';
				}
				
				println "5.6.3 resolving \@import $value" if $debug;
				my $import	= IRI->new(value => $value, base => $self->base_iri)->abs;
				
				println "5.6.4 loading $import" if $debug;
				my $resp	= $self->_load_document($import, 'http://www.w3.org/ns/json-ld#context', 'http://www.w3.org/ns/json-ld#context');
				
				if (not $resp->is_success) {
					println "5.6.5" if $debug;
					die 'loading remote context failed';
				}
				
				my $content	= $resp->decoded_content;
				my $j		= eval { decode_json(encode('UTF-8', $content))->{'@context'} };
				if ($@) {
					println "5.6.5 $@" if $debug;
					die 'loading remote context failed';
				}
				
				unless (ref($j) eq 'HASH') {
					println "5.6.6" if $debug;
					die 'invalid remote context';
				}
				my $import_context	= $j;
				
				if (exists $import_context->{'@import'}) {
					println "5.6.7" if $debug;
					die 'invalid context entry';
				}
				
				println "5.6.8" if $debug;
				%$context	= (%$import_context, %$context);
				println(Data::Dumper->Dump([$context], ['context'])) if $debug;
			}
			
			if (exists $context->{'@base'} and scalar(@$remote_contexts) == 0) {
				println "5.7" if $debug;
				println "5.7.1" if $debug;
				my $value	= $context->{'@base'};
				println(Data::Dumper->Dump([$result], ['result'])) if $debug;
				
				if (not defined($value)) {
					println "5.7.2" if $debug;
					delete $result->{'@base'};
				} elsif ($self->_is_abs_iri($value)) {
					println "5.7.3 " . Data::Dumper->Dump([$value], ['base']) if $debug;
					$result->{'@base'}	= $value;
				} elsif ($self->_is_iri($value) and defined($result->{'@base'})) {
					println "5.7.4" if $debug;
					my $base	= IRI->new($result->{'@base'});
					my $i	= IRI->new(value => $value, base => $base);
					$result->{'@base'}	= $i->abs;
				} else {
					println "5.7.5" if $debug;
					die 'invalid base IRI';
				}
			}
			
			if (exists $context->{'@vocab'}) {
				println "5.8" if $debug;
				println "5.8.1" if $debug;
				my $value	= $context->{'@vocab'}; # 5.8.1
				if (not defined($value)) {
					println "5.8.2" if $debug;
					delete $result->{'@vocab'}; # 5.8.2
				} elsif ($value =~ /^_/ or $self->_is_iri($value)) {
					println "5.8.3" if $debug;
					my $iri	= $self->_5_2_2_iri_expansion($result, $value, vocab => 1, documentRelative => 1);
					$result->{'@vocab'}	= $iri;
				}
			}
			
			if (exists $context->{'@language'}) {
				println "5.9" if $debug;
				println "5.9.1 language = " . $context->{'@language'} if $debug;
				my $value	= $context->{'@language'};
				
				if (not defined($value)) {
					println "5.9.2" if $debug;
					delete $result->{'@language'};
				} elsif (_is_string($value)) {
					println "5.9.3 value is a string" if $debug;
					$result->{'@language'}	= $value;
					# TODO: validate language tag against BCP47
				} else {
					println "5.9.3 value is NOT a string" if $debug;
					die 'invalid default language';
				}
			}
			
			if (exists $context->{'@direction'}) {
				println "5.10" if $debug;
				if ($self->processing_mode eq 'json-ld-1.0') {
					println "5.10.1" if $debug;
					die 'invalid context entry';
				}
				
				println "5.10.2" if $debug;
				my $value	= $context->{'@direction'};
				
				if (not defined($value)) {
					println "5.10.3" if $debug;
					delete $result->{'@direction'};
				} elsif (_is_string($value)) {
					println "5.10.4 \@direction = $value" if $debug;
					if ($value ne 'ltr' and $value ne 'rtl') {
						die 'invalid base direction';
					}
					$result->{'@direction'}	= $value;
				}
			}
			
			if (exists $context->{'@propagate'}) {
				println "5.11" if $debug;
				if ($self->processing_mode eq 'json-ld-1.0') {
					println "5.11.1" if $debug;
					die 'invalid context entry';
				}
				
				my $p	= $context->{'@propagate'};
				if ($p ne '1' and $p ne '0' and ref($p) ne 'JSON::PP::Boolean') { # boolean true or false
					println "5.11.2" if $debug;
					die 'invalid @propagate value';
				}
				
				println "5.11.3" if $debug;
			}
			
			println "5.12" if $debug;
			my $defined	= {}; # 5.12
			
			my @keys	= reverse sort grep { $_ !~ /^[@](base|direction|import|language|propagate|protected|version|vocab)$/ } keys %$context;
			println "5.13" if $debug;
			foreach my $key (@keys) {
				my $__indent	= indent();
				println "5.13 [$key]" if $debug;
				my $value	= $context->{$key};
				$self->_4_2_2_create_term_definition($result, $context, $key, $defined, protected => $context->{'@protected'}, propagate => $propagate, base_iri => $base_iri); # 5.13
			}
		}

		local($Data::Dumper::Indent)	= 1;
		println "6 returning from _4_1_2_ctx_processing with " . Data::Dumper->Dump([$result], ['final_context']) if $debug;
		return $result; # 6
	}
		
	sub _4_2_2_create_term_definition {
		my $self	= shift;
		my $activeCtx	= shift;
		my $localCtx	= shift;
		my $term		= shift;
		my $defined		= shift // {};
		println "ENTER    =================> _4_2_2_create_term_definition('$term')" if $debug;
		my $__indent	= indent();
		local($Data::Dumper::Indent)	= 0;
		println(Data::Dumper->Dump([$activeCtx], ['*activeCtx'])) if $debug;
		println(Data::Dumper->Dump([$localCtx], ['*localCtx'])) if $debug;
		println(Data::Dumper->Dump([$defined], ['*defined'])) if $debug;
		my %args		= @_;
		my $protected	= $args{protected} // 0;
		my $override_protected	= $args{override_protected} // 0;
		my $propagate	= $args{propagate} // 1;
		my $base_iri	= $args{base_iri} // $self->base_iri->abs;
		
		# 4.2.2
		if (exists ($defined->{$term})) {
			# 1
			println "1" if $debug;
			if ($defined->{$term}) {
				println "returning from _4_2_2_create_term_definition: term definition has already been created\n" if $debug;
				return;
			} elsif (exists $defined->{$term}) {
				die "cyclic IRI mapping";
			}
		}
		
		println "2 [setting defined{$term} = 0]" if $debug;
		$defined->{$term}	= 0; # 2

		println "3" if $debug;
		my $value	= clone($localCtx->{$term}); # 3
		println "3 " . Data::Dumper->Dump([$value], ['value']) if $debug;
		
		# NOTE: the language interaction between 4 and 5 here is a mess. Unclear what "Otherwise" applies to. Similarly with the "Otherwise" that begins 7 below.
		if ($self->processing_mode eq 'json-ld-1.1' and $term eq '@type') {
			# 4
			println "4" if $debug;
			unless (ref($value) eq 'HASH') {
				die 'keyword redefinition';
			}
			my @keys	= grep { $_ ne '@protected' } keys %$value;
			die 'keyword redefinition' unless (scalar(@keys) == 1 and $keys[0] eq '@container');
			die 'keyword redefinition' unless ($value->{'@container'} eq '@set');
		} else {
			# 5
			println "5" if $debug;
			if (exists $keywords{$term}) {
				println "5 keyword redefinition: $term" if $debug;
				die 'keyword redefinition';
			}
			if ($term =~ /^@[A-Za-z]+$/) {
				# https://www.w3.org/2018/json-ld-wg/Meetings/Minutes/2019/2019-09-20-json-ld#section5-2
				warn "create term definition attempted on a term that looks like a keyword: $term\n";
				println "5 returning so as to ignore a term that has the form of a keyword: $term" if $debug;
				return;
			}
		}
		
		println "6" if $debug;
		my $previous_defn	= $self->_ctx_term_defn($activeCtx, $term); # 6
		delete $activeCtx->{'terms'}{$term}; # https://github.com/w3c/json-ld-api/issues/176#issuecomment-545167708

		my $simple_term;
		if (not(defined($value))) {
			println "7" if $debug;
			$value	= {'@id' => undef}; # 7
		} elsif (_is_string($value)) {
			# 8
			println "8 value = {'\@id' => $value}" if $debug;
			$value	= {'@id' => $value};
			$simple_term	= 1;
		} elsif (ref($value) eq 'HASH') {
			println "9" if $debug;
			$simple_term	= 0; # 9
		} else {
			println "9" if $debug;
			die "invalid_term_definition"; # 9
		}
		
		println "10" if $debug;
		my $definition	= {'__source_base_iri' => $base_iri};	# 10
		
		if ($value->{'@protected'}) {
			println "11" if $debug;
			$definition->{'protected'}	= 1; # 11
			println "11 TODO processing mode of json-ld-1.0" if $debug;
		} elsif (not exists $value->{'@protected'} and $protected) {
			println "12" if $debug;
			$definition->{'protected'}	= 1; # 12
		}

		if (exists $value->{'@type'}) {
			# 13
			println "13" if $debug;
			my $type	= $value->{'@type'}; # 13.1
			if (ref($type)) {
				println "13.1" if $debug;
				die "invalid_type_mapping"; # 13.1
			}
			
			println "13.2" if $debug;
			$type	= $self->_5_2_2_iri_expansion($activeCtx, $type, vocab => 1, localCtx => $localCtx, 'defined' => $defined); # 13.2
			println(Data::Dumper->Dump([$type], ['type'])) if $debug;

			if (($type eq '@json' or $type eq '@none') and $self->processing_mode eq 'json-ld-1.0') {
				# https://github.com/w3c/json-ld-api/issues/259
				println "13.3 " . Data::Dumper->Dump([$type], ['*type']) if $debug;
				die 'invalid type mapping';
			}
			
			if ($type ne '@id' and $type ne '@vocab' and $type ne '@none' and $type ne '@json' and not($self->_is_abs_iri($type))) {
				# TODO: handle case "nor, if processing mode is json-ld-1.1, @json nor @none"
				println "13.4 " . Data::Dumper->Dump([$type], ['*type']) if $debug;
				die 'invalid type mapping'; # 13.4
			}
			
			println "13.5" if $debug;
			$definition->{'type_mapping'}	= $type; # 13.5
		}
		
		if (exists $value->{'@reverse'}) {
			# 14
			println "14" if $debug;
			if (exists $value->{'@id'} or exists $value->{'@nest'}) {
				println "14.1" if $debug;
				die 'invalid reverse property'; # 14.1
			}
			my $reverse	= $value->{'@reverse'};
			if (ref($reverse)) {
				println "14.2" if $debug;
				die 'invalid IRI mapping'; # 14.2
			}
			if ($reverse =~ /^@[A-Za-z]+$/) {
				println "14.3" if $debug;
				die '@reverse value looks like a keyword: ' . $reverse; # 14.3
			} else {
				 # 14.4
				println "14.4" if $debug;
				my $m	= $self->_5_2_2_iri_expansion($activeCtx, $reverse, vocab => 1, localCtx => $localCtx, 'defined' => $defined);
				if (not($self->_is_abs_iri($m)) and $m !~ /^:/) {
					die 'invalid IRI mapping';
				}
				$definition->{'iri_mapping'}	= $m;
			}
			
			if (exists $value->{'@container'}) {
				# 14.5
				println "14.5" if $debug;
				my $c	= $value->{'@container'};
				if ($c ne '@set' and $c ne '@index' and not(defined($c))) {
					die 'invalid reverse property';
				}
				$definition->{'container_mapping'}	= [$c];
			}
			
			println "14.6" if $debug;
			$definition->{'reverse'}	= 1; # 14.6
			
			# 14.7
			println "14.7 [setting defined{$term} = 1]" if $debug;
			$activeCtx->{'terms'}{$term}	= $definition;
			$defined->{$term}	= 1;
			local($Data::Dumper::Indent)	= 0;
			println "returning from _4_2_2_create_term_definition: " . Dumper($activeCtx->{'terms'}{$term}) if $debug;
			return;
		}

		println "15" if $debug;
		$definition->{'reverse'}	= 0; # 15
		
		if (exists $value->{'@id'} and (not(defined($value->{'@id'})) or $value->{'@id'} ne $term)) {
			# 16
			println "16" if $debug;
			if (not defined($value->{'@id'})) {
				println "16.1" if $debug;
			} else {
				println "16.2" if $debug;
				my $id	= $value->{'@id'};
					if (not _is_string($id)) {
						println "16.2.1" if $debug;
						die 'invalid IRI mapping'; # 16.2
					}
			
					if (defined($id) and not exists $keywords{$id} and $id =~ /^@[A-Za-z]+$/) {
						println "16.2.2" if $debug;
						warn "create term definition encountered an \@id that looks like a keyword: $id\n";
						return;
					} else {
						println "16.2.3" if $debug;
						my $iri	= $self->_5_2_2_iri_expansion($activeCtx, $id, vocab => 1, localCtx => $localCtx, 'defined' => $defined);
						if (not exists $keywords{$iri} and not $self->_is_abs_iri($iri) and $iri !~ /:/) {
							die 'invalid IRI mapping';
						}
						if ($iri eq '@context') {
							die 'invalid keyword alias';
						}
						$definition->{'iri_mapping'}	= $iri;
					}
					if ($term =~ /.:./ or index($term, '/') >= 0) {
						println "16.2.4" if $debug;
						println "16.2.4.1" if $debug;
						$defined->{$term}	= 1;

						my $iri	= $self->_5_2_2_iri_expansion($activeCtx, $term, vocab => 1, localCtx => $localCtx, 'defined' => $defined);
						if ($iri ne $definition->{'iri_mapping'}) {
							println "16.2.4.2" if $debug;
							die 'invalid IRI mapping'; # 16.5 ; NOTE: the text here doesn't discuss what parameters to pass to IRI expansion
						}
					}
			
					if ($term !~ m{[:/]} and $simple_term and $definition->{'iri_mapping'} =~ m{[][:/?#@]$}) {
						println "16.2.5" if $debug;
						$definition->{'prefix_flag'}	= 1;
					}
	# 			}
			}
		} elsif ($term =~ /.:/) {
			# 17
			println "17" if $debug;
			my ($prefix, $suffix)	= split(/:/, $term, 2);
			if (exists $localCtx->{$prefix}) {
				println "17.1" if $debug;
				$self->_4_2_2_create_term_definition($activeCtx, $localCtx, $prefix, $defined); # 17.1
			}
			if (exists $activeCtx->{'terms'}{$prefix}) {
				println "17.2" if $debug;
				$definition->{'iri_mapping'}	= $activeCtx->{'terms'}{$prefix}{'iri_mapping'} . $suffix; # 17.2
			} else {
				println "17.3" if $debug;
				$definition->{'iri_mapping'}	= $term; # 17.3
			}
		} elsif ($term =~ m{/}) {
			println "18" if $debug;
			println "18.1"; # if $debug;
			
			println "18.2"; # if $debug;
			$definition->{'iri_mapping'}	= $self->_5_2_2_iri_expansion($activeCtx, $term);
			unless (_is_iri($definition->{'iri_mapping'})) {
				die 'invalid IRI mapping';
			}
		} elsif ($term eq '@type') {
			println "19" if $debug;
			$definition->{'iri_mapping'}	= '@type'; # 19
		} else {
			# 20 ; NOTE: this section uses a passive voice "the IRI mapping of definition is set to ..." cf. 18 where it's active: "set the IRI mapping of definition to @type"
			if (exists $activeCtx->{'@vocab'}) {
				println "20" if $debug;
				$definition->{'iri_mapping'}	= $activeCtx->{'@vocab'} . $term;
			} else {
				println "20" if $debug;
				die 'invalid IRI mapping';
			}
		}
		
		if (exists $value->{'@container'}) {
			# TODO: 21
			println "21" if $debug;

			println "21.1" if $debug;
			my $container	= $value->{'@container'}; # 21.1

			# 21.1 error checking
			my %acceptable	= map { $_ => 1 } qw(@graph @id @index @language @list @set @type);
			if (exists $acceptable{$container}) {
			} elsif (ref($container) eq 'ARRAY') {
				if (scalar(@$container) == 1) {
					my ($c)	= @$container;
					unless (exists $acceptable{$c}) {
						println "21.1(a)" if $debug;
						die 'invalid container mapping';
					}
				} elsif (any { $_ =~ /^[@](id|index)$/ } @$container) { # any { $_ eq '@graph' } @$container
					
				} elsif (any { $_ eq '@set' } @$container and any { $_ =~ /^[@](index|graph|id|type|language)$/ } @$container) {
					
				} else {
					println "21.1(b)" if $debug;
					die 'invalid container mapping';
				}
			} else {
				println "21.1(c)" if $debug;
				die 'invalid container mapping';
			}
			
			if ($self->processing_mode eq 'json-ld-1.0') {
				if (any { $container eq $_ } qw(@graph @id @type) or ref($container)) {
					println "21.2" if $debug;
					die 'invalid container mapping';
				}
			}
			
			println "21.3" if $debug;
			if (ref($container) eq 'ARRAY') {
				$definition->{'container_mapping'}	= $container; # 21.3
			} else {
				$definition->{'container_mapping'}	= [$container]; # 21.3
			}
			
			if ($container eq '@type') {
				println "21.4" if $debug;
				if (not defined($definition->{'type_mapping'})) {
					println "21.4.1" if $debug;
					$definition->{'type_mapping'}	= '@id';
				}
				
				my $tm	= $definition->{'type_mapping'};
				if ($tm ne '@id' and $tm ne '@vocab') {
					println "21.4.2" if $debug;
					die 'invalid type mapping';
				}
			}
		}

		if (exists $value->{'@index'}) {
			println "22" if $debug;
			my $container_mapping	= $definition->{'container_mapping'};
			if ($self->processing_mode eq 'json-ld-1.0' or not $self->_cm_contains($container_mapping, '@index')) {
				println "22.1" if $debug;
				die 'invalid term definition';
			}

			println "22.2" if $debug;
			my $index	= $value->{'@index'};
			my $expanded	= $self->_5_2_2_iri_expansion($activeCtx, $index);
			unless ($self->_is_iri($expanded)) {
				die 'invalid term definition';
			}
			
			println "22.3" if $debug;
			$definition->{'index_mapping'}	= $index;
		}

		if (exists $value->{'@context'}) {
			println "23" if $debug;
			if ($self->processing_mode eq 'json-ld-1.0') {
				println "23.1" if $debug;
				die 'invalid term definition';
			}

			println "23.2" if $debug;
			my $context	= $value->{'@context'};

			println "23.3" if $debug;
			$self->_4_1_2_ctx_processing($activeCtx, $context, override_protected => 1, base_iri => $base_iri); # discard result
# 			$self->_4_1_2_ctx_processing($activeCtx, $context, override_protected => 1); # discard result
			
			$definition->{'@context'}	= $context;	# Note: not sure about the spec text wording here: "Set the local context of definition to context." What is the "local context" of a definition?
		}

		if (exists $value->{'@language'} and not exists $value->{'@type'}) {
			println "24" if $debug;
			println "24.1" if $debug;
			my $language	= $value->{'@language'};
			if (defined($language) and ref($language)) {
				die 'invalid language mapping';
			}
			# TODO: validate language tag against BCP47

			println "24.2" if $debug;
			# TODO: normalize language tag
			$definition->{'language_mapping'}	= $language;
		}

		if (exists $value->{'@direction'} and not exists $value->{'@type'}) {
			println "25" if $debug;
			my $direction	= $value->{'@direction'};

			println "25.1" if $debug;
			if (not(defined($direction))) {
			} elsif ($direction ne 'ltr' and $direction ne 'rtl') {
				die 'invalid base direction';
			}
			
			$definition->{'direction_mapping'}	= $direction;
		}

		if (exists $value->{'@nest'}) {
			println "26" if $debug;
			if ($self->processing_mode eq 'json-ld-1.0') {
				println "26.1" if $debug;
				die 'invalid term definition';
			}
			
			println "26.2" if $debug;
			my $nv	= $value->{'@nest'};
			if (not(defined($nv)) or ref($nv)) {
				die 'invalid @nest value';
			} elsif (exists $keywords{$nv} and $nv ne '@nest') {
				die 'invalid @nest value';
			}
			$definition->{'nest_value'}	= $nv;
		}

		if (exists $value->{'@prefix'}) {
			println "27" if $debug;
			if ($self->processing_mode eq 'json-ld-1.0' or $term =~ m{[:/]}) {
				println "27.1" if $debug;
				die 'invalid term definition'; # 27.1
			}
			
			println "27.2" if $debug;
			$definition->{'prefix_flag'}	= $value->{'@prefix'};
			# TODO: check if this value is a boolean. if it is NOT, die 'invalid @prefix value';
			
			if ($definition->{'prefix_flag'} and exists $keywords{$definition->{'iri_mapping'}}) {
				println "27.3" if $debug;
				die 'invalid term definition';
			}
		}

		my @keys	= grep { not m/^[@](id|reverse|container|context|language|nest|prefix|type|direction|protected|index)$/ } keys %$value;
		if (scalar(@keys)) {
			println "28 " . Data::Dumper->Dump([\@keys, $value], ['invalid_keys', 'value']) if $debug;
			die 'invalid term definition'; # 28
		}
		
		if (not($override_protected) and $previous_defn->{'protected'}) {
			# 29
			println "29" if $debug;
			my %cmp_a	= map { $_ => $definition->{$_} } grep { $_ ne 'protected' } keys %$definition;
			my %cmp_b	= map { $_ => $previous_defn->{$_} } grep { $_ ne 'protected' } keys %$previous_defn;
			my $j		= JSON->new->canonical(1);
			if ($j->encode(\%cmp_a) ne $j->encode(\%cmp_b)) {
				println "29.1" if $debug;
				die 'protected term redefinition'; # 29.1
			}
			println "29.2" if $debug;
			$definition	= $previous_defn; # 29.2
		}
		
		println "30 [setting defined{$term} = 1]" if $debug;
		println "30 setting term definition " . Data::Dumper->Dump([$definition], [$term]) if $debug;
		$activeCtx->{'terms'}{$term}	= $definition; # 30
		$defined->{$term}	= 1; # 30
		local($Data::Dumper::Indent)	= 0;
		println "returning from _4_2_2_create_term_definition: " . Dumper($activeCtx->{'terms'}{$term}) if $debug;
		return;
	}

	sub _4_3_inverse_context_creation {
		my $self		= shift;
		my $activeCtx	= shift;
		{
			no warnings 'uninitialized';
			println "ENTER    =================> _4_3_inverse_context_creation" if $debug;
		}
		my $__indent	= indent();
		println "1" if $debug;
		my $result	= {};
		
		println "2" if $debug;
		my $default_language	= '@none';
		if (exists $activeCtx->{'@language'}) {
			$default_language	= lc($activeCtx->{'@language'});
		}
		
		println "3" if $debug;
		foreach my $term (sort { length($a) <=> length($b) || $a cmp $b } keys %{ $activeCtx->{'terms'} }) {
			my $__indent	= indent();
			println "3 [$term]" if $debug;
			my $tdef	= $self->_ctx_term_defn($activeCtx, $term);
			if (not defined($tdef)) {
				println "3.1" if $debug;
				next;
			}
			
			println "3.2" if $debug;
			my $container	= '@none';
			my $container_mapping	= $tdef->{'container_mapping'} || [];
			if (scalar(@$container_mapping)) {
				$container	= join('', sort @$container_mapping);
			}
			
			println "3.3" if $debug;
			my $var	= $tdef->{'iri_mapping'};
			
			if (not exists $result->{$var}) {
				println "3.4" if $debug;
				$result->{$var}	= {};
			}
			
			println "3.5" if $debug;
			my $container_map	= $result->{$var};
			
			if (not exists $container_map->{$container}) {
				println "3.6" if $debug;
				$container_map->{$container}	= {
					'@language'	=> {},
					'@type'		=> {},
					'@any'		=> {
						'@none'	=> $term
					}
				};
			}
			
			println "3.7" if $debug;
			my $type_language_map	= $container_map->{$container};
			
			println "3.8" if $debug;
			my $type_map	= $type_language_map->{'@type'};
			
			if ($tdef->{'reverse'}) {
				println "3.9" if $debug;
				if (not exists $type_map->{'@reverse'}) {
					println "3.9.1" if $debug;
					$type_map->{'@reverse'}	= $term;
				}
			} elsif (($tdef->{'type_mapping'} // '') eq '@none') {
				println "3.10" if $debug;
				println "3.10.1" if $debug;
				my $language_map	= $type_language_map->{'@language'};
				
				if (not exists $language_map->{'@any'}) {
					println "3.10.2" if $debug;
					$language_map->{'@any'}	= $term;
				}
				
				if (not exists $type_map->{'@any'}) {
					println "3.10.3" if $debug;
					$type_map->{'@any'}	= $term;
				}
			} elsif (exists $tdef->{'type_mapping'}) {
				println "3.11" if $debug;
				if (not exists $type_map->{$tdef->{'type_mapping'}}) {
					println "3.11.1" if $debug;
					$type_map->{$tdef->{'type_mapping'}}	= $term;
				}
			}
			
			println "3.12" if $debug;
			my $language_map	= $type_language_map->{'@language'};
			
			my $lang_dir; # TODO: the spec says to create this in 3.13.1, but it's used at this level
			if (exists $tdef->{'language_mapping'} and exists $tdef->{'direction_mapping'}) {
				println "3.13" if $debug;
				println "3.13.1" if $debug;
				# my $lang_dir;

				if (defined($tdef->{'language_mapping'}) and defined($tdef->{'direction_mapping'})) {
					println "3.13.2" if $debug;
					$lang_dir	= lc(join('_', @{ $tdef }{qw(language_mapping direction_mapping)}));
				} elsif (defined($tdef->{'language_mapping'})) {
					println "3.13.3" if $debug;
					$lang_dir	= lc($tdef->{'language_mapping'});
				} elsif (defined($tdef->{'direction_mapping'})) {
					println "3.13.4" if $debug;
					$lang_dir	= '_' . lc($tdef->{'direction_mapping'});
				} else {
					println "3.13.5" if $debug;
					$lang_dir	= '@null';
				}
				if (not exists $language_map->{$lang_dir}) {
					println "3.13.6" if $debug;
					$language_map->{$lang_dir}	= $term;
				}
			} elsif (exists $tdef->{'language_mapping'}) {
				println "3.14" if $debug;
				println "3.14.1" if $debug;
				my $language	= (not defined($tdef->{'language_mapping'})) ? '@null' : lc($tdef->{'language_mapping'});

				if (not exists $language_map->{$language}) {
					println "3.14.2" if $debug;
					$language_map->{$language}	= $term;
				}
			} elsif (exists $tdef->{'direction_mapping'}) {
				println "3.15" if $debug;
				println "3.15.1" if $debug;
				my $direction	= (not defined $tdef->{'direction_mapping'}) ? '@none' : '_' . $tdef->{'direction_mapping'};

				if (not exists $language_map->{$direction}) {
					println "3.15.2" if $debug;
					$language_map->{$direction}	= $term;
				}
			} elsif (exists $activeCtx->{'@direction'}) {
				println "3.16" if $debug;
				println "3.16.1" if $debug;
				my $lang_dir	= lc(join('_', $self->default_language, $self->default_base_direction));
				
				if (not exists $language_map->{$lang_dir}) {
					println "3.16.2" if $debug;
					$language_map->{$lang_dir}	= $term;
				}
				
				if (not exists $language_map->{'@none'}) {
					println "3.16.3" if $debug;
					$language_map->{'@none'}	= $term;
				}
				
				if (not exists $type_map->{'@none'}) {
					println "3.16.4" if $debug;
					$type_map->{'@none'}	= $term;
				}
			} else {
				println "3.17" if $debug;
				if (not exists $language_map->{lc $default_language}) {
					println "3.17.1" if $debug;
					$language_map->{lc $default_language}	= $term;
				}
				
				if (not exists $language_map->{'@none'}) {
					println "3.17.2" if $debug;
					$language_map->{'@none'}	= $term;
				}

				if (not exists $type_map->{'@none'}) {
					println "3.17.3" if $debug;
					$type_map->{'@none'}	= $term;
				}
			}
		}

		println "4" if $debug;
		return $result;
	}

	sub _4_4_2_term_selection {
		my $self				= shift;
		my $inverseCtx			= shift;
		my $var					= shift;
		my $containers			= shift;
		my $type_language		= shift;
		my $preferred_values	= shift;
		println "ENTER    =================> _4_4_2_term_selection" if $debug;
		my $__indent	= indent();
		println "1" if $debug;
		my $container_map	= $inverseCtx->{$var};
		
		println "2" if $debug;
		foreach my $container (@$containers) {
			println "2 [$container]" if $debug;
			if (not exists $container_map->{$container}) {
				println "2.1" if $debug;
				next;
			}
			println "2.2" if $debug;
			my $type_language_map	= $container_map->{$container};
			
			println "2.3" if $debug;
			my $value_map	= $type_language_map->{$type_language};
			
			println "2.4" if $debug;
			foreach my $item (@$preferred_values) {
				if (not exists $value_map->{$item}) {
					println "2.4.1" if $debug;
					next;
				}
				
				println "2.4.2" if $debug;
				return $value_map->{$item};
			}
		}

		println "3" if $debug;
		return;
	}

	sub _5_1_2_expansion {
		my $self		= shift;
		my $activeCtx	= shift;
		my $activeProp	= shift;
		my $element		= shift;
		{
			no warnings 'uninitialized';
			println "ENTER    =================> _5_1_2_expansion('$activeProp')" if $debug;
		}
		my $__indent	= indent();
		local($Data::Dumper::Indent)	= 0;
		println(Data::Dumper->Dump([$activeCtx], ['activeCtx'])) if $debug;
		println(Data::Dumper->Dump([$activeProp], ['activeProp'])) if $debug;
		println(Data::Dumper->Dump([$element], ['element'])) if $debug;
		my %args		= @_;
		my $frameExpansion	= $args{frameExpansion} // 0;
		my $ordered		= $args{ordered} // 0;
		my $fromMap		= $args{fromMap} // 0;
		
		unless (defined($element)) {
			println "1 returning from _5_1_2_expansion: undefined element" if $debug;
			return; # 1
		}
		if (defined($activeProp) and $activeProp eq '@default') {
			println "2" if $debug;
			$frameExpansion = 0; # 2
		}
		
		my $property_scoped_ctx;
		my $property_scoped_ctx_defined	= 0;
		my $tdef = $self->_ctx_term_defn($activeCtx, $activeProp);
		if ($tdef and exists $tdef->{'@context'}) {
			$property_scoped_ctx_defined	= 1;
			my $lctx = $tdef->{'@context'};
			$property_scoped_ctx	= $lctx; # 3
			println "3 property-scoped context for property $activeProp: " . Data::Dumper->Dump([$property_scoped_ctx], [qw(context)]) if $debug;
		}
		
		if (_is_scalar($element)) {
			# 4
			println "4" if $debug;
			if (not(defined($activeProp)) or $activeProp eq '@graph') {
				println "4.1 returning from _5_1_2_expansion: free floating scalar" if $debug;
				return; # 4.1
			}
			if ($property_scoped_ctx_defined) {
				println "4.2" if $debug;
				$activeCtx = $self->_4_1_2_ctx_processing($activeCtx, $property_scoped_ctx); # 4.2
				println "after 4.2: " . Data::Dumper->Dump([$activeCtx], ['activeCtx']) if $debug;
			}
			
			println "4.3" if $debug;
			my $v	= $self->_5_3_2_value_expand($activeCtx, $activeProp, $element);
			local($Data::Dumper::Indent)	= 1;
			println "4.3 returning from _5_1_2_expansion with " . Data::Dumper->Dump([$v], ['expandedValue']) if $debug;
			return $v; # 4.3
		}
		
		if (ref($element) eq 'ARRAY') {
			# 5
			println "5" if $debug;
			my @result; # 5.1
			println "5.1" if $debug;
			foreach my $item (@$element) {
				# 5.2
				println "5.2" if $debug;
				println "5.2.1" if $debug;
				my $expandedItem	= $self->_5_1_2_expansion($activeCtx, $activeProp, $item, fromMap => $fromMap); # 5.2.1
				println "5.2.1 expanded item = " . Dumper($expandedItem) if $debug;
				
				# NOTE: 5.2.2 "container mapping" is in the term definition for active property, right? The text omits the term definition reference.
				my $container_mapping	= $tdef->{'container_mapping'};
#				if (any { $_ eq '@list'} @$container_mapping and ref($expandedItem) eq 'ARRAY') {
				if ($self->_cm_contains($container_mapping, '@list')  and ref($expandedItem) eq 'ARRAY') {
					println "5.2.2" if $debug;
					$expandedItem	= { '@list' => $expandedItem }; # 5.2.2
				}
				
				# 5.2.3
				println "5.2.3" if $debug;
				if (ref($expandedItem) eq 'ARRAY') {
					push(@result, @$expandedItem);
				} elsif (defined($expandedItem)) {
					push(@result, $expandedItem);
				}
			}
			
			local($Data::Dumper::Indent)	= 1;
			println "5.3 returning from _5_1_2_expansion with " . Data::Dumper->Dump([\@result], ['expanded_array_value']) if $debug;
			return \@result; # 5.3
		}
		
		unless (ref($element) eq 'HASH') {
			println "6" if $debug;
			die "Unexpected non-map encountered during expansion: $element";
		}

		if (my $prevCtx = $activeCtx->{'previous_context'}) {
			unless ($fromMap) {
				my @keys	= keys %$element;
				my %expandedKeys	= map { $_ => 1 } map { $self->_5_2_2_iri_expansion($activeCtx, $_, vocab => 1) } @keys;
				unless (exists $expandedKeys{'@value'}) {
					unless (scalar(@keys) == 1 and $self->_5_2_2_iri_expansion($activeCtx, $keys[0], vocab => 1) eq '@id') {
						println "7" if $debug;
						$activeCtx	= $prevCtx; # 7
					}
				}
			}
		}
		
		if ($property_scoped_ctx_defined) {
			println "8" if $debug;
			my %args;
			if ($tdef and exists $tdef->{'__source_base_iri'}) {
				$args{base_iri}	= $tdef->{'__source_base_iri'};
			}
			local($Data::Dumper::Indent)	= 1;
			println "before 8: " . Data::Dumper->Dump([$activeCtx, $property_scoped_ctx], [qw'activeCtx property_scoped_ctx']) if $debug;
			$activeCtx = $self->_4_1_2_ctx_processing($activeCtx, $property_scoped_ctx, %args, override_protected => 1); # 8
			println "after 8: " . Data::Dumper->Dump([$activeCtx], ['activeCtx']) if $debug;
		}
		
		if (exists $element->{'@context'}) {
			println "9" if $debug;
			my $c = $element->{'@context'};
			$activeCtx = $self->_4_1_2_ctx_processing($activeCtx, $c); # 9
		}
		
		println "10" if $debug;
		my $type_scoped_ctx	= clone($activeCtx); # 10
		
		println "11" if $debug;
		foreach my $key (sort keys %$element) {
			my $__indent	= indent();
			my $value	= $element->{$key};
			# 11
			println "11 [$key]" if $debug;
			unless ('@type' eq $self->_5_2_2_iri_expansion($activeCtx, $key, vocab => 1)) {
				println "[skipping key $key in search of \@type]" if $debug;
				next;
			}

			println "11 body [$key]" if $debug;
			
			unless (ref($value) eq 'ARRAY') {
				println "11.1 [$key]" if $debug;
				$value	= [$value]; # 11.1
			}
			
			my %tdefs	= map { $_ => $self->_ctx_term_defn($type_scoped_ctx, $_) } grep { _is_string($_) }@$value; # https://github.com/w3c/json-ld-api/issues/304
			foreach my $term (sort @$value) {
				println "11.2 attempting with [$term]" if $debug;
				if (_is_string($term)) {
					my $tdef	= $tdefs{$term};
					if (exists $tdef->{'@context'}) {
						println "11.2" if $debug;
						my $c = $tdef->{'@context'};
						$activeCtx	= $self->_4_1_2_ctx_processing($activeCtx, $c, propagate => 0);
						local($Data::Dumper::Indent)	= 1;
						println "11.2 " . Data::Dumper->Dump([$activeCtx], ['activeCtx']) if $debug;
					}
				}
			}
			
		}
		println "After 11, " . Data::Dumper->Dump([$element], ['element']) if $debug;
		
		println "12" if $debug;
		my $result	= {}; # 12a
		my $nests	= {}; # 12b
		my $input_type	= '';
		foreach my $key (sort keys %$element) {
			my $expandedKey	= $self->_5_2_2_iri_expansion($activeCtx, $key);
			if ($expandedKey eq '@type') {
				$input_type	= $self->_5_2_2_iri_expansion($activeCtx, $element->{$key});
				last;
			}
		}
		println "12 " . Data::Dumper->Dump([$input_type], ['*input_type']) if $debug;
		
		$self->_5_1_2_expansion_step_13($activeCtx, $type_scoped_ctx, $result, $activeProp, $input_type, $nests, $ordered, $frameExpansion, $element);
		println "after 13,14: " . Data::Dumper->Dump([$result], ['*result']) if $debug;

		if (exists $result->{'@value'}) {
			# 15
			println "15" if $debug;
			my @keys	= keys %$result;
			my %acceptable	= map { $_ => 1 } qw(@direction @index @language @type @value);
			foreach my $k (@keys) {
				unless ($acceptable{$k}) {
					println "15.1 [$k]" if $debug;
					die 'invalid value object' ; # 15.1
				}
			}
			if (exists $result->{'@language'} or exists $result->{'@direction'}) {
				println "15.1 \@language handling" if $debug;
				die 'invalid value object' if (exists $result->{'@type'}); # 15.1
			}
			
# 			if (not(defined($result->{'@value'}))) {
			if (defined($result->{'@type'}) and $result->{'@type'} eq '@json') {
				println "15.2" if $debug;
				# TODO: treat $result->{'@value'} as a JSON literal
			} elsif (not(defined($result->{'@value'})) or (ref($result->{'@value'}) eq 'ARRAY' and not scalar(@{$result->{'@value'}}))) { # based on irc conversation with gkellog
				println "15.3" if $debug;
				return undef;
			} elsif (ref($result->{'@value'}) and exists $result->{'@language'}) {
				println "15.4" if $debug;
				die 'invalid language-tagged value; ' . Dumper($result); # 15.4
			} elsif (exists $result->{'@type'} and not($self->_is_iri($result->{'@type'}))) {
				println "Not an IRI \@type: " . Dumper($result->{'@type'});
				println "15.5" if $debug;
				die 'invalid typed value: ' . Dumper($result); # 15.5
# 			} elsif (exists $result->{'@type'}) {
# 				my $types	= $result->{'@type'};
# 				my @types	= (ref($types) eq 'ARRAY') ? @$types : $types;
# 				foreach my $t (@types) {
# 					unless ($self->_is_iri($t)) {
# 						warn "Not an IRI \@type: " . Dumper($result->{'@type'});
# 						println "15.5" if $debug;
# 						die 'invalid typed value: ' . Dumper($result); # 15.5
# 					}
# 				}
			}
			println "15 resulting in " . Data::Dumper->Dump([$result], ['*result']) if $debug;
		} elsif (exists $result->{'@type'} and ref($result->{'@type'}) ne 'ARRAY') {
			println "16" if $debug;
			$result->{'@type'}	= [$result->{'@type'}]; # 16
			println "16 resulting in " . Data::Dumper->Dump([$result], ['*result']) if $debug;
		} elsif (exists $result->{'@set'} or exists $result->{'@list'}) {
			# 17
			println "17" if $debug;
			my @keys	= grep { $_ ne '@set' and $_ ne '@list' } keys %$result;
			if (scalar(@keys)) {
				println "17.1" if $debug;
				die 'invalid set or list object' unless (scalar(@keys) == 1 and $keys[0] eq '@index'); # 17.1
			}
			if (exists $result->{'@set'}) {
				println "17.2" if $debug;
				$result	= $result->{'@set'}; # 17.2
			}
			println "17 resulting in " . Data::Dumper->Dump([$result], ['*result']) if $debug;
		}
		
		println "after 17 resulting in " . Data::Dumper->Dump([$result], ['*result']) if $debug;
		my @keys	= (ref($result) eq 'HASH') ? keys %$result : ();
		if (ref($result) eq 'HASH') { # NOTE: assuming based on the effects of 16.2 that this condition is necessary to guard against cases where $result is not a hashref.
			if (scalar(@keys) == 1 and $keys[0] eq '@language') {
				println "18" if $debug;
				return undef;
			}
			
			println(Data::Dumper->Dump([$activeProp], ['activeProp'])) if $debug;
			if (not(defined($activeProp)) or $activeProp eq '@graph') {
				# 19
				local($Data::Dumper::Indent)	= 0;
				println "19 " . Data::Dumper->Dump([$result, \@keys], ['*result', '*keys']) if $debug;
				if (ref($result) eq 'HASH' and (scalar(@keys) == 0 or exists $result->{'@value'} or exists $result->{'@list'})) {
					println "19.1" if $debug;
					$result	= undef; # 19.1
				} elsif (ref($result) eq 'HASH' and scalar(@keys) == 1 and $keys[0] eq '@id') {
					unless ($frameExpansion) {
						println "19.2" if $debug;
						$result	= undef; # 19.2
					}
				}
			}
			println "19 resulting in " . Data::Dumper->Dump([$result], ['*result']) if $debug;
		}
		
		local($Data::Dumper::Indent)	= 1;
		println "20 returning from _5_1_2_expansion with final value " . Data::Dumper->Dump([$result], ['*result']) if $debug;
		return $result; # 19
	}
	
	sub _5_1_2_expansion_step_13 {
		my $self			= shift;
		my $activeCtx		= shift;
		my $type_scoped_ctx	= shift;
		my $result			= shift;
		my $activeProp		= shift;
		my $input_type		= shift;
		my $nests			= shift;
		my $ordered			= shift;
		my $frameExpansion	= shift;
		my $element			= shift;
		println "13 --- processing " . Data::Dumper->Dump([$element], ['element']) if ($debug);
		foreach my $key (sort keys %$element) {
			my $__indent	= indent();
			my $value	= $element->{$key};
			# 13
			println '-----------------------------------------------------------------' if $debug;
			println "13 [$key] " . Data::Dumper->Dump([$value], ['value']) if $debug;
			if ($key eq '@context') {
				println "13.1 going to next element key" if $debug;
				next; # 13.1
			}
			local($Data::Dumper::Indent)	= 1;
			println(Data::Dumper->Dump([$activeCtx], ['activeCtx'])) if $debug;
			
			println "13.2" if $debug;
			my $expandedProperty	= $self->_5_2_2_iri_expansion($activeCtx, $key, vocab => 1); # 13.2
			println "13.2 " . Data::Dumper->Dump([$expandedProperty], ['expandedProperty']) if $debug;
			println(Data::Dumper->Dump([$expandedProperty], ['expandedProperty'])) if $debug;
			if (not(defined($expandedProperty)) or ($expandedProperty !~ /:/ and not exists $keywords{$expandedProperty})) {
				println "13.3 going to next element key" if $debug;
				next; # 13.3
			}
		
			my $expandedValue;
			if (exists $keywords{$expandedProperty}) {
				# 13.4
				println "13.4 keyword: $expandedProperty" if $debug;
				
				if (defined($activeProp) and $activeProp eq '@reverse') {
					println "13.4.1" if $debug;
					die 'invalid reverse property map'; # 13.4.1
				}

				if (exists $result->{$expandedProperty}) {
					my $p	= $result->{$expandedProperty};
					if ($expandedProperty ne '@included' and $expandedProperty ne '@type') {
						println "13.4.2 colliding: $expandedProperty" if $debug;
						die 'colliding keywords'; # 13.4.2
					}
				}
				
				# NOTE: another case of an "Otherwise" applying to a partial conjunction
				if ($expandedProperty eq '@id') {
					println "13.4.3" if $debug;
					if (ref($value) or not defined($value)) {
						println "13.4.3.1 invalid" if $debug;
						die 'invalid @id value';
					} else {
						println "13.4.3.2" if $debug;
						$expandedValue	= $self->_5_2_2_iri_expansion($activeCtx, $value, documentRelative => 1);
						println "13.4.3.2 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
					}
				}

				if ($expandedProperty eq '@type') {
					println "13.4.4" if $debug;
					my $is_string = _is_string($value);
					my $is_array	= ref($value) eq 'ARRAY';
					my $is_array_of_strings	= ($is_array and all { _is_string($_) } @$value);
					if (not($is_string) and not($is_array_of_strings)) {
						println "13.4.4.1 invalid" if $debug;
						die 'invalid type value';
					}
					
					if (ref($value) eq 'HASH' and scalar(%$value) == 0) {
						println "13.4.4.2" if $debug;
						$expandedValue	= $value;
						println "13.4.4.2 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
					} elsif ($self->_is_default_object($value)) {
						println "13.4.4.3" if $debug;
						$expandedValue	= { '@default' => $self->_5_2_2_iri_expansion($type_scoped_ctx, $value, vocab => 1, documentRelative => 1) };
						println "13.4.4.3 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
					} else {
						println "13.4.4.4" if $debug;
						if (ref($value)) {
							$expandedValue	= [map {$self->_5_2_2_iri_expansion($type_scoped_ctx, $_, vocab => 1, documentRelative => 1)} @$value];
						} else {
							$expandedValue	= $self->_5_2_2_iri_expansion($type_scoped_ctx, $value, vocab => 1, documentRelative => 1);
						}
						println "13.4.4.4 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
					}
					
					if (my $t = $result->{'@type'}) {
						println "13.4.4.5" if $debug;
						if (ref($expandedValue) ne 'ARRAY') {
							$expandedValue	= [$expandedValue];
						}
						unshift(@$expandedValue, $t);
						println "13.4.4.5 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
					}
				}

				if ($expandedProperty eq '@graph') {
					println "13.4.5" if $debug;
					my $v	= $self->_5_1_2_expansion($activeCtx, '@graph', $value, frameExpansion => $frameExpansion, ordered => $ordered);
					$expandedValue	= (ref($v) eq 'ARRAY') ? $v : [$v];
					println("========================================================================") if $debug;
					println("========================================================================") if $debug;
					println("========================================================================") if $debug;
					println(Dumper($expandedValue)) if $debug;
					println "13.4.5 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
				}

				if ($expandedProperty eq '@included') {
					println "13.4.6" if $debug;
					if ($self->processing_mode eq 'json-ld-1.0') {
						println "13.4.6.1" if $debug;
						next;
					}
					
					println "13.4.6.2" if $debug;
					$expandedValue	= $self->_5_1_2_expansion($activeCtx, $activeProp, $value, frameExpansion => $frameExpansion, ordered => $ordered);
					unless (ref($expandedValue) eq 'ARRAY') {
						$expandedValue	= [$expandedValue];
					}
					
					foreach my $v (@$expandedValue) {
						unless ($self->_is_node_object($v)) {
							println "13.4.6.3" if $debug;
							die 'invalid @included value';
						}
					}
					
					if (exists $result->{'@included'}) {
						# https://github.com/w3c/json-ld-api/issues/333
						println "13.4.6.4" if $debug;
						unshift(@$expandedValue, @{ $result->{'@included'} });
					}
					println "13.4.6 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
				} elsif ($expandedProperty eq '@value') {
					println "13.4.7 " . Data::Dumper->Dump([$input_type], ['*input_type']) if $debug;
					if ($input_type eq '@json') {
						println "13.4.7.1" if $debug;
						$expandedValue	= $value; # 13.4.7.1
						if ($self->processing_mode eq 'json-ld-1.0') {
							die 'invalid value object value';
						}
					} elsif (not (_is_scalar($value) or not defined($value))) { # "if value is not a scalar or null, an invalid value object value error has been detected"
						println "13.4.7.2 " .  Data::Dumper->Dump([$value], ['*value']) if $debug; # NOTE: the language here is ambiguous: "if value is not a scalar or null"
						die 'invalid value object value';
					} else {
						println "13.4.7.3" if $debug;
						$expandedValue	= $value;
					}
					
					unless (defined($expandedValue)) {
						println "13.4.7.4" if $debug;
						$result->{'@value'}	= undef;
						next;
					}
					println "13.4.7 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
				}

				# NOTE: again with the "Otherwise" that seems to apply to only half the conjunction
				if ($expandedProperty eq '@language') {
					println "13.4.8" if $debug;
					if (ref($value)) {
						println "13.4.8.1" if $debug;
						if ($frameExpansion) {
							println "13.4.8.1 TODO: frameExpansion support"; # if $debug;
						}
						die 'invalid language-tagged string';
					}
					println "13.4.8.2" if $debug;
					$expandedValue	= $value; # 13.4.8.2
					# TODO: validate language tag against BCP47
					println "13.4.8 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
				}

				if ($expandedProperty eq '@direction') {
					println "13.4.9" if $debug;
					if ($self->processing_mode eq 'json-ld-1.0') {
						println "13.4.9.1" if $debug;
						next;
					}

					if ($value ne 'ltr' and $value ne 'rtl') {
						println "13.4.9.2" if $debug;
						die 'invalid base direction';
					}

					println "13.4.9.3" if $debug;
					$expandedValue	= $value;

					if ($frameExpansion) {
						println "13.4.9.4 TODO: frameExpansion support"; # if $debug;
					}
					println "13.4.9 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
				}

				if ($expandedProperty eq '@index') {
					println "13.4.10" if $debug;
					if (ref($value)) {
						println "13.4.10.1" if $debug;
						die 'invalid @index value';
					}
					
					println "13.4.10.2" if $debug;
					$expandedValue	= $value;
					println "13.4.10 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
				}

				if ($expandedProperty eq '@list') {
					println "13.4.11" if $debug;
					if (not defined($activeProp) or $activeProp eq '@graph') {
						println "13.4.11.1" if $debug;
						next;
					}

					println "13.4.11.2" if $debug;
					$expandedValue	= $self->_5_1_2_expansion($activeCtx, $activeProp, $value, frameExpansion => $frameExpansion, ordered => $ordered);
					if (ref($expandedValue) ne 'ARRAY') {
						# https://github.com/w3c/json-ld-api/issues/310
						$expandedValue	= [$expandedValue];
					}
					println "13.4.11 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
				}

				if ($expandedProperty eq '@set') {
					println "13.4.12" if $debug;
					$expandedValue	= $self->_5_1_2_expansion($activeCtx, $activeProp, $value, frameExpansion => $frameExpansion, ordered => $ordered);
					println "13.4.12 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
				}

				# NOTE: the language here is really confusing. the first conditional in 13.4.13 is the conjunction "expanded property is @reverse and value is not a map".
				#       however, by context it seems that really everything under 13.4.13 assumes expanded property is @reverse, and the first branch is dependent only on 'value is not a map'.
				if ($expandedProperty eq '@reverse') {
					println "13.4.13" if $debug;
					if (ref($value) ne 'HASH') {
						println "13.4.13.1" if $debug;
						die 'invalid @reverse value';
					}

					println "13.4.13.2" if $debug;
					$expandedValue	= $self->_5_1_2_expansion($activeCtx, '@reverse', $value, frameExpansion => $frameExpansion, ordered => $ordered); # 13.4.13.1
					println "13.4.13.2 " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
					
					if (ref($expandedValue) eq 'HASH' and exists $expandedValue->{'@reverse'}) { # NOTE: spec text does not assert that expandedValue is a map
						println "13.4.13.3" if $debug;
						foreach my $property (keys %{ $expandedValue->{'@reverse'} }) {
							my $__indent	= indent();
							println "13.4.13.3 [$property]" if $debug;
							my $item	= $expandedValue->{'@reverse'}{$property};
							if (not exists $result->{$property}) {
								println "13.4.13.3.1" if $debug;
								$result->{$property}	= [];
							}
						
							println "13.4.13.3.2" if $debug;
							push(@{ $result->{$property} }, @$item);
						}
					}
				
					if (ref($expandedValue) eq 'HASH') { # NOTE: spec text does not assert that expandedValue is a map
						my @keys	= grep { $_ ne '@reverse' } keys %$expandedValue;
						if (scalar(@keys)) {
							println "13.4.13.4" if $debug;
					
							if (not exists $result->{'@reverse'}) {
								println "13.4.13.4.1" if $debug;
								$result->{'@reverse'}	= {};
							}
					
							println "13.4.13.4.2" if $debug;
							my $reverse_map	= $result->{'@reverse'};
					
							println "13.4.13.4.3" if $debug;
							foreach my $property (grep { $_ ne '@reverse' } keys %{ $expandedValue }) {
								my $__indent	= indent();
								println "13.4.13.4.3 [$property]" if $debug;
								my $items	= $expandedValue->{$property};
						
								println "13.4.13.4.3.1" if $debug;
								foreach my $item (@$items) {
									my $__indent	= indent();
									if ($self->_is_value_object($item) or $self->_is_list_object($item)) {
										println "13.4.13.4.3.1.1" if $debug;
										die 'invalid reverse property value';
									}
							
									if (not exists $reverse_map->{$property}) {
										println "13.4.13.4.3.1.2" if $debug;
										$reverse_map->{$property}	= [];
									}
							
									println "13.4.13.4.3.1.3" if $debug;
									push(@{ $reverse_map->{$property} }, $item);
								}
							}
						}
					}
					println "13.4.13.5 going to next element key" if $debug;
					next; # 13.4.13.5
				}

				if ($expandedProperty eq '@nest') {
					println "13.4.14 adding '$key' to nests" if $debug;
					$nests->{$key}	//= [];
					next;
				}

				if ($frameExpansion) {
					my %other_framings	= map { $_ => 1 } qw(@explicit @default @embed @explicit @omitDefault @requireAll);
					if ($other_framings{$expandedProperty}) {
						println "13.4.15" if $debug;
						$expandedValue	= $self->_5_1_2_expansion($activeCtx, $activeProp, $value, frameExpansion => $frameExpansion, ordered => $ordered); # 13.4.15
						println "13.4.15 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
					}
				}
				
				println "before 13.4.16 " . Data::Dumper->Dump([$expandedValue, $expandedProperty, $input_type], [qw'expandedValue expandedProperty input_type']) if $debug;
				unless (not(defined($expandedValue)) and $expandedProperty eq '@value' and $input_type ne '@json') {
					println "13.4.16 setting " . Data::Dumper->Dump([$expandedValue], [$expandedProperty]) if $debug;
# 						println "$expandedProperty expanded value is " . Dumper($expandedValue) if $debug;
					$result->{$expandedProperty}	= $expandedValue; # https://github.com/w3c/json-ld-api/issues/270
					println "13.4.16 resulting in " . Data::Dumper->Dump([$result], ['*result']) if $debug;
				}

				println "13.4.17 going to next element key" if $debug;
				next; # 13.4.17
			}


			my $tdef	= $self->_ctx_term_defn($activeCtx, $key);

			println "13.5 initializing container mapping" if $debug;
			my $container_mapping	= $tdef->{'container_mapping'}; # 13.5
			println(Data::Dumper->Dump([$container_mapping, $value], ['*container_mapping', '*value'])) if $debug;

			if (exists($tdef->{'type_mapping'}) and $tdef->{'type_mapping'} eq '@json') {
				println "13.6" if $debug;
				$expandedValue	= { '@value' => $value, '@type' => '@json' }; # 13.6
				println "13.6 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
			} elsif ($self->_cm_contains($container_mapping, '@language') and ref($value) eq 'HASH') {
				println "13.7" if $debug;
				println "13.7.1" if $debug;
				$expandedValue	= [];
				
				println "13.7.2" if $debug;
				my $direction	= $activeCtx->{'@direction'};
				
				if (exists $tdef->{'direction_mapping'}) {
					println "13.7.3" if $debug;
					$direction	= $tdef->{'direction_mapping'};
				}
				
				println "13.7.4" if $debug;
				for my $language (sort keys %$value) {
					my $__indent	= indent();
					my $language_value	= $value->{$language};
					println "13.7.4 [$language]" if $debug;
					
					if (ref($language_value) ne 'ARRAY') {
						println "13.7.4.1" if $debug;
						$language_value	= [$language_value];
					}
					
					println "13.7.4.2" if $debug;
					foreach my $item (@$language_value) {
						my $__indent	= indent();
						unless (defined($item)) {
							println "13.7.4.2.1" if $debug;
							next;
						}
						
						if (ref($item)) {
							println "13.7.4.2.2" if $debug;
							die 'invalid language map value';
						}
						
						println "13.7.4.2.3" if $debug;
						my $v	= {'@value' => $item, '@language' => $language};
						my $well_formed	= 1; # TODO: check BCP47 well-formedness of $item
						if ($item ne '@none' and not($well_formed)) {
							warn "Language tag is not well-formed: $item";
						}
						# TODO: normalize language tag
						
						my $expandedLanguage = $self->_5_2_2_iri_expansion($activeCtx, $language);
						if ($language eq '@none' or $expandedLanguage eq '@none') {
							println "13.7.4.2.4" if $debug;
							delete $v->{'@language'};
						}
						
						if (defined($direction)) {
							println "13.7.4.2.5" if $debug;
							$v->{'@direction'}	= $direction;
						}
						
						println "13.7.4.2.6" if $debug;
						push(@$expandedValue, $v);
					}
				}
				println "13.7 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
# 				} elsif ((exists $container_mapping->{'@index'} or exists $container_mapping->{'@type'} or exists $container_mapping->{'@id'}) and ref($value) eq 'HASH') {
			} elsif ($self->_cm_contains_any($container_mapping, '@index', '@type', '@id') and ref($value) eq 'HASH') {
				println "13.8" if $debug;
				println "13.8.1" if $debug;
				$expandedValue	= [];
				
				println "13.8.2" if $debug;
				my $index_key	= $tdef->{'index_mapping'} // '@index';
				
				println "13.8.3" if $debug;
				foreach my $index (sort keys %$value) {
					my $__indent	= indent();
					my $index_value	= $value->{$index};
					println '-----------------------------------------------------------------' if $debug;
					println "13.8.3 [$index]" if $debug;
					my $map_context;
					if ($self->_cm_contains_any($container_mapping, '@id', '@type')) {
						println "13.8.3.1" if $debug;
						$map_context	= $activeCtx->{'previous_context'} // $activeCtx;
					} else {
						$map_context	= $activeCtx;
					}
					
					my $index_tdef	= $self->_ctx_term_defn($map_context, $index);
					if ($self->_cm_contains_any($container_mapping, '@type') and exists $index_tdef->{'@context'}) {
						println "13.8.3.2" if $debug;
						$map_context	= $self->_4_1_2_ctx_processing($map_context, $index_tdef->{'@context'});
					} else {
						println "13.8.3.3" if $debug;
						$map_context	= $activeCtx;
					}
					
					println "13.8.3.4" if $debug;
					my $expanded_index	= $self->_5_2_2_iri_expansion($activeCtx, $index, vocab => 1);

					if (ref($index_value) ne 'ARRAY') {
						println "13.8.3.5" if $debug;
						$index_value	= [$index_value];
					}
					
					println "13.8.3.6" if $debug;
					$index_value	= $self->_5_1_2_expansion($map_context, $key, $index_value, frameExpansion => $frameExpansion, ordered => $ordered);
					println(Data::Dumper->Dump([$index_value], ['*index_value'])) if $debug;
					
					println "13.8.3.7" if $debug;
					foreach my $item (@$index_value) {
						my $__indent	= indent();
						println '-----------------------------------------------------------------' if $debug;
						println "13.8.3.7 [$item]" if $debug;
						if ($self->_cm_contains($container_mapping, '@graph') and not $self->_is_graph_object($item)) {
							println(Data::Dumper->Dump([$container_mapping], ['*container_mapping'])) if $debug;
							println "13.8.3.7.1" if $debug;
							$item	= {'@graph' => (ref($item) eq 'ARRAY') ? $item : [$item]};
							println(Data::Dumper->Dump([$item], ['*item'])) if $debug;
						}

						if ($self->_cm_contains($container_mapping, '@index') and $index_key ne '@index' and $expanded_index ne '@none') {
							println "13.8.3.7.2 " . Data::Dumper->Dump([$index_key], ['index_key']) if $debug;
							println "13.8.3.7.2.1" if $debug;
							my $re_expanded_index	= $self->_5_3_2_value_expand($activeCtx, $index_key, $index);
							println "13.8.3.7.2.2" if $debug;
							my $expanded_index_key	= $self->_5_2_2_iri_expansion($activeCtx, $index_key, vocab => 1);
							println "13.8.3.7.2.3" if $debug;
							my $index_property_values	= [$re_expanded_index];
							if (exists $item->{$expanded_index_key}) {
								my $v	= $item->{$expanded_index_key};
								if (ref($v) eq 'ARRAY') {
									push(@{$index_property_values}, @$v);
								} else {
									push(@{$index_property_values}, $v);
								}
							}
							println "13.8.3.7.2.4" if $debug;
							$item->{$expanded_index_key}	= $index_property_values;

							if ($self->_is_value_object($item)) {
								my @keys	= sort keys %$item;
								if (scalar(@keys) != 2) {
									die 'invalid value object';
								}
							}
						} elsif ($self->_cm_contains($container_mapping, '@index') and not exists $item->{'@index'} and $expanded_index ne '@none') {
							println "13.8.3.7.3" if $debug;
							$item->{'@index'}	= $index;
							println(Data::Dumper->Dump([$item], ['*item'])) if $debug;
						} elsif ($self->_cm_contains($container_mapping, '@id') and not exists $item->{'@id'} and $expanded_index ne '@none') {
							println "13.8.3.7.4" if $debug;
							$expanded_index	= $self->_5_2_2_iri_expansion($activeCtx, $index, documentRelative => 1);
							$item->{'@id'}	= $expanded_index;
						} elsif ($self->_cm_contains($container_mapping, '@type') and $expanded_index ne '@none') {
							println "13.8.3.7.5" if $debug;
							my $types	= [$expanded_index];
							if (exists $item->{'@type'}) {
								my $v	= $item->{'@type'};
								if (ref($v) eq 'ARRAY') {
									push(@$types, @{$item->{'@type'}});
								} else {
									push(@$types, $v);
								}
							}
							$item->{'@type'}	= $types;
							println "13.8.3.7.5 " . Data::Dumper->Dump([$types], ['types']) if $debug;
						}
						
						println "13.8.3.7.6" if $debug;
						push(@$expandedValue, $item);
					}
				}
				println "13.8 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
			} else {
				println "13.9" if $debug;
				$expandedValue	= $self->_5_1_2_expansion($activeCtx, $key, $value, frameExpansion => $frameExpansion, ordered => $ordered); # 13.9
				println "13.9 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
			}
		
# 				warn Dumper($expandedValue);
			if (not(defined($expandedValue))) {
				println "13.10 going to next element key" if $debug;
				next; # 13.10
			}
		
			if ($self->_cm_contains($container_mapping, '@list') and not $self->_is_list_object($expandedValue)) {
				# 13.11
				println "13.11" if $debug;
				unless (ref($expandedValue) eq 'ARRAY') {
					$expandedValue	= [$expandedValue];
				}
				$expandedValue	= { '@list' => $expandedValue };
				println "13.11 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
			}

			if ($self->_cm_contains($container_mapping, '@graph') and not($self->_cm_contains($container_mapping, '@id')) and not($self->_cm_contains($container_mapping, '@index'))) {
				# https://github.com/w3c/json-ld-api/issues/311
				# 13.12
				println "13.12" if $debug;
				if (ref($expandedValue) ne 'ARRAY') {
					$expandedValue	= [$expandedValue];
				}
				my @values;
				foreach my $ev (@$expandedValue) {
					println "13.12.1" if $debug;
					my $av	= (ref($ev) eq 'ARRAY') ? $ev : [$ev];
					push(@values, {'@graph' => $av});
				}
				$expandedValue	= \@values;
				println "13.12 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
			}
		
			if ($tdef->{'reverse'}) {
				# 13.13
				println "13.13" if $debug;
				unless (exists $result->{'@reverse'}) {
					println "13.13.1" if $debug;
					$result->{'@reverse'}	= {};
				}
				
				println "13.13.2" if $debug;
				my $reverse_map	= $result->{'@reverse'};
				
				if (ref($expandedValue) ne 'ARRAY') {
					println "13.13.3" if $debug;
					$expandedValue	= [$expandedValue];
				}
				
				foreach my $item (@$expandedValue) {
					println "13.13.4" if $debug;
					if ($self->_is_value_object($item) or $self->_is_list_object($item)) {
						println "13.13.4.1" if $debug;
						die 'invalid reverse property value';
					}
					
					unless (exists $reverse_map->{$expandedProperty}) {
						println "13.13.4.2" if $debug;
						$reverse_map->{$expandedProperty}	= [];
					}
					
					println "13.13.4.3" if $debug;
					push(@{ $reverse_map->{$expandedProperty} }, $item);
				}
				println "13.13 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
			} else {
				# 13.14
				println "13.14" if $debug;
				unless (exists $result->{$expandedProperty}) {
					println "13.14.1" if $debug;
					$result->{$expandedProperty}	= []; # 13.14.1
				}

				println "13.14.2 appending to result[$expandedProperty]: " . Data::Dumper->Dump([$expandedValue], ['expandedValue']) if $debug;
				if (ref($expandedValue) eq 'ARRAY') {
					push(@{$result->{$expandedProperty}}, @$expandedValue); # 13.14.2
				} elsif (ref($expandedValue)) {
					println "setting result[$expandedProperty]" if $debug;
					push(@{$result->{$expandedProperty}}, $expandedValue); # 13.14.2
				}
				println "13.14 resulting in " . Data::Dumper->Dump([$expandedValue], ['*expandedValue']) if $debug;
			}
		}
		$self->_5_1_2_expansion_step_14($activeCtx, $type_scoped_ctx, $result, $activeProp, $input_type, $nests, $ordered, $frameExpansion, $element);
	}

	sub _5_1_2_expansion_step_14 {
		my $self			= shift;
		my $activeCtx		= shift;
		my $type_scoped_ctx	= shift;
		my $result			= shift;
		my $activeProp		= shift;
		my $input_type		= shift;
		my $nests			= shift;
		my $ordered			= shift;
		my $frameExpansion	= shift;
		my $element			= shift;
		
		# https://github.com/w3c/json-ld-api/issues/262
		println "14" if $debug;
		my @keys			= sort keys %$nests; # https://github.com/w3c/json-ld-api/issues/295
		foreach my $nesting_key (@keys) {
			delete $nests->{$nesting_key};
			# 14
			my $__indent	= indent();
			println "14 [$nesting_key]" if $debug;
			println "14.1" if $debug;
	# 		next unless (exists $element->{$nesting_key});
			my $nested_values	= $element->{$nesting_key}; # 14.1
			if (not defined $nested_values) {
				$nested_values	= [];
			}
			if (not(ref($nested_values)) or ref($nested_values) ne 'ARRAY') {
				$nested_values	= [$nested_values];
			}

			println "14.2" if $debug;
			println(Data::Dumper->Dump([$nesting_key, $element, $nested_values], [qw(nesting_key element nested_values)])) if $debug;
			foreach my $nested_value (@$nested_values) {
				my $__indent	= indent();
				println '-----------------------------------------------------------------' if $debug;
				println "14.2 loop iteration" if $debug;
				if (ref($nested_value) ne 'HASH') {
					println "14.2.1 " . Data::Dumper->Dump([$nested_value], ['*invalid_nest_value']) if $debug;
					die 'invalid @nest value'; # 14.2.1
				}
				
				my @keys	= keys %$nested_value;
				my %expandedKeys	= map { $_ => 1 } map { $self->_5_2_2_iri_expansion($activeCtx, $_) } @keys;
				if (exists $expandedKeys{'@value'}) {
					println "14.2.1 " . Data::Dumper->Dump([$nested_value], ['*invalid_nest_value']) if $debug;
					die 'invalid @nest value';
				}
				
				println "14.2.2 ENTER    =================> call to _5_1_2_expansion_step_13" if $debug;
				my $__indent_2	= indent();
				$self->_5_1_2_expansion_step_13($activeCtx, $type_scoped_ctx, $result, $activeProp, $input_type, $nests, $ordered, $frameExpansion, $nested_value); # 14.2.2

			}
		}
		println "after 14 resulting in " . Data::Dumper->Dump([$result], ['*result']) if $debug;
	}

	sub _5_2_2_iri_expansion {
		my $self		= shift;
		my $activeCtx	= shift;
		my $value		= shift;
		println "ENTER    =================> _5_2_2_iri_expansion($value)" if $debug;
		my $__indent	= indent();
		my %args		= @_;
		my %acceptable	= map { $_ => 1 } qw(documentRelative vocab localCtx defined);
		foreach my $k (keys %args) {
			die "Not a recognized IRI expansion algorithm argument: $k" unless exists $acceptable{$k};
		}
		my $vocab				= $args{vocab} // 0;
		my $documentRelative	= $args{documentRelative} // 0;
		my $localCtx			= $args{localCtx} // {};
		my $defined				= $args{'defined'} // {};
		local($Data::Dumper::Indent)	= 0;
		println(Data::Dumper->Dump([$activeCtx], ['*activeCtx'])) if $debug;
		println(Data::Dumper->Dump([$localCtx], ['*localCtx'])) if $debug;
		println(Data::Dumper->Dump([$defined], ['*defined'])) if $debug;
		
		# 5.2.2 algorithm
		
		unless (defined($value) and not exists $keywords{$value}) {
			println "1 returning from _5_2_2_iri_expansion: undefined/keyword value" if $debug;
			return $value;
		}
		
		if ($value =~ /^@[A-Za-z]+$/) {
			println "2" if $debug;
			warn "IRI expansion attempted on a term that looks like a keyword: $value\n"; # 2
			return;
		}
		
		if (defined($localCtx) and my $v = $localCtx->{$value}) {
			unless ($defined->{$v}) {
				println "3" if $debug;
				$self->_4_2_2_create_term_definition($activeCtx, $localCtx, $value, $defined); # 3
			}
		}

		if (my $tdef = $self->_ctx_term_defn($activeCtx, $value)) {
			my $i	= $tdef->{'iri_mapping'} // '';
			if ($keywords{$i}) {
				println "4 returning from _5_2_2_iri_expansion with a keyword: $i" if $debug;
				return $i; # 4
			}
		}
		
		if ($vocab and my $tdef = $self->_ctx_term_defn($activeCtx, $value)) {
			my $i	= $tdef->{'iri_mapping'};
			println "5 returning from _5_2_2_iri_expansion with iri mapping from active context: $i" if $debug;
			return $i; # 5
		}
		
		if ($value =~ /.:/) {
			 # 6
			println "6" if $debug;
			println "6.1" if $debug;
			my ($prefix, $suffix)	= split(/:/, $value, 2); # 6.1
			
			if ($prefix eq '_' or $suffix =~ m{^//}) {
				println "6.2 returning from _5_2_2_iri_expansion: already an absolute IRI or blank node identifier: $value" if $debug;
				return $value; # 6.2
			}
			
			if ($localCtx and exists $localCtx->{$prefix} and not($defined->{$prefix})) {
				println "6.3" if $debug;
				$self->_4_2_2_create_term_definition($activeCtx, $localCtx, $prefix, $defined);
			}
			
			my $tdef	= $self->_ctx_term_defn($activeCtx, $prefix);
			if ($tdef and $tdef->{'iri_mapping'} and $tdef->{'prefix_flag'}) {
				my $i	= $tdef->{'iri_mapping'} . $suffix;
				println "6.4 returning from _5_2_2_iri_expansion with concatenated iri mapping and suffix: $i" if $debug;
				return $i;
			}
			
			if ($self->_is_abs_iri($value)) {
				println "6.5 returning from _5_2_2_iri_expansion with absolute IRI: $value" if $debug;
				return $value;
			}
		}
		
		if ($vocab and exists $activeCtx->{'@vocab'}) {
			my $i	= $activeCtx->{'@vocab'} . $value;
			println "7 returning from _5_2_2_iri_expansion with concatenated vocabulary mapping and value: $i" if $debug;
			return $i;
		} elsif ($documentRelative) {
			# 8
			println "8" if $debug;
			my $base = $activeCtx->{'@base'};
			if (defined $base) {
				my $i = IRI->new(value => $value, base => $base);
				$value	= $i->abs;
			}
		}

		println "9 returning from _5_2_2_iri_expansion with final value: $value" if $debug;
		return $value; # 9
	}
	
	sub _5_3_2_value_expand {
		my $self		= shift;
		my $activeCtx	= shift;
		my $activeProp	= shift;
		my $value		= shift;
		my $_vs			= Data::Dumper->new([$value], ['value'])->Terse(1)->Dump([$value], ['value']);
		println "ENTER    =================> _5_3_2_value_expand($_vs)" if $debug;
		my $__indent	= indent();
		
		my $tdef	= $self->_ctx_term_defn($activeCtx, $activeProp);

		if (exists $tdef->{'type_mapping'}) {
			if ($tdef->{'type_mapping'} eq '@id' and _is_string($value)) {
				my $iri	= $self->_5_2_2_iri_expansion($activeCtx, $value, documentRelative => 1);
				println "1 returning from _5_3_2_value_expand with new map containing \@id: $iri" if $debug;
				return { '@id' => $iri }; # 1
			}

			if ($tdef->{'type_mapping'} eq '@vocab' and _is_string($value)) {
				my $iri	= $self->_5_2_2_iri_expansion($activeCtx, $value, vocab => 1, documentRelative => 1);
				println "1 returning from _5_3_2_value_expand with new map containing vocab \@id: $iri" if $debug;
				return { '@id' => $iri }; # 2
			}
		}
		
		println "3" if $debug;
		my $result	= { '@value' => $value }; # 3
		
		my $tm	= $tdef->{'type_mapping'};
		if (exists($tdef->{'type_mapping'}) and $tm ne '@id' and $tm ne '@vocab' and $tm ne '@none') {
			println "4" if $debug;
			$result->{'@type'}	= $tm; # 4
		} elsif (_is_string($value)) {
			println "5" if $debug;
			println "5.1" if $debug;
			my $language	= (exists $tdef->{'language_mapping'}) ? $tdef->{'language_mapping'} : $activeCtx->{'@language'}; # 5.1

			println "5.2" if $debug;
			my $direction	= (exists $tdef->{'direction_mapping'}) ? $tdef->{'direction_mapping'} : $activeCtx->{'@direction'}; # 5.2

			if (defined($language)) {
				println "5.3" if $debug;
				$result->{'@language'}	= $language; # 5.3
			}
			
			if (defined($direction)) {
				println "5.4" if $debug;
				$result->{'@direction'}	= $direction; # 5.4
			}
		}
		
		println "6 returning from _5_3_2_value_expand with final result" if $debug;
		return $result; # 6
	}

	sub _6_1_2_compaction {
		my $self		= shift;
		my $activeCtx	= shift;
		my $inverseCtx	= shift;
		my $activeProp	= shift;
		my $element		= shift;
		{
			no warnings 'uninitialized';
			println "ENTER    =================> _6_1_2_compaction('$activeProp')" if $debug;
		}
		my $__indent	= indent();
# 		local($Data::Dumper::Indent)	= 0;
# 		println(Data::Dumper->Dump([$activeCtx], ['activeCtx'])) if $debug;
# 		println(Data::Dumper->Dump([$activeProp], ['activeProp'])) if $debug;
		println(Data::Dumper->Dump([$element], ['element'])) if $debug;
		my %args		= @_;
		my $compactArrays	= $args{compactArrays} // 0;
		my $ordered		= $args{ordered} // 0;
		
		println "1" if $debug;
		my $type_scoped_ctx	= clone($activeCtx);
		
		if (_is_scalar($element)) {
			println "2 returning scalar " . Data::Dumper->Dump([$element], [qw(element)]) if $debug;
			return $element;
		}
		
		if (ref($element) eq 'ARRAY') {
			println "3" if $debug;
			println "3.1" if $debug;
			my $result	= [];

			println "3.2" if $debug;
			foreach my $item (@$element) {
				println "3.2.1" if $debug;
				my $compactedItem	= $self->_6_1_2_compaction($activeCtx, $inverseCtx, $activeProp, $item, %args);
				if (defined($compactedItem)) {
					println "3.2.2" if $debug;
					push(@$result, $compactedItem);
				}
			}
			
			my $not_graph	= ($activeProp // '') ne '@graph';
			my $not_set		= ($activeProp // '') ne '@set';
			my $tdef		= $self->_ctx_term_defn($activeCtx, $activeProp);
			my $container_mapping	= $tdef->{'container_mapping'};
			# https://github.com/w3c/json-ld-api/issues/334
# 			if (scalar(@$result) == 1 and $compactArrays and (($not_graph and $not_set) or (not($self->_cm_contains_any($container_mapping, '@list', '@set')) ))) {
			if (scalar(@$result) == 1 and (($not_graph and $not_set) or (not($self->_cm_contains_any($container_mapping, '@list', '@set')) and $compactArrays))) {
				# https://github.com/w3c/json-ld-api/issues/334
				println "3.3" if $debug;
				$result	= $result->[0];
			}
			
			println "3.4 returning " . Data::Dumper->Dump([$result], [qw(result)]) if $debug;
			return $result;
		}
		
		println "4 element is a map" if $debug;
		
		if (exists $activeCtx->{'previous_context'}) {
			if (not exists $element->{'@value'}) {
				my @keys	= keys %$element;
				unless (scalar(@keys) == 1 and $keys[0] eq '@id') {
					println "5" if $debug;
					$activeCtx	= $activeCtx->{'previous_context'};
				}
			}
		}
		
		my $tdef = $self->_ctx_term_defn($activeCtx, $activeProp);
		if (exists $tdef->{'@context'}) {
			println "6" if $debug;
			println "6.1" if $debug;
			$activeCtx = $self->_4_1_2_ctx_processing($activeCtx, $tdef->{'@context'}, override_protected => 1);
			
			println "6.2" if $debug;
			$inverseCtx	= $self->_4_3_inverse_context_creation($activeCtx);
		}
		
		if (exists $element->{'@value'} or exists $element->{'@id'}) {
			println "possibly 7" if $debug;
			my $v	= $self->_6_3_value_compaction($activeCtx, $inverseCtx, $activeProp, $element);
			my $tm	= $tdef->{'type_mapping'} // '';
			if (_is_scalar($v) or $tm eq '@json') {
				println "7 returning " . Data::Dumper->Dump([$v], [qw(result)]) if $debug;
				return $v;
			}
		}
		
		my $container_mapping	= $tdef->{'container_mapping'};
		if (_is_list_object($element) and $self->_cm_contains($container_mapping, '@list')) {
			println "8" if $debug;
			my $result	= $self->_6_1_2_compaction($activeCtx, $inverseCtx, $activeProp, $element->{'@list'}, %args);
			println "8 returning " . Data::Dumper->Dump([$result], [qw(result)]) if $debug;
			return $result;
		}

		println "9" if $debug;
		my $insideReverse	= (defined($activeProp) and $activeProp eq '@reverse');
		
		println "10" if $debug;
		my $result	= {};
		
		if (exists $element->{'@type'}) {
			println "11" if $debug;
			my @types	= _values_from_scalar_or_array($element->{'@type'});
# 			my @types	= @{ $element->{'@type'} };
			my @compacted_types	= map { $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $_, vocab => 1) } @types;
			foreach my $term (sort @compacted_types) {
				println "11.1" if $debug;
				my $tdef	= $self->_ctx_term_defn($type_scoped_ctx, $term);
				if (exists $tdef->{'@context'}) {
					println "11.1.1" if $debug;
					$activeCtx	= $self->_4_1_2_ctx_processing($activeCtx, $tdef->{'@context'});

					println "11.1.2" if $debug;
					$inverseCtx	= $self->_4_3_inverse_context_creation($activeCtx);
				}
			}
		}
		
		println "12" if $debug;
		foreach my $expandedProperty (sort keys %$element) {
			my $expandedValue	= $element->{$expandedProperty};
			println "12 [$expandedProperty]" if $debug;

			my $tdef	= $self->_ctx_term_defn($activeCtx, $activeProp) || {};
			my $container_mapping	= $tdef->{'container_mapping'};
			
			if ($expandedProperty eq '@id') {
				println "12.1" if $debug;
				if (_is_string($expandedValue)) {
					println "12.1.1" if $debug;
					my $compactedValue	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $expandedValue);

					println "12.1.2" if $debug;
					my $alias	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $expandedProperty, vocab => 1);
					
					println "12.1.3" if $debug;
					$result->{$alias}	= $compactedValue;
					next;
				}
			} elsif ($expandedProperty eq '@type') {
				println "12.2" if $debug;
				my $compactedValue;
				if (_is_string($expandedValue)) {
					println "12.2.1" if $debug;
					$compactedValue	= $self->_6_2_2_iri_compaction($type_scoped_ctx, $inverseCtx, $expandedValue, vocab => 1);
				} else {
					println "12.2.2" if $debug;
					println "12.2.2.1" if $debug;
					$compactedValue	= [];

					println "12.2.2.2" if $debug;
					foreach my $expandedType (@{ $expandedValue }) {
						println "12.2.2.2.1" if $debug;
						my $term	= $self->_6_2_2_iri_compaction($type_scoped_ctx, $inverseCtx, $expandedType, vocab => 1);
						
						println "12.2.2.2.2" if $debug;
						push(@{ $compactedValue }, $term);
					}

					println "12.2.2.3" if $debug;
					if (scalar(@$compactedValue) == 1) {
						$compactedValue	= $compactedValue->[0];
					}
				}

				println "12.2.3" if $debug;
				my $alias	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $expandedProperty, vocab => 1);

				println "12.2.4" if $debug;
				my $tdef	= $self->_ctx_term_defn($activeCtx, $alias);
				my $container_mapping	= $tdef->{'container_mapping'};
				my $as_array	= ($self->_cm_contains($container_mapping, '@set'))
								? 1
								: not($compactArrays);
				println "12.2.5" if $debug;
				$self->_add_value($result, $alias, $compactedValue, as_array => $as_array);

				println "12.2.6" if $debug;
				next;
			} elsif ($expandedProperty eq '@reverse') {
				println "12.3" if $debug;
				println "12.3.1" if $debug;
				my $compactedValue	= $self->_6_1_2_compaction($activeCtx, $inverseCtx, '@reverse', $expandedValue, compactArrays => $compactArrays, ordered => $ordered);
				
				foreach my $property (keys %$compactedValue) {
					println "12.3.2 [$property]" if $debug;
					my $value	= $compactedValue->{$property};
					
					my $tdef	= $self->_ctx_term_defn($activeCtx, $property);
					if ($tdef->{'reverse'}) {
						println "12.3.2.1" if $debug;
						println "12.3.2.1.1" if $debug;
						my $container_mapping	= $tdef->{'container_mapping'};
						my $as_array;
						if ($self->_cm_contains($container_mapping, '@set')) {
							$as_array	= 1;
						} else {
							$as_array	= not($compactArrays);
						}
						
						println "12.3.2.1.2" if $debug;
						$self->_add_value($result, $property, $value, as_array => $as_array);
						
						println "12.3.2.1.3" if $debug;
						delete $compactedValue->{$property};
					}
				}
				
				my @keys	= keys %$compactedValue;
				if (scalar(@keys)) {
					println "12.3.3" if $debug;
					println "12.3.3.1" if $debug;
					my $alias	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, '@reverse', vocab => 1);
					
					println "12.3.3.2" if $debug;
					$result->{$alias}	= $compactedValue;
				}
				
				println "12.3.4" if $debug;
				next;
			} elsif ($expandedProperty eq '@preserve') {
				println "12.4" if $debug;
				println "12.4.1" if $debug;
				my $compactedValue	= $self->_6_1_2_compaction($activeCtx, $inverseCtx, $activeProp, $expandedValue, compactArrays => $compactArrays, ordered => $ordered);
				
				unless (ref($expandedValue) eq 'ARRAY' and scalar(@$expandedValue) == 0) {
					println "12.4.2" if $debug;
					$result->{'@preserve'}	= $compactedValue;
				}
			} elsif ($expandedProperty eq '@index' and $self->_cm_contains($container_mapping, '@index')) {
				println "12.5" if $debug;
				next;
			} elsif ($expandedProperty =~ /^@(direction|index|language|value)$/) {
				println "12.6" if $debug;
				println "12.6.1" if $debug;
				my $alias	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $expandedProperty, vocab => 1);
				
				println "12.6.2" if $debug;
				$result->{$alias}	= $expandedValue;
				next;
			}
			
			if (ref($expandedValue) eq 'ARRAY' and scalar(@$expandedValue) == 0) {
				println "12.7" if $debug;
				println "12.7.1" if $debug;
				my $item_active_property	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $expandedProperty, value => $expandedValue, vocab => 1, 'reverse' => $insideReverse);

				println "12.7.2" if $debug;
				my $tdef	= $self->_ctx_term_defn($activeCtx, $item_active_property);
				my $nest_result;
				if (exists $tdef->{'nest_value'}) {
					my $nest_term	= $tdef->{'nest_value'};
					if ($nest_term ne '@nest') {
						die 'invalid @nest value';
					}
					if (not exists $result->{$nest_term}) {
						$result->{$nest_term}	= {};
					}
					if (not exists $result->{$nest_term}{$item_active_property}) {
						$result->{$nest_term}{$item_active_property}	= [];
					} elsif (ref($result->{$nest_term}{$item_active_property}) ne 'ARRAY') {
						$result->{$nest_term}{$item_active_property}	= [$result->{$nest_term}{$item_active_property}];
					}
				} else {
					println "12.7.3" if $debug;
					if (not exists $result->{$item_active_property}) {
						$result->{$item_active_property}	= [];
					} elsif (ref($result->{$item_active_property}) ne 'ARRAY') {
						$result->{$item_active_property}	= [$result->{$item_active_property}];
					}
				}
			}
			
			println "12.8" if $debug;
			
			foreach my $expandedItem (@$expandedValue) {
				println "12.8.1" if $debug;
				my $item_active_property	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $expandedProperty, value => $expandedItem, vocab => 1, 'reverse' => $insideReverse);
				println(Data::Dumper->Dump([$item_active_property], ['item_active_property'])) if $debug;
				
				my $tdef	= $self->_ctx_term_defn($activeCtx, $item_active_property);
				my $nest_result;
				if (exists $tdef->{'nest_value'}) {
					println "12.8.2" if $debug;
					my $nest_term	= $tdef->{'nest_value'};
					if ($nest_term ne '@nest') {
						die 'invalid @nest value';
					}
					$nest_result	= $result->{$nest_term} // {};
				} else {
					println "12.8.3" if $debug;
					$nest_result	= $result;
				}
				
				println "12.8.4 " . Data::Dumper->Dump([$container_mapping], [qw(container_mapping)]) if $debug;
				my $container	= undef;
				my $container_mapping	= $tdef->{'container_mapping'};
				if (defined $container_mapping) {
					# https://github.com/w3c/json-ld-api/issues/322
					$container	= [grep { $_ ne '@set' } @$container_mapping]; # TODO unfiled issue on this
# 					$container	= (grep { $_ ne '@set' } @$container_mapping)[0];
				}
				
				println "12.8.5" if $debug;
				my $as_array	= ($self->_cm_contains($container_mapping, '@set') or $item_active_property eq '@graph' or $item_active_property eq '@list');
				
				println "12.8.6" if $debug;
				my $_element	= ((ref($expandedItem) ne 'HASH' or not exists $expandedItem->{'@list'}) and (not($self->_is_graph_object($expandedItem)) or not exists($expandedItem->{'@list'})))
								? $expandedItem
								: $expandedItem->{'@list'};
				my $compactedItem	= $self->_6_1_2_compaction($activeCtx, $inverseCtx, $item_active_property, $_element, %args);
				
				if ($self->_is_list_object($expandedItem)) {
					println "12.8.7" if $debug;
					if (ref($compactedItem) ne 'ARRAY') {
						println "12.8.7.1" if $debug;
						$compactedItem	= [$compactedItem];
					}
					
					if (not $self->_cm_contains($container, '@list')) {
						println "12.8.7.2" if $debug;
						println "12.8.7.2.1" if $debug;
						my $key	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, '@list', value => $compactedItem);
						$compactedItem	= { $key => $compactedItem };
					
						println "12.8.7.2.2" if $debug;
						if (exists $expandedItem->{'@index'}) {
							my $key	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, '@index');
							$compactedItem->{ $key }	= $expandedItem->{'@index'};
						}
					}
				}
				
				if ($self->_is_graph_object($expandedItem)) {
					println "12.8.8"; # if $debug;
					if ($self->_cm_contains($container, '@graph') and $self->_cm_contains($container, '@id')) {
						println "12.8.8.1" if $debug;
						println "12.8.8.1.1" if $debug;
						my $map_object	= ($nest_result->{$item_active_property} //= {});
						
						println "12.8.8.1.2" if $debug;
						my $map_key	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $expandedItem->{'@id'} // '@none', vocab => (not exists $expandedItem->{'@id'}));
						
						println "12.8.8.1.3" if $debug;
						$self->_add_value($map_object, $map_key, $compactedItem, as_array => $as_array);
					} elsif ($self->_cm_contains($container, '@graph') and $self->_cm_contains($container, '@index') and $self->_is_simple_graph_object($expandedItem)) {
						println "12.8.8.2" if $debug;
						println "12.8.8.2.1" if $debug;
						my $map_object	= ($nest_result->{$item_active_property} //= {});
						
						println "12.8.8.2.2" if $debug;
						my $map_key	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $expandedItem->{'@index'} // '@none');

						println "12.8.8.2.3" if $debug;
						$self->_add_value($map_object, $map_key, $compactedItem, as_array => $as_array);
					} elsif ($container eq '@graph' and $self->_is_simple_graph_object($expandedItem)) {
						println "12.8.8.3" if $debug;
						if (ref($compactedItem) eq 'ARRAY' and scalar(@$compactedItem) > 1) {
							println "12.8.8.3.1" if $debug;
							my $k	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, '@included', value => $compactedItem);
							$compactedItem	= {$k => $compactedItem};
						}
						
						println "12.8.8.3.2" if $debug;
						$self->_add_value($nest_result, $item_active_property, $compactedItem, as_array => $as_array);
					} elsif ($container ne '@graph') {
						println "12.8.8.4" if $debug;
						println "12.8.8.4.1" if $debug;
						my $key	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, '@graph', vocab => 1);
						$compactedItem	= { $key => $compactedItem };
						
						if (exists $expandedItem->{'@id'}) {
							println "12.8.8.4.2" if $debug;
							my $key	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, '@id', vocab => 1);
							my $value	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $expandedItem->{'@id'});
							$compactedItem->{$key}	= $value;
						}
						
						
						if (exists $expandedItem->{'@index'}) {
							println "12.8.8.4.3" if $debug;
							my $key	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, '@index', vocab => 1);
							$compactedItem->{$key}	= $expandedItem->{'@index'};
						}
						
						println "12.8.8.4.4" if $debug;
						$self->_add_value($nest_result, $item_active_property, $compactedItem, as_array => $as_array);
					}
				} elsif ($self->_cm_contains_any($container, '@language', '@index', '@id', '@type') and not $self->_cm_contains($container, '@graph')) {
					# TODO: spec text here is really confused since the type of `container` is scalar: "Otherwise, if container includes @language, @index, @id, or @type and container does not include @graph"
					println "12.8.9" if $debug;
					println "12.8.9.1" if $debug;
					my $map_object	= ($nest_result->{$item_active_property} //= {});
					
					println "12.8.9.2" if $debug;
					my $compaction_value;
					foreach my $k ('@language', '@index', '@id', '@type') {
						$compaction_value	= $k if ($self->_cm_contains_any($container, $k));
					}
					my $container_key	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $compaction_value, vocab => 1);
					
					println "12.8.9.3" if $debug;
					my $tdef	= $self->_ctx_term_defn($activeCtx, $item_active_property) // {};
					my $index_key	= $tdef->{'index_mapping'} // '@index';
					
					my $map_key;
					if ($self->_cm_contains_any($container, '@language') and exists $expandedItem->{'@value'}) {
						println "12.8.9.4" if $debug;
						$compactedItem	= $expandedItem->{'@value'};
						$map_key		= $expandedItem->{'@language'};
					} elsif ($self->_cm_contains_any($container, '@index') and $index_key eq '@index') {
						println "12.8.9.5" if $debug;
						$map_key		= $expandedItem->{'@index'};
					} elsif ($self->_cm_contains_any($container, '@index') and $index_key ne '@index') {
						println "12.8.9.6" if $debug;
						my @values	= @{ $compactedItem->{$container_key} || [] };
						$map_key	= shift @values;
						if (scalar(@values)) {
							$compactedItem->{$container_key}	= [@values];
						} else {
							delete $compactedItem->{$container_key};
						}
					} elsif ($self->_cm_contains_any($container, '@id')) {
						println "12.8.9.7" if $debug;
						$map_key	= delete $compactedItem->{$container_key};
					} elsif ($self->_cm_contains_any($container, '@type')) {
						println "12.8.9.8" if $debug;
						println "12.8.9.8.1" if $debug;
						my @values	= @{ $compactedItem->{$container_key} || [] };
						$map_key	= shift @values;
						
						if (scalar(@values)) {
							println "12.8.9.8.2" if $debug;
							$compactedItem->{$container_key}	= [@values];
						} else {
							println "12.8.9.8.3" if $debug;
							delete $compactedItem->{$container_key};
						}
						
						my @keys	= map { $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $_, vocab => 1) } keys %$compactedItem;
						if (scalar(@keys) == 1 and $keys[0] eq '@id') {
							println "12.8.9.8.4" if $debug;
							$compactedItem	= $self->_6_1_2_compaction($activeCtx, $inverseCtx, $item_active_property, {'@id' => $expandedItem});
						}
					}
					
					if (not defined($map_key)) {
						println "12.8.9.9" if $debug;
						$map_key	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, '@none', vocab => 1);
					}
					
					println "12.8.9.10 adding value to [$map_key]" if $debug;
					$self->_add_value($map_object, $map_key, $compactedItem, as_array => 1);
				} else {
					println "12.8.10" if $debug;
					if (not($compactArrays) and $as_array and ref($compactedItem) ne 'ARRAY') {
						println "12.8.10.1" if $debug;
						$compactedItem	= [$compactedItem];
					}
					
					if (not exists $result->{$item_active_property}) {
						println "12.8.10.2" if $debug;
						$nest_result->{$item_active_property}	= $compactedItem;
					} else {
						println "12.8.10.3" if $debug;
						if (ref($nest_result->{$item_active_property}) ne 'ARRAY') {
							$nest_result->{$item_active_property}	= [$nest_result->{$item_active_property}];
						}
						
						if (ref($compactedItem) eq 'ARRAY') {
							push(@{$nest_result->{$item_active_property}}, @{ $compactedItem });
						} else {
							push(@{$nest_result->{$item_active_property}}, $compactedItem);
						}
					}
				}
			}
			println "after this 12 loop body " . Data::Dumper->Dump([$result], [qw(result)]) if $debug;
		}

		println "13 returning " . Data::Dumper->Dump([$result], [qw(result)]) if $debug;
		return $result;
		
	}

	sub _6_2_2_iri_compaction {
		my $self		= shift;
		my $activeCtx	= shift;
		my $inverseCtx	= shift;
		my $var			= shift;
		my %args		= @_;
		my $value		= $args{'value'};
		my $vocab		= $args{'vocab'} || 0;
		my $reverse		= $args{'reverse'} || 0;
		{
			no warnings 'uninitialized';
			println "ENTER    =================> _6_2_2_iri_compaction('$var')" if $debug;
		}
		my $__indent	= indent();
# 		println(Data::Dumper->Dump([$activeCtx], ['activeCtx'])) if $debug;
# 		println(Data::Dumper->Dump([$inverseCtx], ['inverseCtx'])) if $debug;
		
		unless (defined($var)) {
			println "1 returning" if $debug;
			return;
		}
		
		if ($vocab and exists $inverseCtx->{$var}) {
			println "2" if $debug;
			println "2.1" if $debug;
			my $defaultLanguage;
			if (defined $activeCtx->{'default_base_direction'}) {
				println "2.1.1" if $debug;
				$defaultLanguage	= join('_', $activeCtx->{'@language'}, $activeCtx->{'default_base_direction'});
			} else {
				println "2.1.2" if $debug;
				if (exists $activeCtx->{'@language'}) {
					$defaultLanguage	= lc($activeCtx->{'@language'});
				} else {
					$defaultLanguage	= '@none';
				}
			}
			
			if (ref($value) eq 'HASH' and exists $value->{'@preserve'}) {
				println "2.2" if $debug;
				$value	= $value->{'@preserve'}[0];
			}
			
			println "2.3" if $debug;
			my $containers	= [];
			
			println "2.4" if $debug;
			my $type_language		= '@language';
			my $type_language_value	= '@null';
			
			if (ref($value) eq 'HASH' and exists $value->{'@index'} and not $self->_is_graph_object($value)) {
				println "2.5" if $debug;
				push(@$containers, '@index', '@index@set');
			}
			
			if ($reverse) {
				println "2.6" if $debug;
				$type_language			= '@type';
				$type_language_value	= '@reverse';
				push(@$containers, '@set');
			} elsif ($self->_is_list_object($value)) {
				println "2.7" if $debug;
				if (not exists $value->{'@index'}) {
					println "2.7.1" if $debug;
					push(@$containers, '@list');
				}
				
				println "2.7.2" if $debug;
				my $list	= $value->{'@list'};
				
				println "2.7.3" if $debug;
				my $common_type;
				my $common_language;
				unless (scalar(@$list)) {
					$common_language	= $defaultLanguage;
				}
				
				println "2.7.4" if $debug;
				foreach my $item (@$list) {
					println "2.7.4.1" if $debug;
					my $item_language	= '@none';
					my $item_type		= '@none';
					
					if (exists $item->{'@value'}) {
						println "2.7.4.2" if $debug;
						if (exists $item->{'@direction'}) {
							println "2.7.4.2.1" if $debug;
							$item_language	= lc(join('_', $item->{'@language'} // '', $item->{'@direction'}));
						} elsif (exists $item->{'@language'}) {
							println "2.7.4.2.2" if $debug;
							$item_language	= lc($item->{'@language'});
						} elsif (exists $item->{'@type'}) {
							println "2.7.4.2.3" if $debug;
							$item_type	= $item->{'@type'};
						} else {
							println "2.7.4.2.4" if $debug;
							$item_language	= '@null';
						}
					} else {
						println "2.7.4.3" if $debug;
						$item_type	= '@id';
					}
					
					if (not defined($common_language)) {
						println "2.7.4.4" if $debug;
						$common_language	= $item_language;
					} elsif ($item_language ne $common_language and exists $item->{'@value'}) {
						println "2.7.4.5" if $debug;
						$common_language	= '@none';
					}
					
					if (not defined($common_type)) {
						println "2.7.4.6" if $debug;
						$common_type	= $item_type;
					} elsif ($item_type ne $common_type) {
						println "2.7.4.7" if $debug;
						$common_type	= '@none';
					}
					
					if ($common_language eq '@none' and $common_type eq '@none') {
						println "2.7.4.8" if $debug;
						last;
					}
				}
				
				if (not defined($common_language)) {
					println "2.7.5" if $debug;
					$common_language	= '@none';
				}
				
				if (not defined($common_type)) {
					println "2.7.6" if $debug;
					$common_type	= '@none';
				}
				
				if ($common_type ne '@none') {
					println "2.7.7" if $debug;
					$type_language	= '@type';
					$type_language_value	= $common_type;
				} else {
					println "2.7.8" if $debug;
					$type_language_value	= $common_language;
				}
				
			} elsif ($self->_is_graph_object($value)) {
				println "2.8" if $debug;
				if (exists $value->{'@index'}) {
					println "2.8.1" if $debug;
					push(@$containers, '@graph@index', '@graph@index@set');
				}
				
				if (exists $value->{'@id'}) {
					println "2.8.2" if $debug;
					push(@$containers, '@graph@id', '@graph@id@set');
				}
				
				println "2.8.3" if $debug;
				push(@$containers, '@graph', '@graph@set', '@set');
				
				if (not exists $value->{'@index'}) {
					println "2.8.4" if $debug;
					push(@$containers, '@graph@index', '@graph@index@set');
				}
				
				if (not exists $value->{'@id'}) {
					println "2.8.5" if $debug;
					push(@$containers, '@graph@id', '@graph@id@set');
				}

				println "2.8.6" if $debug;
				push(@$containers, '@index', '@index@set');

				println "2.8.7" if $debug;
				$type_language		= '@type';
				$type_language_value	= '@id';
			} else {
				println "2.9" if $debug;
				if ($self->_is_value_object($value)) {
					println "2.9.1" if $debug;
					if (exists $value->{'@direction'} and not exists $value->{'@index'}) {
						println "2.9.1.1" if $debug;
						$type_language_value	= lc(join('_', $value->{'@language'} // '', $value->{'@direction'}));
						push(@$containers, '@language', '@language@set');
					} elsif (exists $value->{'@language'} and not exists $value->{'@index'}) {
						println "2.9.1.2" if $debug;
						$type_language_value	= $value->{'@language'};
						push(@$containers, '@language', '@language@set');
					} elsif (exists $value->{'@type'}) {
						println "2.9.1.3" if $debug;
						$type_language_value	= $value->{'@type'};
						$type_language			= '@type';
					}
				} else {
					println "2.9.2" if $debug;
					$type_language			= '@type';
					$type_language_value	= '@id';
					push(@$containers, '@id', '@id@set', '@type', '@set@type');
				}
				
				println "2.9.3" if $debug;
				push(@$containers, '@set');
			}
			
			println "2.10" if $debug;
			push(@$containers, '@none');
			
# 			unless (ref($value) eq 'HASH') {
# 				Carp::cluck "unexpected non-HASH in IRI Compaction: " . Dumper($value);
# 			}
			if ($self->processing_mode ne 'json-ld-1.0' and not(exists $value->{'@index'})) {
# 			if ($self->processing_mode ne 'json-ld-1.0' and not(ref($value) eq 'HASH' and exists $value->{'@index'})) {
				# TODO: spec is missing the ref 'HASH' check
				println "2.11" if $debug;
				push(@$containers, '@index', '@index@set');
			}
			
			my @keys	= (ref($value) eq 'HASH') ? keys %$value : ();
			if ($self->processing_mode ne 'json-ld-1.0' and scalar(@keys) == 1 and $keys[0] eq '@value') {
				println "2.12" if $debug;
				push(@$containers, '@language', '@language@set');
			}
			
			if (not defined($type_language_value)) {
				println "2.13" if $debug;
				$type_language_value	= '@null';
			}
			
			println "2.14" if $debug;
			my $preferred_values	= [];
			
			if ($type_language_value eq '@reverse') {
				println "2.15" if $debug;
				push(@$preferred_values, '@reverse');
			}
			
			if (($type_language_value eq '@id' or $type_language_value eq '@reverse') and ref($value) eq 'HASH' and exists $value->{'@id'}) {
				# TODO: spec is missing the ref 'HASH' check
				println "2.16" if $debug;
				my $compact_iri	= $self->_6_2_2_iri_compaction($activeCtx, $activeCtx, $value->{'@id'}, vocab => 1);
				my $tdef	= $self->_ctx_term_defn($activeCtx, $compact_iri);
				if ($tdef and $tdef->{'iri_mapping'} eq $value->{'@id'}) {
					println "2.16.1" if $debug;
					push(@$preferred_values, '@vocab', '@id', '@none');
				} else {
					println "2.16.2" if $debug;
					push(@$preferred_values, '@id', '@vocab', '@none');
				}
			} else {
				println "2.17" if $debug;
				push(@$preferred_values, $type_language_value, '@none');
				if ($self->_is_list_object($value) and not scalar(@{ $value->{'@list'} })) {
					# https://github.com/w3c/json-ld-api/issues/345
					$type_language	= '@any';
				}
			}
			
			println "2.18" if $debug;
			push(@$preferred_values, '@any');
			
			my @underscored	= map { substr($_, 1+index($_, '_')) } grep { /_/ } @$preferred_values;
			if (scalar(@underscored)) {
				println "2.19" if $debug;
				push(@$preferred_values, @underscored);
			}
			
			println "2.20" if $debug;
			my $term	= $self->_4_4_2_term_selection($inverseCtx, $var, $containers, $type_language, $preferred_values);

			if (defined($term)) {
				println "2.21 returning: $term" if $debug;
				return $term;
			}
		}
		
		if ($vocab and exists $activeCtx->{'@vocab'}) {
			println "3" if $debug;
			my $vm	= $activeCtx->{'@vocab'};
			if (substr($var, 0, length($vm)) eq $vm and length($var) > length($vm)) {
				println "3.1" if $debug;
				my $suffix	= substr($var, length($vm));
				my $tdef	= $self->_ctx_term_defn($activeCtx, $suffix);
				unless ($tdef) {
					println "3.1 returning: $suffix" if $debug;
					return $suffix;
				}
			}
		}

		println "4" if $debug;
		my $compact_iri	= undef;
		
		println "5" if $debug;
		foreach my $term (keys %{ $activeCtx->{terms} }) {
			println "5 [$term]" if $debug;
			my $tdef	= $self->_ctx_term_defn($activeCtx, $term);
			my $iri_mapping	= $tdef->{'iri_mapping'};
			my $iri_is_prefix	= _is_prefix_of($iri_mapping, $var);
			
			if (not(defined($tdef)) or (($iri_mapping // '') eq $var) or not($iri_is_prefix) or (not(exists $tdef->{'prefix_flag'}) or not($tdef->{'prefix_flag'}))) {
				println "5.1" if $debug;
				next;
			}

			println "5.2" if $debug;
			my $candidate	= join(':', $term, substr($var, length($iri_mapping)));
			
			println "5.3 candidate = $candidate" if $debug;
			if (not defined($compact_iri)) {
				println "5.3a" if $debug;
				$compact_iri	= $candidate;
			} else {
				my $shorter	= (length($candidate) < length($compact_iri));
				my $less	= ((length($candidate) == length($compact_iri)) and (($candidate cmp $compact_iri) == -1));
				my $cand_tdef	= $self->_ctx_term_defn($activeCtx, $candidate);
				my $no_tdef	= not($cand_tdef);
				println "5.3b CANDIDATE: $candidate" if $debug;
				if (($shorter or $less) and $no_tdef) {
					println "5.3b" if $debug;
					$compact_iri	= $candidate;
				} elsif ($cand_tdef->{'iri_mapping'} eq $var and not defined($value)) {
					println "5.3c" if $debug;
					$compact_iri	= $candidate;
				}
			}
		}
		
		if (defined($compact_iri)) {
			println "6 returning: $compact_iri" if $debug;
			return $compact_iri;
		}
		
		my $iri		= IRI->new($var);
		my $scheme	= $iri->scheme;
		my $scheme_tdef	= $self->_ctx_term_defn($activeCtx, $scheme);
		if ($scheme_tdef and $scheme_tdef->{'prefix_flag'} and substr($var, length($scheme)+1, 2) ne '//') {
			println "7" if $debug;
			die 'IRI confused with prefix';
		}

		if (not $vocab) {
			println "8" if $debug;
			my $base	= (exists $activeCtx->{'@base'}) ? IRI->new($activeCtx->{'@base'}) : $self->base_iri;
			my $iri		= IRI->new($var);
			$var		= _make_relative_iri($base, $iri);
		}

		println "9 returning: $var" if $debug;
		return $var;
	}
	
	sub _6_3_value_compaction {
		my $self		= shift;
		my $activeCtx	= shift;
		my $inverseCtx	= shift;
		my $activeProp	= shift;
		my $value		= shift;
		{
			no warnings 'uninitialized';
			println "ENTER    =================> _6_3_value_compaction('$activeProp')" if $debug;
		}
		my $__indent	= indent();
		println(Data::Dumper->Dump([$value], [qw(value)])) if $debug;
		
		println "1" if $debug;
		my $result	= clone($value); # https://github.com/w3c/json-ld-api/issues/350
		
		println "2" if $debug;
		my $tdef	= $self->_ctx_term_defn($activeCtx, $activeProp);
		my $language	= $tdef->{'language_mapping'} // $self->default_language;
		
		println "3" if $debug;
		my $direction	= $tdef->{'direction_mapping'} // $self->default_base_direction;

		my @keys				= grep { $_ ne '@id' and $_ ne '@index' } keys %$value;
		my $type_mapping		= $tdef->{'type_mapping'} // '';
		my $container_mapping	= $tdef->{'container_mapping'};
		if (exists $value->{'@id'} and scalar(@keys) == 0) {
			println "4" if $debug;
			if ($type_mapping eq '@id') {
				println "4.1" if $debug;
				$result	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $value->{'@id'});
			} elsif ($type_mapping eq '@vocab') {
				println "4.2" if $debug;
				$result	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $value->{'@id'}, vocab => 1);
			}
		} elsif (exists $value->{'@type'} and $value->{'@type'} eq $type_mapping) {
			println "5" if $debug;
			$result	= $value->{'@value'};
		} elsif ($type_mapping eq '@none' or (exists $value->{'@type'} and $value->{'@type'} ne $type_mapping)) {
			println "6" if $debug;
			if (exists $result->{'@type'}) {
				println "6.1" if $debug;
				my @types	= (ref($value->{'@type'}) eq 'ARRAY') ? @{ $value->{'@type'} } : $value->{'@type'};
				$result->{'@type'}	= [ map { $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $_, vocab => 1) } @types ];
			}
		} elsif (not _is_string($value->{'@value'})) {
			println "7" if $debug;
			my @keys	= keys %$value;
			if (scalar(@keys) == 1 and $keys[0] eq '@value') {
				# https://github.com/w3c/json-ld-api/issues/351
				$result	= $value->{'@value'};
			} elsif (exists $value->{'@index'} and $self->_cm_contains($container_mapping, '@index')) {
				println "7.1" if $debug;
				$result	= $value->{'@value'};
			}
		} else {
			# https://github.com/w3c/json-ld-api/issues/313
			my $vlang				= $value->{'@language'} // '';
			my $lang_both_undef		= (not(defined($value->{'@language'})) and not(defined($language)));
			my $lang_same			= ($lang_both_undef or (defined($vlang) and defined($language) and lc($vlang) eq lc($language)));
			my $lang_not_present	= ((not defined($language)) and (not exists $value->{'@language'}));
			my $dir					= $value->{'@direction'};
			my $dir_both_undef		= (not(defined($value->{'@direction'})) and not(defined($direction)));
			my $dir_same			= ($dir_both_undef or ($dir eq $direction));
			my $dir_not_present		= ((not defined($direction)) and (not exists $value->{'@direction'}));
			if (($lang_same or $lang_not_present) and ($dir_same or $dir_not_present)) {
				println "8" if $debug;
				if ((exists $value->{'@index'} and $self->_cm_contains($container_mapping, '@index')) or not exists $value->{'@index'}) {
					println "8.1" if $debug;
					$result	= $value->{'@value'};
				}
			}
		}

		if (ref($result) eq 'HASH') {
			println "9" if $debug;
			my @keys	= keys %$result;
			foreach my $k (@keys) {
				my $__indent	= indent();
				println "----------------" if $debug;
				println "9 [$k]" if $debug;
				my $ck	= $self->_6_2_2_iri_compaction($activeCtx, $inverseCtx, $k, vocab => 1);
				$result->{$ck}	= delete $result->{$k};
			}
		}
		
		println "10" if $debug;
		return $result;
	}
	
	sub _7_1_2_flattening {
		my $self			= shift;
		my $element			= shift;
		my $context			= shift;
		my $ordered			= shift // 0;
		die;
	}	

	sub _7_2_2_nodemap_generation {
		println "ENTER    =================> _7_2_2_nodemap_generation" if $debug;
		my $__indent	= indent();
		my $self			= shift;
		my $element			= shift;
		my $map				= shift;
		my $activeGraph		= shift // '@default';
		println(Data::Dumper->Dump([$activeGraph], [qw(activeGraph)])) if $debug;
		println(Data::Dumper->Dump([$element], [qw(element)])) if $debug;
		my $activeSubject	= shift;
		my $activeProp		= shift;
		my $list			= shift;
		
		println "1" if $debug;
		if (ref($element) eq 'ARRAY') {
			foreach my $item (@$element) {
				$self->_7_2_2_nodemap_generation($item, $map, $activeGraph, $activeSubject, $activeProp, $list);
			}
			return;
		}
		
		println "2" if $debug;
		my $graph	= ($map->{$activeGraph} ||= {});
		my $node;
		my $subjectNode;
		if (not defined($activeSubject)) {
			$node	= undef;
		} else {
			$subjectNode	= $graph->{$activeSubject};
		}
		
		unless (ref($element) eq 'HASH') {
			Carp::cluck 'element is not a HASH';
		}
		
		if (exists $element->{'@type'}) {
			println "3" if $debug;
			# https://github.com/w3c/json-ld-api/issues/276
			if (ref($element) and ref($element) ne 'HASH') {
				Carp::cluck 'element is not a HASH';
			}
			if (ref($element->{'@type'})) {
				foreach my $i (0 .. $#{ $element->{'@type'} }) {
					my $item	= $element->{'@type'}[$i];
					if ($item =~ /^_:/) {
						println "3.1" if $debug;
						$element->{'@type'}[$i]	= $self->_7_4_2_generate_blank_node_ident($item);
					}
				}
			} else {
				if ($element->{'@type'} =~ /^_:/) {
					$element->{'@type'}	= $self->_7_4_2_generate_blank_node_ident($element->{'@type'});
				}
			}
		}
		
		my $j	= JSON->new()->canonical(1);
		if (exists $element->{'@value'}) {
			println "4" if $debug;
			if (not defined($list)) {
				println "4.1" if $debug;
				no warnings 'uninitialized';
				if (not exists $subjectNode->{$activeProp}) {
					println "4.1.1" if $debug;
					$subjectNode->{$activeProp}	= [$element];
				} else {
					println "4.1.2" if $debug;
					my $je	= $j->encode($element);
					my $exists	= any { $j->encode($_) eq $je } @{ $subjectNode->{$activeProp} };
					unless ($exists) {
						push(@{ $subjectNode->{$activeProp} }, $element);
					}
				}
			} else {
				println "4.2" if $debug;
				push(@{ $list->{'@list'} }, $element);
			}
		} elsif (exists $element->{'@list'}) {
			println "5" if $debug;
			println "5.1" if $debug;
			my $result	= {'@list' => []};
			
			println "5.2" if $debug;
			$self->_7_2_2_nodemap_generation($element->{'@list'}, $map, $activeGraph, $activeSubject, $activeProp, $result);
			
			if (not defined($list)) {
				println "5.3" if $debug;
				println(Data::Dumper->Dump([$result], [qw(result)])) if $debug;
				push(@{ $subjectNode->{$activeProp} }, $result);
			} else {
				println "5.4" if $debug;
				push(@{ $list->{'@list'} }, $result);
			}
		} else {
			println "6" if $debug;
			my $id;
			if (exists $element->{'@id'}) {
				println "6.1" if $debug;
				$id	= delete $element->{'@id'};
				if ($id =~ /^_:/) {
					$id	= $self->_7_4_2_generate_blank_node_ident($id);
				}
			} else {
				println "6.2" if $debug;
				$id	= $self->_7_4_2_generate_blank_node_ident();
			}
			
			unless (exists $graph->{$id}) {
				println "6.3" if $debug;
				$graph->{$id}	= { '@id' => $id };
			}
			
			println "6.4" if $debug;
			$node	= $graph->{$id};
			
			if (ref($activeSubject) eq 'HASH') {
				println "6.5" if $debug;
				if (not exists $node->{$activeProp}) {
					println "6.5.1" if $debug;
					$node->{$activeProp}	= [$activeSubject];
				} else {
					println "6.5.2" if $debug;
					my $je	= $j->encode($activeSubject);
					my $exists	= any { $j->encode($_) eq $je } @{ $node->{$activeProp} };
					unless ($exists) {
						push(@{ $node->{$activeProp} }, $activeSubject);
					}
				}
			} elsif (defined($activeProp)) {
				println "6.6" if $debug;
				println "6.6.1" if $debug;
				my $reference	= { '@id' => $id };
				
				if (not defined($list)) {
					println "6.6.2" if $debug;
					if (not exists $subjectNode->{$activeProp}) {
						println "6.6.2.1" if $debug;
						$subjectNode->{$activeProp}	= [$reference];
					} else {
						println "6.6.2.2" if $debug;
						my $je	= $j->encode($reference);
						my $exists	= any { $j->encode($_) eq $je } @{ $subjectNode->{$activeProp} };
						unless ($exists) {
							push(@{ $subjectNode->{$activeProp} }, $reference);
						}
					}
				} else {
					push(@{ $list->{'@list'} }, $reference);
				}
			}
			
			if (exists $element->{'@type'}) {
				println "6.7" if $debug;
				my %exists	= map { $_ => 1 } @{ $node->{'@type'} };
				push(@{ $node->{'@type'} }, grep { not exists $exists{$_} }@{ $element->{'@type'} });
				delete $element->{'@type'};
			}
			
			if (exists $element->{'@index'}) {
				println "6.8" if $debug;
				println "6.8 TODO check if element has a pre-existing value" if $debug;
				$node->{'@index'}	= delete $element->{'@index'};
			}

			if (exists $element->{'@reverse'}) {
				println "6.9" if $debug;
				println "6.9.1" if $debug;
				my $referenced_node	= { '@id' => $id };
				
				println "6.9.2" if $debug;
				my $reverse_map	= $element->{'@reverse'};
				
				println "6.9.3" if $debug;
				foreach my $property (keys %$reverse_map) {
					println "6.9.3 [$property]" if $debug;
					my $values	= $reverse_map->{$property};
					
					println "6.9.3.1" if $debug;
					foreach my $value (@$values) {
						println "6.9.3.1.1" if $debug;
						$self->_7_2_2_nodemap_generation($value, $map, $activeGraph, $referenced_node, $property); # TODO: need to pass $list ?
					}
				}

				println "6.9.3.4" if $debug;
				delete $element->{'@reverse'};
			}
						
			if (exists $element->{'@graph'}) {
				println "6.10" if $debug;
				$self->_7_2_2_nodemap_generation($element->{'@graph'}, $map, $id);
				delete $element->{'@graph'};
			}
						
			if (exists $element->{'@included'}) {
				println "6.11" if $debug;
				$self->_7_2_2_nodemap_generation($element->{'@included'}, $map, $activeGraph);
				delete $element->{'@included'};
			}

			println "6.12" if $debug;
			foreach my $property (sort keys %$element) {
				println '----------------' if $debug;
				println "6.12 [$property]" if $debug;
				my $value	= $element->{$property};
				if ($property =~ /^_:/) {
					println "6.12.1" if $debug;
					$property	= $self->_7_4_2_generate_blank_node_ident($property);
				}
				unless (exists $node->{$property}) {
					println "6.12.2" if $debug;
					$node->{$property}	= [];
				}
				
				println "6.12.3" if $debug;
				$self->_7_2_2_nodemap_generation($value, $map, $activeGraph, $id, $property);
			}
		}
	}

	sub _7_4_2_generate_blank_node_ident {
		println "ENTER    =================> _7_4_2_generate_blank_node_ident" if $debug;
		my $__indent	= indent();
		my $self	= shift;
		my $ident	= shift;
		if (defined($ident) and exists $self->identifier_map->{$ident}) {
			println "1" if $debug;
			return $self->identifier_map->{$ident};
		}
		
		println "2" if $debug;
		my $nid	= $self->next_identifier_id;
		$self->next_identifier_id($nid + 1);
		my $bid	= "_:b$nid";

		if (defined($ident)) {
			println "3" if $debug;
			$self->identifier_map->{$ident}	= $bid;
		}
		
		println "4" if $debug;
		return $bid;
	}

	sub _8_1_2_to_rdf {
		println "ENTER    =================> _8_1_2_to_rdf" if $debug;
		my $__indent	= indent();
		my $self	= shift;
		my $map		= shift;
		my $dataset	= shift;
		my %args	= @_;
		my $produce_genrdf	= $args{'produceGeneralizedRdf'} || 0;
		my $rdfDirection	= $args{'rdfDirection'};
		
		println "1" if $debug;
		for my $graphName (keys %$map) {
			my $__indent	= indent();
			println '----------------------' if $debug;
			println "1 [$graphName]" if $debug;
			my $graph	= $map->{$graphName};

			unless ($self->_is_well_formed_graphname($graphName)) {
				println "1.1" if $debug;
				next;
			}
			
			my $graph_iri;
			println "1.2" if $debug;
			if ($graphName eq '@default') {
				$graph_iri	= $self->default_graph();
			} else {
				$graph_iri	= $self->new_graphname($graphName);
			}
			
			println "1.3" if $debug;
			foreach my $subject (sort keys %$graph) {
				my $__indent	= indent();
				println '----------------------' if $debug;
				println "1.3 [$subject]" if $debug;
				my $node	= $graph->{$subject};

				println "1.3.2" if $debug;
				foreach my $property (sort keys %$node) {
					println "1.3.2 [$property]" if $debug;
					my $values	= $node->{$property};
					println(Dumper($property, $node)) if $debug;
					if ($property eq '@type') {
						println "1.3.2.1" if $debug;
						foreach my $type (@$values) {
							if ($self->_is_well_formed_graph_node($type)) {
								my $q	= $self->new_quad(
									$self->new_graph_node($subject),
									$self->new_iri('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
									$self->new_graph_node($type),
									$graph_iri
								);
								$self->add_quad($q, $dataset);
							}
						}
					} elsif (exists $keywords{$property}) {
						println "1.3.2.2" if $debug;
						next;
					} elsif ($property =~ /^_:(.*)$/ and not $produce_genrdf) {
						println "1.3.2.3" if $debug;
						next;
					} elsif (not $self->_is_well_formed_iri($property)) {
						println "1.3.2.4" if $debug;
						next;
					} else {
						println "1.3.2.5" if $debug;
						foreach my $item (@$values) {
							println "1.3.2.5.1" if $debug;
							println "1.3.2.5.2" if $debug;
							my $list_triples	= [];
							my $s	= $self->_8_2_2_object_to_rdf({'@id' => $subject}, $list_triples);
							my $o	= $self->_8_2_2_object_to_rdf($item, $list_triples);
							if ($o) {
								println "1.3.2.5.3" if $debug;
								my $q	= $self->new_quad(
									$s,
									$self->new_iri($property),
									$o,
									$graph_iri
								);
								if ($q) {
									$self->add_quad($q, $dataset);
								}
							}
							println "1.3.2.5.4" if $debug;
							foreach my $t (@$list_triples) {
								my $q	= $t->as_quad($graph_iri);
								$self->add_quad($q, $dataset);
							}
						}
					}
				}
			}
		}
	}

	sub _8_2_2_object_to_rdf {
		println "ENTER    =================> _8_2_2_object_to_rdf" if $debug;
		my $__indent	= indent();
		my $self			= shift;
		my $item			= shift;
		my $list_triples	= shift;
		if ($self->_is_node_object($item) and not $self->_is_well_formed_graph_node($item->{'@id'})) {
			println "1" if $debug;
			println "*** _8_2_2_object_to_rdf RETURNING NULL on " . Data::Dumper->Dump([$item], ['item']) if $debug;
			return;
		}

		if ($self->_is_node_object($item)) {
			my $value	= $item->{'@id'};
			println "2 $value" if $debug;
			if ($value =~ /^_:(.*)$/) {
				return $self->new_blank($1);
			} else {
				return $self->new_iri($value);
			}
		}
		
		if ($self->_is_list_object($item)) {
			println "3" if $debug;
			return $self->_8_3_2_list_conversion($item->{'@list'}, $list_triples);
		}
		
		println "4" if $debug;
		my $value	= $item->{'@value'};
		
		println "5" if $debug;
		my $datatype	= $item->{'@type'};

		if (defined($datatype) and not $self->_is_well_formed_datatype($datatype)) { # https://github.com/w3c/json-ld-api/issues/282 ; https://github.com/w3c/json-ld-api/issues/298
			println "6" if $debug;
			println "*** _8_2_2_object_to_rdf RETURNING NULL" if $debug;
			return;
		}
		
		if (exists $item->{'@language'} and not $self->_is_well_formed_language($item->{'@language'})) {
			println "7" if $debug;
			println "*** _8_2_2_object_to_rdf RETURNING NULL" if $debug;
			return;
		}
		
		if (defined($datatype) and $datatype eq '@json') {
			println "8" if $debug;
			$value		= decode_utf8(JSON->new->utf8->allow_nonref->encode($value));
			$datatype	= 'http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON';
		}
		
		if (JSON::is_bool($value)) {
			println "9" if $debug;
			$value	= $value ? 'true' : 'false';
			unless ($datatype) {
				$datatype	= 'http://www.w3.org/2001/XMLSchema#boolean';
			}
		}
		
		my $is_num		= _is_numeric($value);
		my $is_int		= _is_integer($value);
		my $int_repr	= $is_num ? sprintf('%011.0f', $value) : '';
		my $double_or_not_int	= (not($is_int) or (defined($datatype) and $datatype eq 'http://www.w3.org/2001/XMLSchema#double'));
		my $is_large_int		= ($is_int and length($int_repr) >= 22 and $int_repr =~ /^[^-+0]/);
		if ($is_large_int or ($is_num and $double_or_not_int)) {
			# https://github.com/w3c/json-ld-api/issues/312
			println "10" if $debug;
			$value	= sprintf('%E', $value);
			$value	=~ s/0+E/0E/;
			$value	=~ s/(\d)0E/$1E/;
			$value	=~ s/E[+]/E/;
			$value	=~ s/E0+([1-9])/E$1/;
			$value	=~ s/E0+/E0/;
			unless ($datatype) {
				$datatype	= 'http://www.w3.org/2001/XMLSchema#double';
			}
		} elsif ($is_num) {
			println "11" if $debug;
			$value	= sprintf('%011.0f', $value);
			$value	=~ s/^(-?)0+(\d)/$1$2/;
			$value	=~ s/^-0/0/;
			
			unless ($datatype) {
				$datatype	= 'http://www.w3.org/2001/XMLSchema#integer';
			}
		}

		if (not defined($datatype)) {
			println "12" if $debug;
			$datatype	= (exists $item->{'@language'}) ? 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' : 'http://www.w3.org/2001/XMLSchema#string';
		}
		
		my $literal;
		if (exists $item->{'@direction'} and defined(my $dir = $self->rdf_direction)) {
			println "13" if $debug;
			if ($dir eq 'i18n-datatype') {
				println "13.1" if $debug;
				my $language	= $item->{'@language'} // '';
				# https://github.com/w3c/json-ld-api/issues/337
				$datatype	= 'https://www.w3.org/ns/i18n#' . join('_', lc($language), $item->{'@direction'});
				$literal	= $self->new_dt_literal($value, $datatype);
			} elsif ($dir eq 'compound-literal') {
				println "13.2" if $debug;
				println "13.2.1" if $debug;
				$literal	= $self->new_blank();
				println "13.2.2" if $debug;
				println "TODO: spec text issue for literal construction";
				my $t	= $self->new_triple(
					$literal,
					$self->new_iri('http://www.w3.org/1999/02/22-rdf-syntax-ns#value'),
					$self->new_dt_literal($item->{'@value'}, 'http://www.w3.org/2001/XMLSchema#string')
# 					$item->{'@value'} # TODO: should this be a term constructor call?
				);
				push(@$list_triples, $t);
				
				if (exists $item->{'@language'}) {
					println "13.2.3" if $debug;
					println "TODO: spec text issue for literal construction";
					my $t	= $self->new_triple(
						$literal,
						$self->new_iri('http://www.w3.org/1999/02/22-rdf-syntax-ns#language'),
						$self->new_dt_literal(lc($item->{'@language'}), 'http://www.w3.org/2001/XMLSchema#string')
# 						$item->{'@language'} # TODO: should this be a term constructor call?
					);
					push(@$list_triples, $t);
				}
				
				println "13.2.4" if $debug;
				println "TODO: spec text issue for literal construction";
				my $t2	= $self->new_triple(
					$literal,
					$self->new_iri('http://www.w3.org/1999/02/22-rdf-syntax-ns#direction'),
					$self->new_dt_literal($item->{'@direction'}, 'http://www.w3.org/2001/XMLSchema#string')
# 					$item->{'@direction'} # TODO: should this be a term constructor call?
				);
				push(@$list_triples, $t2);
			}
		} else {
			println "14" if $debug;
			$literal	= (exists $item->{'@language'})
						? $self->new_lang_literal($value, lc($item->{'@language'} // ''))
						: $self->new_dt_literal($value, $datatype);
		}
		
		println "15" if $debug;
		return $literal;
	}

	sub _8_3_2_list_conversion {
		println "ENTER    =================> _8_3_2_list_conversion" if $debug;
		my $__indent		= indent();
		my $self			= shift;
		my $list			= shift;
		my $list_triples	= shift;
		
		my $NIL	= $self->new_iri('http://www.w3.org/1999/02/22-rdf-syntax-ns#nil');
		if (scalar(@$list) == 0) {
			println "1" if $debug;
			return $NIL;
		}
		
		println "2" if $debug;
		my $bnodes	= [map { $self->new_blank() } @$list];
		foreach my $i (0..$#{$bnodes}) {
			my $subject	= $bnodes->[$i];
			my $item	= $list->[$i];
			println "3" if $debug;
			println "3.1" if $debug;
			my $embedded_triples	= [];
			
			println "3.2" if $debug;
			my $object	= $self->_8_2_2_object_to_rdf($item, $embedded_triples);
			
			if (defined($object)) {
				println "3.3" if $debug;
				push(@$list_triples, $self->new_triple(
					$subject,
					$self->new_iri('http://www.w3.org/1999/02/22-rdf-syntax-ns#first'),
					$object
				));
			}
			
			println "3.4" if $debug;
			my $rest	= ($i == $#{$bnodes}) ? $NIL : $bnodes->[$i+1];
			push(@$list_triples, $self->new_triple(
				$subject,
				$self->new_iri('http://www.w3.org/1999/02/22-rdf-syntax-ns#rest'),
				$rest
			));
			
			println "3.5" if $debug;
			push(@$list_triples, @$embedded_triples);
		}

		println "4" if $debug;
		my $head	= (scalar(@$bnodes)) ? $bnodes->[0] : $NIL;
		return $head;
	}
	
	sub new_graph_node {
		my $self	= shift;
		my $value	= shift;
		if ($value =~ /_:(.*)/) {
			return $self->new_blank($1);
		} else {
			return $self->new_iri($value);
		}
	}

	sub new_graphname {
		my $self	= shift;
		Carp::cluck 'RDF term constructors must be overloaded in a JSONLD subclass';
	}

	sub default_graph {
		my $self	= shift;
		Carp::cluck 'RDF default graph accessor must be overloaded in a JSONLD subclass';
	}
	
	sub new_dataset {
		my $self	= shift;
		Carp::cluck 'RDF dataset constructors must be overloaded in a JSONLD subclass';
	}
	
	sub new_triple {
		my $self	= shift;
		Carp::cluck 'RDF statement constructors must be overloaded in a JSONLD subclass';
	}
	
	sub new_quad {
		my $self	= shift;
		Carp::cluck 'RDF statement constructors must be overloaded in a JSONLD subclass';
	}
	
	sub new_iri {
		my $self	= shift;
		Carp::cluck 'RDF term constructors must be overloaded in a JSONLD subclass';
	}
	
	sub new_blank {
		my $self	= shift;
		Carp::cluck 'RDF term constructors must be overloaded in a JSONLD subclass';
	}
	
	sub new_lang_literal {
		my $self	= shift;
		Carp::cluck 'RDF term constructors must be overloaded in a JSONLD subclass';
	}
	
	sub new_dt_literal {
		my $self	= shift;
		Carp::cluck 'RDF term constructors must be overloaded in a JSONLD subclass';
	}
	
	sub add_quad {
		my $self	= shift;
		Carp::cluck 'RDF dataset methods must be overloaded in a JSONLD subclass';
	}
}



1;

__END__

=back

=head1 BUGS

Please report any bugs or feature requests to through the GitHub web interface
at L<https://github.com/kasei/perl-jsonld/issues>.

=head1 SEE ALSO

=over 4

=item L<irc://irc.perl.org/#perlrdf>

=item L<AtteanX::Parser::JSONLD>

=item L<https://www.w3.org/TR/json-ld11/>

=item L<https://www.w3.org/TR/json-ld-api/>

=item L<MooX::Role::JSON_LD>

=back

=head1 AUTHOR

Gregory Todd Williams  C<< <gwilliams@cpan.org> >>

=head1 COPYRIGHT

Copyright (c) 2019--2020 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.

=cut
