PageRenderTime 51ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/local/arora-0.11.0/src/opensearch/opensearchengine.cpp

http://mingw-lib.googlecode.com/
C++ | 609 lines | 291 code | 81 blank | 237 comment | 55 complexity | 006305496cd136ec563be5dde564ad9f MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0, MPL-2.0-no-copyleft-exception
  1. /*
  2. * Copyright 2009 Jakub Wieczorek <faw217@gmail.com>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software
  16. * Foundation, Inc., 51 Franklin Street, Fifth Floor,
  17. * Boston, MA 02110-1301 USA
  18. */
  19. #include "opensearchengine.h"
  20. #include "opensearchenginedelegate.h"
  21. #include <qbuffer.h>
  22. #include <qcoreapplication.h>
  23. #include <qlocale.h>
  24. #include <qnetworkrequest.h>
  25. #include <qnetworkreply.h>
  26. #include <qregexp.h>
  27. #include <qscriptengine.h>
  28. #include <qscriptvalue.h>
  29. #include <qstringlist.h>
  30. /*!
  31. \class OpenSearchEngine
  32. \brief A class representing a single search engine described in OpenSearch format
  33. OpenSearchEngine is a class that represents a single search engine based on
  34. the OpenSearch format.
  35. For more information about the format, see http://www.opensearch.org/.
  36. Instances of the class hold all the data associated with the corresponding search
  37. engines, such as name(), description() and also URL templates that are used
  38. to construct URLs, which can be used later to perform search queries. Search engine
  39. can also have an image, even an external one, in this case it will be downloaded
  40. automatically from the network.
  41. OpenSearchEngine instances can be constructed from scratch but also read from
  42. external sources and written back to them. OpenSearchReader and OpenSearchWriter
  43. are the classes provided for reading and writing OpenSearch descriptions.
  44. Default constructed engines need to be filled with the necessary information before
  45. they can be used to peform search requests. First of all, a search engine should have
  46. the metadata including the name and the description.
  47. However, the most important are URL templates, which are the construction of URLs
  48. but can also contain template parameters, that are replaced with corresponding values
  49. at the time of constructing URLs.
  50. There are two types of URL templates: search URL template and suggestions URL template.
  51. Search URL template is needed for constructing search URLs, which point directly to
  52. search results. Suggestions URL template is necessary to construct suggestion queries
  53. URLs, which are then used for requesting contextual suggestions, a popular service
  54. offered along with search results that provides search terms related to what has been
  55. supplied by the user.
  56. Both types of URLs are constructed by the class, by searchUrl() and suggestionsUrl()
  57. functions respectively. However, search requests are supposed to be performed outside
  58. the class, while suggestion queries can be executed using the requestSuggestions()
  59. method. The class will take care of peforming the network request and parsing the
  60. JSON response.
  61. Both the image request and suggestion queries need network access. The class can
  62. perform network requests on its own, though the client application needs to provide
  63. a network access manager, which then will to be used for network operations.
  64. Without that, both images delivered from remote locations and contextual suggestions
  65. will be disabled.
  66. \sa OpenSearchReader, OpenSearchWriter
  67. */
  68. /*!
  69. Constructs an engine with a given \a parent.
  70. */
  71. OpenSearchEngine::OpenSearchEngine(QObject *parent)
  72. : QObject(parent)
  73. , m_searchMethod(QLatin1String("get"))
  74. , m_suggestionsMethod(QLatin1String("get"))
  75. , m_networkAccessManager(0)
  76. , m_suggestionsReply(0)
  77. , m_scriptEngine(0)
  78. , m_delegate(0)
  79. {
  80. m_requestMethods.insert(QLatin1String("get"), QNetworkAccessManager::GetOperation);
  81. m_requestMethods.insert(QLatin1String("post"), QNetworkAccessManager::PostOperation);
  82. }
  83. /*!
  84. A destructor.
  85. */
  86. OpenSearchEngine::~OpenSearchEngine()
  87. {
  88. if (m_scriptEngine)
  89. m_scriptEngine->deleteLater();
  90. }
  91. QString OpenSearchEngine::parseTemplate(const QString &searchTerm, const QString &searchTemplate)
  92. {
  93. QString language = QLocale().name();
  94. // Simple conversion to RFC 3066.
  95. language = language.replace(QLatin1Char('_'), QLatin1Char('-'));
  96. QString result = searchTemplate;
  97. result.replace(QLatin1String("{count}"), QLatin1String("20"));
  98. result.replace(QLatin1String("{startIndex}"), QLatin1String("0"));
  99. result.replace(QLatin1String("{startPage}"), QLatin1String("0"));
  100. result.replace(QLatin1String("{language}"), language);
  101. result.replace(QLatin1String("{inputEncoding}"), QLatin1String("UTF-8"));
  102. result.replace(QLatin1String("{outputEncoding}"), QLatin1String("UTF-8"));
  103. result.replace(QRegExp(QLatin1String("\\{([^\\}]*:|)source\\??\\}")), QCoreApplication::applicationName());
  104. result.replace(QLatin1String("{searchTerms}"), QLatin1String(QUrl::toPercentEncoding(searchTerm)));
  105. return result;
  106. }
  107. /*!
  108. \property OpenSearchEngine::name
  109. \brief the name of the engine
  110. \sa description()
  111. */
  112. QString OpenSearchEngine::name() const
  113. {
  114. return m_name;
  115. }
  116. void OpenSearchEngine::setName(const QString &name)
  117. {
  118. m_name = name;
  119. }
  120. /*!
  121. \property OpenSearchEngine::description
  122. \brief the description of the engine
  123. \sa name()
  124. */
  125. QString OpenSearchEngine::description() const
  126. {
  127. return m_description;
  128. }
  129. void OpenSearchEngine::setDescription(const QString &description)
  130. {
  131. m_description = description;
  132. }
  133. /*!
  134. \property OpenSearchEngine::searchUrlTemplate
  135. \brief the template of the search URL
  136. \sa searchUrl(), searchParameters(), suggestionsUrlTemplate()
  137. */
  138. QString OpenSearchEngine::searchUrlTemplate() const
  139. {
  140. return m_searchUrlTemplate;
  141. }
  142. void OpenSearchEngine::setSearchUrlTemplate(const QString &searchUrlTemplate)
  143. {
  144. m_searchUrlTemplate = searchUrlTemplate;
  145. }
  146. /*!
  147. Constructs and returns a search URL with a given \a searchTerm.
  148. The URL template is processed according to the specification:
  149. http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax
  150. A list of template parameters currently supported and what they are replaced with:
  151. \table
  152. \header \o parameter
  153. \o value
  154. \row \o "{count}"
  155. \o "20"
  156. \row \o "{startIndex}"
  157. \o "0"
  158. \row \o "{startPage}"
  159. \o "0"
  160. \row \o "{language}"
  161. \o "the default language code (RFC 3066)"
  162. \row \o "{inputEncoding}"
  163. \o "UTF-8"
  164. \row \o "{outputEncoding}"
  165. \o "UTF-8"
  166. \row \o "{*:source}"
  167. \o "application name, QCoreApplication::applicationName()"
  168. \row \o "{searchTerms}"
  169. \o "the string supplied by the user"
  170. \endtable
  171. \sa searchUrlTemplate(), searchParameters(), suggestionsUrl()
  172. */
  173. QUrl OpenSearchEngine::searchUrl(const QString &searchTerm) const
  174. {
  175. if (m_searchUrlTemplate.isEmpty())
  176. return QUrl();
  177. QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_searchUrlTemplate).toUtf8());
  178. if (m_searchMethod != QLatin1String("post")) {
  179. Parameters::const_iterator end = m_searchParameters.constEnd();
  180. Parameters::const_iterator i = m_searchParameters.constBegin();
  181. for (; i != end; ++i)
  182. retVal.addQueryItem(i->first, parseTemplate(searchTerm, i->second));
  183. }
  184. return retVal;
  185. }
  186. /*!
  187. \property providesSuggestions
  188. \brief indicates whether the engine supports contextual suggestions
  189. */
  190. bool OpenSearchEngine::providesSuggestions() const
  191. {
  192. return !m_suggestionsUrlTemplate.isEmpty();
  193. }
  194. /*!
  195. \property OpenSearchEngine::suggestionsUrlTemplate
  196. \brief the template of the suggestions URL
  197. \sa suggestionsUrl(), suggestionsParameters(), searchUrlTemplate()
  198. */
  199. QString OpenSearchEngine::suggestionsUrlTemplate() const
  200. {
  201. return m_suggestionsUrlTemplate;
  202. }
  203. void OpenSearchEngine::setSuggestionsUrlTemplate(const QString &suggestionsUrlTemplate)
  204. {
  205. m_suggestionsUrlTemplate = suggestionsUrlTemplate;
  206. }
  207. /*!
  208. Constructs a suggestions URL with a given \a searchTerm.
  209. The URL template is processed according to the specification:
  210. http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax
  211. See searchUrl() for more information about processing template parameters.
  212. \sa suggestionsUrlTemplate(), suggestionsParameters(), searchUrl()
  213. */
  214. QUrl OpenSearchEngine::suggestionsUrl(const QString &searchTerm) const
  215. {
  216. if (m_suggestionsUrlTemplate.isEmpty())
  217. return QUrl();
  218. QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_suggestionsUrlTemplate).toUtf8());
  219. if (m_suggestionsMethod != QLatin1String("post")) {
  220. Parameters::const_iterator end = m_suggestionsParameters.constEnd();
  221. Parameters::const_iterator i = m_suggestionsParameters.constBegin();
  222. for (; i != end; ++i)
  223. retVal.addQueryItem(i->first, parseTemplate(searchTerm, i->second));
  224. }
  225. return retVal;
  226. }
  227. /*!
  228. \property searchParameters
  229. \brief additional parameters that will be included in the search URL
  230. For more information see:
  231. http://www.opensearch.org/Specifications/OpenSearch/Extensions/Parameter/1.0
  232. */
  233. OpenSearchEngine::Parameters OpenSearchEngine::searchParameters() const
  234. {
  235. return m_searchParameters;
  236. }
  237. void OpenSearchEngine::setSearchParameters(const Parameters &searchParameters)
  238. {
  239. m_searchParameters = searchParameters;
  240. }
  241. /*!
  242. \property suggestionsParameters
  243. \brief additional parameters that will be included in the suggestions URL
  244. For more information see:
  245. http://www.opensearch.org/Specifications/OpenSearch/Extensions/Parameter/1.0
  246. */
  247. OpenSearchEngine::Parameters OpenSearchEngine::suggestionsParameters() const
  248. {
  249. return m_suggestionsParameters;
  250. }
  251. void OpenSearchEngine::setSuggestionsParameters(const Parameters &suggestionsParameters)
  252. {
  253. m_suggestionsParameters = suggestionsParameters;
  254. }
  255. /*!
  256. \property searchMethod
  257. \brief HTTP request method that will be used to perform search requests
  258. */
  259. QString OpenSearchEngine::searchMethod() const
  260. {
  261. return m_searchMethod;
  262. }
  263. void OpenSearchEngine::setSearchMethod(const QString &method)
  264. {
  265. QString requestMethod = method.toLower();
  266. if (!m_requestMethods.contains(requestMethod))
  267. return;
  268. m_searchMethod = requestMethod;
  269. }
  270. /*!
  271. \property suggestionsMethod
  272. \brief HTTP request method that will be used to perform suggestions requests
  273. */
  274. QString OpenSearchEngine::suggestionsMethod() const
  275. {
  276. return m_suggestionsMethod;
  277. }
  278. void OpenSearchEngine::setSuggestionsMethod(const QString &method)
  279. {
  280. QString requestMethod = method.toLower();
  281. if (!m_requestMethods.contains(requestMethod))
  282. return;
  283. m_suggestionsMethod = requestMethod;
  284. }
  285. /*!
  286. \property imageUrl
  287. \brief the image URL of the engine
  288. When setting a new image URL, it won't be loaded immediately. The first request will be
  289. deferred until image() is called for the first time.
  290. \note To be able to request external images, you need to provide a network access manager,
  291. which will be used for network operations.
  292. \sa image(), networkAccessManager()
  293. */
  294. QString OpenSearchEngine::imageUrl() const
  295. {
  296. return m_imageUrl;
  297. }
  298. void OpenSearchEngine::setImageUrl(const QString &imageUrl)
  299. {
  300. m_imageUrl = imageUrl;
  301. }
  302. void OpenSearchEngine::loadImage() const
  303. {
  304. if (!m_networkAccessManager || m_imageUrl.isEmpty())
  305. return;
  306. QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(QUrl::fromEncoded(m_imageUrl.toUtf8())));
  307. connect(reply, SIGNAL(finished()), this, SLOT(imageObtained()));
  308. }
  309. void OpenSearchEngine::imageObtained()
  310. {
  311. QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
  312. if (!reply)
  313. return;
  314. QByteArray response = reply->readAll();
  315. reply->close();
  316. reply->deleteLater();
  317. if (response.isEmpty())
  318. return;
  319. m_image.loadFromData(response);
  320. emit imageChanged();
  321. }
  322. /*!
  323. \property image
  324. \brief the image of the engine
  325. When no image URL has been set and an image will be set explicitly, a new data URL
  326. will be constructed, holding the image data encoded with Base64.
  327. \sa imageUrl()
  328. */
  329. QImage OpenSearchEngine::image() const
  330. {
  331. if (m_image.isNull())
  332. loadImage();
  333. return m_image;
  334. }
  335. void OpenSearchEngine::setImage(const QImage &image)
  336. {
  337. if (m_imageUrl.isEmpty()) {
  338. QBuffer imageBuffer;
  339. imageBuffer.open(QBuffer::ReadWrite);
  340. if (image.save(&imageBuffer, "PNG")) {
  341. m_imageUrl = QString(QLatin1String("data:image/png;base64,%1"))
  342. .arg(QLatin1String(imageBuffer.buffer().toBase64()));
  343. }
  344. }
  345. m_image = image;
  346. emit imageChanged();
  347. }
  348. /*!
  349. \property valid
  350. \brief indicates whether the engine is valid i.e. the description was properly formed and included all necessary information
  351. */
  352. bool OpenSearchEngine::isValid() const
  353. {
  354. return (!m_name.isEmpty() && !m_searchUrlTemplate.isEmpty());
  355. }
  356. bool OpenSearchEngine::operator==(const OpenSearchEngine &other) const
  357. {
  358. return (m_name == other.m_name
  359. && m_description == other.m_description
  360. && m_imageUrl == other.m_imageUrl
  361. && m_searchUrlTemplate == other.m_searchUrlTemplate
  362. && m_suggestionsUrlTemplate == other.m_suggestionsUrlTemplate
  363. && m_searchParameters == other.m_searchParameters
  364. && m_suggestionsParameters == other.m_suggestionsParameters);
  365. }
  366. bool OpenSearchEngine::operator<(const OpenSearchEngine &other) const
  367. {
  368. return (m_name < other.m_name);
  369. }
  370. /*!
  371. Requests contextual suggestions on the search engine, for a given \a searchTerm.
  372. If succeeded, suggestions() signal will be emitted once the suggestions are received.
  373. \note To be able to request suggestions, you need to provide a network access manager,
  374. which will be used for network operations.
  375. \sa requestSearchResults()
  376. */
  377. void OpenSearchEngine::requestSuggestions(const QString &searchTerm)
  378. {
  379. if (searchTerm.isEmpty() || !providesSuggestions())
  380. return;
  381. Q_ASSERT(m_networkAccessManager);
  382. if (!m_networkAccessManager)
  383. return;
  384. if (m_suggestionsReply) {
  385. m_suggestionsReply->disconnect(this);
  386. m_suggestionsReply->abort();
  387. m_suggestionsReply->deleteLater();
  388. m_suggestionsReply = 0;
  389. }
  390. Q_ASSERT(m_requestMethods.contains(m_suggestionsMethod));
  391. if (m_suggestionsMethod == QLatin1String("get")) {
  392. m_suggestionsReply = m_networkAccessManager->get(QNetworkRequest(suggestionsUrl(searchTerm)));
  393. } else {
  394. QStringList parameters;
  395. Parameters::const_iterator end = m_suggestionsParameters.constEnd();
  396. Parameters::const_iterator i = m_suggestionsParameters.constBegin();
  397. for (; i != end; ++i)
  398. parameters.append(i->first + QLatin1String("=") + i->second);
  399. QByteArray data = parameters.join(QLatin1String("&")).toUtf8();
  400. m_suggestionsReply = m_networkAccessManager->post(QNetworkRequest(suggestionsUrl(searchTerm)), data);
  401. }
  402. connect(m_suggestionsReply, SIGNAL(finished()), this, SLOT(suggestionsObtained()));
  403. }
  404. /*!
  405. Requests search results on the search engine, for a given \a searchTerm.
  406. The default implementation does nothing, to supply your own you need to create your own
  407. OpenSearchEngineDelegate subclass and supply it to the engine. Then the function will call
  408. the performSearchRequest() method of the delegate, which can then handle the request
  409. in a custom way.
  410. \sa requestSuggestions(), delegate()
  411. */
  412. void OpenSearchEngine::requestSearchResults(const QString &searchTerm)
  413. {
  414. if (!m_delegate || searchTerm.isEmpty())
  415. return;
  416. Q_ASSERT(m_requestMethods.contains(m_searchMethod));
  417. QNetworkRequest request(QUrl(searchUrl(searchTerm)));
  418. QByteArray data;
  419. QNetworkAccessManager::Operation operation = m_requestMethods.value(m_searchMethod);
  420. if (operation == QNetworkAccessManager::PostOperation) {
  421. QStringList parameters;
  422. Parameters::const_iterator end = m_searchParameters.constEnd();
  423. Parameters::const_iterator i = m_searchParameters.constBegin();
  424. for (; i != end; ++i)
  425. parameters.append(i->first + QLatin1String("=") + i->second);
  426. data = parameters.join(QLatin1String("&")).toUtf8();
  427. }
  428. m_delegate->performSearchRequest(request, operation, data);
  429. }
  430. void OpenSearchEngine::suggestionsObtained()
  431. {
  432. QString response(QString::fromUtf8(m_suggestionsReply->readAll()));
  433. response = response.trimmed();
  434. m_suggestionsReply->close();
  435. m_suggestionsReply->deleteLater();
  436. m_suggestionsReply = 0;
  437. if (response.isEmpty())
  438. return;
  439. if (!response.startsWith(QLatin1Char('[')) || !response.endsWith(QLatin1Char(']')))
  440. return;
  441. if (!m_scriptEngine)
  442. m_scriptEngine = new QScriptEngine();
  443. // Evaluate the JSON response using QtScript.
  444. if (!m_scriptEngine->canEvaluate(response))
  445. return;
  446. QScriptValue responseParts = m_scriptEngine->evaluate(response);
  447. if (!responseParts.property(1).isArray())
  448. return;
  449. QStringList suggestionsList;
  450. qScriptValueToSequence(responseParts.property(1), suggestionsList);
  451. emit suggestions(suggestionsList);
  452. }
  453. /*!
  454. \property networkAccessManager
  455. \brief the network access manager that is used to perform network requests
  456. It is required for network operations: loading external images and requesting
  457. contextual suggestions.
  458. */
  459. QNetworkAccessManager *OpenSearchEngine::networkAccessManager() const
  460. {
  461. return m_networkAccessManager;
  462. }
  463. void OpenSearchEngine::setNetworkAccessManager(QNetworkAccessManager *networkAccessManager)
  464. {
  465. m_networkAccessManager = networkAccessManager;
  466. }
  467. /*!
  468. \property delegate
  469. \brief the delegate that is used to perform specific tasks.
  470. It can be currently supplied to provide a custom behaviour ofthe requetSearchResults() method.
  471. The default implementation does nothing.
  472. */
  473. OpenSearchEngineDelegate *OpenSearchEngine::delegate() const
  474. {
  475. return m_delegate;
  476. }
  477. void OpenSearchEngine::setDelegate(OpenSearchEngineDelegate *delegate)
  478. {
  479. m_delegate = delegate;
  480. }
  481. /*!
  482. \fn void OpenSearchEngine::imageChanged()
  483. This signal is emitted whenever the image of the engine changes.
  484. \sa image(), imageUrl()
  485. */
  486. /*!
  487. \fn void OpenSearchEngine::suggestions(const QStringList &suggestions)
  488. This signal is emitted whenever new contextual suggestions have been provided
  489. by the search engine. To request suggestions, use requestSuggestions().
  490. The suggestion set is specified by \a suggestions.
  491. \sa requestSuggestions()
  492. */