Filename | /var/www/foswikidev/core/lib/Foswiki/Search.pm |
Statements | Executed 567737 statements in 680ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
40 | 1 | 1 | 516ms | 28.3s | formatResults | Foswiki::Search::
70083 | 7 | 5 | 99.9ms | 99.9ms | metacache | Foswiki::Search::
8800 | 2 | 1 | 17.1ms | 17.1ms | _collate_to_list | Foswiki::Search::
40 | 1 | 1 | 13.8ms | 137s | searchWeb | Foswiki::Search::
40 | 1 | 1 | 2.25ms | 114ms | loadTemplates | Foswiki::Search::
1 | 1 | 1 | 2.06ms | 2.90ms | BEGIN@19 | Foswiki::Search::
1 | 1 | 1 | 1.27ms | 1.42ms | BEGIN@26 | Foswiki::Search::
40 | 1 | 1 | 1.23ms | 13.9ms | parseSearch | Foswiki::Search::
1 | 1 | 1 | 1.01ms | 1.10ms | BEGIN@20 | Foswiki::Search::
1 | 1 | 1 | 861µs | 952µs | BEGIN@23 | Foswiki::Search::
80 | 2 | 1 | 552µs | 552µs | setup_callback | Foswiki::Search::
1 | 1 | 1 | 442µs | 488µs | BEGIN@25 | Foswiki::Search::
40 | 1 | 1 | 282µs | 282µs | formatCommon | Foswiki::Search::
40 | 1 | 1 | 227µs | 11.2ms | __ANON__[:134] | Foswiki::Search::
1 | 1 | 1 | 76µs | 6.10ms | finish | Foswiki::Search::
1 | 1 | 1 | 15µs | 15µs | new | Foswiki::Search::
1 | 1 | 1 | 15µs | 27µs | BEGIN@12 | Foswiki::Search::
1 | 1 | 1 | 10µs | 14µs | BEGIN@13 | Foswiki::Search::
1 | 1 | 1 | 9µs | 33µs | BEGIN@14 | Foswiki::Search::
1 | 1 | 1 | 9µs | 39µs | BEGIN@29 | Foswiki::Search::
1 | 1 | 1 | 8µs | 111µs | BEGIN@15 | Foswiki::Search::
1 | 1 | 1 | 5µs | 5µs | BEGIN@24 | Foswiki::Search::
1 | 1 | 1 | 5µs | 5µs | BEGIN@21 | Foswiki::Search::
1 | 1 | 1 | 5µs | 5µs | BEGIN@17 | Foswiki::Search::
1 | 1 | 1 | 5µs | 5µs | BEGIN@27 | Foswiki::Search::
1 | 1 | 1 | 5µs | 5µs | BEGIN@31 | Foswiki::Search::
1 | 1 | 1 | 3µs | 3µs | BEGIN@22 | Foswiki::Search::
1 | 1 | 1 | 3µs | 3µs | BEGIN@18 | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1013] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1014] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1015] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1016] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1020] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1021] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1022] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1023] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1024] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1025] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1026] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1027] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1028] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1044] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1053] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:1398] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:139] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:191] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:194] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:649] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:650] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:651] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:652] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:653] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:654] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:666] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:678] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:689] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:701] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:712] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:968] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | __ANON__[:975] | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | _countPattern | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | _extractPattern | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | _isSetTrue | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | displayFormField | Foswiki::Search::
0 | 0 | 0 | 0s | 0s | formatResult | Foswiki::Search::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | # See bottom of file for license and copyright information | ||||
2 | package Foswiki::Search; | ||||
3 | |||||
4 | =begin TML | ||||
5 | |||||
6 | ---+ package Foswiki::Search | ||||
7 | |||||
8 | This module implements all the search functionality. | ||||
9 | |||||
10 | =cut | ||||
11 | |||||
12 | 2 | 29µs | 2 | 40µs | # spent 27µs (15+13) within Foswiki::Search::BEGIN@12 which was called:
# once (15µs+13µs) by Foswiki::search at line 12 # spent 27µs making 1 call to Foswiki::Search::BEGIN@12
# spent 13µs making 1 call to strict::import |
13 | 2 | 23µs | 2 | 18µs | # spent 14µs (10+4) within Foswiki::Search::BEGIN@13 which was called:
# once (10µs+4µs) by Foswiki::search at line 13 # spent 14µs making 1 call to Foswiki::Search::BEGIN@13
# spent 4µs making 1 call to warnings::import |
14 | 2 | 32µs | 2 | 56µs | # spent 33µs (9+24) within Foswiki::Search::BEGIN@14 which was called:
# once (9µs+24µs) by Foswiki::search at line 14 # spent 33µs making 1 call to Foswiki::Search::BEGIN@14
# spent 24µs making 1 call to Exporter::import |
15 | 2 | 29µs | 2 | 214µs | # spent 111µs (8+103) within Foswiki::Search::BEGIN@15 which was called:
# once (8µs+103µs) by Foswiki::search at line 15 # spent 111µs making 1 call to Foswiki::Search::BEGIN@15
# spent 103µs making 1 call to Error::import |
16 | |||||
17 | 2 | 18µs | 1 | 5µs | # spent 5µs within Foswiki::Search::BEGIN@17 which was called:
# once (5µs+0s) by Foswiki::search at line 17 # spent 5µs making 1 call to Foswiki::Search::BEGIN@17 |
18 | 2 | 17µs | 1 | 3µs | # spent 3µs within Foswiki::Search::BEGIN@18 which was called:
# once (3µs+0s) by Foswiki::search at line 18 # spent 3µs making 1 call to Foswiki::Search::BEGIN@18 |
19 | 2 | 94µs | 1 | 2.90ms | # spent 2.90ms (2.06+835µs) within Foswiki::Search::BEGIN@19 which was called:
# once (2.06ms+835µs) by Foswiki::search at line 19 # spent 2.90ms making 1 call to Foswiki::Search::BEGIN@19 |
20 | 2 | 97µs | 1 | 1.10ms | # spent 1.10ms (1.01+95µs) within Foswiki::Search::BEGIN@20 which was called:
# once (1.01ms+95µs) by Foswiki::search at line 20 # spent 1.10ms making 1 call to Foswiki::Search::BEGIN@20 |
21 | 2 | 19µs | 1 | 5µs | # spent 5µs within Foswiki::Search::BEGIN@21 which was called:
# once (5µs+0s) by Foswiki::search at line 21 # spent 5µs making 1 call to Foswiki::Search::BEGIN@21 |
22 | 2 | 21µs | 1 | 3µs | # spent 3µs within Foswiki::Search::BEGIN@22 which was called:
# once (3µs+0s) by Foswiki::search at line 22 # spent 3µs making 1 call to Foswiki::Search::BEGIN@22 |
23 | 2 | 91µs | 1 | 952µs | # spent 952µs (861+91) within Foswiki::Search::BEGIN@23 which was called:
# once (861µs+91µs) by Foswiki::search at line 23 # spent 952µs making 1 call to Foswiki::Search::BEGIN@23 |
24 | 2 | 19µs | 1 | 5µs | # spent 5µs within Foswiki::Search::BEGIN@24 which was called:
# once (5µs+0s) by Foswiki::search at line 24 # spent 5µs making 1 call to Foswiki::Search::BEGIN@24 |
25 | 2 | 82µs | 1 | 488µs | # spent 488µs (442+46) within Foswiki::Search::BEGIN@25 which was called:
# once (442µs+46µs) by Foswiki::search at line 25 # spent 488µs making 1 call to Foswiki::Search::BEGIN@25 |
26 | 2 | 88µs | 1 | 1.42ms | # spent 1.42ms (1.27+156µs) within Foswiki::Search::BEGIN@26 which was called:
# once (1.27ms+156µs) by Foswiki::search at line 26 # spent 1.42ms making 1 call to Foswiki::Search::BEGIN@26 |
27 | 2 | 23µs | 1 | 5µs | # spent 5µs within Foswiki::Search::BEGIN@27 which was called:
# once (5µs+0s) by Foswiki::search at line 27 # spent 5µs making 1 call to Foswiki::Search::BEGIN@27 |
28 | |||||
29 | 2 | 51µs | 2 | 69µs | # spent 39µs (9+30) within Foswiki::Search::BEGIN@29 which was called:
# once (9µs+30µs) by Foswiki::search at line 29 # spent 39µs making 1 call to Foswiki::Search::BEGIN@29
# spent 30µs making 1 call to constant::import |
30 | |||||
31 | # spent 5µs within Foswiki::Search::BEGIN@31 which was called:
# once (5µs+0s) by Foswiki::search at line 36 | ||||
32 | 1 | 5µs | if ( $Foswiki::cfg{UseLocale} ) { | ||
33 | require locale; | ||||
34 | import locale(); | ||||
35 | } | ||||
36 | 1 | 5.32ms | 1 | 5µs | } # spent 5µs making 1 call to Foswiki::Search::BEGIN@31 |
37 | |||||
38 | =begin TML | ||||
39 | |||||
40 | ---++ ClassMethod new ($session) | ||||
41 | |||||
42 | Constructor for the singleton Search engine object. | ||||
43 | |||||
44 | =cut | ||||
45 | |||||
46 | # spent 15µs within Foswiki::Search::new which was called:
# once (15µs+0s) by Foswiki::search at line 2406 of /var/www/foswikidev/core/lib/Foswiki.pm | ||||
47 | 1 | 1µs | my ( $class, $session ) = @_; | ||
48 | 1 | 12µs | my $this = bless( { session => $session }, $class ); | ||
49 | |||||
50 | 1 | 6µs | return $this; | ||
51 | } | ||||
52 | |||||
53 | =begin TML | ||||
54 | |||||
55 | ---++ ObjectMethod finish() | ||||
56 | Break circular references. | ||||
57 | |||||
58 | Note to developers; please undef *all* fields in the object explicitly, | ||||
59 | whether they are references or not. That way this method is "golden | ||||
60 | documentation" of the live fields in the object. | ||||
61 | |||||
62 | =cut | ||||
63 | |||||
64 | # spent 6.10ms (76µs+6.03) within Foswiki::Search::finish which was called:
# once (76µs+6.03ms) by Foswiki::finish at line 2488 of /var/www/foswikidev/core/lib/Foswiki.pm | ||||
65 | 1 | 600ns | my $this = shift; | ||
66 | 1 | 1µs | undef $this->{session}; | ||
67 | |||||
68 | # these may well be function objects, but if (a setting changes, it needs to be picked up again. | ||||
69 | 1 | 43µs | if ( defined( $this->{queryParser} ) ) { | ||
70 | 1 | 10µs | 1 | 2µs | $this->{queryParser}->finish(); # spent 2µs making 1 call to Foswiki::Infix::Parser::finish |
71 | 1 | 1µs | undef $this->{queryParser}; | ||
72 | } | ||||
73 | 1 | 700ns | if ( defined( $this->{searchParser} ) ) { | ||
74 | $this->{searchParser}->finish(); | ||||
75 | undef $this->{searchParser}; | ||||
76 | } | ||||
77 | 1 | 5µs | if ( defined( $this->{MetaCache} ) ) { | ||
78 | 1 | 5µs | 1 | 6.03ms | $this->{MetaCache}->finish(); # spent 6.03ms making 1 call to Foswiki::MetaCache::finish |
79 | 1 | 800ns | undef $this->{MetaCache}; | ||
80 | } | ||||
81 | } | ||||
82 | |||||
83 | =begin TML | ||||
84 | |||||
85 | ---++ ObjectMethod metacache | ||||
86 | returns the metacache. | ||||
87 | |||||
88 | =cut | ||||
89 | |||||
90 | # spent 99.9ms (99.9+8µs) within Foswiki::Search::metacache which was called 70083 times, avg 1µs/call:
# 17520 times (25.5ms+0s) by Foswiki::Store::Interfaces::QueryAlgorithm::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm:194] at line 176 of /var/www/foswikidev/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm, avg 1µs/call
# 17520 times (24.1ms+0s) by Foswiki::Store::Interfaces::QueryAlgorithm::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm:194] at line 186 of /var/www/foswikidev/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm, avg 1µs/call
# 8760 times (14.3ms+0s) by Foswiki::Search::formatResults at line 775, avg 2µs/call
# 8760 times (13.1ms+0s) by Foswiki::Search::InfoCache::addTopic at line 105 of /var/www/foswikidev/core/lib/Foswiki/Search/InfoCache.pm, avg 1µs/call
# 8760 times (12.5ms+0s) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 203 of /var/www/foswikidev/core/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 1µs/call
# 8760 times (10.4ms+0s) by Foswiki::Search::formatResults at line 782, avg 1µs/call
# 3 times (12µs+8µs) by Foswiki::Meta::unload at line 504 of /var/www/foswikidev/core/lib/Foswiki/Meta.pm, avg 7µs/call | ||||
91 | 70083 | 16.7ms | my $this = shift; | ||
92 | |||||
93 | # these may well be function objects, but if (a setting changes, it needs to be picked up again. | ||||
94 | 70083 | 19.6ms | 1 | 8µs | if ( !defined( $this->{MetaCache} ) ) { # spent 8µs making 1 call to Foswiki::MetaCache::new |
95 | $this->{MetaCache} = new Foswiki::MetaCache( $this->{session} ); | ||||
96 | } | ||||
97 | 70083 | 218ms | return $this->{MetaCache}; | ||
98 | } | ||||
99 | |||||
100 | =begin TML | ||||
101 | |||||
102 | ---++ ObjectMethod parseSearch($searchString, $params) -> Foswiki::*::Node | ||||
103 | |||||
104 | parses the search string and builds the appropriate nodes (uses $param->{type} to work out which parser | ||||
105 | |||||
106 | TODO: make parser register themselves with their type, so that we could plug in anything. | ||||
107 | |||||
108 | =cut | ||||
109 | |||||
110 | # spent 13.9ms (1.23+12.7) within Foswiki::Search::parseSearch which was called 40 times, avg 348µs/call:
# 40 times (1.23ms+12.7ms) by Foswiki::Search::searchWeb at line 371, avg 348µs/call | ||||
111 | 40 | 22µs | my $this = shift; | ||
112 | 40 | 14µs | my $searchString = shift; | ||
113 | 40 | 9µs | my $params = shift; | ||
114 | |||||
115 | 40 | 4µs | my $query; | ||
116 | 40 | 7µs | my $theParser; | ||
117 | 40 | 54µs | if ( $params->{type} eq 'query' ) { | ||
118 | 40 | 28µs | unless ( defined( $this->{queryParser} ) ) { | ||
119 | 1 | 1µs | require Foswiki::Query::Parser; | ||
120 | 1 | 6µs | 1 | 833µs | $this->{queryParser} = new Foswiki::Query::Parser(); # spent 833µs making 1 call to Foswiki::Query::Parser::new |
121 | } | ||||
122 | 40 | 25µs | $theParser = $this->{queryParser}; | ||
123 | } | ||||
124 | else { | ||||
125 | unless ( defined( $this->{searchParser} ) ) { | ||||
126 | require Foswiki::Search::Parser; | ||||
127 | $this->{searchParser} = | ||||
128 | new Foswiki::Search::Parser( $this->{session} ); | ||||
129 | } | ||||
130 | $theParser = $this->{searchParser}; | ||||
131 | } | ||||
132 | # spent 11.2ms (227µs+11.0) within Foswiki::Search::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Search.pm:134] which was called 40 times, avg 280µs/call:
# 40 times (227µs+11.0ms) by Error::subs::try at line 419 of Error.pm, avg 280µs/call | ||||
133 | 40 | 286µs | 40 | 11.0ms | $query = $theParser->parse( $searchString, $params ); # spent 11.0ms making 40 calls to Foswiki::Infix::Parser::parse, avg 275µs/call |
134 | } | ||||
135 | catch Foswiki::Infix::Error with { | ||||
136 | |||||
137 | # Pass the error on to the caller | ||||
138 | throw Error::Simple( shift->stringify() ); | ||||
139 | 40 | 825µs | 120 | 197µs | }; # spent 143µs making 40 calls to Error::catch, avg 4µs/call
# spent 54µs making 40 calls to Error::subs::with, avg 1µs/call
# spent 11.7ms making 40 calls to Error::subs::try, avg 292µs/call, recursion: max depth 2, sum of overlapping time 11.7ms |
140 | |||||
141 | #print STDERR "parseSearch($searchString) => ".$query->stringify()."\n" if MONITOR; | ||||
142 | |||||
143 | 40 | 119µs | return $query; | ||
144 | } | ||||
145 | |||||
146 | sub _extractPattern { | ||||
147 | my ( $text, $pattern, $encode ) = @_; | ||||
148 | |||||
149 | # Pattern comes from topic, therefore tainted | ||||
150 | $pattern = | ||||
151 | Foswiki::Sandbox::untaint( $pattern, \&Foswiki::validatePattern ); | ||||
152 | |||||
153 | my $ok = 0; | ||||
154 | eval { | ||||
155 | |||||
156 | # The eval acts as a try block in case there is anything evil in | ||||
157 | # the pattern. | ||||
158 | $ok = 1 if ( $text =~ s/$pattern/$1/is ); | ||||
159 | }; | ||||
160 | $text = '' unless $ok; | ||||
161 | |||||
162 | if ($encode) { | ||||
163 | |||||
164 | # Reverse the action of Foswiki::expandStandardEscapes | ||||
165 | $text =~ s/$/\$dollar/g; | ||||
166 | $text =~ s/&/\$amp()/g; | ||||
167 | $text =~ s/>/\$gt()/g; | ||||
168 | $text =~ s/</\$lt()/g; | ||||
169 | $text =~ s/%/\$percent()/g; | ||||
170 | $text =~ s/>/\$comma()/g; | ||||
171 | $text =~ s/"/\$quot()/g; | ||||
172 | $text =~ s/\n/\$n()/g; | ||||
173 | } | ||||
174 | |||||
175 | return $text; | ||||
176 | } | ||||
177 | |||||
178 | # With the same argument as $pattern, returns a number which is the count of | ||||
179 | # occurences of the pattern argument. | ||||
180 | sub _countPattern { | ||||
181 | my ( $text, $pattern ) = @_; | ||||
182 | |||||
183 | $pattern = | ||||
184 | Foswiki::Sandbox::untaint( $pattern, \&Foswiki::validatePattern ); | ||||
185 | |||||
186 | my $count; | ||||
187 | try { | ||||
188 | |||||
189 | # see: perldoc -q count | ||||
190 | $count = () = $text =~ m/$pattern/g; | ||||
191 | } | ||||
192 | catch Error with { | ||||
193 | $count = 0; | ||||
194 | }; | ||||
195 | |||||
196 | return $count; | ||||
197 | } | ||||
198 | |||||
199 | =begin TML | ||||
200 | |||||
201 | ---++ StaticMethod _isSetTrue( $value, $default ) -> $boolean | ||||
202 | |||||
203 | Returns 1 if =$value= is _actually set to_ true, and 0 otherwise. | ||||
204 | |||||
205 | If the value is undef, then =$default= is returned. If =$default= is | ||||
206 | not specified it is taken as 0. | ||||
207 | |||||
208 | =cut | ||||
209 | |||||
210 | sub _isSetTrue { | ||||
211 | my ( $value, $default ) = @_; | ||||
212 | |||||
213 | $default ||= 0; | ||||
214 | |||||
215 | return $default unless defined($value); | ||||
216 | |||||
217 | $value =~ s/on//gi; | ||||
218 | $value =~ s/yes//gi; | ||||
219 | $value =~ s/true//gi; | ||||
220 | return ($value) ? 0 : 1; | ||||
221 | } | ||||
222 | |||||
223 | =begin TML | ||||
224 | |||||
225 | ---++ ObjectMethod searchWeb (...) | ||||
226 | |||||
227 | Search one or more webs according to the parameters. | ||||
228 | |||||
229 | If =_callback= is set, that means the caller wants results as | ||||
230 | soon as they are ready. =_callback_ should be set to a reference | ||||
231 | to a function which takes =_cbdata= as the first parameter and | ||||
232 | remaining parameters the same as 'print'. | ||||
233 | |||||
234 | If =_callback= is set, the result is always undef. Otherwise the | ||||
235 | result is a string containing the rendered search results. | ||||
236 | |||||
237 | The function will throw Error::Simple if it encounters any problems with the | ||||
238 | syntax of the search string. | ||||
239 | |||||
240 | Note: If =format= is set, =template= will be ignored. | ||||
241 | |||||
242 | Note: For legacy, if =regex= is defined, it will force type='regex' | ||||
243 | |||||
244 | If =type="word"= it will be changed to =type="keyword"= with =wordboundaries=1=. This will be used for searching with scope="text" only, because scope="topic" will do a Perl search on topic names. | ||||
245 | |||||
246 | SMELL: If =template= is defined =bookview= will not work | ||||
247 | |||||
248 | SMELL: it seems that if you define =_callback= then you are | ||||
249 | responsible for converting the TML to HTML yourself! | ||||
250 | |||||
251 | FIXME: =callback= cannot work with format parameter (consider format='| $topic |' | ||||
252 | |||||
253 | =cut | ||||
254 | |||||
255 | # spent 137s (13.8ms+137) within Foswiki::Search::searchWeb which was called 40 times, avg 3.42s/call:
# 40 times (13.8ms+137s) by Foswiki::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Macros/SEARCH.pm:40] at line 39 of /var/www/foswikidev/core/lib/Foswiki/Macros/SEARCH.pm, avg 3.42s/call | ||||
256 | 40 | 21µs | my $this = shift; | ||
257 | 40 | 23µs | my $session = $this->{session}; | ||
258 | ASSERT( defined $session->{webName} ) if DEBUG; | ||||
259 | 40 | 169µs | my %params = @_; | ||
260 | |||||
261 | 40 | 152µs | 40 | 428µs | my $baseWebObject = Foswiki::Meta->new( $session, $session->{webName} ); # spent 428µs making 40 calls to Foswiki::Meta::new, avg 11µs/call |
262 | |||||
263 | 40 | 125µs | 40 | 237µs | my ( $callback, $cbdata ) = setup_callback( \%params, $baseWebObject ); # spent 237µs making 40 calls to Foswiki::Search::setup_callback, avg 6µs/call |
264 | |||||
265 | 40 | 30µs | my $baseTopic = $params{basetopic} || $session->{topicName}; | ||
266 | 40 | 18µs | my $baseWeb = $params{baseweb} || $session->{webName}; | ||
267 | 40 | 132µs | 40 | 85µs | $params{casesensitive} = Foswiki::isTrue( $params{casesensitive} ); # spent 85µs making 40 calls to Foswiki::isTrue, avg 2µs/call |
268 | 40 | 44µs | $params{excludeTopics} = $params{excludetopic} || ''; | ||
269 | 40 | 45µs | my $formatDefined = $params{formatdefined} = defined $params{format}; | ||
270 | 40 | 14µs | my $format = $params{format}; | ||
271 | |||||
272 | 40 | 93µs | 40 | 61µs | $params{multiple} = Foswiki::isTrue( $params{multiple} ); # spent 61µs making 40 calls to Foswiki::isTrue, avg 2µs/call |
273 | |||||
274 | # Let the search know we're only concerned with one hit per file | ||||
275 | 40 | 92µs | 40 | 319µs | $params{files_without_match} = not Foswiki::isTrue( $params{multiple} ); # spent 319µs making 40 calls to Foswiki::isTrue, avg 8µs/call |
276 | |||||
277 | 40 | 76µs | 40 | 266µs | $params{nonoise} = Foswiki::isTrue( $params{nonoise} ); # spent 266µs making 40 calls to Foswiki::isTrue, avg 7µs/call |
278 | 40 | 97µs | 40 | 69µs | $params{noempty} = Foswiki::isTrue( $params{noempty}, $params{nonoise} ); # spent 69µs making 40 calls to Foswiki::isTrue, avg 2µs/call |
279 | ### $params{zeroresults} = Foswiki::isTrue( ( $params{zeroresults} ), $params{nonoise} ); | ||||
280 | |||||
281 | #TODO: refactorme | ||||
282 | 40 | 24µs | my $header = $params{header}; | ||
283 | 40 | 62µs | my $footer = $params{footer}; | ||
284 | 40 | 92µs | 40 | 58µs | my $noTotal = Foswiki::isTrue( $params{nototal}, $params{nonoise} ); # spent 58µs making 40 calls to Foswiki::isTrue, avg 1µs/call |
285 | |||||
286 | 40 | 67µs | 40 | 193µs | my $noEmpty = Foswiki::isTrue( $params{noempty}, $params{nonoise} ); # spent 193µs making 40 calls to Foswiki::isTrue, avg 5µs/call |
287 | |||||
288 | # Note: a defined header/footer overrides noheader/nofooter | ||||
289 | # To maintain Cairo compatibility we ommit default header/footer if the | ||||
290 | 40 | 116µs | 40 | 62µs | my $noHeader = # spent 62µs making 40 calls to Foswiki::isTrue, avg 2µs/call |
291 | !defined($header) | ||||
292 | && Foswiki::isTrue( $params{noheader}, $params{nonoise} ) | ||||
293 | || ( !$header && $formatDefined ); | ||||
294 | |||||
295 | 40 | 33µs | my $noFooter = | ||
296 | !defined($footer) | ||||
297 | && Foswiki::isTrue( $params{nofooter}, $params{nonoise} ) | ||||
298 | || ( !$footer && $formatDefined ); | ||||
299 | |||||
300 | 40 | 91µs | 40 | 56µs | my $noSummary = Foswiki::isTrue( $params{nosummary}, $params{nonoise} ); # spent 56µs making 40 calls to Foswiki::isTrue, avg 1µs/call |
301 | 40 | 83µs | 40 | 54µs | my $zeroResults = # spent 54µs making 40 calls to Foswiki::isTrue, avg 1µs/call |
302 | Foswiki::isTrue( $params{zeroresults}, $params{nonoise} || 1 ); | ||||
303 | |||||
304 | #END TODO | ||||
305 | |||||
306 | 40 | 75µs | 40 | 58µs | my $doBookView = Foswiki::isTrue( $params{bookview} ); # spent 58µs making 40 calls to Foswiki::isTrue, avg 1µs/call |
307 | |||||
308 | 40 | 75µs | 40 | 57µs | my $revSort = Foswiki::isTrue( $params{reverse} ); # spent 57µs making 40 calls to Foswiki::isTrue, avg 1µs/call |
309 | 40 | 40µs | $params{scope} = $params{scope} || ''; | ||
310 | 40 | 23µs | my $searchString = defined $params{search} ? $params{search} : ''; | ||
311 | |||||
312 | 40 | 50µs | $params{includeTopics} = $params{topic} || ''; | ||
313 | 40 | 25µs | $params{type} = $params{type} || ''; | ||
314 | |||||
315 | 40 | 27µs | $params{wordboundaries} = 0; | ||
316 | 40 | 29µs | if ( $params{type} eq 'word' ) { | ||
317 | |||||
318 | # 'word' is exactly the same as 'keyword', except we will be searching | ||||
319 | # with word boundaries | ||||
320 | $params{type} = 'keyword'; | ||||
321 | $params{wordboundaries} = 1; | ||||
322 | } | ||||
323 | |||||
324 | 40 | 28µs | my $webNames = $params{web} || ''; | ||
325 | 40 | 16µs | my $date = $params{date} || ''; | ||
326 | 40 | 15µs | my $recurse = $params{'recurse'} || ''; | ||
327 | |||||
328 | 40 | 81µs | $baseWeb =~ s/\./\//g; | ||
329 | |||||
330 | 40 | 22µs | $params{type} = 'regex' if ( $params{regex} ); | ||
331 | |||||
332 | #TODO: quick hackjob - see what the feature proposal gives before it becomes public | ||||
333 | 40 | 53µs | if ( defined( $params{groupby} ) and ( $params{groupby} eq 'none' ) ) { | ||
334 | |||||
335 | #_only_ allow groupby="none" - as its a secrect none public setting. | ||||
336 | } | ||||
337 | else { | ||||
338 | 40 | 34µs | $params{groupby} = 'web'; | ||
339 | } | ||||
340 | |||||
341 | # need to tell the Meta::query pager settings so it can optimise | ||||
342 | 40 | 33µs | require Digest::MD5; | ||
343 | 40 | 19µs | my $string_id = $params{_RAW} || 'we had better not go there'; | ||
344 | 40 | 162µs | 40 | 402µs | $string_id = Foswiki::encode_utf8($string_id); # spent 402µs making 40 calls to Encode::encode_utf8, avg 10µs/call |
345 | 40 | 375µs | 40 | 237µs | my $paging_ID = 'SEARCH' . Digest::MD5::md5_hex($string_id); # spent 237µs making 40 calls to Digest::MD5::md5_hex, avg 6µs/call |
346 | 40 | 43µs | $params{pager_urlparam_id} = $paging_ID; | ||
347 | |||||
348 | # 1-based system; 0 is not a valid page number | ||||
349 | 40 | 183µs | 40 | 1.02ms | $params{showpage} = $session->{request}->param($paging_ID) # spent 1.02ms making 40 calls to Foswiki::Request::param, avg 26µs/call |
350 | || $params{showpage}; | ||||
351 | |||||
352 | #append a pager to the end of the search result. | ||||
353 | 40 | 122µs | 40 | 82µs | $params{pager} = Foswiki::isTrue( $params{pager} ); # spent 82µs making 40 calls to Foswiki::isTrue, avg 2µs/call |
354 | |||||
355 | 40 | 62µs | if ( defined( $params{pagesize} ) | ||
356 | or defined( $params{showpage} ) | ||||
357 | or $params{pager} ) | ||||
358 | { | ||||
359 | if ( !defined( $params{pagesize} ) ) { | ||||
360 | $params{pagesize} = $Foswiki::cfg{Search}{DefaultPageSize} || 25; | ||||
361 | } | ||||
362 | $params{showpage} = 1 unless ( defined( $params{showpage} ) ); | ||||
363 | $params{paging_on} = 1; | ||||
364 | } | ||||
365 | else { | ||||
366 | |||||
367 | #denote the pager is off. | ||||
368 | 40 | 37µs | $params{paging_on} = 0; | ||
369 | } | ||||
370 | ################### Perform The Search | ||||
371 | 40 | 122µs | 40 | 13.9ms | my $query = $this->parseSearch( $searchString, \%params ); # spent 13.9ms making 40 calls to Foswiki::Search::parseSearch, avg 348µs/call |
372 | |||||
373 | #setting the inputTopicSet to be undef allows the search/query algo to use | ||||
374 | #the topic="" and excludetopic="" params and web Obj to get a new list of topics. | ||||
375 | #this allows the algo's to customise and optimise the getting of this list themselves. | ||||
376 | |||||
377 | #NOTE: as of Jun2011 foswiki 2.0's query() returns a result set filtered by ACL and paged to $showpage | ||||
378 | #TODO: work out if and how to avoid it | ||||
379 | 40 | 162µs | 40 | 108s | my $infoCache = Foswiki::Meta::query( $query, undef, \%params ); # spent 108s making 40 calls to Foswiki::Meta::query, avg 2.71s/call |
380 | |||||
381 | ################### Do the Rendering | ||||
382 | |||||
383 | # If the search did not return anything, return the rendered zeroresults | ||||
384 | # if it is defined as a string. | ||||
385 | # (http://foswiki.org/Development/AddDefaultTopicParameterToINCLUDE) | ||||
386 | 40 | 97µs | 40 | 128ms | if ( not $infoCache->hasNext() ) { # spent 128ms making 40 calls to Foswiki::Iterator::FilterIterator::hasNext, avg 3.20ms/call |
387 | if ( not $zeroResults ) { | ||||
388 | return ''; | ||||
389 | } | ||||
390 | else { | ||||
391 | if ( not _isSetTrue( $params{zeroresults}, 1 ) ) { | ||||
392 | |||||
393 | #foswiki 1.1 Feature Proposal: SEARCH needs an alt parameter in case of zero results | ||||
394 | |||||
395 | #TODO: extract & merge with extraction of footer processing code below | ||||
396 | my $result = $params{zeroresults}; | ||||
397 | |||||
398 | $result =~ s/\$web/$baseWeb/gs; # expand name of web | ||||
399 | $result =~ s/([^\n])$/$1\n/s; # add new line at end | ||||
400 | |||||
401 | # output footer of $web | ||||
402 | $result =~ s/\$ntopics/0/gs; | ||||
403 | $result =~ s/\$nhits/0/gs; | ||||
404 | $result =~ s/\$index/0/gs; | ||||
405 | |||||
406 | #legacy SEARCH counter support | ||||
407 | $result =~ s/%NTOPICS%/0/g; | ||||
408 | |||||
409 | $result = Foswiki::expandStandardEscapes($result); | ||||
410 | $result =~ s/\n$//s; # remove trailing new line | ||||
411 | |||||
412 | return $result; | ||||
413 | } | ||||
414 | } | ||||
415 | } | ||||
416 | |||||
417 | 40 | 323µs | 40 | 114ms | my $tmplSearch = # spent 114ms making 40 calls to Foswiki::Search::loadTemplates, avg 2.85ms/call |
418 | $this->loadTemplates( \%params, $baseWebObject, $formatDefined, | ||||
419 | $doBookView, $noHeader, $noSummary, $noTotal, $noFooter ); | ||||
420 | |||||
421 | # Generate 'Search:' part showing actual search string used | ||||
422 | # Ommit any text before search results if either nosearch or nonoise is on | ||||
423 | 40 | 105µs | 40 | 423µs | my $nonoise = Foswiki::isTrue( $params{nonoise} ); # spent 423µs making 40 calls to Foswiki::isTrue, avg 11µs/call |
424 | 40 | 104µs | 40 | 76µs | my $noSearch = Foswiki::isTrue( $params{nosearch}, $nonoise ); # spent 76µs making 40 calls to Foswiki::isTrue, avg 2µs/call |
425 | 40 | 17µs | unless ($noSearch) { | ||
426 | my $searchStr = $searchString; | ||||
427 | $searchStr =~ s/&/&/g; | ||||
428 | $searchStr =~ s/</</g; | ||||
429 | $searchStr =~ s/>/>/g; | ||||
430 | |||||
431 | $tmplSearch =~ s/%SEARCHSTRING%/$searchStr/g; | ||||
432 | &$callback( $cbdata, $tmplSearch ); | ||||
433 | } | ||||
434 | |||||
435 | # We now format the results. | ||||
436 | # All the | ||||
437 | 40 | 295µs | 40 | 28.3s | my ( $numberOfResults, $web_searchResult ) = # spent 28.3s making 40 calls to Foswiki::Search::formatResults, avg 707ms/call |
438 | $this->formatResults( $query, $infoCache, \%params ); | ||||
439 | |||||
440 | 40 | 28µs | return if ( defined $params{_callback} ); | ||
441 | |||||
442 | 40 | 300µs | my $searchResult = join( '', @{ $params{_cbdata} } ); | ||
443 | |||||
444 | 40 | 212µs | 40 | 460µs | $searchResult = Foswiki::expandStandardEscapes($searchResult); # spent 460µs making 40 calls to Foswiki::expandStandardEscapes, avg 12µs/call |
445 | |||||
446 | # Remove trailing separator or new line if nofinalnewline parameter is set | ||||
447 | 40 | 142µs | 40 | 122µs | my $noFinalNewline = Foswiki::isTrue( $params{nofinalnewline}, 1 ); # spent 122µs making 40 calls to Foswiki::isTrue, avg 3µs/call |
448 | 40 | 32µs | if ( $formatDefined && $noFinalNewline ) { | ||
449 | 40 | 32µs | if ( $params{separator} ) { | ||
450 | my $separator = quotemeta( $params{separator} ); | ||||
451 | $searchResult =~ s/$separator$//s; # remove separator at end | ||||
452 | } | ||||
453 | else { | ||||
454 | 40 | 40µs | $searchResult =~ s/\n$//s; # remove trailing new line | ||
455 | } | ||||
456 | } | ||||
457 | |||||
458 | 40 | 2.44ms | return $searchResult; | ||
459 | } | ||||
460 | |||||
461 | =begin TML | ||||
462 | |||||
463 | ---++ ObjectMethod loadTemplates (...) | ||||
464 | |||||
465 | this code was extracted from searchWeb, and should probably be private. | ||||
466 | |||||
467 | =cut | ||||
468 | |||||
469 | # spent 114ms (2.25+112) within Foswiki::Search::loadTemplates which was called 40 times, avg 2.85ms/call:
# 40 times (2.25ms+112ms) by Foswiki::Search::searchWeb at line 417, avg 2.85ms/call | ||||
470 | my ( | ||||
471 | 40 | 127µs | $this, $params, $baseWebObject, | ||
472 | $formatDefined, $doBookView, $noHeader, | ||||
473 | $noSummary, $noTotal, $noFooter | ||||
474 | ) = @_; | ||||
475 | |||||
476 | 40 | 31µs | my $session = $this->{session}; | ||
477 | |||||
478 | #tmpl loading code. | ||||
479 | 40 | 27µs | my $tmpl = ''; | ||
480 | |||||
481 | 40 | 74µs | my $template = $params->{template} || ''; | ||
482 | 40 | 25µs | if ($formatDefined) { | ||
483 | $template = 'searchformat'; | ||||
484 | } | ||||
485 | elsif ($template) { | ||||
486 | |||||
487 | # template definition overrides book and rename views | ||||
488 | } | ||||
489 | elsif ($doBookView) { | ||||
490 | $template = 'searchbookview'; | ||||
491 | } | ||||
492 | else { | ||||
493 | $template = 'search'; | ||||
494 | } | ||||
495 | 40 | 442µs | 80 | 102ms | $tmpl = $session->templates->readTemplate($template); # spent 101ms making 40 calls to Foswiki::Templates::readTemplate, avg 2.54ms/call
# spent 230µs making 40 calls to Foswiki::templates, avg 6µs/call |
496 | |||||
497 | #print STDERR "}}} $tmpl {{{\n"; | ||||
498 | # SMELL: the only META tags in a template will be METASEARCH | ||||
499 | # Why the heck are they being filtered???? | ||||
500 | #TODO: write a unit test that uses topic based templates with META's in them and if ok, remove. | ||||
501 | 40 | 55µs | $tmpl =~ s/\%META\{.*?\}\%//g; # remove %META{'parent'}% | ||
502 | |||||
503 | # Split template into 5 sections | ||||
504 | 40 | 61µs | my ( $tmplHead, $tmplSearch, $tmplTable, $tmplNumber, $tmplTail ) = | ||
505 | split( /%SPLIT%/, $tmpl ); | ||||
506 | |||||
507 | 40 | 31µs | my $repeatText; | ||
508 | |||||
509 | 40 | 45µs | if ( !defined($tmplTail) ) { | ||
510 | 40 | 238µs | 80 | 3.10ms | $tmplSearch = $session->templates->expandTemplate('SEARCH:searched'); # spent 3.02ms making 40 calls to Foswiki::Templates::expandTemplate, avg 75µs/call
# spent 79µs making 40 calls to Foswiki::templates, avg 2µs/call |
511 | 40 | 124µs | 80 | 4.86ms | $tmplNumber = $session->templates->expandTemplate('SEARCH:count'); # spent 4.80ms making 40 calls to Foswiki::Templates::expandTemplate, avg 120µs/call
# spent 69µs making 40 calls to Foswiki::templates, avg 2µs/call |
512 | |||||
513 | #it'd be nice to not need this if, but it seem that the noheader setting is ignored if a header= is set. truely bizzare | ||||
514 | #TODO: push up the 'noheader' evaluation to take not of this quirk | ||||
515 | #TODO: um, we die when ASSERT is on with a wide char in print | ||||
516 | 40 | 18µs | unless ($noHeader) { | ||
517 | $params->{header} = | ||||
518 | $session->templates->expandTemplate('SEARCH:header') | ||||
519 | unless defined $params->{header}; | ||||
520 | } | ||||
521 | |||||
522 | 40 | 129µs | 80 | 2.09ms | $repeatText = $session->templates->expandTemplate('SEARCH:format'); # spent 2.02ms making 40 calls to Foswiki::Templates::expandTemplate, avg 51µs/call
# spent 72µs making 40 calls to Foswiki::templates, avg 2µs/call |
523 | |||||
524 | 40 | 57µs | unless ($noFooter) { | ||
525 | $params->{footer} = | ||||
526 | $session->templates->expandTemplate('SEARCH:footer') | ||||
527 | unless defined $params->{footer}; | ||||
528 | } | ||||
529 | } | ||||
530 | else { | ||||
531 | |||||
532 | #Historical legacy form of the search TMPL's | ||||
533 | # header and footer of $web | ||||
534 | my $beforeText; | ||||
535 | my $afterText; | ||||
536 | ( $beforeText, $repeatText, $afterText ) = | ||||
537 | split( /%REPEAT%/, $tmplTable ); | ||||
538 | |||||
539 | unless ($noHeader) { | ||||
540 | $params->{header} = $beforeText unless defined $params->{header}; | ||||
541 | } | ||||
542 | |||||
543 | unless ($noFooter) { | ||||
544 | $params->{footer} ||= $afterText; | ||||
545 | } | ||||
546 | } | ||||
547 | |||||
548 | #nosummary="on" nosearch="on" noheader="on" nototal="on" | ||||
549 | 40 | 29µs | if ($noSummary) { | ||
550 | 40 | 20µs | $repeatText =~ s/%TEXTHEAD%//g; | ||
551 | 40 | 51µs | $repeatText =~ s/ //g; | ||
552 | } | ||||
553 | else { | ||||
554 | $repeatText =~ s/%TEXTHEAD%/\$summary(searchcontext)/g; | ||||
555 | } | ||||
556 | 40 | 49µs | $params->{format} ||= $repeatText; | ||
557 | |||||
558 | 40 | 121µs | $params->{footercounter} ||= $tmplNumber; | ||
559 | |||||
560 | 40 | 143µs | return $tmplSearch; | ||
561 | } | ||||
562 | |||||
563 | =begin TML | ||||
564 | ---++ formatResults | ||||
565 | |||||
566 | the implementation of %FORMAT{}% | ||||
567 | |||||
568 | TODO: rewrite to take a resultset, a set of params? and a hash of sub's to | ||||
569 | enable evaluations of things like '$include(blah)' in format strings. | ||||
570 | |||||
571 | have a default set of replacements like $lt, $nop, $percnt, $dollar etc, and then | ||||
572 | the hash of subs can take care of %MACRO{}% specific complex to evaluate replacements.. | ||||
573 | |||||
574 | (that way we don't pre-evaluate and then subst) | ||||
575 | |||||
576 | =cut | ||||
577 | |||||
578 | # spent 28.3s (516ms+27.7) within Foswiki::Search::formatResults which was called 40 times, avg 707ms/call:
# 40 times (516ms+27.7s) by Foswiki::Search::searchWeb at line 437, avg 707ms/call | ||||
579 | 40 | 35µs | my ( $this, $query, $infoCache, $params ) = @_; | ||
580 | 40 | 31µs | my $session = $this->{session}; | ||
581 | 40 | 32µs | my $users = $session->{users}; | ||
582 | 40 | 186µs | 40 | 314µs | my ( $callback, $cbdata ) = setup_callback($params); # spent 314µs making 40 calls to Foswiki::Search::setup_callback, avg 8µs/call |
583 | |||||
584 | 40 | 35µs | my $baseTopic = $session->{topicName}; | ||
585 | 40 | 21µs | my $baseWeb = $session->{webName}; | ||
586 | 40 | 75µs | 40 | 67µs | my $doBookView = Foswiki::isTrue( $params->{bookview} ); # spent 67µs making 40 calls to Foswiki::isTrue, avg 2µs/call |
587 | 40 | 59µs | 40 | 205µs | my $caseSensitive = Foswiki::isTrue( $params->{casesensitive} ); # spent 205µs making 40 calls to Foswiki::isTrue, avg 5µs/call |
588 | 40 | 90µs | 40 | 65µs | my $doExpandVars = Foswiki::isTrue( $params->{expandvariables} ); # spent 65µs making 40 calls to Foswiki::isTrue, avg 2µs/call |
589 | 40 | 67µs | 40 | 171µs | my $nonoise = Foswiki::isTrue( $params->{nonoise} ); # spent 171µs making 40 calls to Foswiki::isTrue, avg 4µs/call |
590 | 40 | 73µs | 40 | 60µs | my $noSearch = Foswiki::isTrue( $params->{nosearch}, $nonoise ); # spent 60µs making 40 calls to Foswiki::isTrue, avg 2µs/call |
591 | 40 | 55µs | my $formatDefined = defined $params->{format}; | ||
592 | 40 | 21µs | my $format = $params->{format} || ''; | ||
593 | 40 | 33µs | my $header = $params->{header} || ''; | ||
594 | 40 | 27µs | my $footer = $params->{footer} || ''; | ||
595 | 40 | 43µs | my $limit = $params->{limit} || ''; | ||
596 | 40 | 19µs | my $itemView = $params->{itemview}; | ||
597 | |||||
598 | # Limit search results. Cannot be deprecated | ||||
599 | # Limit will still be needed for the application types of SEARCHES | ||||
600 | # even if pagesize is added as feature. Example for searching and listing | ||||
601 | # text from first 5 bullets in a formatted multiple type search | ||||
602 | 40 | 39µs | if ( $limit =~ m/(^\d+$)/ ) { | ||
603 | |||||
604 | # only digits, all else is the same as | ||||
605 | # an empty string. "+10" won't work. | ||||
606 | $limit = $1; | ||||
607 | } | ||||
608 | else { | ||||
609 | |||||
610 | # change 'all' to 0, then to big number | ||||
611 | 40 | 16µs | $limit = 0; | ||
612 | } | ||||
613 | 40 | 16µs | $limit = 32000 unless ($limit); | ||
614 | |||||
615 | #pager formatting | ||||
616 | 40 | 12µs | my %pager_formatting; | ||
617 | 40 | 15µs | if ( $params->{paging_on} ) #TODO: if can skip() | ||
618 | { | ||||
619 | |||||
620 | #TODO: this is a dangerous assumption that should be abstracted | ||||
621 | ASSERT( $infoCache->isa('Foswiki::Iterator::PagerIterator') ) if DEBUG; | ||||
622 | |||||
623 | $limit = $infoCache->pagesize(); | ||||
624 | |||||
625 | my $paging_ID = $params->{pager_urlparam_id}; | ||||
626 | |||||
627 | # 1-based system; 0 is not a valid page number | ||||
628 | my $showpage = $infoCache->showpage(); | ||||
629 | |||||
630 | #TODO: need to ask the result set | ||||
631 | my $numberofpages = $infoCache->numberOfPages(); | ||||
632 | |||||
633 | #TODO: excuse me? | ||||
634 | my $sep = ' '; | ||||
635 | |||||
636 | my $nextidx = $showpage + 1; | ||||
637 | my $previousidx = $showpage - 1; | ||||
638 | |||||
639 | my %new_params; | ||||
640 | |||||
641 | #kill me please, i can't find a way to just load up the hash :( | ||||
642 | foreach my $key ( $session->{request}->param ) { | ||||
643 | $new_params{$key} = $session->{request}->param($key); | ||||
644 | } | ||||
645 | |||||
646 | $session->templates->readTemplate('searchformat'); | ||||
647 | |||||
648 | %pager_formatting = ( | ||||
649 | 'previouspage' => sub { return $previousidx }, | ||||
650 | 'currentpage' => sub { return $showpage }, | ||||
651 | 'nextpage' => sub { return $showpage + 1 }, | ||||
652 | 'numberofpages' => sub { return $infoCache->numberOfPages() }, | ||||
653 | 'pagesize' => sub { return $infoCache->pagesize() }, | ||||
654 | 'sep' => sub { return $sep; } | ||||
655 | ); | ||||
656 | |||||
657 | $pager_formatting{'previousurl'} = sub { | ||||
658 | my $previouspageurl = ''; | ||||
659 | if ( $previousidx >= 1 ) { | ||||
660 | $new_params{$paging_ID} = $previousidx; | ||||
661 | $previouspageurl = | ||||
662 | Foswiki::Func::getScriptUrl( $baseWeb, $baseTopic, 'view', | ||||
663 | %new_params ); | ||||
664 | } | ||||
665 | return $previouspageurl; | ||||
666 | }; | ||||
667 | |||||
668 | $pager_formatting{'previousbutton'} = sub { | ||||
669 | my $previouspagebutton = ''; | ||||
670 | if ( $previousidx >= 1 ) { | ||||
671 | $new_params{$paging_ID} = $previousidx; | ||||
672 | $previouspagebutton = | ||||
673 | $session->templates->expandTemplate('SEARCH:pager_previous'); | ||||
674 | } | ||||
675 | $previouspagebutton = | ||||
676 | $this->formatCommon( $previouspagebutton, \%pager_formatting ); | ||||
677 | return $previouspagebutton; | ||||
678 | }; | ||||
679 | |||||
680 | $pager_formatting{'nexturl'} = sub { | ||||
681 | my $nextpageurl = ''; | ||||
682 | if ( $nextidx <= $infoCache->numberOfPages() ) { | ||||
683 | $new_params{$paging_ID} = $nextidx; | ||||
684 | $nextpageurl = | ||||
685 | Foswiki::Func::getScriptUrl( $baseWeb, $baseTopic, 'view', | ||||
686 | %new_params ); | ||||
687 | } | ||||
688 | return $nextpageurl; | ||||
689 | }; | ||||
690 | |||||
691 | $pager_formatting{'nextbutton'} = sub { | ||||
692 | my $nextpagebutton = ''; | ||||
693 | if ( $nextidx <= $infoCache->numberOfPages() ) { | ||||
694 | $new_params{$paging_ID} = $nextidx; | ||||
695 | $nextpagebutton = | ||||
696 | $session->templates->expandTemplate('SEARCH:pager_next'); | ||||
697 | } | ||||
698 | $nextpagebutton = | ||||
699 | $this->formatCommon( $nextpagebutton, \%pager_formatting ); | ||||
700 | return $nextpagebutton; | ||||
701 | }; | ||||
702 | |||||
703 | $pager_formatting{'pager'} = sub { | ||||
704 | my $pager_control = ''; | ||||
705 | if ( $infoCache->numberOfPages() > 1 ) { | ||||
706 | $pager_control = $params->{pagerformat} | ||||
707 | || $session->templates->expandTemplate('SEARCH:pager'); | ||||
708 | $pager_control = | ||||
709 | $this->formatCommon( $pager_control, \%pager_formatting ); | ||||
710 | } | ||||
711 | return $pager_control; | ||||
712 | }; | ||||
713 | } | ||||
714 | |||||
715 | #TODO: multiple is an attribute of the ResultSet | ||||
716 | 40 | 107µs | 40 | 192µs | my $doMultiple = Foswiki::isTrue( $params->{multiple} ); # spent 192µs making 40 calls to Foswiki::isTrue, avg 5µs/call |
717 | 40 | 66µs | 40 | 184µs | my $noEmpty = Foswiki::isTrue( $params->{noempty}, $nonoise ); # spent 184µs making 40 calls to Foswiki::isTrue, avg 5µs/call |
718 | |||||
719 | # Note: a defined header/footer overrides noheader/nofooter | ||||
720 | # To maintain Cairo compatibility we ommit default header/footer if the | ||||
721 | 40 | 81µs | my $noHeader = | ||
722 | !defined($header) && Foswiki::isTrue( $params->{noheader}, $nonoise ) | ||||
723 | || ( !$header && $formatDefined ); | ||||
724 | |||||
725 | 40 | 32µs | my $noFooter = | ||
726 | !defined($footer) && Foswiki::isTrue( $params->{nofooter}, $nonoise ) | ||||
727 | || ( !$footer && $formatDefined ); | ||||
728 | |||||
729 | 40 | 91µs | 40 | 65µs | my $noSummary = Foswiki::isTrue( $params->{nosummary}, $nonoise ); # spent 65µs making 40 calls to Foswiki::isTrue, avg 2µs/call |
730 | 40 | 78µs | 40 | 54µs | my $zeroResults = # spent 54µs making 40 calls to Foswiki::isTrue, avg 1µs/call |
731 | Foswiki::isTrue( ( $params->{zeroresults} ), $nonoise || 1 ); | ||||
732 | 40 | 73µs | 40 | 53µs | my $noTotal = Foswiki::isTrue( $params->{nototal}, $nonoise ); # spent 53µs making 40 calls to Foswiki::isTrue, avg 1µs/call |
733 | 40 | 46µs | my $newLine = $params->{newline} || ''; | ||
734 | 40 | 26µs | my $separator = $params->{separator}; | ||
735 | 40 | 62µs | my $type = $params->{type} || ''; | ||
736 | |||||
737 | # output the list of topics in $web | ||||
738 | 40 | 37µs | my $ntopics = 0; # number of topics in current web | ||
739 | 40 | 9µs | my $nhits = 0; # number of hits (if multiple=on) in current web | ||
740 | 40 | 31µs | my $headerDone = $noHeader; | ||
741 | |||||
742 | 40 | 8µs | my $web = $baseWeb; | ||
743 | 40 | 115µs | 40 | 441µs | my $webObject = new Foswiki::Meta( $session, $web ); # spent 441µs making 40 calls to Foswiki::Meta::new, avg 11µs/call |
744 | 40 | 17µs | my $lastWebProcessed = ''; | ||
745 | |||||
746 | #total number of topics and hits - not reset when we swap webs | ||||
747 | 40 | 12µs | my $ttopics = 0; | ||
748 | 40 | 12µs | my $thits = 0; | ||
749 | |||||
750 | 40 | 92µs | 40 | 64µs | while ( $infoCache->hasNext() ) { # spent 64µs making 40 calls to Foswiki::Iterator::FilterIterator::hasNext, avg 2µs/call |
751 | 8760 | 14.6ms | 8760 | 59.9ms | my $listItem = $infoCache->next(); # spent 59.9ms making 8760 calls to Foswiki::Iterator::FilterIterator::next, avg 7µs/call |
752 | ASSERT( defined($listItem) ) if DEBUG; | ||||
753 | |||||
754 | ############################################################# | ||||
755 | #TOPIC specific | ||||
756 | 8760 | 4.20ms | my $topic = $listItem; | ||
757 | 8760 | 866µs | my $text; #undef means the formatResult() gets it from $info->text; | ||
758 | 8760 | 630µs | my $info; | ||
759 | 8760 | 5.74ms | my @multipleHitLines = (); | ||
760 | 8760 | 84.8ms | 17520 | 28.2ms | if ( # spent 28.2ms making 17520 calls to UNIVERSAL::isa, avg 2µs/call |
761 | ( $infoCache->isa('Foswiki::Search::ResultSet') ) or #SEARCH | ||||
762 | ( $infoCache->isa('Foswiki::Iterator::FilterIterator') ) or #SEARCH | ||||
763 | ( $infoCache->isa('Foswiki::Iterator::PagerIterator') ) or #SEARCH | ||||
764 | ( $infoCache->isa('Foswiki::Search::InfoCache') ) #FORMAT | ||||
765 | ) | ||||
766 | { | ||||
767 | 8760 | 25.1ms | 8760 | 130ms | ( $web, $topic ) = # spent 130ms making 8760 calls to Foswiki::Func::normalizeWebTopicName, avg 15µs/call |
768 | Foswiki::Func::normalizeWebTopicName( '', $listItem ); | ||||
769 | |||||
770 | # add dependencies (TODO: unclear if this should be before the paging, or after the allowView - sadly, it can't be _in_ the infoCache) | ||||
771 | 8760 | 5.71ms | if ( my $cache = $session->{cache} ) { | ||
772 | $cache->addDependency( $web, $topic ); | ||||
773 | } | ||||
774 | |||||
775 | 8760 | 21.3ms | 17520 | 87.3ms | my $topicMeta = $this->metacache->getMeta( $web, $topic ); # spent 73.0ms making 8760 calls to Foswiki::MetaCache::getMeta, avg 8µs/call
# spent 14.3ms making 8760 calls to Foswiki::Search::metacache, avg 2µs/call |
776 | 8760 | 1.16ms | if ( not defined($topicMeta) ) { | ||
777 | |||||
778 | #TODO: OMG! Search.pm relies on Meta::load (in the metacache) returning a meta object even when the topic does not exist. | ||||
779 | #lets change that | ||||
780 | $topicMeta = new Foswiki::Meta( $session, $web, $topic ); | ||||
781 | } | ||||
782 | 8760 | 17.3ms | 17520 | 357ms | $info = $this->metacache->get( $web, $topic, $topicMeta ); # spent 346ms making 8760 calls to Foswiki::MetaCache::get, avg 40µs/call
# spent 10.4ms making 8760 calls to Foswiki::Search::metacache, avg 1µs/call |
783 | ASSERT( defined( $info->{tom} ) ) if DEBUG; | ||||
784 | |||||
785 | 8760 | 2.16ms | $text = ''; | ||
786 | |||||
787 | # Special handling for format='...' | ||||
788 | 8760 | 3.22ms | if ($formatDefined) { | ||
789 | 8760 | 14.8ms | 8760 | 27.6ms | $text = $info->{tom}->text(); # spent 27.6ms making 8760 calls to Foswiki::Meta::text, avg 3µs/call |
790 | 8760 | 1.14ms | $text = '' unless defined $text; | ||
791 | |||||
792 | 8760 | 2.35ms | if ($doExpandVars) { | ||
793 | if ( $web eq $baseWeb && $topic eq $baseTopic ) { | ||||
794 | |||||
795 | # primitive way to prevent recursion | ||||
796 | $text =~ s/%SEARCH/%<nop>SEARCH/g; | ||||
797 | } | ||||
798 | $text = $info->{tom}->expandMacros($text); | ||||
799 | } | ||||
800 | } | ||||
801 | |||||
802 | #TODO: should extract this somehow | ||||
803 | |||||
804 | 8760 | 1.03ms | if ( $doMultiple && !$query->isEmpty() ) { | ||
805 | |||||
806 | #TODO: Sven wonders if this should be a HoistRE.. | ||||
807 | #TODO: well, um, and how does this work for query search? | ||||
808 | my @tokens = @{ $query->tokens() }; | ||||
809 | my $pattern = $tokens[$#tokens]; # last token in an AND search | ||||
810 | $pattern = quotemeta($pattern) if ( $type ne 'regex' ); | ||||
811 | $text = $info->{tom}->text() unless defined $text; | ||||
812 | $text = '' unless defined $text; | ||||
813 | |||||
814 | if ($caseSensitive) { | ||||
815 | @multipleHitLines = | ||||
816 | reverse grep { /$pattern/ } split( /[\n\r]+/, $text ); | ||||
817 | } | ||||
818 | else { | ||||
819 | @multipleHitLines = | ||||
820 | reverse grep { /$pattern/i } split( /[\n\r]+/, $text ); | ||||
821 | } | ||||
822 | } | ||||
823 | |||||
824 | # Apply heading offset - posibly to each hit result independently | ||||
825 | 8760 | 5.42ms | if ( $params->{headingoffset} | ||
826 | && $params->{headingoffset} =~ m/^([-+]?\d+)$/ | ||||
827 | && $1 != 0 ) | ||||
828 | { | ||||
829 | my ( $off, $noff ) = ( 0 + $1, 0 - $1 ); | ||||
830 | if ( scalar(@multipleHitLines) ) { | ||||
831 | @multipleHitLines = map { | ||||
832 | $_ =~ "<ho off=\"$off\">\n$text\n<ho off=\"$noff\"/>" | ||||
833 | } @multipleHitLines; | ||||
834 | } | ||||
835 | else { | ||||
836 | $text = "<ho off=\"$off\">\n$text\n<ho off=\"$noff\"/>"; | ||||
837 | } | ||||
838 | } | ||||
839 | } | ||||
840 | |||||
841 | 8760 | 3.04ms | $ntopics += 1; | ||
842 | 8760 | 1.23ms | $ttopics += 1; | ||
843 | 8760 | 11.7ms | do { # multiple=on loop | ||
844 | |||||
845 | 8760 | 848µs | $nhits += 1; | ||
846 | 8760 | 778µs | $thits += 1; | ||
847 | |||||
848 | 8760 | 3.13ms | $text = pop(@multipleHitLines) if ( scalar(@multipleHitLines) ); | ||
849 | |||||
850 | 8760 | 2.02ms | my $justdidHeaderOrFooter = 0; | ||
851 | 8760 | 7.29ms | if ( ( defined( $params->{groupby} ) ) | ||
852 | and ( $params->{groupby} eq 'web' ) ) | ||||
853 | { | ||||
854 | 8760 | 2.55ms | if ( $lastWebProcessed ne $web ) { | ||
855 | |||||
856 | #output the footer for the previous listItem | ||||
857 | 40 | 14µs | if ( $lastWebProcessed ne '' ) { | ||
858 | |||||
859 | #c&p from below | ||||
860 | #TODO: needs refactoring. | ||||
861 | my $processedfooter = $footer; | ||||
862 | if ( not $noTotal ) { | ||||
863 | $processedfooter .= $params->{footercounter}; | ||||
864 | } | ||||
865 | if ( defined($processedfooter) | ||||
866 | and ( $processedfooter ne '' ) ) | ||||
867 | { | ||||
868 | |||||
869 | #footer comes before result | ||||
870 | $ntopics--; | ||||
871 | $nhits--; | ||||
872 | |||||
873 | #because $pager contains more $ntopics like format strings, it needs to be expanded first. | ||||
874 | $processedfooter = | ||||
875 | $this->formatCommon( $processedfooter, | ||||
876 | \%pager_formatting ); | ||||
877 | $processedfooter =~ | ||||
878 | s/\$web/$lastWebProcessed/gs; # expand name of web | ||||
879 | |||||
880 | # $processedfooter =~ # s/([^\n])$/$1\n/s; # add new line at end | ||||
881 | # output footer of $web | ||||
882 | |||||
883 | $processedfooter =~ s/\$ntopics/$ntopics/gs; | ||||
884 | $processedfooter =~ s/\$nhits/$nhits/gs; | ||||
885 | $processedfooter =~ s/\$index/$thits/gs; | ||||
886 | |||||
887 | #legacy SEARCH counter support | ||||
888 | $processedfooter =~ s/%NTOPICS%/$ntopics/g; | ||||
889 | |||||
890 | $processedfooter = | ||||
891 | $this->formatCommon( $processedfooter, | ||||
892 | \%pager_formatting ); | ||||
893 | |||||
894 | # $processedfooter =~ # s/\n$//s; # remove trailing new line | ||||
895 | |||||
896 | $justdidHeaderOrFooter = 1; | ||||
897 | &$callback( $cbdata, $processedfooter ); | ||||
898 | |||||
899 | #go back to counting results | ||||
900 | $ntopics++; | ||||
901 | $nhits++; | ||||
902 | } | ||||
903 | } | ||||
904 | |||||
905 | #trigger a header for this new web | ||||
906 | 40 | 10µs | $headerDone = undef; | ||
907 | } | ||||
908 | } | ||||
909 | |||||
910 | 8760 | 1.34ms | if ( $lastWebProcessed ne $web ) { | ||
911 | 40 | 195µs | 40 | 462µs | $webObject = new Foswiki::Meta( $session, $web ); # spent 462µs making 40 calls to Foswiki::Meta::new, avg 12µs/call |
912 | 40 | 14µs | $lastWebProcessed = $web; | ||
913 | |||||
914 | #reset our web partitioned legacy counts | ||||
915 | 40 | 15µs | $ntopics = 1; | ||
916 | 40 | 16µs | $nhits = 1; | ||
917 | } | ||||
918 | |||||
919 | # lazy output of header (only if needed for the first time) | ||||
920 | 8760 | 3.88ms | if ( ( !$headerDone and ( defined($header) ) ) | ||
921 | and ( $header ne '' ) ) | ||||
922 | { | ||||
923 | |||||
924 | my $processedheader = $header; | ||||
925 | |||||
926 | # because $pager contains more $ntopics like format | ||||
927 | # strings, it needs to be expanded first. | ||||
928 | $processedheader = | ||||
929 | $this->formatCommon( $processedheader, \%pager_formatting ); | ||||
930 | $processedheader =~ s/\$web/$web/gs; # expand name of web | ||||
931 | |||||
932 | # add new line after the header unless separator is defined | ||||
933 | # per Item1773 / SearchSeparatorDefaultHeaderFooter | ||||
934 | unless ( defined $separator ) { | ||||
935 | $processedheader =~ s/([^\n])$/$1\n/s; | ||||
936 | } | ||||
937 | |||||
938 | $headerDone = 1; | ||||
939 | my $thisWebBGColor = $webObject->getPreference('WEBBGCOLOR') | ||||
940 | || '\#FF00FF'; | ||||
941 | $processedheader =~ s/%WEBBGCOLOR%/$thisWebBGColor/g; | ||||
942 | $processedheader =~ s/%WEB%/$web/g; | ||||
943 | $processedheader =~ s/\$ntopics/($ntopics-1)/gse; | ||||
944 | $processedheader =~ s/\$nhits/($nhits-1)/gse; | ||||
945 | $processedheader =~ s/\$index/($thits-1)/gs; | ||||
946 | $processedheader = | ||||
947 | $this->formatCommon( $processedheader, \%pager_formatting ); | ||||
948 | &$callback( $cbdata, $processedheader ); | ||||
949 | $justdidHeaderOrFooter = 1; | ||||
950 | } | ||||
951 | |||||
952 | 8760 | 1.06ms | if ( defined($separator) | ||
953 | and ( $thits > 1 ) | ||||
954 | and ( $justdidHeaderOrFooter != 1 ) ) | ||||
955 | { | ||||
956 | &$callback( $cbdata, $separator ); | ||||
957 | } | ||||
958 | |||||
959 | ###################Render the result | ||||
960 | 8760 | 985µs | my $out; | ||
961 | my $handleRev1Info = sub { | ||||
962 | |||||
963 | # Handle e.g. createdate, createwikiuser etc | ||||
964 | my $info = | ||||
965 | $this->metacache->get( $_[1]->web, $_[1]->topic, $_[1] ); | ||||
966 | my $r = $info->{tom}->getRev1Info( $_[0] ); | ||||
967 | return $r; | ||||
968 | 8760 | 29.0ms | }; | ||
969 | my $handleRevInfo = sub { | ||||
970 | return $session->renderer->renderRevisionInfo( | ||||
971 | $_[1], | ||||
972 | $info->{revNum} || 0, | ||||
973 | '$' . $_[0] | ||||
974 | ); | ||||
975 | 8760 | 13.7ms | }; | ||
976 | |||||
977 | 8760 | 3.50ms | if ( $formatDefined and ( $format ne '' ) ) { | ||
978 | |||||
979 | # SMELL: hack to convert a bad SEARCH format to the one | ||||
980 | # used by getRevInfo.. | ||||
981 | $format =~ s/\$createdate/\$createlongdate/gs; | ||||
982 | |||||
983 | # SMELL: it looks like $isodate in format is equive to $iso | ||||
984 | # in renderRevisionInfo | ||||
985 | # SMELL: clean these 3 hacks up | ||||
986 | $format =~ s/\$isodate/\$iso/gs; | ||||
987 | $format =~ s/\%TIME\%/\$date/gs; | ||||
988 | $format =~ s/\$date/\$longdate/gs; | ||||
989 | |||||
990 | # other tmpl based renderings | ||||
991 | $format =~ s/%WEB%/\$web/g; | ||||
992 | $format =~ s/%TOPICNAME%/\$topic/g; | ||||
993 | $format =~ s/%AUTHOR%/\$wikiusername/g; | ||||
994 | |||||
995 | # pass search options to summary parser | ||||
996 | my $searchOptions = { | ||||
997 | type => $params->{type}, | ||||
998 | wordboundaries => $params->{wordboundaries}, | ||||
999 | casesensitive => $caseSensitive, | ||||
1000 | tokens => $query ? $query->tokens() : undef, | ||||
1001 | }; | ||||
1002 | |||||
1003 | # SMELL: why is this not part of the callback? at least the | ||||
1004 | # non-result element format strings can be common here. | ||||
1005 | # or does Sven need a formatCommon sub that formatResult can | ||||
1006 | # also call.. (which then goes into the callback? | ||||
1007 | $out = $this->formatResult( | ||||
1008 | $format, | ||||
1009 | $info->{tom} || $webObject, #SMELL: horrid hack | ||||
1010 | $text, | ||||
1011 | $searchOptions, | ||||
1012 | { | ||||
1013 | 'ntopics' => sub { return $ntopics }, | ||||
1014 | 'nhits' => sub { return $nhits }, | ||||
1015 | 'index' => sub { return $thits }, | ||||
1016 | 'item' => sub { return $listItem }, | ||||
1017 | |||||
1018 | %pager_formatting, | ||||
1019 | |||||
1020 | 'revNum' => sub { return ( $info->{revNum} || 0 ); }, | ||||
1021 | 'doBookView' => sub { return $doBookView; }, | ||||
1022 | 'baseWeb' => sub { return $baseWeb; }, | ||||
1023 | 'baseTopic' => sub { return $baseTopic; }, | ||||
1024 | 'newLine' => sub { return $newLine; }, | ||||
1025 | 'separator' => sub { return $separator; }, | ||||
1026 | 'noTotal' => sub { return $noTotal; }, | ||||
1027 | 'paramsHash' => sub { return $params; }, | ||||
1028 | 'itemView' => sub { return $itemView; } | ||||
1029 | }, | ||||
1030 | { | ||||
1031 | |||||
1032 | #rev1 info | ||||
1033 | # TODO: move the $create* formats into Render::renderRevisionInfo.. | ||||
1034 | # which implies moving the infocache's pre-extracted data into the tom obj too. | ||||
1035 | # $out =~ s/\$create(longdate|username|wikiname|wikiusername)/ | ||||
1036 | # $infoCache->getRev1Info( $topic, "create$1" )/ges; | ||||
1037 | 'createlongdate' => $handleRev1Info, | ||||
1038 | 'createusername' => $handleRev1Info, | ||||
1039 | 'createwikiname' => $handleRev1Info, | ||||
1040 | 'createwikiusername' => $handleRev1Info, | ||||
1041 | 'createusername' => $handleRev1Info, | ||||
1042 | |||||
1043 | # rev info | ||||
1044 | 'web' => sub { return $_[1]->web }, | ||||
1045 | 'topic' => sub { | ||||
1046 | if ( defined $_[2] ) { | ||||
1047 | return Foswiki::Render::breakName( $_[1]->topic, | ||||
1048 | $_[2] ); | ||||
1049 | } | ||||
1050 | else { | ||||
1051 | return $_[1]->topic; | ||||
1052 | } | ||||
1053 | }, | ||||
1054 | 'rev' => $handleRevInfo, | ||||
1055 | 'wikiusername' => $handleRevInfo, | ||||
1056 | 'wikiname' => $handleRevInfo, | ||||
1057 | 'username' => $handleRevInfo, | ||||
1058 | 'iso' => $handleRevInfo, | ||||
1059 | 'longdate' => $handleRevInfo, | ||||
1060 | 'date' => $handleRevInfo, | ||||
1061 | } | ||||
1062 | ); | ||||
1063 | } | ||||
1064 | else { | ||||
1065 | 8760 | 2.70ms | $out = ''; | ||
1066 | } | ||||
1067 | |||||
1068 | 8760 | 40.8ms | 8760 | 17.0ms | &$callback( $cbdata, $out ); # spent 17.0ms making 8760 calls to Foswiki::Search::_collate_to_list, avg 2µs/call |
1069 | } while (@multipleHitLines); # multiple=on loop | ||||
1070 | |||||
1071 | 8760 | 24.2ms | 8760 | 27.0s | if ( # spent 27.0s making 8760 calls to Foswiki::Iterator::FilterIterator::hasNext, avg 3.09ms/call |
1072 | ( $params->{paging_on} ) | ||||
1073 | or ( ( defined( $params->{groupby} ) ) | ||||
1074 | and ( $params->{groupby} ne 'web' ) ) | ||||
1075 | ) | ||||
1076 | { | ||||
1077 | last if ( $ttopics >= $limit ); | ||||
1078 | } | ||||
1079 | else { | ||||
1080 | 8760 | 2.20ms | if ( $ntopics >= $limit ) { | ||
1081 | $infoCache->nextWeb(); | ||||
1082 | } | ||||
1083 | } | ||||
1084 | } # end topic loop | ||||
1085 | |||||
1086 | # output footer only if hits in web | ||||
1087 | 40 | 25µs | if ( $ntopics == 0 ) { | ||
1088 | if ( $zeroResults and not $noTotal ) { | ||||
1089 | $footer = $params->{footercounter}; | ||||
1090 | } | ||||
1091 | else { | ||||
1092 | $footer = ''; | ||||
1093 | } | ||||
1094 | ##MOVEDUP $webObject = new Foswiki::Meta( $session, $baseWeb ); | ||||
1095 | } | ||||
1096 | else { | ||||
1097 | 40 | 17µs | if ( ( not $noTotal ) and ( defined( $params->{footercounter} ) ) ) { | ||
1098 | $footer .= $params->{footercounter}; | ||||
1099 | } | ||||
1100 | |||||
1101 | 40 | 32µs | if ( $params->{pager} ) { | ||
1102 | |||||
1103 | #auto-append the pager | ||||
1104 | $footer .= '$pager'; | ||||
1105 | } | ||||
1106 | } | ||||
1107 | 40 | 25µs | if ( defined $footer ) { | ||
1108 | |||||
1109 | #because $pager contains more $ntopics like format strings, it needs to be expanded first. | ||||
1110 | 40 | 113µs | 40 | 282µs | $footer = $this->formatCommon( $footer, \%pager_formatting ); # spent 282µs making 40 calls to Foswiki::Search::formatCommon, avg 7µs/call |
1111 | 40 | 50µs | $footer =~ s/\$web/$web/gs; # expand name of web | ||
1112 | |||||
1113 | # $footer =~ s/([^\n])$/$1\n/s; # add new line at end | ||||
1114 | |||||
1115 | # output footer of $web | ||||
1116 | 40 | 21µs | $footer =~ s/\$ntopics/$ntopics/gs; | ||
1117 | 40 | 182µs | $footer =~ s/\$nhits/$nhits/gs; | ||
1118 | 40 | 27µs | $footer =~ s/\$index/$thits/gs; | ||
1119 | |||||
1120 | #legacy SEARCH counter support | ||||
1121 | 40 | 45µs | $footer =~ s/%NTOPICS%/$ntopics/g; | ||
1122 | |||||
1123 | # $footer =~ s/\n$//s; # remove trailing new line | ||||
1124 | |||||
1125 | 40 | 71µs | 40 | 68µs | &$callback( $cbdata, $footer ); # spent 68µs making 40 calls to Foswiki::Search::_collate_to_list, avg 2µs/call |
1126 | } | ||||
1127 | |||||
1128 | 40 | 39µs | my $result = ''; | ||
1129 | 40 | 369µs | if ( !defined $params->{_callback} && $nhits >= 0 ) { | ||
1130 | $result = join( '', @$cbdata ); | ||||
1131 | } | ||||
1132 | 40 | 275µs | return ( $ntopics, $result ); | ||
1133 | } | ||||
1134 | |||||
1135 | # spent 282µs within Foswiki::Search::formatCommon which was called 40 times, avg 7µs/call:
# 40 times (282µs+0s) by Foswiki::Search::formatResults at line 1110, avg 7µs/call | ||||
1136 | 40 | 43µs | my ( $this, $out, $customKeys ) = @_; | ||
1137 | |||||
1138 | 40 | 23µs | my $session = $this->{session}; | ||
1139 | |||||
1140 | 40 | 148µs | foreach my $key ( keys(%$customKeys) ) { | ||
1141 | $out =~ s/\$$key/&{$customKeys->{$key}}()/ges; | ||||
1142 | } | ||||
1143 | |||||
1144 | 40 | 122µs | return $out; | ||
1145 | } | ||||
1146 | |||||
1147 | =begin TML | ||||
1148 | |||||
1149 | ---++ ObjectMethod formatResult | ||||
1150 | * $text can be undefined. | ||||
1151 | * $searchOptions is an options hash to pass on to the summary parser | ||||
1152 | * $nonTomKeys is a hash of key -> sub($key,$item) where $item is *not* assumed to be a topic object. | ||||
1153 | * tomKeys is a hash of {'$key' => sub($key,$item,$params) } | ||||
1154 | where $item is a tom object (initially a Foswiki::Meta, but I'd like to be more generic) and $params is whatever is in trailing () | ||||
1155 | |||||
1156 | TODO: i don't really know what we'll need to do about order of processing. | ||||
1157 | TODO: at minimum, the keys need to be sorted by length so that $datatime is processed before $date | ||||
1158 | TODO: need to cater for $summary(params) style too | ||||
1159 | |||||
1160 | =cut | ||||
1161 | |||||
1162 | sub formatResult { | ||||
1163 | my ( $this, $out, $item, $text, $searchOptions, $nonTomKeys, $tomKeys ) = | ||||
1164 | @_; | ||||
1165 | |||||
1166 | my $session = $this->{session}; | ||||
1167 | |||||
1168 | #TODO: these need to go away. | ||||
1169 | my $revNum = &{ $nonTomKeys->{'revNum'} }(); | ||||
1170 | my $doBookView = &{ $nonTomKeys->{'doBookView'} }(); | ||||
1171 | my $baseWeb = &{ $nonTomKeys->{'baseWeb'} }(); | ||||
1172 | my $baseTopic = &{ $nonTomKeys->{'baseTopic'} }(); | ||||
1173 | my $newLine = &{ $nonTomKeys->{'newLine'} }(); | ||||
1174 | my $separator = &{ $nonTomKeys->{'separator'} }(); | ||||
1175 | my $noTotal = &{ $nonTomKeys->{'noTotal'} }(); | ||||
1176 | my $params = &{ $nonTomKeys->{'paramsHash'} }(); | ||||
1177 | my $itemView = &{ $nonTomKeys->{'itemView'} }(); | ||||
1178 | foreach my $key ( | ||||
1179 | 'revNum', 'doBookView', 'baseWeb', 'baseTopic', | ||||
1180 | 'newLine', 'separator', 'noTotal', 'paramsHash', | ||||
1181 | 'itemView' | ||||
1182 | ) | ||||
1183 | { | ||||
1184 | delete $tomKeys->{$key}; | ||||
1185 | delete $nonTomKeys->{$key}; | ||||
1186 | } | ||||
1187 | |||||
1188 | # render each item differently, based on SEARCH param 'itemview' | ||||
1189 | if ( $item->topic | ||||
1190 | && defined $itemView | ||||
1191 | && $itemView =~ m/([[:alnum:].\s\(\)\$]+)/ ) | ||||
1192 | { | ||||
1193 | |||||
1194 | # brackets added to regex to allow $formfield(name) | ||||
1195 | |||||
1196 | # add to skinpath - only to pass as param to readTemplate | ||||
1197 | $itemView = $1; | ||||
1198 | |||||
1199 | # parse formatted search tokens | ||||
1200 | $itemView =~ | ||||
1201 | s/\$formfield\(\s*([^\)]*)\s*\)/displayFormField( $item, $1, $newLine )/ges; | ||||
1202 | foreach my $key ( keys(%$tomKeys) ) { | ||||
1203 | $itemView =~ s[\$$key(?:\(([^\)]*)\))?] | ||||
1204 | [&{$tomKeys->{$key}}($key, $item, $1)]ges; | ||||
1205 | } | ||||
1206 | |||||
1207 | # load the appropriate template for this item | ||||
1208 | my $tmpl = | ||||
1209 | $session->templates->readTemplate( ucfirst $itemView . 'ItemView' ); | ||||
1210 | my $text = $session->templates->expandTemplate('LISTITEM'); | ||||
1211 | $out = $text if $text; | ||||
1212 | } | ||||
1213 | |||||
1214 | foreach my $key ( keys(%$nonTomKeys) ) { | ||||
1215 | $out =~ s/\$$key/&{$nonTomKeys->{$key}}($key, $item)/ges; | ||||
1216 | |||||
1217 | #print STDERR "1: $key $out\n"; | ||||
1218 | } | ||||
1219 | if ( $item->topic ) { | ||||
1220 | |||||
1221 | # Only process tomKeys if the item is a valid topicObject | ||||
1222 | foreach my $key ( keys(%$tomKeys) ) { | ||||
1223 | $out =~ s[\$$key(?:\(([^\)]*)\))?] | ||||
1224 | [&{$tomKeys->{$key}}($key, $item, $1)]ges; | ||||
1225 | } | ||||
1226 | |||||
1227 | # Note that we cannot send a formatted search through renderRevisionInfo | ||||
1228 | # without expanding tokens we should not because the function also sends | ||||
1229 | # the input through formatTime and probably other nasty filters | ||||
1230 | # So we send each token through one by one. | ||||
1231 | if ( $out =~ m/\$text/ ) { | ||||
1232 | $text = $item->text() unless defined $text; | ||||
1233 | $text = '' unless defined $text; | ||||
1234 | |||||
1235 | if ( $item->topic eq $session->{topicName} ) { | ||||
1236 | |||||
1237 | #TODO: extract the diffusion and generalise to whatever MACRO we are processing - anything with a format can loop | ||||
1238 | |||||
1239 | # defuse SEARCH in current topic to prevent loop | ||||
1240 | $text =~ s/%SEARCH\{.*?\}%/SEARCH{...}/g; | ||||
1241 | } | ||||
1242 | $out =~ s/\$text/$text/gs; | ||||
1243 | } | ||||
1244 | } | ||||
1245 | foreach my $key ( keys(%$nonTomKeys) ) { | ||||
1246 | $out =~ s/\$$key/&{$nonTomKeys->{$key}}($key, $item)/ges; | ||||
1247 | |||||
1248 | #print STDERR "2: $key $out\n"; | ||||
1249 | } | ||||
1250 | |||||
1251 | #TODO: extract the rev | ||||
1252 | my $srev = 'r' . $revNum; | ||||
1253 | if ( $revNum eq '0' || $revNum eq '1' ) { | ||||
1254 | $srev = CGI::span( { class => 'foswikiNew' }, | ||||
1255 | ( $session->i18n->maketext('NEW') ) ); | ||||
1256 | } | ||||
1257 | $out =~ s/%REVISION%/$srev/; | ||||
1258 | |||||
1259 | if ($doBookView) { | ||||
1260 | |||||
1261 | # BookView | ||||
1262 | $text = $item->text() unless defined $text; | ||||
1263 | $text = '' unless defined $text; | ||||
1264 | |||||
1265 | if ( $item->web eq $baseWeb && $item->topic eq $baseTopic ) { | ||||
1266 | |||||
1267 | # primitive way to prevent recursion | ||||
1268 | $text =~ s/%SEARCH/%<nop>SEARCH/g; | ||||
1269 | } | ||||
1270 | $text = $item->expandMacros($text); | ||||
1271 | $text = $item->renderTML($text); | ||||
1272 | |||||
1273 | $out =~ s/%TEXTHEAD%/$text/g; | ||||
1274 | |||||
1275 | } | ||||
1276 | else { | ||||
1277 | |||||
1278 | #TODO: more topic specific bits | ||||
1279 | if ( defined( $item->topic ) ) { | ||||
1280 | $out =~ s/\$summary(?:\(([^\)]*)\))?/ | ||||
1281 | $item->summariseText( $1, $text, $searchOptions )/ges; | ||||
1282 | $out =~ s/\$changes(?:\(([^\)]*)\))?/ | ||||
1283 | $item->summariseChanges(Foswiki::Store::cleanUpRevID($1), $revNum)/ges; | ||||
1284 | $out =~ s/\$formfield\(\s*([^\)]*)\s*\)/ | ||||
1285 | displayFormField( $item, $1, $newLine )/ges; | ||||
1286 | $out =~ s/\$parent\(([^\)]*)\)/ | ||||
1287 | Foswiki::Render::breakName( | ||||
1288 | $item->getParent(), $1 )/ges; | ||||
1289 | $out =~ s/\$parent/$item->getParent()/ges; | ||||
1290 | $out =~ s/\$formname/$item->getFormName()/ges; | ||||
1291 | $out =~ s/\$count\((.*?\s*\.\*)\)/_countPattern( $text, $1 )/ges; | ||||
1292 | |||||
1293 | # FIXME: Allow all regex characters but escape them | ||||
1294 | # Note: The RE requires a .* at the end of a pattern to avoid false positives | ||||
1295 | # in pattern matching | ||||
1296 | $out =~ | ||||
1297 | s/\$extract\((.*?\s*\.\*)\)/_extractPattern( $text, $1, 1 )/ges; | ||||
1298 | $out =~ | ||||
1299 | s/\$pattern\((.*?\s*\.\*)\)/_extractPattern( $text, $1, 0 )/ges; | ||||
1300 | } | ||||
1301 | $out =~ s/\r?\n/$newLine/gs if ($newLine); | ||||
1302 | |||||
1303 | # If separator is not defined we default to \n | ||||
1304 | # We also add new line after last search result but before footer | ||||
1305 | # when separator is not defined for backwards compatibility | ||||
1306 | # per Item1773 / SearchSeparatorDefaultHeaderFooter | ||||
1307 | if ( !defined($separator) ) { | ||||
1308 | unless ( $noTotal && !$params->{formatdefined} ) { | ||||
1309 | $out =~ s/([^\n])$/$1\n/s; | ||||
1310 | } | ||||
1311 | } | ||||
1312 | } | ||||
1313 | |||||
1314 | #see http://foswiki.org/Tasks/Item2371 - needs unit test exploration | ||||
1315 | #the problem is that when I separated the formating from the searching, I set the format string to what is in the template, | ||||
1316 | #and thus here, format is always set. | ||||
1317 | # elsif ($noSummary) { | ||||
1318 | # #TODO: i think that means I've broken SEARCH{nosummary=on" with no format specified | ||||
1319 | # $out =~ s/%TEXTHEAD%//g; | ||||
1320 | # $out =~ s/ //g; | ||||
1321 | # } | ||||
1322 | # else { | ||||
1323 | # #SEARCH with no format and nonoise="off" or nosummary="off" | ||||
1324 | # #TODO: BROKEN, need to fix the meaning of nosummary and nonoise in SEARCH | ||||
1325 | # # regular search view | ||||
1326 | # $text = $info->{tom}->summariseText( '', $text ); | ||||
1327 | # $out =~ s/%TEXTHEAD%/$text/g; | ||||
1328 | # } | ||||
1329 | return $out; | ||||
1330 | } | ||||
1331 | |||||
1332 | =begin TML | ||||
1333 | |||||
1334 | ---++ StaticMethod displayFormField( $meta, $args, $newline ) -> $text | ||||
1335 | |||||
1336 | Parse the arguments to a $formfield specification and extract | ||||
1337 | the relevant formfield from the given meta data. | ||||
1338 | |||||
1339 | * =args= string containing name of form field | ||||
1340 | * =$newline= - replacement text for newlines within the form field, if | ||||
1341 | not defined defaults to <br /> | ||||
1342 | |||||
1343 | In addition to the name of a field =args= can be appended with a commas | ||||
1344 | followed by a string format (\d+)([,\s*]\.\.\.)?). This supports the formatted | ||||
1345 | search function $formfield and is used to shorten the returned string or a | ||||
1346 | hyphenated string. | ||||
1347 | |||||
1348 | =cut | ||||
1349 | |||||
1350 | sub displayFormField { | ||||
1351 | my ( $meta, $args, $newline ) = @_; | ||||
1352 | |||||
1353 | $newline ||= '<br />'; | ||||
1354 | my ( $name, @params ) = split( /,\s*/, $args ); | ||||
1355 | my $format = '$value'; # default is to show the unmapped value | ||||
1356 | my $breakArgs = ''; | ||||
1357 | if (@params) { | ||||
1358 | if ( $params[0] eq 'display' ) { | ||||
1359 | |||||
1360 | # The displayed value is required | ||||
1361 | $format = '$value(display)'; | ||||
1362 | shift @params; | ||||
1363 | } | ||||
1364 | $breakArgs = join( ',', @params ); | ||||
1365 | } | ||||
1366 | |||||
1367 | # SMELL: this is a *terrible* idea. Not only does it munge the result | ||||
1368 | # so it can only be used for display, it also munges it such that it | ||||
1369 | # can't be repaired by the options on %SEARCH. :-( | ||||
1370 | return $meta->renderFormFieldForDisplay( | ||||
1371 | $name, $format, | ||||
1372 | { | ||||
1373 | break => $breakArgs, | ||||
1374 | protectdollar => 1, | ||||
1375 | showhidden => 1, | ||||
1376 | newline => $newline | ||||
1377 | } | ||||
1378 | ); | ||||
1379 | } | ||||
1380 | |||||
1381 | #my ($callback, $cbdata) = setup_callback(\%params, $baseWebObject); | ||||
1382 | sub setup_callback { | ||||
1383 | 80 | 63µs | my ( $params, $webObj ) = @_; | ||
1384 | |||||
1385 | 80 | 52µs | my $callback = $params->{_callback}; | ||
1386 | 80 | 27µs | my $cbdata = $params->{_cbdata}; | ||
1387 | |||||
1388 | #add in the rendering.. | ||||
1389 | 80 | 53µs | if ( defined( $params->{_callback} ) ) { | ||
1390 | $callback = sub { | ||||
1391 | my $cbdata = shift; | ||||
1392 | my $text = shift; | ||||
1393 | my $oldcallback = $params->{_callback}; | ||||
1394 | |||||
1395 | $text = $webObj->renderTML($text) if defined($webObj); | ||||
1396 | $text =~ s|</*nop/*>||gi; # remove <nop> tag | ||||
1397 | &$oldcallback( $cbdata, $text ); | ||||
1398 | }; | ||||
1399 | } | ||||
1400 | else { | ||||
1401 | 80 | 65µs | $cbdata = $params->{_cbdata} = [] unless ( defined($cbdata) ); | ||
1402 | 80 | 78µs | $callback = \&_collate_to_list; | ||
1403 | } | ||||
1404 | 80 | 225µs | return ( $callback, $cbdata ); | ||
1405 | } | ||||
1406 | |||||
1407 | # callback for search function to collate to list | ||||
1408 | sub _collate_to_list { | ||||
1409 | 8800 | 1.78ms | my $ref = shift; | ||
1410 | |||||
1411 | 8800 | 34.6ms | push( @$ref, @_ ); | ||
1412 | } | ||||
1413 | |||||
1414 | 1 | 2µs | 1; | ||
1415 | __END__ |