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