use strict;
use warnings;
+use Text::Tradition::Error;
use Text::Tradition::Collation::Relationship;
+use TryCatch;
use Moose;
relationships => 'edges',
add_reading => 'add_vertex',
delete_reading => 'delete_vertex',
+ delete_relationship => 'delete_edge',
},
);
+around 'delete_relationship' => sub {
+ my $orig = shift;
+ my $self = shift;
+ my @vector;
+ if( @_ == 1 && ref( $_[0] ) eq 'ARRAY' ) {
+ # Dereference the edge arrayref that was passed.
+ my $edge = shift;
+ @vector = @$edge;
+ } else {
+ @vector = @_;
+ }
+ return $self->$orig( @vector );
+};
+
=head2 get_relationship
Return the relationship object, if any, that exists between two readings.
=cut
sub get_relationship {
- my( $self, @vector ) = @_;
+ my $self = shift;
+ my @vector;
+ if( @_ == 1 && ref( $_[0] ) eq 'ARRAY' ) {
+ # Dereference the edge arrayref that was passed.
+ my $edge = shift;
+ @vector = @$edge;
+ } else {
+ @vector = @_;
+ }
my $relationship;
if( $self->graph->has_edge_attribute( @vector, 'object' ) ) {
$relationship = $self->graph->get_edge_attribute( @vector, 'object' );
$self->graph->add_edge( @vector );
$self->graph->set_edge_attribute( @vector, 'object', $relationship );
}
+
+sub _remove_relationship {
+ my( $self, @vector ) = @_;
+ $self->graph->delete_edge( @vector );
+}
=head2 create
my $rel = $self->get_relationship( $source, $target );
if( $rel ) {
if( $rel->type ne $options->{'type'} ) {
- warn "Another relationship of type " . $rel->type
- . " already exists between $source and $target";
- return;
+ throw( "Another relationship of type " . $rel->type
+ . " already exists between $source and $target" );
} else {
return $rel;
}
if( $rel && $rel->type eq $options->{'type'} ) {
return $rel;
} elsif( $rel ) {
- warn sprintf( "Relationship of type %s with scope %s already defined for readings %s and %s", $rel->type, $rel->scope, $options->{'reading_a'}, $options->{'reading_b'} );
- return;
+ throw( sprintf( "Relationship of type %s with scope %s already defined for readings %s and %s", $rel->type, $rel->scope, $options->{'reading_a'}, $options->{'reading_b'} ) );
} else {
$rel = Text::Tradition::Collation::Relationship->new( $options );
$self->add_scoped_relationship( $rel ) if $rel->nonlocal;
my( $is_valid, $reason ) =
$self->relationship_valid( $source, $target, $options->{'type'} );
unless( $is_valid ) {
- return ( undef, $reason );
+ throw( "Invalid relationship: $reason" );
}
# Try to create the relationship object.
$options->{'reading_b'} = $target_rdg->text;
$options->{'orig_a'} = $source;
$options->{'orig_b'} = $target;
- my $relationship = $self->create( $options );
- return( undef, "Relationship creation failed" ) unless $relationship;
+ my $relationship = $self->create( $options ); # Will throw on error
# Find all the pairs for which we need to set the relationship.
my @vectors = ( [ $source, $target ] );
foreach my $v ( @vectors ) {
my $rel = $self->get_relationship( @$v );
if( $rel ) {
- my $warning = $rel->nonlocal
- ? "Found conflicting relationship at @$v"
- : "Not overriding local relationship set at @$v";
- warn $warning;
+ if( $rel->nonlocal ) {
+ throw( "Found conflicting relationship at @$v" );
+ } else {
+ warn "Not overriding local relationship set at @$v";
+ }
next;
}
$self->_set_relationship( $relationship, @$v );
push( @pairs_set, $v );
}
- return( 1, @pairs_set );
+ return @pairs_set;
}
=head2 relationship_valid( $source, $target, $type )
}
} else {
# Check that linking the source and target in a relationship won't lead
- # to a path loop for any witness. First make a lookup table of all the
+ # to a path loop for any witness. If they have the same rank then fine.
+ return( 1, "ok" )
+ if $c->reading( $source )->has_rank
+ && $c->reading( $target )->has_rank
+ && $c->reading( $source )->rank == $c->reading( $target )->rank;
+
+ # Otherwise, first make a lookup table of all the
# readings related to either the source or the target.
my @proposed_related = ( $source, $target );
push( @proposed_related, $self->related_readings( $source, 'colocated' ) );
my %pr_ids;
map { $pr_ids{ $_ } = 1 } @proposed_related;
- # None of these proposed related readings should have a neighbor that
- # is also in proposed_related.
+ # The cumulative predecessors and successors of the proposed-related readings
+ # should not overlap.
+ my %all_pred;
+ my %all_succ;
foreach my $pr ( keys %pr_ids ) {
- foreach my $neighbor( $c->sequence->neighbors( $pr ) ) {
- return( 0, "Would relate neighboring readings $pr and $neighbor" )
- if exists $pr_ids{$neighbor};
- }
- }
+ map { $all_pred{$_} = 1 } $c->sequence->all_predecessors( $pr );
+ map { $all_succ{$_} = 1 } $c->sequence->all_successors( $pr );
+ }
+ foreach my $k ( keys %all_pred ) {
+ return( 0, "Relationship would create witness loop" )
+ if exists $all_succ{$k};
+ }
+ foreach my $k ( keys %pr_ids ) {
+ return( 0, "Relationship would create witness loop" )
+ if exists $all_pred{$k} || exists $all_succ{$k};
+ }
return ( 1, "ok" );
}
}
$self->delete_reading( $deleted );
}
-sub as_graphml {
+sub _as_graphml {
my( $self, $graphml_ns, $xmlroot, $node_hash, $nodeid_key, $edge_keys ) = @_;
my $rgraph = $xmlroot->addNewChild( $graphml_ns, 'graph' );
$data_el->appendText( $value );
}
+sub throw {
+ Text::Tradition::Error->throw(
+ 'ident' => 'Relationship error',
+ 'message' => $_[0],
+ );
+}
+
no Moose;
__PACKAGE__->meta->make_immutable;