/src/libreoffice/sw/source/uibase/shells/translatehelper.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
11 | | * Licensed to the Apache Software Foundation (ASF) under one or more |
12 | | * contributor license agreements. See the NOTICE file distributed |
13 | | * with this work for additional information regarding copyright |
14 | | * ownership. The ASF licenses this file to you under the Apache |
15 | | * License, Version 2.0 (the "License"); you may not use this file |
16 | | * except in compliance with the License. You may obtain a copy of |
17 | | * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
18 | | */ |
19 | | |
20 | | #include <sal/config.h> |
21 | | |
22 | | #include <config_features.h> |
23 | | #include <wrtsh.hxx> |
24 | | #include <pam.hxx> |
25 | | #include <node.hxx> |
26 | | #include <ndtxt.hxx> |
27 | | #include <translatehelper.hxx> |
28 | | #include <o3tl/string_view.hxx> |
29 | | #include <sal/log.hxx> |
30 | | #include <rtl/string.h> |
31 | | #include <shellio.hxx> |
32 | | #include <vcl/svapp.hxx> |
33 | | #include <vcl/htmltransferable.hxx> |
34 | | #include <vcl/transfer.hxx> |
35 | | #include <swdtflvr.hxx> |
36 | | #include <linguistic/translate.hxx> |
37 | | #include <com/sun/star/task/XStatusIndicator.hpp> |
38 | | #include <sfx2/viewfrm.hxx> |
39 | | #include <com/sun/star/task/XStatusIndicatorFactory.hpp> |
40 | | #include <officecfg/Office/Linguistic.hxx> |
41 | | #include <strings.hrc> |
42 | | |
43 | | namespace SwTranslateHelper |
44 | | { |
45 | | OString ExportPaMToHTML(SwPaM* pCursor) |
46 | 0 | { |
47 | 0 | SolarMutexGuard gMutex; |
48 | 0 | OString aResult; |
49 | 0 | WriterRef xWrt; |
50 | 0 | GetHTMLWriter(u"NoLineLimit,SkipHeaderFooter,NoPrettyPrint", OUString(), xWrt); |
51 | 0 | if (pCursor != nullptr) |
52 | 0 | { |
53 | 0 | SvMemoryStream aMemoryStream; |
54 | 0 | SwWriter aWriter(aMemoryStream, *pCursor); |
55 | 0 | ErrCodeMsg nError = aWriter.Write(xWrt); |
56 | 0 | if (nError.IsError()) |
57 | 0 | { |
58 | 0 | SAL_WARN("sw.ui", "ExportPaMToHTML: failed to export selection to HTML " << nError); |
59 | 0 | return {}; |
60 | 0 | } |
61 | 0 | aResult |
62 | 0 | = OString(static_cast<const char*>(aMemoryStream.GetData()), aMemoryStream.GetSize()); |
63 | 0 | aResult = aResult.replaceAll("<p"_ostr, "<span"_ostr); |
64 | 0 | aResult = aResult.replaceAll("</p>"_ostr, "</span>"_ostr); |
65 | | |
66 | | // HTML has for that <br> and <p> also does new line |
67 | 0 | aResult = aResult.replaceAll("<ul>"_ostr, ""_ostr); |
68 | 0 | aResult = aResult.replaceAll("</ul>"_ostr, ""_ostr); |
69 | 0 | aResult = aResult.replaceAll("<ol>"_ostr, ""_ostr); |
70 | 0 | aResult = aResult.replaceAll("</ol>"_ostr, ""_ostr); |
71 | 0 | aResult = aResult.replaceAll("\n"_ostr, ""_ostr).trim(); |
72 | 0 | return aResult; |
73 | 0 | } |
74 | 0 | return {}; |
75 | 0 | } |
76 | | |
77 | | void PasteHTMLToPaM(SwWrtShell& rWrtSh, const SwPaM* pCursor, const OString& rData) |
78 | 0 | { |
79 | 0 | SolarMutexGuard gMutex; |
80 | 0 | rtl::Reference<vcl::unohelper::HtmlTransferable> pHtmlTransferable |
81 | 0 | = new vcl::unohelper::HtmlTransferable(rData); |
82 | 0 | if (pHtmlTransferable.is()) |
83 | 0 | { |
84 | 0 | TransferableDataHelper aDataHelper(pHtmlTransferable); |
85 | 0 | if (aDataHelper.GetXTransferable().is() |
86 | 0 | && SwTransferable::IsPasteSpecial(rWrtSh, aDataHelper)) |
87 | 0 | { |
88 | 0 | rWrtSh.SetSelection(*pCursor); |
89 | 0 | SwTransferable::Paste(rWrtSh, aDataHelper); |
90 | 0 | rWrtSh.KillSelection(nullptr, false); |
91 | 0 | } |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | #if HAVE_FEATURE_CURL |
96 | | void TranslateDocument(SwWrtShell& rWrtSh, const OString& rTargetLang) |
97 | 0 | { |
98 | 0 | bool bCancel = false; |
99 | 0 | TranslateDocumentCancellable(rWrtSh, rTargetLang, bCancel); |
100 | 0 | } |
101 | | |
102 | | static bool IsTranslationServiceConfigured(OString* pAPIUrl, OString* pKey) |
103 | 0 | { |
104 | 0 | auto oDeeplAPIUrl = officecfg::Office::Linguistic::Translation::Deepl::ApiURL::get(); |
105 | 0 | auto oDeeplKey = officecfg::Office::Linguistic::Translation::Deepl::AuthKey::get(); |
106 | 0 | auto sApiUrlTrimmed = oDeeplAPIUrl ? o3tl::trim(*oDeeplAPIUrl) : std::u16string_view(); |
107 | 0 | auto sKeyTrimmed = oDeeplKey ? o3tl::trim(*oDeeplKey) : std::u16string_view(); |
108 | 0 | if (sApiUrlTrimmed.empty() || sKeyTrimmed.empty()) |
109 | 0 | return false; |
110 | 0 | if (pAPIUrl) |
111 | 0 | *pAPIUrl = OUStringToOString(sApiUrlTrimmed, RTL_TEXTENCODING_UTF8) + "?tag_handling=html"; |
112 | 0 | if (pKey) |
113 | 0 | *pKey = OUStringToOString(sKeyTrimmed, RTL_TEXTENCODING_UTF8); |
114 | 0 | return true; |
115 | 0 | } |
116 | | |
117 | 0 | bool IsTranslationServiceConfigured() { return IsTranslationServiceConfigured(nullptr, nullptr); } |
118 | | |
119 | | bool TranslateDocumentCancellable(SwWrtShell& rWrtSh, const OString& rTargetLang, |
120 | | const bool& rCancelTranslation) |
121 | 0 | { |
122 | 0 | OString aAPIUrl, aAuthKey; |
123 | 0 | if (!IsTranslationServiceConfigured(&aAPIUrl, &aAuthKey)) |
124 | 0 | { |
125 | 0 | SAL_WARN("sw.ui", "TranslateDocumentCancellable: API options are not set"); |
126 | 0 | return false; |
127 | 0 | } |
128 | | |
129 | 0 | auto m_pCurrentPam = rWrtSh.GetCursor(); |
130 | 0 | bool bHasSelection = rWrtSh.HasSelection(); |
131 | |
|
132 | 0 | if (bHasSelection) |
133 | 0 | { |
134 | | // iteration will start top to bottom |
135 | 0 | m_pCurrentPam->Normalize(); |
136 | 0 | } |
137 | |
|
138 | 0 | auto const& pNodes = rWrtSh.GetNodes(); |
139 | 0 | SwPosition aPoint = *m_pCurrentPam->GetPoint(); |
140 | 0 | SwPosition aMark = *m_pCurrentPam->GetMark(); |
141 | 0 | auto startNode = bHasSelection ? aPoint.nNode.GetIndex() : SwNodeOffset(0); |
142 | 0 | auto endNode = bHasSelection ? aMark.nNode.GetIndex() : pNodes.Count() - 1; |
143 | |
|
144 | 0 | sal_Int32 nCount(0); |
145 | 0 | sal_Int32 nProgress(0); |
146 | |
|
147 | 0 | for (SwNodeOffset n(startNode); n <= endNode; ++n) |
148 | 0 | { |
149 | 0 | if (pNodes[n] && pNodes[n]->IsTextNode()) |
150 | 0 | { |
151 | 0 | if (pNodes[n]->GetTextNode()->GetText().isEmpty()) |
152 | 0 | continue; |
153 | 0 | nCount++; |
154 | 0 | } |
155 | 0 | } |
156 | |
|
157 | 0 | SfxViewFrame* pFrame = SfxViewFrame::Current(); |
158 | 0 | uno::Reference<frame::XFrame> xFrame(pFrame ? pFrame->GetFrame().GetFrameInterface() : nullptr); |
159 | 0 | uno::Reference<task::XStatusIndicatorFactory> xProgressFactory(xFrame, uno::UNO_QUERY); |
160 | 0 | uno::Reference<task::XStatusIndicator> xStatusIndicator; |
161 | |
|
162 | 0 | if (xProgressFactory.is()) |
163 | 0 | { |
164 | 0 | xStatusIndicator = xProgressFactory->createStatusIndicator(); |
165 | 0 | } |
166 | |
|
167 | 0 | if (xStatusIndicator.is()) |
168 | 0 | xStatusIndicator->start(SwResId(STR_STATSTR_SWTRANSLATE), nCount); |
169 | |
|
170 | 0 | for (SwNodeOffset n(startNode); n <= endNode; ++n) |
171 | 0 | { |
172 | 0 | if (rCancelTranslation) |
173 | 0 | break; |
174 | | |
175 | 0 | if (n >= rWrtSh.GetNodes().Count()) |
176 | 0 | break; |
177 | | |
178 | 0 | if (!pNodes[n]) |
179 | 0 | break; |
180 | | |
181 | 0 | SwNode* pNode = pNodes[n]; |
182 | 0 | if (pNode->IsTextNode()) |
183 | 0 | { |
184 | 0 | if (pNode->GetTextNode()->GetText().isEmpty()) |
185 | 0 | continue; |
186 | | |
187 | 0 | auto cursor |
188 | 0 | = Writer::NewUnoCursor(*rWrtSh.GetDoc(), pNode->GetIndex(), pNode->GetIndex()); |
189 | | |
190 | | // set edges (start, end) for nodes inside the selection. |
191 | 0 | if (bHasSelection) |
192 | 0 | { |
193 | 0 | if (startNode == endNode) |
194 | 0 | { |
195 | 0 | cursor->SetMark(); |
196 | 0 | cursor->GetPoint()->nContent = aPoint.nContent; |
197 | 0 | cursor->GetMark()->nContent = aMark.nContent; |
198 | 0 | } |
199 | 0 | else if (n == startNode) |
200 | 0 | { |
201 | 0 | cursor->SetMark(); |
202 | 0 | cursor->GetPoint()->nContent = aPoint.nContent; |
203 | 0 | } |
204 | 0 | else if (n == endNode) |
205 | 0 | { |
206 | 0 | cursor->SetMark(); |
207 | 0 | cursor->GetMark()->nContent = aMark.nContent; |
208 | 0 | cursor->GetPoint()->nContent = 0; |
209 | 0 | } |
210 | 0 | } |
211 | |
|
212 | 0 | const auto aOut = SwTranslateHelper::ExportPaMToHTML(cursor.get()); |
213 | 0 | const auto aTranslatedOut = linguistic::Translate(rTargetLang, aAPIUrl, aAuthKey, aOut); |
214 | 0 | if (!aTranslatedOut.isEmpty()) |
215 | 0 | { |
216 | 0 | SwTranslateHelper::PasteHTMLToPaM(rWrtSh, cursor.get(), aTranslatedOut); |
217 | 0 | } |
218 | 0 | else |
219 | 0 | { |
220 | 0 | std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog( |
221 | 0 | nullptr, VclMessageType::Error, VclButtonsType::Ok, |
222 | 0 | SwResId(STR_SWTRANSLATE_ERROR))); |
223 | 0 | xBox->run(); |
224 | 0 | break; |
225 | 0 | } |
226 | | |
227 | 0 | if (xStatusIndicator.is() && nCount) |
228 | 0 | xStatusIndicator->setValue((100 * ++nProgress) / nCount); |
229 | |
|
230 | 0 | Idle aIdle("TranslateDocumentCancellable aIdle"); |
231 | 0 | aIdle.SetPriority(TaskPriority::POST_PAINT); |
232 | 0 | aIdle.Start(); |
233 | |
|
234 | 0 | rWrtSh.LockView(true); |
235 | 0 | while (aIdle.IsActive() && !Application::IsQuit()) |
236 | 0 | { |
237 | 0 | Application::Yield(); |
238 | 0 | } |
239 | 0 | rWrtSh.LockView(false); |
240 | 0 | } |
241 | 0 | } |
242 | |
|
243 | 0 | if (xStatusIndicator.is()) |
244 | 0 | xStatusIndicator->end(); |
245 | 0 | return true; |
246 | 0 | } |
247 | | #endif // HAVE_FEATURE_CURL |
248 | | } |