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 'grammar_invalid' => (
153 has 'is_nonsense' => (
159 has 'normal_form' => (
162 predicate => 'has_normal_form',
165 # Holds the lexemes for the reading.
166 has 'reading_lexemes' => (
168 isa => 'ArrayRef[Text::Tradition::Collation::Reading::Lexeme]',
170 lexemes => 'elements',
171 has_lexemes => 'count',
172 clear_lexemes => 'clear',
173 add_lexeme => 'push',
175 default => sub { [] },
178 ## For prefix/suffix readings
180 has 'join_prior' => (
193 around BUILDARGS => sub {
203 # If one of our special booleans is set, we change the text and the
205 if( exists $args->{'is_lacuna'} && !exists $args->{'text'} ) {
206 $args->{'text'} = '#LACUNA#';
207 } elsif( exists $args->{'is_start'} ) {
208 $args->{'id'} = '__START__'; # Change the ID to ensure we have only one
209 $args->{'text'} = '#START#';
211 } elsif( exists $args->{'is_end'} ) {
212 $args->{'id'} = '__END__'; # Change the ID to ensure we have only one
213 $args->{'text'} = '#END#';
214 } elsif( exists $args->{'is_ph'} ) {
215 $args->{'text'} = $args->{'id'};
218 # Backwards compatibility for non-XMLname IDs
219 my $rid = $args->{'id'};
222 if( $rid !~ /^$xml10_namestartchar_rx/ ) {
225 $args->{'id'} = $rid;
227 $class->$orig( $args );
230 # Look for a lexeme-string argument in the build args.
232 my( $self, $args ) = @_;
233 if( exists $args->{'lexemes'} ) {
234 $self->_deserialize_lexemes( $args->{'lexemes'} );
240 A meta attribute (ha ha), which should be true if any of our 'special'
241 booleans are true. Implies that the reading does not represent a bit
242 of text found in a witness.
248 return $self->is_start || $self->is_end || $self->is_lacuna || $self->is_ph;
251 =head1 Convenience methods
253 =head2 related_readings
255 Calls Collation's related_readings with $self as the first argument.
259 sub related_readings {
261 return $self->collation->related_readings( $self, @_ );
266 Calls Collation's reading_witnesses with $self as the first argument.
272 return $self->collation->reading_witnesses( $self, @_ );
277 Returns a list of Reading objects that immediately precede $self in the collation.
283 my @pred = $self->collation->sequence->predecessors( $self->id );
284 return map { $self->collation->reading( $_ ) } @pred;
289 Returns a list of Reading objects that immediately follow $self in the collation.
295 my @succ = $self->collation->sequence->successors( $self->id );
296 return map { $self->collation->reading( $_ ) } @succ;
299 =head2 set_identical( $other_reading)
301 Backwards compatibility method, to add a transposition relationship
302 between $self and $other_reading. Don't use this.
307 my( $self, $other ) = @_;
308 return $self->collation->add_relationship( $self, $other,
309 { 'type' => 'transposition' } );
319 Methods for the morphological information (if any) attached to readings.
320 A reading may be made up of multiple lexemes; the concatenated lexeme
321 strings ought to match the reading's normalized form.
323 See L<Text::Tradition::Collation::Reading::Lexeme> for more information
324 on Lexeme objects and their attributes.
328 Returns a true value if the reading has any attached lexemes.
332 Returns the Lexeme objects (if any) attached to the reading.
336 Wipes any associated Lexeme objects out of the reading.
338 =head2 add_lexeme( $lexobj )
340 Adds the Lexeme in $lexobj to the list of lexemes.
344 If the language of the reading is set, this method will use the appropriate
345 Language model to determine the lexemes that belong to this reading. See
346 L<Text::Tradition::lemmatize> if you wish to lemmatize an entire tradition.
352 unless( $self->has_language ) {
353 warn "Please set a language to lemmatize a tradition";
356 my $mod = "Text::Tradition::Language::" . $self->language;
358 $mod->can( 'reading_lookup' )->( $self );
362 # For graph serialization. Return a JSON representation of the associated
364 sub _serialize_lexemes {
366 my $json = JSON->new->allow_blessed(1)->convert_blessed(1);
367 return $json->encode( [ $self->lexemes ] );
370 # Given a JSON representation of the lexemes, instantiate them and add
371 # them to the reading.
372 sub _deserialize_lexemes {
373 my( $self, $json ) = @_;
374 my $data = from_json( $json );
375 return unless @$data;
377 # Need to have the lexeme module in order to have lexemes.
378 eval { use Text::Tradition::Collation::Reading::Lexeme; };
381 # Good to go - add the lexemes.
383 foreach my $lexhash ( @$data ) {
384 push( @lexemes, Text::Tradition::Collation::Reading::Lexeme->new(
385 'JSON' => $lexhash ) );
387 $self->clear_lexemes;
388 $self->add_lexeme( @lexemes );
399 Text::Tradition::Error->throw(
400 'ident' => 'Reading error',
406 __PACKAGE__->meta->make_immutable;