1 package Text::Tradition::Collation::Reading;
4 use Moose::Util qw/ does_role apply_all_roles /;
5 use Text::Tradition::Datatypes;
6 use Text::Tradition::Error;
7 use XML::Easy::Syntax qw( $xml10_name_rx $xml10_namestartchar_rx );
8 use overload '""' => \&_stringify, 'fallback' => 1;
10 # Enable plugin(s) if available
11 eval { with 'Text::Tradition::Morphology'; };
12 # Morphology package is not on CPAN, so don't warn of its absence
14 # warn "Text::Tradition::Morphology not found: $@. Disabling lexeme functionality";
19 Text::Tradition::Collation::Reading - represents a reading (usually a word)
24 Text::Tradition is a library for representation and analysis of collated
25 texts, particularly medieval ones. A 'reading' refers to a unit of text,
26 usually a word, that appears in one or more witnesses (manuscripts) of the
27 tradition; the text of a given witness is composed of a set of readings in
34 Creates a new reading in the given collation with the given attributes.
39 =item collation - The Text::Tradition::Collation object to which this
40 reading belongs. Required.
42 =item id - A unique identifier for this reading. Required.
44 =item text - The word or other text of the reading.
46 =item is_start - The reading is the starting point for the collation.
48 =item is_end - The reading is the ending point for the collation.
50 =item is_lacuna - The 'reading' represents a known gap in the text.
52 =item is_ph - A temporary placeholder for apparatus parsing purposes. Do
53 not use unless you know what you are doing.
55 =item rank - The sequence number of the reading. This should probably not
60 One of 'text', 'is_start', 'is_end', or 'is_lacuna' is required.
76 Accessor methods for the given attributes.
82 isa => 'Text::Tradition::Collation',
97 writer => 'alter_text',
104 writer => 'make_lemma',
140 predicate => 'has_rank',
141 clearer => 'clear_rank',
144 ## For prefix/suffix readings
146 has 'join_prior' => (
150 writer => '_set_join_prior',
157 writer => '_set_join_next',
161 around BUILDARGS => sub {
171 # If one of our special booleans is set, we change the text and the
173 if( exists $args->{'is_lacuna'} && $args->{'is_lacuna'} && !exists $args->{'text'} ) {
174 $args->{'text'} = '#LACUNA#';
175 } elsif( exists $args->{'is_start'} && $args->{'is_start'} ) {
176 $args->{'id'} = '__START__'; # Change the ID to ensure we have only one
177 $args->{'text'} = '#START#';
179 } elsif( exists $args->{'is_end'} && $args->{'is_end'} ) {
180 $args->{'id'} = '__END__'; # Change the ID to ensure we have only one
181 $args->{'text'} = '#END#';
182 } elsif( exists $args->{'is_ph'} && $args->{'is_ph'} ) {
183 $args->{'text'} = $args->{'id'};
186 # Backwards compatibility for non-XMLname IDs
187 my $rid = $args->{'id'};
190 if( $rid !~ /^$xml10_namestartchar_rx/ ) {
193 $args->{'id'} = $rid;
195 $class->$orig( $args );
198 # Look for a lexeme-string argument in the build args; if there, pull in the
199 # morphology role if possible.
201 my( $self, $args ) = @_;
202 if( exists $args->{'lexemes'} ) {
203 unless( $self->can( '_deserialize_lexemes' ) ) {
204 warn "No morphology package installed; DROPPING lexemes";
207 $self->_deserialize_lexemes( $args->{'lexemes'} );
211 around make_lemma => sub {
216 # TODO unset the lemma from any other reading at the same rank.
217 if( $val && $self->does( 'Text::Tradition::Morphology' )) {
218 # Set the normal form on all orthographically related readings to match
219 # the normal form on this one.
222 my $rltype = $self->collation->relations->type( $rl->type );
223 return $rltype->bindlevel < 2
225 foreach my $r ( $self->related_readings( $filter ) ) {
226 $r->normal_form( $self->normal_form );
229 $self->$orig( $val );
234 A meta attribute (ha ha), which should be true if any of our 'special'
235 booleans are true. Implies that the reading does not represent a bit
236 of text found in a witness.
242 return $self->is_start || $self->is_end || $self->is_lacuna || $self->is_ph;
245 =head2 is_identical( $other_reading )
247 Returns true if the reading is identical to the other reading. The basic test
248 is equality of ->text attributes, but this may be wrapped or overridden by
254 my( $self, $other ) = @_;
255 return $self->text eq $other->text;
260 Returns true if the reading may in theory be combined into a multi-reading
261 segment within the collation graph. The reading must not be a meta reading,
262 and it must not have any relationships in its own right with any others.
263 This test may be wrapped or overridden by extensions.
269 return undef if $self->is_meta;
270 return !$self->related_readings();
273 # Not really meant for public consumption. Adopt the text of the other reading
276 my( $self, $other, $joinstr ) = @_;
277 $self->alter_text( join( $joinstr, $self->text, $other->text ) );
278 # Change this reading to a joining one if necessary
279 $self->_set_join_next( $other->join_next );
282 =head1 Convenience methods
284 =head2 related_readings
286 Calls Collation's related_readings with $self as the first argument.
290 sub related_readings {
292 return $self->collation->related_readings( $self, @_ );
297 Calls Collation's reading_witnesses with $self as the first argument.
303 return $self->collation->reading_witnesses( $self, @_ );
308 Returns a list of Reading objects that immediately precede $self in the collation.
314 my @pred = $self->collation->sequence->predecessors( $self->id );
315 return map { $self->collation->reading( $_ ) } @pred;
320 Returns a list of Reading objects that immediately follow $self in the collation.
326 my @succ = $self->collation->sequence->successors( $self->id );
327 return map { $self->collation->reading( $_ ) } @succ;
343 Text::Tradition::Error->throw(
344 'ident' => 'Reading error',
350 __PACKAGE__->meta->make_immutable;