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