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.1.1';
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   14 use Scalar::Util qw(weaken);
  2         5  
  2         134  
17 2     2   997 use PMLTQ::Common qw(:all);
  2         21638  
  2         475  
18 2     2   23 use Treex::PML::Schema::Constants;
  2         4  
  2         182  
19 2     2   11 use PMLTQ::Suggest::Utils;
  2         4  
  2         18  
20              
21             sub make_pmltq {
22 8     8 0 23 my ($positions,%opts)=@_;
23 8         18 my @open_files;
24 8         20 my %cur_fsfiles; @cur_fsfiles{@open_files}=();
25             # my $keep_cur;
26 8         16 my %fsfiles;
27             my @new_fsfiles;
28 8         52 foreach my $f (map $_->[0], @$positions) {
29 15 100       74 next if exists $fsfiles{$f};
30 10         41 my $fsfile = PMLTQ::Suggest::Utils::open_file($f);
31 10         41 my @new = ($fsfile, PMLTQ::Suggest::Utils::GetSecondaryFiles($fsfile));
32 10         29 push @new_fsfiles, @new;
33 10         22 push @open_files, @new;
34 10         974 $fsfiles{$_->filename}=$_ for @new; # including $fsfile
35 10         3283 $fsfiles{$f}=$fsfile; # $f may be different from $fsfile->filename
36             }
37 8         21 my @nodes;
38 8         30 for my $pos (@$positions) {
39 15         73 my $win = { FSFile => $fsfiles{$pos->[0]} };
40 15 50 33     53 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         60 push @nodes, [ $win->{currentNode}, $win->{FSFile} ];
45             }
46 8 50       29 print STDERR "generating query\n" if $opts{verbose};
47 8         35 return nodes_to_pmltq(\@nodes,\%opts);
48             }
49              
50              
51              
52              
53             sub nodes_to_pmltq {
54 8     8 0 16 my ($nodes,$opts)=@_;
55 8   50     22 $opts||={};
56 8         12 my %id_member;
57 8         15 my $name = 'a';
58 8   33     25 $name++ while $opts->{reserved_names} && exists($opts->{reserved_names}{$name});
59 8         9 my %node2name;
60             $opts->{id2name} = { map {
61 8         41 my $n = $_->[0];
  15         30  
62 15         46 my $t = $n->type;
63 15   66     173 my $id_member = ( $id_member{$t}||=_id_member_name($t) );
64 15         87 my $var = $node2name{$n} = $name++;
65 15   33     44 $name++ while $opts->{reserved_names} && exists($opts->{reserved_names}{$name});
66 15         73 ($n->{$id_member} => $var)
67             } @$nodes };
68              
69             # discover relations;
70 8         16 my %marked;
71 8         34 @marked{map $_->[0], @$nodes}=(); # undef by default, 1 if connected
72 8         15 my %parents=();
73 8         13 my %connect;
74 8         20 for my $m (@$nodes) {
75 15         40 my ($n,$fsfile)=@$m;
76 15         44 my $parent = $n->parent;
77 15   66     90 $parents{$parent}||=$n;
78 15 100 66     76 if ($parent and exists($marked{$parent})) {
    100          
79 1         2 push @{$connect{$n->parent}{child}}, $n;
  1         2  
80             # print STDERR "$node2name{$n->parent} has child $node2name{$n}\n";
81 1         8 $marked{$n}=1;
82             } elsif ($parents{$parent}!=$n) {
83 1         3 push @{$connect{$parents{$parent}}{sibling}}, $n;
  1         6  
84             # print STDERR "$node2name{$parents{$parent}} has sibling $node2name{$n}\n";
85 1         4 $marked{$n}=1;
86             } else {
87 13   66     39 $parent = $parent && $parent->parent;
88 13         61 while ($parent) {
89 25 100       80 if (exists $marked{$parent}) {
90             # print STDERR "$node2name{$parent} has descendant $node2name{$n}\n";
91 1         2 push @{$connect{$parent}{descendant}}, $n;
  1         4  
92 1         4 $marked{$n}=1;
93 1         3 last;
94             }
95 24         37 $parent = $parent->parent;
96             }
97             }
98             }
99 8         40 $opts->{connect}=\%connect;
100             return join(";\n\n", map {
101 12         38 node_to_pmltq($_->[0],$_->[1],$opts)}
102 8         22 grep { !$marked{$_->[0]} } @$nodes);
  15         39  
103             }
104              
105             sub node_to_pmltq {
106 15     15 0 37 my ($node,$fsfile,$opts)=@_;
107 15 50       37 return unless $node;
108 15         47 my $type = $node->type;
109 15 50       120 return unless $type;
110 15         27 my $out='';
111 15   100     52 my $indent = $opts->{indent} || '';
112              
113 15   33     62 my $var = $opts->{id2name} && $opts->{id2name}{$node->{_id_member_name($node->type)}};
114 15 50       126 $var = ' $'.$var.' := ' if $var;
115 15         53 $out .= PMLTQ::Common::DeclToQueryType($type).$var." [\n";
116 15         492 foreach my $attr ('#name',$type->get_normal_fields) {
117 386         7396 my $m = $type->get_member_by_name($attr);
118             # next if $m and $m->get_role() eq '#ID';
119 386         2178 my $val = $node->{$attr};
120 386 100       630 next unless defined $val;
121 125 100       192 $m = $type->get_member_by_name($attr.'.rf') unless $m;
122 125 50       276 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       217 my $name = $attr eq '#content' ? 'content()' : $attr;
130 125 0 33     201 next if $opts->{exclude} and $opts->{exclude}{$name};
131 125         256 $out.=member_to_pmltq($name,$val,$m,$indent.' ',$fsfile,$opts);
132             }
133 15 50       68 if (defined $opts->{rbrothers}) {
134 0 0       0 $out .= $indent.qq{ # rbrothers()=$opts->{rbrothers},\n} unless $opts->{no_comments};
135             }
136 15 50 0     37 if ($opts->{connect}) {
    0          
137 15         34 my $rels = $opts->{connect}{$node};
138 15 100       36 if ($rels) {
139 3         13 foreach my $rel (sort keys %$rels) {
140 3         6 foreach my $n (@{$rels->{$rel}}) {
  3         10  
141 3         32 $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         30 $out.=$indent.']';
164 15         175 return $out;
165              
166             }
167              
168             sub _id_member_name {
169 24     24   117 my ($type)=@_;
170 24 50       64 return undef unless $type;
171 24 50       64 if ($type->get_decl_type == PML_ELEMENT_DECL) {
172 0         0 $type = $type->get_content_decl;
173             }
174 24         137 my ($omember) = $type->find_members_by_role('#ID');
175 24 50       4524 if ($omember) {
176 24         71 return ($omember->get_name);
177             }
178 0         0 return undef; # we want this undef
179             }
180              
181             sub _pmltq_string {
182 151     151   226 my ($string)=@_;
183 151         256 $string=~s/([\\'])/\\$1/g;
184 151         216 $string=~s/(\n)/\\n/g;
185 151         450 return qq{'$string'};
186             }
187              
188             sub resolve_pmlref {
189 20     20 0 42 my ($ref,$fsfile)=@_;
190 20 100       88 if ($ref=~m{^(.+?)\#(.+)$}) {
    50          
191 14         47 my ($file_id,$id)=($1,$2);
192 14         55 my $refs = $fsfile->appData('ref');
193 14   66     145 my $reffile = $refs && $refs->{$file_id};
194 14 100       32 if (UNIVERSAL::DOES::does($reffile,'Treex::PML::Document')) {
    50          
195 12         156 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         29 return undef;
204             }
205              
206             sub member_to_pmltq {
207 210     210 0 375 my ($name, $val, $type, $indent, $fsfile, $opts)=@_;
208 210         220 my $out;
209 210 50       425 my $mtype = $name eq 'content()' ? $type : $type->get_knit_content_decl;
210 210 100 66     2067 if ($mtype->get_decl_type == PML_ALT_DECL and !UNIVERSAL::DOES::does($val,'Treex::PML::Alt')) {
211 16         429 $mtype = $mtype->get_knit_content_decl;
212             }
213 210 100 33     873 if (not ref($val)) {
    100          
    50          
    50          
    0          
214 174 50       313 if (!$mtype->is_atomic) {
215 0         0 $out.=$indent."# ignoring $name\n",
216             } else {
217 174 100 100     480 my $is_pmlref = (($mtype->get_decl_type == PML_CDATA_DECL) and ($mtype->get_format eq 'PMLREF')) ? 1 : 0;
218 174 100 100     988 if ($type and ($type->get_role() =~ /^#(ID|ORDER)$/ or $is_pmlref)) {
      66        
219 53 100 66     667 if ($is_pmlref and $opts->{id2name} and $val=~/(?:^.*?\#)?(.+)$/ and $opts->{id2name}{$1}) {
    100 33        
    50 66        
220 3         17 $out .= $indent.qq{$name \$}.$opts->{id2name}{$1}.qq{,\n};
221             } elsif ($is_pmlref) {
222 20         57 my $target = resolve_pmlref($val,$fsfile);
223 20 100 66     155 if ($target && $target->type) {
224 15         159 $out.=$indent.'# '.$name.' '.PMLTQ::Common::DeclToQueryType( $target->type ).qq{ [ ],\n};
225             } else {
226 5         15 $out.=$indent.'# '.$name.qq{->[ ],\n};
227             }
228             } elsif ($opts->{no_comments}) {
229 0         0 return;
230             } else {
231 30         96 $out.=$indent.'# '.qq{$name = }._pmltq_string($val).qq{,\n};
232             }
233             } else {
234 121         559 $out.=$indent;
235 121         211 $out.=qq{$name = }._pmltq_string($val).qq{,\n};
236             }
237             }
238             } elsif (UNIVERSAL::DOES::does($val,'Treex::PML::List')) {
239 7 100       96 if ($mtype->is_ordered) {
240 1         7 my $i=1;
241 1         2 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         36 foreach my $v (@$val) {
247 6         26 $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         1343 $out.=$indent.qq{member $name \[\n};
256 29         73 foreach my $attr ($mtype->get_normal_fields) {
257 232         4685 my $m = $mtype->get_member_by_name($attr);
258             # next if $m and $m->get_role() eq '#ID';
259 232         1268 my $v = $val->{$attr};
260 232 100       418 next unless defined $v;
261 78 100       136 $m = $mtype->get_member_by_name($attr.'.rf') unless $m;
262 78 50       137 if (!$m) {
263 0 0       0 $out .= " # $attr ???;" unless $opts->{no_comments};
264 0         0 next;
265             }
266 78 50       127 my $n = $attr eq '#content' ? 'content()' : $attr;
267 78 0 33     130 next if $opts->{exclude} and $opts->{exclude}{$n};
268 78         172 $out.=member_to_pmltq($n,$v,$m,$indent.' ',$fsfile,$opts);
269             }
270 29         69 $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         1009 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 38 my ( $rf, $fsfile ) = @_;
293             # if (!defined $fsfile) {
294             # warn("GetNodeByID TODO: FIX THIS !!!");
295             #$fsfile = $grp->{FSFile};
296             # }
297 18         33 $rf =~ s/^.*#//;
298 18         35 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 45 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       35 return {} if !ref $fsfile;
319 18 50       44 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         129 return $fsfile->appData('id-hash');
334             }
335              
336              
337             1;