summaryrefslogtreecommitdiffstats
path: root/lingucomponent/source/spellcheck/languagetool
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /lingucomponent/source/spellcheck/languagetool
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lingucomponent/source/spellcheck/languagetool')
-rw-r--r--lingucomponent/source/spellcheck/languagetool/LanguageTool.component26
-rw-r--r--lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx519
-rw-r--r--lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx75
3 files changed, 620 insertions, 0 deletions
diff --git a/lingucomponent/source/spellcheck/languagetool/LanguageTool.component b/lingucomponent/source/spellcheck/languagetool/LanguageTool.component
new file mode 100644
index 0000000000..9f7eb3d087
--- /dev/null
+++ b/lingucomponent/source/spellcheck/languagetool/LanguageTool.component
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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 .
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="org.openoffice.lingu.LanguageToolGrammarChecker"
+ constructor="lingucomponent_LanguageToolGrammarChecker_get_implementation" single-instance="true">
+ <service name="com.sun.star.linguistic2.Proofreader"/>
+ </implementation>
+</component>
diff --git a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx
new file mode 100644
index 0000000000..fe912cb6b3
--- /dev/null
+++ b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx
@@ -0,0 +1,519 @@
+/* -*- 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 <sal/config.h>
+
+#include <config_version.h>
+
+#include <cppuhelper/factory.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+#include "languagetoolimp.hxx"
+
+#include <i18nlangtag/languagetag.hxx>
+#include <svtools/strings.hrc>
+#include <unotools/resmgr.hxx>
+
+#include <vector>
+#include <set>
+#include <string.h>
+
+#include <officecfg/Office/Linguistic.hxx>
+
+#include <curl/curl.h>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/json_parser.hpp>
+#include <algorithm>
+#include <string_view>
+
+#include <systools/curlinit.hxx>
+
+#include <sal/log.hxx>
+#include <tools/color.hxx>
+#include <tools/long.hxx>
+#include <com/sun/star/text/TextMarkupType.hpp>
+#include <com/sun/star/uno/Any.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <unotools/lingucfg.hxx>
+#include <osl/mutex.hxx>
+#include <rtl/uri.hxx>
+
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::linguistic2;
+
+constexpr OUStringLiteral sDuden = u"duden";
+
+namespace
+{
+constexpr size_t MAX_SUGGESTIONS_SIZE = 10;
+using LanguageToolCfg = officecfg::Office::Linguistic::GrammarChecking::LanguageTool;
+
+PropertyValue lcl_GetLineColorPropertyFromErrorId(const std::string& rErrorId)
+{
+ Color aColor;
+ if (rErrorId == "TYPOS" || rErrorId == "orth")
+ {
+ aColor = COL_LIGHTRED;
+ }
+ else if (rErrorId == "STYLE")
+ {
+ aColor = COL_LIGHTBLUE;
+ }
+ else
+ {
+ // Same color is used for other errorId's such as GRAMMAR, TYPOGRAPHY..
+ constexpr Color COL_ORANGE(0xD1, 0x68, 0x20);
+ aColor = COL_ORANGE;
+ }
+ return comphelper::makePropertyValue("LineColor", aColor);
+}
+
+OString encodeTextForLT(const OUString& text)
+{
+ // Let's be a bit conservative. I don't find a good description what needs encoding (and in
+ // which way) at https://languagetool.org/http-api/; the "Try it out!" function shows that
+ // different cases are handled differently by the demo; some percent-encode the UTF-8
+ // representation, like %D0%90 (for cyrillic А); some turn into entities like &#33; (for
+ // exclamation mark !); some other to things like \u0027 (for apostrophe '). So only keep
+ // RFC 3986's "Unreserved Characters" set unencoded, use UTF-8 percent-encoding for the rest.
+ static constexpr auto myCharClass = rtl::createUriCharClass(
+ u8"-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
+ return OUStringToOString(
+ rtl::Uri::encode(text, myCharClass.data(), rtl_UriEncodeStrict, RTL_TEXTENCODING_UTF8),
+ RTL_TEXTENCODING_ASCII_US);
+}
+
+// Callback to get the response data from server.
+size_t WriteCallback(void* ptr, size_t size, size_t nmemb, void* userp)
+{
+ if (!userp)
+ return 0;
+
+ std::string* response = static_cast<std::string*>(userp);
+ size_t real_size = size * nmemb;
+ response->append(static_cast<char*>(ptr), real_size);
+ return real_size;
+}
+
+enum class HTTP_METHOD
+{
+ HTTP_GET,
+ HTTP_POST
+};
+
+std::string makeHttpRequest_impl(std::u16string_view aURL, HTTP_METHOD method,
+ const OString& aPostData, curl_slist* pHttpHeader,
+ tools::Long& nStatusCode)
+{
+ struct curl_cleanup_t
+ {
+ void operator()(CURL* p) const { curl_easy_cleanup(p); }
+ };
+ std::unique_ptr<CURL, curl_cleanup_t> curl(curl_easy_init());
+ if (!curl)
+ {
+ SAL_WARN("languagetool", "CURL initialization failed");
+ return {}; // empty string
+ }
+
+ ::InitCurl_easy(curl.get());
+
+ OString aURL8 = OUStringToOString(aURL, RTL_TEXTENCODING_UTF8);
+ (void)curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, pHttpHeader);
+ (void)curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1L);
+ (void)curl_easy_setopt(curl.get(), CURLOPT_URL, aURL8.getStr());
+ (void)curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, 10L);
+ // (void)curl_easy_setopt(curl.get(), CURLOPT_VERBOSE, 1L);
+
+ std::string response_body;
+ (void)curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback);
+ (void)curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_body);
+
+ // allow unknown or self-signed certificates
+ if (!LanguageToolCfg::SSLCertVerify::get())
+ {
+ (void)curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, false);
+ (void)curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYHOST, false);
+ }
+
+ if (method == HTTP_METHOD::HTTP_POST)
+ {
+ (void)curl_easy_setopt(curl.get(), CURLOPT_POST, 1L);
+ (void)curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, aPostData.getStr());
+ }
+
+ CURLcode cc = curl_easy_perform(curl.get());
+ if (cc != CURLE_OK)
+ {
+ SAL_WARN("languagetool",
+ "CURL request returned with error: " << static_cast<sal_Int32>(cc));
+ }
+
+ curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &nStatusCode);
+ return response_body;
+}
+
+std::string makeDudenHttpRequest(std::u16string_view aURL, const OString& aPostData,
+ tools::Long& nStatusCode)
+{
+ struct curl_slist* pList = nullptr;
+ OString sAccessToken
+ = OUStringToOString(LanguageToolCfg::ApiKey::get().value_or(""), RTL_TEXTENCODING_UTF8);
+
+ pList = curl_slist_append(pList, "Cache-Control: no-cache");
+ pList = curl_slist_append(pList, "Content-Type: application/json");
+ if (!sAccessToken.isEmpty())
+ {
+ sAccessToken = "access_token: " + sAccessToken;
+ pList = curl_slist_append(pList, sAccessToken.getStr());
+ }
+
+ return makeHttpRequest_impl(aURL, HTTP_METHOD::HTTP_POST, aPostData, pList, nStatusCode);
+}
+
+std::string makeHttpRequest(std::u16string_view aURL, HTTP_METHOD method, const OString& aPostData,
+ tools::Long& nStatusCode)
+{
+ OString realPostData(aPostData);
+ if (method == HTTP_METHOD::HTTP_POST)
+ {
+ OString apiKey
+ = OUStringToOString(LanguageToolCfg::ApiKey::get().value_or(""), RTL_TEXTENCODING_UTF8);
+ OUString username = LanguageToolCfg::Username::get().value_or("");
+ if (!apiKey.isEmpty() && !username.isEmpty())
+ realPostData += "&username=" + encodeTextForLT(username) + "&apiKey=" + apiKey;
+ }
+
+ return makeHttpRequest_impl(aURL, method, realPostData, nullptr, nStatusCode);
+}
+
+template <typename Func>
+uno::Sequence<SingleProofreadingError> parseJson(std::string&& json, std::string path, Func f)
+{
+ std::stringstream aStream(std::move(json)); // Optimized in C++20
+ boost::property_tree::ptree aRoot;
+ boost::property_tree::read_json(aStream, aRoot);
+
+ if (auto tree = aRoot.get_child_optional(path))
+ {
+ uno::Sequence<SingleProofreadingError> aErrors(tree->size());
+ auto it = tree->begin();
+ for (auto& rError : asNonConstRange(aErrors))
+ {
+ f(it->second, rError);
+ it++;
+ }
+ return aErrors;
+ }
+ return {};
+}
+
+void parseDudenResponse(ProofreadingResult& rResult, std::string&& aJSONBody)
+{
+ rResult.aErrors = parseJson(
+ std::move(aJSONBody), "check-positions",
+ [](const boost::property_tree::ptree& rPos, SingleProofreadingError& rError) {
+ rError.nErrorStart = rPos.get<int>("offset", 0);
+ rError.nErrorLength = rPos.get<int>("length", 0);
+ rError.nErrorType = text::TextMarkupType::PROOFREADING;
+ //rError.aShortComment = ??
+ //rError.aFullComment = ??
+ const std::string sType = rPos.get<std::string>("type", {});
+ rError.aProperties = { lcl_GetLineColorPropertyFromErrorId(sType) };
+
+ const auto proposals = rPos.get_child_optional("proposals");
+ if (!proposals)
+ return;
+ rError.aSuggestions.realloc(std::min(proposals->size(), MAX_SUGGESTIONS_SIZE));
+ auto itProp = proposals->begin();
+ for (auto& rSuggestion : asNonConstRange(rError.aSuggestions))
+ {
+ rSuggestion = OStringToOUString(itProp->second.data(), RTL_TEXTENCODING_UTF8);
+ itProp++;
+ }
+ });
+}
+
+/*
+ rResult is both input and output
+ aJSONBody is the response body from the HTTP Request to LanguageTool API
+*/
+void parseProofreadingJSONResponse(ProofreadingResult& rResult, std::string&& aJSONBody)
+{
+ rResult.aErrors = parseJson(
+ std::move(aJSONBody), "matches",
+ [](const boost::property_tree::ptree& match, SingleProofreadingError& rError) {
+ rError.nErrorStart = match.get<int>("offset", 0);
+ rError.nErrorLength = match.get<int>("length", 0);
+ rError.nErrorType = text::TextMarkupType::PROOFREADING;
+ const std::string shortMessage = match.get<std::string>("message", {});
+ const std::string message = match.get<std::string>("shortMessage", {});
+
+ rError.aShortComment = OStringToOUString(shortMessage, RTL_TEXTENCODING_UTF8);
+ rError.aFullComment = OStringToOUString(message, RTL_TEXTENCODING_UTF8);
+
+ // Parse the error category for Line Color
+ std::string errorCategoryId;
+ if (auto rule = match.get_child_optional("rule"))
+ if (auto ruleCategory = rule->get_child_optional("category"))
+ errorCategoryId = ruleCategory->get<std::string>("id", {});
+ rError.aProperties = { lcl_GetLineColorPropertyFromErrorId(errorCategoryId) };
+
+ const auto replacements = match.get_child_optional("replacements");
+ if (!replacements)
+ return;
+ // 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.)
+ rError.aSuggestions.realloc(std::min(replacements->size(), MAX_SUGGESTIONS_SIZE));
+ auto itRep = replacements->begin();
+ for (auto& rSuggestion : asNonConstRange(rError.aSuggestions))
+ {
+ std::string replacementStr = itRep->second.get<std::string>("value", {});
+ rSuggestion = OStringToOUString(replacementStr, RTL_TEXTENCODING_UTF8);
+ itRep++;
+ }
+ });
+}
+
+OUString getCheckerURL()
+{
+ if (auto oURL = LanguageToolCfg::BaseURL::get())
+ if (!oURL->isEmpty())
+ return *oURL + "/check";
+ return {};
+}
+}
+
+LanguageToolGrammarChecker::LanguageToolGrammarChecker()
+ : mCachedResults(10)
+{
+}
+
+LanguageToolGrammarChecker::~LanguageToolGrammarChecker() {}
+
+sal_Bool SAL_CALL LanguageToolGrammarChecker::isSpellChecker() { return false; }
+
+sal_Bool SAL_CALL LanguageToolGrammarChecker::hasLocale(const Locale& rLocale)
+{
+ if (!m_aSuppLocales.hasElements())
+ getLocales();
+
+ for (auto const& suppLocale : std::as_const(m_aSuppLocales))
+ if (rLocale == suppLocale)
+ return true;
+
+ SAL_INFO("languagetool", "No locale \"" << LanguageTag::convertToBcp47(rLocale, false) << "\"");
+ return false;
+}
+
+uno::Sequence<Locale> SAL_CALL LanguageToolGrammarChecker::getLocales()
+{
+ osl::MutexGuard aGuard(linguistic::GetLinguMutex());
+
+ if (m_aSuppLocales.hasElements())
+ return m_aSuppLocales;
+
+ if (!LanguageToolCfg::IsEnabled::get())
+ return m_aSuppLocales;
+
+ SvtLinguConfig aLinguCfg;
+ uno::Sequence<OUString> aLocaleList;
+
+ if (LanguageToolCfg::RestProtocol::get().value_or("") == sDuden)
+ {
+ aLocaleList.realloc(3);
+ aLocaleList.getArray()[0] = "de-DE";
+ aLocaleList.getArray()[1] = "en-US";
+ aLocaleList.getArray()[2] = "en-GB";
+ }
+ else
+ aLinguCfg.GetLocaleListFor("GrammarCheckers",
+ "org.openoffice.lingu.LanguageToolGrammarChecker", aLocaleList);
+
+ auto nLength = aLocaleList.getLength();
+ m_aSuppLocales.realloc(nLength);
+ auto pArray = m_aSuppLocales.getArray();
+ auto pLocaleList = aLocaleList.getArray();
+
+ for (auto i = 0; i < nLength; i++)
+ {
+ pArray[i] = LanguageTag::convertToLocale(pLocaleList[i]);
+ }
+
+ return m_aSuppLocales;
+}
+
+ProofreadingResult SAL_CALL LanguageToolGrammarChecker::doProofreading(
+ const OUString& aDocumentIdentifier, const OUString& aText, const Locale& aLocale,
+ sal_Int32 nStartOfSentencePosition, sal_Int32 nSuggestedBehindEndOfSentencePosition,
+ const uno::Sequence<PropertyValue>& 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 = {};
+ xRes.xProofreader = this;
+ xRes.aErrors = {};
+
+ if (aText.isEmpty())
+ {
+ return xRes;
+ }
+
+ if (nStartOfSentencePosition != 0)
+ {
+ return xRes;
+ }
+
+ xRes.nStartOfNextSentencePosition = aText.getLength();
+
+ if (!LanguageToolCfg::IsEnabled::get())
+ {
+ return xRes;
+ }
+
+ OUString checkerURL = getCheckerURL();
+ 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());
+
+ OString langTag(LanguageTag::convertToBcp47(aLocale, false).toUtf8());
+ OString postData;
+ const bool bDudenProtocol = LanguageToolCfg::RestProtocol::get().value_or("") == "duden";
+ if (bDudenProtocol)
+ {
+ std::stringstream aStream;
+ boost::property_tree::ptree aTree;
+ aTree.put("text-language", langTag.getStr());
+ aTree.put("text", aText.toUtf8()); // We don't encode the text in Duden Corrector tool case.
+ aTree.put("hyphenation", false);
+ aTree.put("spellchecking-level", 3);
+ aTree.put("correction-proposals", true);
+ boost::property_tree::write_json(aStream, aTree);
+ postData = OString(aStream.str());
+ }
+ else
+ {
+ postData = "text=" + encodeTextForLT(aText) + "&language=" + langTag;
+ }
+
+ if (auto cachedResult = mCachedResults.find(postData); cachedResult != mCachedResults.end())
+ {
+ xRes.aErrors = cachedResult->second;
+ return xRes;
+ }
+
+ tools::Long http_code = 0;
+ std::string response_body;
+ if (bDudenProtocol)
+ response_body = makeDudenHttpRequest(checkerURL, postData, http_code);
+ else
+ response_body = makeHttpRequest(checkerURL, HTTP_METHOD::HTTP_POST, postData, http_code);
+
+ if (http_code != 200)
+ {
+ return xRes;
+ }
+
+ if (response_body.length() <= 0)
+ {
+ return xRes;
+ }
+
+ if (bDudenProtocol)
+ {
+ parseDudenResponse(xRes, std::move(response_body));
+ }
+ else
+ {
+ parseProofreadingJSONResponse(xRes, std::move(response_body));
+ }
+ // cache the result
+ mCachedResults.insert(std::make_pair(postData, xRes.aErrors));
+ return xRes;
+}
+
+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);
+}
+
+uno::Sequence<OUString> SAL_CALL LanguageToolGrammarChecker::getSupportedServiceNames()
+{
+ return { SN_GRAMMARCHECKER };
+}
+
+void SAL_CALL LanguageToolGrammarChecker::initialize(const uno::Sequence<uno::Any>&) {}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+lingucomponent_LanguageToolGrammarChecker_get_implementation(
+ css::uno::XComponentContext*, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new LanguageToolGrammarChecker());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx
new file mode 100644
index 0000000000..93d2c84c61
--- /dev/null
+++ b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx
@@ -0,0 +1,75 @@
+/* -*- 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 <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceDisplayName.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XServiceName.hpp>
+#include <com/sun/star/linguistic2/XProofreader.hpp>
+#include <com/sun/star/linguistic2/ProofreadingResult.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/PropertyValues.hpp>
+#include <linguistic/misc.hxx>
+#include <string_view>
+#include <o3tl/lru_map.hxx>
+#include <tools/long.hxx>
+
+class LanguageToolGrammarChecker
+ : public cppu::WeakImplHelper<css::linguistic2::XProofreader, css::lang::XInitialization,
+ css::lang::XServiceInfo, css::lang::XServiceDisplayName>
+{
+ css::uno::Sequence<css::lang::Locale> m_aSuppLocales;
+ o3tl::lru_map<OString, css::uno::Sequence<css::linguistic2::SingleProofreadingError>>
+ mCachedResults;
+ LanguageToolGrammarChecker(const LanguageToolGrammarChecker&) = delete;
+ LanguageToolGrammarChecker& operator=(const LanguageToolGrammarChecker&) = delete;
+
+public:
+ LanguageToolGrammarChecker();
+ virtual ~LanguageToolGrammarChecker() override;
+
+ // XSupportedLocales
+ virtual css::uno::Sequence<css::lang::Locale> SAL_CALL getLocales() override;
+ virtual sal_Bool SAL_CALL hasLocale(const css::lang::Locale& rLocale) override;
+
+ // XProofReader
+ virtual sal_Bool SAL_CALL isSpellChecker() override;
+ virtual css::linguistic2::ProofreadingResult SAL_CALL
+ doProofreading(const OUString& aDocumentIdentifier, const OUString& aText,
+ const css::lang::Locale& aLocale, sal_Int32 nStartOfSentencePosition,
+ sal_Int32 nSuggestedBehindEndOfSentencePosition,
+ const css::uno::Sequence<css::beans::PropertyValue>& aProperties) override;
+
+ virtual void SAL_CALL ignoreRule(const OUString& aRuleIdentifier,
+ const css::lang::Locale& aLocale) override;
+ virtual void SAL_CALL resetIgnoreRules() override;
+
+ // XServiceDisplayName
+ virtual OUString SAL_CALL getServiceDisplayName(const css::lang::Locale& rLocale) override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArguments) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */