File Coverage

lib/PMLTQ/Suggest.pm
Criterion Covered Total %
statement 163 203 80.3
branch 65 108 60.1
condition 37 70 52.8
subroutine 13 13 100.0
pod 0 7 0.0
total 278 401 69.3


line stmt bran cond sub pod time code
1             package PMLTQ::Suggest;
2             our $AUTHORITY = 'cpan:MATY';
3             $PMLTQ::Suggest::VERSION = '1.0.4';
4             # ABSTRACT: Tool for generating simple PMLTQ query based on given nodes
5              
6             =head1 NAME
7              
8             PMLTQ::Suggest - Tool for generating simple PMLTQ query based on given nodes
9              
10             =head1 DESCRIPTION
11              
12             This is an implementation of a Suggest server and a plugin for PML-TQ command-line client.
13              
14             =cut
15              
16 2     2   17 use Scalar::Util qw(weaken);
  2         4  
  2         169  
17 2     2   1222 use PMLTQ::Common qw(:all);
  2         25076  
  2         492  
18 2     2   21 use Treex::PML::Schema::Constants;
  2         5  
  2         194  
19 2     2   14 use PMLTQ::Suggest::Utils;
  2         6  
  2         16  
20              
21             sub make_pmltq {
22 8     8 0 31 my ($positions,%opts)=@_;
23 8         22 my @open_files;
24 8         26 my %cur_fsfiles; @cur_fsfiles{@open_files}=();
25             # my $keep_cur;
26 8         18 my %fsfiles;
27             my @new_fsfiles;
28 8         78 foreach my $f (map $_->[0], @$positions) {
29 15 100       87 next if exists $fsfiles{$f};
30 10         49 my $fsfile = PMLTQ::Suggest::Utils::open_file($f);
31 10         56 my @new = ($fsfile, PMLTQ::Suggest::Utils::GetSecondaryFiles($fsfile));
32 10         33 push @new_fsfiles, @new;
33 10         35 push @open_files, @new;
34 10         85 $fsfiles{$_->filename}=$_ for @new; # including $fsfile
35 10         4218 $fsfiles{$f}=$fsfile; # $f may be different from $fsfile->filename
36             }
37 8         24 my @nodes;
38 8         34 for my $pos (@$positions) {
39 15         78 my $win = { FSFile => $fsfiles{$pos->[0]} };
40 15 50 33     68 unless (PMLTQ::Suggest::Utils::apply_file_suffix($win,$pos->[1]) and $win->{currentNode}) {
41 0         0 warn "Didn't find node [@$pos, @{[%$win]}]\n";
  0         0  
42 0         0 return;
43             }
44 15         79 push @nodes, [ $win->{currentNode}, $win->{FSFile} ];
45             }
46 8 50       37 print STDERR "generating query\n" if $opts{verbose};
47 8         44 return nodes_to_pmltq(\@nodes,\%opts);
48             }
49              
50              
51              
52              
53             sub nodes_to_pmltq {
54 8     8 0 31 my ($nodes,$opts)=@_;
55 8   50     25 $opts||={};
56 8         16 my %id_member;
57 8         36 my $name = 'a';
58 8   33     35 $name++ while $opts->{reserved_names} && exists($opts->{reserved_names}{$name});
59 8         43 my %node2name;
60             $opts->{id2name} = { map {
61 8         95 my $n = $_->[0];
  15         42  
62 15         55 my $t = $n->type;
63 15   66     207 my $id_member = ( $id_member{$t}||=_id_member_name($t) );
64 15         131 my $var = $node2name{$n} = $name++;
65 15   33     43 $name++ while $opts->{reserved_names} && exists($opts->{reserved_names}{$name});
66 15         86 ($n->{$id_member} => $var)
67             } @$nodes };
68              
69             # discover relations;
70 8         27 my %marked;
71 8         48 @marked{map $_->[0], @$nodes}=(); # undef by default, 1 if connected
72 8         22 my %parents=();
73 8         19 my %connect;
74 8         24 for my $m (@$nodes) {
75 15         57 my ($n,$fsfile)=@$m;
76 15         44 my $parent = $n->parent;
77 15   66     115 $parents{$parent}||=$n;
78 15 100 66     98 if ($parent and exists($marked{$parent})) {
    100          
79 1         4 push @{$connect{$n->parent}{child}}, $n;
  1         3  
80             # print STDERR "$node2name{$n->parent} has child $node2name{$n}\n";
81 1         9 $marked{$n}=1;
82             } elsif ($parents{$parent}!=$n) {
83 1         5 push @{$connect{$parents{$parent}}{sibling}}, $n;
  1         7  
84             # print STDERR "$node2name{$parents{$parent}} has sibling $node2name{$n}\n";
85 1         5 $marked{$n}=1;
86             } else {
87 13   66     47 $parent = $parent && $parent->parent;
88 13         73 while ($parent) {
89 25 100       88 if (exists $marked{$parent}) {
90             # print STDERR "$node2name{$parent} has descendant $node2name{$n}\n";
91 1         2 push @{$connect{$parent}{descendant}}, $n;
  1         5  
92 1         4 $marked{$n}=1;
93 1         4 last;
94             }
95 24         47 $parent = $parent->parent;
96             }
97             }
98             }
99 8         58 $opts->{connect}=\%connect;
100             return join(";\n\n", map {
101 12         47 node_to_pmltq($_->[0],$_->[1],$opts)}
102 8         25 grep { !$marked{$_->[0]} } @$nodes);
  15         48  
103             }
104              
105             sub node_to_pmltq {
106 15     15 0 40 my ($node,$fsfile,$opts)=@_;
107 15 50       49 return unless $node;
108 15         45 my $type = $node->type;
109 15 50       153 return unless $type;
110 15         26 my $out='';
111 15   100     66 my $indent = $opts->{indent} || '';
112              
113 15   33     74 my $var = $opts->{id2name} && $opts->{id2name}{$node->{_id_member_name($node->type)}};
114 15 50       212 $var = ' $'.$var.' := ' if $var;
115 15         74 $out .= PMLTQ::Common::DeclToQueryType($type).$var." [\n";
116 15         590 foreach my $attr ('#name',$type->get_normal_fields) {
117 386         9223 my $m = $type->get_member_by_name($attr);
118             # next if $m and $m->get_role() eq '#ID';
119 386         2844 my $val = $node->{$attr};
120 386 100       806 next unless defined $val;
121 125 100       241 $m = $type->get_member_by_name($attr.'.rf') unless $m;
122 125 50       365 if ($attr eq '#name') {
    50          
123 0         0 $out .= $indent.qq{ name() = }._pmltq_string($val).qq{,\n};
124 0         0 next;
125             } elsif (!$m) {
126 0 0       0 $out .= $indent." # $attr ???;" unless $opts->{no_comments};
127 0         0 next;
128             }
129 125 50       265 my $name = $attr eq '#content' ? 'content()' : $attr;
130 125 0 33     250 next if $opts->{exclude} and $opts->{exclude}{$name};
131 125         333 $out.=member_to_pmltq($name,$val,$m,$indent.' ',$fsfile,$opts);
132             }
133 15 50       90 if (defined $opts->{rbrothers}) {
134 0 0       0 $out .= $indent.qq{ # rbrothers()=$opts->{rbrothers},\n} unless $opts->{no_comments};
135             }
136 15 50 0     38 if ($opts->{connect}) {
    0          
137 15         43 my $rels = $opts->{connect}{$node};
138 15 100       38 if ($rels) {
139 3         17 foreach my $rel (sort keys %$rels) {
140 3         6 foreach my $n (@{$rels->{$rel}}) {
  3         12  
141 3         31 $out.=' '.$indent.$rel.' '.node_to_pmltq($n,$fsfile,{
142             %$opts,
143             indent=>$indent.' ',
144             }).",\n";
145             }
146             }
147             }
148             } elsif ($opts->{children} or $opts->{descendants}) {
149 0         0 my $i = 0;
150 0         0 my $son = $node->firstson;
151 0         0 while ($son) {
152 0         0 $out.=' '.$indent.'child '.node_to_pmltq($son,$fsfile,{
153             %$opts,
154             indent=>$indent.' ',
155             children => 0,
156             rbrothers=>$i,
157             }).",\n";
158 0         0 $i++;
159 0         0 $son=$son->rbrother;
160             }
161 0 0       0 $out .= $indent.qq{ # sons()=$i,\n} unless $opts->{no_comments};
162             }
163 15         41 $out.=$indent.']';
164 15         171 return $out;
165              
166             }
167              
168             sub _id_member_name {
169 24     24   136 my ($type)=@_;
170 24 50       70 return undef unless $type;
171 24 50       65 if ($type->get_decl_type == PML_ELEMENT_DECL) {
172 0         0 $type = $type->get_content_decl;
173             }
174 24         119 my ($omember) = $type->find_members_by_role('#ID');
175 24 50       5702 if ($omember) {
176 24         75 return ($omember->get_name);
177             }
178 0         0 return undef; # we want this undef
179             }
180              
181             sub _pmltq_string {
182 151     151   316 my ($string)=@_;
183 151         325 $string=~s/([\\'])/\\$1/g;
184 151         264 $string=~s/(\n)/\\n/g;
185 151         537 return qq{'$string'};
186             }
187              
188             sub resolve_pmlref {
189 20     20 0 56 my ($ref,$fsfile)=@_;
190 20 100       116 if ($ref=~m{^(.+?)\#(.+)$}) {
    50          
191 14         50 my ($file_id,$id)=($1,$2);
192 14         52 my $refs = $fsfile->appData('ref');
193 14   66     142 my $reffile = $refs && $refs->{$file_id};
194 14 100       47 if (UNIVERSAL::DOES::does($reffile,'Treex::PML::Document')) {
    50          
195 12         216 return GetNodeByID($id,$reffile);
196             } elsif (UNIVERSAL::DOES::does($reffile,'Treex::PML::Instance')) {
197 0         0 return $reffile->lookup_id($id);
198             }
199             } elsif ($ref=~m{\#?([^#]+)}) {
200 6         18 return GetNodeByID($1, $fsfile);
201             # return GetNodeByID($1);
202             }
203 2         46 return undef;
204             }
205              
206             sub member_to_pmltq {
207 210     210 0 462 my ($name, $val, $type, $indent, $fsfile, $opts)=@_;
208 210         320 my $out;
209 210 50       565 my $mtype = $name eq 'content()' ? $type : $type->get_knit_content_decl;
210 210 100 66     2602 if ($mtype->get_decl_type == PML_ALT_DECL and !UNIVERSAL::DOES::does($val,'Treex::PML::Alt')) {
211 16         526 $mtype = $mtype->get_knit_content_decl;
212             }
213 210 100 33     1028 if (not ref($val)) {
    100          
    50          
    50          
    0          
214 174 50       428 if (!$mtype->is_atomic) {
215 0         0 $out.=$indent."# ignoring $name\n",
216             } else {
217 174 100 100     582 my $is_pmlref = (($mtype->get_decl_type == PML_CDATA_DECL) and ($mtype->get_format eq 'PMLREF')) ? 1 : 0;
218 174 100 100     1236 if ($type and ($type->get_role() =~ /^#(ID|ORDER)$/ or $is_pmlref)) {
      66        
219 53 100 66     787 if ($is_pmlref and $opts->{id2name} and $val=~/(?:^.*?\#)?(.+)$/ and $opts->{id2name}{$1}) {
    100 33        
    50 66        
220 3         21 $out .= $indent.qq{$name \$}.$opts->{id2name}{$1}.qq{,\n};
221             } elsif ($is_pmlref) {
222 20         59 my $target = resolve_pmlref($val,$fsfile);
223 20 100 66     197 if ($target && $target->type) {
224 15         223 $out.=$indent.'# '.$name.' '.PMLTQ::Common::DeclToQueryType( $target->type ).qq{ [ ],\n};
225             } else {
226 5         21 $out.=$indent.'# '.$name.qq{->[ ],\n};
227             }
228             } elsif ($opts->{no_comments}) {
229 0         0 return;
230             } else {
231 30         115 $out.=$indent.'# '.qq{$name = }._pmltq_string($val).qq{,\n};
232             }
233             } else {
234 121         692 $out.=$indent;
235 121         277 $out.=qq{$name = }._pmltq_string($val).qq{,\n};
236             }
237             }
238             } elsif (UNIVERSAL::DOES::does($val,'Treex::PML::List')) {
239 7 100       136 if ($mtype->is_ordered) {
240 1         9 my $i=1;
241 1         4 foreach my $v (@$val) {
242 1         6 $out.=member_to_pmltq("$name/[$i]",$v,$mtype,$indent,$fsfile,$opts);
243 1         3 $i++;
244             }
245             } else {
246 6         44 foreach my $v (@$val) {
247 6         29 $out.=member_to_pmltq($name,$v,$mtype,$indent,$fsfile,$opts);
248             }
249             }
250             } elsif (UNIVERSAL::DOES::does($val,'Treex::PML::Alt')) {
251 0         0 foreach my $v (@$val) {
252 0         0 $out.=member_to_pmltq($name,$v,$mtype,$indent,$fsfile,$opts);
253             }
254             } elsif (UNIVERSAL::DOES::does($val,'Treex::PML::Struct') or UNIVERSAL::DOES::does($val,'Treex::PML::Container')) {
255 29         1714 $out.=$indent.qq{member $name \[\n};
256 29         89 foreach my $attr ($mtype->get_normal_fields) {
257 232         6065 my $m = $mtype->get_member_by_name($attr);
258             # next if $m and $m->get_role() eq '#ID';
259 232         1743 my $v = $val->{$attr};
260 232 100       489 next unless defined $v;
261 78 100       161 $m = $mtype->get_member_by_name($attr.'.rf') unless $m;
262 78 50       186 if (!$m) {
263 0 0       0 $out .= " # $attr ???;" unless $opts->{no_comments};
264 0         0 next;
265             }
266 78 50       177 my $n = $attr eq '#content' ? 'content()' : $attr;
267 78 0 33     163 next if $opts->{exclude} and $opts->{exclude}{$n};
268 78         222 $out.=member_to_pmltq($n,$v,$m,$indent.' ',$fsfile,$opts);
269             }
270 29         88 $out.=$indent.qq{],\n}
271             } elsif (UNIVERSAL::DOES::does($val,'Treex::PML::Seq')) {
272 0         0 my $i=1;
273 0         0 foreach my $v ($val->elements) {
274 0         0 my $n = $v->name;
275 0 0 0     0 next if $opts->{exclude} and $opts->{exclude}{$n};
276 0         0 $out.=member_to_pmltq("$name/[$i]$n",$v->value,$mtype->get_element_by_name($n),$indent,$fsfile,$opts);
277 0         0 $i++;
278             }
279             }
280 210         1182 return $out;
281             }
282              
283              
284             #=item PML::GetNodeByID($id_or_ref,$fsfile?)
285              
286             #Looks up a node from the current file (or given fsfile) by its ID (or
287             #PMLREF - i.e. the ID preceded by a file prefix of the form C).
288              
289             #=cut
290              
291             sub GetNodeByID {
292 18     18 0 51 my ( $rf, $fsfile ) = @_;
293             # if (!defined $fsfile) {
294             # warn("GetNodeByID TODO: FIX THIS !!!");
295             #$fsfile = $grp->{FSFile};
296             # }
297 18         48 $rf =~ s/^.*#//;
298 18         45 return GetNodeHash($fsfile)->{$rf};
299             }
300              
301             #=item PML::GetNodeHash($fsfile?)
302              
303             #Return a reference to a hash indexing nodes in a given file (or the
304             #current file if no argument is given). If such a hash was not yet
305             #created, it is built upon the first call to this function (or other
306             #functions calling it, such as C. Use C to
307             #clear the hash.
308              
309             #=cut
310              
311             sub GetNodeHash {
312 18 50   18 0 53 if (!ref $_[0]) {
313 0         0 shift;
314             }
315             #warn("GetNodeHash TODO: fix this:");
316             #my $fsfile = $_[0] || $grp->{FSFile};
317 18         29 my $fsfile = $_[0];
318 18 50       40 return {} if !ref $fsfile;
319 18 50       57 if ( !ref $fsfile->appData('id-hash') ) {
320 0         0 my %ids;
321 0         0 my $trees = $fsfile->treeList();
322 0         0 for ( my $i = 0; $i <= $#{$trees}; $i++ ) {
  0         0  
323 0         0 my $node = $trees->[$i];
324 0         0 while ($node) {
325 0         0 weaken( $ids{ $node->{id} } = $node );
326             }
327             continue {
328 0         0 $node = $node->following;
329             }
330             }
331 0         0 $fsfile->changeAppData( 'id-hash', \%ids );
332             }
333 18         154 return $fsfile->appData('id-hash');
334             }
335              
336              
337             1;