Monday, November 08, 2010 at 12:05 AM.
system.verbs.builtins.searchEngine.doSearch
on doSearch (sites, urlThisPage, adrCaller, args = "", adrPrefs=@user.searchEngine.prefs) {
<<Run the search. From a .wsf page, call searchEngine.searchMacro instead of this script.
<<If you're running from inside an .fcgi, you can call this script directly.
<<Parameters:
<<The sites parameter is a list of site names.
<<urlThisPage is the URL of the calling page.
<<adrCaller is the Frontier address of the calling page or CGI script.
<<args is the arg string.
<<The optional adrPrefs parameter lets you use multiple prefs tables for different searches.
<<You can duplicate and modify the table at user.searchEngine.prefs.
<<To create a new prefs table, call searchEngine.init with the address of a new prefs table.
<<This script returns HTML text.
local (searchString, searchType);
local (filteredSearchString);
local (numHits = 0, ctKeys);
local (htmlText);
local (searchTable, end);
local (argTable, totalHits);
local (origSearchString);
local (adrStrings = @adrPrefs^.strings);
local (adrDefaults = @adrPrefs^.defaults);
local (adrStopWords = @adrPrefs^.stopWords);
local (previewsList = {});
local (indexesList = {});
local (maxHitsPerPage = adrDefaults^.hitsPerPage, hitsThisPage, start = 1);
urlThisPage = string.nthField (urlThisPage, '/', string.countFields (urlThisPage, '/'));
new (tableType, @argTable);
new (tableType, @searchTable);
loop {
if typeOf (adrStopWords) != addressType {
break};
adrStopWords = adrStopWords^};
if typeOf (sites) != listType {
scriptError ("Can't run a search because the sites parameter is not a list.")};
on add (s) {
htmlText = htmlText + s};
on getPreview (adrItem) {
local (prefix = "\r<p>\r" + (start + numHits) + ") ");
local (i);
for i = 1 to sizeOf (previewsList) {
try {
return (prefix + previewsList [i]^.[adrItem])}};
return (prefix + adrItem)};
on find (keywords) {
local (x, flAND = (searchType == "and"));
local (countKeys = string.countFields (keywords, ' '));
local (ctIgnored = 0);
local (resultsTables = {});
local (oldTarget);
local (adrSearchString);
for x = 1 to countKeys { //loop through keywords, finding matches
local (i);
local (searchString = string.nthField (keywords, ' ', x));
searchString = string.popTrailing (searchString, 's');
searchString = string.dropNonAlphas (searchString);
if not (searchEngine.checkStopWords (searchString, adrStopWords)) {
ctIgnored++;
continue};
for i = 1 to sizeOf (indexesList) { //loop through indexes, looking for matches
local (adrIndex = indexesList [i]);
local (firstLetter = string.mid (searchString, 1, 1));
local (adrLetter = @adrIndex^.[firstLetter]);
if defined (adrLetter^) {
local (adrResults = @adrLetter^.[searchString]);
if defined (adrResults^) {
resultsTables = resultsTables + adrResults}}}};
if resultsTables == {} { //was nothing found?
return (adrStrings^.nothingFound)};
searchEngine.mergeResults (resultsTables, @searchTable); //combine the search results into one table
bundle { //sort results by relevancy
oldTarget = target.set (@searchTable);
target.set (@searchTable);
table.sortBy ("Value")};
local (results);
local (min = ((countKeys - ctIgnored) - 1) * 4000);
local (adrTable = @searchTable);
totalHits = sizeOf (adrTable^);
if flAnd { //don't count OR matches
for i = totalHits downTo 1 {
local (value = adrTable^ [i], name = nameOf (adrTable^ [i]));
if value < min {
totalHits = (totalHits - i);
break}}};
local (top = (sizeOf (adrTable^) + 1) - start);
local (bottom = (top + 1) - maxHitsPerPage);
end = (start + maxHitsPerPage) - 1;
if end > totalHits {
end = totalHits};
if bottom < 1 {
bottom = 1};
for i = top downTo bottom {
local (value = adrTable^ [i], name = nameOf (adrTable^ [i]));
if flAND {
if value < min {
break}};
if numHits == 0 {
add ("<<replacenumhits>>")};
results = results + getPreview (name);
numHits++};
target.set (oldTarget);
add (results);
bundle { //log search
if adrPrefs^.logSearches {
local (adrSearches = @adrPrefs^.searches);
if not defined (adrSearches^) {
new (tableType, adrSearches)};
local (adrPageSearches = @adrSearches^.[adrCaller]);
if not defined (adrPageSearches^) {
new (tableType, adrPageSearches)};
local (adrDaySearches = @adrPageSearches^.[date.shortString (clock.now ())]);
if not defined (adrDaySearches^) {
new (tableType, adrDaySearches)};
adrSearchString = @adrDaySearches^.[string.urlDecode (origSearchString)];
if not defined (adrSearchString^) {
new (tableType, adrSearchString);
adrSearchString^.ct = 0};
if typeOf (adrSearchString^) != tableType {
local (ct = adrSearchString^);
delete (adrSearchString);
new (tableType, adrSearchString);
adrSearchString^.ct = ct};
adrSearchString^.ct++;
adrSearchString^.[searchType] = totalHits}}};
bundle { //get the search args
if args != "" { //is this a search query?
webserver.parseArgs (args, @argTable);
try { //search string
searchString = string.urlDecode (argTable.q);
origSearchString = argTable.q;
origSearchString = string.replaceAll (origSearchString, " ", "+")}
else {
searchString = ""};
try { //search type
searchType = string.urlDecode (argTable.t);
searchType = string.lower (searchType)}
else {
searchType = string.lower (adrDefaults^.searchType)};
if searchType != "or" and searchType != "and" { //it must be "or" or "and"
searchType = "and"};
try { //max hits per page
maxHitsPerPage = string.urlDecode (argTable.m);
maxHitsPerPage = number (maxHitsPerPage)}
else {
maxHitsPerPage = adrDefaults^.hitsPerPage;
if typeOf (maxHitsPerPage) != numberType { //this must be a number
try {
maxHitsPerPage = number (maxHitsPerPage)}
else {
maxHitsPerPage = 10}}}; //fallback position
try { //start
start = string.urlDecode (argTable.s);
start = number (start)}
else {
start = 1}}};
bundle { //add the form
if searchString == "" or adrPrefs^.formOnResultsPage { //do we place the form on this page?
local (adrForm = @adrPrefs^.form);
if defined (adrForm^) { //over-ride the default form with sysop's form
loop {
if typeOf (adrForm^) == addressType { //the form can be an address
adrForm = adrForm^}
else {
break}};
if typeOf (adrForm^) == scriptType {
add (adrForm^ (argTable, sites, urlThisPage, adrCaller, adrPrefs))}
else {
add (string (adrForm^))}}
else { //build the default form
on addOption (value, visibleValue, matchValue) {
add ("<option value=\"" + value + "\"");
if matchValue == value {
add (" selected")};
add (">" + visibleValue + "\r")};
add ("<form method=\"GET\" action=\"" + urlThisPage + "\">\r");
if adrPrefs^.booleanPopup {
add ("<br> <br>Find ");
add ("<select name=\"t\">\r");
addOption ("and", "all the words", searchType);
addOption ("or", "any of the words", searchType);
add ("</select>\r");
add (" in<p>\r")}
else {
add ("<br> <br>Find ");
if string.lower (searchType) == "and" {
add ("all the words in:<p>\r");
add ("<input name=\"t\" type=\"hidden\" value=\"and\">")}
else {
add ("any of the words in:<p>\r");
add ("<input name=\"t\" type=\"hidden\" value=\"or\">")}};
add ("<input name=\"q\" size=30");
if searchString != "" {
add (" value=\"" + string.replaceAll (searchString, "\"", """) + "\"")};
add ("><p>\r");
if adrPrefs^.matchesPopup { //add the matches per page popup?
add ("Matches per page: ");
add ("<select name=\"m\">\r");
addOption (10, "10", maxHitsPerPage);
addOption (25, "25", maxHitsPerPage);
addOption (50, "50", maxHitsPerPage);
addOption (100, "100", maxHitsPerPage);
add ("</select>\r");
add ("<p>\r")}
else {
add ("<input name=\"m\" type=\"hidden\" value=\"" + maxHitsPerPage + "\">")};
add ("<input name=\"s\" type=\"hidden\" value=\"1\">\r"); //starting hit
add ("<input type=\"submit\" value=\"Search\">\r</form>\r<p>\r");
add (string (adrStrings^.searchTips))};
if searchString == "" { //we're not doing a search, just return the form.
return (htmlText)};
add ("<p><hr noshade size=1><p>")}}; //add a separator between the form and the results
bundle { //build list of indexes and previews addresses
local (i);
for i = 1 to sizeOf (sites) {
indexesList [i] = searchEngine.getIndexAddress (sites [i]);
<<Get the address of the previews table.
<<For the sake of speed, don't call searchEngine.getPreviewsAddress.
local (adr = indexesList [i]);
adr = parentOf (adr^);
adr = @adr^.previews;
previewsList [i] = adr}};
bundle { //add "Searching for" text
local (searchingFor = adrStrings^.searchingFor);
searchingFor = searchEngine.replaceAll (searchingFor, "<<searchString>>", searchString, true);
add (searchingFor + "<p>\r")};
bundle { //filter the search string: remove markup, punctuation, etc.
filteredSearchString = searchEngine.cleanText (searchString);
local(theOS = sys.os());
if theOS == "MacOS" || theOS == "MacCn" {
filteredSearchString = latinToMac.convert (filteredSearchString)}}; //convert Latin to Mac characters
find (filteredSearchString); //do the find
bundle { //report number of matches; add links to more screens
if totalHits < 1 {
add ("<p>" + adrStrings^.nothingFound)}
else {
if totalHits == 1 {
htmlText = searchEngine.replaceAll (htmlText, "<<replacenumhits>>", adrStrings^.oneMatchFound + "<p>\r", true)}
else {
local (replacer = adrStrings^.matchesFound + "<p>\r");
replacer = searchEngine.replaceAll (replacer, "<<start>>", start, true);
replacer = searchEngine.replaceAll (replacer, "<<end>>", end, true);
replacer = searchEngine.replaceAll (replacer, "<<totalHits>>", totalHits, true);
htmlText = searchEngine.replaceAll (htmlText, "<<replaceNumHits>>", replacer, true);
on doMoreLinks () {
local (totalScreens = ((totalHits - 1) / maxHitsPerPage) + 1);
totalScreens = number (totalScreens);
if totalScreens == 1 {
return (false)};
<<if mod (totalHits, maxHitsPerPage) == 0
<<totalScreens = totalScreens - 1
local (thisScreen = number (start / maxHitsPerPage));
local (i);
add ("<p>\r" + adrStrings^.moreMatches + "<p>\r");
for i = 0 to totalScreens - 1 {
if i == thisScreen {
add (i + 1)}
else {
local (url = urlThisPage + "?");
url = url + "t=" + argTable.t;
url = url + "&q=" + string.urlEncode (origSearchString);
url = url + "&m=" + argTable.m;
url = url + "&s=" + ((i * maxHitsPerPage) + 1);
add (html.getLink (i + 1, url))};
if i != totalScreens - 1 {
add (" - ")}}};
doMoreLinks ()}}};
return (htmlText)}
This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.