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"
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"
43 * IMPORTANT: If you change anything here, please run the regression test
44 * ../tests/kurifiltertest
47 class KURISearchFilterEnginePrivate
50 KURISearchFilterEngine instance
;
53 K_GLOBAL_STATIC(KURISearchFilterEnginePrivate
, kURISearchFilterEngine
)
55 KURISearchFilterEngine::KURISearchFilterEngine()
60 QString
KURISearchFilterEngine::webShortcutQuery( const QString
& typedString
) const
64 if (m_bWebShortcutsEnabled
)
66 QString search
= typedString
;
67 int pos
= search
.indexOf(m_cKeywordDelimiter
);
71 key
= search
.left(pos
);
72 else if ( m_cKeywordDelimiter
== ' ' && !search
.isEmpty() )
75 if (!key
.isEmpty() && !KProtocolInfo::isKnownProtocol( key
))
77 SearchProvider
*provider
= SearchProvider::findByKey(key
);
81 result
= formatResult(provider
->query(), provider
->charset(),
82 QString(), search
.mid(pos
+1), true);
92 QString
KURISearchFilterEngine::autoWebSearchQuery( const QString
& typedString
) const
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
);
107 result
= formatResult (provider
->query(), provider
->charset(),
108 QString(), typedString
, true);
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:
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)
145 QString s
= userquery
.mid (pos
, qsexpr
.matchedLength());
146 while ((i
= s
.indexOf(" ")) != -1)
148 s
= s
.replace (i
, 1, "%20");
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 -> " "):
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
++)
176 QString nr
= QString::number(i
);
178 // Add whole user query (\{0}) to substitution map:
181 // Add partial user query items to substitution map:
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:
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, "\\");
203 PDVAR (" map['" + k
+ "']", map
[k
]);
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();
217 *it
= QLatin1String( QUrl::toPercentEncoding( *it
) ); //KUrl::encode_string(*it);
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):
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:
244 QRegExp
reflist("\\\\\\{[^\\}]+\\}");
246 // Substitute reflists (\{ref1,ref2,...}):
247 while ((pos
= reflist
.indexIn(newurl
)) >= 0)
253 QString rlstring
= newurl
.mid(pos
+ 2, reflist
.matchedLength() - 3);
254 PDVAR (" reference list", rlstring
);
256 // \{@} gets a special treatment later
263 // TODO: strip whitespaces around commas
264 QStringList rl
= rlstring
.split(",", QString::SkipEmptyParts
);
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();
285 for (int i
=first
; i
<=last
; i
++)
287 v
+= map
[QString::number(i
)] + ' ';
288 // Remove used value from ql (needed for \{@}):
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:
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:
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);
318 // It's a numeric reference to '0'
319 for (QStringList::Iterator it
= ql
.begin(); it
!=ql
.end(); ++it
)
322 else if ((c
>="0") && (c
<="9"))
324 // It's a numeric reference > '0'
325 int n
= rlitem
.toInt();
330 // It's a alphanumeric reference
331 QStringList::Iterator it
= ql
.begin();
332 while ((it
!= ql
.end()) && ((rlitem
+ '=') != (*it
).left(rlitem
.length()+1)))
334 if ((rlitem
+ '=') == (*it
).left(rlitem
.length()+1))
338 // Encode '+', otherwise it would be interpreted as space in the resulting url:
340 while ((vpos
= v
.indexOf('+')) != -1)
341 v
= v
.replace (vpos
, 1, "%2B");
344 else if (rlitem
== "@")
353 newurl
= newurl
.replace(pos
, reflist
.matchedLength(), v
);
356 // Special handling for \{@};
358 PDVAR (" newurl", newurl
);
359 // Generate list of unmatched strings:
361 for (int i
=0; i
<ql
.count(); i
++) {
366 v
= encodeString(v
, encodingMib
);
368 // Substitute \{@} with list of unmatched query strings
370 while ((vpos
= newurl
.indexOf("\\@")) != -1)
371 newurl
= newurl
.replace (vpos
, 2, v
);
378 QString
KURISearchFilterEngine::formatResult( const QString
& url
,
379 const QString
& cset1
,
380 const QString
& cset2
,
381 const QString
& query
,
382 bool isMalformed
) const
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)
400 // Debug info of map:
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
;
411 cseta
= "iso-8859-1";
413 QTextCodec
*csetacodec
= QTextCodec::codecForName(cseta
.toLatin1());
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
;
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
);
442 void KURISearchFilterEngine::loadConfig()
444 PIDDBG
<< "Keywords Engine: Loading config..." << endl
;
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
;