add more spacing
[personal-kdebase.git] / runtime / kurifilter-plugins / ikws / kuriikwsfiltereng.cpp
blobe68afaf99568406c52815fe26048d2900dd9b5b9
2 /* This file is part of the KDE project
4 Copyright (C) 2002, 2003 Dawit Alemayehu <adawit@kde.org>
5 Copyright (C) 2000 Yves Arrouye <yves@realnames.com>
6 Copyright (C) 1999 Simon Hausmann <hausmann@kde.org>
8 Advanced web shortcuts:
9 Copyright (C) 2001 Andreas Hochsteger <e9625392@student.tuwien.ac.at>
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 #include "kuriikwsfiltereng.h"
29 #include <unistd.h>
31 #include <QTextCodec>
33 #include <kdebug.h>
34 #include <kconfiggroup.h>
35 #include <kprotocolinfo.h>
37 #include "searchprovider.h"
39 #define PIDDBG kDebug(7023) << "(" << getpid() << ") "
40 #define PDVAR(n,v) PIDDBG << n << " = '" << v << "'\n"
42 /**
43 * IMPORTANT: If you change anything here, please run the regression test
44 * ../tests/kurifiltertest
47 class KURISearchFilterEnginePrivate
49 public:
50 KURISearchFilterEngine instance;
53 K_GLOBAL_STATIC(KURISearchFilterEnginePrivate, kURISearchFilterEngine)
55 KURISearchFilterEngine::KURISearchFilterEngine()
57 loadConfig();
60 QString KURISearchFilterEngine::webShortcutQuery( const QString& typedString ) const
62 QString result;
64 if (m_bWebShortcutsEnabled)
66 QString search = typedString;
67 int pos = search.indexOf(m_cKeywordDelimiter);
69 QString key;
70 if ( pos > -1 )
71 key = search.left(pos);
72 else if ( m_cKeywordDelimiter == ' ' && !search.isEmpty() )
73 key = search;
75 if (!key.isEmpty() && !KProtocolInfo::isKnownProtocol( key ))
77 SearchProvider *provider = SearchProvider::findByKey(key);
79 if (provider)
81 result = formatResult(provider->query(), provider->charset(),
82 QString(), search.mid(pos+1), true);
83 delete provider;
88 return result;
92 QString KURISearchFilterEngine::autoWebSearchQuery( const QString& typedString ) const
94 QString result;
96 if (m_bWebShortcutsEnabled && !m_defaultSearchEngine.isEmpty())
98 // Make sure we ignore supported protocols, e.g. "smb:", "http:"
99 int pos = typedString.indexOf(':');
101 if (pos == -1 || !KProtocolInfo::isKnownProtocol(typedString.left(pos)))
103 SearchProvider *provider = SearchProvider::findByDesktopName(m_defaultSearchEngine);
105 if (provider)
107 result = formatResult (provider->query(), provider->charset(),
108 QString(), typedString, true);
109 delete provider;
114 return result;
117 QByteArray KURISearchFilterEngine::name() const
119 return "kuriikwsfilter";
122 KURISearchFilterEngine* KURISearchFilterEngine::self()
124 return &kURISearchFilterEngine->instance;
127 QStringList KURISearchFilterEngine::modifySubstitutionMap(SubstMap& map,
128 const QString& query) const
130 // Returns the number of query words
131 QString userquery = query;
133 // Do some pre-encoding, before we can start the work:
135 int start = 0;
136 int pos = 0;
137 QRegExp qsexpr("\\\"[^\\\"]*\\\"");
139 // Temporary substitute spaces in quoted strings (" " -> "%20")
140 // Needed to split user query into StringList correctly.
141 while ((pos = qsexpr.indexIn(userquery, start)) >= 0)
143 int i = 0;
144 int n = 0;
145 QString s = userquery.mid (pos, qsexpr.matchedLength());
146 while ((i = s.indexOf(" ")) != -1)
148 s = s.replace (i, 1, "%20");
149 n++;
151 start = pos + qsexpr.matchedLength() + 2*n; // Move after last quote
152 userquery = userquery.replace (pos, qsexpr.matchedLength(), s);
156 // Split user query between spaces:
157 QStringList l = userquery.simplified().split(" ", QString::SkipEmptyParts);
159 // Back-substitute quoted strings (%20 -> " "):
161 int i = 0;
162 while ((i = userquery.indexOf("%20")) != -1)
163 userquery = userquery.replace(i, 3, " ");
165 for ( QStringList::Iterator it = l.begin(); it != l.end(); ++it )
166 *it = (*it).replace("%20", " ");
169 PIDDBG << "Generating substitution map:\n";
170 // Generate substitution map from user query:
171 for (int i=0; i<=l.count(); i++)
173 int j = 0;
174 int pos = 0;
175 QString v = "";
176 QString nr = QString::number(i);
178 // Add whole user query (\{0}) to substitution map:
179 if (i==0)
180 v = userquery;
181 // Add partial user query items to substitution map:
182 else
183 v = l[i-1];
185 // Back-substitute quoted strings (%20 -> " "):
186 while ((j = v.indexOf("%20")) != -1)
187 v = v.replace(j, 3, " ");
189 // Insert partial queries (referenced by \1 ... \n) to map:
190 map.insert(QString::number(i), v);
191 PDVAR (" map['" + nr + "']", map[nr]);
193 // Insert named references (referenced by \name) to map:
194 j = 0;
195 if ((i>0) && (pos = v.indexOf("=")) > 0)
197 QString s = v.mid(pos + 1);
198 QString k = v.left(pos);
200 // Back-substitute references contained in references (e.g. '\refname' substitutes to 'thisquery=\0')
201 while ((j = s.indexOf("%5C")) != -1) s = s.replace(j, 3, "\\");
202 map.insert(k, s);
203 PDVAR (" map['" + k + "']", map[k]);
207 return l;
210 static QString encodeString(const QString &s, int mib)
212 Q_UNUSED( mib ); // removed in KDE4/Qt4.
213 QStringList l = s.split(" ");
214 for(QStringList::Iterator it = l.begin();
215 it != l.end(); ++it)
217 *it = QLatin1String( QUrl::toPercentEncoding( *it ) ); //KUrl::encode_string(*it);
219 return l.join("+");
222 QString KURISearchFilterEngine::substituteQuery(const QString& url, SubstMap &map, const QString& userquery, const int encodingMib) const
224 QString newurl = url;
225 QStringList ql = modifySubstitutionMap (map, userquery);
226 int count = ql.count();
228 // Check, if old style '\1' is found and replace it with \{@} (compatibility mode):
230 int pos = -1;
231 if ((pos = newurl.indexOf("\\1")) >= 0)
233 PIDDBG << "WARNING: Using compatibility mode for newurl='" << newurl
234 << "'. Please replace old style '\\1' with new style '\\{0}' "
235 "in the query definition.\n";
236 newurl = newurl.replace(pos, 2, "\\{@}");
240 PIDDBG << "Substitute references:\n";
241 // Substitute references (\{ref1,ref2,...}) with values from user query:
243 int pos = 0;
244 QRegExp reflist("\\\\\\{[^\\}]+\\}");
246 // Substitute reflists (\{ref1,ref2,...}):
247 while ((pos = reflist.indexIn(newurl)) >= 0)
249 bool found = false;
251 //bool rest = false;
252 QString v = "";
253 QString rlstring = newurl.mid(pos + 2, reflist.matchedLength() - 3);
254 PDVAR (" reference list", rlstring);
256 // \{@} gets a special treatment later
257 if (rlstring == "@")
259 v = "\\@";
260 found = true;
263 // TODO: strip whitespaces around commas
264 QStringList rl = rlstring.split(",", QString::SkipEmptyParts);
265 int i = 0;
267 while ((i<rl.count()) && !found)
269 QString rlitem = rl[i];
270 QRegExp range("[0-9]*\\-[0-9]*");
272 // Substitute a range of keywords
273 if (range.indexIn(rlitem) >= 0)
275 int pos = rlitem.indexOf("-");
276 int first = rlitem.left(pos).toInt();
277 int last = rlitem.right(rlitem.length()-pos-1).toInt();
279 if (first == 0)
280 first = 1;
282 if (last == 0)
283 last = count;
285 for (int i=first; i<=last; i++)
287 v += map[QString::number(i)] + ' ';
288 // Remove used value from ql (needed for \{@}):
289 ql[i-1] = "";
292 v = v.trimmed();
293 if (!v.isEmpty())
294 found = true;
296 PDVAR (" range", QString::number(first) + '-' + QString::number(last) + " => '" + v + '\'');
297 v = encodeString(v, encodingMib);
299 else if ( rlitem.startsWith('\"') && rlitem.endsWith('\"') )
301 // Use default string from query definition:
302 found = true;
303 QString s = rlitem.mid(1, rlitem.length() - 2);
304 v = encodeString(s, encodingMib);
305 PDVAR (" default", s);
307 else if (map.contains(rlitem))
309 // Use value from substitution map:
310 found = true;
311 PDVAR (" map['" + rlitem + "']", map[rlitem]);
312 v = encodeString(map[rlitem], encodingMib);
314 // Remove used value from ql (needed for \{@}):
315 QString c = rlitem.left(1);
316 if (c=="0")
318 // It's a numeric reference to '0'
319 for (QStringList::Iterator it = ql.begin(); it!=ql.end(); ++it)
320 (*it) = "";
322 else if ((c>="0") && (c<="9"))
324 // It's a numeric reference > '0'
325 int n = rlitem.toInt();
326 ql[n-1] = "";
328 else
330 // It's a alphanumeric reference
331 QStringList::Iterator it = ql.begin();
332 while ((it != ql.end()) && ((rlitem + '=') != (*it).left(rlitem.length()+1)))
333 ++it;
334 if ((rlitem + '=') == (*it).left(rlitem.length()+1))
335 (*it) = "";
338 // Encode '+', otherwise it would be interpreted as space in the resulting url:
339 int vpos = 0;
340 while ((vpos = v.indexOf('+')) != -1)
341 v = v.replace (vpos, 1, "%2B");
344 else if (rlitem == "@")
346 v = "\\@";
347 PDVAR (" v", v);
350 i++;
353 newurl = newurl.replace(pos, reflist.matchedLength(), v);
356 // Special handling for \{@};
358 PDVAR (" newurl", newurl);
359 // Generate list of unmatched strings:
360 QString v = "";
361 for (int i=0; i<ql.count(); i++) {
362 v += ' ' + ql[i];
364 v = v.simplified();
365 PDVAR (" rest", v);
366 v = encodeString(v, encodingMib);
368 // Substitute \{@} with list of unmatched query strings
369 int vpos = 0;
370 while ((vpos = newurl.indexOf("\\@")) != -1)
371 newurl = newurl.replace (vpos, 2, v);
375 return newurl;
378 QString KURISearchFilterEngine::formatResult( const QString& url,
379 const QString& cset1,
380 const QString& cset2,
381 const QString& query,
382 bool isMalformed ) const
384 SubstMap map;
385 return formatResult (url, cset1, cset2, query, isMalformed, map);
388 QString KURISearchFilterEngine::formatResult( const QString& url,
389 const QString& cset1,
390 const QString& cset2,
391 const QString& query,
392 bool /* isMalformed */,
393 SubstMap& map ) const
395 // Return nothing if userquery is empty and it contains
396 // substitution strings...
397 if (query.isEmpty() && url.indexOf(QRegExp(QRegExp::escape("\\{"))) > 0)
398 return QString();
400 // Debug info of map:
401 if (!map.isEmpty())
403 PIDDBG << "Got non-empty substitution map:\n";
404 for(SubstMap::Iterator it = map.begin(); it != map.end(); ++it)
405 PDVAR (" map['" + it.key() + "']", it.value());
408 // Create a codec for the desired encoding so that we can transcode the user's "url".
409 QString cseta = cset1;
410 if (cseta.isEmpty())
411 cseta = "iso-8859-1";
413 QTextCodec *csetacodec = QTextCodec::codecForName(cseta.toLatin1());
414 if (!csetacodec)
416 cseta = "iso-8859-1";
417 csetacodec = QTextCodec::codecForName(cseta.toLatin1());
420 // Decode user query:
421 QString userquery = QUrl::fromPercentEncoding( query.toUtf8() );
423 PDVAR ("user query", userquery);
424 PDVAR ("query definition", url);
426 // Add charset indicator for the query to substitution map:
427 map.insert("ikw_charset", cseta);
429 // Add charset indicator for the fallback query to substitution map:
430 QString csetb = cset2;
431 if (csetb.isEmpty())
432 csetb = "iso-8859-1";
433 map.insert("wsc_charset", csetb);
435 QString newurl = substituteQuery (url, map, userquery, csetacodec->mibEnum());
437 PDVAR ("substituted query", newurl);
439 return newurl;
442 void KURISearchFilterEngine::loadConfig()
444 PIDDBG << "Keywords Engine: Loading config..." << endl;
446 // Load the config.
447 KConfig config( name() + "rc", KConfig::NoGlobals );
448 KConfigGroup group = config.group( "General" );
450 m_cKeywordDelimiter = QString(group.readEntry("KeywordDelimiter", ":")).at(0).toLatin1();
451 m_bWebShortcutsEnabled = group.readEntry("EnableWebShortcuts", true);
452 m_defaultSearchEngine = group.readEntry("DefaultSearchEngine");
453 m_bVerbose = group.readEntry("Verbose", false);
455 // Use either a white space or a : as the keyword delimiter...
456 if (strchr (" :",m_cKeywordDelimiter) == 0)
457 m_cKeywordDelimiter = ':';
459 PIDDBG << "Keyword Delimiter: " << m_cKeywordDelimiter << endl;
460 PIDDBG << "Default Search Engine: " << m_defaultSearchEngine << endl;
461 PIDDBG << "Web Shortcuts Enabled: " << m_bWebShortcutsEnabled << endl;
462 PIDDBG << "Verbose: " << m_bVerbose << endl;