File: | home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx |
Warning: | line 2723, column 1 Potential leak of memory pointed to by 'xStorage.pObj' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 <memory> | |||
21 | #include <string_view> | |||
22 | #include <sal/config.h> | |||
23 | ||||
24 | #include <com/sun/star/linguistic2/XSpellChecker1.hpp> | |||
25 | #include <com/sun/star/embed/XStorage.hpp> | |||
26 | #include <com/sun/star/io/IOException.hpp> | |||
27 | #include <com/sun/star/io/XStream.hpp> | |||
28 | #include <tools/urlobj.hxx> | |||
29 | #include <i18nlangtag/mslangid.hxx> | |||
30 | #include <i18nutil/transliteration.hxx> | |||
31 | #include <sal/log.hxx> | |||
32 | #include <osl/diagnose.h> | |||
33 | #include <vcl/svapp.hxx> | |||
34 | #include <vcl/settings.hxx> | |||
35 | #include <svl/fstathelper.hxx> | |||
36 | #include <svl/urihelper.hxx> | |||
37 | #include <unotools/charclass.hxx> | |||
38 | #include <com/sun/star/i18n/UnicodeType.hpp> | |||
39 | #include <unotools/collatorwrapper.hxx> | |||
40 | #include <com/sun/star/i18n/UnicodeScript.hpp> | |||
41 | #include <com/sun/star/i18n/OrdinalSuffix.hpp> | |||
42 | #include <unotools/localedatawrapper.hxx> | |||
43 | #include <unotools/transliterationwrapper.hxx> | |||
44 | #include <comphelper/processfactory.hxx> | |||
45 | #include <comphelper/storagehelper.hxx> | |||
46 | #include <comphelper/string.hxx> | |||
47 | #include <editeng/editids.hrc> | |||
48 | #include <sot/storage.hxx> | |||
49 | #include <editeng/udlnitem.hxx> | |||
50 | #include <editeng/wghtitem.hxx> | |||
51 | #include <editeng/postitem.hxx> | |||
52 | #include <editeng/crossedoutitem.hxx> | |||
53 | #include <editeng/escapementitem.hxx> | |||
54 | #include <editeng/svxacorr.hxx> | |||
55 | #include <editeng/unolingu.hxx> | |||
56 | #include <vcl/window.hxx> | |||
57 | #include <com/sun/star/xml/sax/InputSource.hpp> | |||
58 | #include <com/sun/star/xml/sax/FastParser.hpp> | |||
59 | #include <com/sun/star/xml/sax/Writer.hpp> | |||
60 | #include <com/sun/star/xml/sax/SAXParseException.hpp> | |||
61 | #include <unotools/streamwrap.hxx> | |||
62 | #include "SvXMLAutoCorrectImport.hxx" | |||
63 | #include "SvXMLAutoCorrectExport.hxx" | |||
64 | #include "SvXMLAutoCorrectTokenHandler.hxx" | |||
65 | #include <ucbhelper/content.hxx> | |||
66 | #include <com/sun/star/ucb/ContentCreationException.hpp> | |||
67 | #include <com/sun/star/ucb/XCommandEnvironment.hpp> | |||
68 | #include <com/sun/star/ucb/TransferInfo.hpp> | |||
69 | #include <com/sun/star/ucb/NameClash.hpp> | |||
70 | #include <tools/diagnose_ex.h> | |||
71 | #include <xmloff/xmltoken.hxx> | |||
72 | #include <unordered_map> | |||
73 | #include <rtl/character.hxx> | |||
74 | ||||
75 | using namespace ::com::sun::star::ucb; | |||
76 | using namespace ::com::sun::star::uno; | |||
77 | using namespace ::com::sun::star::xml::sax; | |||
78 | using namespace ::com::sun::star; | |||
79 | using namespace ::xmloff::token; | |||
80 | using namespace ::utl; | |||
81 | ||||
82 | namespace { | |||
83 | ||||
84 | enum class Flags { | |||
85 | NONE = 0x00, | |||
86 | FullStop = 0x01, | |||
87 | ExclamationMark = 0x02, | |||
88 | QuestionMark = 0x04, | |||
89 | }; | |||
90 | ||||
91 | } | |||
92 | ||||
93 | namespace o3tl { | |||
94 | template<> struct typed_flags<Flags> : is_typed_flags<Flags, 0x07> {}; | |||
95 | } | |||
96 | const sal_Unicode cNonBreakingSpace = 0xA0; // UNICODE code for no break space | |||
97 | ||||
98 | const char pXMLImplWrdStt_ExcptLstStr[] = "WordExceptList.xml"; | |||
99 | const char pXMLImplCplStt_ExcptLstStr[] = "SentenceExceptList.xml"; | |||
100 | const char pXMLImplAutocorr_ListStr[] = "DocumentList.xml"; | |||
101 | ||||
102 | const char | |||
103 | /* also at these beginnings - Brackets and all kinds of begin characters */ | |||
104 | sImplSttSkipChars[] = "\"\'([{\x83\x84\x89\x91\x92\x93\x94", | |||
105 | /* also at these ends - Brackets and all kinds of begin characters */ | |||
106 | sImplEndSkipChars[] = "\"\')]}\x83\x84\x89\x91\x92\x93\x94"; | |||
107 | ||||
108 | static OUString EncryptBlockName_Imp(const OUString& rName); | |||
109 | ||||
110 | static bool NonFieldWordDelim( const sal_Unicode c ) | |||
111 | { | |||
112 | return ' ' == c || '\t' == c || 0x0a == c || | |||
113 | cNonBreakingSpace == c || 0x2011 == c; | |||
114 | } | |||
115 | ||||
116 | static bool IsWordDelim( const sal_Unicode c ) | |||
117 | { | |||
118 | return c == 0x1 || NonFieldWordDelim(c); | |||
119 | } | |||
120 | ||||
121 | ||||
122 | static bool IsLowerLetter( sal_Int32 nCharType ) | |||
123 | { | |||
124 | return CharClass::isLetterType( nCharType ) && | |||
125 | ( css::i18n::KCharacterType::LOWER & nCharType); | |||
126 | } | |||
127 | ||||
128 | static bool IsUpperLetter( sal_Int32 nCharType ) | |||
129 | { | |||
130 | return CharClass::isLetterType( nCharType ) && | |||
131 | ( css::i18n::KCharacterType::UPPER & nCharType); | |||
132 | } | |||
133 | ||||
134 | static bool lcl_IsUnsupportedUnicodeChar( CharClass const & rCC, const OUString& rTxt, | |||
135 | sal_Int32 nStt, sal_Int32 nEnd ) | |||
136 | { | |||
137 | for( ; nStt < nEnd; ++nStt ) | |||
138 | { | |||
139 | css::i18n::UnicodeScript nScript = rCC.getScript( rTxt, nStt ); | |||
140 | switch( nScript ) | |||
141 | { | |||
142 | case css::i18n::UnicodeScript_kCJKRadicalsSupplement: | |||
143 | case css::i18n::UnicodeScript_kHangulJamo: | |||
144 | case css::i18n::UnicodeScript_kCJKSymbolPunctuation: | |||
145 | case css::i18n::UnicodeScript_kHiragana: | |||
146 | case css::i18n::UnicodeScript_kKatakana: | |||
147 | case css::i18n::UnicodeScript_kHangulCompatibilityJamo: | |||
148 | case css::i18n::UnicodeScript_kEnclosedCJKLetterMonth: | |||
149 | case css::i18n::UnicodeScript_kCJKCompatibility: | |||
150 | case css::i18n::UnicodeScript_k_CJKUnifiedIdeographsExtensionA: | |||
151 | case css::i18n::UnicodeScript_kCJKUnifiedIdeograph: | |||
152 | case css::i18n::UnicodeScript_kHangulSyllable: | |||
153 | case css::i18n::UnicodeScript_kCJKCompatibilityIdeograph: | |||
154 | case css::i18n::UnicodeScript_kHalfwidthFullwidthForm: | |||
155 | return true; | |||
156 | default: ; //do nothing | |||
157 | } | |||
158 | } | |||
159 | return false; | |||
160 | } | |||
161 | ||||
162 | static bool lcl_IsSymbolChar( CharClass const & rCC, const OUString& rTxt, | |||
163 | sal_Int32 nStt, sal_Int32 nEnd ) | |||
164 | { | |||
165 | for( ; nStt < nEnd; ++nStt ) | |||
166 | { | |||
167 | if( css::i18n::UnicodeType::PRIVATE_USE == rCC.getType( rTxt, nStt )) | |||
168 | return true; | |||
169 | } | |||
170 | return false; | |||
171 | } | |||
172 | ||||
173 | static bool lcl_IsInAsciiArr( const char* pArr, const sal_Unicode c ) | |||
174 | { | |||
175 | // tdf#54409 check also typographical quotation marks in the case of skipped ASCII quotation marks | |||
176 | if ( 0x2018 <= c && c <= 0x201F && (pArr == sImplSttSkipChars || pArr == sImplEndSkipChars) ) | |||
177 | return true; | |||
178 | ||||
179 | bool bRet = false; | |||
180 | for( ; *pArr; ++pArr ) | |||
181 | if( *pArr == c ) | |||
182 | { | |||
183 | bRet = true; | |||
184 | break; | |||
185 | } | |||
186 | return bRet; | |||
187 | } | |||
188 | ||||
189 | SvxAutoCorrDoc::~SvxAutoCorrDoc() | |||
190 | { | |||
191 | } | |||
192 | ||||
193 | // Called by the functions: | |||
194 | // - FnCapitalStartWord | |||
195 | // - FnCapitalStartSentence | |||
196 | // after the exchange of characters. Then the words, if necessary, can be inserted | |||
197 | // into the exception list. | |||
198 | void SvxAutoCorrDoc::SaveCpltSttWord( ACFlags, sal_Int32, const OUString&, | |||
199 | sal_Unicode ) | |||
200 | { | |||
201 | } | |||
202 | ||||
203 | LanguageType SvxAutoCorrDoc::GetLanguage( sal_Int32 ) const | |||
204 | { | |||
205 | return LANGUAGE_SYSTEMLanguageType(0x0000); | |||
206 | } | |||
207 | ||||
208 | static const LanguageTag& GetAppLang() | |||
209 | { | |||
210 | return Application::GetSettings().GetLanguageTag(); | |||
211 | } | |||
212 | ||||
213 | /// Never use an unresolved LANGUAGE_SYSTEM. | |||
214 | static LanguageType GetDocLanguage( const SvxAutoCorrDoc& rDoc, sal_Int32 nPos ) | |||
215 | { | |||
216 | LanguageType eLang = rDoc.GetLanguage( nPos ); | |||
217 | if (eLang == LANGUAGE_SYSTEMLanguageType(0x0000)) | |||
218 | eLang = GetAppLang().getLanguageType(); // the current work locale | |||
219 | return eLang; | |||
220 | } | |||
221 | ||||
222 | static LocaleDataWrapper& GetLocaleDataWrapper( LanguageType nLang ) | |||
223 | { | |||
224 | static LocaleDataWrapper aLclDtWrp( GetAppLang() ); | |||
225 | LanguageTag aLcl( nLang ); | |||
226 | const LanguageTag& rLcl = aLclDtWrp.getLoadedLanguageTag(); | |||
227 | if( aLcl != rLcl ) | |||
228 | aLclDtWrp.setLanguageTag( aLcl ); | |||
229 | return aLclDtWrp; | |||
230 | } | |||
231 | static TransliterationWrapper& GetIgnoreTranslWrapper() | |||
232 | { | |||
233 | static int bIsInit = 0; | |||
234 | static TransliterationWrapper aWrp( ::comphelper::getProcessComponentContext(), | |||
235 | TransliterationFlags::IGNORE_KANA | | |||
236 | TransliterationFlags::IGNORE_WIDTH ); | |||
237 | if( !bIsInit ) | |||
238 | { | |||
239 | aWrp.loadModuleIfNeeded( GetAppLang().getLanguageType() ); | |||
240 | bIsInit = 1; | |||
241 | } | |||
242 | return aWrp; | |||
243 | } | |||
244 | static CollatorWrapper& GetCollatorWrapper() | |||
245 | { | |||
246 | static CollatorWrapper aCollWrp = [&]() | |||
247 | { | |||
248 | CollatorWrapper tmp( ::comphelper::getProcessComponentContext() ); | |||
249 | tmp.loadDefaultCollator( GetAppLang().getLocale(), 0 ); | |||
250 | return tmp; | |||
251 | }(); | |||
252 | return aCollWrp; | |||
253 | } | |||
254 | ||||
255 | bool SvxAutoCorrect::IsAutoCorrectChar( sal_Unicode cChar ) | |||
256 | { | |||
257 | return cChar == '\0' || cChar == '\t' || cChar == 0x0a || | |||
258 | cChar == ' ' || cChar == '\'' || cChar == '\"' || | |||
259 | cChar == '*' || cChar == '_' || cChar == '%' || | |||
260 | cChar == '.' || cChar == ',' || cChar == ';' || | |||
261 | cChar == ':' || cChar == '?' || cChar == '!' || | |||
262 | cChar == '<' || cChar == '>' || | |||
263 | cChar == '/' || cChar == '-'; | |||
264 | } | |||
265 | ||||
266 | namespace | |||
267 | { | |||
268 | bool IsCompoundWordDelimChar(sal_Unicode cChar) | |||
269 | { | |||
270 | return cChar == '-' || SvxAutoCorrect::IsAutoCorrectChar(cChar); | |||
271 | } | |||
272 | } | |||
273 | ||||
274 | bool SvxAutoCorrect::NeedsHardspaceAutocorr( sal_Unicode cChar ) | |||
275 | { | |||
276 | return cChar == '%' || cChar == ';' || cChar == ':' || cChar == '?' || cChar == '!' || | |||
277 | cChar == '/' /*case for the urls exception*/; | |||
278 | } | |||
279 | ||||
280 | ACFlags SvxAutoCorrect::GetDefaultFlags() | |||
281 | { | |||
282 | ACFlags nRet = ACFlags::Autocorrect | |||
283 | | ACFlags::CapitalStartSentence | |||
284 | | ACFlags::CapitalStartWord | |||
285 | | ACFlags::ChgOrdinalNumber | |||
286 | | ACFlags::ChgToEnEmDash | |||
287 | | ACFlags::AddNonBrkSpace | |||
288 | | ACFlags::TransliterateRTL | |||
289 | | ACFlags::ChgAngleQuotes | |||
290 | | ACFlags::ChgWeightUnderl | |||
291 | | ACFlags::SetINetAttr | |||
292 | | ACFlags::ChgQuotes | |||
293 | | ACFlags::SaveWordCplSttLst | |||
294 | | ACFlags::SaveWordWrdSttLst | |||
295 | | ACFlags::CorrectCapsLock; | |||
296 | LanguageType eLang = GetAppLang().getLanguageType(); | |||
297 | if( eLang.anyOf( | |||
298 | LANGUAGE_ENGLISHLanguageType(0x0009), | |||
299 | LANGUAGE_ENGLISH_USLanguageType(0x0409), | |||
300 | LANGUAGE_ENGLISH_UKLanguageType(0x0809), | |||
301 | LANGUAGE_ENGLISH_AUSLanguageType(0x0C09), | |||
302 | LANGUAGE_ENGLISH_CANLanguageType(0x1009), | |||
303 | LANGUAGE_ENGLISH_NZLanguageType(0x1409), | |||
304 | LANGUAGE_ENGLISH_EIRELanguageType(0x1809), | |||
305 | LANGUAGE_ENGLISH_SAFRICALanguageType(0x1C09), | |||
306 | LANGUAGE_ENGLISH_JAMAICALanguageType(0x2009), | |||
307 | LANGUAGE_ENGLISH_CARIBBEANLanguageType(0x2409))) | |||
308 | nRet &= ~ACFlags(ACFlags::ChgQuotes|ACFlags::ChgSglQuotes); | |||
309 | return nRet; | |||
310 | } | |||
311 | ||||
312 | constexpr sal_Unicode cEmDash = 0x2014; | |||
313 | constexpr sal_Unicode cEnDash = 0x2013; | |||
314 | constexpr sal_Unicode cApostrophe = 0x2019; | |||
315 | constexpr sal_Unicode cLeftDoubleAngleQuote = 0xAB; | |||
316 | constexpr sal_Unicode cRightDoubleAngleQuote = 0xBB; | |||
317 | constexpr sal_Unicode cLeftSingleAngleQuote = 0x2039; | |||
318 | constexpr sal_Unicode cRightSingleAngleQuote = 0x203A; | |||
319 | // stop characters for searching preceding quotes | |||
320 | // (the first character is also the opening quote we are looking for) | |||
321 | const sal_Unicode aStopDoubleAngleQuoteStart[] = { 0x201E, 0x201D, 0x201C, 0 }; // preceding ,, | |||
322 | const sal_Unicode aStopDoubleAngleQuoteEnd[] = { cRightDoubleAngleQuote, cLeftDoubleAngleQuote, 0x201D, 0x201E, 0 }; // preceding >> | |||
323 | // preceding << for Romanian, handle also alternative primary closing quotation mark U+201C | |||
324 | const sal_Unicode aStopDoubleAngleQuoteEndRo[] = { cLeftDoubleAngleQuote, cRightDoubleAngleQuote, 0x201D, 0x201E, 0x201C, 0 }; | |||
325 | const sal_Unicode aStopSingleQuoteEnd[] = { 0x201A, 0x2018, 0x201C, 0x201E, 0 }; | |||
326 | const sal_Unicode aStopSingleQuoteEndRuUa[] = { 0x201E, 0x201C, cRightDoubleAngleQuote, cLeftDoubleAngleQuote, 0 }; | |||
327 | ||||
328 | SvxAutoCorrect::SvxAutoCorrect( const OUString& rShareAutocorrFile, | |||
329 | const OUString& rUserAutocorrFile ) | |||
330 | : sShareAutoCorrFile( rShareAutocorrFile ) | |||
331 | , sUserAutoCorrFile( rUserAutocorrFile ) | |||
332 | , eCharClassLang( LANGUAGE_DONTKNOWLanguageType(0x03FF) ) | |||
333 | , nFlags(SvxAutoCorrect::GetDefaultFlags()) | |||
334 | , cStartDQuote( 0 ) | |||
335 | , cEndDQuote( 0 ) | |||
336 | , cStartSQuote( 0 ) | |||
337 | , cEndSQuote( 0 ) | |||
338 | { | |||
339 | } | |||
340 | ||||
341 | SvxAutoCorrect::SvxAutoCorrect( const SvxAutoCorrect& rCpy ) | |||
342 | : sShareAutoCorrFile( rCpy.sShareAutoCorrFile ) | |||
343 | , sUserAutoCorrFile( rCpy.sUserAutoCorrFile ) | |||
344 | , aSwFlags( rCpy.aSwFlags ) | |||
345 | , eCharClassLang(rCpy.eCharClassLang) | |||
346 | , nFlags( rCpy.nFlags & ~ACFlags(ACFlags::ChgWordLstLoad|ACFlags::CplSttLstLoad|ACFlags::WrdSttLstLoad)) | |||
347 | , cStartDQuote( rCpy.cStartDQuote ) | |||
348 | , cEndDQuote( rCpy.cEndDQuote ) | |||
349 | , cStartSQuote( rCpy.cStartSQuote ) | |||
350 | , cEndSQuote( rCpy.cEndSQuote ) | |||
351 | { | |||
352 | } | |||
353 | ||||
354 | ||||
355 | SvxAutoCorrect::~SvxAutoCorrect() | |||
356 | { | |||
357 | } | |||
358 | ||||
359 | void SvxAutoCorrect::GetCharClass_( LanguageType eLang ) | |||
360 | { | |||
361 | pCharClass.reset( new CharClass( LanguageTag( eLang)) ); | |||
362 | eCharClassLang = eLang; | |||
363 | } | |||
364 | ||||
365 | void SvxAutoCorrect::SetAutoCorrFlag( ACFlags nFlag, bool bOn ) | |||
366 | { | |||
367 | ACFlags nOld = nFlags; | |||
368 | nFlags = bOn ? nFlags | nFlag | |||
369 | : nFlags & ~nFlag; | |||
370 | ||||
371 | if( !bOn ) | |||
372 | { | |||
373 | if( (nOld & ACFlags::CapitalStartSentence) != (nFlags & ACFlags::CapitalStartSentence) ) | |||
374 | nFlags &= ~ACFlags::CplSttLstLoad; | |||
375 | if( (nOld & ACFlags::CapitalStartWord) != (nFlags & ACFlags::CapitalStartWord) ) | |||
376 | nFlags &= ~ACFlags::WrdSttLstLoad; | |||
377 | if( (nOld & ACFlags::Autocorrect) != (nFlags & ACFlags::Autocorrect) ) | |||
378 | nFlags &= ~ACFlags::ChgWordLstLoad; | |||
379 | } | |||
380 | } | |||
381 | ||||
382 | ||||
383 | // Correct TWo INitial CApitals | |||
384 | void SvxAutoCorrect::FnCapitalStartWord( SvxAutoCorrDoc& rDoc, const OUString& rTxt, | |||
385 | sal_Int32 nSttPos, sal_Int32 nEndPos, | |||
386 | LanguageType eLang ) | |||
387 | { | |||
388 | CharClass& rCC = GetCharClass( eLang ); | |||
389 | ||||
390 | // Delete all non alphanumeric. Test the characters at the beginning/end of | |||
391 | // the word ( recognizes: "(min.", "/min.", and so on.) | |||
392 | for( ; nSttPos < nEndPos; ++nSttPos ) | |||
393 | if( rCC.isLetterNumeric( rTxt, nSttPos )) | |||
394 | break; | |||
395 | for( ; nSttPos < nEndPos; --nEndPos ) | |||
396 | if( rCC.isLetterNumeric( rTxt, nEndPos - 1 )) | |||
397 | break; | |||
398 | ||||
399 | // Is the word a compounded word separated by delimiters? | |||
400 | // If so, keep track of all delimiters so each constituent | |||
401 | // word can be checked for two initial capital letters. | |||
402 | std::deque<sal_Int32> aDelimiters; | |||
403 | ||||
404 | // Always check for two capitals at the beginning | |||
405 | // of the entire word, so start at nSttPos. | |||
406 | aDelimiters.push_back(nSttPos); | |||
407 | ||||
408 | // Find all compound word delimiters | |||
409 | for (sal_Int32 n = nSttPos; n < nEndPos; ++n) | |||
410 | { | |||
411 | if (IsCompoundWordDelimChar(rTxt[ n ])) | |||
412 | { | |||
413 | aDelimiters.push_back( n + 1 ); // Get position of char after delimiter | |||
414 | } | |||
415 | } | |||
416 | ||||
417 | // Decide where to put the terminating delimiter. | |||
418 | // If the last AutoCorrect char was a newline, then the AutoCorrect | |||
419 | // char will not be included in rTxt. | |||
420 | // If the last AutoCorrect char was not a newline, then the AutoCorrect | |||
421 | // character will be the last character in rTxt. | |||
422 | if (!IsCompoundWordDelimChar(rTxt[nEndPos-1])) | |||
423 | aDelimiters.push_back(nEndPos); | |||
424 | ||||
425 | // Iterate through the word and all words that compose it. | |||
426 | // Two capital letters at the beginning of word? | |||
427 | for (size_t nI = 0; nI < aDelimiters.size() - 1; ++nI) | |||
428 | { | |||
429 | nSttPos = aDelimiters[nI]; | |||
430 | nEndPos = aDelimiters[nI + 1]; | |||
431 | ||||
432 | if( nSttPos+2 < nEndPos && | |||
433 | IsUpperLetter( rCC.getCharacterType( rTxt, nSttPos )) && | |||
434 | IsUpperLetter( rCC.getCharacterType( rTxt, ++nSttPos )) && | |||
435 | // Is the third character a lower case | |||
436 | IsLowerLetter( rCC.getCharacterType( rTxt, nSttPos +1 )) && | |||
437 | // Do not replace special attributes | |||
438 | 0x1 != rTxt[ nSttPos ] && 0x2 != rTxt[ nSttPos ]) | |||
439 | { | |||
440 | // test if the word is in an exception list | |||
441 | OUString sWord( rTxt.copy( nSttPos - 1, nEndPos - nSttPos + 1 )); | |||
442 | if( !FindInWrdSttExceptList(eLang, sWord) ) | |||
443 | { | |||
444 | // Check that word isn't correctly spelt before correcting: | |||
445 | css::uno::Reference< css::linguistic2::XSpellChecker1 > xSpeller = | |||
446 | LinguMgr::GetSpellChecker(); | |||
447 | if( xSpeller->hasLanguage(static_cast<sal_uInt16>(eLang)) ) | |||
448 | { | |||
449 | Sequence< css::beans::PropertyValue > aEmptySeq; | |||
450 | if (xSpeller->isValid(sWord, static_cast<sal_uInt16>(eLang), aEmptySeq)) | |||
451 | { | |||
452 | return; | |||
453 | } | |||
454 | } | |||
455 | sal_Unicode cSave = rTxt[ nSttPos ]; | |||
456 | OUString sChar = rCC.lowercase( OUString(cSave) ); | |||
457 | if( sChar[0] != cSave && rDoc.ReplaceRange( nSttPos, 1, sChar )) | |||
458 | { | |||
459 | if( ACFlags::SaveWordWrdSttLst & nFlags ) | |||
460 | rDoc.SaveCpltSttWord( ACFlags::CapitalStartWord, nSttPos, sWord, cSave ); | |||
461 | } | |||
462 | } | |||
463 | } | |||
464 | } | |||
465 | } | |||
466 | ||||
467 | // Format ordinal numbers suffixes (1st -> 1^st) | |||
468 | bool SvxAutoCorrect::FnChgOrdinalNumber( | |||
469 | SvxAutoCorrDoc& rDoc, const OUString& rTxt, | |||
470 | sal_Int32 nSttPos, sal_Int32 nEndPos, | |||
471 | LanguageType eLang) | |||
472 | { | |||
473 | // 1st, 2nd, 3rd, 4 - 0th | |||
474 | // 201th or 201st | |||
475 | // 12th or 12nd | |||
476 | bool bChg = false; | |||
477 | ||||
478 | // In some languages ordinal suffixes should never be | |||
479 | // changed to superscript. Let's break for those languages. | |||
480 | if (!eLang.anyOf( | |||
481 | LANGUAGE_SWEDISHLanguageType(0x041D), | |||
482 | LANGUAGE_SWEDISH_FINLANDLanguageType(0x081D))) | |||
483 | { | |||
484 | CharClass& rCC = GetCharClass(eLang); | |||
485 | ||||
486 | for (; nSttPos < nEndPos; ++nSttPos) | |||
487 | if (!lcl_IsInAsciiArr(sImplSttSkipChars, rTxt[nSttPos])) | |||
488 | break; | |||
489 | for (; nSttPos < nEndPos; --nEndPos) | |||
490 | if (!lcl_IsInAsciiArr(sImplEndSkipChars, rTxt[nEndPos - 1])) | |||
491 | break; | |||
492 | ||||
493 | ||||
494 | // Get the last number in the string to check | |||
495 | sal_Int32 nNumEnd = nEndPos; | |||
496 | bool bFoundEnd = false; | |||
497 | bool isValidNumber = true; | |||
498 | sal_Int32 i = nEndPos; | |||
499 | while (i > nSttPos) | |||
500 | { | |||
501 | i--; | |||
502 | bool isDigit = rCC.isDigit(rTxt, i); | |||
503 | if (bFoundEnd) | |||
504 | isValidNumber &= (isDigit || !rCC.isLetter(rTxt, i)); | |||
505 | ||||
506 | if (isDigit && !bFoundEnd) | |||
507 | { | |||
508 | bFoundEnd = true; | |||
509 | nNumEnd = i; | |||
510 | } | |||
511 | } | |||
512 | ||||
513 | if (bFoundEnd && isValidNumber) { | |||
514 | sal_Int32 nNum = rTxt.copy(nSttPos, nNumEnd - nSttPos + 1).toInt32(); | |||
515 | ||||
516 | // Check if the characters after that number correspond to the ordinal suffix | |||
517 | uno::Reference< i18n::XOrdinalSuffix > xOrdSuffix | |||
518 | = i18n::OrdinalSuffix::create(comphelper::getProcessComponentContext()); | |||
519 | ||||
520 | const uno::Sequence< OUString > aSuffixes = xOrdSuffix->getOrdinalSuffix(nNum, rCC.getLanguageTag().getLocale()); | |||
521 | for (OUString const & sSuffix : aSuffixes) | |||
522 | { | |||
523 | OUString sEnd = rTxt.copy(nNumEnd + 1, nEndPos - nNumEnd - 1); | |||
524 | ||||
525 | if (sSuffix == sEnd) | |||
526 | { | |||
527 | // Check if the ordinal suffix has to be set as super script | |||
528 | if (rCC.isLetter(sSuffix)) | |||
529 | { | |||
530 | // Do the change | |||
531 | SvxEscapementItem aSvxEscapementItem(DFLT_ESC_AUTO_SUPER(13999 +1), | |||
532 | DFLT_ESC_PROP58, SID_ATTR_CHAR_ESCAPEMENT( 10000 + 21 )); | |||
533 | rDoc.SetAttr(nNumEnd + 1, nEndPos, | |||
534 | SID_ATTR_CHAR_ESCAPEMENT( 10000 + 21 ), | |||
535 | aSvxEscapementItem); | |||
536 | bChg = true; | |||
537 | } | |||
538 | } | |||
539 | } | |||
540 | } | |||
541 | } | |||
542 | return bChg; | |||
543 | } | |||
544 | ||||
545 | // Replace dashes | |||
546 | bool SvxAutoCorrect::FnChgToEnEmDash( | |||
547 | SvxAutoCorrDoc& rDoc, const OUString& rTxt, | |||
548 | sal_Int32 nSttPos, sal_Int32 nEndPos, | |||
549 | LanguageType eLang ) | |||
550 | { | |||
551 | bool bRet = false; | |||
552 | CharClass& rCC = GetCharClass( eLang ); | |||
553 | if (eLang == LANGUAGE_SYSTEMLanguageType(0x0000)) | |||
554 | eLang = GetAppLang().getLanguageType(); | |||
555 | bool bAlwaysUseEmDash = (eLang == LANGUAGE_RUSSIANLanguageType(0x0419) || eLang == LANGUAGE_UKRAINIANLanguageType(0x0422)); | |||
556 | ||||
557 | // replace " - " or " --" with "enDash" | |||
558 | if( 1 < nSttPos && 1 <= nEndPos - nSttPos ) | |||
559 | { | |||
560 | sal_Unicode cCh = rTxt[ nSttPos ]; | |||
561 | if( '-' == cCh ) | |||
562 | { | |||
563 | if( 1 < nEndPos - nSttPos && | |||
564 | ' ' == rTxt[ nSttPos-1 ] && | |||
565 | '-' == rTxt[ nSttPos+1 ]) | |||
566 | { | |||
567 | sal_Int32 n; | |||
568 | for( n = nSttPos+2; n < nEndPos && lcl_IsInAsciiArr( | |||
569 | sImplSttSkipChars,(cCh = rTxt[ n ])); | |||
570 | ++n ) | |||
571 | ; | |||
572 | ||||
573 | // found: " --[<AnySttChars>][A-z0-9] | |||
574 | if( rCC.isLetterNumeric( OUString(cCh) ) ) | |||
575 | { | |||
576 | for( n = nSttPos-1; n && lcl_IsInAsciiArr( | |||
577 | sImplEndSkipChars,(cCh = rTxt[ --n ])); ) | |||
578 | ; | |||
579 | ||||
580 | // found: "[A-z0-9][<AnyEndChars>] --[<AnySttChars>][A-z0-9] | |||
581 | if( rCC.isLetterNumeric( OUString(cCh) )) | |||
582 | { | |||
583 | rDoc.Delete( nSttPos, nSttPos + 2 ); | |||
584 | rDoc.Insert( nSttPos, bAlwaysUseEmDash ? OUString(cEmDash) : OUString(cEnDash) ); | |||
585 | bRet = true; | |||
586 | } | |||
587 | } | |||
588 | } | |||
589 | } | |||
590 | else if( 3 < nSttPos && | |||
591 | ' ' == rTxt[ nSttPos-1 ] && | |||
592 | '-' == rTxt[ nSttPos-2 ]) | |||
593 | { | |||
594 | sal_Int32 n, nLen = 1, nTmpPos = nSttPos - 2; | |||
595 | if( '-' == ( cCh = rTxt[ nTmpPos-1 ]) ) | |||
596 | { | |||
597 | --nTmpPos; | |||
598 | ++nLen; | |||
599 | cCh = rTxt[ nTmpPos-1 ]; | |||
600 | } | |||
601 | if( ' ' == cCh ) | |||
602 | { | |||
603 | for( n = nSttPos; n < nEndPos && lcl_IsInAsciiArr( | |||
604 | sImplSttSkipChars,(cCh = rTxt[ n ])); | |||
605 | ++n ) | |||
606 | ; | |||
607 | ||||
608 | // found: " - [<AnySttChars>][A-z0-9] | |||
609 | if( rCC.isLetterNumeric( OUString(cCh) ) ) | |||
610 | { | |||
611 | cCh = ' '; | |||
612 | for( n = nTmpPos-1; n && lcl_IsInAsciiArr( | |||
613 | sImplEndSkipChars,(cCh = rTxt[ --n ])); ) | |||
614 | ; | |||
615 | // found: "[A-z0-9][<AnyEndChars>] - [<AnySttChars>][A-z0-9] | |||
616 | if( rCC.isLetterNumeric( OUString(cCh) )) | |||
617 | { | |||
618 | rDoc.Delete( nTmpPos, nTmpPos + nLen ); | |||
619 | rDoc.Insert( nTmpPos, bAlwaysUseEmDash ? OUString(cEmDash) : OUString(cEnDash) ); | |||
620 | bRet = true; | |||
621 | } | |||
622 | } | |||
623 | } | |||
624 | } | |||
625 | } | |||
626 | ||||
627 | // Replace [A-z0-9]--[A-z0-9] double dash with "emDash" or "enDash" | |||
628 | // [0-9]--[0-9] double dash always replaced with "enDash" | |||
629 | // Finnish and Hungarian use enDash instead of emDash. | |||
630 | bool bEnDash = (eLang == LANGUAGE_HUNGARIANLanguageType(0x040E) || eLang == LANGUAGE_FINNISHLanguageType(0x040B)); | |||
631 | if( 4 <= nEndPos - nSttPos ) | |||
632 | { | |||
633 | OUString sTmp( rTxt.copy( nSttPos, nEndPos - nSttPos ) ); | |||
634 | sal_Int32 nFndPos = sTmp.indexOf("--"); | |||
635 | if( nFndPos != -1 && nFndPos && | |||
636 | nFndPos + 2 < sTmp.getLength() && | |||
637 | ( rCC.isLetterNumeric( sTmp, nFndPos - 1 ) || | |||
638 | lcl_IsInAsciiArr( sImplEndSkipChars, rTxt[ nFndPos - 1 ] )) && | |||
639 | ( rCC.isLetterNumeric( sTmp, nFndPos + 2 ) || | |||
640 | lcl_IsInAsciiArr( sImplSttSkipChars, rTxt[ nFndPos + 2 ] ))) | |||
641 | { | |||
642 | nSttPos = nSttPos + nFndPos; | |||
643 | rDoc.Delete( nSttPos, nSttPos + 2 ); | |||
644 | rDoc.Insert( nSttPos, (bEnDash || (rCC.isDigit( sTmp, nFndPos - 1 ) && | |||
645 | rCC.isDigit( sTmp, nFndPos + 2 )) ? OUString(cEnDash) : OUString(cEmDash)) ); | |||
646 | bRet = true; | |||
647 | } | |||
648 | } | |||
649 | return bRet; | |||
650 | } | |||
651 | ||||
652 | // Add non-breaking space before specific punctuation marks in French text | |||
653 | bool SvxAutoCorrect::FnAddNonBrkSpace( | |||
654 | SvxAutoCorrDoc& rDoc, const OUString& rTxt, | |||
655 | sal_Int32 nEndPos, | |||
656 | LanguageType eLang, bool& io_bNbspRunNext ) | |||
657 | { | |||
658 | bool bRet = false; | |||
659 | ||||
660 | CharClass& rCC = GetCharClass( eLang ); | |||
661 | ||||
662 | if ( rCC.getLanguageTag().getLanguage() == "fr" ) | |||
663 | { | |||
664 | bool bFrCA = (rCC.getLanguageTag().getCountry() == "CA"); | |||
665 | OUString allChars = ":;?!%"; | |||
666 | OUString chars( allChars ); | |||
667 | if ( bFrCA ) | |||
668 | chars = ":"; | |||
669 | ||||
670 | sal_Unicode cChar = rTxt[ nEndPos ]; | |||
671 | bool bHasSpace = chars.indexOf( cChar ) != -1; | |||
672 | bool bIsSpecial = allChars.indexOf( cChar ) != -1; | |||
673 | if ( bIsSpecial ) | |||
674 | { | |||
675 | // Get the last word delimiter position | |||
676 | sal_Int32 nSttWdPos = nEndPos; | |||
677 | bool bWasWordDelim = false; | |||
678 | while( nSttWdPos ) | |||
679 | { | |||
680 | bWasWordDelim = IsWordDelim( rTxt[ --nSttWdPos ]); | |||
681 | if (bWasWordDelim) | |||
682 | break; | |||
683 | } | |||
684 | ||||
685 | //See if the text is the start of a protocol string, e.g. have text of | |||
686 | //"http" see if it is the start of "http:" and if so leave it alone | |||
687 | sal_Int32 nIndex = nSttWdPos + (bWasWordDelim ? 1 : 0); | |||
688 | sal_Int32 nProtocolLen = nEndPos - nSttWdPos + 1; | |||
689 | if (nIndex + nProtocolLen <= rTxt.getLength()) | |||
690 | { | |||
691 | if (INetURLObject::CompareProtocolScheme(rTxt.copy(nIndex, nProtocolLen)) != INetProtocol::NotValid) | |||
692 | return false; | |||
693 | } | |||
694 | ||||
695 | // Check the presence of "://" in the word | |||
696 | sal_Int32 nStrPos = rTxt.indexOf( "://", nSttWdPos + 1 ); | |||
697 | if ( nStrPos == -1 && nEndPos > 0 ) | |||
698 | { | |||
699 | // Check the previous char | |||
700 | sal_Unicode cPrevChar = rTxt[ nEndPos - 1 ]; | |||
701 | if ( ( chars.indexOf( cPrevChar ) == -1 ) && cPrevChar != '\t' ) | |||
702 | { | |||
703 | // Remove any previous normal space | |||
704 | sal_Int32 nPos = nEndPos - 1; | |||
705 | while ( cPrevChar == ' ' || cPrevChar == cNonBreakingSpace ) | |||
706 | { | |||
707 | if ( nPos == 0 ) break; | |||
708 | nPos--; | |||
709 | cPrevChar = rTxt[ nPos ]; | |||
710 | } | |||
711 | ||||
712 | nPos++; | |||
713 | if ( nEndPos - nPos > 0 ) | |||
714 | rDoc.Delete( nPos, nEndPos ); | |||
715 | ||||
716 | // Add the non-breaking space at the end pos | |||
717 | if ( bHasSpace ) | |||
718 | rDoc.Insert( nPos, OUString(cNonBreakingSpace) ); | |||
719 | io_bNbspRunNext = true; | |||
720 | bRet = true; | |||
721 | } | |||
722 | else if ( chars.indexOf( cPrevChar ) != -1 ) | |||
723 | io_bNbspRunNext = true; | |||
724 | } | |||
725 | } | |||
726 | else if ( cChar == '/' && nEndPos > 1 && rTxt.getLength() > (nEndPos - 1) ) | |||
727 | { | |||
728 | // Remove the hardspace right before to avoid formatting URLs | |||
729 | sal_Unicode cPrevChar = rTxt[ nEndPos - 1 ]; | |||
730 | sal_Unicode cMaybeSpaceChar = rTxt[ nEndPos - 2 ]; | |||
731 | if ( cPrevChar == ':' && cMaybeSpaceChar == cNonBreakingSpace ) | |||
732 | { | |||
733 | rDoc.Delete( nEndPos - 2, nEndPos - 1 ); | |||
734 | bRet = true; | |||
735 | } | |||
736 | } | |||
737 | } | |||
738 | ||||
739 | return bRet; | |||
740 | } | |||
741 | ||||
742 | // URL recognition | |||
743 | bool SvxAutoCorrect::FnSetINetAttr( SvxAutoCorrDoc& rDoc, const OUString& rTxt, | |||
744 | sal_Int32 nSttPos, sal_Int32 nEndPos, | |||
745 | LanguageType eLang ) | |||
746 | { | |||
747 | OUString sURL( URIHelper::FindFirstURLInText( rTxt, nSttPos, nEndPos, | |||
748 | GetCharClass( eLang ) )); | |||
749 | bool bRet = !sURL.isEmpty(); | |||
750 | if( bRet ) // so, set attribute: | |||
751 | rDoc.SetINetAttr( nSttPos, nEndPos, sURL ); | |||
752 | return bRet; | |||
753 | } | |||
754 | ||||
755 | // Automatic *bold*, /italic/, -strikeout- and _underline_ | |||
756 | bool SvxAutoCorrect::FnChgWeightUnderl( SvxAutoCorrDoc& rDoc, const OUString& rTxt, | |||
757 | sal_Int32 nEndPos ) | |||
758 | { | |||
759 | // Condition: | |||
760 | // at the beginning: _, *, / or ~ after Space with the following !Space | |||
761 | // at the end: _, *, / or ~ before Space (word delimiter?) | |||
762 | ||||
763 | sal_Unicode cInsChar = rTxt[ nEndPos ]; // underline, bold, italic or strikeout | |||
764 | if( ++nEndPos != rTxt.getLength() && | |||
765 | !IsWordDelim( rTxt[ nEndPos ] ) ) | |||
766 | return false; | |||
767 | ||||
768 | --nEndPos; | |||
769 | ||||
770 | bool bAlphaNum = false; | |||
771 | sal_Int32 nPos = nEndPos; | |||
772 | sal_Int32 nFndPos = -1; | |||
773 | CharClass& rCC = GetCharClass( LANGUAGE_SYSTEMLanguageType(0x0000) ); | |||
774 | ||||
775 | while( nPos ) | |||
776 | { | |||
777 | switch( sal_Unicode c = rTxt[ --nPos ] ) | |||
778 | { | |||
779 | case '_': | |||
780 | case '-': | |||
781 | case '/': | |||
782 | case '*': | |||
783 | if( c == cInsChar ) | |||
784 | { | |||
785 | if( bAlphaNum && nPos+1 < nEndPos && ( !nPos || | |||
786 | IsWordDelim( rTxt[ nPos-1 ])) && | |||
787 | !IsWordDelim( rTxt[ nPos+1 ])) | |||
788 | nFndPos = nPos; | |||
789 | else | |||
790 | // Condition is not satisfied, so cancel | |||
791 | nFndPos = -1; | |||
792 | nPos = 0; | |||
793 | } | |||
794 | break; | |||
795 | default: | |||
796 | if( !bAlphaNum ) | |||
797 | bAlphaNum = rCC.isLetterNumeric( rTxt, nPos ); | |||
798 | } | |||
799 | } | |||
800 | ||||
801 | if( -1 != nFndPos ) | |||
802 | { | |||
803 | // first delete the Character at the end - this allows insertion | |||
804 | // of an empty hint in SetAttr which would be removed by Delete | |||
805 | // (fdo#62536, AUTOFMT in Writer) | |||
806 | rDoc.Delete( nEndPos, nEndPos + 1 ); | |||
807 | rDoc.Delete( nFndPos, nFndPos + 1 ); | |||
808 | // Span the Attribute over the area | |||
809 | // the end. | |||
810 | if( '*' == cInsChar ) // Bold | |||
811 | { | |||
812 | SvxWeightItem aSvxWeightItem( WEIGHT_BOLD, SID_ATTR_CHAR_WEIGHT( 10000 + 9 ) ); | |||
813 | rDoc.SetAttr( nFndPos, nEndPos - 1, | |||
814 | SID_ATTR_CHAR_WEIGHT( 10000 + 9 ), | |||
815 | aSvxWeightItem); | |||
816 | } | |||
817 | else if( '/' == cInsChar ) // Italic | |||
818 | { | |||
819 | SvxPostureItem aSvxPostureItem( ITALIC_NORMAL, SID_ATTR_CHAR_POSTURE( 10000 + 8 ) ); | |||
820 | rDoc.SetAttr( nFndPos, nEndPos - 1, | |||
821 | SID_ATTR_CHAR_POSTURE( 10000 + 8 ), | |||
822 | aSvxPostureItem); | |||
823 | } | |||
824 | else if( '-' == cInsChar ) // Strikeout | |||
825 | { | |||
826 | SvxCrossedOutItem aSvxCrossedOutItem( STRIKEOUT_SINGLE, SID_ATTR_CHAR_STRIKEOUT( 10000 + 13 ) ); | |||
827 | rDoc.SetAttr( nFndPos, nEndPos - 1, | |||
828 | SID_ATTR_CHAR_STRIKEOUT( 10000 + 13 ), | |||
829 | aSvxCrossedOutItem); | |||
830 | } | |||
831 | else // Underline | |||
832 | { | |||
833 | SvxUnderlineItem aSvxUnderlineItem( LINESTYLE_SINGLE, SID_ATTR_CHAR_UNDERLINE( 10000 + 14 ) ); | |||
834 | rDoc.SetAttr( nFndPos, nEndPos - 1, | |||
835 | SID_ATTR_CHAR_UNDERLINE( 10000 + 14 ), | |||
836 | aSvxUnderlineItem); | |||
837 | } | |||
838 | } | |||
839 | ||||
840 | return -1 != nFndPos; | |||
841 | } | |||
842 | ||||
843 | // Capitalize first letter of every sentence | |||
844 | void SvxAutoCorrect::FnCapitalStartSentence( SvxAutoCorrDoc& rDoc, | |||
845 | const OUString& rTxt, bool bNormalPos, | |||
846 | sal_Int32 nSttPos, sal_Int32 nEndPos, | |||
847 | LanguageType eLang ) | |||
848 | { | |||
849 | ||||
850 | if( rTxt.isEmpty() || nEndPos <= nSttPos ) | |||
851 | return; | |||
852 | ||||
853 | CharClass& rCC = GetCharClass( eLang ); | |||
854 | OUString aText( rTxt ); | |||
855 | const sal_Unicode *pStart = aText.getStr(), | |||
856 | *pStr = pStart + nEndPos, | |||
857 | *pWordStt = nullptr, | |||
858 | *pDelim = nullptr; | |||
859 | ||||
860 | bool bAtStart = false; | |||
861 | do { | |||
862 | --pStr; | |||
863 | if (rCC.isLetter(aText, pStr - pStart)) | |||
864 | { | |||
865 | if( !pWordStt ) | |||
866 | pDelim = pStr+1; | |||
867 | pWordStt = pStr; | |||
868 | } | |||
869 | else if (pWordStt && !rCC.isDigit(aText, pStr - pStart)) | |||
870 | { | |||
871 | if( (lcl_IsInAsciiArr( "-'", *pStr ) || *pStr == cApostrophe) && // These characters are allowed in words | |||
872 | pWordStt - 1 == pStr && | |||
873 | // Installation at beginning of paragraph. Replaced < by <= (#i38971#) | |||
874 | (pStart + 1) <= pStr && | |||
875 | rCC.isLetter(aText, pStr-1 - pStart)) | |||
876 | pWordStt = --pStr; | |||
877 | else | |||
878 | break; | |||
879 | } | |||
880 | bAtStart = (pStart == pStr); | |||
881 | } while( !bAtStart ); | |||
882 | ||||
883 | if (!pWordStt) | |||
884 | return; // no character to be replaced | |||
885 | ||||
886 | ||||
887 | if (rCC.isDigit(aText, pStr - pStart)) | |||
888 | return; // already ok | |||
889 | ||||
890 | if (IsUpperLetter(rCC.getCharacterType(aText, pWordStt - pStart))) | |||
891 | return; // already ok | |||
892 | ||||
893 | //See if the text is the start of a protocol string, e.g. have text of | |||
894 | //"http" see if it is the start of "http:" and if so leave it alone | |||
895 | sal_Int32 nIndex = pWordStt - pStart; | |||
896 | sal_Int32 nProtocolLen = pDelim - pWordStt + 1; | |||
897 | if (nIndex + nProtocolLen <= rTxt.getLength()) | |||
898 | { | |||
899 | if (INetURLObject::CompareProtocolScheme(rTxt.copy(nIndex, nProtocolLen)) != INetProtocol::NotValid) | |||
900 | return; // already ok | |||
901 | } | |||
902 | ||||
903 | if (0x1 == *pWordStt || 0x2 == *pWordStt) | |||
904 | return; // already ok | |||
905 | ||||
906 | // Only capitalize, if string before specified characters is long enough | |||
907 | if( *pDelim && 2 >= pDelim - pWordStt && | |||
908 | lcl_IsInAsciiArr( ".-)>", *pDelim ) ) | |||
909 | return; | |||
910 | ||||
911 | // tdf#59666 don't capitalize single Greek letters (except in Greek texts) | |||
912 | if ( 1 == pDelim - pWordStt && 0x03B1 <= *pWordStt && *pWordStt <= 0x03C9 && eLang != LANGUAGE_GREEKLanguageType(0x0408) ) | |||
913 | return; | |||
914 | ||||
915 | if( !bAtStart ) // Still no beginning of a paragraph? | |||
916 | { | |||
917 | if (NonFieldWordDelim(*pStr)) | |||
918 | { | |||
919 | for (;;) | |||
920 | { | |||
921 | bAtStart = (pStart == pStr--); | |||
922 | if (bAtStart || !NonFieldWordDelim(*pStr)) | |||
923 | break; | |||
924 | } | |||
925 | } | |||
926 | // Asian full stop, full width full stop, full width exclamation mark | |||
927 | // and full width question marks are treated as word delimiters | |||
928 | else if ( 0x3002 != *pStr && 0xFF0E != *pStr && 0xFF01 != *pStr && | |||
929 | 0xFF1F != *pStr ) | |||
930 | return; // no valid separator -> no replacement | |||
931 | } | |||
932 | ||||
933 | // No replacement for words in TWo INitial CApitals or sMALL iNITIAL list | |||
934 | if (FindInWrdSttExceptList(eLang, OUString(pWordStt, pDelim - pWordStt))) | |||
935 | return; | |||
936 | ||||
937 | if( bAtStart ) // at the beginning of a paragraph? | |||
938 | { | |||
939 | // Check out the previous paragraph, if it exists. | |||
940 | // If so, then check to paragraph separator at the end. | |||
941 | OUString const*const pPrevPara = rDoc.GetPrevPara(bNormalPos); | |||
942 | if (!pPrevPara) | |||
943 | { | |||
944 | // valid separator -> replace | |||
945 | OUString sChar( *pWordStt ); | |||
946 | sChar = rCC.titlecase(sChar); //see fdo#56740 | |||
947 | if (!comphelper::string::equals(sChar, *pWordStt)) | |||
948 | rDoc.ReplaceRange( pWordStt - pStart, 1, sChar ); | |||
949 | return; | |||
950 | } | |||
951 | ||||
952 | aText = *pPrevPara; | |||
953 | bAtStart = false; | |||
954 | pStart = aText.getStr(); | |||
955 | pStr = pStart + aText.getLength(); | |||
956 | ||||
957 | do { // overwrite all blanks | |||
958 | --pStr; | |||
959 | if (!NonFieldWordDelim(*pStr)) | |||
960 | break; | |||
961 | bAtStart = (pStart == pStr); | |||
962 | } while( !bAtStart ); | |||
963 | ||||
964 | if( bAtStart ) | |||
965 | return; // no valid separator -> no replacement | |||
966 | } | |||
967 | ||||
968 | // Found [ \t]+[A-Z0-9]+ until here. Test now on the paragraph separator. | |||
969 | // all three can happen, but not more than once! | |||
970 | const sal_Unicode* pExceptStt = nullptr; | |||
971 | bool bContinue = true; | |||
972 | Flags nFlag = Flags::NONE; | |||
973 | do | |||
974 | { | |||
975 | switch (*pStr) | |||
976 | { | |||
977 | // Western and Asian full stop | |||
978 | case '.': | |||
979 | case 0x3002: | |||
980 | case 0xFF0E: | |||
981 | { | |||
982 | if (pStr >= pStart + 2 && *(pStr - 2) == '.') | |||
983 | { | |||
984 | //e.g. text "f.o.o. word": Now currently considering | |||
985 | //capitalizing word but second last character of | |||
986 | //previous word is a . So probably last word is an | |||
987 | //anagram that ends in . and not truly the end of a | |||
988 | //previous sentence, so don't autocapitalize this word | |||
989 | return; | |||
990 | } | |||
991 | if (nFlag & Flags::FullStop) | |||
992 | return; // no valid separator -> no replacement | |||
993 | nFlag |= Flags::FullStop; | |||
994 | pExceptStt = pStr; | |||
995 | } | |||
996 | break; | |||
997 | case '!': | |||
998 | case 0xFF01: | |||
999 | { | |||
1000 | if (nFlag & Flags::ExclamationMark) | |||
1001 | return; // no valid separator -> no replacement | |||
1002 | nFlag |= Flags::ExclamationMark; | |||
1003 | } | |||
1004 | break; | |||
1005 | case '?': | |||
1006 | case 0xFF1F: | |||
1007 | { | |||
1008 | if (nFlag & Flags::QuestionMark) | |||
1009 | return; // no valid separator -> no replacement | |||
1010 | nFlag |= Flags::QuestionMark; | |||
1011 | } | |||
1012 | break; | |||
1013 | default: | |||
1014 | if (nFlag == Flags::NONE) | |||
1015 | return; // no valid separator -> no replacement | |||
1016 | else | |||
1017 | bContinue = false; | |||
1018 | break; | |||
1019 | } | |||
1020 | ||||
1021 | if (bContinue && pStr-- == pStart) | |||
1022 | { | |||
1023 | return; // no valid separator -> no replacement | |||
1024 | } | |||
1025 | } while (bContinue); | |||
1026 | if (Flags::FullStop != nFlag) | |||
1027 | pExceptStt = nullptr; | |||
1028 | ||||
1029 | // Only capitalize, if string is long enough | |||
1030 | if( 2 > ( pStr - pStart ) ) | |||
1031 | return; | |||
1032 | ||||
1033 | if (!rCC.isLetterNumeric(aText, pStr-- - pStart)) | |||
1034 | { | |||
1035 | bool bValid = false, bAlphaFnd = false; | |||
1036 | const sal_Unicode* pTmpStr = pStr; | |||
1037 | while( !bValid ) | |||
1038 | { | |||
1039 | if( rCC.isDigit( aText, pTmpStr - pStart ) ) | |||
1040 | { | |||
1041 | bValid = true; | |||
1042 | pStr = pTmpStr - 1; | |||
1043 | } | |||
1044 | else if( rCC.isLetter( aText, pTmpStr - pStart ) ) | |||
1045 | { | |||
1046 | if( bAlphaFnd ) | |||
1047 | { | |||
1048 | bValid = true; | |||
1049 | pStr = pTmpStr; | |||
1050 | } | |||
1051 | else | |||
1052 | bAlphaFnd = true; | |||
1053 | } | |||
1054 | else if (bAlphaFnd || NonFieldWordDelim(*pTmpStr)) | |||
1055 | break; | |||
1056 | ||||
1057 | if( pTmpStr == pStart ) | |||
1058 | break; | |||
1059 | ||||
1060 | --pTmpStr; | |||
1061 | } | |||
1062 | ||||
1063 | if( !bValid ) | |||
1064 | return; // no valid separator -> no replacement | |||
1065 | } | |||
1066 | ||||
1067 | bool bNumericOnly = '0' <= *(pStr+1) && *(pStr+1) <= '9'; | |||
1068 | ||||
1069 | // Search for the beginning of the word | |||
1070 | while (!NonFieldWordDelim(*pStr)) | |||
1071 | { | |||
1072 | if( bNumericOnly && rCC.isLetter( aText, pStr - pStart ) ) | |||
1073 | bNumericOnly = false; | |||
1074 | ||||
1075 | if( pStart == pStr ) | |||
1076 | break; | |||
1077 | ||||
1078 | --pStr; | |||
1079 | } | |||
1080 | ||||
1081 | if( bNumericOnly ) // consists of only numbers, then not | |||
1082 | return; | |||
1083 | ||||
1084 | if (NonFieldWordDelim(*pStr)) | |||
1085 | ++pStr; | |||
1086 | ||||
1087 | OUString sWord; | |||
1088 | ||||
1089 | // check on the basis of the exception list | |||
1090 | if( pExceptStt ) | |||
1091 | { | |||
1092 | sWord = OUString(pStr, pExceptStt - pStr + 1); | |||
1093 | if( FindInCplSttExceptList(eLang, sWord) ) | |||
1094 | return; | |||
1095 | ||||
1096 | // Delete all non alphanumeric. Test the characters at the | |||
1097 | // beginning/end of the word ( recognizes: "(min.", "/min.", and so on.) | |||
1098 | OUString sTmp( sWord ); | |||
1099 | while( !sTmp.isEmpty() && | |||
1100 | !rCC.isLetterNumeric( sTmp, 0 ) ) | |||
1101 | sTmp = sTmp.copy(1); | |||
1102 | ||||
1103 | // Remove all non alphanumeric characters towards the end up until | |||
1104 | // the last one. | |||
1105 | sal_Int32 nLen = sTmp.getLength(); | |||
1106 | while( nLen && !rCC.isLetterNumeric( sTmp, nLen-1 ) ) | |||
1107 | --nLen; | |||
1108 | if( nLen + 1 < sTmp.getLength() ) | |||
1109 | sTmp = sTmp.copy( 0, nLen + 1 ); | |||
1110 | ||||
1111 | if( !sTmp.isEmpty() && sTmp.getLength() != sWord.getLength() && | |||
1112 | FindInCplSttExceptList(eLang, sTmp)) | |||
1113 | return; | |||
1114 | ||||
1115 | if(FindInCplSttExceptList(eLang, sWord, true)) | |||
1116 | return; | |||
1117 | } | |||
1118 | ||||
1119 | // Ok, then replace | |||
1120 | sal_Unicode cSave = *pWordStt; | |||
1121 | nSttPos = pWordStt - rTxt.getStr(); | |||
1122 | OUString sChar = rCC.titlecase(OUString(cSave)); //see fdo#56740 | |||
1123 | bool bRet = sChar[0] != cSave && rDoc.ReplaceRange( nSttPos, 1, sChar ); | |||
1124 | ||||
1125 | // Perhaps someone wants to have the word | |||
1126 | if( bRet && ACFlags::SaveWordCplSttLst & nFlags ) | |||
1127 | rDoc.SaveCpltSttWord( ACFlags::CapitalStartSentence, nSttPos, sWord, cSave ); | |||
1128 | } | |||
1129 | ||||
1130 | // Correct accidental use of cAPS LOCK key | |||
1131 | bool SvxAutoCorrect::FnCorrectCapsLock( SvxAutoCorrDoc& rDoc, const OUString& rTxt, | |||
1132 | sal_Int32 nSttPos, sal_Int32 nEndPos, | |||
1133 | LanguageType eLang ) | |||
1134 | { | |||
1135 | if (nEndPos - nSttPos < 2) | |||
1136 | // string must be at least 2-character long. | |||
1137 | return false; | |||
1138 | ||||
1139 | CharClass& rCC = GetCharClass( eLang ); | |||
1140 | ||||
1141 | // Check the first 2 letters. | |||
1142 | if ( !IsLowerLetter(rCC.getCharacterType(rTxt, nSttPos)) ) | |||
1143 | return false; | |||
1144 | ||||
1145 | if ( !IsUpperLetter(rCC.getCharacterType(rTxt, nSttPos+1)) ) | |||
1146 | return false; | |||
1147 | ||||
1148 | OUStringBuffer aConverted; | |||
1149 | aConverted.append( rCC.uppercase(OUString(rTxt[nSttPos])) ); | |||
1150 | aConverted.append( rCC.lowercase(OUString(rTxt[nSttPos+1])) ); | |||
1151 | ||||
1152 | // No replacement for words in TWo INitial CApitals or sMALL iNITIAL list | |||
1153 | if (FindInWrdSttExceptList(eLang, rTxt.copy(nSttPos, nEndPos - nSttPos))) | |||
1154 | return false; | |||
1155 | ||||
1156 | for( sal_Int32 i = nSttPos+2; i < nEndPos; ++i ) | |||
1157 | { | |||
1158 | if ( IsLowerLetter(rCC.getCharacterType(rTxt, i)) ) | |||
1159 | // A lowercase letter disqualifies the whole text. | |||
1160 | return false; | |||
1161 | ||||
1162 | if ( IsUpperLetter(rCC.getCharacterType(rTxt, i)) ) | |||
1163 | // Another uppercase letter. Convert it. | |||
1164 | aConverted.append( rCC.lowercase(OUString(rTxt[i])) ); | |||
1165 | else | |||
1166 | // This is not an alphabetic letter. Leave it as-is. | |||
1167 | aConverted.append( rTxt[i] ); | |||
1168 | } | |||
1169 | ||||
1170 | // Replace the word. | |||
1171 | rDoc.Delete(nSttPos, nEndPos); | |||
1172 | rDoc.Insert(nSttPos, aConverted.makeStringAndClear()); | |||
1173 | ||||
1174 | return true; | |||
1175 | } | |||
1176 | ||||
1177 | ||||
1178 | sal_Unicode SvxAutoCorrect::GetQuote( sal_Unicode cInsChar, bool bSttQuote, | |||
1179 | LanguageType eLang ) const | |||
1180 | { | |||
1181 | sal_Unicode cRet = bSttQuote ? ( '\"' == cInsChar | |||
1182 | ? GetStartDoubleQuote() | |||
1183 | : GetStartSingleQuote() ) | |||
1184 | : ( '\"' == cInsChar | |||
1185 | ? GetEndDoubleQuote() | |||
1186 | : GetEndSingleQuote() ); | |||
1187 | if( !cRet ) | |||
1188 | { | |||
1189 | // then through the Language find the right character | |||
1190 | if( LANGUAGE_NONELanguageType(0x00FF) == eLang ) | |||
1191 | cRet = cInsChar; | |||
1192 | else | |||
1193 | { | |||
1194 | LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); | |||
1195 | OUString sRet( bSttQuote | |||
1196 | ? ( '\"' == cInsChar | |||
1197 | ? rLcl.getDoubleQuotationMarkStart() | |||
1198 | : rLcl.getQuotationMarkStart() ) | |||
1199 | : ( '\"' == cInsChar | |||
1200 | ? rLcl.getDoubleQuotationMarkEnd() | |||
1201 | : rLcl.getQuotationMarkEnd() )); | |||
1202 | cRet = !sRet.isEmpty() ? sRet[0] : cInsChar; | |||
1203 | } | |||
1204 | } | |||
1205 | return cRet; | |||
1206 | } | |||
1207 | ||||
1208 | void SvxAutoCorrect::InsertQuote( SvxAutoCorrDoc& rDoc, sal_Int32 nInsPos, | |||
1209 | sal_Unicode cInsChar, bool bSttQuote, | |||
1210 | bool bIns, LanguageType eLang, ACQuotes eType ) const | |||
1211 | { | |||
1212 | sal_Unicode cRet; | |||
1213 | ||||
1214 | if ( eType == ACQuotes::DoubleAngleQuote ) | |||
1215 | { | |||
1216 | bool bSwiss = eLang == LANGUAGE_FRENCH_SWISSLanguageType(0x100C); | |||
1217 | // pressing " inside a quotation -> use second level angle quotes | |||
1218 | bool bLeftQuote = '\"' == cInsChar && | |||
1219 | // start position and Romanian OR | |||
1220 | // not start position and Hungarian | |||
1221 | bSttQuote == (eLang != LANGUAGE_HUNGARIANLanguageType(0x040E)); | |||
1222 | cRet = ( '<' == cInsChar || bLeftQuote ) | |||
1223 | ? ( bSwiss ? cLeftSingleAngleQuote : cLeftDoubleAngleQuote ) | |||
1224 | : ( bSwiss ? cRightSingleAngleQuote : cRightDoubleAngleQuote ); | |||
1225 | } | |||
1226 | else if ( eType == ACQuotes::UseApostrophe ) | |||
1227 | cRet = cApostrophe; | |||
1228 | else | |||
1229 | cRet = GetQuote( cInsChar, bSttQuote, eLang ); | |||
1230 | ||||
1231 | OUString sChg( cInsChar ); | |||
1232 | if( bIns ) | |||
1233 | rDoc.Insert( nInsPos, sChg ); | |||
1234 | else | |||
1235 | rDoc.Replace( nInsPos, sChg ); | |||
1236 | ||||
1237 | sChg = OUString(cRet); | |||
1238 | ||||
1239 | if( eType == ACQuotes::NonBreakingSpace ) | |||
1240 | { | |||
1241 | if( rDoc.Insert( bSttQuote ? nInsPos+1 : nInsPos, OUStringChar(cNonBreakingSpace) )) | |||
1242 | { | |||
1243 | if( !bSttQuote ) | |||
1244 | ++nInsPos; | |||
1245 | } | |||
1246 | } | |||
1247 | else if( eType == ACQuotes::DoubleAngleQuote && cInsChar != '\"' ) | |||
1248 | { | |||
1249 | rDoc.Delete( nInsPos-1, nInsPos); | |||
1250 | --nInsPos; | |||
1251 | } | |||
1252 | ||||
1253 | rDoc.Replace( nInsPos, sChg ); | |||
1254 | ||||
1255 | // i' -> I' in English (last step for the Undo) | |||
1256 | if( eType == ACQuotes::CapitalizeIAm ) | |||
1257 | rDoc.Replace( nInsPos-1, "I" ); | |||
1258 | } | |||
1259 | ||||
1260 | OUString SvxAutoCorrect::GetQuote( SvxAutoCorrDoc const & rDoc, sal_Int32 nInsPos, | |||
1261 | sal_Unicode cInsChar, bool bSttQuote ) | |||
1262 | { | |||
1263 | const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); | |||
1264 | sal_Unicode cRet = GetQuote( cInsChar, bSttQuote, eLang ); | |||
1265 | ||||
1266 | OUString sRet(cRet); | |||
1267 | ||||
1268 | if( '\"' == cInsChar ) | |||
1269 | { | |||
1270 | if (primary(eLang) == primary(LANGUAGE_FRENCHLanguageType(0x040C)) && eLang != LANGUAGE_FRENCH_SWISSLanguageType(0x100C)) | |||
1271 | { | |||
1272 | if( bSttQuote ) | |||
1273 | sRet += " "; | |||
1274 | else | |||
1275 | sRet = " " + sRet; | |||
1276 | } | |||
1277 | } | |||
1278 | return sRet; | |||
1279 | } | |||
1280 | ||||
1281 | // search preceding opening quote in the paragraph before the insert position | |||
1282 | static bool lcl_HasPrecedingChar( const OUString& rTxt, sal_Int32 nPos, | |||
1283 | const sal_Unicode sPrecedingChar, const sal_Unicode* aStopChars ) | |||
1284 | { | |||
1285 | sal_Unicode cTmpChar; | |||
1286 | ||||
1287 | do { | |||
1288 | cTmpChar = rTxt[ --nPos ]; | |||
1289 | if ( cTmpChar == sPrecedingChar ) | |||
1290 | return true; | |||
1291 | ||||
1292 | for ( const sal_Unicode* pCh = aStopChars; *pCh; ++pCh ) | |||
1293 | if ( cTmpChar == *pCh ) | |||
1294 | return false; | |||
1295 | ||||
1296 | } while ( nPos > 0 ); | |||
1297 | ||||
1298 | return false; | |||
1299 | } | |||
1300 | ||||
1301 | // WARNING: rText may become invalid, see comment below | |||
1302 | void SvxAutoCorrect::DoAutoCorrect( SvxAutoCorrDoc& rDoc, const OUString& rTxt, | |||
1303 | sal_Int32 nInsPos, sal_Unicode cChar, | |||
1304 | bool bInsert, bool& io_bNbspRunNext, vcl::Window const * pFrameWin ) | |||
1305 | { | |||
1306 | bool bIsNextRun = io_bNbspRunNext; | |||
1307 | io_bNbspRunNext = false; // if it was set, then it has to be turned off | |||
1308 | ||||
1309 | do{ // only for middle check loop !! | |||
1310 | if( cChar ) | |||
1311 | { | |||
1312 | // Prevent double space | |||
1313 | if( nInsPos && ' ' == cChar && | |||
1314 | IsAutoCorrFlag( ACFlags::IgnoreDoubleSpace ) && | |||
1315 | ' ' == rTxt[ nInsPos - 1 ]) | |||
1316 | { | |||
1317 | break; | |||
1318 | } | |||
1319 | ||||
1320 | bool bSingle = '\'' == cChar; | |||
1321 | bool bIsReplaceQuote = | |||
1322 | (IsAutoCorrFlag( ACFlags::ChgQuotes ) && ('\"' == cChar )) || | |||
1323 | (IsAutoCorrFlag( ACFlags::ChgSglQuotes ) && bSingle ); | |||
1324 | if( bIsReplaceQuote ) | |||
1325 | { | |||
1326 | bool bSttQuote = !nInsPos; | |||
1327 | ACQuotes eType = ACQuotes::NONE; | |||
1328 | const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); | |||
1329 | if (!bSttQuote) | |||
1330 | { | |||
1331 | sal_Unicode cPrev = rTxt[ nInsPos-1 ]; | |||
1332 | bSttQuote = NonFieldWordDelim(cPrev) || | |||
1333 | lcl_IsInAsciiArr( "([{", cPrev ) || | |||
1334 | ( cEmDash == cPrev ) || | |||
1335 | ( cEnDash == cPrev ); | |||
1336 | // tdf#38394 use opening quotation mark << in French l'<<word>> | |||
1337 | if ( !bSingle && !bSttQuote && cPrev == cApostrophe && | |||
1338 | primary(eLang) == primary(LANGUAGE_FRENCHLanguageType(0x040C)) && | |||
1339 | ( ( ( nInsPos == 2 || ( nInsPos > 2 && IsWordDelim( rTxt[ nInsPos-3 ] ) ) ) && | |||
1340 | // abbreviated form of ce, de, je, la, le, ne, me, te, se or si | |||
1341 | OUString("cdjlnmtsCDJLNMTS").indexOf( rTxt[ nInsPos-2 ] ) > -1 ) || | |||
1342 | ( ( nInsPos == 3 || (nInsPos > 3 && IsWordDelim( rTxt[ nInsPos-4 ] ) ) ) && | |||
1343 | // abbreviated form of que | |||
1344 | ( rTxt[ nInsPos-2 ] == 'u' || rTxt[ nInsPos-2 ] == 'U' ) && | |||
1345 | ( rTxt[ nInsPos-3 ] == 'q' || rTxt[ nInsPos-3 ] == 'Q' ) ) ) ) | |||
1346 | { | |||
1347 | bSttQuote = true; | |||
1348 | } | |||
1349 | // tdf#108423 for capitalization of English i'm | |||
1350 | else if ( bSingle && ( cPrev == 'i' ) && | |||
1351 | primary(eLang) == primary(LANGUAGE_ENGLISHLanguageType(0x0009)) && | |||
1352 | ( nInsPos == 1 || IsWordDelim( rTxt[ nInsPos-2 ] ) ) ) | |||
1353 | { | |||
1354 | eType = ACQuotes::CapitalizeIAm; | |||
1355 | } | |||
1356 | // tdf#133524 support >>Hungarian<< and <<Romanian>> secondary level quotations | |||
1357 | else if ( !bSingle && nInsPos && | |||
1358 | ( ( eLang == LANGUAGE_HUNGARIANLanguageType(0x040E) && | |||
1359 | lcl_HasPrecedingChar( rTxt, nInsPos, | |||
1360 | bSttQuote ? aStopDoubleAngleQuoteStart[0] : aStopDoubleAngleQuoteEnd[0], | |||
1361 | bSttQuote ? aStopDoubleAngleQuoteStart + 1 : aStopDoubleAngleQuoteEnd + 1 ) ) || | |||
1362 | ( eLang.anyOf( | |||
1363 | LANGUAGE_ROMANIANLanguageType(0x0418), | |||
1364 | LANGUAGE_ROMANIAN_MOLDOVALanguageType(0x0818) ) && | |||
1365 | lcl_HasPrecedingChar( rTxt, nInsPos, | |||
1366 | bSttQuote ? aStopDoubleAngleQuoteStart[0] : aStopDoubleAngleQuoteEndRo[0], | |||
1367 | bSttQuote ? aStopDoubleAngleQuoteStart + 1 : aStopDoubleAngleQuoteEndRo + 1 ) ) ) ) | |||
1368 | { | |||
1369 | LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); | |||
1370 | // only if the opening double quotation mark is the default one | |||
1371 | if ( rLcl.getDoubleQuotationMarkStart() == OUStringChar(aStopDoubleAngleQuoteStart[0]) ) | |||
1372 | eType = ACQuotes::DoubleAngleQuote; | |||
1373 | } | |||
1374 | else if ( bSingle && nInsPos && !bSttQuote && | |||
1375 | // tdf#128860 use apostrophe outside of second level quotation in Czech, German, Icelandic, | |||
1376 | // Slovak and Slovenian instead of the – in this case, bad – closing quotation mark U+2018. | |||
1377 | // tdf#123786 the same for Russian and Ukrainian | |||
1378 | ( ( eLang.anyOf ( | |||
1379 | LANGUAGE_CZECHLanguageType(0x0405), | |||
1380 | LANGUAGE_GERMANLanguageType(0x0407), | |||
1381 | LANGUAGE_GERMAN_SWISSLanguageType(0x0807), | |||
1382 | LANGUAGE_GERMAN_AUSTRIANLanguageType(0x0C07), | |||
1383 | LANGUAGE_GERMAN_LUXEMBOURGLanguageType(0x1007), | |||
1384 | LANGUAGE_GERMAN_LIECHTENSTEINLanguageType(0x1407), | |||
1385 | LANGUAGE_ICELANDICLanguageType(0x040F), | |||
1386 | LANGUAGE_SLOVAKLanguageType(0x041B), | |||
1387 | LANGUAGE_SLOVENIANLanguageType(0x0424) ) && | |||
1388 | !lcl_HasPrecedingChar( rTxt, nInsPos, aStopSingleQuoteEnd[0], aStopSingleQuoteEnd + 1 ) ) || | |||
1389 | ( eLang.anyOf ( | |||
1390 | LANGUAGE_RUSSIANLanguageType(0x0419), | |||
1391 | LANGUAGE_UKRAINIANLanguageType(0x0422) ) && | |||
1392 | !lcl_HasPrecedingChar( rTxt, nInsPos, aStopSingleQuoteEndRuUa[0], aStopSingleQuoteEndRuUa + 1 ) ) ) ) | |||
1393 | { | |||
1394 | LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); | |||
1395 | CharClass& rCC = GetCharClass( eLang ); | |||
1396 | if ( ( rLcl.getQuotationMarkStart() == OUStringChar(aStopSingleQuoteEnd[0]) || | |||
1397 | rLcl.getQuotationMarkStart() == OUStringChar(aStopSingleQuoteEndRuUa[0]) ) && | |||
1398 | // use apostrophe only after letters, not after digits or punctuation | |||
1399 | rCC.isLetter(rTxt, nInsPos-1) ) | |||
1400 | { | |||
1401 | eType = ACQuotes::UseApostrophe; | |||
1402 | } | |||
1403 | } | |||
1404 | } | |||
1405 | ||||
1406 | if ( eType == ACQuotes::NONE && !bSingle && | |||
1407 | ( primary(eLang) == primary(LANGUAGE_FRENCHLanguageType(0x040C)) && eLang != LANGUAGE_FRENCH_SWISSLanguageType(0x100C) ) ) | |||
1408 | eType = ACQuotes::NonBreakingSpace; | |||
1409 | ||||
1410 | InsertQuote( rDoc, nInsPos, cChar, bSttQuote, bInsert, eLang, eType ); | |||
1411 | break; | |||
1412 | } | |||
1413 | // tdf#133524 change "<<" and ">>" to double angle quotation marks | |||
1414 | else if ( IsAutoCorrFlag( ACFlags::ChgQuotes ) && | |||
1415 | IsAutoCorrFlag( ACFlags::ChgAngleQuotes ) && | |||
1416 | ('<' == cChar || '>' == cChar) && | |||
1417 | nInsPos > 0 && cChar == rTxt[ nInsPos-1 ] ) | |||
1418 | { | |||
1419 | const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); | |||
1420 | if ( eLang.anyOf( | |||
1421 | LANGUAGE_CATALANLanguageType(0x0403), // primary level | |||
1422 | LANGUAGE_CATALAN_VALENCIANLanguageType(0x0803), // primary level | |||
1423 | LANGUAGE_FINNISHLanguageType(0x040B), // alternative primary level | |||
1424 | LANGUAGE_FRENCH_SWISSLanguageType(0x100C), // second level | |||
1425 | LANGUAGE_GALICIANLanguageType(0x0456), // primary level | |||
1426 | LANGUAGE_HUNGARIANLanguageType(0x040E), // second level | |||
1427 | LANGUAGE_POLISHLanguageType(0x0415), // second level | |||
1428 | LANGUAGE_PORTUGUESELanguageType(0x0816), // primary level | |||
1429 | LANGUAGE_PORTUGUESE_BRAZILIANLanguageType(0x0416), // primary level | |||
1430 | LANGUAGE_ROMANIANLanguageType(0x0418), // second level | |||
1431 | LANGUAGE_ROMANIAN_MOLDOVALanguageType(0x0818), // second level | |||
1432 | LANGUAGE_SWEDISHLanguageType(0x041D), // alternative primary level | |||
1433 | LANGUAGE_SWEDISH_FINLANDLanguageType(0x081D), // alternative primary level | |||
1434 | LANGUAGE_UKRAINIANLanguageType(0x0422), // primary level | |||
1435 | LANGUAGE_USER_ARAGONESELanguageType(0x0665), // primary level | |||
1436 | LANGUAGE_USER_ASTURIANLanguageType(0x064A) ) || // primary level | |||
1437 | primary(eLang) == primary(LANGUAGE_GERMANLanguageType(0x0407)) || // alternative primary level | |||
1438 | primary(eLang) == primary(LANGUAGE_SPANISHLanguageType(0x0C0A)) ) // primary level | |||
1439 | { | |||
1440 | InsertQuote( rDoc, nInsPos, cChar, false, bInsert, eLang, ACQuotes::DoubleAngleQuote ); | |||
1441 | break; | |||
1442 | } | |||
1443 | } | |||
1444 | ||||
1445 | if( bInsert ) | |||
1446 | rDoc.Insert( nInsPos, OUString(cChar) ); | |||
1447 | else | |||
1448 | rDoc.Replace( nInsPos, OUString(cChar) ); | |||
1449 | ||||
1450 | // Hardspaces autocorrection | |||
1451 | if ( IsAutoCorrFlag( ACFlags::AddNonBrkSpace ) ) | |||
1452 | { | |||
1453 | if ( NeedsHardspaceAutocorr( cChar ) && | |||
1454 | FnAddNonBrkSpace( rDoc, rTxt, nInsPos, GetDocLanguage( rDoc, nInsPos ), io_bNbspRunNext ) ) | |||
1455 | { | |||
1456 | ; | |||
1457 | } | |||
1458 | else if ( bIsNextRun && !IsAutoCorrectChar( cChar ) ) | |||
1459 | { | |||
1460 | // Remove the NBSP if it wasn't an autocorrection | |||
1461 | if ( nInsPos != 0 && NeedsHardspaceAutocorr( rTxt[ nInsPos - 1 ] ) && | |||
1462 | cChar != ' ' && cChar != '\t' && cChar != cNonBreakingSpace ) | |||
1463 | { | |||
1464 | // Look for the last HARD_SPACE | |||
1465 | sal_Int32 nPos = nInsPos - 1; | |||
1466 | bool bContinue = true; | |||
1467 | while ( bContinue ) | |||
1468 | { | |||
1469 | const sal_Unicode cTmpChar = rTxt[ nPos ]; | |||
1470 | if ( cTmpChar == cNonBreakingSpace ) | |||
1471 | { | |||
1472 | rDoc.Delete( nPos, nPos + 1 ); | |||
1473 | bContinue = false; | |||
1474 | } | |||
1475 | else if ( !NeedsHardspaceAutocorr( cTmpChar ) || nPos == 0 ) | |||
1476 | bContinue = false; | |||
1477 | nPos--; | |||
1478 | } | |||
1479 | } | |||
1480 | } | |||
1481 | } | |||
1482 | } | |||
1483 | ||||
1484 | if( !nInsPos ) | |||
1485 | break; | |||
1486 | ||||
1487 | sal_Int32 nPos = nInsPos - 1; | |||
1488 | ||||
1489 | if( IsWordDelim( rTxt[ nPos ])) | |||
1490 | break; | |||
1491 | ||||
1492 | // Set bold or underline automatically? | |||
1493 | if (('*' == cChar || '_' == cChar || '/' == cChar || '-' == cChar) && (nPos+1 < rTxt.getLength())) | |||
1494 | { | |||
1495 | if( IsAutoCorrFlag( ACFlags::ChgWeightUnderl ) ) | |||
1496 | { | |||
1497 | FnChgWeightUnderl( rDoc, rTxt, nPos+1 ); | |||
1498 | } | |||
1499 | break; | |||
1500 | } | |||
1501 | ||||
1502 | while( nPos && !IsWordDelim( rTxt[ --nPos ])) | |||
1503 | ; | |||
1504 | ||||
1505 | // Found a Paragraph-start or a Blank, search for the word shortcut in | |||
1506 | // auto. | |||
1507 | sal_Int32 nCapLttrPos = nPos+1; // on the 1st Character | |||
1508 | if( !nPos && !IsWordDelim( rTxt[ 0 ])) | |||
1509 | --nCapLttrPos; // begin of paragraph and no blank | |||
1510 | ||||
1511 | const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos ); | |||
1512 | CharClass& rCC = GetCharClass( eLang ); | |||
1513 | ||||
1514 | // no symbol characters | |||
1515 | if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nInsPos )) | |||
1516 | break; | |||
1517 | ||||
1518 | if( IsAutoCorrFlag( ACFlags::Autocorrect ) ) | |||
1519 | { | |||
1520 | // WARNING ATTENTION: rTxt is an alias of the text node's OUString | |||
1521 | // and becomes INVALID if ChgAutoCorrWord returns true! | |||
1522 | // => use aPara/pPara to create a valid copy of the string! | |||
1523 | OUString aPara; | |||
1524 | OUString* pPara = IsAutoCorrFlag(ACFlags::CapitalStartSentence) ? &aPara : nullptr; | |||
1525 | ||||
1526 | bool bChgWord = rDoc.ChgAutoCorrWord( nCapLttrPos, nInsPos, | |||
1527 | *this, pPara ); | |||
1528 | if( !bChgWord ) | |||
1529 | { | |||
1530 | sal_Int32 nCapLttrPos1 = nCapLttrPos, nInsPos1 = nInsPos; | |||
1531 | while( nCapLttrPos1 < nInsPos && | |||
1532 | lcl_IsInAsciiArr( sImplSttSkipChars, rTxt[ nCapLttrPos1 ] ) | |||
1533 | ) | |||
1534 | ++nCapLttrPos1; | |||
1535 | while( nCapLttrPos1 < nInsPos1 && nInsPos1 && | |||
1536 | lcl_IsInAsciiArr( sImplEndSkipChars, rTxt[ nInsPos1-1 ] ) | |||
1537 | ) | |||
1538 | --nInsPos1; | |||
1539 | ||||
1540 | if( (nCapLttrPos1 != nCapLttrPos || nInsPos1 != nInsPos ) && | |||
1541 | nCapLttrPos1 < nInsPos1 && | |||
1542 | rDoc.ChgAutoCorrWord( nCapLttrPos1, nInsPos1, *this, pPara )) | |||
1543 | { | |||
1544 | bChgWord = true; | |||
1545 | nCapLttrPos = nCapLttrPos1; | |||
1546 | } | |||
1547 | } | |||
1548 | ||||
1549 | if( bChgWord ) | |||
1550 | { | |||
1551 | if( !aPara.isEmpty() ) | |||
1552 | { | |||
1553 | sal_Int32 nEnd = nCapLttrPos; | |||
1554 | while( nEnd < aPara.getLength() && | |||
1555 | !IsWordDelim( aPara[ nEnd ])) | |||
1556 | ++nEnd; | |||
1557 | ||||
1558 | // Capital letter at beginning of paragraph? | |||
1559 | if( IsAutoCorrFlag( ACFlags::CapitalStartSentence ) ) | |||
1560 | { | |||
1561 | FnCapitalStartSentence( rDoc, aPara, false, | |||
1562 | nCapLttrPos, nEnd, eLang ); | |||
1563 | } | |||
1564 | ||||
1565 | if( IsAutoCorrFlag( ACFlags::ChgToEnEmDash ) ) | |||
1566 | { | |||
1567 | FnChgToEnEmDash( rDoc, aPara, nCapLttrPos, nEnd, eLang ); | |||
1568 | } | |||
1569 | } | |||
1570 | break; | |||
1571 | } | |||
1572 | } | |||
1573 | ||||
1574 | if( IsAutoCorrFlag( ACFlags::TransliterateRTL ) && GetDocLanguage( rDoc, nInsPos ) == LANGUAGE_HUNGARIANLanguageType(0x040E) ) | |||
1575 | { | |||
1576 | // WARNING ATTENTION: rTxt is an alias of the text node's OUString | |||
1577 | // and becomes INVALID if TransliterateRTLWord returns true! | |||
1578 | if ( rDoc.TransliterateRTLWord( nCapLttrPos, nInsPos ) ) | |||
1579 | break; | |||
1580 | } | |||
1581 | ||||
1582 | if( ( IsAutoCorrFlag( ACFlags::ChgOrdinalNumber ) && | |||
1583 | (nInsPos >= 2 ) && // fdo#69762 avoid autocorrect for 2e-3 | |||
1584 | ( '-' != cChar || 'E' != rtl::toAsciiUpperCase(rTxt[nInsPos-1]) || '0' > rTxt[nInsPos-2] || '9' < rTxt[nInsPos-2] ) && | |||
1585 | FnChgOrdinalNumber( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) || | |||
1586 | ( IsAutoCorrFlag( ACFlags::SetINetAttr ) && | |||
1587 | ( ' ' == cChar || '\t' == cChar || 0x0a == cChar || !cChar ) && | |||
1588 | FnSetINetAttr( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) ) | |||
1589 | ; | |||
1590 | else | |||
1591 | { | |||
1592 | bool bLockKeyOn = pFrameWin && (pFrameWin->GetIndicatorState() & KeyIndicatorState::CAPSLOCK); | |||
1593 | bool bUnsupported = lcl_IsUnsupportedUnicodeChar( rCC, rTxt, nCapLttrPos, nInsPos ); | |||
1594 | ||||
1595 | if ( bLockKeyOn && IsAutoCorrFlag( ACFlags::CorrectCapsLock ) && | |||
1596 | FnCorrectCapsLock( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) | |||
1597 | { | |||
1598 | // Correct accidental use of cAPS LOCK key (do this only when | |||
1599 | // the caps or shift lock key is pressed). Turn off the caps | |||
1600 | // lock afterwards. | |||
1601 | pFrameWin->SimulateKeyPress( KEY_CAPSLOCK ); | |||
1602 | } | |||
1603 | ||||
1604 | // Capital letter at beginning of paragraph ? | |||
1605 | if( !bUnsupported && | |||
1606 | IsAutoCorrFlag( ACFlags::CapitalStartSentence ) ) | |||
1607 | { | |||
1608 | FnCapitalStartSentence( rDoc, rTxt, true, nCapLttrPos, nInsPos, eLang ); | |||
1609 | } | |||
1610 | ||||
1611 | // Two capital letters at beginning of word ?? | |||
1612 | if( !bUnsupported && | |||
1613 | IsAutoCorrFlag( ACFlags::CapitalStartWord ) ) | |||
1614 | { | |||
1615 | FnCapitalStartWord( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ); | |||
1616 | } | |||
1617 | ||||
1618 | if( IsAutoCorrFlag( ACFlags::ChgToEnEmDash ) ) | |||
1619 | { | |||
1620 | FnChgToEnEmDash( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ); | |||
1621 | } | |||
1622 | } | |||
1623 | ||||
1624 | } while( false ); | |||
1625 | } | |||
1626 | ||||
1627 | SvxAutoCorrectLanguageLists& SvxAutoCorrect::GetLanguageList_( | |||
1628 | LanguageType eLang ) | |||
1629 | { | |||
1630 | LanguageTag aLanguageTag( eLang); | |||
1631 | if (m_aLangTable.find(aLanguageTag) == m_aLangTable.end()) | |||
1632 | (void)CreateLanguageFile(aLanguageTag); | |||
1633 | return *(m_aLangTable.find(aLanguageTag)->second); | |||
1634 | } | |||
1635 | ||||
1636 | void SvxAutoCorrect::SaveCplSttExceptList( LanguageType eLang ) | |||
1637 | { | |||
1638 | auto const iter = m_aLangTable.find(LanguageTag(eLang)); | |||
1639 | if (iter != m_aLangTable.end() && iter->second) | |||
1640 | iter->second->SaveCplSttExceptList(); | |||
1641 | else | |||
1642 | { | |||
1643 | SAL_WARN("editeng", "Save an empty list? ")do { if (true) { switch (sal_detail_log_report(::SAL_DETAIL_LOG_LEVEL_WARN , "editeng")) { case SAL_DETAIL_LOG_ACTION_IGNORE: break; case SAL_DETAIL_LOG_ACTION_LOG: if (sizeof ::sal::detail::getResult ( ::sal::detail::StreamStart() << "Save an empty list? " ) == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("editeng" ), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "1643" ": "), ::sal::detail::unwrapStream( ::sal::detail ::StreamStart() << "Save an empty list? "), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream << "Save an empty list? "; ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN ), ("editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "1643" ": "), sal_detail_stream, 0); }; break; case SAL_DETAIL_LOG_ACTION_FATAL : if (sizeof ::sal::detail::getResult( ::sal::detail::StreamStart () << "Save an empty list? ") == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "1643" ": "), ::sal::detail::unwrapStream( ::sal::detail ::StreamStart() << "Save an empty list? "), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream << "Save an empty list? "; ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN ), ("editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "1643" ": "), sal_detail_stream, 0); }; std::abort(); break ; } } } while (false); | |||
1644 | } | |||
1645 | } | |||
1646 | ||||
1647 | void SvxAutoCorrect::SaveWrdSttExceptList(LanguageType eLang) | |||
1648 | { | |||
1649 | auto const iter = m_aLangTable.find(LanguageTag(eLang)); | |||
1650 | if (iter != m_aLangTable.end() && iter->second) | |||
1651 | iter->second->SaveWrdSttExceptList(); | |||
1652 | else | |||
1653 | { | |||
1654 | SAL_WARN("editeng", "Save an empty list? ")do { if (true) { switch (sal_detail_log_report(::SAL_DETAIL_LOG_LEVEL_WARN , "editeng")) { case SAL_DETAIL_LOG_ACTION_IGNORE: break; case SAL_DETAIL_LOG_ACTION_LOG: if (sizeof ::sal::detail::getResult ( ::sal::detail::StreamStart() << "Save an empty list? " ) == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("editeng" ), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "1654" ": "), ::sal::detail::unwrapStream( ::sal::detail ::StreamStart() << "Save an empty list? "), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream << "Save an empty list? "; ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN ), ("editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "1654" ": "), sal_detail_stream, 0); }; break; case SAL_DETAIL_LOG_ACTION_FATAL : if (sizeof ::sal::detail::getResult( ::sal::detail::StreamStart () << "Save an empty list? ") == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "1654" ": "), ::sal::detail::unwrapStream( ::sal::detail ::StreamStart() << "Save an empty list? "), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream << "Save an empty list? "; ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN ), ("editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "1654" ": "), sal_detail_stream, 0); }; std::abort(); break ; } } } while (false); | |||
1655 | } | |||
1656 | } | |||
1657 | ||||
1658 | // Adds a single word. The list will immediately be written to the file! | |||
1659 | bool SvxAutoCorrect::AddCplSttException( const OUString& rNew, | |||
1660 | LanguageType eLang ) | |||
1661 | { | |||
1662 | SvxAutoCorrectLanguageLists* pLists = nullptr; | |||
1663 | // either the right language is present or it will be this in the general list | |||
1664 | auto iter = m_aLangTable.find(LanguageTag(eLang)); | |||
1665 | if (iter != m_aLangTable.end()) | |||
1666 | pLists = iter->second.get(); | |||
1667 | else | |||
1668 | { | |||
1669 | LanguageTag aLangTagUndetermined( LANGUAGE_UNDETERMINEDLanguageType(0xFFF0)); | |||
1670 | iter = m_aLangTable.find(aLangTagUndetermined); | |||
1671 | if (iter != m_aLangTable.end()) | |||
1672 | pLists = iter->second.get(); | |||
1673 | else if(CreateLanguageFile(aLangTagUndetermined)) | |||
1674 | pLists = m_aLangTable.find(aLangTagUndetermined)->second.get(); | |||
1675 | } | |||
1676 | OSL_ENSURE(pLists, "No auto correction data")do { if (true && (!(pLists))) { sal_detail_logFormat( (SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "1676" ": "), "%s", "No auto correction data"); } } while (false); | |||
1677 | return pLists && pLists->AddToCplSttExceptList(rNew); | |||
1678 | } | |||
1679 | ||||
1680 | // Adds a single word. The list will immediately be written to the file! | |||
1681 | bool SvxAutoCorrect::AddWrtSttException( const OUString& rNew, | |||
1682 | LanguageType eLang ) | |||
1683 | { | |||
1684 | SvxAutoCorrectLanguageLists* pLists = nullptr; | |||
1685 | //either the right language is present or it is set in the general list | |||
1686 | auto iter = m_aLangTable.find(LanguageTag(eLang)); | |||
1687 | if (iter != m_aLangTable.end()) | |||
1688 | pLists = iter->second.get(); | |||
1689 | else | |||
1690 | { | |||
1691 | LanguageTag aLangTagUndetermined( LANGUAGE_UNDETERMINEDLanguageType(0xFFF0)); | |||
1692 | iter = m_aLangTable.find(aLangTagUndetermined); | |||
1693 | if (iter != m_aLangTable.end()) | |||
1694 | pLists = iter->second.get(); | |||
1695 | else if(CreateLanguageFile(aLangTagUndetermined)) | |||
1696 | pLists = m_aLangTable.find(aLangTagUndetermined)->second.get(); | |||
1697 | } | |||
1698 | OSL_ENSURE(pLists, "No auto correction file!")do { if (true && (!(pLists))) { sal_detail_logFormat( (SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "1698" ": "), "%s", "No auto correction file!"); } } while (false); | |||
1699 | return pLists && pLists->AddToWrdSttExceptList(rNew); | |||
1700 | } | |||
1701 | ||||
1702 | OUString SvxAutoCorrect::GetPrevAutoCorrWord(SvxAutoCorrDoc const& rDoc, const OUString& rTxt, | |||
1703 | sal_Int32 nPos) | |||
1704 | { | |||
1705 | OUString sRet; | |||
1706 | if( !nPos ) | |||
1707 | return sRet; | |||
1708 | ||||
1709 | sal_Int32 nEnd = nPos; | |||
1710 | ||||
1711 | // it must be followed by a blank or tab! | |||
1712 | if( ( nPos < rTxt.getLength() && | |||
1713 | !IsWordDelim( rTxt[ nPos ])) || | |||
1714 | IsWordDelim( rTxt[ --nPos ])) | |||
1715 | return sRet; | |||
1716 | ||||
1717 | while( nPos && !IsWordDelim( rTxt[ --nPos ])) | |||
1718 | ; | |||
1719 | ||||
1720 | // Found a Paragraph-start or a Blank, search for the word shortcut in | |||
1721 | // auto. | |||
1722 | sal_Int32 nCapLttrPos = nPos+1; // on the 1st Character | |||
1723 | if( !nPos && !IsWordDelim( rTxt[ 0 ])) | |||
1724 | --nCapLttrPos; // Beginning of paragraph and no Blank! | |||
1725 | ||||
1726 | while( lcl_IsInAsciiArr( sImplSttSkipChars, rTxt[ nCapLttrPos ]) ) | |||
1727 | if( ++nCapLttrPos >= nEnd ) | |||
1728 | return sRet; | |||
1729 | ||||
1730 | if( 3 > nEnd - nCapLttrPos ) | |||
1731 | return sRet; | |||
1732 | ||||
1733 | const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos ); | |||
1734 | ||||
1735 | CharClass& rCC = GetCharClass(eLang); | |||
1736 | ||||
1737 | if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nEnd )) | |||
1738 | return sRet; | |||
1739 | ||||
1740 | sRet = rTxt.copy( nCapLttrPos, nEnd - nCapLttrPos ); | |||
1741 | return sRet; | |||
1742 | } | |||
1743 | ||||
1744 | // static | |||
1745 | std::vector<OUString> SvxAutoCorrect::GetChunkForAutoText(const OUString& rTxt, | |||
1746 | const sal_Int32 nPos) | |||
1747 | { | |||
1748 | constexpr sal_Int32 nMinLen = 3; | |||
1749 | constexpr sal_Int32 nMaxLen = 9; | |||
1750 | std::vector<OUString> aRes; | |||
1751 | if (nPos >= nMinLen) | |||
1752 | { | |||
1753 | sal_Int32 nBegin = std::max<sal_Int32>(nPos - nMaxLen, 0); | |||
1754 | // TODO: better detect word boundaries (not only whitespaces, but also e.g. punctuation) | |||
1755 | if (nBegin > 0 && !IsWordDelim(rTxt[nBegin-1])) | |||
1756 | { | |||
1757 | while (nBegin + nMinLen <= nPos && !IsWordDelim(rTxt[nBegin])) | |||
1758 | ++nBegin; | |||
1759 | } | |||
1760 | if (nBegin + nMinLen <= nPos) | |||
1761 | { | |||
1762 | OUString sRes = rTxt.copy(nBegin, nPos - nBegin); | |||
1763 | aRes.push_back(sRes); | |||
1764 | bool bLastStartedWithDelim = IsWordDelim(sRes[0]); | |||
1765 | for (sal_Int32 i = 1; i <= sRes.getLength() - nMinLen; ++i) | |||
1766 | { | |||
1767 | bool bAdd = bLastStartedWithDelim; | |||
1768 | bLastStartedWithDelim = IsWordDelim(sRes[i]); | |||
1769 | bAdd = bAdd || bLastStartedWithDelim; | |||
1770 | if (bAdd) | |||
1771 | aRes.push_back(sRes.copy(i)); | |||
1772 | } | |||
1773 | } | |||
1774 | } | |||
1775 | return aRes; | |||
1776 | } | |||
1777 | ||||
1778 | bool SvxAutoCorrect::CreateLanguageFile( const LanguageTag& rLanguageTag, bool bNewFile ) | |||
1779 | { | |||
1780 | OSL_ENSURE(m_aLangTable.find(rLanguageTag) == m_aLangTable.end(), "Language already exists ")do { if (true && (!(m_aLangTable.find(rLanguageTag) == m_aLangTable.end()))) { sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN ), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "1780" ": "), "%s", "Language already exists "); } } while (false); | |||
1781 | ||||
1782 | OUString sUserDirFile( GetAutoCorrFileName( rLanguageTag, true )); | |||
1783 | OUString sShareDirFile( sUserDirFile ); | |||
1784 | ||||
1785 | SvxAutoCorrectLanguageLists* pLists = nullptr; | |||
1786 | ||||
1787 | tools::Time nMinTime( 0, 2 ), nAktTime( tools::Time::SYSTEM ), nLastCheckTime( tools::Time::EMPTY ); | |||
1788 | ||||
1789 | auto nFndPos = aLastFileTable.find(rLanguageTag); | |||
1790 | if(nFndPos != aLastFileTable.end() && | |||
1791 | (nLastCheckTime.SetTime(nFndPos->second), nLastCheckTime < nAktTime) && | |||
1792 | nAktTime - nLastCheckTime < nMinTime) | |||
1793 | { | |||
1794 | // no need to test the file, because the last check is not older then | |||
1795 | // 2 minutes. | |||
1796 | if( bNewFile ) | |||
1797 | { | |||
1798 | sShareDirFile = sUserDirFile; | |||
1799 | pLists = new SvxAutoCorrectLanguageLists( *this, sShareDirFile, sUserDirFile ); | |||
1800 | LanguageTag aTmp(rLanguageTag); // this insert() needs a non-const reference | |||
1801 | m_aLangTable.insert(std::make_pair(aTmp, std::unique_ptr<SvxAutoCorrectLanguageLists>(pLists))); | |||
1802 | aLastFileTable.erase(nFndPos); | |||
1803 | } | |||
1804 | } | |||
1805 | else if( | |||
1806 | ( FStatHelper::IsDocument( sUserDirFile ) || | |||
1807 | FStatHelper::IsDocument( sShareDirFile = | |||
1808 | GetAutoCorrFileName( rLanguageTag ) ) || | |||
1809 | FStatHelper::IsDocument( sShareDirFile = | |||
1810 | GetAutoCorrFileName( rLanguageTag, false, false, true) ) | |||
1811 | ) || | |||
1812 | ( sShareDirFile = sUserDirFile, bNewFile ) | |||
1813 | ) | |||
1814 | { | |||
1815 | pLists = new SvxAutoCorrectLanguageLists( *this, sShareDirFile, sUserDirFile ); | |||
1816 | LanguageTag aTmp(rLanguageTag); // this insert() needs a non-const reference | |||
1817 | m_aLangTable.insert(std::make_pair(aTmp, std::unique_ptr<SvxAutoCorrectLanguageLists>(pLists))); | |||
1818 | if (nFndPos != aLastFileTable.end()) | |||
1819 | aLastFileTable.erase(nFndPos); | |||
1820 | } | |||
1821 | else if( !bNewFile ) | |||
1822 | { | |||
1823 | aLastFileTable[rLanguageTag] = nAktTime.GetTime(); | |||
1824 | } | |||
1825 | return pLists != nullptr; | |||
1826 | } | |||
1827 | ||||
1828 | bool SvxAutoCorrect::PutText( const OUString& rShort, const OUString& rLong, | |||
1829 | LanguageType eLang ) | |||
1830 | { | |||
1831 | LanguageTag aLanguageTag( eLang); | |||
1832 | auto const iter = m_aLangTable.find(aLanguageTag); | |||
1833 | if (iter != m_aLangTable.end()) | |||
1834 | return iter->second->PutText(rShort, rLong); | |||
1835 | if(CreateLanguageFile(aLanguageTag)) | |||
1836 | return m_aLangTable.find(aLanguageTag)->second->PutText(rShort, rLong); | |||
1837 | return false; | |||
1838 | } | |||
1839 | ||||
1840 | void SvxAutoCorrect::MakeCombinedChanges( std::vector<SvxAutocorrWord>& aNewEntries, | |||
1841 | std::vector<SvxAutocorrWord>& aDeleteEntries, | |||
1842 | LanguageType eLang ) | |||
1843 | { | |||
1844 | LanguageTag aLanguageTag( eLang); | |||
1845 | auto const iter = m_aLangTable.find(aLanguageTag); | |||
1846 | if (iter != m_aLangTable.end()) | |||
| ||||
1847 | { | |||
1848 | iter->second->MakeCombinedChanges( aNewEntries, aDeleteEntries ); | |||
1849 | } | |||
1850 | else if(CreateLanguageFile( aLanguageTag )) | |||
1851 | { | |||
1852 | m_aLangTable.find( aLanguageTag )->second->MakeCombinedChanges( aNewEntries, aDeleteEntries ); | |||
1853 | } | |||
1854 | } | |||
1855 | ||||
1856 | // - return the replacement text (only for SWG-Format, all other | |||
1857 | // can be taken from the word list!) | |||
1858 | bool SvxAutoCorrect::GetLongText( const OUString&, OUString& ) | |||
1859 | { | |||
1860 | return false; | |||
1861 | } | |||
1862 | ||||
1863 | void SvxAutoCorrect::refreshBlockList( const uno::Reference< embed::XStorage >& ) | |||
1864 | { | |||
1865 | } | |||
1866 | ||||
1867 | // Text with attribution (only the SWG - SWG format!) | |||
1868 | bool SvxAutoCorrect::PutText( const css::uno::Reference < css::embed::XStorage >&, | |||
1869 | const OUString&, const OUString&, SfxObjectShell&, OUString& ) | |||
1870 | { | |||
1871 | return false; | |||
1872 | } | |||
1873 | ||||
1874 | OUString EncryptBlockName_Imp(const OUString& rName) | |||
1875 | { | |||
1876 | OUStringBuffer aName; | |||
1877 | aName.append('#').append(rName); | |||
1878 | for (sal_Int32 nLen = rName.getLength(), nPos = 1; nPos < nLen; ++nPos) | |||
1879 | { | |||
1880 | if (lcl_IsInAsciiArr( "!/:.\\", aName[nPos])) | |||
1881 | aName[nPos] &= 0x0f; | |||
1882 | } | |||
1883 | return aName.makeStringAndClear(); | |||
1884 | } | |||
1885 | ||||
1886 | /* This code is copied from SwXMLTextBlocks::GeneratePackageName */ | |||
1887 | static void GeneratePackageName ( const OUString& rShort, OUString& rPackageName ) | |||
1888 | { | |||
1889 | OString sByte(OUStringToOString(rShort, RTL_TEXTENCODING_UTF7(((rtl_TextEncoding) 75)))); | |||
1890 | OUStringBuffer aBuf(OStringToOUString(sByte, RTL_TEXTENCODING_ASCII_US(((rtl_TextEncoding) 11)))); | |||
1891 | ||||
1892 | for (sal_Int32 nPos = 0; nPos < aBuf.getLength(); ++nPos) | |||
1893 | { | |||
1894 | switch (aBuf[nPos]) | |||
1895 | { | |||
1896 | case '!': | |||
1897 | case '/': | |||
1898 | case ':': | |||
1899 | case '.': | |||
1900 | case '\\': | |||
1901 | aBuf[nPos] = '_'; | |||
1902 | break; | |||
1903 | default: | |||
1904 | break; | |||
1905 | } | |||
1906 | } | |||
1907 | ||||
1908 | rPackageName = aBuf.makeStringAndClear(); | |||
1909 | } | |||
1910 | ||||
1911 | static const SvxAutocorrWord* lcl_SearchWordsInList( | |||
1912 | SvxAutoCorrectLanguageLists* pList, const OUString& rTxt, | |||
1913 | sal_Int32& rStt, sal_Int32 nEndPos) | |||
1914 | { | |||
1915 | const SvxAutocorrWordList* pAutoCorrWordList = pList->GetAutocorrWordList(); | |||
1916 | return pAutoCorrWordList->SearchWordsInList( rTxt, rStt, nEndPos ); | |||
1917 | } | |||
1918 | ||||
1919 | // the search for the words in the substitution table | |||
1920 | const SvxAutocorrWord* SvxAutoCorrect::SearchWordsInList( | |||
1921 | const OUString& rTxt, sal_Int32& rStt, sal_Int32 nEndPos, | |||
1922 | SvxAutoCorrDoc&, LanguageTag& rLang ) | |||
1923 | { | |||
1924 | const SvxAutocorrWord* pRet = nullptr; | |||
1925 | LanguageTag aLanguageTag( rLang); | |||
1926 | if( aLanguageTag.isSystemLocale() ) | |||
1927 | aLanguageTag.reset( MsLangId::getSystemLanguage()); | |||
1928 | ||||
1929 | /* TODO-BCP47: this is so ugly, should all maybe be a proper fallback | |||
1930 | * list instead? */ | |||
1931 | ||||
1932 | // First search for eLang, then US-English -> English | |||
1933 | // and last in LANGUAGE_UNDETERMINED | |||
1934 | if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) | |||
1935 | { | |||
1936 | //the language is available - so bring it on | |||
1937 | std::unique_ptr<SvxAutoCorrectLanguageLists> const& pList = m_aLangTable.find(aLanguageTag)->second; | |||
1938 | pRet = lcl_SearchWordsInList( pList.get(), rTxt, rStt, nEndPos ); | |||
1939 | if( pRet ) | |||
1940 | { | |||
1941 | rLang = aLanguageTag; | |||
1942 | return pRet; | |||
1943 | } | |||
1944 | } | |||
1945 | ||||
1946 | // If it still could not be found here, then keep on searching | |||
1947 | LanguageType eLang = aLanguageTag.getLanguageType(); | |||
1948 | // the primary language for example EN | |||
1949 | aLanguageTag.reset(aLanguageTag.getLanguage()); | |||
1950 | LanguageType nTmpKey = aLanguageTag.getLanguageType(false); | |||
1951 | if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINEDLanguageType(0xFFF0) && | |||
1952 | (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || | |||
1953 | CreateLanguageFile(aLanguageTag, false))) | |||
1954 | { | |||
1955 | //the language is available - so bring it on | |||
1956 | std::unique_ptr<SvxAutoCorrectLanguageLists> const& pList = m_aLangTable.find(aLanguageTag)->second; | |||
1957 | pRet = lcl_SearchWordsInList( pList.get(), rTxt, rStt, nEndPos ); | |||
1958 | if( pRet ) | |||
1959 | { | |||
1960 | rLang = aLanguageTag; | |||
1961 | return pRet; | |||
1962 | } | |||
1963 | } | |||
1964 | ||||
1965 | if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINEDLanguageType(0xFFF0))) != m_aLangTable.end() || | |||
1966 | CreateLanguageFile(aLanguageTag, false)) | |||
1967 | { | |||
1968 | //the language is available - so bring it on | |||
1969 | std::unique_ptr<SvxAutoCorrectLanguageLists> const& pList = m_aLangTable.find(aLanguageTag)->second; | |||
1970 | pRet = lcl_SearchWordsInList( pList.get(), rTxt, rStt, nEndPos ); | |||
1971 | if( pRet ) | |||
1972 | { | |||
1973 | rLang = aLanguageTag; | |||
1974 | return pRet; | |||
1975 | } | |||
1976 | } | |||
1977 | return nullptr; | |||
1978 | } | |||
1979 | ||||
1980 | bool SvxAutoCorrect::FindInWrdSttExceptList( LanguageType eLang, | |||
1981 | const OUString& sWord ) | |||
1982 | { | |||
1983 | LanguageTag aLanguageTag( eLang); | |||
1984 | ||||
1985 | /* TODO-BCP47: again horrible ugliness */ | |||
1986 | ||||
1987 | // First search for eLang, then primary language of eLang | |||
1988 | // and last in LANGUAGE_UNDETERMINED | |||
1989 | ||||
1990 | if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) | |||
1991 | { | |||
1992 | //the language is available - so bring it on | |||
1993 | auto const& pList = m_aLangTable.find(aLanguageTag)->second; | |||
1994 | if(pList->GetWrdSttExceptList()->find(sWord) != pList->GetWrdSttExceptList()->end() ) | |||
1995 | return true; | |||
1996 | } | |||
1997 | ||||
1998 | // If it still could not be found here, then keep on searching | |||
1999 | // the primary language for example EN | |||
2000 | aLanguageTag.reset(aLanguageTag.getLanguage()); | |||
2001 | LanguageType nTmpKey = aLanguageTag.getLanguageType(false); | |||
2002 | if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINEDLanguageType(0xFFF0) && | |||
2003 | (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || | |||
2004 | CreateLanguageFile(aLanguageTag, false))) | |||
2005 | { | |||
2006 | //the language is available - so bring it on | |||
2007 | auto const& pList = m_aLangTable.find(aLanguageTag)->second; | |||
2008 | if(pList->GetWrdSttExceptList()->find(sWord) != pList->GetWrdSttExceptList()->end() ) | |||
2009 | return true; | |||
2010 | } | |||
2011 | ||||
2012 | if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINEDLanguageType(0xFFF0))) != m_aLangTable.end() || | |||
2013 | CreateLanguageFile(aLanguageTag, false)) | |||
2014 | { | |||
2015 | //the language is available - so bring it on | |||
2016 | auto const& pList = m_aLangTable.find(aLanguageTag)->second; | |||
2017 | if(pList->GetWrdSttExceptList()->find(sWord) != pList->GetWrdSttExceptList()->end() ) | |||
2018 | return true; | |||
2019 | } | |||
2020 | return false; | |||
2021 | } | |||
2022 | ||||
2023 | static bool lcl_FindAbbreviation(const SvStringsISortDtor* pList, const OUString& sWord) | |||
2024 | { | |||
2025 | SvStringsISortDtor::const_iterator it = pList->find( "~" ); | |||
2026 | SvStringsISortDtor::size_type nPos = it - pList->begin(); | |||
2027 | if( nPos < pList->size() ) | |||
2028 | { | |||
2029 | OUString sLowerWord(sWord.toAsciiLowerCase()); | |||
2030 | OUString sAbr; | |||
2031 | for( SvStringsISortDtor::size_type n = nPos; n < pList->size(); ++n ) | |||
2032 | { | |||
2033 | sAbr = (*pList)[ n ]; | |||
2034 | if (sAbr[0] != '~') | |||
2035 | break; | |||
2036 | // ~ and ~. are not allowed! | |||
2037 | if( 2 < sAbr.getLength() && sAbr.getLength() - 1 <= sWord.getLength() ) | |||
2038 | { | |||
2039 | OUString sLowerAbk(sAbr.toAsciiLowerCase()); | |||
2040 | for (sal_Int32 i = sLowerAbk.getLength(), ii = sLowerWord.getLength(); i;) | |||
2041 | { | |||
2042 | if( !--i ) // agrees | |||
2043 | return true; | |||
2044 | ||||
2045 | if( sLowerAbk[i] != sLowerWord[--ii]) | |||
2046 | break; | |||
2047 | } | |||
2048 | } | |||
2049 | } | |||
2050 | } | |||
2051 | OSL_ENSURE( !(nPos && '~' == (*pList)[ --nPos ][ 0 ] ),do { if (true && (!(!(nPos && '~' == (*pList) [ --nPos ][ 0 ] )))) { sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN ), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2052" ": "), "%s", "Wrongly sorted exception list?"); } } while (false) | |||
2052 | "Wrongly sorted exception list?" )do { if (true && (!(!(nPos && '~' == (*pList) [ --nPos ][ 0 ] )))) { sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN ), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2052" ": "), "%s", "Wrongly sorted exception list?"); } } while (false); | |||
2053 | return false; | |||
2054 | } | |||
2055 | ||||
2056 | bool SvxAutoCorrect::FindInCplSttExceptList(LanguageType eLang, | |||
2057 | const OUString& sWord, bool bAbbreviation) | |||
2058 | { | |||
2059 | LanguageTag aLanguageTag( eLang); | |||
2060 | ||||
2061 | /* TODO-BCP47: did I mention terrible horrible ugliness? */ | |||
2062 | ||||
2063 | // First search for eLang, then primary language of eLang | |||
2064 | // and last in LANGUAGE_UNDETERMINED | |||
2065 | ||||
2066 | if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) | |||
2067 | { | |||
2068 | //the language is available - so bring it on | |||
2069 | const SvStringsISortDtor* pList = m_aLangTable.find(aLanguageTag)->second->GetCplSttExceptList(); | |||
2070 | if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) | |||
2071 | return true; | |||
2072 | } | |||
2073 | ||||
2074 | // If it still could not be found here, then keep on searching | |||
2075 | // the primary language for example EN | |||
2076 | aLanguageTag.reset(aLanguageTag.getLanguage()); | |||
2077 | LanguageType nTmpKey = aLanguageTag.getLanguageType(false); | |||
2078 | if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINEDLanguageType(0xFFF0) && | |||
2079 | (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || | |||
2080 | CreateLanguageFile(aLanguageTag, false))) | |||
2081 | { | |||
2082 | //the language is available - so bring it on | |||
2083 | const SvStringsISortDtor* pList = m_aLangTable.find(aLanguageTag)->second->GetCplSttExceptList(); | |||
2084 | if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) | |||
2085 | return true; | |||
2086 | } | |||
2087 | ||||
2088 | if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINEDLanguageType(0xFFF0))) != m_aLangTable.end() || | |||
2089 | CreateLanguageFile(aLanguageTag, false)) | |||
2090 | { | |||
2091 | //the language is available - so bring it on | |||
2092 | const SvStringsISortDtor* pList = m_aLangTable.find(aLanguageTag)->second->GetCplSttExceptList(); | |||
2093 | if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) | |||
2094 | return true; | |||
2095 | } | |||
2096 | return false; | |||
2097 | } | |||
2098 | ||||
2099 | OUString SvxAutoCorrect::GetAutoCorrFileName( const LanguageTag& rLanguageTag, | |||
2100 | bool bNewFile, bool bTst, bool bUnlocalized ) const | |||
2101 | { | |||
2102 | OUString sRet, sExt( rLanguageTag.getBcp47() ); | |||
2103 | if (bUnlocalized) | |||
2104 | { | |||
2105 | // we don't want variant, so we'll take "fr" instead of "fr-CA" for example | |||
2106 | std::vector< OUString > vecFallBackStrings = rLanguageTag.getFallbackStrings(false); | |||
2107 | if (!vecFallBackStrings.empty()) | |||
2108 | sExt = vecFallBackStrings[0]; | |||
2109 | } | |||
2110 | ||||
2111 | sExt = "_" + sExt + ".dat"; | |||
2112 | if( bNewFile ) | |||
2113 | sRet = sUserAutoCorrFile + sExt; | |||
2114 | else if( !bTst ) | |||
2115 | sRet = sShareAutoCorrFile + sExt; | |||
2116 | else | |||
2117 | { | |||
2118 | // test first in the user directory - if not exist, then | |||
2119 | sRet = sUserAutoCorrFile + sExt; | |||
2120 | if( !FStatHelper::IsDocument( sRet )) | |||
2121 | sRet = sShareAutoCorrFile + sExt; | |||
2122 | } | |||
2123 | return sRet; | |||
2124 | } | |||
2125 | ||||
2126 | SvxAutoCorrectLanguageLists::SvxAutoCorrectLanguageLists( | |||
2127 | SvxAutoCorrect& rParent, | |||
2128 | const OUString& rShareAutoCorrectFile, | |||
2129 | const OUString& rUserAutoCorrectFile) | |||
2130 | : sShareAutoCorrFile( rShareAutoCorrectFile ), | |||
2131 | sUserAutoCorrFile( rUserAutoCorrectFile ), | |||
2132 | aModifiedDate( Date::EMPTY ), | |||
2133 | aModifiedTime( tools::Time::EMPTY ), | |||
2134 | aLastCheckTime( tools::Time::EMPTY ), | |||
2135 | rAutoCorrect(rParent), | |||
2136 | nFlags(ACFlags::NONE) | |||
2137 | { | |||
2138 | } | |||
2139 | ||||
2140 | SvxAutoCorrectLanguageLists::~SvxAutoCorrectLanguageLists() | |||
2141 | { | |||
2142 | } | |||
2143 | ||||
2144 | bool SvxAutoCorrectLanguageLists::IsFileChanged_Imp() | |||
2145 | { | |||
2146 | // Access the file system only every 2 minutes to check the date stamp | |||
2147 | bool bRet = false; | |||
2148 | ||||
2149 | tools::Time nMinTime( 0, 2 ); | |||
2150 | tools::Time nAktTime( tools::Time::SYSTEM ); | |||
2151 | if( aLastCheckTime <= nAktTime) // overflow? | |||
2152 | return false; | |||
2153 | nAktTime -= aLastCheckTime; | |||
2154 | if( nAktTime > nMinTime ) // min time past | |||
2155 | { | |||
2156 | Date aTstDate( Date::EMPTY ); tools::Time aTstTime( tools::Time::EMPTY ); | |||
2157 | if( FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, | |||
2158 | &aTstDate, &aTstTime ) && | |||
2159 | ( aModifiedDate != aTstDate || aModifiedTime != aTstTime )) | |||
2160 | { | |||
2161 | bRet = true; | |||
2162 | // then remove all the lists fast! | |||
2163 | if( (ACFlags::CplSttLstLoad & nFlags) && pCplStt_ExcptLst ) | |||
2164 | { | |||
2165 | pCplStt_ExcptLst.reset(); | |||
2166 | } | |||
2167 | if( (ACFlags::WrdSttLstLoad & nFlags) && pWrdStt_ExcptLst ) | |||
2168 | { | |||
2169 | pWrdStt_ExcptLst.reset(); | |||
2170 | } | |||
2171 | if( (ACFlags::ChgWordLstLoad & nFlags) && pAutocorr_List ) | |||
2172 | { | |||
2173 | pAutocorr_List.reset(); | |||
2174 | } | |||
2175 | nFlags &= ~ACFlags(ACFlags::CplSttLstLoad | ACFlags::WrdSttLstLoad | ACFlags::ChgWordLstLoad ); | |||
2176 | } | |||
2177 | aLastCheckTime = tools::Time( tools::Time::SYSTEM ); | |||
2178 | } | |||
2179 | return bRet; | |||
2180 | } | |||
2181 | ||||
2182 | void SvxAutoCorrectLanguageLists::LoadXMLExceptList_Imp( | |||
2183 | std::unique_ptr<SvStringsISortDtor>& rpLst, | |||
2184 | const char* pStrmName, | |||
2185 | tools::SvRef<SotStorage>& rStg) | |||
2186 | { | |||
2187 | if( rpLst ) | |||
2188 | rpLst->clear(); | |||
2189 | else | |||
2190 | rpLst.reset( new SvStringsISortDtor ); | |||
2191 | ||||
2192 | { | |||
2193 | const OUString sStrmName( pStrmName, strlen(pStrmName), RTL_TEXTENCODING_MS_1252(((rtl_TextEncoding) 1)) ); | |||
2194 | ||||
2195 | if( rStg.is() && rStg->IsStream( sStrmName ) ) | |||
2196 | { | |||
2197 | tools::SvRef<SotStorageStream> xStrm = rStg->OpenSotStream( sStrmName, | |||
2198 | ( StreamMode::READ | StreamMode::SHARE_DENYWRITE | StreamMode::NOCREATE ) ); | |||
2199 | if( ERRCODE_NONEErrCode(0) != xStrm->GetError()) | |||
2200 | { | |||
2201 | xStrm.clear(); | |||
2202 | rStg.clear(); | |||
2203 | RemoveStream_Imp( sStrmName ); | |||
2204 | } | |||
2205 | else | |||
2206 | { | |||
2207 | uno::Reference< uno::XComponentContext > xContext = | |||
2208 | comphelper::getProcessComponentContext(); | |||
2209 | ||||
2210 | xml::sax::InputSource aParserInput; | |||
2211 | aParserInput.sSystemId = sStrmName; | |||
2212 | ||||
2213 | xStrm->Seek( 0 ); | |||
2214 | xStrm->SetBufferSize( 8 * 1024 ); | |||
2215 | aParserInput.aInputStream = new utl::OInputStreamWrapper( *xStrm ); | |||
2216 | ||||
2217 | // get filter | |||
2218 | rtl::Reference< SvXMLExceptionListImport > xImport = new SvXMLExceptionListImport ( xContext, *rpLst ); | |||
2219 | ||||
2220 | // connect parser and filter | |||
2221 | uno::Reference<xml::sax::XFastTokenHandler> xTokenHandler = new SvXMLAutoCorrectTokenHandler; | |||
2222 | xImport->setTokenHandler( xTokenHandler ); | |||
2223 | ||||
2224 | // parse | |||
2225 | try | |||
2226 | { | |||
2227 | xImport->parseStream( aParserInput ); | |||
2228 | } | |||
2229 | catch( const xml::sax::SAXParseException& ) | |||
2230 | { | |||
2231 | // re throw ? | |||
2232 | } | |||
2233 | catch( const xml::sax::SAXException& ) | |||
2234 | { | |||
2235 | // re throw ? | |||
2236 | } | |||
2237 | catch( const io::IOException& ) | |||
2238 | { | |||
2239 | // re throw ? | |||
2240 | } | |||
2241 | } | |||
2242 | } | |||
2243 | ||||
2244 | // Set time stamp | |||
2245 | FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, | |||
2246 | &aModifiedDate, &aModifiedTime ); | |||
2247 | aLastCheckTime = tools::Time( tools::Time::SYSTEM ); | |||
2248 | } | |||
2249 | ||||
2250 | } | |||
2251 | ||||
2252 | void SvxAutoCorrectLanguageLists::SaveExceptList_Imp( | |||
2253 | const SvStringsISortDtor& rLst, | |||
2254 | const char* pStrmName, | |||
2255 | tools::SvRef<SotStorage> const &rStg, | |||
2256 | bool bConvert ) | |||
2257 | { | |||
2258 | if( !rStg.is() ) | |||
2259 | return; | |||
2260 | ||||
2261 | OUString sStrmName( pStrmName, strlen(pStrmName), RTL_TEXTENCODING_MS_1252(((rtl_TextEncoding) 1)) ); | |||
2262 | if( rLst.empty() ) | |||
2263 | { | |||
2264 | rStg->Remove( sStrmName ); | |||
2265 | rStg->Commit(); | |||
2266 | } | |||
2267 | else | |||
2268 | { | |||
2269 | tools::SvRef<SotStorageStream> xStrm = rStg->OpenSotStream( sStrmName, | |||
2270 | ( StreamMode::READ | StreamMode::WRITE | StreamMode::SHARE_DENYWRITE ) ); | |||
2271 | if( xStrm.is() ) | |||
2272 | { | |||
2273 | xStrm->SetSize( 0 ); | |||
2274 | xStrm->SetBufferSize( 8192 ); | |||
2275 | xStrm->SetProperty( "MediaType", Any(OUString( "text/xml" )) ); | |||
2276 | ||||
2277 | ||||
2278 | uno::Reference< uno::XComponentContext > xContext = | |||
2279 | comphelper::getProcessComponentContext(); | |||
2280 | ||||
2281 | uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext); | |||
2282 | uno::Reference < io::XOutputStream> xOut = new utl::OOutputStreamWrapper( *xStrm ); | |||
2283 | xWriter->setOutputStream(xOut); | |||
2284 | ||||
2285 | uno::Reference < xml::sax::XDocumentHandler > xHandler(xWriter, UNO_QUERY_THROW); | |||
2286 | rtl::Reference< SvXMLExceptionListExport > xExp( new SvXMLExceptionListExport( xContext, rLst, sStrmName, xHandler ) ); | |||
2287 | ||||
2288 | xExp->exportDoc( XML_BLOCK_LIST ); | |||
2289 | ||||
2290 | xStrm->Commit(); | |||
2291 | if( xStrm->GetError() == ERRCODE_NONEErrCode(0) ) | |||
2292 | { | |||
2293 | xStrm.clear(); | |||
2294 | if (!bConvert) | |||
2295 | { | |||
2296 | rStg->Commit(); | |||
2297 | if( ERRCODE_NONEErrCode(0) != rStg->GetError() ) | |||
2298 | { | |||
2299 | rStg->Remove( sStrmName ); | |||
2300 | rStg->Commit(); | |||
2301 | } | |||
2302 | } | |||
2303 | } | |||
2304 | } | |||
2305 | } | |||
2306 | } | |||
2307 | ||||
2308 | SvxAutocorrWordList* SvxAutoCorrectLanguageLists::LoadAutocorrWordList() | |||
2309 | { | |||
2310 | if( pAutocorr_List ) | |||
2311 | pAutocorr_List->DeleteAndDestroyAll(); | |||
2312 | else | |||
2313 | pAutocorr_List.reset( new SvxAutocorrWordList() ); | |||
2314 | ||||
2315 | try | |||
2316 | { | |||
2317 | uno::Reference < embed::XStorage > xStg = comphelper::OStorageHelper::GetStorageFromURL( sShareAutoCorrFile, embed::ElementModes::READ ); | |||
2318 | uno::Reference < io::XStream > xStrm = xStg->openStreamElement( pXMLImplAutocorr_ListStr, embed::ElementModes::READ ); | |||
2319 | uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); | |||
2320 | ||||
2321 | xml::sax::InputSource aParserInput; | |||
2322 | aParserInput.sSystemId = pXMLImplAutocorr_ListStr; | |||
2323 | aParserInput.aInputStream = xStrm->getInputStream(); | |||
2324 | ||||
2325 | // get parser | |||
2326 | uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create(xContext); | |||
2327 | SAL_INFO("editeng", "AutoCorrect Import" )do { if (true) { switch (sal_detail_log_report(::SAL_DETAIL_LOG_LEVEL_INFO , "editeng")) { case SAL_DETAIL_LOG_ACTION_IGNORE: break; case SAL_DETAIL_LOG_ACTION_LOG: if (sizeof ::sal::detail::getResult ( ::sal::detail::StreamStart() << "AutoCorrect Import") == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_INFO), ("editeng" ), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2327" ": "), ::sal::detail::unwrapStream( ::sal::detail ::StreamStart() << "AutoCorrect Import"), 0); } else { :: std::ostringstream sal_detail_stream; sal_detail_stream << "AutoCorrect Import"; ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_INFO ), ("editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2327" ": "), sal_detail_stream, 0); }; break; case SAL_DETAIL_LOG_ACTION_FATAL : if (sizeof ::sal::detail::getResult( ::sal::detail::StreamStart () << "AutoCorrect Import") == 1) { ::sal_detail_log( ( ::SAL_DETAIL_LOG_LEVEL_INFO), ("editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2327" ": "), ::sal::detail::unwrapStream( ::sal::detail ::StreamStart() << "AutoCorrect Import"), 0); } else { :: std::ostringstream sal_detail_stream; sal_detail_stream << "AutoCorrect Import"; ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_INFO ), ("editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2327" ": "), sal_detail_stream, 0); }; std::abort(); break ; } } } while (false); | |||
2328 | uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SvXMLAutoCorrectImport( xContext, pAutocorr_List.get(), rAutoCorrect, xStg ); | |||
2329 | uno::Reference<xml::sax::XFastTokenHandler> xTokenHandler = new SvXMLAutoCorrectTokenHandler; | |||
2330 | ||||
2331 | // connect parser and filter | |||
2332 | xParser->setFastDocumentHandler( xFilter ); | |||
2333 | xParser->registerNamespace( "http://openoffice.org/2001/block-list", SvXMLAutoCorrectToken::NAMESPACE ); | |||
2334 | xParser->setTokenHandler(xTokenHandler); | |||
2335 | ||||
2336 | // parse | |||
2337 | xParser->parseStream( aParserInput ); | |||
2338 | } | |||
2339 | catch ( const uno::Exception& ) | |||
2340 | { | |||
2341 | TOOLS_WARN_EXCEPTION("editeng", "when loading " << sShareAutoCorrFile)do { css::uno::Any tools_warn_exception( DbgGetCaughtException () ); do { if (true) { switch (sal_detail_log_report(::SAL_DETAIL_LOG_LEVEL_WARN , "editeng")) { case SAL_DETAIL_LOG_ACTION_IGNORE: break; case SAL_DETAIL_LOG_ACTION_LOG: if (sizeof ::sal::detail::getResult ( ::sal::detail::StreamStart() << "when loading " << sShareAutoCorrFile << " " << exceptionToString(tools_warn_exception )) == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ( "editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2341" ": "), ::sal::detail::unwrapStream( ::sal::detail ::StreamStart() << "when loading " << sShareAutoCorrFile << " " << exceptionToString(tools_warn_exception )), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream << "when loading " << sShareAutoCorrFile << " " << exceptionToString(tools_warn_exception); ::sal:: detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2341" ": "), sal_detail_stream, 0); }; break; case SAL_DETAIL_LOG_ACTION_FATAL : if (sizeof ::sal::detail::getResult( ::sal::detail::StreamStart () << "when loading " << sShareAutoCorrFile << " " << exceptionToString(tools_warn_exception)) == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("editeng") , ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2341" ": "), ::sal::detail::unwrapStream( ::sal::detail ::StreamStart() << "when loading " << sShareAutoCorrFile << " " << exceptionToString(tools_warn_exception )), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream << "when loading " << sShareAutoCorrFile << " " << exceptionToString(tools_warn_exception); ::sal:: detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2341" ": "), sal_detail_stream, 0); }; std::abort(); break ; } } } while (false); } while (false); | |||
2342 | } | |||
2343 | ||||
2344 | // Set time stamp | |||
2345 | FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, | |||
2346 | &aModifiedDate, &aModifiedTime ); | |||
2347 | aLastCheckTime = tools::Time( tools::Time::SYSTEM ); | |||
2348 | ||||
2349 | return pAutocorr_List.get(); | |||
2350 | } | |||
2351 | ||||
2352 | const SvxAutocorrWordList* SvxAutoCorrectLanguageLists::GetAutocorrWordList() | |||
2353 | { | |||
2354 | if( !( ACFlags::ChgWordLstLoad & nFlags ) || IsFileChanged_Imp() ) | |||
2355 | { | |||
2356 | LoadAutocorrWordList(); | |||
2357 | if( !pAutocorr_List ) | |||
2358 | { | |||
2359 | OSL_ENSURE( false, "No valid list" )do { if (true && (!(false))) { sal_detail_logFormat(( SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2359" ": "), "%s", "No valid list"); } } while (false); | |||
2360 | pAutocorr_List.reset( new SvxAutocorrWordList() ); | |||
2361 | } | |||
2362 | nFlags |= ACFlags::ChgWordLstLoad; | |||
2363 | } | |||
2364 | return pAutocorr_List.get(); | |||
2365 | } | |||
2366 | ||||
2367 | SvStringsISortDtor* SvxAutoCorrectLanguageLists::GetCplSttExceptList() | |||
2368 | { | |||
2369 | if( !( ACFlags::CplSttLstLoad & nFlags ) || IsFileChanged_Imp() ) | |||
2370 | { | |||
2371 | LoadCplSttExceptList(); | |||
2372 | if( !pCplStt_ExcptLst ) | |||
2373 | { | |||
2374 | OSL_ENSURE( false, "No valid list" )do { if (true && (!(false))) { sal_detail_logFormat(( SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2374" ": "), "%s", "No valid list"); } } while (false); | |||
2375 | pCplStt_ExcptLst.reset( new SvStringsISortDtor ); | |||
2376 | } | |||
2377 | nFlags |= ACFlags::CplSttLstLoad; | |||
2378 | } | |||
2379 | return pCplStt_ExcptLst.get(); | |||
2380 | } | |||
2381 | ||||
2382 | bool SvxAutoCorrectLanguageLists::AddToCplSttExceptList(const OUString& rNew) | |||
2383 | { | |||
2384 | bool bRet = false; | |||
2385 | if( !rNew.isEmpty() && GetCplSttExceptList()->insert( rNew ).second ) | |||
2386 | { | |||
2387 | MakeUserStorage_Impl(); | |||
2388 | tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); | |||
2389 | ||||
2390 | SaveExceptList_Imp( *pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); | |||
2391 | ||||
2392 | xStg = nullptr; | |||
2393 | // Set time stamp | |||
2394 | FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, | |||
2395 | &aModifiedDate, &aModifiedTime ); | |||
2396 | aLastCheckTime = tools::Time( tools::Time::SYSTEM ); | |||
2397 | bRet = true; | |||
2398 | } | |||
2399 | return bRet; | |||
2400 | } | |||
2401 | ||||
2402 | bool SvxAutoCorrectLanguageLists::AddToWrdSttExceptList(const OUString& rNew) | |||
2403 | { | |||
2404 | bool bRet = false; | |||
2405 | SvStringsISortDtor* pExceptList = LoadWrdSttExceptList(); | |||
2406 | if( !rNew.isEmpty() && pExceptList && pExceptList->insert( rNew ).second ) | |||
2407 | { | |||
2408 | MakeUserStorage_Impl(); | |||
2409 | tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); | |||
2410 | ||||
2411 | SaveExceptList_Imp( *pWrdStt_ExcptLst, pXMLImplWrdStt_ExcptLstStr, xStg ); | |||
2412 | ||||
2413 | xStg = nullptr; | |||
2414 | // Set time stamp | |||
2415 | FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, | |||
2416 | &aModifiedDate, &aModifiedTime ); | |||
2417 | aLastCheckTime = tools::Time( tools::Time::SYSTEM ); | |||
2418 | bRet = true; | |||
2419 | } | |||
2420 | return bRet; | |||
2421 | } | |||
2422 | ||||
2423 | SvStringsISortDtor* SvxAutoCorrectLanguageLists::LoadCplSttExceptList() | |||
2424 | { | |||
2425 | try | |||
2426 | { | |||
2427 | tools::SvRef<SotStorage> xStg = new SotStorage( sShareAutoCorrFile, StreamMode::READ | StreamMode::SHARE_DENYNONE ); | |||
2428 | if( xStg.is() && xStg->IsContained( pXMLImplCplStt_ExcptLstStr ) ) | |||
2429 | LoadXMLExceptList_Imp( pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); | |||
2430 | } | |||
2431 | catch (const css::ucb::ContentCreationException&) | |||
2432 | { | |||
2433 | } | |||
2434 | return pCplStt_ExcptLst.get(); | |||
2435 | } | |||
2436 | ||||
2437 | void SvxAutoCorrectLanguageLists::SaveCplSttExceptList() | |||
2438 | { | |||
2439 | MakeUserStorage_Impl(); | |||
2440 | tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); | |||
2441 | ||||
2442 | SaveExceptList_Imp( *pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); | |||
2443 | ||||
2444 | xStg = nullptr; | |||
2445 | ||||
2446 | // Set time stamp | |||
2447 | FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, | |||
2448 | &aModifiedDate, &aModifiedTime ); | |||
2449 | aLastCheckTime = tools::Time( tools::Time::SYSTEM ); | |||
2450 | } | |||
2451 | ||||
2452 | SvStringsISortDtor* SvxAutoCorrectLanguageLists::LoadWrdSttExceptList() | |||
2453 | { | |||
2454 | try | |||
2455 | { | |||
2456 | tools::SvRef<SotStorage> xStg = new SotStorage( sShareAutoCorrFile, StreamMode::READ | StreamMode::SHARE_DENYNONE ); | |||
2457 | if( xStg.is() && xStg->IsContained( pXMLImplWrdStt_ExcptLstStr ) ) | |||
2458 | LoadXMLExceptList_Imp( pWrdStt_ExcptLst, pXMLImplWrdStt_ExcptLstStr, xStg ); | |||
2459 | } | |||
2460 | catch (const css::ucb::ContentCreationException &) | |||
2461 | { | |||
2462 | TOOLS_WARN_EXCEPTION("editeng", "SvxAutoCorrectLanguageLists::LoadWrdSttExceptList")do { css::uno::Any tools_warn_exception( DbgGetCaughtException () ); do { if (true) { switch (sal_detail_log_report(::SAL_DETAIL_LOG_LEVEL_WARN , "editeng")) { case SAL_DETAIL_LOG_ACTION_IGNORE: break; case SAL_DETAIL_LOG_ACTION_LOG: if (sizeof ::sal::detail::getResult ( ::sal::detail::StreamStart() << "SvxAutoCorrectLanguageLists::LoadWrdSttExceptList" << " " << exceptionToString(tools_warn_exception )) == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ( "editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2462" ": "), ::sal::detail::unwrapStream( ::sal::detail ::StreamStart() << "SvxAutoCorrectLanguageLists::LoadWrdSttExceptList" << " " << exceptionToString(tools_warn_exception )), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream << "SvxAutoCorrectLanguageLists::LoadWrdSttExceptList" << " " << exceptionToString(tools_warn_exception ); ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("editeng" ), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2462" ": "), sal_detail_stream, 0); }; break; case SAL_DETAIL_LOG_ACTION_FATAL : if (sizeof ::sal::detail::getResult( ::sal::detail::StreamStart () << "SvxAutoCorrectLanguageLists::LoadWrdSttExceptList" << " " << exceptionToString(tools_warn_exception )) == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ( "editeng"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2462" ": "), ::sal::detail::unwrapStream( ::sal::detail ::StreamStart() << "SvxAutoCorrectLanguageLists::LoadWrdSttExceptList" << " " << exceptionToString(tools_warn_exception )), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream << "SvxAutoCorrectLanguageLists::LoadWrdSttExceptList" << " " << exceptionToString(tools_warn_exception ); ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("editeng" ), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2462" ": "), sal_detail_stream, 0); }; std::abort(); break ; } } } while (false); } while (false); | |||
2463 | } | |||
2464 | return pWrdStt_ExcptLst.get(); | |||
2465 | } | |||
2466 | ||||
2467 | void SvxAutoCorrectLanguageLists::SaveWrdSttExceptList() | |||
2468 | { | |||
2469 | MakeUserStorage_Impl(); | |||
2470 | tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); | |||
2471 | ||||
2472 | SaveExceptList_Imp( *pWrdStt_ExcptLst, pXMLImplWrdStt_ExcptLstStr, xStg ); | |||
2473 | ||||
2474 | xStg = nullptr; | |||
2475 | // Set time stamp | |||
2476 | FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, | |||
2477 | &aModifiedDate, &aModifiedTime ); | |||
2478 | aLastCheckTime = tools::Time( tools::Time::SYSTEM ); | |||
2479 | } | |||
2480 | ||||
2481 | SvStringsISortDtor* SvxAutoCorrectLanguageLists::GetWrdSttExceptList() | |||
2482 | { | |||
2483 | if( !( ACFlags::WrdSttLstLoad & nFlags ) || IsFileChanged_Imp() ) | |||
2484 | { | |||
2485 | LoadWrdSttExceptList(); | |||
2486 | if( !pWrdStt_ExcptLst ) | |||
2487 | { | |||
2488 | OSL_ENSURE( false, "No valid list" )do { if (true && (!(false))) { sal_detail_logFormat(( SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/editeng/source/misc/svxacorr.cxx" ":" "2488" ": "), "%s", "No valid list"); } } while (false); | |||
2489 | pWrdStt_ExcptLst.reset( new SvStringsISortDtor ); | |||
2490 | } | |||
2491 | nFlags |= ACFlags::WrdSttLstLoad; | |||
2492 | } | |||
2493 | return pWrdStt_ExcptLst.get(); | |||
2494 | } | |||
2495 | ||||
2496 | void SvxAutoCorrectLanguageLists::RemoveStream_Imp( const OUString& rName ) | |||
2497 | { | |||
2498 | if( sShareAutoCorrFile != sUserAutoCorrFile ) | |||
2499 | { | |||
2500 | tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); | |||
2501 | if( xStg.is() && ERRCODE_NONEErrCode(0) == xStg->GetError() && | |||
2502 | xStg->IsStream( rName ) ) | |||
2503 | { | |||
2504 | xStg->Remove( rName ); | |||
2505 | xStg->Commit(); | |||
2506 | ||||
2507 | xStg = nullptr; | |||
2508 | } | |||
2509 | } | |||
2510 | } | |||
2511 | ||||
2512 | void SvxAutoCorrectLanguageLists::MakeUserStorage_Impl() | |||
2513 | { | |||
2514 | // The conversion needs to happen if the file is already in the user | |||
2515 | // directory and is in the old format. Additionally it needs to | |||
2516 | // happen when the file is being copied from share to user. | |||
2517 | ||||
2518 | bool bError = false, bConvert = false, bCopy = false; | |||
2519 | INetURLObject aDest; | |||
2520 | INetURLObject aSource; | |||
2521 | ||||
2522 | if (sUserAutoCorrFile != sShareAutoCorrFile ) | |||
2523 | { | |||
2524 | aSource = INetURLObject ( sShareAutoCorrFile ); | |||
2525 | aDest = INetURLObject ( sUserAutoCorrFile ); | |||
2526 | if ( SotStorage::IsOLEStorage ( sShareAutoCorrFile ) ) | |||
2527 | { | |||
2528 | aDest.SetExtension ( "bak" ); | |||
2529 | bConvert = true; | |||
2530 | } | |||
2531 | bCopy = true; | |||
2532 | } | |||
2533 | else if ( SotStorage::IsOLEStorage ( sUserAutoCorrFile ) ) | |||
2534 | { | |||
2535 | aSource = INetURLObject ( sUserAutoCorrFile ); | |||
2536 | aDest = INetURLObject ( sUserAutoCorrFile ); | |||
2537 | aDest.SetExtension ( "bak" ); | |||
2538 | bCopy = bConvert = true; | |||
2539 | } | |||
2540 | if (bCopy) | |||
2541 | { | |||
2542 | try | |||
2543 | { | |||
2544 | OUString sMain(aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri )); | |||
2545 | sal_Int32 nSlashPos = sMain.lastIndexOf('/'); | |||
2546 | sMain = sMain.copy(0, nSlashPos); | |||
2547 | ::ucbhelper::Content aNewContent( sMain, uno::Reference< XCommandEnvironment >(), comphelper::getProcessComponentContext() ); | |||
2548 | TransferInfo aInfo; | |||
2549 | aInfo.NameClash = NameClash::OVERWRITE; | |||
2550 | aInfo.NewTitle = aDest.GetLastName(); | |||
2551 | aInfo.SourceURL = aSource.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); | |||
2552 | aInfo.MoveData = false; | |||
2553 | aNewContent.executeCommand( "transfer", Any(aInfo)); | |||
2554 | } | |||
2555 | catch (...) | |||
2556 | { | |||
2557 | bError = true; | |||
2558 | } | |||
2559 | } | |||
2560 | if (bConvert && !bError) | |||
2561 | { | |||
2562 | tools::SvRef<SotStorage> xSrcStg = new SotStorage( aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ), StreamMode::READ ); | |||
2563 | tools::SvRef<SotStorage> xDstStg = new SotStorage( sUserAutoCorrFile, StreamMode::WRITE ); | |||
2564 | ||||
2565 | if( xSrcStg.is() && xDstStg.is() ) | |||
2566 | { | |||
2567 | std::unique_ptr<SvStringsISortDtor> pTmpWordList; | |||
2568 | ||||
2569 | if (xSrcStg->IsContained( pXMLImplWrdStt_ExcptLstStr ) ) | |||
2570 | LoadXMLExceptList_Imp( pTmpWordList, pXMLImplWrdStt_ExcptLstStr, xSrcStg ); | |||
2571 | ||||
2572 | if (pTmpWordList) | |||
2573 | { | |||
2574 | SaveExceptList_Imp( *pTmpWordList, pXMLImplWrdStt_ExcptLstStr, xDstStg, true ); | |||
2575 | pTmpWordList.reset(); | |||
2576 | } | |||
2577 | ||||
2578 | ||||
2579 | if (xSrcStg->IsContained( pXMLImplCplStt_ExcptLstStr ) ) | |||
2580 | LoadXMLExceptList_Imp( pTmpWordList, pXMLImplCplStt_ExcptLstStr, xSrcStg ); | |||
2581 | ||||
2582 | if (pTmpWordList) | |||
2583 | { | |||
2584 | SaveExceptList_Imp( *pTmpWordList, pXMLImplCplStt_ExcptLstStr, xDstStg, true ); | |||
2585 | pTmpWordList->clear(); | |||
2586 | } | |||
2587 | ||||
2588 | GetAutocorrWordList(); | |||
2589 | MakeBlocklist_Imp( *xDstStg ); | |||
2590 | sShareAutoCorrFile = sUserAutoCorrFile; | |||
2591 | xDstStg = nullptr; | |||
2592 | try | |||
2593 | { | |||
2594 | ::ucbhelper::Content aContent ( aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ), uno::Reference < XCommandEnvironment >(), comphelper::getProcessComponentContext() ); | |||
2595 | aContent.executeCommand ( "delete", makeAny ( true ) ); | |||
2596 | } | |||
2597 | catch (...) | |||
2598 | { | |||
2599 | } | |||
2600 | } | |||
2601 | } | |||
2602 | else if( bCopy && !bError ) | |||
2603 | sShareAutoCorrFile = sUserAutoCorrFile; | |||
2604 | } | |||
2605 | ||||
2606 | bool SvxAutoCorrectLanguageLists::MakeBlocklist_Imp( SotStorage& rStg ) | |||
2607 | { | |||
2608 | bool bRet = true, bRemove = !pAutocorr_List || pAutocorr_List->empty(); | |||
2609 | if( !bRemove ) | |||
2610 | { | |||
2611 | tools::SvRef<SotStorageStream> refList = rStg.OpenSotStream( pXMLImplAutocorr_ListStr, | |||
2612 | ( StreamMode::READ | StreamMode::WRITE | StreamMode::SHARE_DENYWRITE ) ); | |||
2613 | if( refList.is() ) | |||
2614 | { | |||
2615 | refList->SetSize( 0 ); | |||
2616 | refList->SetBufferSize( 8192 ); | |||
2617 | refList->SetProperty( "MediaType", Any(OUString( "text/xml" )) ); | |||
2618 | ||||
2619 | uno::Reference< uno::XComponentContext > xContext = | |||
2620 | comphelper::getProcessComponentContext(); | |||
2621 | ||||
2622 | uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext); | |||
2623 | uno::Reference < io::XOutputStream> xOut = new utl::OOutputStreamWrapper( *refList ); | |||
2624 | xWriter->setOutputStream(xOut); | |||
2625 | ||||
2626 | rtl::Reference< SvXMLAutoCorrectExport > xExp( new SvXMLAutoCorrectExport( xContext, pAutocorr_List.get(), pXMLImplAutocorr_ListStr, xWriter ) ); | |||
2627 | ||||
2628 | xExp->exportDoc( XML_BLOCK_LIST ); | |||
2629 | ||||
2630 | refList->Commit(); | |||
2631 | bRet = ERRCODE_NONEErrCode(0) == refList->GetError(); | |||
2632 | if( bRet ) | |||
2633 | { | |||
2634 | refList.clear(); | |||
2635 | rStg.Commit(); | |||
2636 | if( ERRCODE_NONEErrCode(0) != rStg.GetError() ) | |||
2637 | { | |||
2638 | bRemove = true; | |||
2639 | bRet = false; | |||
2640 | } | |||
2641 | } | |||
2642 | } | |||
2643 | else | |||
2644 | bRet = false; | |||
2645 | } | |||
2646 | ||||
2647 | if( bRemove ) | |||
2648 | { | |||
2649 | rStg.Remove( pXMLImplAutocorr_ListStr ); | |||
2650 | rStg.Commit(); | |||
2651 | } | |||
2652 | ||||
2653 | return bRet; | |||
2654 | } | |||
2655 | ||||
2656 | bool SvxAutoCorrectLanguageLists::MakeCombinedChanges( std::vector<SvxAutocorrWord>& aNewEntries, std::vector<SvxAutocorrWord>& aDeleteEntries ) | |||
2657 | { | |||
2658 | // First get the current list! | |||
2659 | GetAutocorrWordList(); | |||
2660 | ||||
2661 | MakeUserStorage_Impl(); | |||
2662 | tools::SvRef<SotStorage> xStorage = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); | |||
2663 | ||||
2664 | bool bRet = xStorage.is() && ERRCODE_NONEErrCode(0) == xStorage->GetError(); | |||
2665 | ||||
2666 | if( bRet
| |||
2667 | { | |||
2668 | for (SvxAutocorrWord & aWordToDelete : aDeleteEntries) | |||
2669 | { | |||
2670 | std::optional<SvxAutocorrWord> xFoundEntry = pAutocorr_List->FindAndRemove( &aWordToDelete ); | |||
2671 | if( xFoundEntry ) | |||
2672 | { | |||
2673 | if( !xFoundEntry->IsTextOnly() ) | |||
2674 | { | |||
2675 | OUString aName( aWordToDelete.GetShort() ); | |||
2676 | if (xStorage->IsOLEStorage()) | |||
2677 | aName = EncryptBlockName_Imp(aName); | |||
2678 | else | |||
2679 | GeneratePackageName ( aWordToDelete.GetShort(), aName ); | |||
2680 | ||||
2681 | if( xStorage->IsContained( aName ) ) | |||
2682 | { | |||
2683 | xStorage->Remove( aName ); | |||
2684 | bRet = xStorage->Commit(); | |||
2685 | } | |||
2686 | } | |||
2687 | } | |||
2688 | } | |||
2689 | ||||
2690 | for (const SvxAutocorrWord & aNewEntrie : aNewEntries) | |||
2691 | { | |||
2692 | SvxAutocorrWord aWordToAdd(aNewEntrie.GetShort(), aNewEntrie.GetLong(), true ); | |||
2693 | std::optional<SvxAutocorrWord> xRemoved = pAutocorr_List->FindAndRemove( &aWordToAdd ); | |||
2694 | if( xRemoved ) | |||
2695 | { | |||
2696 | if( !xRemoved->IsTextOnly() ) | |||
2697 | { | |||
2698 | // Still have to remove the Storage | |||
2699 | OUString sStorageName( aWordToAdd.GetShort() ); | |||
2700 | if (xStorage->IsOLEStorage()) | |||
2701 | sStorageName = EncryptBlockName_Imp(sStorageName); | |||
2702 | else | |||
2703 | GeneratePackageName ( aWordToAdd.GetShort(), sStorageName); | |||
2704 | ||||
2705 | if( xStorage->IsContained( sStorageName ) ) | |||
2706 | xStorage->Remove( sStorageName ); | |||
2707 | } | |||
2708 | } | |||
2709 | bRet = pAutocorr_List->Insert( std::move(aWordToAdd) ); | |||
2710 | ||||
2711 | if ( !bRet ) | |||
2712 | { | |||
2713 | break; | |||
2714 | } | |||
2715 | } | |||
2716 | ||||
2717 | if ( bRet ) | |||
2718 | { | |||
2719 | bRet = MakeBlocklist_Imp( *xStorage ); | |||
2720 | } | |||
2721 | } | |||
2722 | return bRet; | |||
2723 | } | |||
| ||||
2724 | ||||
2725 | bool SvxAutoCorrectLanguageLists::PutText( const OUString& rShort, const OUString& rLong ) | |||
2726 | { | |||
2727 | // First get the current list! | |||
2728 | GetAutocorrWordList(); | |||
2729 | ||||
2730 | MakeUserStorage_Impl(); | |||
2731 | tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); | |||
2732 | ||||
2733 | bool bRet = xStg.is() && ERRCODE_NONEErrCode(0) == xStg->GetError(); | |||
2734 | ||||
2735 | // Update the word list | |||
2736 | if( bRet ) | |||
2737 | { | |||
2738 | SvxAutocorrWord aNew(rShort, rLong, true ); | |||
2739 | std::optional<SvxAutocorrWord> xRemove = pAutocorr_List->FindAndRemove( &aNew ); | |||
2740 | if( xRemove ) | |||
2741 | { | |||
2742 | if( !xRemove->IsTextOnly() ) | |||
2743 | { | |||
2744 | // Still have to remove the Storage | |||
2745 | OUString sStgNm( rShort ); | |||
2746 | if (xStg->IsOLEStorage()) | |||
2747 | sStgNm = EncryptBlockName_Imp(sStgNm); | |||
2748 | else | |||
2749 | GeneratePackageName ( rShort, sStgNm); | |||
2750 | ||||
2751 | if( xStg->IsContained( sStgNm ) ) | |||
2752 | xStg->Remove( sStgNm ); | |||
2753 | } | |||
2754 | } | |||
2755 | ||||
2756 | if( pAutocorr_List->Insert( std::move(aNew) ) ) | |||
2757 | { | |||
2758 | bRet = MakeBlocklist_Imp( *xStg ); | |||
2759 | xStg = nullptr; | |||
2760 | } | |||
2761 | else | |||
2762 | { | |||
2763 | bRet = false; | |||
2764 | } | |||
2765 | } | |||
2766 | return bRet; | |||
2767 | } | |||
2768 | ||||
2769 | void SvxAutoCorrectLanguageLists::PutText( const OUString& rShort, | |||
2770 | SfxObjectShell& rShell ) | |||
2771 | { | |||
2772 | // First get the current list! | |||
2773 | GetAutocorrWordList(); | |||
2774 | ||||
2775 | MakeUserStorage_Impl(); | |||
2776 | ||||
2777 | try | |||
2778 | { | |||
2779 | uno::Reference < embed::XStorage > xStg = comphelper::OStorageHelper::GetStorageFromURL( sUserAutoCorrFile, embed::ElementModes::READWRITE ); | |||
2780 | OUString sLong; | |||
2781 | bool bRet = rAutoCorrect.PutText( xStg, sUserAutoCorrFile, rShort, rShell, sLong ); | |||
2782 | xStg = nullptr; | |||
2783 | ||||
2784 | // Update the word list | |||
2785 | if( bRet ) | |||
2786 | { | |||
2787 | if( pAutocorr_List->Insert( SvxAutocorrWord(rShort, sLong, false) ) ) | |||
2788 | { | |||
2789 | tools::SvRef<SotStorage> xStor = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); | |||
2790 | MakeBlocklist_Imp( *xStor ); | |||
2791 | } | |||
2792 | } | |||
2793 | } | |||
2794 | catch ( const uno::Exception& ) | |||
2795 | { | |||
2796 | } | |||
2797 | } | |||
2798 | ||||
2799 | // Keep the list sorted ... | |||
2800 | struct SvxAutocorrWordList::CompareSvxAutocorrWordList | |||
2801 | { | |||
2802 | bool operator()( SvxAutocorrWord const & lhs, SvxAutocorrWord const & rhs ) const | |||
2803 | { | |||
2804 | CollatorWrapper& rCmp = ::GetCollatorWrapper(); | |||
2805 | return rCmp.compareString( lhs.GetShort(), rhs.GetShort() ) < 0; | |||
2806 | } | |||
2807 | }; | |||
2808 | ||||
2809 | namespace { | |||
2810 | ||||
2811 | typedef std::unordered_map<OUString, SvxAutocorrWord> AutocorrWordHashType; | |||
2812 | ||||
2813 | } | |||
2814 | ||||
2815 | struct SvxAutocorrWordList::Impl | |||
2816 | { | |||
2817 | ||||
2818 | // only one of these contains the data | |||
2819 | // maSortedVector is manually sorted so we can optimise data movement | |||
2820 | mutable AutocorrWordSetType maSortedVector; | |||
2821 | mutable AutocorrWordHashType maHash; // key is 'Short' | |||
2822 | ||||
2823 | void DeleteAndDestroyAll() | |||
2824 | { | |||
2825 | maHash.clear(); | |||
2826 | maSortedVector.clear(); | |||
2827 | } | |||
2828 | }; | |||
2829 | ||||
2830 | SvxAutocorrWordList::SvxAutocorrWordList() : mpImpl(new Impl) {} | |||
2831 | ||||
2832 | SvxAutocorrWordList::~SvxAutocorrWordList() | |||
2833 | { | |||
2834 | } | |||
2835 | ||||
2836 | void SvxAutocorrWordList::DeleteAndDestroyAll() | |||
2837 | { | |||
2838 | mpImpl->DeleteAndDestroyAll(); | |||
2839 | } | |||
2840 | ||||
2841 | // returns true if inserted | |||
2842 | const SvxAutocorrWord* SvxAutocorrWordList::Insert(SvxAutocorrWord aWord) const | |||
2843 | { | |||
2844 | if ( mpImpl->maSortedVector.empty() ) // use the hash | |||
2845 | { | |||
2846 | OUString aShort = aWord.GetShort(); | |||
2847 | auto [it,inserted] = mpImpl->maHash.emplace( std::move(aShort), std::move(aWord) ); | |||
2848 | if (inserted) | |||
2849 | return &(it->second); | |||
2850 | return nullptr; | |||
2851 | } | |||
2852 | else | |||
2853 | { | |||
2854 | auto it = std::lower_bound(mpImpl->maSortedVector.begin(), mpImpl->maSortedVector.end(), aWord, CompareSvxAutocorrWordList()); | |||
2855 | CollatorWrapper& rCmp = ::GetCollatorWrapper(); | |||
2856 | if (it == mpImpl->maSortedVector.end() || rCmp.compareString( aWord.GetShort(), it->GetShort() ) != 0) | |||
2857 | { | |||
2858 | it = mpImpl->maSortedVector.insert(it, std::move(aWord)); | |||
2859 | return &*it; | |||
2860 | } | |||
2861 | return nullptr; | |||
2862 | } | |||
2863 | } | |||
2864 | ||||
2865 | void SvxAutocorrWordList::LoadEntry(const OUString& sWrong, const OUString& sRight, bool bOnlyTxt) | |||
2866 | { | |||
2867 | (void)Insert(SvxAutocorrWord( sWrong, sRight, bOnlyTxt )); | |||
2868 | } | |||
2869 | ||||
2870 | bool SvxAutocorrWordList::empty() const | |||
2871 | { | |||
2872 | return mpImpl->maHash.empty() && mpImpl->maSortedVector.empty(); | |||
2873 | } | |||
2874 | ||||
2875 | std::optional<SvxAutocorrWord> SvxAutocorrWordList::FindAndRemove(const SvxAutocorrWord *pWord) | |||
2876 | { | |||
2877 | ||||
2878 | if ( mpImpl->maSortedVector.empty() ) // use the hash | |||
2879 | { | |||
2880 | AutocorrWordHashType::iterator it = mpImpl->maHash.find( pWord->GetShort() ); | |||
2881 | if( it != mpImpl->maHash.end() ) | |||
2882 | { | |||
2883 | SvxAutocorrWord pMatch = std::move(it->second); | |||
2884 | mpImpl->maHash.erase (it); | |||
2885 | return pMatch; | |||
2886 | } | |||
2887 | } | |||
2888 | else | |||
2889 | { | |||
2890 | auto it = std::lower_bound(mpImpl->maSortedVector.begin(), mpImpl->maSortedVector.end(), *pWord, CompareSvxAutocorrWordList()); | |||
2891 | if (it != mpImpl->maSortedVector.end() && !CompareSvxAutocorrWordList()(*pWord, *it)) | |||
2892 | { | |||
2893 | SvxAutocorrWord pMatch = std::move(*it); | |||
2894 | mpImpl->maSortedVector.erase (it); | |||
2895 | return pMatch; | |||
2896 | } | |||
2897 | } | |||
2898 | return std::optional<SvxAutocorrWord>(); | |||
2899 | } | |||
2900 | ||||
2901 | // return the sorted contents - defer sorting until we have to. | |||
2902 | const SvxAutocorrWordList::AutocorrWordSetType& SvxAutocorrWordList::getSortedContent() const | |||
2903 | { | |||
2904 | // convert from hash to set permanently | |||
2905 | if ( mpImpl->maSortedVector.empty() ) | |||
2906 | { | |||
2907 | std::vector<SvxAutocorrWord> tmp; | |||
2908 | tmp.reserve(mpImpl->maHash.size()); | |||
2909 | for (auto & rPair : mpImpl->maHash) | |||
2910 | tmp.emplace_back(std::move(rPair.second)); | |||
2911 | mpImpl->maHash.clear(); | |||
2912 | // sort twice - this gets the list into mostly-sorted order, which | |||
2913 | // reduces the number of times we need to invoke the expensive ICU collate fn. | |||
2914 | std::sort(tmp.begin(), tmp.end(), | |||
2915 | [] ( SvxAutocorrWord const & lhs, SvxAutocorrWord const & rhs ) | |||
2916 | { | |||
2917 | return lhs.GetShort() < rhs.GetShort(); | |||
2918 | }); | |||
2919 | // This beast has some O(N log(N)) in a terribly slow ICU collate fn. | |||
2920 | // stable_sort is twice as fast as sort in this situation because it does | |||
2921 | // fewer comparison operations. | |||
2922 | std::stable_sort(tmp.begin(), tmp.end(), CompareSvxAutocorrWordList()); | |||
2923 | mpImpl->maSortedVector = std::move(tmp); | |||
2924 | } | |||
2925 | return mpImpl->maSortedVector; | |||
2926 | } | |||
2927 | ||||
2928 | const SvxAutocorrWord* SvxAutocorrWordList::WordMatches(const SvxAutocorrWord *pFnd, | |||
2929 | const OUString &rTxt, | |||
2930 | sal_Int32 &rStt, | |||
2931 | sal_Int32 nEndPos) const | |||
2932 | { | |||
2933 | const OUString& rChk = pFnd->GetShort(); | |||
2934 | ||||
2935 | sal_Int32 left_wildcard = rChk.startsWith( ".*" ) ? 2 : 0; // ".*word" pattern? | |||
2936 | sal_Int32 right_wildcard = rChk.endsWith( ".*" ) ? 2 : 0; // "word.*" pattern? | |||
2937 | sal_Int32 nSttWdPos = nEndPos; | |||
2938 | ||||
2939 | // direct replacement of keywords surrounded by colons (for example, ":name:") | |||
2940 | bool bColonNameColon = rTxt.getLength() > nEndPos && | |||
2941 | rTxt[nEndPos] == ':' && rChk[0] == ':' && rChk.endsWith(":"); | |||
2942 | if ( nEndPos + (bColonNameColon ? 1 : 0) >= rChk.getLength() - left_wildcard - right_wildcard ) | |||
2943 | { | |||
2944 | ||||
2945 | bool bWasWordDelim = false; | |||
2946 | sal_Int32 nCalcStt = nEndPos - rChk.getLength() + left_wildcard; | |||
2947 | if (bColonNameColon) | |||
2948 | nCalcStt++; | |||
2949 | if( !right_wildcard && ( !nCalcStt || nCalcStt == rStt || left_wildcard || bColonNameColon || | |||
2950 | ( nCalcStt < rStt && | |||
2951 | IsWordDelim( rTxt[ nCalcStt - 1 ] ))) ) | |||
2952 | { | |||
2953 | TransliterationWrapper& rCmp = GetIgnoreTranslWrapper(); | |||
2954 | OUString sWord = rTxt.copy(nCalcStt, rChk.getLength() - left_wildcard); | |||
2955 | if( (!left_wildcard && rCmp.isEqual( rChk, sWord )) || (left_wildcard && rCmp.isEqual( rChk.copy(left_wildcard), sWord) )) | |||
2956 | { | |||
2957 | rStt = nCalcStt; | |||
2958 | if (!left_wildcard) | |||
2959 | { | |||
2960 | // fdo#33899 avoid "1/2", "1/3".. to be replaced by fractions in dates, eg. 1/2/14 | |||
2961 | if (rTxt.getLength() > nEndPos && rTxt[nEndPos] == '/' && rChk.indexOf('/') != -1) | |||
2962 | return nullptr; | |||
2963 | return pFnd; | |||
2964 | } | |||
2965 | // get the first word delimiter position before the matching ".*word" pattern | |||
2966 | while( rStt && !(bWasWordDelim = IsWordDelim( rTxt[ --rStt ]))) | |||
2967 | ; | |||
2968 | if (bWasWordDelim) rStt++; | |||
2969 | OUString left_pattern = rTxt.copy(rStt, nEndPos - rStt - rChk.getLength() + left_wildcard); | |||
2970 | // avoid double spaces before simple "word" replacement | |||
2971 | left_pattern += (left_pattern.getLength() == 0 && pFnd->GetLong()[0] == 0x20) ? pFnd->GetLong().copy(1) : pFnd->GetLong(); | |||
2972 | if( const SvxAutocorrWord* pNew = Insert( SvxAutocorrWord(rTxt.copy(rStt, nEndPos - rStt), left_pattern) ) ) | |||
2973 | return pNew; | |||
2974 | } | |||
2975 | } else | |||
2976 | // match "word.*" or ".*word.*" patterns, eg. "i18n.*", ".*---.*", TODO: add transliteration support | |||
2977 | if ( right_wildcard ) | |||
2978 | { | |||
2979 | ||||
2980 | OUString sTmp( rChk.copy( left_wildcard, rChk.getLength() - left_wildcard - right_wildcard ) ); | |||
2981 | // Get the last word delimiter position | |||
2982 | bool not_suffix; | |||
2983 | ||||
2984 | while( nSttWdPos && !(bWasWordDelim = IsWordDelim( rTxt[ --nSttWdPos ]))) | |||
2985 | ; | |||
2986 | // search the first occurrence (with a left word delimitation, if needed) | |||
2987 | sal_Int32 nFndPos = -1; | |||
2988 | do { | |||
2989 | nFndPos = rTxt.indexOf( sTmp, nFndPos + 1); | |||
2990 | if (nFndPos == -1) | |||
2991 | break; | |||
2992 | not_suffix = bWasWordDelim && (nSttWdPos >= (nFndPos + sTmp.getLength())); | |||
2993 | } while ( (!left_wildcard && nFndPos && !IsWordDelim( rTxt[ nFndPos - 1 ])) || not_suffix ); | |||
2994 | ||||
2995 | if ( nFndPos != -1 ) | |||
2996 | { | |||
2997 | sal_Int32 extra_repl = nFndPos + sTmp.getLength() > nEndPos ? 1: 0; // for patterns with terminating characters, eg. "a:" | |||
2998 | ||||
2999 | if ( left_wildcard ) | |||
3000 | { | |||
3001 | // get the first word delimiter position before the matching ".*word.*" pattern | |||
3002 | while( nFndPos && !(bWasWordDelim = IsWordDelim( rTxt[ --nFndPos ]))) | |||
3003 | ; | |||
3004 | if (bWasWordDelim) nFndPos++; | |||
3005 | } | |||
3006 | if (nEndPos + extra_repl <= nFndPos) | |||
3007 | { | |||
3008 | return nullptr; | |||
3009 | } | |||
3010 | // store matching pattern and its replacement as a new list item, eg. "i18ns" -> "internationalizations" | |||
3011 | OUString aShort = rTxt.copy(nFndPos, nEndPos - nFndPos + extra_repl); | |||
3012 | ||||
3013 | OUString aLong; | |||
3014 | rStt = nFndPos; | |||
3015 | if ( !left_wildcard ) | |||
3016 | { | |||
3017 | sal_Int32 siz = nEndPos - nFndPos - sTmp.getLength(); | |||
3018 | aLong = pFnd->GetLong() + (siz > 0 ? rTxt.copy(nFndPos + sTmp.getLength(), siz) : ""); | |||
3019 | } else { | |||
3020 | OUStringBuffer buf; | |||
3021 | do { | |||
3022 | nSttWdPos = rTxt.indexOf( sTmp, nFndPos); | |||
3023 | if (nSttWdPos != -1) | |||
3024 | { | |||
3025 | sal_Int32 nTmp(nFndPos); | |||
3026 | while (nTmp < nSttWdPos && !IsWordDelim(rTxt[nTmp])) | |||
3027 | nTmp++; | |||
3028 | if (nTmp < nSttWdPos) | |||
3029 | break; // word delimiter found | |||
3030 | buf.append(std::u16string_view(rTxt).substr(nFndPos, nSttWdPos - nFndPos)).append(pFnd->GetLong()); | |||
3031 | nFndPos = nSttWdPos + sTmp.getLength(); | |||
3032 | } | |||
3033 | } while (nSttWdPos != -1); | |||
3034 | if (nEndPos - nFndPos > extra_repl) | |||
3035 | buf.append(std::u16string_view(rTxt).substr(nFndPos, nEndPos - nFndPos)); | |||
3036 | aLong = buf.makeStringAndClear(); | |||
3037 | } | |||
3038 | if ( const SvxAutocorrWord* pNew = Insert( SvxAutocorrWord(aShort, aLong) ) ) | |||
3039 | { | |||
3040 | if ( (rTxt.getLength() > nEndPos && IsWordDelim(rTxt[nEndPos])) || rTxt.getLength() == nEndPos ) | |||
3041 | return pNew; | |||
3042 | } | |||
3043 | } | |||
3044 | } | |||
3045 | } | |||
3046 | return nullptr; | |||
3047 | } | |||
3048 | ||||
3049 | const SvxAutocorrWord* SvxAutocorrWordList::SearchWordsInList(const OUString& rTxt, sal_Int32& rStt, | |||
3050 | sal_Int32 nEndPos) const | |||
3051 | { | |||
3052 | for (auto const& elem : mpImpl->maHash) | |||
3053 | { | |||
3054 | if( const SvxAutocorrWord *pTmp = WordMatches( &elem.second, rTxt, rStt, nEndPos ) ) | |||
3055 | return pTmp; | |||
3056 | } | |||
3057 | ||||
3058 | for (auto const& elem : mpImpl->maSortedVector) | |||
3059 | { | |||
3060 | if( const SvxAutocorrWord *pTmp = WordMatches( &elem, rTxt, rStt, nEndPos ) ) | |||
3061 | return pTmp; | |||
3062 | } | |||
3063 | return nullptr; | |||
3064 | } | |||
3065 | ||||
3066 | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |