convert Catalyst app to use KiokuDB backend
[scpubgit/stemmatology.git] / lib / Text / Tradition / Collation / Reading.pm
CommitLineData
784877d9 1package Text::Tradition::Collation::Reading;
2
8e1394aa 3use Moose;
784877d9 4use MooseX::NonMoose;
5
6extends 'Graph::Easy::Node';
7
910a0a6d 8has 'rank' => (
9 is => 'rw',
10 isa => 'Int',
11 predicate => 'has_rank',
12 );
eca16057 13
14has 'is_lacuna' => (
15 is => 'rw',
16 isa => 'Bool',
17 );
784877d9 18
19# This contains an array of reading objects; the array is a pool,
20# shared by the reading objects inside the pool. When a reading is
21# added to the pool, all the same_as attributes should be updated.
22has 'same_as' => (
d047cd52 23 is => 'rw',
24 isa => 'ArrayRef[Text::Tradition::Collation::Reading]',
25 );
784877d9 26
8e1394aa 27# Deal with the non-arg option for Graph::Easy's constructor.
28around BUILDARGS => sub {
29 my $orig = shift;
30 my $class = shift;
31
32 my %args;
33 if( @_ == 1 && ref( $_[0] ) ne 'HASH' ) {
34 return $class->$orig( 'name' => $_[0] );
35 } else {
36 return $class->$orig( @_ );
37 }
38};
39
94c00c71 40# A lacuna node is also a meta node.
41before is_lacuna => sub {
42 my( $self, $arg ) = @_;
43 if( $arg ) {
44 $self->is_meta( 1 );
45 }
46};
47
d047cd52 48# Initialize the identity pool.
49sub BUILD {
50 my( $self, $args ) = @_;
8e1394aa 51 $self->same_as( [ $self ] );
d047cd52 52}
784877d9 53
e2902068 54sub text {
55 # Wrapper function around 'label' attribute.
56 my $self = shift;
57 if( @_ ) {
eca16057 58 if( defined $_[0] ) {
59 $self->set_attribute( 'label', $_[0] );
60 } else {
61 $self->del_attribute( 'label' );
62 }
e2902068 63 }
9463b0bf 64 return $self->label;
e2902068 65}
66
910a0a6d 67sub witnessed_by {
68 my( $self, $sigil, $backup ) = @_;
69 my @wits = $self->witnesses;
70 return 1 if grep { $_ eq $sigil } @wits;
71 if( $backup ) {
72 return 1 if grep { $_ eq $backup } @wits;
73 }
74 return 0;
75}
76
77sub witnesses {
78 my( $self ) = @_;
79 my @paths = grep { $_->get_attribute( 'class' ) eq 'path' } $self->outgoing;
80 push( @paths, grep { $_->get_attribute( 'class' ) eq 'path' } $self->incoming );
81 my %wits;
82 foreach my $p ( @paths ) {
83 if( $p->has_hidden_witnesses ) {
84 foreach ( @{$p->hidden_witnesses} ) {
85 $wits{$_} = 1;
86 }
87 } else {
88 $wits{$p->label} = 1;
89 }
90 }
91 return keys %wits;
92}
93
784877d9 94sub merge_from {
95 my( $self, $merged_node ) = @_;
96 # Adopt the identity pool of the other node.
97 my @now_identical = grep { $_ ne $merged_node } @{$merged_node->same_as};
98 my $new_pool = _merge_array_pool( \@now_identical, $self->same_as )
99 if @now_identical;
100
910a0a6d 101 # TODO Adopt the relationship attributes and segment memberships of the other node.
784877d9 102}
103
3265b0ce 104## Dealing with transposed readings. These methods are only really
105## applicable if we have a linear collation graph.
106
784877d9 107sub set_identical {
108 my( $self, $other_node ) = @_;
109 my $enlarged_pool = _merge_array_pool( $self->same_as,
110 $other_node->same_as );
111
112 # ...and set this node to point to the enlarged pool.
8e1394aa 113 $self->same_as( $enlarged_pool );
784877d9 114}
115
de51424a 116sub identical_readings {
117 my $self = shift;
118 my @same = grep { $_ ne $self } @{$self->same_as};
119 return @same;
120}
121
784877d9 122sub _merge_array_pool {
123 my( $pool, $main_pool ) = @_;
124 my %poolhash;
125 foreach ( @$main_pool ) {
126 # Note which nodes are already in the main pool so that we
127 # don't re-add them.
128 $poolhash{$_->name} = 1;
129 }
130
131 foreach( @$pool ) {
132 # Add the remaining nodes to the main pool...
133 push( @$main_pool, $_ ) unless $poolhash{$_->name};
134 }
135 return $main_pool;
136}
d047cd52 137
8e1394aa 138sub has_primary {
139 my $self = shift;
140 my $pool = $self->same_as;
df6d9812 141 return $pool->[0]->name ne $self->name;
8e1394aa 142}
143
144sub primary {
145 my $self = shift;
146 return $self->same_as->[0];
147}
148
94c00c71 149# Looks from the outside like an accessor for a Boolean, but really
150# sets the node's class. Should apply to start, end, and lacunae.
151
152sub is_meta {
153 my $self = shift;
154 my $arg = shift;
155 if( defined $arg && $arg ) {
156 $self->set_attribute( 'class', 'meta' );
157 } elsif ( defined $arg ) {
158 $self->del_attribute( 'class' );
159 }
160 return $self->sub_class eq 'meta';
161}
162
4cdd82f1 163# Returns all readings that adjoin this one on any path.
164sub neighbor_readings {
165 my( $self, $direction ) = @_;
166 $direction = 'both' unless $direction;
167 my @paths = grep { $_->isa( 'Text::Tradition::Collation::Path' ) } $self->edges;
168 my %connected;
169 foreach my $p ( @paths ) {
170 if( $p->to eq $self ) {
171 next if $direction eq 'forward';
172 $connected{$p->from->name} = $p->from;
173 } else { # $p->from eq $self
174 next if $direction =~ /^back/;
175 $connected{$p->to->name} = $p->to;
176 }
177 }
178 return values( %connected );
179}
180
910a0a6d 181# Returns all readings related to the one we've got.
182sub related_readings {
db5d04e0 183 my( $self, $colocated, $queried ) = @_;
184 $queried = { $self->name => 1 } unless $queried;
910a0a6d 185 my @related;
db5d04e0 186 # Get the nodes directly related to this one
910a0a6d 187 foreach my $e ( $self->edges ) {
188 next unless $e->isa( 'Text::Tradition::Collation::Relationship' );
189 next if $colocated && $e->type eq 'repetition';
db5d04e0 190 my $n = $e->from eq $self ? $e->to : $e->from;
191 next if $queried->{$n->name};
192 push( @related, $n );
4cdd82f1 193 }
db5d04e0 194 # Now query those nodes for their relations, recursively
195 map { $queried->{$_->name} = 1 } @related;
196 my @also_related;
197 foreach ( @related ) {
198 push( @also_related, $_->related_readings( $colocated, $queried ) );
199 }
200 push( @related, @also_related );
910a0a6d 201 return @related;
4cdd82f1 202}
203
3265b0ce 204## Keep track of which readings are unchanged across witnesses.
4a8828f0 205sub is_common {
206 my( $self ) = shift;
207 return $self->get_attribute( 'class' ) eq 'common';
208}
209
94c00c71 210## TODO Rationalize make_common, is_meta, etc.
4a8828f0 211sub make_common {
212 my( $self ) = shift;
213 $self->set_attribute( 'class', 'common' );
214}
215
216sub make_variant {
217 my( $self ) = shift;
218 $self->set_attribute( 'class', 'variant' );
219}
220
d047cd52 221no Moose;
222__PACKAGE__->meta->make_immutable;
223
2241;
225
226######################################################
227## copied from Graph::Easy::Parser docs
228######################################################
229# when overriding nodes, we also need ::Anon
230
231package Text::Tradition::Collation::Reading::Anon;
021bdbac 232use Moose;
233use MooseX::NonMoose;
234extends 'Text::Tradition::Collation::Reading';
235extends 'Graph::Easy::Node::Anon';
236no Moose;
237__PACKAGE__->meta->make_immutable;
d047cd52 238
021bdbac 2391;
240# use base qw/Text::Tradition::Collation::Reading/;
241# use base qw/Graph::Easy::Node::Anon/;
d047cd52 242
243######################################################
244# and :::Empty
245
246package Text::Tradition::Collation::Reading::Empty;
021bdbac 247use Moose;
248use MooseX::NonMoose;
249extends 'Graph::Easy::Node::Empty';
250no Moose;
251__PACKAGE__->meta->make_immutable;
d047cd52 252
021bdbac 2531;
254# use base qw/Text::Tradition::Collation::Reading/;
d047cd52 255
256######################################################