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_lemma - The reading serves as a lemma for the constructed text.
48 =item is_start - The reading is the starting point for the collation.
50 =item is_end - The reading is the ending point for the collation.
52 =item is_lacuna - The 'reading' represents a known gap in the text.
54 =item is_ph - A temporary placeholder for apparatus parsing purposes. Do
55 not use unless you know what you are doing.
57 =item rank - The sequence number of the reading. This should probably not
62 One of 'text', 'is_start', 'is_end', or 'is_lacuna' is required.
78 =head2 rank( $new_rank )
80 Accessor methods for the given attributes.
84 Changes the text of the reading.
88 Sets this reading as a lemma for the constructed text.
94 isa => 'Text::Tradition::Collation',
109 writer => 'alter_text',
116 writer => 'make_lemma',
152 predicate => 'has_rank',
153 clearer => 'clear_rank',
156 ## For prefix/suffix readings
158 has 'join_prior' => (
162 writer => '_set_join_prior',
169 writer => '_set_join_next',
173 around BUILDARGS => sub {
183 # If one of our special booleans is set, we change the text and the
185 if( exists $args->{'is_lacuna'} && $args->{'is_lacuna'} && !exists $args->{'text'} ) {
186 $args->{'text'} = '#LACUNA#';
187 } elsif( exists $args->{'is_start'} && $args->{'is_start'} ) {
188 $args->{'id'} = '__START__'; # Change the ID to ensure we have only one
189 $args->{'text'} = '#START#';
191 } elsif( exists $args->{'is_end'} && $args->{'is_end'} ) {
192 $args->{'id'} = '__END__'; # Change the ID to ensure we have only one
193 $args->{'text'} = '#END#';
194 } elsif( exists $args->{'is_ph'} && $args->{'is_ph'} ) {
195 $args->{'text'} = $args->{'id'};
198 # Backwards compatibility for non-XMLname IDs
199 my $rid = $args->{'id'};
202 if( $rid !~ /^$xml10_namestartchar_rx/ ) {
205 $args->{'id'} = $rid;
207 $class->$orig( $args );
210 # Look for a lexeme-string argument in the build args; if there, pull in the
211 # morphology role if possible.
213 my( $self, $args ) = @_;
214 if( exists $args->{'lexemes'} ) {
215 unless( $self->can( '_deserialize_lexemes' ) ) {
216 warn "No morphology package installed; DROPPING lexemes";
219 $self->_deserialize_lexemes( $args->{'lexemes'} );
227 around make_lemma => sub {
232 my @altered = ( $self );
234 # Unset the is_lemma flag for other readings at our rank
235 foreach my $rdg ( $self->collation->readings_at_rank( $self->rank ) ) {
236 next if $rdg eq $self;
237 if( $rdg->is_lemma ) {
239 push( @altered, $rdg );
242 # Call the morphology handler
243 if( $self->does( 'Text::Tradition::Morphology' ) ) {
244 push( @altered, $self->push_normal_form() );
247 $self->$orig( $val );
253 A meta attribute (ha ha), which should be true if any of our 'special'
254 booleans are true. Implies that the reading does not represent a bit
255 of text found in a witness.
261 return $self->is_start || $self->is_end || $self->is_lacuna || $self->is_ph;
264 =head2 is_identical( $other_reading )
266 Returns true if the reading is identical to the other reading. The basic test
267 is equality of ->text attributes, but this may be wrapped or overridden by
273 my( $self, $other ) = @_;
274 return $self->text eq $other->text;
279 Returns true if the reading may in theory be combined into a multi-reading
280 segment within the collation graph. The reading must not be a meta reading,
281 and it must not have any relationships in its own right with any others.
282 This test may be wrapped or overridden by extensions.
288 return undef if $self->is_meta;
289 return !$self->related_readings();
292 # Not really meant for public consumption. Adopt the text of the other reading
295 my( $self, $other, $joinstr ) = @_;
296 $self->alter_text( join( $joinstr, $self->text, $other->text ) );
297 # Change this reading to a joining one if necessary
298 $self->_set_join_next( $other->join_next );
301 =head1 Convenience methods
303 =head2 related_readings
305 Calls Collation's related_readings with $self as the first argument.
309 sub related_readings {
311 return $self->collation->related_readings( $self, @_ );
316 Calls Collation's reading_witnesses with $self as the first argument.
322 return $self->collation->reading_witnesses( $self, @_ );
327 Returns a list of Reading objects that immediately precede $self in the collation.
333 my @pred = $self->collation->sequence->predecessors( $self->id );
334 return map { $self->collation->reading( $_ ) } @pred;
339 Returns a list of Reading objects that immediately follow $self in the collation.
345 my @succ = $self->collation->sequence->successors( $self->id );
346 return map { $self->collation->reading( $_ ) } @succ;
362 Text::Tradition::Error->throw(
363 'ident' => 'Reading error',
369 __PACKAGE__->meta->make_immutable;