1 package Text::Tradition::Collation::Reading;
4 use JSON qw/ from_json /;
6 use Text::Tradition::Error;
8 use overload '""' => \&_stringify, 'fallback' => 1;
12 Text::Tradition::Collation::Reading - represents a reading (usually a word)
17 Text::Tradition is a library for representation and analysis of collated
18 texts, particularly medieval ones. A 'reading' refers to a unit of text,
19 usually a word, that appears in one or more witnesses (manuscripts) of the
20 tradition; the text of a given witness is composed of a set of readings in
27 Creates a new reading in the given collation with the given attributes.
32 =item collation - The Text::Tradition::Collation object to which this
33 reading belongs. Required.
35 =item id - A unique identifier for this reading. Required.
37 =item text - The word or other text of the reading.
39 =item is_start - The reading is the starting point for the collation.
41 =item is_end - The reading is the ending point for the collation.
43 =item is_lacuna - The 'reading' represents a known gap in the text.
45 =item is_ph - A temporary placeholder for apparatus parsing purposes. Do
46 not use unless you know what you are doing.
48 =item rank - The sequence number of the reading. This should probably not
53 One of 'text', 'is_start', 'is_end', or 'is_lacuna' is required.
69 Accessor methods for the given attributes.
75 isa => 'Text::Tradition::Collation',
90 writer => 'alter_text',
96 predicate => 'has_language',
132 predicate => 'has_rank',
133 clearer => 'clear_rank',
136 ## For morphological analysis
138 has 'normal_form' => (
141 predicate => 'has_normal_form',
144 # Holds the lexemes for the reading.
145 has 'reading_lexemes' => (
147 isa => 'ArrayRef[Text::Tradition::Collation::Reading::Lexeme]',
149 lexemes => 'elements',
150 has_lexemes => 'count',
151 clear_lexemes => 'clear',
152 add_lexeme => 'push',
154 default => sub { [] },
157 ## For prefix/suffix readings
159 has 'join_prior' => (
172 around BUILDARGS => sub {
182 # If one of our special booleans is set, we change the text and the
184 if( exists $args->{'is_lacuna'} && !exists $args->{'text'} ) {
185 $args->{'text'} = '#LACUNA#';
186 } elsif( exists $args->{'is_start'} ) {
187 $args->{'id'} = '#START#'; # Change the ID to ensure we have only one
188 $args->{'text'} = '#START#';
190 } elsif( exists $args->{'is_end'} ) {
191 $args->{'id'} = '#END#'; # Change the ID to ensure we have only one
192 $args->{'text'} = '#END#';
193 } elsif( exists $args->{'is_ph'} ) {
194 $args->{'text'} = $args->{'id'};
197 $class->$orig( $args );
200 # Look for a lexeme-string argument in the build args.
202 my( $self, $args ) = @_;
203 if( exists $args->{'lexemes'} ) {
204 $self->_deserialize_lexemes( $args->{'lexemes'} );
210 A meta attribute (ha ha), which should be true if any of our 'special'
211 booleans are true. Implies that the reading does not represent a bit
212 of text found in a witness.
218 return $self->is_start || $self->is_end || $self->is_lacuna || $self->is_ph;
221 =head1 Convenience methods
223 =head2 related_readings
225 Calls Collation's related_readings with $self as the first argument.
229 sub related_readings {
231 return $self->collation->related_readings( $self, @_ );
236 Calls Collation's reading_witnesses with $self as the first argument.
242 return $self->collation->reading_witnesses( $self, @_ );
247 Returns a list of Reading objects that immediately precede $self in the collation.
253 my @pred = $self->collation->sequence->predecessors( $self->id );
254 return map { $self->collation->reading( $_ ) } @pred;
259 Returns a list of Reading objects that immediately follow $self in the collation.
265 my @succ = $self->collation->sequence->successors( $self->id );
266 return map { $self->collation->reading( $_ ) } @succ;
269 =head2 set_identical( $other_reading)
271 Backwards compatibility method, to add a transposition relationship
272 between $self and $other_reading. Don't use this.
277 my( $self, $other ) = @_;
278 return $self->collation->add_relationship( $self, $other,
279 { 'type' => 'transposition' } );
289 Methods for the morphological information (if any) attached to readings.
290 A reading may be made up of multiple lexemes; the concatenated lexeme
291 strings ought to match the reading's normalized form.
293 See L<Text::Tradition::Collation::Reading::Lexeme> for more information
294 on Lexeme objects and their attributes.
298 Returns a true value if the reading has any attached lexemes.
302 Returns the Lexeme objects (if any) attached to the reading.
306 Wipes any associated Lexeme objects out of the reading.
308 =head2 add_lexeme( $lexobj )
310 Adds the Lexeme in $lexobj to the list of lexemes.
314 If the language of the reading is set, this method will use the appropriate
315 Language model to determine the lexemes that belong to this reading. See
316 L<Text::Tradition::lemmatize> if you wish to lemmatize an entire tradition.
322 unless( $self->has_language ) {
323 warn "Please set a language to lemmatize a tradition";
326 my $mod = "Text::Tradition::Language::" . $self->language;
328 $mod->can( 'reading_lookup' )->( $self );
332 # For graph serialization. Return a JSON representation of the associated
334 sub _serialize_lexemes {
336 my $json = JSON->new->allow_blessed(1)->convert_blessed(1);
337 return $json->encode( [ $self->lexemes ] );
340 # Given a JSON representation of the lexemes, instantiate them and add
341 # them to the reading.
342 sub _deserialize_lexemes {
343 my( $self, $json ) = @_;
344 my $data = from_json( $json );
345 return unless @$data;
347 # Need to have the lexeme module in order to have lexemes.
348 eval { use Text::Tradition::Collation::Reading::Lexeme; };
351 # Good to go - add the lexemes.
353 foreach my $lexhash ( @$data ) {
354 push( @lexemes, Text::Tradition::Collation::Reading::Lexeme->new(
355 'JSON' => $lexhash ) );
357 $self->clear_lexemes;
358 $self->add_lexeme( @lexemes );
369 Text::Tradition::Error->throw(
370 'ident' => 'Reading error',
376 __PACKAGE__->meta->make_immutable;