File Coverage

blib/lib/Treex/Core/Node.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Treex::Core::Node;
2             $Treex::Core::Node::VERSION = '2.20150928';
3 24     24   21024 use namespace::autoclean;
  24         20414  
  24         239  
4              
5 24     24   2542 use Moose;
  24         476276  
  24         264  
6 24     24   146885 use MooseX::NonMoose;
  24         1759  
  24         196  
7 24     24   209857 use Treex::Core::Common;
  24         65  
  24         372  
8 24     24   154085 use Cwd;
  24         59  
  24         1994  
9 24     24   139 use Scalar::Util qw(refaddr);
  24         57  
  24         1199  
10 24     24   22888 use Treex::PML;
  0            
  0            
11              
12             extends 'Treex::PML::Node';
13             with 'Treex::Core::WildAttr';
14              
15             # overloading does not work with namespace::autoclean
16             # see https://rt.cpan.org/Public/Bug/Display.html?id=50938
17             # We may want to use https://metacpan.org/module/namespace::sweep instead.
18             #
19             # use overload
20             # '""' => 'to_string',
21             # '==' => 'equals',
22             # '!=' => '_not_equals',
23             # 'eq' => 'equals', # deprecated
24             # 'ne' => '_not_equals', # deprecated
25             # 'bool' => sub{1},
26             #
27             # # We can A) let Magic Autogeneration to build "derived" overloadings,
28             # # or B) we can disable this feature (via fallback=>0)
29             # # and define only the needed overloadings
30             # # (so all other overloadings will result in fatal errors).
31             # # See perldoc overload.
32             # # I decided for A, but uncommenting the following lines can catch some misuses.
33             # #'!' => sub{0},
34             # #'.' => sub{$_[2] ? $_[1] . $_[0]->to_string : $_[0]->to_string . $_[1]},
35             # #fallback => 0,
36             # ;
37             # # TODO: '<' => 'precedes' (or better '<=>' => ...)
38             # # 'eq' => sub {log_warn 'You should use ==' && return $_[0]==$_[1]} # similarly for 'ne'
39              
40             Readonly my $_SWITCHES_REGEX => qr/^(ordered|add_self|(preceding|following|first|last)_only)$/x;
41             my $CHECK_FOR_CYCLES = 1;
42              
43             our $LOG_NEW = 0;
44             our $LOG_EDITS = 0;
45             # tip: you can use Util::Eval doc='$Treex::Core::Node::LOG_EDITS=1;' in your scenario
46             # Note that most attributes are not set by set_attr. See TODO below.
47              
48             has _zone => (
49             is => 'rw',
50             writer => '_set_zone',
51             reader => '_get_zone',
52             weak_ref => 1,
53             );
54              
55             has id => (
56             is => 'rw',
57             trigger => \&_index_my_id,
58             );
59              
60             sub BUILD {
61             my ( $self, $arg_ref ) = @_;
62              
63             if (( not defined $arg_ref or not defined $arg_ref->{_called_from_core_} )
64             and not $Treex::Core::Config::running_in_tred
65             )
66             {
67             log_fatal 'Because of node indexing, no nodes can be created outside of documents. '
68             . 'You have to use $zone->create_tree(...) or $node->create_child() '
69             . 'instead of Treex::Core::Node...->new().';
70             }
71             return;
72             }
73              
74             sub to_string {
75             my ($self) = @_;
76             return $self->id // 'node_without_id(addr=' . refaddr($self) . ')';
77             }
78              
79             # Since we have overloaded stringification, we must overload == as well,
80             # so you can use "if ($nodeA == $nodeB){...}".
81             sub equals {
82             my ($self, $node) = @_;
83             #return ref($node) && $node->id eq $self->id;
84             return ref($node) && refaddr($node) == refaddr($self);
85             }
86              
87             sub _not_equals {
88             my ($self, $node) = @_;
89             return !$self->equals($node);
90             }
91              
92             sub _index_my_id {
93             my $self = shift;
94             $self->get_document->index_node_by_id( $self->id, $self );
95             return;
96             }
97              
98             sub _caller_signature {
99             my $level = 1;
100             my ($package, $filename, $line) = caller;
101             while ($package =~ /^Treex::Core/){
102             ($package, $filename, $line) = caller $level++;
103             }
104             $package =~ s/^Treex::Block:://;
105             return "$package#$line";
106             }
107              
108             # ---- access to attributes ----
109              
110             # unlike attr (implemented in Treex::PML::Instance::get_data)
111             # get_attr implements only "plain" and "nested hash" attribute names,
112             # i.e. no XPath-like expressions (a/aux.rf[3]) are allowed.
113             # This results in much faster code.
114             sub get_attr {
115             my ( $self, $attr_name ) = @_;
116             log_fatal('Incorrect number of arguments') if @_ != 2;
117             my $val = $self;
118             for my $step ( split /\//, $attr_name ) {
119             if ( !defined $val ) {
120             log_fatal "Attribute '$attr_name' contains strange symbols."
121             . " For XPath like constructs (e.g. 'a/aux.rf[3]') use the 'attr' method."
122             if $attr_name =~ /[^-\w\/.]/;
123             }
124             $val = $val->{$step};
125             }
126             return $val;
127             }
128              
129             use Treex::PML::Factory;
130              
131             sub set_attr {
132             my ( $self, $attr_name, $attr_value ) = @_;
133             log_fatal('Incorrect number of arguments') if @_ != 3;
134             if ( $attr_name eq 'id' ) {
135             if ( not defined $attr_value or $attr_value eq '' ) {
136             log_fatal 'Setting undefined or empty ID is not allowed';
137             }
138             $self->get_document->index_node_by_id( $attr_value, $self );
139             }
140             elsif ( ref($attr_value) eq 'ARRAY' ) {
141             $attr_value = Treex::PML::List->new( @{$attr_value} );
142             }
143              
144             if ($attr_name =~ /\.rf$/){
145             my $document = $self->get_document();
146              
147             # Delete previous back references
148             my $old_value = $self->get_attr($attr_name);
149             if ($old_value) {
150             if ( ref $old_value eq 'Treex::PML::List' && @$old_value ) {
151             $document->remove_backref( $attr_name, $self->id, $old_value );
152             }
153             else {
154             $document->remove_backref( $attr_name, $self->id, [$old_value] );
155             }
156             }
157              
158             # Set new back references
159             my $ids = ref($attr_value) eq 'Treex::PML::List' ? $attr_value : [$attr_value];
160             $document->index_backref( $attr_name, $self->id, $ids );
161             }
162             elsif ($attr_name eq 'alignment'){
163             my $document = $self->get_document();
164             if ($self->{alignment}){
165             my @old_ids = map { $_->{'counterpart.rf'} } @{$self->{alignment}};
166             $document->remove_backref( 'alignment', $self->id, \@old_ids );
167             }
168             if ($attr_value && @$attr_value){
169             my @new_ids = map { $_->{'counterpart.rf'} } @$attr_value;
170             $document->index_backref( $attr_name, $self->id, \@new_ids );
171             }
172             }
173              
174             # TODO: most attributes are set by Moose setters,
175             # e.g. $anode->set_form("Hi") does not call set_attr.
176             # We would need to redefine all the setter to fill wild->{edited_by}.
177             if ($LOG_EDITS){
178             my $signature = $self->wild->{edited_by};
179             if ($signature) {$signature .= "\n";}
180             else {$signature = '';}
181             my $a_value = $attr_value // 'undef';
182             $signature .= "$attr_name=$a_value ". $self->_caller_signature();
183             $self->wild->{edited_by} = $signature;
184             }
185              
186             #simple attributes can be accessed directly
187             return $self->{$attr_name} = $attr_value if $attr_name =~ /^[\w\.]+$/ || $attr_name eq '#name';
188             log_fatal "Attribute '$attr_name' contains strange symbols."
189             . " No XPath like constructs (e.g. 'a/aux.rf[3]') are allowed."
190             if $attr_name =~ /[^-\w\/.]/;
191              
192             my $val = $self;
193             my @steps = split /\//, $attr_name;
194             while (1) {
195             my $step = shift @steps;
196             if (@steps) {
197             if ( !defined( $val->{$step} ) ) {
198             $val->{$step} = Treex::PML::Factory->createStructure();
199             }
200             $val = $val->{$step};
201             }
202             else {
203             return $val->{$step} = $attr_value;
204             }
205             }
206             return;
207             }
208              
209             sub get_deref_attr {
210             my ( $self, $attr_name ) = @_;
211             log_fatal('Incorrect number of arguments') if @_ != 2;
212             my $attr_value = $self->get_attr($attr_name);
213              
214             return if !$attr_value;
215             my $document = $self->get_document();
216             return [ map { $document->get_node_by_id($_) } @{$attr_value} ]
217             if ref($attr_value) eq 'Treex::PML::List';
218             return $document->get_node_by_id($attr_value);
219             }
220              
221             sub set_deref_attr {
222             my ( $self, $attr_name, $attr_value ) = @_;
223             log_fatal('Incorrect number of arguments') if @_ != 3;
224              
225             # If $attr_value is an array of nodes
226             if ( ref($attr_value) eq 'ARRAY' ) {
227             my @list = map { $_->id } @{$attr_value};
228             $attr_value = Treex::PML::List->new(@list);
229             }
230              
231             # If $attr_value is just one node
232             else {
233             $attr_value = $attr_value->id;
234             }
235              
236             # Set the new reference(s)
237             $self->set_attr( $attr_name, $attr_value );
238             return;
239             }
240              
241             sub get_bundle {
242             log_fatal 'Incorrect number of arguments' if @_ != 1;
243             my $self = shift;
244             return $self->get_zone->get_bundle;
245             }
246              
247             # reference to embedding zone is stored only with tree root, not with nodes
248             sub get_zone {
249             log_fatal 'Incorrect number of arguments' if @_ != 1;
250             my $self = shift;
251             my $zone;
252             if ( $self->is_root ) {
253             $zone = $self->_get_zone;
254             }
255             else {
256             $zone = $self->get_root->_get_zone; ## no critic (ProtectPrivateSubs)
257             }
258              
259             log_fatal "a node (" . $self->id . ") can't reveal its zone" if !$zone;
260             return $zone;
261              
262             }
263              
264             sub remove {
265             my ($self, $arg_ref) = @_;
266             if ( $self->is_root ) {
267             log_fatal 'Tree root cannot be removed using $root->remove().'
268             . ' Use $zone->remove_tree($layer) instead';
269             }
270             my $root = $self->get_root();
271             my $document = $self->get_document();
272            
273             my @children = $self->get_children();
274             if (@children){
275             my $what_to_do = 'remove';
276             if ($arg_ref && $arg_ref->{children}){
277             $what_to_do = $arg_ref->{children};
278             }
279             if ($what_to_do =~ /^rehang/){
280             foreach my $child (@children){
281             $child->set_parent($self->get_parent);
282             }
283             }
284             if ($what_to_do =~ /warn$/){
285             log_warn $self->get_address . " is being removed by remove({children=>$what_to_do}), but it has (unexpected) children";
286             }
287             }
288              
289             # Remove the subtree from the document's indexing table
290             my @to_remove = ( $self, $self->get_descendants );
291             foreach my $node ( @to_remove) {
292             if ( defined $node->id ) {
293             $document->_remove_references_to_node( $node );
294             $document->index_node_by_id( $node->id, undef );
295             }
296             }
297              
298             # Disconnect the node from its parent (& siblings) and delete all attributes
299             # It actually does: $self->cut(); undef %$_ for ($self->descendants(), $self);
300             $self->destroy;
301              
302             # TODO: order normalizing can be done in a more efficient way
303             # (update just the following ords)
304             $root->_normalize_node_ordering();
305              
306             # By reblessing we make sure that
307             # all methods called on removed nodes will result in fatal errors.
308             foreach my $node (@to_remove){
309             bless $node, 'Treex::Core::Node::Deleted';
310             }
311             return;
312             }
313              
314             # Return all nodes that have a reference of the given type (e.g. 'alignment', 'a/lex.rf') to this node
315             sub get_referencing_nodes {
316             my ( $self, $type, $lang, $sel ) = @_;
317             my $doc = $self->get_document;
318             my $refs = $doc->get_references_to_id( $self->id );
319             return if ( !$refs || !$refs->{$type} );
320             if ((defined $lang) && (defined $sel)) {
321             my @ref_filtered_by_tree;
322             if ($sel eq q() ) {
323             @ref_filtered_by_tree = grep { /(a|t)\_tree\-$lang\-.+/; }@{ $refs->{$type} };
324             }
325             else {
326             @ref_filtered_by_tree = grep { /(a|t)\_tree\-$lang\_$sel\-.+/; }@{ $refs->{$type} };
327             }
328             return map { $doc->get_node_by_id($_) } @ref_filtered_by_tree;
329             }
330             return map { $doc->get_node_by_id($_) } @{ $refs->{$type} };
331             }
332              
333             # Remove a reference of the given type to the given node. This will not remove a reverse reference from document
334             # index, since it is itself called when removing reverse references; use the API methods for the individual
335             # references if you want to keep reverse references up-to-date.
336             sub remove_reference {
337             my ( $self, $type, $id ) = @_;
338              
339             if ( $type eq 'alignment' ) { # handle alignment links separately
340              
341             my $links = $self->get_attr('alignment');
342              
343             if ($links) {
344             $self->set_attr( 'alignment', [ grep { $_->{'counterpart.rf'} ne $id } @{$links} ] );
345             }
346             }
347             else {
348             my $attr = $self->get_attr($type);
349             log_fatal "undefined attr $type (id=$id)" if !defined $attr;
350              
351             if ( $attr eq $id || scalar( @{$attr} ) <= 1 ) { # single-value attributes
352             $self->set_attr( $type, undef );
353             }
354             else {
355             $attr->delete_value($id); # TODO : will it be always a Treex::PML::List? Looks like it works.
356             }
357             }
358             return;
359             }
360              
361              
362             sub fix_pml_type {
363             log_fatal 'Incorrect number of arguments' if @_ != 1;
364             my $self = shift;
365             if ( not $self->type() ) {
366             my $type = $self->get_pml_type_name();
367             if ( not $type ) {
368             log_warn "No PML type recognized for node $self";
369             return;
370             }
371             my $fs_file = $self->get_document()->_pmldoc;
372             $self->set_type_by_name( $fs_file->metaData('schema'), $type );
373             }
374             return;
375             }
376              
377             sub get_pml_type_name {
378             log_fatal 'Incorrect number of arguments' if @_ != 1;
379             my $self = shift;
380             return;
381             }
382              
383             sub get_layer {
384             log_fatal 'Incorrect number of arguments' if @_ != 1;
385             my $self = shift;
386             if ( ref($self) =~ /Node::(\w)$/ ) {
387             return lc($1);
388             }
389             else {
390             log_fatal "Cannot recognize node's layer: $self";
391             }
392             }
393              
394             sub language {
395             log_fatal 'Incorrect number of arguments' if @_ != 1;
396             my $self = shift;
397             return $self->get_zone()->language;
398             }
399              
400             sub selector {
401             log_fatal 'Incorrect number of arguments' if @_ != 1;
402             my $self = shift;
403             return $self->get_zone()->selector;
404             }
405              
406             sub create_child {
407             my $self = shift;
408              
409             #NOT VALIDATED INTENTIONALLY - passing args to to new (and it's also black magic, so I'm not touching it)
410              
411             # TODO:
412             #my $new_node = ( ref $self )->new(@_);
413             # Previous line is very strange and causes errors which are hard to debug.
414             # Magically, it works on UFAL machines, but nowhere else - I don't know why.
415             # Substituting the hash by hashref is a workaround,
416             # but the black magic is still there.
417             my $arg_ref;
418             if ( scalar @_ == 1 && ref $_[0] eq 'HASH' ) {
419             $arg_ref = $_[0];
420             }
421             elsif ( @_ % 2 ) {
422             log_fatal "Odd number of elements for create_child";
423             }
424             else {
425             $arg_ref = {@_};
426             }
427              
428             # Structured attributes (e.g. morphcat/pos) must be handled separately
429             # TODO: And also attributes which don't have accessors (those are not Moose attributes).
430             # Note: mlayer_pos was not added to Treex::Core::Node::T because it goes
431             # against the "tectogrammatical ideology" and we use it as a temporary hack.
432             my %structured_attrs;
433             foreach my $attr ( keys %{$arg_ref} ) {
434             if ( $attr =~ m{/} || $attr eq 'mlayer_pos' || $attr eq '#name') {
435             $structured_attrs{$attr} = delete $arg_ref->{$attr};
436             }
437             }
438              
439             $arg_ref->{_called_from_core_} = 1;
440             my $new_node = ( ref $self )->new($arg_ref);
441             $new_node->set_parent($self);
442              
443             my $new_id = $self->generate_new_id();
444             $new_node->set_id($new_id);
445              
446             foreach my $attr ( keys %structured_attrs ) {
447             $new_node->set_attr( $attr, $structured_attrs{$attr} );
448             }
449              
450             # my $type = $new_node->get_pml_type_name();
451             # return $new_node if !defined $type;
452             # my $fs_file = $self->get_bundle->get_document()->_pmldoc;
453             # $self->set_type_by_name( $fs_file->metaData('schema'), $type );
454              
455             $new_node->fix_pml_type();
456              
457             # Remember which module (Treex block) and line number in its source code are responsible for creating this node.
458             if ($LOG_NEW){
459             $new_node->wild->{created_by} = $self->_caller_signature();
460             }
461              
462             return $new_node;
463             }
464              
465             #************************************
466             #---- TREE NAVIGATION ------
467              
468             sub get_document {
469             log_fatal 'Incorrect number of arguments' if @_ != 1;
470             my $self = shift;
471             my $bundle = $self->get_bundle();
472             log_fatal('Cannot call get_document on a node which is in no bundle') if !defined $bundle;
473             return $bundle->get_document();
474             }
475              
476             sub get_root {
477             log_fatal 'Incorrect number of arguments' if @_ != 1;
478             my $self = shift;
479             return $self->root();
480             }
481              
482             sub is_root {
483             log_fatal 'Incorrect number of arguments' if @_ != 1;
484             my $self = shift;
485             return !$self->parent;
486             }
487              
488             sub is_leaf {
489             log_fatal 'Incorrect number of arguments' if @_ != 1;
490             my $self = shift;
491             return not $self->firstson;
492             }
493              
494             sub get_parent {
495             log_fatal 'Incorrect number of arguments' if @_ != 1;
496             my $self = shift;
497             return $self->parent;
498             }
499              
500             sub set_parent {
501             my $self = shift;
502             my ($parent) = pos_validated_list(
503             \@_,
504             { isa => 'Treex::Core::Node' },
505             );
506              
507             # TODO check for this (but set_parent is called also from create_child)
508             #if ($self->get_document() != $parent->get_document()) {
509             # log_fatal("Cannot move a node from one document to another");
510             #}
511              
512             # We cannot detach a node by setting an undefined parent. The if statement below will die.
513             # Let's inform the user where the bad call is.
514             log_fatal( 'Cannot attach the node ' . $self->id . ' to an undefined parent' ) if ( !defined($parent) );
515             if ( $self == $parent || $CHECK_FOR_CYCLES && $parent->is_descendant_of($self) ) {
516             my $id = $self->id;
517             my $p_id = $parent->id;
518             log_fatal("Attempt to set parent of $id to the node $p_id, which would lead to a cycle.");
519             }
520              
521             # TODO: Too much FSlib (aka Treex::PML) here
522             $self->cut();
523             my $fsfile = $parent->get_document()->_pmldoc;
524             my @fschildren = $parent->children();
525             if (@fschildren) {
526             Treex::PML::PasteAfter( $self, $fschildren[-1] );
527             }
528             else {
529             Treex::PML::Paste( $self, $parent, $fsfile->FS() );
530             }
531              
532             return;
533             }
534              
535             sub _check_switches {
536              
537             #This method may be replaced by subtype and checked as parameter
538             my $self = shift;
539             my ($arg_ref) = pos_validated_list(
540             \@_,
541             { isa => 'Maybe[HashRef]' },
542             );
543              
544             # Check for role Ordered
545             log_fatal('This type of node does not support ordering')
546             if (
547             ( $arg_ref->{ordered} || any { $arg_ref->{ $_ . '_only' } } qw(first last preceding following) )
548             &&
549             !$self->does('Treex::Core::Node::Ordered')
550             );
551              
552             # Check switches for not allowed combinations
553             log_fatal('Specified both preceding_only and following_only.')
554             if $arg_ref->{preceding_only} && $arg_ref->{following_only};
555             log_fatal('Specified both first_only and last_only.')
556             if $arg_ref->{first_only} && $arg_ref->{last_only};
557              
558             # Check for explicit "ordered" when not needed (possible typo)
559             log_warn('Specifying (first|last|preceding|following)_only implies ordered.')
560             if $arg_ref->{ordered}
561             && any { $arg_ref->{ $_ . '_only' } } qw(first last preceding following);
562              
563             # Check for unknown switches
564             my $unknown = first { $_ !~ $_SWITCHES_REGEX } keys %{$arg_ref};
565             log_warn("Unknown switch $unknown") if defined $unknown;
566              
567             return;
568             }
569              
570             # Shared processing of switches: ordered, (preceding|following|first|last)_only
571             # for subs get_children, get_descendants and get_siblings.
572             # This is quite an uneffective implementation in case of e.g. first_only
573             sub _process_switches {
574             my $self = shift;
575             my ( $arg_ref, @nodes ) = pos_validated_list(
576             \@_,
577             { isa => 'Maybe[HashRef]' },
578             MX_PARAMS_VALIDATE_ALLOW_EXTRA => 1,
579             );
580              
581             # Check for unknown switches and not allowed combinations
582             $self->_check_switches($arg_ref);
583              
584             # Add this node if add_self
585             if ( $arg_ref->{add_self} ) {
586             push @nodes, $self;
587             }
588              
589             # Sort nodes if needed
590             if (( $arg_ref->{ordered} || any { $arg_ref->{ $_ . '_only' } } qw(first last preceding following) )
591             && @nodes && defined $nodes[0]->ord
592             )
593             {
594             @nodes = sort { $a->ord() <=> $b->ord() } @nodes;
595             }
596              
597             # Leave preceding/following only if needed
598             if ( $arg_ref->{preceding_only} ) {
599             @nodes = grep { $_->ord() <= $self->ord } @nodes;
600             }
601             elsif ( $arg_ref->{following_only} ) {
602             @nodes = grep { $_->ord() >= $self->ord } @nodes;
603             }
604              
605             # first_only / last_only
606             return $nodes[0] if $arg_ref->{first_only};
607             return $nodes[-1] if $arg_ref->{last_only};
608             return @nodes;
609             }
610              
611             sub get_children {
612             my $self = shift;
613             my ($arg_ref) = pos_validated_list(
614             \@_,
615             { isa => 'Maybe[HashRef]', optional => 1 },
616             );
617              
618             my @children = $self->children();
619             return @children if !$arg_ref;
620             return $self->_process_switches( $arg_ref, @children );
621             }
622              
623             sub get_descendants {
624             my $self = shift;
625             my ($arg_ref) = pos_validated_list(
626             \@_,
627             { isa => 'Maybe[HashRef]', optional => 1 },
628             );
629              
630             my @descendants;
631             if ( $arg_ref && $arg_ref->{except} ) {
632             my $except_node = delete $arg_ref->{except};
633             return () if $self == $except_node;
634             @descendants = map {
635             $_->get_descendants( { except => $except_node, add_self => 1 } )
636             } $self->get_children();
637             }
638             else {
639             @descendants = $self->descendants();
640             }
641             return @descendants if !$arg_ref;
642             return $self->_process_switches( $arg_ref, @descendants );
643             }
644              
645             sub get_siblings {
646             my $self = shift;
647             my ($arg_ref) = pos_validated_list(
648             \@_,
649             { isa => 'Maybe[HashRef]', optional => 1 },
650             );
651             my $parent = $self->get_parent();
652             return () if !$parent;
653             my @siblings = grep { $_ ne $self } $parent->get_children();
654             return @siblings if !$arg_ref;
655             return $self->_process_switches( $arg_ref, @siblings );
656             }
657              
658             sub get_left_neighbor { return $_[0]->get_siblings( { preceding_only => 1, last_only => 1 } ); }
659             sub get_right_neighbor { return $_[0]->get_siblings( { following_only => 1, first_only => 1 } ); }
660              
661             sub is_descendant_of {
662             my $self = shift;
663             my ($another_node) = pos_validated_list(
664             \@_,
665             { isa => 'Treex::Core::Node' },
666             );
667              
668             my $parent = $self->get_parent();
669             while ($parent) {
670             return 1 if $parent == $another_node;
671             $parent = $parent->get_parent();
672             }
673             return 0;
674             }
675             sub dominates {
676             my $self = shift;
677             my $another_node = shift;
678             return $another_node->is_descendant_of($self);
679             }
680              
681             #----------- alignment -------------
682              
683             sub get_aligned_nodes {
684             my ($self) = @_;
685             my $links_rf = $self->get_attr('alignment');
686             if ($links_rf) {
687             my $document = $self->get_document;
688             my @nodes = map { $document->get_node_by_id( $_->{'counterpart.rf'} ) } @$links_rf;
689             my @types = map { $_->{'type'} } @$links_rf;
690             return ( \@nodes, \@types );
691             }
692             return ( undef, undef );
693             }
694              
695             sub get_aligned_nodes_by_tree {
696             my ($self, $lang, $selector) = @_;
697             my @nodes = ();
698             my @types = ();
699             my $links_rf = $self->get_attr('alignment');
700             if ($links_rf) {
701             my $document = $self->get_document;
702             foreach my $l_rf (@{$links_rf}) {
703             if ($l_rf->{'counterpart.rf'} =~ /^(a|t)_tree-$lang(_$selector)?-.+$/) {
704             my $n = $document->get_node_by_id( $l_rf->{'counterpart.rf'} );
705             my $t = $l_rf->{'type'};
706             push @nodes, $n;
707             push @types, $t;
708             }
709             }
710             return ( \@nodes, \@types ) if scalar(@nodes) > 0 ;
711             }
712             return ( undef, undef );
713             }
714              
715             sub get_aligned_nodes_of_type {
716             my ( $self, $type_regex, $lang, $selector ) = @_;
717             my @nodes;
718             my ( $n_rf, $t_rf );
719             if ((defined $lang) && (defined $selector)) {
720             ( $n_rf, $t_rf ) = $self->get_aligned_nodes_by_tree($lang, $selector);
721             }
722             else {
723             ( $n_rf, $t_rf ) = $self->get_aligned_nodes();
724             }
725             return if !$n_rf;
726             my $iterator = List::MoreUtils::each_arrayref( $n_rf, $t_rf );
727             while ( my ( $node, $type ) = $iterator->() ) {
728             if ( $type =~ /$type_regex/ ) {
729             push @nodes, $node;
730             }
731             }
732             return @nodes;
733             }
734              
735             sub is_aligned_to {
736             my ( $self, $node, $type ) = @_;
737             log_fatal 'Incorrect number of parameters' if @_ != 3;
738             return ((any { $_ eq $node } $self->get_aligned_nodes_of_type( $type )) ? 1 : 0);
739             }
740              
741             sub delete_aligned_node {
742             my ( $self, $node, $type ) = @_;
743             my $links_rf = $self->get_attr('alignment');
744             my @links = ();
745             if ($links_rf) {
746             @links = grep {
747             $_->{'counterpart.rf'} ne $node->id
748             || ( defined($type) && defined( $_->{'type'} ) && $_->{'type'} ne $type )
749             }
750             @$links_rf;
751             }
752             $self->set_attr( 'alignment', \@links );
753             return;
754             }
755              
756             sub add_aligned_node {
757             my ( $self, $node, $type ) = @_;
758             my $links_rf = $self->get_attr('alignment');
759             my %new_link = ( 'counterpart.rf' => $node->id, 'type' => $type // ''); #/ so we have no undefs
760             push( @$links_rf, \%new_link );
761             $self->set_attr( 'alignment', $links_rf );
762             return;
763             }
764              
765             # remove invalid alignment links (leading to unindexed nodes)
766             sub update_aligned_nodes {
767             my ($self) = @_;
768             my $doc = $self->get_document();
769             my $links_rf = $self->get_attr('alignment');
770             my @new_links;
771              
772             foreach my $link ( @{$links_rf} ) {
773             push @new_links, $link if ( $doc->id_is_indexed( $link->{'counterpart.rf'} ) );
774             }
775             $self->set_attr( 'alignment', \@new_links );
776             return;
777             }
778              
779             #************************************
780             #---- OTHER ------
781              
782             sub get_depth {
783             log_fatal 'Incorrect number of arguments' if @_ != 1;
784             my $self = shift;
785             my $depth = 0;
786             while ( $self = $self->get_parent() ) {
787             $depth++;
788             }
789             return $depth;
790             }
791              
792             # This is called from $node->remove()
793             # so it must be defined in this class,
794             # but it is overriden in Treex::Core::Node::Ordered.
795             sub _normalize_node_ordering {
796             }
797              
798             sub get_address {
799             log_fatal 'Incorrect number of arguments' if @_ != 1;
800             my $self = shift;
801             my $id = $self->id;
802             my $bundle = $self->get_bundle();
803             my $doc = $bundle->get_document();
804             my $file = $doc->loaded_from || ( $doc->full_filename . '.treex' );
805             my $position = $bundle->get_position() + 1;
806              
807             #my $filename = Cwd::abs_path($file);
808             return "$file##$position.$id";
809             }
810              
811             # Empty DESTROY method is a hack to get rid of the "Deep recursion warning"
812             # in Treex::PML::Node::DESTROY and MooseX::NonMoose::Meta::Role::Class::_check_superclass_destructor.
813             # Without this hack, you get the warning after creating a node with 99 or more children.
814             # Deep recursion on subroutine "Class::MOP::Method::execute" at .../5.12.2/MooseX/NonMoose/Meta/Role/Class.pm line 183.
815             sub DESTROY {
816             }
817              
818             #*************************************
819             #---- DEPRECATED & QUESTIONABLE ------
820              
821             sub generate_new_id { #TODO move to Core::Document?
822             log_fatal 'Incorrect number of arguments' if @_ != 1;
823             my $self = shift;
824             my $doc = $self->get_document;
825              
826             my $latest_node_number = $doc->_latest_node_number;
827              
828             my $new_id;
829              
830             #$self->get_root->id =~ /(.+)root/;
831             #my $id_base = $1 || "";
832             my $id_base;
833             if ( $self->get_root->id =~ /(.+)root/ ) {
834             $id_base = $1;
835             }
836             else {
837             $id_base = q();
838             }
839              
840             while (1) {
841             $latest_node_number++;
842             $new_id = "${id_base}n$latest_node_number";
843             last if !$doc->id_is_indexed($new_id);
844              
845             }
846              
847             $doc->_set_latest_node_number($latest_node_number);
848              
849             return $new_id;
850             }
851              
852             sub add_to_listattr {
853             my $self = shift;
854             my ( $attr_name, $attr_value ) = pos_validated_list(
855             \@_,
856             { isa => 'Str' },
857             { isa => 'Any' },
858             );
859              
860             my $list = $self->attr($attr_name);
861             log_fatal("Attribute $attr_name is not a list!")
862             if !defined $list || ref($list) ne 'Treex::PML::List';
863             my @new_list = @{$list};
864             if ( ref($attr_value) eq 'ARRAY' ) {
865             push @new_list, @{$attr_value};
866             }
867             else {
868             push @new_list, $attr_value;
869             }
870             return $self->set_attr( $attr_name, Treex::PML::List->new(@new_list) );
871             }
872              
873             # Get more attributes at once
874             sub get_attrs {
875             my $self = shift;
876             my @attr_names = pos_validated_list(
877             \@_,
878             { isa => 'Any' }, #at least one parameter
879             MX_PARAMS_VALIDATE_ALLOW_EXTRA => 1,
880             );
881              
882             my @attr_values;
883             if ( ref $attr_names[-1] ) {
884             my $arg_ref = pop @attr_names;
885             my $change_undefs_to = $arg_ref->{undefs};
886             @attr_values = map {
887             defined $self->get_attr($_) ? $self->get_attr($_) : $change_undefs_to
888             } @attr_names;
889             }
890             else {
891             @attr_values = map { $self->get_attr($_) } @attr_names;
892             }
893              
894             return @attr_values;
895             }
896              
897             # Return all attributes of the given node (sub)type that contain references
898             sub _get_reference_attrs {
899             my ($self) = @_;
900             return ();
901             }
902              
903             # Return IDs of all nodes to which there are reference links from this node (must be overridden in
904             # the respective node types)
905             sub _get_referenced_ids {
906             my ($self) = @_;
907             my $ret = {};
908              
909             # handle alignment separately
910             my $links_rf = $self->get_attr('alignment');
911             $ret->{alignment} = [ map { $_->{'counterpart.rf'} } @{$links_rf} ] if ($links_rf);
912              
913             # all other references
914             foreach my $ref_attr ( $self->_get_reference_attrs() ) {
915             my $val = $self->get_attr($ref_attr) or next;
916             if ( !ref $val ) { # single-valued
917             $ret->{$ref_attr} = [$val];
918             }
919             else {
920             $ret->{$ref_attr} = $val;
921             }
922             }
923             return $ret;
924             }
925              
926              
927             # ---------------------
928              
929             # changing the functionality of Treex::PML::Node's following() so that it traverses all
930             # nodes in all trees in all zones (needed for search in TrEd)
931              
932             sub following {
933             my ( $self ) = @_;
934              
935             my $pml_following = Treex::PML::Node::following(@_);
936              
937             if ( $pml_following ) {
938             return $pml_following;
939             }
940              
941             else {
942             my $bundle = ( ref($self) eq 'Treex::Core::Bundle' ) ? $self : $self->get_bundle;
943              
944             my @all_trees = map {
945             ref($_) ne 'Treex::PML::Struct'
946             ? $_->get_all_trees
947             : ()
948             } $bundle->get_all_zones;
949              
950             if ( ref($self) eq 'Treex::Core::Bundle' ) {
951             return $all_trees[0];
952             }
953              
954             else {
955             my $my_root = $self->get_root;
956             foreach my $index ( 0..$#all_trees ) {
957             if ( $all_trees[$index] eq $my_root ) {
958             return $all_trees[$index+1];
959             }
960             }
961             log_fatal "Node belongs to no tree: this should never happen";
962             }
963             }
964             }
965              
966             # This is copied from Treex::PML::Node.
967             # Using Treex::PML::Node::following is faster than recursion
968             # and it does not cause "deep recursion" warnings.
969             sub descendants {
970             my $self = $_[0];
971             my @kin = ();
972             my $desc = $self->Treex::PML::Node::following($self);
973             while ($desc) {
974             push @kin, $desc;
975             $desc = $desc->Treex::PML::Node::following($self);
976             }
977             return @kin;
978             }
979              
980             # TODO: How to do this in an elegant way?
981             # Unless we find a better way, we must disable two perlcritics
982             package Treex::Core::Node::Removed; ## no critic (ProhibitMultiplePackages)
983             $Treex::Core::Node::Removed::VERSION = '2.20150928';
984             use Treex::Core::Log;
985              
986             sub AUTOLOAD { ## no critic (ProhibitAutoloading)
987             our $AUTOLOAD;
988             if ( $AUTOLOAD !~ /DESTROY$/ ) {
989             log_fatal("You cannot call any methods on removed nodes, but have called $AUTOLOAD");
990             }
991             }
992              
993             package Treex::Core::Node; ## no critic (ProhibitMultiplePackages)
994              
995             __PACKAGE__->meta->make_immutable;
996              
997             1;
998              
999             __END__
1000              
1001             ##-- begin proposal
1002             # Example usage:
1003             # Treex::Core::Node::T methods get_lex_anode and get_aux_anodes could use:
1004             # my $a_lex = $t_node->get_r_attr('a/lex.rf'); # returns the node or undef
1005             # my @a_aux = $t_node->get_r_attr('a/aux.rf'); # returns the nodes or ()
1006             sub get_r_attr {
1007             my ( $self, $attr_name ) = @_;
1008             log_fatal('Incorrect number of arguments') if @_ != 2;
1009             my $attr_value = $self->get_attr($attr_name);
1010              
1011             return if !$attr_value;
1012             my $document = $self->get_document();
1013             if (wantarray) {
1014             log_fatal("Attribute '$attr_name' is not a list, but get_r_attr() called in a list context.")
1015             if ref($attr_value) ne 'Treex::PML::List';
1016             return map { $document->get_node_by_id($_) } @{$attr_value};
1017             }
1018              
1019             log_fatal("Attribute $attr_name is a list, but get_r_attr() not called in a list context.")
1020             if ref($attr_value) eq 'Treex::PML::List';
1021             return $document->get_node_by_id($attr_value);
1022             }
1023              
1024             # Example usage:
1025             # $t_node->set_r_attr('a/lex.rf', $a_lex);
1026             # $t_node->set_r_attr('a/aux.rf', @a_aux);
1027             sub set_r_attr {
1028             my ( $self, $attr_name, @attr_values ) = @_;
1029             log_fatal('Incorrect number of arguments') if @_ < 3;
1030             my $fs = $self;
1031              
1032             # TODO $fs->type nefunguje - asi protoze se v konstruktorech nenastavuje typ
1033             if ( $fs->type($attr_name) eq 'Treex::PML::List' ) {
1034             my @list = map { $_->id } @attr_values;
1035              
1036             # TODO: overriden Node::N::set_attr is bypassed by this call
1037             return $fs->set_attr( $attr_name, Treex::PML::List->new(@list) );
1038             }
1039             log_fatal("Attribute '$attr_name' is not a list, but set_r_attr() called with @attr_values values.")
1040             if @attr_values > 1;
1041              
1042             # TODO: overriden Node::N::set_attr is bypassed by this call
1043             return $fs->set_attr( $attr_name, $attr_values[0]->id );
1044             }
1045              
1046              
1047              
1048             =for Pod::Coverage BUILD
1049              
1050              
1051             =encoding utf-8
1052              
1053             =head1 NAME
1054              
1055             Treex::Core::Node - smallest unit that holds information in Treex
1056              
1057             =head1 VERSION
1058              
1059             version 2.20150928
1060              
1061             =head1 DESCRIPTION
1062              
1063             This class represents a Treex node.
1064             Treex trees (contained in bundles) are formed by nodes and edges.
1065             Attributes can be attached only to nodes. Edge's attributes must
1066             be stored as the lower node's attributes.
1067             Tree's attributes must be stored as attributes of the root node.
1068              
1069             =head1 METHODS
1070              
1071             =head2 Construction
1072              
1073             =over 4
1074              
1075             =item my $new_node = $existing_node->create_child({lemma=>'house', tag=>'NN' });
1076              
1077             Creates a new node as a child of an existing node. Some of its attribute
1078             can be filled. Direct calls of node constructors (C<< ->new >>) should be avoided.
1079              
1080              
1081             =back
1082              
1083              
1084              
1085             =head2 Access to the containers
1086              
1087             =over 4
1088              
1089             =item my $bundle = $node->get_bundle();
1090              
1091             Returns the L<Treex::Core::Bundle> object in which the node's tree is contained.
1092              
1093             =item my $document = $node->get_document();
1094              
1095             Returns the L<Treex::Core::Document> object in which the node's tree is contained.
1096              
1097             =item get_layer
1098              
1099             Return the layer of this node (I<a>, I<t>, I<n> or I<p>).
1100              
1101             =item get_zone
1102              
1103             Return the zone (L<Treex::Core::BundleZone>) to which this node
1104             (and the whole tree) belongs.
1105              
1106             =item $lang_code = $node->language
1107              
1108             shortcut for C<< $lang_code = $node->get_zone()->language >>
1109              
1110             =item $selector = $node->selector
1111              
1112             shortcut for C<< $selector = $node->get_zone()->selector >>
1113              
1114             =back
1115              
1116              
1117             =head2 Access to attributes
1118              
1119             =over 4
1120              
1121             =item my $value = $node->get_attr($name);
1122              
1123             Returns the value of the node attribute of the given name.
1124              
1125             =item my $node->set_attr($name,$value);
1126              
1127             Sets the given attribute of the node with the given value.
1128             If the attribute name is C<id>, then the document's indexing table
1129             is updated. If value of the type C<List> is to be filled,
1130             then C<$value> must be a reference to the array of values.
1131              
1132             =item my $node2 = $node1->get_deref_attr($name);
1133              
1134             If value of the given attribute is reference (or list of references),
1135             it returns the appropriate node (or a reference to the list of nodes).
1136              
1137             =item my $node1->set_deref_attr($name, $node2);
1138              
1139             Sets the given attribute with C<id> (list of C<id>s) of the given node (list of nodes).
1140              
1141             =item my $node->add_to_listattr($name, $value);
1142              
1143             If the given attribute is list, the given value is appended to it.
1144              
1145             =item my $node->get_attrs(qw(name_of_attr1 name_of_attr2 ...));
1146              
1147             Get more attributes at once.
1148             If the last argument is C<{undefs=E<gt>$value}>, all undefs are substituted
1149             by a C<$value> (typically the value is an empty string).
1150              
1151             =back
1152              
1153              
1154              
1155              
1156             =head2 Access to tree topology
1157              
1158             =over 4
1159              
1160             =item my $parent_node = $node->get_parent();
1161              
1162             Returns the parent node, or C<undef> if there is none (if C<$node> itself is the root)
1163              
1164             =item $node->set_parent($parent_node);
1165              
1166             Makes C<$node> a child of C<$parent_node>.
1167              
1168             =item $node->remove({children=>remove});
1169              
1170             Deletes a node.
1171             Node identifier is removed from the document indexing table.
1172             The removed node cannot be further used.
1173              
1174             Optional argument C<children> in C<$arg_ref> can specify
1175             what to do with children (and all descendants,
1176             i.e. the subtree rooted by the given node) if present:
1177             C<remove>, C<remove_warn>, C<rehang>, C<rehang_warn>.
1178             The default is C<remove> -- remove recursively.
1179             C<rehang> means reattach the children of C<$node> to the parent of C<$node>.
1180             The C<_warn> variants will in addition produce a warning.
1181              
1182             =item my $root_node = $node->get_root();
1183              
1184             Returns the root of the node's tree.
1185              
1186             =item $node->is_root();
1187              
1188             Returns C<true> if the node has no parent.
1189              
1190             =item $node->is_leaf();
1191              
1192             Returns C<true> if the node has no children.
1193              
1194             =item $node1->is_descendant_of($node2);
1195              
1196             Tests whether C<$node1> is among transitive descendants of C<$node2>;
1197              
1198             =back
1199              
1200             Next three methods (for access to children / descendants / siblings)
1201             have an optional argument C<$arg_ref> for specifying switches.
1202             By adding some switches, you can modify the behavior of these methods.
1203             See L<"Switches"> for examples.
1204              
1205             =over
1206              
1207             =item my @child_nodes = $node->get_children($arg_ref);
1208              
1209             Returns an array of child nodes.
1210              
1211             =item my @descendant_nodes = $node->get_descendants($arg_ref);
1212              
1213             Returns an array of descendant nodes ('transitive children').
1214              
1215             =item my @sibling_nodes = $node->get_siblings($arg_ref);
1216              
1217             Returns an array of nodes sharing the parent with the current node.
1218              
1219             =back
1220              
1221             =head3 Switches
1222              
1223             Currently there are 6 switches:
1224              
1225             =over
1226              
1227             =item * ordered
1228              
1229             =item * preceding_only, following_only
1230              
1231             =item * first_only, last_only
1232              
1233             =item * add_self
1234              
1235             =back
1236              
1237             =head4 Examples of usage
1238              
1239             Names of variables in the examples suppose a language with left-to-right script.
1240              
1241             my @ordered_descendants = $node->get_descendants({ordered=>1});
1242             my @self_and_left_children = $node->get_children({preceding_only=>1, add_self=>1});
1243             my @ordered_self_and_children = $node->get_children({ordered=>1, add_self=>1});
1244             my $leftmost_child = $node->get_children({first_only=>1});
1245             my @ordered_siblings = $node->get_siblings({ordered=>1});
1246             my $left_neighbor = $node->get_siblings({preceding_only=>1, last_only=>1});
1247             my $right_neighbor = $node->get_siblings({following_only=>1, first_only=>1});
1248             my $leftmost_sibling_or_self = $node->get_siblings({add_self=>1, first_only=>1});
1249              
1250             =head4 Restrictions
1251              
1252             =over
1253              
1254             =item *
1255              
1256             C<first_only> and C<last_only> switches makes the method return just one item -
1257             a scalar, even if combined with the C<add_self> switch.
1258              
1259             =item *
1260              
1261             Specifying C<(first|last|preceding|following)_only> implies C<ordered>,
1262             so explicit addition of C<ordered> gives a warning.
1263              
1264             =item *
1265              
1266             Specifying both C<preceding_only> and C<following_only> gives an error
1267             (same for combining C<first_only> and C<last_only>).
1268              
1269             =back
1270              
1271             =head4 Shortcuts
1272              
1273             There are shortcuts for comfort of those who use B<left-to-right> scripts:
1274              
1275             =over
1276              
1277             =item my $left_neighbor_node = $node->get_left_neighbor();
1278              
1279             Returns the rightmost node from the set of left siblings (the nearest left sibling).
1280             Actually, this is shortcut for C<$node-E<gt>get_siblings({preceding_only=E<gt>1, last_only=E<gt>1})>.
1281              
1282             =item my $right_neighbor_node = $node->get_right_neighbor();
1283              
1284             Returns the leftmost node from the set of right siblings (the nearest right sibling).
1285             Actually, this is shortcut for C<$node-E<gt>get_siblings({following_only=E<gt>1, first_only=E<gt>1})>.
1286              
1287             =back
1288              
1289             =head2 PML-related methods
1290              
1291             =over
1292              
1293             =item my $type = $node->get_pml_type_name;
1294              
1295             =item $node->fix_pml_type();
1296              
1297             If a node has no PML type, then its type is detected (according
1298             to the node's location) and filled by the PML interface.
1299              
1300             =back
1301              
1302              
1303             =head2 Access to alignment
1304              
1305             =over
1306              
1307             =item $node->add_aligned_node($target, $type)
1308              
1309             Aligns $target node to $node. The prior existence of the link is not checked.
1310              
1311             =item my ($nodes_rf, $types_rf) = $node->get_aligned_nodes()
1312              
1313             Returns an array containing two array references. The first array contains the nodes aligned to this node, the second array contains types of the links.
1314              
1315             =item my @nodes = $node->get_aligned_nodes_of_type($regex_constraint_on_type)
1316              
1317             Returns a list of nodes aligned to the $node by the specified alignment type.
1318              
1319             =item $node->delete_aligned_node($target, $type)
1320              
1321             All alignments of the $target to $node are deleted, if their types equal $type.
1322              
1323             =item my $is_aligned = $node->is_aligned_to($target, $regex_constraint_on_type)
1324              
1325             Returns 1 if the nodes are aligned, 0 otherwise.
1326              
1327             =item $node->update_aligned_nodes()
1328              
1329             Removes all alignment links leading to nodes which have been deleted.
1330              
1331             =back
1332              
1333             =head2 References (alignment and other references depending on node subtype)
1334              
1335             =over
1336              
1337             =item my @refnodes = $node->get_referencing_nodes($ref_type);
1338              
1339             Returns an array of nodes referencing this node with the given reference type (e.g. 'alignment', 'a/lex.rf' etc.).
1340              
1341             =back
1342              
1343             =head2 Other methods
1344              
1345             =over 4
1346              
1347             =item $node->generate_new_id();
1348              
1349             Generate new (= so far unindexed) identifier (to be used when creating new
1350             nodes). The new identifier is derived from the identifier of the root
1351             (C<< $node->root >>), by adding suffix C<x1> (or C<x2>, if C<...x1> has already
1352             been indexed, etc.) to the root's C<id>.
1353              
1354             =item my $levels = $node->get_depth();
1355              
1356             Return the depth of the node. The root has depth = 0, its children have depth = 1 etc.
1357              
1358             =item my $address = $node->get_address();
1359              
1360             Return the node address, i.e. file name and node's position within the file,
1361             similarly to TrEd's C<FPosition()> (but the value is only returned, not printed).
1362              
1363             =item $node->equals($another_node)
1364              
1365             This is the internal implementation of overloaded C<==> operator,
1366             which checks whether C<$node == $another_node> (the object instance must be identical).
1367              
1368             =item my $string = $node->to_string()
1369              
1370             This is the internal implementation of overloaded stringification,
1371             so you can use e.g. C<print "There is a node $node.">.
1372             It returns the id (C<$node->id>), but the behavior may be overridden in subclasses.
1373             See L<overload> pragma for details about overloading operators in Perl.
1374              
1375             =back
1376              
1377              
1378             =head1 AUTHORS
1379              
1380             ZdenÄ›k Žabokrtský <zabokrtsky@ufal.mff.cuni.cz>
1381              
1382             Martin Popel <popel@ufal.mff.cuni.cz>
1383              
1384             David Mareček <marecek@ufal.mff.cuni.cz>
1385              
1386             Daniel Zeman <zeman@ufal.mff.cuni.cz>
1387              
1388             OndÅ™ej DuÅ¡ek <odusek@ufal.mff.cuni.cz>
1389              
1390             =head1 COPYRIGHT AND LICENSE
1391              
1392             Copyright © 2011-2012 by Institute of Formal and Applied Linguistics, Charles University in Prague
1393              
1394             This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.