blob: 2970d8f33d4c27f3b18286bcb75eb5fe4f397264 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/spellchecker/spelling_service_client.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/json/string_escape.h"
#include "base/logging.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/spellcheck_result.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/google_api_keys.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_fetcher.h"
#include "unicode/uloc.h"
// Use the public URL to the Spelling service on Chromium.
#ifndef SPELLING_SERVICE_URL
#define SPELLING_SERVICE_URL "https://www.googleapis.com/rpc"
#endif
SpellingServiceClient::SpellingServiceClient() : tag_(0) {
}
SpellingServiceClient::~SpellingServiceClient() {
}
bool SpellingServiceClient::RequestTextCheck(
Profile* profile,
int tag,
ServiceType type,
const string16& text,
const TextCheckCompleteCallback& callback) {
DCHECK(type == SUGGEST || type == SPELLCHECK);
std::string locale = profile->GetPrefs()->GetString(
prefs::kSpellCheckDictionary);
char language[ULOC_LANG_CAPACITY] = ULOC_ENGLISH;
const char* country = "USA";
if (!locale.empty()) {
// Create the parameters needed by Spelling API. Spelling API needs three
// parameters: ISO language code, ISO3 country code, and text to be checked
// by the service. On the other hand, Chrome uses an ISO locale ID and it
// may not include a country ID, e.g. "fr", "de", etc. To create the input
// parameters, we convert the UI locale to a full locale ID, and convert
// the full locale ID to an ISO language code and and ISO3 country code.
// Also, we convert the given text to a JSON string, i.e. quote all its
// non-ASCII characters.
UErrorCode error = U_ZERO_ERROR;
char id[ULOC_LANG_CAPACITY + ULOC_SCRIPT_CAPACITY + ULOC_COUNTRY_CAPACITY];
uloc_addLikelySubtags(locale.c_str(), id, arraysize(id), &error);
error = U_ZERO_ERROR;
uloc_getLanguage(id, language, arraysize(language), &error);
country = uloc_getISO3Country(id);
}
if (!IsAvailable(profile, type))
return false;
// Format the JSON request to be sent to the Spelling service.
std::string encoded_text;
base::JsonDoubleQuote(text, false, &encoded_text);
static const char kSpellingRequest[] =
"{"
"\"method\":\"spelling.check\","
"\"apiVersion\":\"v%d\","
"\"params\":{"
"\"text\":\"%s\","
"\"language\":\"%s\","
"\"originCountry\":\"%s\","
"\"key\":\"%s\""
"}"
"}";
std::string api_key = google_apis::GetAPIKey();
std::string request = base::StringPrintf(
kSpellingRequest,
type,
encoded_text.c_str(),
language,
country,
api_key.c_str());
static const char kSpellingServiceURL[] = SPELLING_SERVICE_URL;
GURL url = GURL(kSpellingServiceURL);
fetcher_.reset(CreateURLFetcher(url));
fetcher_->SetRequestContext(profile->GetRequestContext());
fetcher_->SetUploadData("application/json", request);
fetcher_->SetLoadFlags(
net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES);
fetcher_->Start();
tag_ = tag;
text_ = text;
callback_ = callback;
return true;
}
bool SpellingServiceClient::IsAvailable(Profile* profile, ServiceType type) {
const PrefService* pref = profile->GetPrefs();
if (!pref->GetBoolean(prefs::kEnableSpellCheck) ||
!pref->GetBoolean(prefs::kSpellCheckUseSpellingService))
return false;
// The spellchecking service should be avilable only when asynchronous
// spellchecking is enabled because this service depends on it.
const CommandLine* command_line = CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kDisableAsynchronousSpellChecking))
return type == SUGGEST;
// Enable the suggest service only on languages not supported by the
// spellcheck service. When this client calls the spellcheck service, it
// returns not only spellcheck results but also spelling suggestions provided
// by the suggest service. That is, it is not useful to use the suggest
// service when this client can use the spellcheck service.
std::string locale = pref->GetString(prefs::kSpellCheckDictionary);
#if defined(OS_MACOSX)
bool spellcheck_available = locale.empty();
#else
bool spellcheck_available = locale.empty() || !locale.compare(0, 2, "en");
#endif
return type == SUGGEST ? !spellcheck_available : spellcheck_available;
}
void SpellingServiceClient::OnURLFetchComplete(
const net::URLFetcher* source) {
scoped_ptr<net::URLFetcher> clean_up_fetcher(fetcher_.release());
bool success = false;
std::vector<SpellCheckResult> results;
if (source->GetResponseCode() / 100 == 2) {
std::string data;
source->GetResponseAsString(&data);
success = ParseResponse(data, &results);
}
callback_.Run(tag_, success, text_, results);
}
net::URLFetcher* SpellingServiceClient::CreateURLFetcher(const GURL& url) {
return net::URLFetcher::Create(url, net::URLFetcher::POST, this);
}
bool SpellingServiceClient::ParseResponse(
const std::string& data,
std::vector<SpellCheckResult>* results) {
// When this JSON-RPC call finishes successfully, the Spelling service returns
// an JSON object listed below.
// * result - an envelope object representing the result from the APIARY
// server, which is the JSON-API front-end for the Spelling service. This
// object consists of the following variable:
// - spellingCheckResponse (SpellingCheckResponse).
// * SpellingCheckResponse - an object representing the result from the
// Spelling service. This object consists of the following variable:
// - misspellings (optional array of Misspelling)
// * Misspelling - an object representing a misspelling region and its
// suggestions. This object consists of the following variables:
// - charStart (number) - the beginning of the misspelled region;
// - charLength (number) - the length of the misspelled region;
// - suggestions (array of string) - the suggestions for the misspelling
// text, and;
// - canAutoCorrect (optional boolean) - whether we can use the first
// suggestion for auto-correction.
// For example, the Spelling service returns the following JSON when we send a
// spelling request for "duck goes quisk" as of 16 August, 2011.
// {
// "result": {
// "spellingCheckResponse": {
// "misspellings": [{
// "charStart": 10,
// "charLength": 5,
// "suggestions": [{ "suggestion": "quack" }],
// "canAutoCorrect": false
// }]
// }
// }
// }
scoped_ptr<DictionaryValue> value(
static_cast<DictionaryValue*>(
base::JSONReader::Read(data, base::JSON_ALLOW_TRAILING_COMMAS)));
if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
return false;
// Retrieve the array of Misspelling objects. When the input text does not
// have misspelled words, it returns an empty JSON. (In this case, its HTTP
// status is 200.) We just return true for this case.
ListValue* misspellings = NULL;
const char kMisspellings[] = "result.spellingCheckResponse.misspellings";
if (!value->GetList(kMisspellings, &misspellings))
return true;
for (size_t i = 0; i < misspellings->GetSize(); ++i) {
// Retrieve the i-th misspelling region and put it to the given vector. When
// the Spelling service sends two or more suggestions, we read only the
// first one because SpellCheckResult can store only one suggestion.
DictionaryValue* misspelling = NULL;
if (!misspellings->GetDictionary(i, &misspelling))
return false;
int start = 0;
int length = 0;
ListValue* suggestions = NULL;
if (!misspelling->GetInteger("charStart", &start) ||
!misspelling->GetInteger("charLength", &length) ||
!misspelling->GetList("suggestions", &suggestions)) {
return false;
}
DictionaryValue* suggestion = NULL;
string16 replacement;
if (!suggestions->GetDictionary(0, &suggestion) ||
!suggestion->GetString("suggestion", &replacement)) {
return false;
}
SpellCheckResult result(
SpellCheckResult::SPELLING, start, length, replacement);
results->push_back(result);
}
return true;
}