1 package Text::Tradition::Collation::Reading;
4 use Moose::Util::TypeConstraints;
5 use JSON qw/ from_json /;
7 use Text::Tradition::Error;
8 use XML::Easy::Syntax qw( $xml10_name_rx $xml10_namestartchar_rx );
10 use overload '""' => \&_stringify, 'fallback' => 1;
14 where { $_ =~ /\A$xml10_name_rx\z/ },
15 message { 'Reading ID must be a valid XML attribute string' };
17 no Moose::Util::TypeConstraints;
21 Text::Tradition::Collation::Reading - represents a reading (usually a word)
26 Text::Tradition is a library for representation and analysis of collated
27 texts, particularly medieval ones. A 'reading' refers to a unit of text,
28 usually a word, that appears in one or more witnesses (manuscripts) of the
29 tradition; the text of a given witness is composed of a set of readings in
36 Creates a new reading in the given collation with the given attributes.
41 =item collation - The Text::Tradition::Collation object to which this
42 reading belongs. Required.
44 =item id - A unique identifier for this reading. Required.
46 =item text - The word or other text of the reading.
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 Accessor methods for the given attributes.
84 isa => 'Text::Tradition::Collation',
99 writer => 'alter_text',
105 predicate => 'has_language',
141 predicate => 'has_rank',
142 clearer => 'clear_rank',
145 ## For morphological analysis
147 has 'normal_form' => (
150 predicate => 'has_normal_form',
153 # Holds the lexemes for the reading.
154 has 'reading_lexemes' => (
156 isa => 'ArrayRef[Text::Tradition::Collation::Reading::Lexeme]',
158 lexemes => 'elements',
159 has_lexemes => 'count',
160 clear_lexemes => 'clear',
161 add_lexeme => 'push',
163 default => sub { [] },
166 ## For prefix/suffix readings
168 has 'join_prior' => (
181 around BUILDARGS => sub {
191 # If one of our special booleans is set, we change the text and the
193 if( exists $args->{'is_lacuna'} && !exists $args->{'text'} ) {
194 $args->{'text'} = '#LACUNA#';
195 } elsif( exists $args->{'is_start'} ) {
196 $args->{'id'} = '__START__'; # Change the ID to ensure we have only one
197 $args->{'text'} = '#START#';
199 } elsif( exists $args->{'is_end'} ) {
200 $args->{'id'} = '__END__'; # Change the ID to ensure we have only one
201 $args->{'text'} = '#END#';
202 } elsif( exists $args->{'is_ph'} ) {
203 $args->{'text'} = $args->{'id'};
206 # Backwards compatibility for non-XMLname IDs
207 my $rid = $args->{'id'};
210 if( $rid !~ /^$xml10_namestartchar_rx/ ) {
213 $args->{'id'} = $rid;
215 $class->$orig( $args );
218 # Look for a lexeme-string argument in the build args.
220 my( $self, $args ) = @_;
221 if( exists $args->{'lexemes'} ) {
222 $self->_deserialize_lexemes( $args->{'lexemes'} );
228 A meta attribute (ha ha), which should be true if any of our 'special'
229 booleans are true. Implies that the reading does not represent a bit
230 of text found in a witness.
236 return $self->is_start || $self->is_end || $self->is_lacuna || $self->is_ph;
239 =head1 Convenience methods
241 =head2 related_readings
243 Calls Collation's related_readings with $self as the first argument.
247 sub related_readings {
249 return $self->collation->related_readings( $self, @_ );
254 Calls Collation's reading_witnesses with $self as the first argument.
260 return $self->collation->reading_witnesses( $self, @_ );
265 Returns a list of Reading objects that immediately precede $self in the collation.
271 my @pred = $self->collation->sequence->predecessors( $self->id );
272 return map { $self->collation->reading( $_ ) } @pred;
277 Returns a list of Reading objects that immediately follow $self in the collation.
283 my @succ = $self->collation->sequence->successors( $self->id );
284 return map { $self->collation->reading( $_ ) } @succ;
287 =head2 set_identical( $other_reading)
289 Backwards compatibility method, to add a transposition relationship
290 between $self and $other_reading. Don't use this.
295 my( $self, $other ) = @_;
296 return $self->collation->add_relationship( $self, $other,
297 { 'type' => 'transposition' } );
307 Methods for the morphological information (if any) attached to readings.
308 A reading may be made up of multiple lexemes; the concatenated lexeme
309 strings ought to match the reading's normalized form.
311 See L<Text::Tradition::Collation::Reading::Lexeme> for more information
312 on Lexeme objects and their attributes.
316 Returns a true value if the reading has any attached lexemes.
320 Returns the Lexeme objects (if any) attached to the reading.
324 Wipes any associated Lexeme objects out of the reading.
326 =head2 add_lexeme( $lexobj )
328 Adds the Lexeme in $lexobj to the list of lexemes.
332 If the language of the reading is set, this method will use the appropriate
333 Language model to determine the lexemes that belong to this reading. See
334 L<Text::Tradition::lemmatize> if you wish to lemmatize an entire tradition.
340 unless( $self->has_language ) {
341 warn "Please set a language to lemmatize a tradition";
344 my $mod = "Text::Tradition::Language::" . $self->language;
346 $mod->can( 'reading_lookup' )->( $self );
350 # For graph serialization. Return a JSON representation of the associated
352 sub _serialize_lexemes {
354 my $json = JSON->new->allow_blessed(1)->convert_blessed(1);
355 return $json->encode( [ $self->lexemes ] );
358 # Given a JSON representation of the lexemes, instantiate them and add
359 # them to the reading.
360 sub _deserialize_lexemes {
361 my( $self, $json ) = @_;
362 my $data = from_json( $json );
363 return unless @$data;
365 # Need to have the lexeme module in order to have lexemes.
366 eval { use Text::Tradition::Collation::Reading::Lexeme; };
369 # Good to go - add the lexemes.
371 foreach my $lexhash ( @$data ) {
372 push( @lexemes, Text::Tradition::Collation::Reading::Lexeme->new(
373 'JSON' => $lexhash ) );
375 $self->clear_lexemes;
376 $self->add_lexeme( @lexemes );
387 Text::Tradition::Error->throw(
388 'ident' => 'Reading error',
394 __PACKAGE__->meta->make_immutable;