minor fixes for clang++
[senf.git] / doclib / search_functions.php
1 <?php include 'search_paths.php'; ?>
2 <?php
3
4 function search_results()
5 {
6   return "Search Results";
7 }
8
9 function matches_text($num)
10 {
11   if ($num==0)
12   {
13     return "Sorry, no documents matching your query.";
14   }
15   else if ($num==1)
16   {
17     return "Found <b>1</b> document matching your query.";
18   }
19   else // $num>1
20   {
21     return "Found <b>$num</b> documents matching your query. Showing best matches first.";
22   }
23 }
24
25 function report_matches()
26 {
27   return "Matches: ";
28 }
29 function inputfield()
30 {
31   $query="";
32   if (array_key_exists("query", $_GET))
33   {
34     $query=$_GET["query"];
35   }
36   echo "<input type=\"text\" name=\"query\" value=\"$query\" size=\"20\" accesskey=\"s\"/>";
37 }
38
39 function readInt($file)
40 {
41   $b1 = ord(fgetc($file)); $b2 = ord(fgetc($file));
42   $b3 = ord(fgetc($file)); $b4 = ord(fgetc($file));
43   return ($b1<<24)|($b2<<16)|($b3<<8)|$b4;
44 }
45
46 function readString($file)
47 {
48   $result="";
49   while (ord($c=fgetc($file))) $result.=$c;
50   return $result;
51 }
52
53 function readHeader($file)
54 {
55   $header =fgetc($file); $header.=fgetc($file);
56   $header.=fgetc($file); $header.=fgetc($file);
57   return $header;
58 }
59
60 function computeIndex($word)
61 {
62   // Fast string hashing
63   //$lword = strtolower($word);
64   //$l = strlen($lword);
65   //for ($i=0;$i<$l;$i++)
66   //{
67   //  $c = ord($lword{$i});
68   //  $v = (($v & 0xfc00) ^ ($v << 6) ^ $c) & 0xffff;
69   //}
70   //return $v;
71
72   // Simple hashing that allows for substring search
73   if (strlen($word)<2) return -1;
74   // high char of the index
75   $hi = ord($word{0});
76   if ($hi==0) return -1;
77   // low char of the index
78   $lo = ord($word{1});
79   if ($lo==0) return -1;
80   // return index
81   return $hi*256+$lo;
82 }
83
84 function do_search($path, $file,$word,&$statsList)
85 {
86   $index = computeIndex($word);
87   if ($index!=-1) // found a valid index
88   {
89     fseek($file,$index*4+4); // 4 bytes per entry, skip header
90     $index = readInt($file);
91     if ($index) // found words matching the hash key
92     {
93       $start=sizeof($statsList);
94       $count=$start;
95       fseek($file,$index);
96       $w = readString($file);
97       while ($w)
98       {
99         $statIdx = readInt($file);
100         if ($word==substr($w,0,strlen($word)))
101         { // found word that matches (as substring)
102           $statsList[$count++]=array(
103               "word"=>$word,
104               "match"=>$w,
105               "index"=>$statIdx,
106               "full"=>strlen($w)==strlen($word),
107               "docs"=>array()
108               );
109         }
110         $w = readString($file);
111       }
112       $totalHi=0;
113       $totalFreqHi=0;
114       $totalFreqLo=0;
115       for ($count=$start;$count<sizeof($statsList);$count++)
116       {
117         $statInfo = &$statsList[$count];
118         $multiplier = 1;
119         // whole word matches have a double weight
120         if ($statInfo["full"]) $multiplier=2;
121         fseek($file,$statInfo["index"]); 
122         $numDocs = readInt($file);
123         $docInfo = array();
124         // read docs info + occurrence frequency of the word
125         for ($i=0;$i<$numDocs;$i++)
126         {
127           $idx=readInt($file); 
128           $freq=readInt($file); 
129           $docInfo[$i]=array("idx"  => $idx,
130                              "freq" => $freq>>1,
131                              "rank" => 0.0,
132                              "hi"   => $freq&1
133                             );
134           if ($freq&1) // word occurs in high priority doc
135           {
136             $totalHi++;
137             $totalFreqHi+=$freq*$multiplier;
138           }
139           else // word occurs in low priority doc
140           {
141             $totalFreqLo+=$freq*$multiplier;
142           }
143         }
144         // read name and url info for the doc
145         for ($i=0;$i<$numDocs;$i++)
146         {
147           fseek($file,$docInfo[$i]["idx"]);
148           $docInfo[$i]["name"]=readString($file);
149           $docInfo[$i]["url"]=$path.readString($file);
150         }
151         $statInfo["docs"]=$docInfo;
152       }
153       $totalFreq=($totalHi+1)*$totalFreqLo + $totalFreqHi;
154       for ($count=$start;$count<sizeof($statsList);$count++)
155       {
156         $statInfo = &$statsList[$count];
157         $multiplier = 1;
158         // whole word matches have a double weight
159         if ($statInfo["full"]) $multiplier=2;
160         for ($i=0;$i<sizeof($statInfo["docs"]);$i++)
161         {
162           $docInfo = &$statInfo["docs"];
163           // compute frequency rank of the word in each doc
164           $freq=$docInfo[$i]["freq"];
165           if ($docInfo[$i]["hi"])
166           {
167             $statInfo["docs"][$i]["rank"]=
168               (float)($freq*$multiplier+$totalFreqLo)/$totalFreq;
169           }
170           else
171           {
172             $statInfo["docs"][$i]["rank"]=
173               (float)($freq*$multiplier)/$totalFreq;
174           }
175         }
176       }
177     }
178   }
179   return $statsList;
180 }
181
182 function combine_results($results,&$docs)
183 {
184   foreach ($results as $wordInfo)
185   {
186     $docsList = &$wordInfo["docs"];
187     foreach ($docsList as $di)
188     {
189       $key=$di["url"];
190       $rank=$di["rank"];
191       if (in_array($key, array_keys($docs)))
192       {
193         $docs[$key]["rank"]+=$rank;
194       }
195       else
196       {
197         $docs[$key] = array("url"=>$key,
198             "name"=>$di["name"],
199             "rank"=>$rank
200             );
201       }
202       $docs[$key]["words"][] = array(
203                "word"=>$wordInfo["word"],
204                "match"=>$wordInfo["match"],
205                "freq"=>$di["freq"]
206                );
207     }
208   }
209   return $docs;
210 }
211
212 function filter_results($docs,&$requiredWords,&$forbiddenWords)
213 {
214   $filteredDocs=array();
215   while (list ($key, $val) = each ($docs)) 
216   {
217     $words = &$docs[$key]["words"];
218     $copy=1; // copy entry by default
219     if (sizeof($requiredWords)>0)
220     {
221       foreach ($requiredWords as $reqWord)
222       {
223         $found=0;
224         foreach ($words as $wordInfo)
225         { 
226           $found = $wordInfo["word"]==$reqWord;
227           if ($found) break;
228         }
229         if (!$found) 
230         {
231           $copy=0; // document contains none of the required words
232           break;
233         }
234       }
235     }
236     if (sizeof($forbiddenWords)>0)
237     {
238       foreach ($words as $wordInfo)
239       {
240         if (in_array($wordInfo["word"],$forbiddenWords))
241         {
242           $copy=0; // document contains a forbidden word
243           break;
244         }
245       }
246     }
247     if ($copy) $filteredDocs[$key]=$docs[$key];
248   }
249   return $filteredDocs;
250 }
251
252 function compare_rank($a,$b)
253 {
254   if ($a["rank"] == $b["rank"]) 
255   {
256     return 0;
257   }
258   return ($a["rank"]>$b["rank"]) ? -1 : 1; 
259 }
260
261 function sort_results($docs,&$sorted)
262 {
263   $sorted = $docs;
264   usort($sorted,"compare_rank");
265   return $sorted;
266 }
267
268 function report_results(&$docs)
269 {
270   echo "<table cellspacing=\"2\">\n";
271   echo "  <tr>\n";
272   echo "    <td colspan=\"2\"><h2>".search_results()."</h2></td>\n";
273   echo "  </tr>\n";
274   $numDocs = sizeof($docs);
275   if ($numDocs==0)
276   {
277     echo "  <tr>\n";
278     echo "    <td colspan=\"2\">".matches_text(0)."</td>\n";
279     echo "  </tr>\n";
280   }
281   else
282   {
283     echo "  <tr>\n";
284     echo "    <td colspan=\"2\">".matches_text($numDocs);
285     echo "\n";
286     echo "    </td>\n";
287     echo "  </tr>\n";
288     $num=1;
289     foreach ($docs as $doc)
290     {
291       echo "  <tr>\n";
292       echo "    <td align=\"right\">$num.</td>";
293       echo     "<td><a class=\"el\" href=\"".$doc["url"]."\">".$doc["name"]."</a></td>\n";
294       echo "  <tr>\n";
295       echo "    <td></td><td class=\"tiny\">".report_matches()." ";
296       foreach ($doc["words"] as $wordInfo)
297       {
298         $word = $wordInfo["word"];
299         $matchRight = substr($wordInfo["match"],strlen($word));
300         echo "<b>$word</b>$matchRight(".$wordInfo["freq"].") ";
301       }
302       echo "    </td>\n";
303       echo "  </tr>\n";
304       $num++;
305     }
306   }
307   echo "</table>\n";
308 }
309
310 function search()
311 {
312   if(strcmp('4.1.0', phpversion()) > 0) 
313   {
314     die("Error: PHP version 4.1.0 or above required!");
315   }
316
317   $paths = paths();
318   $files = array();
319   $j=0;
320   for ($i=0; $i<sizeof($paths); $i++) {
321     if (!($f=@fopen($paths[$i]."search.idx","rb"))) 
322     {
323       die("Error: Search index file could NOT be opened!");
324       continue;
325     }
326     $files[$j++] = $f;
327     if (readHeader($f)!="DOXS")
328     {
329       die("Error: Header of index file is invalid!");
330     }
331   }
332  
333   $query="";
334   if (array_key_exists("query", $_GET))
335   {
336     $query=$_GET["query"];
337   }
338   //end_form($query);
339   echo "&nbsp;\n<div class=\"searchresults\">\n";
340   $results = array();
341   $requiredWords = array();
342   $forbiddenWords = array();
343   $foundWords = array();
344   $word=strtok($query," ");
345   while ($word) // for each word in the search query
346   {
347     if (($word{0}=='+')) { $word=substr($word,1); $requiredWords[]=$word; }
348     if (($word{0}=='-')) { $word=substr($word,1); $forbiddenWords[]=$word; }
349     if (!in_array($word,$foundWords))
350     {
351       $foundWords[]=$word;
352       for ($i=0; $i<sizeof($files); $i++) {
353         do_search($paths[$i], $files[$i], strtolower($word), $results);
354       }
355     }
356     $word=strtok(" ");
357   }
358   $docs = array();
359   combine_results($results,$docs);
360   // filter out documents with forbidden word or that do not contain
361   // required words
362   $filteredDocs = filter_results($docs,$requiredWords,$forbiddenWords);
363   // sort the results based on rank
364   $sorted = array();
365   sort_results($filteredDocs,$sorted);
366   // report results to the user
367   report_results($sorted);
368   echo "</div>\n";
369   foreach ($files as $file) {
370     fclose($file);
371   }
372 }
373
374
375 ?>