cui/source/options/optlanguagetool.cxx | 3 cui/source/options/optlanguagetool.hxx | 3 cui/uiconfig/ui/langtoolconfigpage.ui | 38 +- desktop/source/lib/init.cxx | 11 include/svtools/languagetoolcfg.hxx | 3 lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx | 148 +++++++ lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx | 7 linguistic/CppunitTest_linguistic_restprotocol.mk | 39 ++ linguistic/Module_linguistic.mk | 7 linguistic/qa/restprotocol.cxx | 189 ++++++++++ officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs | 6 svtools/source/config/languagetoolcfg.cxx | 21 + sw/qa/extras/htmlexport/htmlexport.cxx | 61 +++ sw/source/filter/html/htmlnumwriter.cxx | 21 - sw/source/uibase/shells/translatehelper.cxx | 9 15 files changed, 539 insertions(+), 27 deletions(-)
New commits: commit a38bd74c12076ec89145d6890cfe9e574f2d4ab2 Author: Szymon Kłos <szymon.k...@collabora.com> AuthorDate: Thu Dec 1 11:39:05 2022 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Dec 2 15:00:27 2022 +0000 htmlexport: close li mark not only in xhtml As commented in sw/source/filter/html/htmlatr.cxx:766 "// OutHTML_NumberBulletListEnd() will end a list item" We have to do it also in HTML not only XHTML so node will be closed and we get correct structure. This is followup for: commit 2466b9fcf20ee61f6fc621298011c0efaa55e7c6 Author: Miklos Vajna <vmik...@collabora.com> Date: Mon Feb 21 16:38:51 2022 +0100 sw HTML export, XHTML mode: fix lost </li> when last list item is not numbered Change-Id: I99baa59b437572b157df8057445cf6c741f936b4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143508 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Andras Timar <andras.ti...@collabora.com> diff --git a/sw/qa/extras/htmlexport/htmlexport.cxx b/sw/qa/extras/htmlexport/htmlexport.cxx index 45e44186d69b..31f6d03902f8 100644 --- a/sw/qa/extras/htmlexport/htmlexport.cxx +++ b/sw/qa/extras/htmlexport/htmlexport.cxx @@ -208,6 +208,13 @@ public: rStream.Seek(0); } + static void wrapHTMLFragment(const utl::TempFile& rTempFile, SvMemoryStream& rStream) + { + SvFileStream aFileStream(rTempFile.GetURL(), StreamMode::READ); + rStream.WriteStream(aFileStream); + rStream.Seek(0); + } + /// Wraps an RTF fragment into a complete RTF file, so an RTF parser can handle it. static void wrapRtfFragment(const OUString& rURL, SvMemoryStream& rStream) { @@ -284,6 +291,8 @@ public: void ExportToReqif(); /// Import using the C++ HTML import filter, with xhtmlns=reqif-xhtml. void ImportFromReqif(const OUString& rUrl); + /// Export using the C++ HTML export filter + void ExportToHTML(); }; OUString SwHtmlDomExportTest::GetOlePath() @@ -338,6 +347,15 @@ void SwHtmlDomExportTest::ExportToReqif() xStorable->storeToURL(maTempFile.GetURL(), aStoreProperties); } +void SwHtmlDomExportTest::ExportToHTML() +{ + uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY); + uno::Sequence<beans::PropertyValue> aStoreProperties = { + comphelper::makePropertyValue("FilterName", OUString("HTML (StarWriter)")), + }; + xStorable->storeToURL(maTempFile.GetURL(), aStoreProperties); +} + void SwHtmlDomExportTest::ImportFromReqif(const OUString& rUrl) { uno::Sequence<beans::PropertyValue> aLoadProperties = { @@ -1535,6 +1553,49 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testPartiallyNumberedList) "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:ol/reqif-xhtml:li/reqif-xhtml:p", 2); } +CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testPartiallyNumberedListHTML) +{ + // Given a document with a list, first para is numbered, second is not: + loadURL("private:factory/swriter", nullptr); + SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get()); + SwWrtShell* pWrtShell = pTextDoc->GetDocShell()->GetWrtShell(); + pWrtShell->Insert("list header"); + SwDoc* pDoc = pWrtShell->GetDoc(); + sal_uInt16 nPos = pDoc->MakeNumRule(pDoc->GetUniqueNumRuleName()); + SwNumRule* pNumRule = pDoc->GetNumRuleTable()[nPos]; + { + SwNode& rNode = pWrtShell->GetCursor()->GetPoint()->nNode.GetNode(); + SwTextNode& rTextNode = *rNode.GetTextNode(); + rTextNode.SetAttr(SwNumRuleItem(pNumRule->GetName())); + } + pWrtShell->Insert2("numbered"); + pWrtShell->SplitNode(); + pWrtShell->Insert2("not numbered"); + { + SwNode& rNode = pWrtShell->GetCursor()->GetPoint()->nNode.GetNode(); + SwTextNode& rTextNode = *rNode.GetTextNode(); + rTextNode.SetAttr(SwNumRuleItem(pNumRule->GetName())); + rTextNode.SetCountedInList(false); + } + + // When exporting to HTML: + ExportToHTML(); + + SvMemoryStream aStream; + HtmlExportTest::wrapHTMLFragment(maTempFile, aStream); + + xmlDocUniquePtr pXmlDoc = parseXmlStream(&aStream); + CPPUNIT_ASSERT(pXmlDoc); // if we have missing closing marks - parse error + + // Without the accompanying fix in place, this test would have failed: + // - expected: <li><p>...</p><p>...</p></li> + // - actual : <li><p>...</p><p>...</p> + // because a <li> without a matching </li> is not well-formed, and the </li> was omitted because + // the second para was not numbered. + + assertXPath(pXmlDoc, "/html/body/ol/li/p", 2); +} + CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testListHeaderAndItem) { // Given a document with a list, first para is not numbered, but the second is: diff --git a/sw/source/filter/html/htmlnumwriter.cxx b/sw/source/filter/html/htmlnumwriter.cxx index f41ac73929f9..0144e2d0315b 100644 --- a/sw/source/filter/html/htmlnumwriter.cxx +++ b/sw/source/filter/html/htmlnumwriter.cxx @@ -324,7 +324,7 @@ Writer& OutHTML_NumberBulletListEnd( SwHTMLWriter& rWrt, bool bListEnd = !bSameRule || rNextInfo.GetDepth() < rInfo.GetDepth() || rNextInfo.IsRestart(); std::optional<bool> oAtLeastOneNumbered; - if (rWrt.mbXHTML && !rInfo.IsNumbered()) + if (!rInfo.IsNumbered()) { oAtLeastOneNumbered = false; SwNodeOffset nPos = rWrt.m_pCurrentPam->GetPoint()->nNode.GetIndex() - 1; @@ -354,18 +354,15 @@ Writer& OutHTML_NumberBulletListEnd( SwHTMLWriter& rWrt, } } - if (rWrt.mbXHTML) + // The list is numbered if the previous text node is numbered or any other previous text + // node is numbered. + bool bPrevIsNumbered = rInfo.IsNumbered() || *oAtLeastOneNumbered; + // XHTML </li> for the list item content, if there is an open <li>. + if ((bListEnd && bPrevIsNumbered) || (!bListEnd && rNextInfo.IsNumbered())) { - // The list is numbered if the previous text node is numbered or any other previous text - // node is numbered. - bool bPrevIsNumbered = rInfo.IsNumbered() || *oAtLeastOneNumbered; - // XHTML </li> for the list item content, if there is an open <li>. - if ((bListEnd && bPrevIsNumbered) || (!bListEnd && rNextInfo.IsNumbered())) - { - HTMLOutFuncs::Out_AsciiTag( - rWrt.Strm(), OStringConcatenation(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_li), - false); - } + HTMLOutFuncs::Out_AsciiTag( + rWrt.Strm(), OStringConcatenation(rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_li), + false); } if (!bListEnd) commit e8f06727e919479916c588bd06c38d707e89e6a0 Author: Szymon Kłos <szymon.k...@collabora.com> AuthorDate: Wed Nov 30 17:53:34 2022 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Dec 2 15:00:16 2022 +0000 deepl: don't introduce new bullets Used HTML should be free from new line characters and also <ul> marks so we don't generate new lines. Change-Id: I02b8f9a9af9f93e8d90a7f4a22ea02c8e1f47651 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143507 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Andras Timar <andras.ti...@collabora.com> diff --git a/sw/source/uibase/shells/translatehelper.cxx b/sw/source/uibase/shells/translatehelper.cxx index f6802967a984..36ae57d177f6 100644 --- a/sw/source/uibase/shells/translatehelper.cxx +++ b/sw/source/uibase/shells/translatehelper.cxx @@ -44,7 +44,7 @@ OString ExportPaMToHTML(SwPaM* pCursor, bool bReplacePTag) SolarMutexGuard gMutex; OString aResult; WriterRef xWrt; - GetHTMLWriter(OUString("NoLineLimit,SkipHeaderFooter"), OUString(), xWrt); + GetHTMLWriter(OUString("NoLineLimit,SkipHeaderFooter,NoPrettyPrint"), OUString(), xWrt); if (pCursor != nullptr) { SvMemoryStream aMemoryStream; @@ -62,6 +62,13 @@ OString ExportPaMToHTML(SwPaM* pCursor, bool bReplacePTag) aResult = aResult.replaceAll("<p", "<span"); aResult = aResult.replaceAll("</p>", "</span>"); } + + // HTML has for that <br> and <p> also does new line + aResult = aResult.replaceAll("<ul>", ""); + aResult = aResult.replaceAll("</ul>", ""); + aResult = aResult.replaceAll("<ol>", ""); + aResult = aResult.replaceAll("</ol>", ""); + aResult = aResult.replaceAll("\n", "").trim(); return aResult; } return {}; commit 2b6bebfa3fc8f35aa8473c1bd5e4efdedf0f8d89 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Thu Nov 24 18:35:08 2022 -0400 Commit: Henry Castro <hcas...@collabora.com> CommitDate: Fri Dec 2 12:26:36 2022 +0000 linguistic: add REST API protocol unit test Signed-off-by: Henry Castro <hcas...@collabora.com> Change-Id: I4768f8bb5bfa572d222fa5610f95c99169e6e390 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143249 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> diff --git a/linguistic/CppunitTest_linguistic_restprotocol.mk b/linguistic/CppunitTest_linguistic_restprotocol.mk new file mode 100644 index 000000000000..4e56286efe0d --- /dev/null +++ b/linguistic/CppunitTest_linguistic_restprotocol.mk @@ -0,0 +1,39 @@ +# -*- 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_CppunitTest_CppunitTest,linguistic_restprotocol)) + +$(eval $(call gb_CppunitTest_add_exception_objects,linguistic_restprotocol, \ + linguistic/qa/restprotocol \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,linguistic_restprotocol, \ + comphelper \ + cppu \ + cppuhelper \ + sal \ + svt \ + utl \ + test \ + unotest \ +)) + +$(eval $(call gb_CppunitTest_use_api,linguistic_restprotocol,\ + udkapi \ + offapi \ + oovbaapi \ +)) + +$(eval $(call gb_CppunitTest_use_configuration,linguistic_restprotocol)) + +$(eval $(call gb_CppunitTest_use_ure,linguistic_restprotocol)) + +$(eval $(call gb_CppunitTest_use_rdb,linguistic_restprotocol,services)) + +# vim: set noet sw=4 ts=4: diff --git a/linguistic/Module_linguistic.mk b/linguistic/Module_linguistic.mk index 956c1bbc2e80..b5366b97e611 100644 --- a/linguistic/Module_linguistic.mk +++ b/linguistic/Module_linguistic.mk @@ -16,9 +16,16 @@ $(eval $(call gb_Module_add_targets,linguistic,\ #$(eval $(call gb_Module_add_check_targets,linguistic,\ #)) +ifeq ($(OS),LINUX) $(eval $(call gb_Module_add_subsequentcheck_targets,linguistic,\ JunitTest_linguistic_unoapi \ + CppunitTest_linguistic_restprotocol \ )) +else +$(eval $(call gb_Module_add_subsequentcheck_targets,linguistic,\ + JunitTest_linguistic_unoapi \ +)) +endif # was disabled in old build system # JunitTest_linguistic_complex \ diff --git a/linguistic/qa/restprotocol.cxx b/linguistic/qa/restprotocol.cxx new file mode 100644 index 000000000000..f0557be83dc8 --- /dev/null +++ b/linguistic/qa/restprotocol.cxx @@ -0,0 +1,189 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <algorithm> +#include <cassert> +#include <cstring> + +#include <sal/log.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/strbuf.hxx> +#include <osl/socket.hxx> +#include <osl/thread.hxx> +#include <svtools/languagetoolcfg.hxx> +#include <unotest/bootstrapfixturebase.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/connection/XAcceptor.hpp> +#include <com/sun/star/connection/XConnector.hpp> +#include <com/sun/star/linguistic2/XProofreader.hpp> +#include <com/sun/star/linguistic2/ProofreadingResult.hpp> + +using namespace ::com::sun::star::uno; + +class MockServerThread : public ::osl::Thread +{ +public: + MockServerThread() : + m_aSocketAddr("localhost", 2022) + { + } + + virtual void SAL_CALL run() + { + if (m_aAcceptorSocket.acceptConnection(m_aStreamSocket) != osl_Socket_Ok) + { + return; + } + + sal_Int32 nReadBytes; + Sequence<sal_Int8> aBuffer(512); + sal_Int32 nTcpNoDelay = sal_Int32(true); + m_aStreamSocket.setOption(osl_Socket_OptionTcpNoDelay, &nTcpNoDelay, + sizeof(nTcpNoDelay), osl_Socket_LevelTcp); + + nReadBytes = m_aStreamSocket.recv(aBuffer.getArray(), + aBuffer.getLength()); + if (nReadBytes) + { + std::string aText(reinterpret_cast<const char*>(aBuffer.getConstArray()), + nReadBytes); + + if (aText.find("POST /api/check") == std::string::npos) + { + NotFound(); + } + else if (aText.find("Content-Type: application/json") == std::string::npos) + { + NotFound(); + } + else + { + ResponseOK(); + } + + } + } + + void ResponseOK() + { + OStringBuffer aResponse; + + aResponse.append("HTTP/1.1 200 OK\r\n"); + aResponse.append("Server: MockServer\r\n"); + aResponse.append("Cache-Control: no-cache\r\n"); + aResponse.append("Content-Type: application/json\r\n"); + + aResponse.append("\r\n"); + aResponse.append("{\"check-positions\":[{\"offset\":15,\"length\":6,\"errorcode\":4711,\"type\":\"orth\"," + "\"severity\":1,\"proposals\":[\"Entwurf\",\"Entw\u00fcrfe\"]}," + "{\"offset\":22,\"length\":3,\"errorcode\":8221,\"type\":\"orth\",\"severity\":1}]}"); + + m_aStreamSocket.write(aResponse.getStr(), aResponse.getLength()); + m_aStreamSocket.close(); + } + + void NotFound() + { + OStringBuffer aResponse; + + aResponse.append("HTTP/1.1 404 Not Found\r\n"); + aResponse.append("Connection: Closed\r\n"); + aResponse.append("\r\n"); + + m_aStreamSocket.write(aResponse.getStr(), aResponse.getLength()); + m_aStreamSocket.close(); + } + + void stop() + { + m_aAcceptorSocket.close(); + join(); + } + + void init() + { + m_aAcceptorSocket.setOption(osl_Socket_OptionReuseAddr, 1); + CPPUNIT_ASSERT(m_aAcceptorSocket.bind(m_aSocketAddr)); + CPPUNIT_ASSERT(m_aAcceptorSocket.listen()); + } + +private: + ::osl::SocketAddr m_aSocketAddr; + ::osl::AcceptorSocket m_aAcceptorSocket; + ::osl::StreamSocket m_aStreamSocket; +}; + +MockServerThread aMockServer; + +class TestRestProtocol: public test::BootstrapFixtureBase { +public: + virtual void setUp() override; + virtual void tearDown() override; + +private: + CPPUNIT_TEST_SUITE(TestRestProtocol); + CPPUNIT_TEST(testProofreading); + CPPUNIT_TEST_SUITE_END(); + + void testProofreading(); + +}; + +void TestRestProtocol::testProofreading() +{ + css::lang::Locale aLocale("en", "US", ""); + Sequence<::com::sun::star::beans::PropertyValue> aProperties; + SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); + rLanguageOpts.setBaseURL("http://127.0.0.1:2022/api"); + rLanguageOpts.setUsername("hcastro"); + rLanguageOpts.setApiKey("hcvhcvhcv"); + rLanguageOpts.setEnabled(true); + rLanguageOpts.setSSLVerification(false); + rLanguageOpts.setRestProtocol("duden"); + CPPUNIT_ASSERT_EQUAL(OUString("duden"), rLanguageOpts.getRestProtocol()); + + Reference<::com::sun::star::linguistic2::XProofreader> xProofreader ( + m_xSFactory->createInstance("com.sun.star.linguistic2.Proofreader"), UNO_QUERY); + CPPUNIT_ASSERT(xProofreader.is()); + + com::sun::star::linguistic2::ProofreadingResult aResult = + xProofreader->doProofreading(OUString("id"), + OUString("ths is a tst"), + aLocale, + 0, + 0, + aProperties); + + CPPUNIT_ASSERT_EQUAL(2, aResult.aErrors.getLength()); +} + +void TestRestProtocol::setUp() +{ + test::BootstrapFixtureBase::setUp(); + + aMockServer.init(); + aMockServer.create(); + osl::Thread::wait(std::chrono::seconds(1)); +} + +void TestRestProtocol::tearDown() +{ + aMockServer.stop(); + + test::BootstrapFixtureBase::tearDown(); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(TestRestProtocol); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ commit fd8a522775ae087f6942409ab3c2558ee950c8e7 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Tue Nov 22 11:39:45 2022 -0400 Commit: Henry Castro <hcas...@collabora.com> CommitDate: Fri Dec 2 12:25:39 2022 +0000 lok: add Rest Protocol property Signed-off-by: Henry Castro <hcas...@collabora.com> Change-Id: I7353e16dbf835684e5ce5963387359924352937b Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143124 diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index 8f44e686417c..9fc497d46b0e 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -6992,22 +6992,27 @@ void setLanguageToolConfig() { const char* pEnabled = ::getenv("LANGUAGETOOL_ENABLED"); const char* pBaseUrlString = ::getenv("LANGUAGETOOL_BASEURL"); - const char* pUsername = ::getenv("LANGUAGETOOL_USERNAME"); - const char* pApikey = ::getenv("LANGUAGETOOL_APIKEY"); - const char* pSSLVerification = ::getenv("LANGUAGETOOL_SSL_VERIFICATION"); + if (pEnabled && pBaseUrlString) { + const char* pUsername = ::getenv("LANGUAGETOOL_USERNAME"); + const char* pApikey = ::getenv("LANGUAGETOOL_APIKEY"); + const char* pSSLVerification = ::getenv("LANGUAGETOOL_SSL_VERIFICATION"); + const char* pRestProtocol = ::getenv("LANGUAGETOOL_RESTPROTOCOL"); + OUString aEnabled = OStringToOUString(pEnabled, RTL_TEXTENCODING_UTF8); OUString aSSLVerification = OStringToOUString(pSSLVerification, RTL_TEXTENCODING_UTF8); if (aEnabled != "true") return; OUString aBaseUrl = OStringToOUString(pBaseUrlString, RTL_TEXTENCODING_UTF8); + OUString aRestProtocol = OStringToOUString(pRestProtocol, RTL_TEXTENCODING_UTF8); try { SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); rLanguageOpts.setBaseURL(aBaseUrl); rLanguageOpts.setEnabled(true); rLanguageOpts.setSSLVerification(aSSLVerification == "true"); + rLanguageOpts.setRestProtocol(aRestProtocol); if (pUsername && pApikey) { OUString aUsername = OStringToOUString(pUsername, RTL_TEXTENCODING_UTF8); commit 9feb70dcea15af05e3684009bb9d7d41a35d6cb1 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Mon Nov 21 21:38:40 2022 -0400 Commit: Henry Castro <hcas...@collabora.com> CommitDate: Fri Dec 2 12:25:03 2022 +0000 lingucomponent: implement custom parse response rest protocol Response: HTTP/1.1 200 OK Transfer encoding: chunked Content-Type: application/json { "hyphenation-positions":[ {"offset":15,"quality":0}, {"offset":20,"quality":1} ], }, "check-positions":[ {"offset":15,"length":6,"errorcode":4711,"type":"orth","severity":1,"propos als":["Entwurf","Entw\u00fcrfe"]}, {"offset":22,"length":3,"errorcode":8221,"type":"orth","severity":1} ] } Signed-off-by: Henry Castro <hcas...@collabora.com> Change-Id: I87228237f23306fb367edab1e21ce002aaf13f2c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143108 diff --git a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx index 2e7ca4c2e698..6cae9f78378a 100644 --- a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx +++ b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx @@ -65,7 +65,7 @@ PropertyValue lcl_MakePropertyValue(const OUString& rName, uno::Any& rValue) Sequence<PropertyValue> lcl_GetLineColorPropertyFromErrorId(const std::string& rErrorId) { uno::Any aColor; - if (rErrorId == "TYPOS") + if (rErrorId == "TYPOS" || rErrorId == "orth") { aColor <<= COL_LIGHTRED; } @@ -250,13 +250,77 @@ ProofreadingResult SAL_CALL LanguageToolGrammarChecker::doProofreading( return xRes; } - parseProofreadingJSONResponse(xRes, response_body); + if (rLanguageOpts.getRestProtocol() == sDuden) + { + parseDudenResponse(xRes, response_body); + } + else + { + parseProofreadingJSONResponse(xRes, response_body); + } // cache the result mCachedResults.insert( std::pair<OUString, Sequence<SingleProofreadingError>>(aText, xRes.aErrors)); return xRes; } +void LanguageToolGrammarChecker::parseDudenResponse(ProofreadingResult& rResult, + std::string_view aJSONBody) +{ + size_t nSize; + int nProposalSize; + boost::property_tree::ptree aRoot; + std::stringstream aStream(aJSONBody.data()); + boost::property_tree::read_json(aStream, aRoot); + + const boost::optional<boost::property_tree::ptree&> aPositions = aRoot.get_child_optional("check-positions"); + if (!aPositions || !(nSize = aPositions.get().size())) + { + return; + } + + Sequence<SingleProofreadingError> aChecks(nSize); + auto pChecks = aChecks.getArray(); + size_t nIndex1 = 0, nIndex2 = 0; + auto itPos = aPositions.get().begin(); + while (itPos != aPositions.get().end()) + { + const boost::property_tree::ptree& rTree = itPos->second; + const std::string sType= rTree.get<std::string>("type", ""); + const int nOffset = rTree.get<int>("offset", 0); + const int nLength = rTree.get<int>("length", 0); + + pChecks[nIndex1].nErrorStart = nOffset; + pChecks[nIndex1].nErrorLength = nLength; + pChecks[nIndex1].nErrorType = PROOFREADING_ERROR; + //pChecks[nIndex1].aShortComment = ?? + //pChecks[nIndex1].aFullComment = ?? + pChecks[nIndex1].aProperties = lcl_GetLineColorPropertyFromErrorId(sType); + + const boost::optional<const boost::property_tree::ptree&> aProposals = + rTree.get_child_optional("proposals"); + if (aProposals && (nProposalSize = aProposals.get().size())) + { + pChecks[nIndex1].aSuggestions.realloc(std::min(nProposalSize, MAX_SUGGESTIONS_SIZE)); + + nIndex2 = 0; + auto itProp = aProposals.get().begin(); + auto pSuggestions = pChecks[nIndex1].aSuggestions.getArray(); + while (itProp != aProposals.get().end() && nIndex2 < MAX_SUGGESTIONS_SIZE) + { + pSuggestions[nIndex2++] = + OStringToOUString(itProp->second.data(), RTL_TEXTENCODING_UTF8); + itProp++; + } + } + + nIndex1++; + itPos++; + } + + rResult.aErrors = aChecks; +} + /* rResult is both input and output aJSONBody is the response body from the HTTP Request to LanguageTool API diff --git a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx index 3316e2ecb74b..a711a71597ec 100644 --- a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx +++ b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx @@ -54,6 +54,8 @@ class LanguageToolGrammarChecker o3tl::lru_map<OUString, Sequence<SingleProofreadingError>> mCachedResults; LanguageToolGrammarChecker(const LanguageToolGrammarChecker&) = delete; LanguageToolGrammarChecker& operator=(const LanguageToolGrammarChecker&) = delete; + static void parseDudenResponse(ProofreadingResult& rResult, + std::string_view aJSONBody); static void parseProofreadingJSONResponse(ProofreadingResult& rResult, std::string_view aJSONBody); static std::string makeDudenHttpRequest(std::string_view aURL, HTTP_METHOD method, @@ -91,4 +93,4 @@ public: virtual OUString SAL_CALL getImplementationName() override; virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override; virtual Sequence<OUString> SAL_CALL getSupportedServiceNames() override; -}; \ No newline at end of file +}; commit ae4006a9c8829981ad9de8055bcc5d93f0e94b70 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Mon Nov 21 21:34:24 2022 -0400 Commit: Henry Castro <hcas...@collabora.com> CommitDate: Fri Dec 2 12:23:25 2022 +0000 lingucomponent: implement custom request http rest protocol Request: POST /api/check HTTP/1.1 Host: localhost:8099 Content-Type: application/json Cache-Control: no-cache { "dictionaries": [ "daily", "jungblutt" ], "text-language": "en-DE", "property-sets": [ "base", "daily", "culture" ], "hyphenation": true, "spellchecking-level": 1, "correction-proposals": true, "single-word-mode": false, "message-language": "fr-LU", "message-level": 1, "text": "This is a final throw for the interface to the Duden server." } Signed-off-by: Henry Castro <hcas...@collabora.com> Change-Id: I2a288a7c573014d42df03f7cc12c57a7f788750e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143107 Reviewed-by: Ashod Nakashian <a...@collabora.com> diff --git a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx index da0d133a6965..2e7ca4c2e698 100644 --- a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx +++ b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.cxx @@ -53,6 +53,8 @@ using namespace linguistic; #define COL_ORANGE Color(0xD1, 0x68, 0x20) +constexpr OUStringLiteral sDuden = u"duden"; + namespace { PropertyValue lcl_MakePropertyValue(const OUString& rName, uno::Any& rValue) @@ -215,11 +217,28 @@ ProofreadingResult SAL_CALL LanguageToolGrammarChecker::doProofreading( } tools::Long http_code = 0; + std::string response_body; 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 (rLanguageOpts.getRestProtocol() == sDuden) + { + std::stringstream aStream; + boost::property_tree::ptree aTree; + aTree.put("text-language", langTag.toUtf8().getStr()); + aTree.put("text", aText.toUtf8().getStr()); + aTree.put("hyphenation", false); + aTree.put("spellchecking-level", 3); + aTree.put("correction-proposals", true); + boost::property_tree::write_json(aStream, aTree); + response_body = makeDudenHttpRequest(checkerURL, HTTP_METHOD::HTTP_POST, + aStream.str().c_str(), http_code); + } + else + { + OString postData(OUStringToOString( + OUStringConcatenation("text=" + aText + "&language=" + langTag), RTL_TEXTENCODING_UTF8)); + response_body = makeHttpRequest(checkerURL, HTTP_METHOD::HTTP_POST, postData, http_code); + } if (http_code != 200) { @@ -306,6 +325,57 @@ void LanguageToolGrammarChecker::parseProofreadingJSONResponse(ProofreadingResul rResult.aErrors = aErrors; } +std::string LanguageToolGrammarChecker::makeDudenHttpRequest(std::string_view aURL, HTTP_METHOD method, + const OString& aData, + tools::Long& nCode) +{ + std::unique_ptr<CURL, std::function<void(CURL*)>> curl(curl_easy_init(), + [](CURL* p) { curl_easy_cleanup(p); }); + if (!curl) + return {}; // empty string + + std::string sResponseBody; + struct curl_slist* pList = nullptr; + SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); + OString sAccessToken = OString("access_token: ") + + OUStringToOString(rLanguageOpts.getApiKey(), RTL_TEXTENCODING_UTF8); + + pList = curl_slist_append(pList, "Cache-Control: no-cache"); + pList = curl_slist_append(pList, "Content-Type: application/json"); + if (!sAccessToken.isEmpty()) + pList = curl_slist_append(pList, sAccessToken.getStr()); + + curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, pList); + curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(curl.get(), CURLOPT_URL, aURL.data()); + curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, CURL_TIMEOUT); + curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, static_cast<void*>(&sResponseBody)); + + // allow unknown or self-signed certificates + if (rLanguageOpts.getSSLVerification() == false) + { + curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYHOST, false); + } + + if (method == HTTP_METHOD::HTTP_POST) + { + curl_easy_setopt(curl.get(), CURLOPT_POST, 1L); + curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, aData.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, &nCode); + return sResponseBody; + +} + std::string LanguageToolGrammarChecker::makeHttpRequest(std::string_view aURL, HTTP_METHOD method, const OString& aPostData, tools::Long& nStatusCode) @@ -399,4 +469,4 @@ lingucomponent_LanguageToolGrammarChecker_get_implementation( css::uno::XComponentContext*, css::uno::Sequence<css::uno::Any> const&) { return cppu::acquire(static_cast<cppu::OWeakObject*>(new LanguageToolGrammarChecker())); -} \ No newline at end of file +} diff --git a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx index e0dadfecab68..3316e2ecb74b 100644 --- a/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx +++ b/lingucomponent/source/spellcheck/languagetool/languagetoolimp.hxx @@ -56,6 +56,9 @@ class LanguageToolGrammarChecker LanguageToolGrammarChecker& operator=(const LanguageToolGrammarChecker&) = delete; static void parseProofreadingJSONResponse(ProofreadingResult& rResult, std::string_view aJSONBody); + static std::string makeDudenHttpRequest(std::string_view aURL, HTTP_METHOD method, + const OString& aPostData, + tools::Long& nStatusCode); static std::string makeHttpRequest(std::string_view aURL, HTTP_METHOD method, const OString& aPostData, tools::Long& nStatusCode); commit 70ff8fc992c06edfd6ae60da39cabed8cb56a5c5 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Mon Nov 21 21:30:02 2022 -0400 Commit: Henry Castro <hcas...@collabora.com> CommitDate: Fri Dec 2 12:22:49 2022 +0000 cui: add entry "RestProtocol" to language tool dialog Signed-off-by: Henry Castro <hcas...@collabora.com> Change-Id: I6511fc2b353c47b1ff537c42d3484b3a42c1b121 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143106 Reviewed-by: Ashod Nakashian <a...@collabora.com> diff --git a/cui/source/options/optlanguagetool.cxx b/cui/source/options/optlanguagetool.cxx index fc7a42536051..a2cc19b93b68 100644 --- a/cui/source/options/optlanguagetool.cxx +++ b/cui/source/options/optlanguagetool.cxx @@ -28,6 +28,7 @@ OptLanguageToolTabPage::OptLanguageToolTabPage(weld::Container* pPage, , m_xBaseURLED(m_xBuilder->weld_entry("baseurl")) , m_xUsernameED(m_xBuilder->weld_entry("username")) , m_xApiKeyED(m_xBuilder->weld_entry("apikey")) + , m_xRestProtocol(m_xBuilder->weld_entry("restprotocol")) , m_xActivateBox(m_xBuilder->weld_check_button("activate")) , m_xSSLDisableVerificationBox(m_xBuilder->weld_check_button("verifyssl")) , m_xApiSettingsFrame(m_xBuilder->weld_frame("apisettings")) @@ -59,6 +60,7 @@ void OptLanguageToolTabPage::Reset(const SfxItemSet*) m_xBaseURLED->set_text(rLanguageOpts.getBaseURL()); m_xUsernameED->set_text(rLanguageOpts.getUsername()); m_xApiKeyED->set_text(rLanguageOpts.getApiKey()); + m_xRestProtocol->set_text(rLanguageOpts.getRestProtocol()); m_xSSLDisableVerificationBox->set_active(rLanguageOpts.getSSLVerification() != true); } @@ -68,6 +70,7 @@ bool OptLanguageToolTabPage::FillItemSet(SfxItemSet*) rLanguageOpts.setBaseURL(m_xBaseURLED->get_text()); rLanguageOpts.setUsername(m_xUsernameED->get_text()); rLanguageOpts.setApiKey(m_xApiKeyED->get_text()); + rLanguageOpts.setRestProtocol(m_xRestProtocol->get_text()); rLanguageOpts.setSSLVerification(m_xSSLDisableVerificationBox->get_active() != true); return false; } diff --git a/cui/source/options/optlanguagetool.hxx b/cui/source/options/optlanguagetool.hxx index 85e0238b0318..47179f016b2f 100644 --- a/cui/source/options/optlanguagetool.hxx +++ b/cui/source/options/optlanguagetool.hxx @@ -35,6 +35,7 @@ private: std::unique_ptr<weld::Entry> m_xBaseURLED; std::unique_ptr<weld::Entry> m_xUsernameED; std::unique_ptr<weld::Entry> m_xApiKeyED; + std::unique_ptr<weld::Entry> m_xRestProtocol; std::unique_ptr<weld::CheckButton> m_xActivateBox; std::unique_ptr<weld::CheckButton> m_xSSLDisableVerificationBox; std::unique_ptr<weld::Frame> m_xApiSettingsFrame; @@ -42,4 +43,4 @@ private: void EnableControls(bool bEnable); DECL_LINK(CheckHdl, weld::Toggleable&, void); -}; \ No newline at end of file +}; diff --git a/cui/uiconfig/ui/langtoolconfigpage.ui b/cui/uiconfig/ui/langtoolconfigpage.ui index 237040fa76b7..1d7452161998 100644 --- a/cui/uiconfig/ui/langtoolconfigpage.ui +++ b/cui/uiconfig/ui/langtoolconfigpage.ui @@ -80,7 +80,7 @@ <object class="GtkGrid" id="grid2"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="row_spacing">5</property> + <property name="row_spacing">6</property> <property name="column_spacing">12</property> <child> <object class="GtkLabel" id="base"> @@ -191,6 +191,42 @@ <property name="top_attach">5</property> </packing> </child> + <child> + <object class="GtkLabel" id="restlbl"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes" context="langtoolconfigpage|restlbl">REST protocol:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">restprotocol</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">6</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="restprotocol"> + <property name="visible">True</property> + <property name="can_focus">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">6</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="restdesc"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes" context="langtoolconfigpage|restdesc">Your LanguageTool REST API protocol for usage.</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">7</property> + </packing> + </child> <child> <placeholder/> </child> commit b0ca71915096c2f094d741c19ab8d7ee67ee735e Author: Henry Castro <hcas...@collabora.com> AuthorDate: Mon Nov 21 21:27:24 2022 -0400 Commit: Henry Castro <hcas...@collabora.com> CommitDate: Fri Dec 2 12:22:16 2022 +0000 svtools: add new entry "RestProtocol" Custom Rest API protocol Signed-off-by: Henry Castro <hcas...@collabora.com> Change-Id: If2f72330f2ed9768f230dc88296df7f757be263a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143105 Reviewed-by: Ashod Nakashian <a...@collabora.com> diff --git a/include/svtools/languagetoolcfg.hxx b/include/svtools/languagetoolcfg.hxx index 3f30c4bd94a8..24c4de6408aa 100644 --- a/include/svtools/languagetoolcfg.hxx +++ b/include/svtools/languagetoolcfg.hxx @@ -39,6 +39,9 @@ public: const OUString& getBaseURL() const; void setBaseURL(const OUString& rVal); + const OUString& getRestProtocol() const; + void setRestProtocol(const OUString& rVal); + const OUString& getUsername() const; void setUsername(const OUString& rVal); diff --git a/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs b/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs index c80db6c6aa09..61057f2ba02e 100644 --- a/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs +++ b/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs @@ -442,6 +442,12 @@ </info> <value>true</value> </prop> + <prop oor:name="RestProtocol" oor:type="xs:string"> + <info> + <desc>LanguageTool Grammar Checker REST API protocol</desc> + <label>REST API protocol</label> + </info> + </prop> </group> </group> <group oor:name="Translation"> diff --git a/svtools/source/config/languagetoolcfg.cxx b/svtools/source/config/languagetoolcfg.cxx index 3f48010141db..311cefb320f4 100644 --- a/svtools/source/config/languagetoolcfg.cxx +++ b/svtools/source/config/languagetoolcfg.cxx @@ -31,6 +31,7 @@ struct LanguageToolOptions_Impl OUString sBaseURL; OUString sUsername; OUString sApiKey; + OUString sRestProtocol; bool bEnabled; bool bSSLCertVerificatrionEnabled; }; @@ -43,6 +44,7 @@ const Sequence<OUString>& SvxLanguageToolOptions::GetPropertyNames() "LanguageTool/ApiKey", "LanguageTool/IsEnabled", "LanguageTool/SSLCertVerify", + "LanguageTool/RestProtocol" }; return aNames; } @@ -69,12 +71,23 @@ OUString SvxLanguageToolOptions::getCheckerURL() const { return pImpl->sBaseURL const OUString& SvxLanguageToolOptions::getApiKey() const { return pImpl->sApiKey; } +const OUString& SvxLanguageToolOptions::getRestProtocol() const { return pImpl->sRestProtocol; } + void SvxLanguageToolOptions::setApiKey(const OUString& rVal) { pImpl->sApiKey = rVal; SetModified(); } +void SvxLanguageToolOptions::setRestProtocol(const OUString& rVal) +{ + if (pImpl->sRestProtocol != rVal) + { + pImpl->sRestProtocol = rVal; + SetModified(); + } +} + bool SvxLanguageToolOptions::getEnabled() const { return pImpl->bEnabled; } bool SvxLanguageToolOptions::getSSLVerification() const { return pImpl->bSSLCertVerificatrionEnabled; } @@ -142,6 +155,9 @@ void SvxLanguageToolOptions::Load(const css::uno::Sequence<OUString>& aNames) case 4: pValues[nProp] >>= pImpl->bSSLCertVerificatrionEnabled; break; + case 5: + pValues[nProp] >>= pImpl->sRestProtocol; + break; default: break; } @@ -172,9 +188,12 @@ void SvxLanguageToolOptions::ImplCommit() case 4: pValues[nProp] <<= pImpl->bSSLCertVerificatrionEnabled; break; + case 5: + pValues[nProp] <<= pImpl->sRestProtocol; + break; default: break; } } PutProperties(aNames, aValues); -} \ No newline at end of file +}