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