From d952df361a5e190246d7a0738ae5347b82cd57b5 Mon Sep 17 00:00:00 2001 From: Mert Tumer Date: Tue, 10 May 2022 13:06:06 +0300 Subject: LanguageTool Grammar Checker implementation Signed-off-by: Mert Tumer Change-Id: I275cbea668afc5beb5147370119631df8b6a2d46 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135178 Tested-by: Jenkins Reviewed-by: Christian Lohmaier --- Repository.mk | 1 + cui/Library_cui.mk | 1 + cui/UIConfig_cui.mk | 1 + cui/inc/treeopt.hrc | 3 +- cui/source/options/optlanguagetool.cxx | 76 ++++ cui/source/options/optlanguagetool.hxx | 44 +++ cui/source/options/treeopt.cxx | 2 + cui/uiconfig/ui/langtoolconfigpage.ui | 240 ++++++++++++ include/sfx2/pageids.hxx | 1 + include/svtools/languagetoolcfg.hxx | 59 +++ include/svtools/strings.hrc | 1 + lingucomponent/Library_LanguageTool.mk | 48 +++ lingucomponent/Module_lingucomponent.mk | 1 + .../spellcheck/languagetool/LanguageTool.component | 26 ++ .../spellcheck/languagetool/languagetoolimp.cxx | 407 +++++++++++++++++++++ .../spellcheck/languagetool/languagetoolimp.hxx | 91 +++++ .../schema/org/openoffice/Office/Linguistic.xcs | 30 ++ scripting/source/stringresource/stringresource.cxx | 1 + svtools/Library_svt.mk | 1 + svtools/source/config/languagetoolcfg.cxx | 164 +++++++++ 20 files changed, 1197 insertions(+), 1 deletion(-) create mode 100644 cui/source/options/optlanguagetool.cxx create mode 100644 cui/source/options/optlanguagetool.hxx create mode 100644 cui/uiconfig/ui/langtoolconfigpage.ui create mode 100644 include/svtools/languagetoolcfg.hxx create mode 100644 lingucomponent/Library_LanguageTool.mk create mode 100644 lingucomponent/source/spellcheck/languagetool/LanguageTool.component create mode 100644 lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx create mode 100644 lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx create mode 100644 svtools/source/config/languagetoolcfg.cxx diff --git a/Repository.mk b/Repository.mk index 4004cbe142df..5ee1546b2504 100644 --- a/Repository.mk +++ b/Repository.mk @@ -368,6 +368,7 @@ endif $(eval $(call gb_Helper_register_libraries_for_install,OOOLIBS,ooo, \ avmedia \ + LanguageTool \ $(call gb_Helper_optional,AVMEDIA, \ $(if $(filter MACOSX,$(OS)),\ avmediaMacAVF \ diff --git a/cui/Library_cui.mk b/cui/Library_cui.mk index 8f8e73afe6cd..cb26653b395d 100644 --- a/cui/Library_cui.mk +++ b/cui/Library_cui.mk @@ -187,6 +187,7 @@ $(eval $(call gb_Library_add_exception_objects,cui,\ cui/source/options/optgdlg \ cui/source/options/optgenrl \ cui/source/options/opthtml \ + cui/source/options/optlanguagetool \ cui/source/options/optinet2 \ cui/source/options/optjava \ cui/source/options/optjsearch \ diff --git a/cui/UIConfig_cui.mk b/cui/UIConfig_cui.mk index eda70a1dd29c..cdedcc15a857 100644 --- a/cui/UIConfig_cui.mk +++ b/cui/UIConfig_cui.mk @@ -143,6 +143,7 @@ $(eval $(call gb_UIConfig_add_uifiles,cui,\ cui/uiconfig/ui/optfontspage \ cui/uiconfig/ui/optgeneralpage \ cui/uiconfig/ui/opthtmlpage \ + cui/uiconfig/ui/langtoolconfigpage \ cui/uiconfig/ui/optionsdialog \ cui/uiconfig/ui/optjsearchpage \ cui/uiconfig/ui/optlanguagespage \ diff --git a/cui/inc/treeopt.hrc b/cui/inc/treeopt.hrc index a3033143983b..f4fc28c5725b 100644 --- a/cui/inc/treeopt.hrc +++ b/cui/inc/treeopt.hrc @@ -55,7 +55,8 @@ const std::pair SID_LANGUAGE_OPTIONS_RES[] = { NC_("SID_LANGUAGE_OPTIONS_RES", "Writing Aids"), RID_SFXPAGE_LINGU }, { NC_("SID_LANGUAGE_OPTIONS_RES", "Searching in Japanese"), RID_SVXPAGE_JSEARCH_OPTIONS }, { NC_("SID_LANGUAGE_OPTIONS_RES", "Asian Layout"), RID_SVXPAGE_ASIAN_LAYOUT }, - { NC_("SID_LANGUAGE_OPTIONS_RES", "Complex Text Layout"), RID_SVXPAGE_OPTIONS_CTL } + { NC_("SID_LANGUAGE_OPTIONS_RES", "Complex Text Layout"), RID_SVXPAGE_OPTIONS_CTL }, + { NC_("SID_LANGUAGE_OPTIONS_RES", "LanguageTool Server Settings"), RID_SVXPAGE_LANGTOOL_OPTIONS } }; const std::pair SID_INET_DLG_RES[] = diff --git a/cui/source/options/optlanguagetool.cxx b/cui/source/options/optlanguagetool.cxx new file mode 100644 index 000000000000..38807bc337d3 --- /dev/null +++ b/cui/source/options/optlanguagetool.cxx @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "optlanguagetool.hxx" +#include +#include + +OptLanguageToolTabPage::OptLanguageToolTabPage(weld::Container* pPage, + weld::DialogController* pController, + const SfxItemSet& rSet) + : SfxTabPage(pPage, pController, "cui/ui/langtoolconfigpage.ui", "OptLangToolPage", &rSet) + , m_xBaseURLED(m_xBuilder->weld_entry("baseurl")) + , m_xUsernameED(m_xBuilder->weld_entry("username")) + , m_xApiKeyED(m_xBuilder->weld_entry("apikey")) + , m_xActivateBox(m_xBuilder->weld_check_button("activate")) + , m_xApiSettingsFrame(m_xBuilder->weld_frame("apisettings")) +{ + m_xActivateBox->connect_toggled(LINK(this, OptLanguageToolTabPage, CheckHdl)); + SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); + EnableControls(rLanguageOpts.getEnabled()); +} + +OptLanguageToolTabPage::~OptLanguageToolTabPage() {} + +void OptLanguageToolTabPage::EnableControls(bool bEnable) +{ + SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); + rLanguageOpts.setEnabled(bEnable); + m_xApiSettingsFrame->set_visible(bEnable); + m_xActivateBox->set_active(bEnable); +} + +IMPL_LINK_NOARG(OptLanguageToolTabPage, CheckHdl, weld::Toggleable&, void) +{ + EnableControls(m_xActivateBox->get_active()); +} + +void OptLanguageToolTabPage::Reset(const SfxItemSet*) +{ + SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); + m_xBaseURLED->set_text(rLanguageOpts.getBaseURL()); + m_xUsernameED->set_text(rLanguageOpts.getUsername()); + m_xApiKeyED->set_text(rLanguageOpts.getApiKey()); +} + +bool OptLanguageToolTabPage::FillItemSet(SfxItemSet*) +{ + SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); + rLanguageOpts.setBaseURL(m_xBaseURLED->get_text()); + rLanguageOpts.setUsername(m_xUsernameED->get_text()); + rLanguageOpts.setApiKey(m_xApiKeyED->get_text()); + return false; +} + +std::unique_ptr OptLanguageToolTabPage::Create(weld::Container* pPage, + weld::DialogController* pController, + const SfxItemSet* rAttrSet) +{ + return std::make_unique(pPage, pController, *rAttrSet); +} diff --git a/cui/source/options/optlanguagetool.hxx b/cui/source/options/optlanguagetool.hxx new file mode 100644 index 000000000000..46a60ecbe103 --- /dev/null +++ b/cui/source/options/optlanguagetool.hxx @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#pragma once +#include + +class OptLanguageToolTabPage : public SfxTabPage +{ +public: + OptLanguageToolTabPage(weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet& rSet); + virtual ~OptLanguageToolTabPage() override; + static std::unique_ptr + Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rAttrSet); + + virtual bool FillItemSet(SfxItemSet* rSet) override; + virtual void Reset(const SfxItemSet* rSet) override; + +private: + std::unique_ptr m_xBaseURLED; + std::unique_ptr m_xUsernameED; + std::unique_ptr m_xApiKeyED; + std::unique_ptr m_xActivateBox; + std::unique_ptr m_xApiSettingsFrame; + + void EnableControls(bool bEnable); + + DECL_LINK(CheckHdl, weld::Toggleable&, void); +}; \ No newline at end of file diff --git a/cui/source/options/treeopt.cxx b/cui/source/options/treeopt.cxx index 812d24511a5a..5973f422a7ae 100644 --- a/cui/source/options/treeopt.cxx +++ b/cui/source/options/treeopt.cxx @@ -62,6 +62,7 @@ #include "personalization.hxx" #include #include "optbasic.hxx" +#include "optlanguagetool.hxx" #include #include @@ -296,6 +297,7 @@ static std::unique_ptr CreateGeneralTabPage(sal_uInt16 nId, weld::Co case SID_SB_DBREGISTEROPTIONS: fnCreate = &svx::DbRegistrationOptionsPage::Create; break; case RID_SVXPAGE_ACCESSIBILITYCONFIG: fnCreate = &SvxAccessibilityOptionsTabPage::Create; break; case RID_SVXPAGE_OPTIONS_CTL: fnCreate = &SvxCTLOptionsPage::Create ; break; + case RID_SVXPAGE_LANGTOOL_OPTIONS: fnCreate = &OptLanguageToolTabPage::Create ; break; case RID_SVXPAGE_OPTIONS_JAVA: fnCreate = &SvxJavaOptionsPage::Create ; break; #if HAVE_FEATURE_OPENCL case RID_SVXPAGE_OPENCL: fnCreate = &SvxOpenCLTabPage::Create ; break; diff --git a/cui/uiconfig/ui/langtoolconfigpage.ui b/cui/uiconfig/ui/langtoolconfigpage.ui new file mode 100644 index 000000000000..7b822325e3f1 --- /dev/null +++ b/cui/uiconfig/ui/langtoolconfigpage.ui @@ -0,0 +1,240 @@ + + + + + + True + False + 6 + 12 + + + True + False + 0 + none + + + True + False + True + True + + + True + False + start + 6 + 4 + If you enable this, the data will be sent to an external server. + True + + + 0 + 0 + + + + + Please read the privacy policy + True + True + True + start + start + none + https://languagetool.org/legal/privacy + + + 0 + 1 + + + + + Enable LanguageTool + True + True + False + start + 6 + 9 + True + + + 0 + 2 + + + + + False + 10 + 0 + none + + + True + False + 5 + 12 + + + True + False + start + Base URL: + True + baseurl + + + 0 + 0 + + + + + True + True + True + + + 1 + 0 + + + + + True + False + start + User name: + True + username + + + 0 + 2 + + + + + True + False + start + API key: + True + apikey + + + 0 + 4 + + + + + True + True + + + 1 + 2 + + + + + True + True + + + 1 + 4 + + + + + True + False + start + Please use the base URL e.g. without "/check" at the end. + + + 1 + 1 + + + + + True + False + start + Your LanguageTool account's username for premium usage. + + + 1 + 3 + + + + + True + False + start + Your LanguageTool account's api key for premium usage. + + + 1 + 5 + + + + + + + + + + + + + + + + True + False + 10 + API Settings + + + + + + + + 0 + 3 + + + + + + + True + False + 4 + LanguageTool API Options + + + + + + + + + False + True + 0 + + + + diff --git a/include/sfx2/pageids.hxx b/include/sfx2/pageids.hxx index 1dcba1219c31..9371848b784a 100644 --- a/include/sfx2/pageids.hxx +++ b/include/sfx2/pageids.hxx @@ -56,6 +56,7 @@ #define RID_SVXPAGE_PERSONALIZATION (RID_SVX_START + 247) #define RID_SVXPAGE_COLORCONFIG (RID_SVX_START + 249) #define RID_SVXPAGE_BASICIDE_OPTIONS (RID_SVX_START + 209) +#define RID_SVXPAGE_LANGTOOL_OPTIONS (RID_SVX_START + 210) // Resource-Id's ------------------------------------------------------------ diff --git a/include/svtools/languagetoolcfg.hxx b/include/svtools/languagetoolcfg.hxx new file mode 100644 index 000000000000..7578ad6ba281 --- /dev/null +++ b/include/svtools/languagetoolcfg.hxx @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once +#include +#include +#include + +using namespace utl; +using namespace com::sun::star::uno; + +struct LanguageToolOptions_Impl; + +class SVT_DLLPUBLIC SvxLanguageToolOptions final : public utl::ConfigItem +{ +public: + SvxLanguageToolOptions(); + virtual ~SvxLanguageToolOptions() override; + + virtual void Notify(const css::uno::Sequence& _rPropertyNames) override; + static SvxLanguageToolOptions& Get(); + + const OUString& getBaseURL() const; + void setBaseURL(const OUString& rVal); + + const OUString& getUsername() const; + void setUsername(const OUString& rVal); + + OUString getLocaleListURL() const; + OUString getCheckerURL() const; + + const OUString& getApiKey() const; + void setApiKey(const OUString& rVal); + + bool getEnabled() const; + void setEnabled(bool enabled); + +private: + std::unique_ptr pImpl; + void Load(const css::uno::Sequence& rPropertyNames); + virtual void ImplCommit() override; + static const Sequence& GetPropertyNames(); +}; diff --git a/include/svtools/strings.hrc b/include/svtools/strings.hrc index 22c8c6a3e16f..238856e13ccc 100644 --- a/include/svtools/strings.hrc +++ b/include/svtools/strings.hrc @@ -342,5 +342,6 @@ #define STR_DESCRIPTION_LIBHYPHEN NC_("STR_DESCRIPTION_LIBHYPHEN", "Libhyphen Hyphenator") #define STR_DESCRIPTION_MYTHES NC_("STR_DESCRIPTION_MYTHES", "MyThes Thesaurus") #define STR_DESCRIPTION_IGNOREALLLIST NC_("STR_DESCRIPTION_IGNOREALLLIST", "List of Ignored Words") +#define STR_DESCRIPTION_LANGUAGETOOL NC_("STR_DESCRIPTION_LANGUAGETOOL", "LanguageTool Remote Grammar Checker") /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/lingucomponent/Library_LanguageTool.mk b/lingucomponent/Library_LanguageTool.mk new file mode 100644 index 000000000000..98ff7cf7edf2 --- /dev/null +++ b/lingucomponent/Library_LanguageTool.mk @@ -0,0 +1,48 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Library_Library,LanguageTool)) + +$(eval $(call gb_Library_set_componentfile,LanguageTool,lingucomponent/source/spellcheck/languagetool/LanguageTool,services)) + +$(eval $(call gb_Library_set_include,LanguageTool,\ + $$(INCLUDE) \ + -I$(SRCDIR)/lingucomponent/source/lingutil \ +)) + +$(eval $(call gb_Library_use_sdk_api,LanguageTool)) + +$(eval $(call gb_Library_use_libraries,LanguageTool,\ + comphelper \ + cppu \ + cppuhelper \ + i18nlangtag \ + svt \ + lng \ + sal \ + tl \ + utl \ +)) + +$(eval $(call gb_Library_use_static_libraries,LanguageTool,\ + ulingu \ +)) + +$(eval $(call gb_Library_use_externals,LanguageTool,\ + boost_headers \ + icuuc \ + curl \ +)) + +$(eval $(call gb_Library_add_exception_objects,LanguageTool,\ + lingucomponent/source/spellcheck/languagetool/languagetoolimp \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/lingucomponent/Module_lingucomponent.mk b/lingucomponent/Module_lingucomponent.mk index 9f19dcedbc67..13f1a829d98a 100644 --- a/lingucomponent/Module_lingucomponent.mk +++ b/lingucomponent/Module_lingucomponent.mk @@ -28,6 +28,7 @@ endif $(eval $(call gb_Module_add_targets,lingucomponent,\ $(if $(filter iOS MACOSX,$(OS)),Library_MacOSXSpell) \ Library_numbertext \ + Library_LanguageTool \ )) # vim: set noet sw=4 ts=4: diff --git a/lingucomponent/source/spellcheck/languagetool/LanguageTool.component b/lingucomponent/source/spellcheck/languagetool/LanguageTool.component new file mode 100644 index 000000000000..9f7eb3d08789 --- /dev/null +++ b/lingucomponent/source/spellcheck/languagetool/LanguageTool.component @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx new file mode 100644 index 000000000000..06b4fcb64175 --- /dev/null +++ b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx @@ -0,0 +1,407 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include "languagetoolimp.hxx" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace osl; +using namespace com::sun::star; +using namespace com::sun::star::beans; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace com::sun::star::linguistic2; +using namespace linguistic; + +#define COL_ORANGE Color(0xD1, 0x68, 0x20) + +namespace +{ +PropertyValue lcl_MakePropertyValue(const OUString& rName, uno::Any& rValue) +{ + return PropertyValue(rName, -1, rValue, css::beans::PropertyState_DIRECT_VALUE); +} + +Sequence lcl_GetLineColorPropertyFromErrorId(const std::string& rErrorId) +{ + uno::Any aColor; + if (rErrorId == "TYPOS") + { + aColor <<= COL_LIGHTRED; + } + else if (rErrorId == "STYLE") + { + aColor <<= COL_LIGHTBLUE; + } + else + { + // Same color is used for other errorId's such as GRAMMAR, TYPOGRAPHY.. + aColor <<= COL_ORANGE; + } + Sequence aProperties{ lcl_MakePropertyValue("LineColor", aColor) }; + return aProperties; +} +} + +LanguageToolGrammarChecker::LanguageToolGrammarChecker() + : mCachedResults(MAX_CACHE_SIZE) +{ +} + +LanguageToolGrammarChecker::~LanguageToolGrammarChecker() {} + +sal_Bool SAL_CALL LanguageToolGrammarChecker::isSpellChecker() { return false; } + +sal_Bool SAL_CALL LanguageToolGrammarChecker::hasLocale(const Locale& rLocale) +{ + bool bRes = false; + if (!m_aSuppLocales.hasElements()) + getLocales(); + + for (auto const& suppLocale : std::as_const(m_aSuppLocales)) + { + if (rLocale == suppLocale) + { + bRes = true; + break; + } + } + + return bRes; +} + +Sequence SAL_CALL LanguageToolGrammarChecker::getLocales() +{ + if (m_aSuppLocales.hasElements()) + return m_aSuppLocales; + SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); + OString localeUrl = OUStringToOString(rLanguageOpts.getLocaleListURL(), RTL_TEXTENCODING_UTF8); + if (localeUrl.isEmpty()) + { + return m_aSuppLocales; + } + tools::Long statusCode = 0; + std::string response = makeHttpRequest(localeUrl, HTTP_METHOD::HTTP_GET, OString(), statusCode); + if (statusCode != 200) + { + return m_aSuppLocales; + } + if (response.empty()) + { + return m_aSuppLocales; + } + boost::property_tree::ptree root; + std::stringstream aStream(response); + boost::property_tree::read_json(aStream, root); + + size_t length = root.size(); + m_aSuppLocales.realloc(length); + auto pArray = m_aSuppLocales.getArray(); + int i = 0; + for (auto it = root.begin(); it != root.end(); it++, i++) + { + boost::property_tree::ptree& localeItem = it->second; + const std::string longCode = localeItem.get("longCode"); + Locale aLocale = LanguageTag::convertToLocale( + OUString(longCode.c_str(), longCode.length(), RTL_TEXTENCODING_UTF8)); + pArray[i] = aLocale; + } + return m_aSuppLocales; +} + +// Callback to get the response data from server. +static size_t WriteCallback(void* ptr, size_t size, size_t nmemb, void* userp) +{ + if (!userp) + return 0; + + std::string* response = static_cast(userp); + size_t real_size = size * nmemb; + response->append(static_cast(ptr), real_size); + return real_size; +} + +ProofreadingResult SAL_CALL LanguageToolGrammarChecker::doProofreading( + const OUString& aDocumentIdentifier, const OUString& aText, const Locale& aLocale, + sal_Int32 nStartOfSentencePosition, sal_Int32 nSuggestedBehindEndOfSentencePosition, + const Sequence& aProperties) +{ + // ProofreadingResult declared here instead of parseHttpJSONResponse because of the early exists. + ProofreadingResult xRes; + xRes.aDocumentIdentifier = aDocumentIdentifier; + xRes.aText = aText; + xRes.aLocale = aLocale; + xRes.nStartOfSentencePosition = nStartOfSentencePosition; + xRes.nBehindEndOfSentencePosition = nSuggestedBehindEndOfSentencePosition; + xRes.aProperties = Sequence(); + xRes.xProofreader = this; + xRes.aErrors = Sequence(); + + if (aText.isEmpty()) + { + return xRes; + } + + if (nStartOfSentencePosition != 0) + { + return xRes; + } + + xRes.nStartOfNextSentencePosition = aText.getLength(); + + SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); + if (rLanguageOpts.getEnabled() == false) + { + return xRes; + } + + OString checkerURL = OUStringToOString(rLanguageOpts.getCheckerURL(), RTL_TEXTENCODING_UTF8); + if (checkerURL.isEmpty()) + { + return xRes; + } + + if (aProperties.getLength() > 0 && aProperties[0].Name == "Update") + { + // locale changed + xRes.aText = ""; + return xRes; + } + + sal_Int32 spaceIndex = std::min(xRes.nStartOfNextSentencePosition, aText.getLength() - 1); + while (spaceIndex < aText.getLength() && aText[spaceIndex] == ' ') + { + xRes.nStartOfNextSentencePosition += 1; + spaceIndex = xRes.nStartOfNextSentencePosition; + } + if (xRes.nStartOfNextSentencePosition == nSuggestedBehindEndOfSentencePosition + && spaceIndex < aText.getLength()) + { + xRes.nStartOfNextSentencePosition + = std::min(nSuggestedBehindEndOfSentencePosition + 1, aText.getLength()); + } + xRes.nBehindEndOfSentencePosition + = std::min(xRes.nStartOfNextSentencePosition, aText.getLength()); + + auto cachedResult = mCachedResults.find(aText); + if (cachedResult != mCachedResults.end()) + { + xRes.aErrors = cachedResult->second; + return xRes; + } + + tools::Long http_code = 0; + OUString langTag(aLocale.Language + "-" + aLocale.Country); + OString postData(OUStringToOString( + OUStringConcatenation("text=" + aText + "&language=" + langTag), RTL_TEXTENCODING_UTF8)); + const std::string response_body + = makeHttpRequest(checkerURL, HTTP_METHOD::HTTP_POST, postData, http_code); + + if (http_code != 200) + { + return xRes; + } + + if (response_body.length() <= 0) + { + return xRes; + } + + parseProofreadingJSONResponse(xRes, response_body); + // cache the result + mCachedResults.insert( + std::pair>(aText, xRes.aErrors)); + return xRes; +} + +/* + rResult is both input and output + aJSONBody is the response body from the HTTP Request to LanguageTool API +*/ +void LanguageToolGrammarChecker::parseProofreadingJSONResponse(ProofreadingResult& rResult, + std::string_view aJSONBody) +{ + boost::property_tree::ptree root; + std::stringstream aStream(aJSONBody.data()); + boost::property_tree::read_json(aStream, root); + boost::property_tree::ptree& matches = root.get_child("matches"); + size_t matchSize = matches.size(); + + if (matchSize <= 0) + { + return; + } + Sequence aErrors(matchSize); + auto pErrors = aErrors.getArray(); + size_t i = 0; + for (auto it1 = matches.begin(); it1 != matches.end(); it1++, i++) + { + const boost::property_tree::ptree& match = it1->second; + int offset = match.get("offset"); + int length = match.get("length"); + const std::string shortMessage = match.get("message"); + const std::string message = match.get("shortMessage"); + + // Parse the error category for Line Color + const boost::property_tree::ptree& rule = match.get_child("rule"); + const boost::property_tree::ptree& ruleCategory = rule.get_child("category"); + const std::string errorCategoryId = ruleCategory.get("id"); + + OUString aShortComment(shortMessage.c_str(), shortMessage.length(), RTL_TEXTENCODING_UTF8); + OUString aFullComment(message.c_str(), message.length(), RTL_TEXTENCODING_UTF8); + + pErrors[i].nErrorStart = offset; + pErrors[i].nErrorLength = length; + pErrors[i].nErrorType = PROOFREADING_ERROR; + pErrors[i].aShortComment = aShortComment; + pErrors[i].aFullComment = aFullComment; + pErrors[i].aProperties = lcl_GetLineColorPropertyFromErrorId(errorCategoryId); + ; + const boost::property_tree::ptree& replacements = match.get_child("replacements"); + int suggestionSize = replacements.size(); + + if (suggestionSize <= 0) + { + continue; + } + pErrors[i].aSuggestions.realloc(std::min(suggestionSize, MAX_SUGGESTIONS_SIZE)); + auto pSuggestions = pErrors[i].aSuggestions.getArray(); + // Limit suggestions to avoid crash on context menu popup: + // (soffice:17251): Gdk-CRITICAL **: 17:00:21.277: ../../../../../gdk/wayland/gdkdisplay-wayland.c:1399: Unable to create Cairo image + // surface: invalid value (typically too big) for the size of the input (surface, pattern, etc.) + int j = 0; + for (auto it2 = replacements.begin(); it2 != replacements.end() && j < MAX_SUGGESTIONS_SIZE; + it2++, j++) + { + const boost::property_tree::ptree& replacement = it2->second; + std::string replacementStr = replacement.get("value"); + pSuggestions[j] + = OUString(replacementStr.c_str(), replacementStr.length(), RTL_TEXTENCODING_UTF8); + } + } + rResult.aErrors = aErrors; +} + +std::string LanguageToolGrammarChecker::makeHttpRequest(std::string_view aURL, HTTP_METHOD method, + const OString& aPostData, + tools::Long& nStatusCode) +{ + std::unique_ptr> curl(curl_easy_init(), + [](CURL* p) { curl_easy_cleanup(p); }); + if (!curl) + return {}; // empty string + + bool isPremium = false; + SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); + OString apiKey = OUStringToOString(rLanguageOpts.getApiKey(), RTL_TEXTENCODING_UTF8); + OString username = OUStringToOString(rLanguageOpts.getUsername(), RTL_TEXTENCODING_UTF8); + OString premiumPostData; + if (!apiKey.isEmpty() && !username.isEmpty()) + { + isPremium = true; + } + + std::string response_body; + curl_easy_setopt(curl.get(), CURLOPT_URL, aURL.data()); + + curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1L); + // curl_easy_setopt(curl.get(), CURLOPT_VERBOSE, 1L); + + curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, static_cast(&response_body)); + curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYHOST, false); + curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, CURL_TIMEOUT); + + if (method == HTTP_METHOD::HTTP_POST) + { + curl_easy_setopt(curl.get(), CURLOPT_POST, 1L); + if (isPremium == false) + { + curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, aPostData.getStr()); + } + else + { + premiumPostData = aPostData + "&username=" + username + "&apiKey=" + apiKey; + curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, premiumPostData.getStr()); + } + } + + /*CURLcode cc = */ + curl_easy_perform(curl.get()); + curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &nStatusCode); + return response_body; +} + +void SAL_CALL LanguageToolGrammarChecker::ignoreRule(const OUString& /*aRuleIdentifier*/, + const Locale& /*aLocale*/ +) +{ +} +void SAL_CALL LanguageToolGrammarChecker::resetIgnoreRules() {} + +OUString SAL_CALL LanguageToolGrammarChecker::getServiceDisplayName(const Locale& rLocale) +{ + std::locale loc(Translate::Create("svt", LanguageTag(rLocale))); + return Translate::get(STR_DESCRIPTION_LANGUAGETOOL, loc); +} + +OUString SAL_CALL LanguageToolGrammarChecker::getImplementationName() +{ + return "org.openoffice.lingu.LanguageToolGrammarChecker"; +} + +sal_Bool SAL_CALL LanguageToolGrammarChecker::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence SAL_CALL LanguageToolGrammarChecker::getSupportedServiceNames() +{ + return { SN_GRAMMARCHECKER }; +} + +void SAL_CALL LanguageToolGrammarChecker::initialize(const Sequence& /*rArguments*/) {} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +lingucomponent_LanguageToolGrammarChecker_get_implementation( + css::uno::XComponentContext*, css::uno::Sequence const&) +{ + return cppu::acquire(static_cast(new LanguageToolGrammarChecker())); +} \ No newline at end of file diff --git a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx new file mode 100644 index 000000000000..e0dadfecab68 --- /dev/null +++ b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::linguistic2; + +// Magical numbers +#define MAX_CACHE_SIZE 10 +#define MAX_SUGGESTIONS_SIZE 10 +#define PROOFREADING_ERROR 2 +#define CURL_TIMEOUT 10L + +enum class HTTP_METHOD +{ + HTTP_GET, + HTTP_POST +}; + +class LanguageToolGrammarChecker + : public cppu::WeakImplHelper +{ + Sequence m_aSuppLocales; + o3tl::lru_map> mCachedResults; + LanguageToolGrammarChecker(const LanguageToolGrammarChecker&) = delete; + LanguageToolGrammarChecker& operator=(const LanguageToolGrammarChecker&) = delete; + static void parseProofreadingJSONResponse(ProofreadingResult& rResult, + std::string_view aJSONBody); + static std::string makeHttpRequest(std::string_view aURL, HTTP_METHOD method, + const OString& aPostData, tools::Long& nStatusCode); + +public: + LanguageToolGrammarChecker(); + virtual ~LanguageToolGrammarChecker() override; + + // XSupportedLocales + virtual Sequence SAL_CALL getLocales() override; + virtual sal_Bool SAL_CALL hasLocale(const Locale& rLocale) override; + + // XProofReader + virtual sal_Bool SAL_CALL isSpellChecker() override; + virtual ProofreadingResult SAL_CALL doProofreading( + const OUString& aDocumentIdentifier, const OUString& aText, const Locale& aLocale, + sal_Int32 nStartOfSentencePosition, sal_Int32 nSuggestedBehindEndOfSentencePosition, + const Sequence& aProperties) override; + + virtual void SAL_CALL ignoreRule(const OUString& aRuleIdentifier, + const Locale& aLocale) override; + virtual void SAL_CALL resetIgnoreRules() override; + + // XServiceDisplayName + virtual OUString SAL_CALL getServiceDisplayName(const Locale& rLocale) override; + + // XInitialization + virtual void SAL_CALL initialize(const Sequence& rArguments) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override; + virtual Sequence SAL_CALL getSupportedServiceNames() override; +}; \ No newline at end of file diff --git a/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs b/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs index 400f749de315..fe9365d036dc 100644 --- a/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs +++ b/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs @@ -377,6 +377,36 @@ true + + + Contains grammar checking relevant settings. + + + + LanguageTool Grammar Checker URL + + + + + + LanguageTool Grammar Checker Username for private API + + + + + + LanguageTool Grammar Checker Api Key for private API + + + + + + Enable or disable LanguageTool Remote Grammar Checker + + + false + + diff --git a/scripting/source/stringresource/stringresource.cxx b/scripting/source/stringresource/stringresource.cxx index d9a2e377e5ec..06b52b0377c7 100644 --- a/scripting/source/stringresource/stringresource.cxx +++ b/scripting/source/stringresource/stringresource.cxx @@ -37,6 +37,7 @@ #include #include #include +#include using namespace ::com::sun::star; using namespace ::com::sun::star::lang; diff --git a/svtools/Library_svt.mk b/svtools/Library_svt.mk index 643481368caa..5c814cfcb7e1 100644 --- a/svtools/Library_svt.mk +++ b/svtools/Library_svt.mk @@ -83,6 +83,7 @@ $(eval $(call gb_Library_add_exception_objects,svt,\ svtools/source/config/extcolorcfg \ svtools/source/config/fontsubstconfig \ svtools/source/config/htmlcfg \ + svtools/source/config/languagetoolcfg \ svtools/source/config/itemholder2 \ svtools/source/config/miscopt \ svtools/source/config/slidesorterbaropt \ diff --git a/svtools/source/config/languagetoolcfg.cxx b/svtools/source/config/languagetoolcfg.cxx new file mode 100644 index 000000000000..9f81c8e787f1 --- /dev/null +++ b/svtools/source/config/languagetoolcfg.cxx @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include + +using namespace utl; +using namespace com::sun::star::uno; + +struct LanguageToolOptions_Impl +{ + OUString sBaseURL; + OUString sUsername; + OUString sApiKey; + bool bEnabled; +}; + +const Sequence& SvxLanguageToolOptions::GetPropertyNames() +{ + static Sequence const aNames{ + "LanguageTool/BaseURL", + "LanguageTool/Username", + "LanguageTool/ApiKey", + "LanguageTool/IsEnabled", + }; + return aNames; +} + +const OUString& SvxLanguageToolOptions::getBaseURL() const { return pImpl->sBaseURL; } + +void SvxLanguageToolOptions::setBaseURL(const OUString& rVal) +{ + pImpl->sBaseURL = rVal; + SetModified(); +} + +const OUString& SvxLanguageToolOptions::getUsername() const { return pImpl->sUsername; } + +void SvxLanguageToolOptions::setUsername(const OUString& rVal) +{ + pImpl->sUsername = rVal; + SetModified(); +} + +OUString SvxLanguageToolOptions::getLocaleListURL() const { return pImpl->sBaseURL + "/languages"; } + +OUString SvxLanguageToolOptions::getCheckerURL() const { return pImpl->sBaseURL + "/check"; } + +const OUString& SvxLanguageToolOptions::getApiKey() const { return pImpl->sApiKey; } + +void SvxLanguageToolOptions::setApiKey(const OUString& rVal) +{ + pImpl->sApiKey = rVal; + SetModified(); +} + +bool SvxLanguageToolOptions::getEnabled() const { return pImpl->bEnabled; } + +void SvxLanguageToolOptions::setEnabled(bool bEnabled) +{ + pImpl->bEnabled = bEnabled; + SetModified(); +} + +namespace +{ +class theSvxLanguageToolOptions + : public rtl::Static +{ +}; +} + +SvxLanguageToolOptions& SvxLanguageToolOptions::Get() { return theSvxLanguageToolOptions::get(); } + +SvxLanguageToolOptions::SvxLanguageToolOptions() + : ConfigItem("Office.Linguistic/GrammarChecking") + , pImpl(new LanguageToolOptions_Impl) +{ + Load(GetPropertyNames()); +} + +SvxLanguageToolOptions::~SvxLanguageToolOptions() {} +void SvxLanguageToolOptions::Notify(const css::uno::Sequence&) +{ + Load(GetPropertyNames()); +} + +void SvxLanguageToolOptions::Load(const css::uno::Sequence& aNames) +{ + Sequence aValues = GetProperties(aNames); + const Any* pValues = aValues.getConstArray(); + DBG_ASSERT(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if (aValues.getLength() != aNames.getLength()) + return; + for (int nProp = 0; nProp < aNames.getLength(); nProp++) + { + if (!pValues[nProp].hasValue()) + continue; + switch (nProp) + { + case 0: + pValues[nProp] >>= pImpl->sBaseURL; + break; + case 1: + pValues[nProp] >>= pImpl->sUsername; + break; + case 2: + pValues[nProp] >>= pImpl->sApiKey; + break; + case 3: + pValues[nProp] >>= pImpl->bEnabled; + break; + default: + break; + } + } +} + +void SvxLanguageToolOptions::ImplCommit() +{ + const Sequence& aNames = GetPropertyNames(); + Sequence aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + for (int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch (nProp) + { + case 0: + pValues[nProp] <<= pImpl->sBaseURL; + break; + case 1: + pValues[nProp] <<= pImpl->sUsername; + break; + case 2: + pValues[nProp] <<= pImpl->sApiKey; + break; + case 3: + pValues[nProp] <<= pImpl->bEnabled; + break; + default: + break; + } + } + PutProperties(aNames, aValues); +} \ No newline at end of file -- cgit