/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <memory>
#include <sal/config.h>
#include <com/sun/star/io/IOException.hpp>
#include <com/sun/star/io/XStream.hpp>
#include <com/sun/star/lang/Locale.hpp>
#include <tools/urlobj.hxx>
#include <i18nlangtag/mslangid.hxx>
#include <i18nutil/transliteration.hxx>
#include <sal/log.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <sot/storinfo.hxx>
#include <svl/fstathelper.hxx>
#include <svtools/helpopt.hxx>
#include <svl/urihelper.hxx>
#include <unotools/charclass.hxx>
#include <com/sun/star/i18n/UnicodeType.hpp>
#include <unotools/collatorwrapper.hxx>
#include <com/sun/star/i18n/CollatorOptions.hpp>
#include <com/sun/star/i18n/UnicodeScript.hpp>
#include <com/sun/star/i18n/OrdinalSuffix.hpp>
#include <unotools/localedatawrapper.hxx>
#include <unotools/transliterationwrapper.hxx>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/io/XActiveDataSource.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/storagehelper.hxx>
#include <comphelper/string.hxx>
#include <editeng/editids.hrc>
#include <sot/storage.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <editeng/escapementitem.hxx>
#include <editeng/svxacorr.hxx>
#include <editeng/unolingu.hxx>
#include <vcl/window.hxx>
#include <com/sun/star/xml/sax/InputSource.hpp>
#include <com/sun/star/xml/sax/FastParser.hpp>
#include <com/sun/star/xml/sax/FastToken.hpp>
#include <com/sun/star/xml/sax/Writer.hpp>
#include <com/sun/star/xml/sax/FastTokenHandler.hpp>
#include <unotools/streamwrap.hxx>
#include "SvXMLAutoCorrectImport.hxx"
#include "SvXMLAutoCorrectExport.hxx"
#include "SvXMLAutoCorrectTokenHandler.hxx"
#include <ucbhelper/content.hxx>
#include <com/sun/star/ucb/ContentCreationException.hpp>
#include <com/sun/star/ucb/XCommandEnvironment.hpp>
#include <com/sun/star/ucb/TransferInfo.hpp>
#include <com/sun/star/ucb/NameClash.hpp>
#include <xmloff/xmltoken.hxx>
#include <vcl/help.hxx>
#include <set>
#include <unordered_map>
#include <rtl/character.hxx>
using namespace ::com::sun::star::ucb;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::xml::sax;
using namespace ::com::sun::star;
using namespace ::xmloff::token;
using namespace ::utl;
enum class Flags {
NONE = 0x00,
FullStop = 0x01,
ExclamationMark = 0x02,
QuestionMark = 0x04,
};
namespace o3tl {
template<> struct typed_flags<Flags> : is_typed_flags<Flags, 0x07> {};
}
static const sal_Unicode cNonBreakingSpace = 0xA0;
static const sal_Char pXMLImplWrdStt_ExcptLstStr[] = "WordExceptList.xml";
static const sal_Char pXMLImplCplStt_ExcptLstStr[] = "SentenceExceptList.xml";
static const sal_Char pXMLImplAutocorr_ListStr[] = "DocumentList.xml";
static const sal_Char
/* also at these beginnings - Brackets and all kinds of begin characters */
sImplSttSkipChars[] = "\"\'([{\x83\x84\x89\x91\x92\x93\x94",
/* also at these ends - Brackets and all kinds of begin characters */
sImplEndSkipChars[] = "\"\')]}\x83\x84\x89\x91\x92\x93\x94";
OUString EncryptBlockName_Imp(const OUString& rName);
static inline bool NonFieldWordDelim( const sal_Unicode c )
{
return ' ' == c || '\t' == c || 0x0a == c ||
cNonBreakingSpace == c || 0x2011 == c;
}
static inline bool IsWordDelim( const sal_Unicode c )
{
return c == 0x1 || NonFieldWordDelim(c);
}
static inline bool IsLowerLetter( sal_Int32 nCharType )
{
return CharClass::isLetterType( nCharType ) &&
( css::i18n::KCharacterType::LOWER & nCharType);
}
static inline bool IsUpperLetter( sal_Int32 nCharType )
{
return CharClass::isLetterType( nCharType ) &&
( css::i18n::KCharacterType::UPPER & nCharType);
}
bool lcl_IsUnsupportedUnicodeChar( CharClass const & rCC, const OUString& rTxt,
sal_Int32 nStt, sal_Int32 nEnd )
{
for( ; nStt < nEnd; ++nStt )
{
css::i18n::UnicodeScript nScript = rCC.getScript( rTxt, nStt );
switch( nScript )
{
case css::i18n::UnicodeScript_kCJKRadicalsSupplement:
case css::i18n::UnicodeScript_kHangulJamo:
case css::i18n::UnicodeScript_kCJKSymbolPunctuation:
case css::i18n::UnicodeScript_kHiragana:
case css::i18n::UnicodeScript_kKatakana:
case css::i18n::UnicodeScript_kHangulCompatibilityJamo:
case css::i18n::UnicodeScript_kEnclosedCJKLetterMonth:
case css::i18n::UnicodeScript_kCJKCompatibility:
case css::i18n::UnicodeScript_k_CJKUnifiedIdeographsExtensionA:
case css::i18n::UnicodeScript_kCJKUnifiedIdeograph:
case css::i18n::UnicodeScript_kHangulSyllable:
case css::i18n::UnicodeScript_kCJKCompatibilityIdeograph:
case css::i18n::UnicodeScript_kHalfwidthFullwidthForm:
return true;
default: ; //do nothing
}
}
return false;
}
static bool lcl_IsSymbolChar( CharClass const & rCC, const OUString& rTxt,
sal_Int32 nStt, sal_Int32 nEnd )
{
for( ; nStt < nEnd; ++nStt )
{
if( css::i18n::UnicodeType::PRIVATE_USE == rCC.getType( rTxt, nStt ))
return true;
}
return false;
}
static bool lcl_IsInAsciiArr( const sal_Char* pArr, const sal_Unicode c )
{
bool bRet = false;
for( ; *pArr; ++pArr )
if( *pArr == c )
{
bRet = true;
break;
}
return bRet;
}
SvxAutoCorrDoc::~SvxAutoCorrDoc()
{
}
// Called by the functions:
// - FnCapitalStartWord
// - FnCapitalStartSentence
// after the exchange of characters. Then the words, if necessary, can be inserted
// into the exception list.
void SvxAutoCorrDoc::SaveCpltSttWord( ACFlags, sal_Int32, const OUString&,
sal_Unicode )
{
}
LanguageType SvxAutoCorrDoc::GetLanguage( sal_Int32 ) const
{
return LANGUAGE_SYSTEM;
}
static const LanguageTag& GetAppLang()
{
return Application::GetSettings().GetLanguageTag();
}
/// Never use an unresolved LANGUAGE_SYSTEM.
static LanguageType GetDocLanguage( const SvxAutoCorrDoc& rDoc, sal_Int32 nPos )
{
LanguageType eLang = rDoc.GetLanguage( nPos );
if (eLang == LANGUAGE_SYSTEM)
eLang = GetAppLang().getLanguageType(); // the current work locale
return eLang;
}
static LocaleDataWrapper& GetLocaleDataWrapper( LanguageType nLang )
{
static LocaleDataWrapper aLclDtWrp( GetAppLang() );
LanguageTag aLcl( nLang );
const LanguageTag& rLcl = aLclDtWrp.getLoadedLanguageTag();
if( aLcl != rLcl )
aLclDtWrp.setLanguageTag( aLcl );
return aLclDtWrp;
}
static TransliterationWrapper& GetIgnoreTranslWrapper()
{
static int bIsInit = 0;
static TransliterationWrapper aWrp( ::comphelper::getProcessComponentContext(),
TransliterationFlags::IGNORE_KANA |
TransliterationFlags::IGNORE_WIDTH );
if( !bIsInit )
{
aWrp.loadModuleIfNeeded( GetAppLang().getLanguageType() );
bIsInit = 1;
}
return aWrp;
}
static CollatorWrapper& GetCollatorWrapper()
{
static int bIsInit = 0;
static CollatorWrapper aCollWrp( ::comphelper::getProcessComponentContext() );
if( !bIsInit )
{
aCollWrp.loadDefaultCollator( GetAppLang().getLocale(), 0 );
bIsInit = 1;
}
return aCollWrp;
}
bool SvxAutoCorrect::IsAutoCorrectChar( sal_Unicode cChar )
{
return cChar == '\0' || cChar == '\t' || cChar == 0x0a ||
cChar == ' ' || cChar == '\'' || cChar == '\"' ||
cChar == '*' || cChar == '_' || cChar == '%' ||
cChar == '.' || cChar == ',' || cChar == ';' ||
cChar == ':' || cChar == '?' || cChar == '!' ||
cChar == '/' || cChar == '-';
}
namespace
{
bool IsCompoundWordDelimChar(sal_Unicode cChar)
{
return cChar == '-' || SvxAutoCorrect::IsAutoCorrectChar(cChar);
}
}
bool SvxAutoCorrect::NeedsHardspaceAutocorr( sal_Unicode cChar )
{
return cChar == '%' || cChar == ';' || cChar == ':' || cChar == '?' || cChar == '!' ||
cChar == '/' /*case for the urls exception*/;
}
ACFlags SvxAutoCorrect::GetDefaultFlags()
{
ACFlags nRet = ACFlags::Autocorrect
| ACFlags::CapitalStartSentence
| ACFlags::CapitalStartWord
| ACFlags::ChgOrdinalNumber
| ACFlags::ChgToEnEmDash
| ACFlags::AddNonBrkSpace
| ACFlags::ChgWeightUnderl
| ACFlags::SetINetAttr
| ACFlags::ChgQuotes
| ACFlags::SaveWordCplSttLst
| ACFlags::SaveWordWrdSttLst
| ACFlags::CorrectCapsLock;
LanguageType eLang = GetAppLang().getLanguageType();
if( eLang.anyOf(
LANGUAGE_ENGLISH,
LANGUAGE_ENGLISH_US,
LANGUAGE_ENGLISH_UK,
LANGUAGE_ENGLISH_AUS,
LANGUAGE_ENGLISH_CAN,
LANGUAGE_ENGLISH_NZ,
LANGUAGE_ENGLISH_EIRE,
LANGUAGE_ENGLISH_SAFRICA,
LANGUAGE_ENGLISH_JAMAICA,
LANGUAGE_ENGLISH_CARRIBEAN))
nRet &= ~ACFlags(ACFlags::ChgQuotes|ACFlags::ChgSglQuotes);
return nRet;
}
SvxAutoCorrect::SvxAutoCorrect( const OUString& rShareAutocorrFile,
const OUString& rUserAutocorrFile )
: sShareAutoCorrFile( rShareAutocorrFile )
, sUserAutoCorrFile( rUserAutocorrFile )
, eCharClassLang( LANGUAGE_DONTKNOW )
, nFlags(SvxAutoCorrect::GetDefaultFlags())
, cStartDQuote( 0 )
, cEndDQuote( 0 )
, cStartSQuote( 0 )
, cEndSQuote( 0 )
, cEmDash( 0x2014 )
, cEnDash( 0x2013)
{
}
SvxAutoCorrect::SvxAutoCorrect( const SvxAutoCorrect& rCpy )
: sShareAutoCorrFile( rCpy.sShareAutoCorrFile )
, sUserAutoCorrFile( rCpy.sUserAutoCorrFile )
, aSwFlags( rCpy.aSwFlags )
, eCharClassLang(rCpy.eCharClassLang)
, nFlags( rCpy.nFlags & ~ACFlags(ACFlags::ChgWordLstLoad|ACFlags::CplSttLstLoad|ACFlags::WrdSttLstLoad))
, cStartDQuote( rCpy.cStartDQuote )
, cEndDQuote( rCpy.cEndDQuote )
, cStartSQuote( rCpy.cStartSQuote )
, cEndSQuote( rCpy.cEndSQuote )
, cEmDash( rCpy.cEmDash )
, cEnDash( rCpy.cEnDash )
{
}
SvxAutoCorrect::~SvxAutoCorrect()
{
}
void SvxAutoCorrect::GetCharClass_( LanguageType eLang )
{
pCharClass.reset( new CharClass( LanguageTag( eLang)) );
eCharClassLang = eLang;
}
void SvxAutoCorrect::SetAutoCorrFlag( ACFlags nFlag, bool bOn )
{
ACFlags nOld = nFlags;
nFlags = bOn ? nFlags | nFlag
: nFlags & ~nFlag;
if( !bOn )
{
if( (nOld & ACFlags::CapitalStartSentence) != (nFlags & ACFlags::CapitalStartSentence) )
nFlags &= ~ACFlags::CplSttLstLoad;
if( (nOld & ACFlags::CapitalStartWord) != (nFlags & ACFlags::CapitalStartWord) )
nFlags &= ~ACFlags::WrdSttLstLoad;
if( (nOld & ACFlags::Autocorrect) != (nFlags & ACFlags::Autocorrect) )
nFlags &= ~ACFlags::ChgWordLstLoad;
}
}
// Two capital letters at the beginning of word?
void SvxAutoCorrect::FnCapitalStartWord( SvxAutoCorrDoc& rDoc, const OUString& rTxt,
sal_Int32 nSttPos, sal_Int32 nEndPos,
LanguageType eLang )
{
CharClass& rCC = GetCharClass( eLang );
// Delete all non alphanumeric. Test the characters at the beginning/end of
// the word ( recognizes: "(min.", "/min.", and so on.)
for( ; nSttPos < nEndPos; ++nSttPos )
if( rCC.isLetterNumeric( rTxt, nSttPos ))
break;
for( ; nSttPos < nEndPos; --nEndPos )
if( rCC.isLetterNumeric( rTxt, nEndPos - 1 ))
break;
// Is the word a compounded word separated by delimiters?
// If so, keep track of all delimiters so each constituent
// word can be checked for two initial capital letters.
std::deque<sal_Int32> aDelimiters;
// Always check for two capitals at the beginning
// of the entire word, so start at nSttPos.
aDelimiters.push_back(nSttPos);
// Find all compound word delimiters
for (sal_Int32 n = nSttPos; n < nEndPos; ++n)
{
if (IsCompoundWordDelimChar(rTxt[ n ]))
{
aDelimiters.push_back( n + 1 ); // Get position of char after delimiter
}
}
// Decide where to put the terminating delimiter.
// If the last AutoCorrect char was a newline, then the AutoCorrect
// char will not be included in rTxt.
// If the last AutoCorrect char was not a newline, then the AutoCorrect
// character will be the last character in rTxt.
if (!IsCompoundWordDelimChar(rTxt[nEndPos-1]))
aDelimiters.push_back(nEndPos);
// Iterate through the word and all words that compose it.
// Two capital letters at the beginning of word?
for (size_t nI = 0; nI < aDelimiters.size() - 1; ++nI)
{
nSttPos = aDelimiters[nI];
nEndPos = aDelimiters[nI + 1];
if( nSttPos+2 < nEndPos &&
IsUpperLetter( rCC.getCharacterType( rTxt, nSttPos )) &&
IsUpperLetter( rCC.getCharacterType( rTxt, ++nSttPos )) &&
// Is the third character a lower case
IsLowerLetter( rCC.getCharacterType( rTxt, nSttPos +1 )) &&
// Do not replace special attributes
0x1 != rTxt[ nSttPos ] && 0x2 != rTxt[ nSttPos ])
{
// test if the word is in an exception list
OUString sWord( rTxt.copy( nSttPos - 1, nEndPos - nSttPos + 1 ));
if( !FindInWrdSttExceptList(eLang, sWord) )
{
// Check that word isn't correctly spelled before correcting:
css::uno::Reference< css::linguistic2::XSpellChecker1 > xSpeller =
LinguMgr::GetSpellChecker();
if( xSpeller->hasLanguage(static_cast<sal_uInt16>(eLang)) )
{
Sequence< css::beans::PropertyValue > aEmptySeq;
if (xSpeller->isValid(sWord, static_cast<sal_uInt16>(eLang), aEmptySeq))
{
return;
}
}
sal_Unicode cSave = rTxt[ nSttPos ];
OUString sChar( cSave );
sChar = rCC.lowercase( sChar );
if( sChar[0] != cSave && rDoc.ReplaceRange( nSttPos, 1, sChar ))
{
if( ACFlags::SaveWordWrdSttLst & nFlags )
rDoc.SaveCpltSttWord( ACFlags::CapitalStartWord, nSttPos, sWord, cSave );
}
}
}
}
}
bool SvxAutoCorrect::FnChgOrdinalNumber(
SvxAutoCorrDoc& rDoc, const OUString& rTxt,
sal_Int32 nSttPos, sal_Int32 nEndPos,
LanguageType eLang)
{
// 1st, 2nd, 3rd, 4 - 0th
// 201th or 201st
// 12th or 12nd
bool bChg = false;
// In some languages ordinal suffixes should never be
// changed to superscript. Let's break for those languages.
if (!eLang.anyOf(
LANGUAGE_SWEDISH,
LANGUAGE_SWEDISH_FINLAND))
{
CharClass& rCC = GetCharClass(eLang);
for (; nSttPos < nEndPos; ++nSttPos)
if (!lcl_IsInAsciiArr(sImplSttSkipChars, rTxt[nSttPos]))
break;
for (; nSttPos < nEndPos; --nEndPos)
if (!lcl_IsInAsciiArr(sImplEndSkipChars, rTxt[nEndPos - 1]))
break;
// Get the last number in the string to check
sal_Int32 nNumEnd = nEndPos;
bool bFoundEnd = false;
bool isValidNumber = true;
sal_Int32 i = nEndPos;
while (i > nSttPos)
{
i--;
bool isDigit = rCC.isDigit(rTxt, i);
if (bFoundEnd)
isValidNumber &= (isDigit || !rCC.isLetter(rTxt, i));
if (isDigit && !bFoundEnd)
{
bFoundEnd = true;
nNumEnd = i;
}
}
if (bFoundEnd && isValidNumber) {
sal_Int32 nNum = rTxt.copy(nSttPos, nNumEnd - nSttPos + 1).toInt32();
// Check if the characters after that number correspond to the ordinal suffix
uno::Reference< i18n::XOrdinalSuffix > xOrdSuffix
= i18n::OrdinalSuffix::create(comphelper::getProcessComponentContext());
uno::Sequence< OUString > aSuffixes = xOrdSuffix->getOrdinalSuffix(nNum, rCC.getLanguageTag().getLocale());
for (sal_Int32 nSuff = 0; nSuff < aSuffixes.getLength(); nSuff++)
{
OUString sSuffix(aSuffixes[nSuff]);
OUString sEnd = rTxt.copy(nNumEnd + 1, nEndPos - nNumEnd - 1);
if (sSuffix == sEnd)
{
// Check if the ordinal suffix has to be set as super script
if (rCC.isLetter(sSuffix))
{
// Do the change
SvxEscapementItem aSvxEscapementItem(DFLT_ESC_AUTO_SUPER,
DFLT_ESC_PROP, SID_ATTR_CHAR_ESCAPEMENT);
rDoc.SetAttr(nNumEnd + 1, nEndPos,
SID_ATTR_CHAR_ESCAPEMENT,
aSvxEscapementItem);
bChg = true;
}
}
}
}
}
return bChg;
}
bool SvxAutoCorrect::FnChgToEnEmDash(
SvxAutoCorrDoc& rDoc, const OUString& rTxt,
sal_Int32 nSttPos, sal_Int32 nEndPos,
LanguageType eLang )
{
bool bRet = false;
CharClass& rCC = GetCharClass( eLang );
if (eLang == LANGUAGE_SYSTEM)
eLang = GetAppLang().getLanguageType();
bool bAlwaysUseEmDash = (cEmDash && (eLang == LANGUAGE_RUSSIAN || eLang == LANGUAGE_UKRAINIAN));
// replace " - " or " --" with "enDash"
if( cEnDash && 1 < nSttPos && 1 <= nEndPos - nSttPos )
{
sal_Unicode cCh = rTxt[ nSttPos ];
if( '-' == cCh )
{
if( ' ' == rTxt[ nSttPos-1 ] &&
'-' == rTxt[ nSttPos+1 ])
{
sal_Int32 n;
for( n = nSttPos+2; n < nEndPos && lcl_IsInAsciiArr(
sImplSttSkipChars,(cCh = rTxt[ n ]));
++n )
;
// found: " --[<AnySttChars>][A-z0-9]
if( rCC.isLetterNumeric( OUString(cCh) ) )
{
for( n = nSttPos-1; n && lcl_IsInAsciiArr(
sImplEndSkipChars,(cCh = rTxt[ --n ])); )
;
// found: "[A-z0-9][<AnyEndChars>] --[<AnySttChars>][A-z0-9]
if( rCC.isLetterNumeric( OUString(cCh) ))
{
rDoc.Delete( nSttPos, nSttPos + 2 );
rDoc.Insert( nSttPos, bAlwaysUseEmDash ? OUString(cEmDash) : OUString(cEnDash) );
bRet = true;
}
}
}
}
else if( 3 < nSttPos &&
' ' == rTxt[ nSttPos-1 ] &&
'-' == rTxt[ nSttPos-2 ])
{
sal_Int32 n, nLen = 1, nTmpPos = nSttPos - 2;
if( '-' == ( cCh = rTxt[ nTmpPos-1 ]) )
{
--nTmpPos;
++nLen;
cCh = rTxt[ nTmpPos-1 ];
}
if( ' ' == cCh )
{
for( n = nSttPos; n < nEndPos && lcl_IsInAsciiArr(
sImplSttSkipChars,(cCh = rTxt[ n ]));
++n )
;
// found: " - [<AnySttChars>][A-z0-9]
if( rCC.isLetterNumeric( OUString(cCh) ) )
{
cCh = ' ';
for( n = nTmpPos-1; n && lcl_IsInAsciiArr(
sImplEndSkipChars,(cCh = rTxt[ --n ])); )
;
// found: "[A-z0-9][<AnyEndChars>] - [<AnySttChars>][A-z0-9]
if( rCC.isLetterNumeric( OUString(cCh) ))
{
rDoc.Delete( nTmpPos, nTmpPos + nLen );
rDoc.Insert( nTmpPos, bAlwaysUseEmDash ? OUString(cEmDash) : OUString(cEnDash) );
bRet = true;
}
}
}
}
}
// Replace [A-z0-9]--[A-z0-9] double dash with "emDash" or "enDash"
// [0-9]--[0-9] double dash always replaced with "enDash"
// Finnish and Hungarian use enDash instead of emDash.
bool bEnDash = (eLang == LANGUAGE_HUNGARIAN || eLang == LANGUAGE_FINNISH);
if( ((cEmDash && !bEnDash) || (cEnDash && bEnDash)) && 4 <= nEndPos - nSttPos )
{
OUString sTmp( rTxt.copy( nSttPos, nEndPos - nSttPos ) );
sal_Int32 nFndPos = sTmp.indexOf("--");
if( nFndPos != -1 && nFndPos &&
nFndPos + 2 < sTmp.getLength() &&
( rCC.isLetterNumeric( sTmp, nFndPos - 1 ) ||
lcl_IsInAsciiArr( sImplEndSkipChars, rTxt[ nFndPos - 1 ] )) &&
( rCC.isLetterNumeric( sTmp, nFndPos + 2 ) ||
lcl_IsInAsciiArr( sImplSttSkipChars, rTxt[ nFndPos + 2 ] )))
{
nSttPos = nSttPos + nFndPos;
rDoc.Delete( nSttPos, nSttPos + 2 );
rDoc.Insert( nSttPos, (bEnDash || (rCC.isDigit( sTmp, nFndPos - 1 ) &&
rCC.isDigit( sTmp, nFndPos + 2 )) ? OUString(cEnDash) : OUString(cEmDash)) );
bRet = true;
}
}
return bRet;
}
bool SvxAutoCorrect::FnAddNonBrkSpace(
SvxAutoCorrDoc& rDoc, const OUString& rTxt,
sal_Int32 nEndPos,
LanguageType eLang, bool& io_bNbspRunNext )
{
bool bRet = false;
CharClass& rCC = GetCharClass( eLang );
if ( rCC.getLanguageTag().getLanguage() == "fr" )
{
bool bFrCA = (rCC.getLanguageTag().getCountry() == "CA");
OUString allChars = ":;?!%";
OUString chars( allChars );
if ( bFrCA )
chars = ":";
sal_Unicode cChar = rTxt[ nEndPos ];
bool bHasSpace = chars.indexOf( cChar ) != -1;
bool bIsSpecial = allChars.indexOf( cChar ) != -1;
if ( bIsSpecial )
{
// Get the last word delimiter position
sal_Int32 nSttWdPos = nEndPos;
bool bWasWordDelim = false;
while( nSttWdPos && !(bWasWordDelim = IsWordDelim( rTxt[ --nSttWdPos ])))
;
//See if the text is the start of a protocol string, e.g. have text of
//"http" see if it is the start of "http:" and if so leave it alone
sal_Int32 nIndex = nSttWdPos + (bWasWordDelim ? 1 : 0);
sal_Int32 nProtocolLen = nEndPos - nSttWdPos + 1;
if (nIndex + nProtocolLen <= rTxt.getLength())
{
if (INetURLObject::CompareProtocolScheme(rTxt.copy(nIndex, nProtocolLen)) != INetProtocol::NotValid)
return false;
}
// Check the presence of "://" in the word
sal_Int32 nStrPos = rTxt.indexOf( "://", nSttWdPos + 1 );
if ( nStrPos == -1 && nEndPos > 0 )
{
// Check the previous char
sal_Unicode cPrevChar = rTxt[ nEndPos - 1 ];
if ( ( chars.indexOf( cPrevChar ) == -1 ) && cPrevChar != '\t' )
{
// Remove any previous normal space
sal_Int32 nPos = nEndPos - 1;
while ( cPrevChar == ' ' || cPrevChar == cNonBreakingSpace )
{
if ( nPos == 0 ) break;
nPos--;
cPrevChar = rTxt[ nPos ];
}
nPos++;
if ( nEndPos - nPos > 0 )
rDoc.Delete( nPos, nEndPos );
// Add the non-breaking space at the end pos
if ( bHasSpace )
rDoc.Insert( nPos, OUString(cNonBreakingSpace) );
io_bNbspRunNext = true;
bRet = true;
}
else if ( chars.indexOf( cPrevChar ) != -1 )
io_bNbspRunNext = true;
}
}
else if ( cChar == '/' && nEndPos > 1 && rTxt.getLength() > (nEndPos - 1) )
{
// Remove the hardspace right before to avoid formatting URLs
sal_Unicode cPrevChar = rTxt[ nEndPos - 1 ];
sal_Unicode cMaybeSpaceChar = rTxt[ nEndPos - 2 ];
if ( cPrevChar == ':' && cMaybeSpaceChar == cNonBreakingSpace )
{
rDoc.Delete( nEndPos - 2, nEndPos - 1 );
bRet = true;
}
}
}
return bRet;
}
bool SvxAutoCorrect::FnSetINetAttr( SvxAutoCorrDoc& rDoc, const OUString& rTxt,
sal_Int32 nSttPos, sal_Int32 nEndPos,
LanguageType eLang )
{
OUString sURL( URIHelper::FindFirstURLInText( rTxt, nSttPos, nEndPos,
GetCharClass( eLang ) ));
bool bRet = !sURL.isEmpty();
if( bRet ) // so, set attribute:
rDoc.SetINetAttr( nSttPos, nEndPos, sURL );
return bRet;
}
bool SvxAutoCorrect::FnChgWeightUnderl( SvxAutoCorrDoc& rDoc, const OUString& rTxt,
sal_Int32 nEndPos )
{
// Condition:
// at the beginning: _, *, / or ~ after Space with the following !Space
// at the end: _, *, / or ~ before Space (word delimiter?)
sal_Unicode cInsChar = rTxt[ nEndPos ]; // underline, bold, italic or strikeout
if( ++nEndPos != rTxt.getLength() &&
!IsWordDelim( rTxt[ nEndPos ] ) )
return false;
--nEndPos;
bool bAlphaNum = false;
sal_Int32 nPos = nEndPos;
sal_Int32 nFndPos = -1;
CharClass& rCC = GetCharClass( LANGUAGE_SYSTEM );
while( nPos )
{
switch( sal_Unicode c = rTxt[ --nPos ] )
{
case '_':
case '-':
case '/':
case '*':
if( c == cInsChar )
{
if( bAlphaNum && nPos+1 < nEndPos && ( !nPos ||
IsWordDelim( rTxt[ nPos-1 ])) &&
!IsWordDelim( rTxt[ nPos+1 ]))
nFndPos = nPos;
else
// Condition is not satisfied, so cancel
nFndPos = -1;
nPos = 0;
}
break;
default:
if( !bAlphaNum )
bAlphaNum = rCC.isLetterNumeric( rTxt, nPos );
}
}
if( -1 != nFndPos )
{
// first delete the Character at the end - this allows insertion
// of an empty hint in SetAttr which would be removed by Delete
// (fdo#62536, AUTOFMT in Writer)
rDoc.Delete( nEndPos, nEndPos + 1 );
rDoc.Delete( nFndPos, nFndPos + 1 );
// Span the Attribute over the area
// the end.
if( '*' == cInsChar ) // Bold
{
SvxWeightItem aSvxWeightItem( WEIGHT_BOLD, SID_ATTR_CHAR_WEIGHT );
rDoc.SetAttr( nFndPos, nEndPos - 1,
SID_ATTR_CHAR_WEIGHT,
aSvxWeightItem);
}
else if( '/' == cInsChar ) // Italic
{
SvxPostureItem aSvxPostureItem( ITALIC_NORMAL, SID_ATTR_CHAR_POSTURE );
rDoc.SetAttr( nFndPos, nEndPos - 1,
SID_ATTR_CHAR_POSTURE,
aSvxPostureItem);
}
else if( '-' == cInsChar ) // Strikeout
{
SvxCrossedOutItem aSvxCrossedOutItem( STRIKEOUT_SINGLE, SID_ATTR_CHAR_STRIKEOUT );
rDoc.SetAttr( nFndPos, nEndPos - 1,
SID_ATTR_CHAR_STRIKEOUT,
aSvxCrossedOutItem);
}
else // Underline
{
SvxUnderlineItem aSvxUnderlineItem( LINESTYLE_SINGLE, SID_ATTR_CHAR_UNDERLINE );
rDoc.SetAttr( nFndPos, nEndPos - 1,
SID_ATTR_CHAR_UNDERLINE,
aSvxUnderlineItem);
}
}
return -1 != nFndPos;
}
void SvxAutoCorrect::FnCapitalStartSentence( SvxAutoCorrDoc& rDoc,
const OUString& rTxt, bool bNormalPos,
sal_Int32 nSttPos, sal_Int32 nEndPos,
LanguageType eLang )
{
if( rTxt.isEmpty() || nEndPos <= nSttPos )
return;
CharClass& rCC = GetCharClass( eLang );
OUString aText( rTxt );
const sal_Unicode *pStart = aText.getStr(),
*pStr = pStart + nEndPos,
*pWordStt = nullptr,
*pDelim = nullptr;
bool bAtStart = false;
do {
--pStr;
if (rCC.isLetter(aText, pStr - pStart))
{
if( !pWordStt )
pDelim = pStr+1;
pWordStt = pStr;
}
else if (pWordStt && !rCC.isDigit(aText, pStr - pStart))
{
if( lcl_IsInAsciiArr( "-'", *pStr ) && // These characters are allowed in words
pWordStt - 1 == pStr &&
// Installation at beginning of paragraph. Replaced < by <= (#i38971#)
(pStart + 1) <= pStr &&
rCC.isLetter(aText, pStr-1 - pStart))
pWordStt = --pStr;
else
break;
}
bAtStart = (pStart == pStr);
} while( !bAtStart );
if (!pWordStt)
return; // no character to be replaced
if (rCC.isDigit(aText, pStr - pStart))
return; // already ok
if (IsUpperLetter(rCC.getCharacterType(aText, pWordStt - pStart)))
return; // already ok
//See if the text is the start of a protocol string, e.g. have text of
//"http" see if it is the start of "http:" and if so leave it alone
sal_Int32 nIndex = pWordStt - pStart;
sal_Int32 nProtocolLen = pDelim - pWordStt + 1;
if (nIndex + nProtocolLen <= rTxt.getLength())
{
if (INetURLObject::CompareProtocolScheme(rTxt.copy(nIndex, nProtocolLen)) != INetProtocol::NotValid)
return; // already ok
}
if (0x1 == *pWordStt || 0x2 == *pWordStt)
return; // already ok
if( *pDelim && 2 >= pDelim - pWordStt &&
lcl_IsInAsciiArr( ".-)>", *pDelim ) )
return;
if( !bAtStart ) // Still no beginning of a paragraph?
{
if (NonFieldWordDelim(*pStr))
{
while (!(bAtStart = (pStart == pStr--)) && NonFieldWordDelim(*pStr))
;
}
// Asian full stop, full width full stop, full width exclamation mark
// and full width question marks are treated as word delimiters
else if ( 0x3002 != *pStr && 0xFF0E != *pStr && 0xFF01 != *pStr &&
0xFF1F != *pStr )
return; // no valid separator -> no replacement
}
if( bAtStart ) // at the beginning of a paragraph?
{
// Check out the previous paragraph, if it exists.
// If so, then check to paragraph separator at the end.
OUString const*const pPrevPara = rDoc.GetPrevPara(bNormalPos);
if (!pPrevPara)
{
// valid separator -> replace
OUString sChar( *pWordStt );
sChar = rCC.titlecase(sChar); //see fdo#56740
if (!comphelper::string::equals(sChar, *pWordStt))
rDoc.ReplaceRange( pWordStt - pStart, 1, sChar );
return;
}
aText = *pPrevPara;
bAtStart = false;
pStart = aText.getStr();
pStr = pStart + aText.getLength();
do { // overwrite all blanks
--pStr;
if (!NonFieldWordDelim(*pStr))
break;
bAtStart = (pStart == pStr);
} while( !bAtStart );
if( bAtStart )
return; // no valid separator -> no replacement
}
// Found [ \t]+[A-Z0-9]+ until here. Test now on the paragraph separator.
// all three can happen, but not more than once!
const sal_Unicode* pExceptStt = nullptr;
if( !bAtStart )
{
bool bContinue = true;
Flags nFlag = Flags::NONE;
do {
switch( *pStr )
{
// Western and Asian full stop
case '.':
case 0x3002 :
case 0xFF0E :
{
if (pStr >= pStart + 2 && *(pStr-2) == '.')
{
//e.g. text "f.o.o. word": Now currently considering
//capitalizing word but second last character of
//previous word is a . So probably last word is an
//anagram that ends in . and not truly the end of a
//previous sentence, so don't autocapitalize this word
return;
}
if( nFlag & Flags::FullStop )
return; // no valid separator -> no replacement
nFlag |= Flags::FullStop;
pExceptStt = pStr;
}
break;
case '!':
case 0xFF01 :
{
if( nFlag & Flags::ExclamationMark )
return; // no valid separator -> no replacement
nFlag |= Flags::ExclamationMark;
}
break;
case '?':
case 0xFF1F :
{
if( nFlag & Flags::QuestionMark)
return; // no valid separator -> no replacement
nFlag |= Flags::QuestionMark;
}
break;
default:
if( nFlag == Flags::NONE )
return; // no valid separator -> no replacement
else
bContinue = false;
break;
}
if( bContinue && pStr-- == pStart )
{
return; // no valid separator -> no replacement
}
} while( bContinue );
if( Flags::FullStop != nFlag )
pExceptStt = nullptr;
}
if( 2 > ( pStr - pStart ) )
return;
if (!rCC.isLetterNumeric(aText, pStr-- - pStart))
{
bool bValid = false, bAlphaFnd = false;
const sal_Unicode* pTmpStr = pStr;
while( !bValid )
{
if( rCC.isDigit( aText, pTmpStr - pStart ) )
{
bValid = true;
pStr = pTmpStr - 1;
}
else if( rCC.isLetter( aText, pTmpStr - pStart ) )
{
if( bAlphaFnd )
{
bValid = true;
pStr = pTmpStr;
}
else
bAlphaFnd = true;
}
else if (bAlphaFnd || NonFieldWordDelim(*pTmpStr))
break;
if( pTmpStr == pStart )
break;
--pTmpStr;
}
if( !bValid )
return; // no valid separator -> no replacement
}
bool bNumericOnly = '0' <= *(pStr+1) && *(pStr+1) <= '9';
// Search for the beginning of the word
while (!NonFieldWordDelim(*pStr))
{
if( bNumericOnly && rCC.isLetter( aText, pStr - pStart ) )
bNumericOnly = false;
if( pStart == pStr )
break;
--pStr;
}
if( bNumericOnly ) // consists of only numbers, then not
return;
if (NonFieldWordDelim(*pStr))
++pStr;
OUString sWord;
// check on the basis of the exception list
if( pExceptStt )
{
sWord = OUString(pStr, pExceptStt - pStr + 1);
if( FindInCplSttExceptList(eLang, sWord) )
return;
// Delete all non alphanumeric. Test the characters at the
// beginning/end of the word ( recognizes: "(min.", "/min.", and so on.)
OUString sTmp( sWord );
while( !sTmp.isEmpty() &&
!rCC.isLetterNumeric( sTmp, 0 ) )
sTmp = sTmp.copy(1);
// Remove all non alphanumeric characters towards the end up until
// the last one.
sal_Int32 nLen = sTmp.getLength();
while( nLen && !rCC.isLetterNumeric( sTmp, nLen-1 ) )
--nLen;
if( nLen + 1 < sTmp.getLength() )
sTmp = sTmp.copy( 0, nLen + 1 );
if( !sTmp.isEmpty() && sTmp.getLength() != sWord.getLength() &&
FindInCplSttExceptList(eLang, sTmp))
return;
if(FindInCplSttExceptList(eLang, sWord, true))
return;
}
// Ok, then replace
sal_Unicode cSave = *pWordStt;
nSttPos = pWordStt - rTxt.getStr();
OUString sChar( cSave );
sChar = rCC.titlecase(sChar); //see fdo#56740
bool bRet = sChar[0] != cSave && rDoc.ReplaceRange( nSttPos, 1, sChar );
// Perhaps someone wants to have the word
if( bRet && ACFlags::SaveWordCplSttLst & nFlags )
rDoc.SaveCpltSttWord( ACFlags::CapitalStartSentence, nSttPos, sWord, cSave );
}
bool SvxAutoCorrect::FnCorrectCapsLock( SvxAutoCorrDoc& rDoc, const OUString& rTxt,
sal_Int32 nSttPos, sal_Int32 nEndPos,
LanguageType eLang )
{
if (nEndPos - nSttPos < 2)
// string must be at least 2-character long.
return false;
CharClass& rCC = GetCharClass( eLang );
// Check the first 2 letters.
if ( !IsLowerLetter(rCC.getCharacterType(rTxt, nSttPos)) )
return false;
if ( !IsUpperLetter(rCC.getCharacterType(rTxt, nSttPos+1)) )
return false;
OUStringBuffer aConverted;
aConverted.append( rCC.uppercase(OUString(rTxt[nSttPos])) );
aConverted.append( rCC.lowercase(OUString(rTxt[nSttPos+1])) );
for( sal_Int32 i = nSttPos+2; i < nEndPos; ++i )
{
if ( IsLowerLetter(rCC.getCharacterType(rTxt, i)) )
// A lowercase letter disqualifies the whole text.
return false;
if ( IsUpperLetter(rCC.getCharacterType(rTxt, i)) )
// Another uppercase letter. Convert it.
aConverted.append( rCC.lowercase(OUString(rTxt[i])) );
else
// This is not an alphabetic letter. Leave it as-is.
aConverted.append( rTxt[i] );
}
// Replace the word.
rDoc.Delete(nSttPos, nEndPos);
rDoc.Insert(nSttPos, aConverted.makeStringAndClear());
return true;
}
sal_Unicode SvxAutoCorrect::GetQuote( sal_Unicode cInsChar, bool bSttQuote,
LanguageType eLang ) const
{
sal_Unicode cRet = bSttQuote ? ( '\"' == cInsChar
? GetStartDoubleQuote()
: GetStartSingleQuote() )
: ( '\"' == cInsChar
? GetEndDoubleQuote()
: GetEndSingleQuote() );
if( !cRet )
{
// then through the Language find the right character
if( LANGUAGE_NONE == eLang )
cRet = cInsChar;
else
{
LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang );
OUString sRet( bSttQuote
? ( '\"' == cInsChar
? rLcl.getDoubleQuotationMarkStart()
: rLcl.getQuotationMarkStart() )
: ( '\"' == cInsChar
? rLcl.getDoubleQuotationMarkEnd()
: rLcl.getQuotationMarkEnd() ));
cRet = !sRet.isEmpty() ? sRet[0] : cInsChar;
}
}
return cRet;
}
void SvxAutoCorrect::InsertQuote( SvxAutoCorrDoc& rDoc, sal_Int32 nInsPos,
sal_Unicode cInsChar, bool bSttQuote,
bool bIns )
{
const LanguageType eLang = GetDocLanguage( rDoc, nInsPos );
sal_Unicode cRet = GetQuote( cInsChar, bSttQuote, eLang );
OUString sChg( cInsChar );
if( bIns )
rDoc.Insert( nInsPos, sChg );
else
rDoc.Replace( nInsPos, sChg );
sChg = OUString(cRet);
if( '\"' == cInsChar )
{
if( eLang.anyOf(
LANGUAGE_FRENCH,
LANGUAGE_FRENCH_BELGIAN,
LANGUAGE_FRENCH_CANADIAN,
LANGUAGE_FRENCH_SWISS,
LANGUAGE_FRENCH_LUXEMBOURG))
{
OUString s( cNonBreakingSpace ); // UNICODE code for no break space
if( rDoc.Insert( bSttQuote ? nInsPos+1 : nInsPos, s ))
{
if( !bSttQuote )
++nInsPos;
}
}
}
rDoc.Replace( nInsPos, sChg );
}
OUString SvxAutoCorrect::GetQuote( SvxAutoCorrDoc const & rDoc, sal_Int32 nInsPos,
sal_Unicode cInsChar, bool bSttQuote )
{
const LanguageType eLang = GetDocLanguage( rDoc, nInsPos );
sal_Unicode cRet = GetQuote( cInsChar, bSttQuote, eLang );
OUString sRet = OUString(cRet);
if( '\"' == cInsChar )
{
if( eLang.anyOf(
LANGUAGE_FRENCH,
LANGUAGE_FRENCH_BELGIAN,
LANGUAGE_FRENCH_CANADIAN,
LANGUAGE_FRENCH_SWISS,
LANGUAGE_FRENCH_LUXEMBOURG))
{
if( bSttQuote )
sRet += " ";
else
sRet = " " + sRet;
}
}
return sRet;
}
// WARNING: rText may become invalid, see comment below
void SvxAutoCorrect::DoAutoCorrect( SvxAutoCorrDoc& rDoc, const OUString& rTxt,
sal_Int32 nInsPos, sal_Unicode cChar,
bool bInsert, bool& io_bNbspRunNext, vcl::Window const * pFrameWin )
{
bool bIsNextRun = io_bNbspRunNext;
io_bNbspRunNext = false; // if it was set, then it has to be turned off
do{ // only for middle check loop !!
if( cChar )
{
// Prevent double space
if( nInsPos && ' ' == cChar &&
IsAutoCorrFlag( ACFlags::IgnoreDoubleSpace ) &&
' ' == rTxt[ nInsPos - 1 ])
{
break;
}
bool bSingle = '\'' == cChar;
bool bIsReplaceQuote =
(IsAutoCorrFlag( ACFlags::ChgQuotes ) && ('\"' == cChar )) ||
(IsAutoCorrFlag( ACFlags::ChgSglQuotes ) && bSingle );
if( bIsReplaceQuote )
{
sal_Unicode cPrev;
bool bSttQuote = !nInsPos ||
NonFieldWordDelim( ( cPrev = rTxt[ nInsPos-1 ])) ||
lcl_IsInAsciiArr( "([{", cPrev ) ||
( cEmDash && cEmDash == cPrev ) ||
( cEnDash && cEnDash == cPrev );
InsertQuote( rDoc, nInsPos, cChar, bSttQuote, bInsert );
break;
}
if( bInsert )
rDoc.Insert( nInsPos, OUString(cChar) );
else
rDoc.Replace( nInsPos, OUString(cChar) );
// Hardspaces autocorrection
if ( IsAutoCorrFlag( ACFlags::AddNonBrkSpace ) )
{
if ( NeedsHardspaceAutocorr( cChar ) &&
FnAddNonBrkSpace( rDoc, rTxt, nInsPos, GetDocLanguage( rDoc, nInsPos ), io_bNbspRunNext ) )
{
;
}
else if ( bIsNextRun && !IsAutoCorrectChar( cChar ) )
{
// Remove the NBSP if it wasn't an autocorrection
if ( nInsPos != 0 && NeedsHardspaceAutocorr( rTxt[ nInsPos - 1 ] ) &&
cChar != ' ' && cChar != '\t' && cChar != cNonBreakingSpace )
{
// Look for the last HARD_SPACE
sal_Int32 nPos = nInsPos - 1;
bool bContinue = true;
while ( bContinue )
{
const sal_Unicode cTmpChar = rTxt[ nPos ];
if ( cTmpChar == cNonBreakingSpace )
{
rDoc.Delete( nPos, nPos + 1 );
bContinue = false;
}
else if ( !NeedsHardspaceAutocorr( cTmpChar ) || nPos == 0 )
bContinue = false;
nPos--;
}
}
}
}
}
if( !nInsPos )
break;
sal_Int32 nPos = nInsPos - 1;
if( IsWordDelim( rTxt[ nPos ]))
break;
// Set bold or underline automatically?
if (('*' == cChar || '_' == cChar || '/' == cChar || '-' == cChar) && (nPos+1 < rTxt.getLength()))
{
if( IsAutoCorrFlag( ACFlags::ChgWeightUnderl ) )
{
FnChgWeightUnderl( rDoc, rTxt, nPos+1 );
}
break;
}
while( nPos && !IsWordDelim( rTxt[ --nPos ]))
;
// Found a Paragraph-start or a Blank, search for the word shortcut in
// auto.
sal_Int32 nCapLttrPos = nPos+1; // on the 1st Character
if( !nPos && !IsWordDelim( rTxt[ 0 ]))
--nCapLttrPos; // begin of paragraph and no blank
const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos );
CharClass& rCC = GetCharClass( eLang );
// no symbol characters
if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nInsPos ))
break;
if( IsAutoCorrFlag( ACFlags::Autocorrect ) )
{
// WARNING ATTENTION: rTxt is an alias of the text node's OUString
// and becomes INVALID if ChgAutoCorrWord returns true!
// => use aPara/pPara to create a valid copy of the string!
OUString aPara;
OUString* pPara = IsAutoCorrFlag(ACFlags::CapitalStartSentence) ? &aPara : nullptr;
bool bChgWord = rDoc.ChgAutoCorrWord( nCapLttrPos, nInsPos,
*this, pPara );
if( !bChgWord )
{
sal_Int32 nCapLttrPos1 = nCapLttrPos, nInsPos1 = nInsPos;
while( nCapLttrPos1 < nInsPos &&
lcl_IsInAsciiArr( sImplSttSkipChars, rTxt[ nCapLttrPos1 ] )
)
++nCapLttrPos1;
while( nCapLttrPos1 < nInsPos1 && nInsPos1 &&
lcl_IsInAsciiArr( sImplEndSkipChars, rTxt[ nInsPos1-1 ] )
)
--nInsPos1;
if( (nCapLttrPos1 != nCapLttrPos || nInsPos1 != nInsPos ) &&
nCapLttrPos1 < nInsPos1 &&
rDoc.ChgAutoCorrWord( nCapLttrPos1, nInsPos1, *this, pPara ))
{
bChgWord = true;
nCapLttrPos = nCapLttrPos1;
}
}
if( bChgWord )
{
if( !aPara.isEmpty() )
{
sal_Int32 nEnd = nCapLttrPos;
while( nEnd < aPara.getLength() &&
!IsWordDelim( aPara[ nEnd ]))
++nEnd;
// Capital letter at beginning of paragraph?
if( IsAutoCorrFlag( ACFlags::CapitalStartSentence ) )
{
FnCapitalStartSentence( rDoc, aPara, false,
nCapLttrPos, nEnd, eLang );
}
if( IsAutoCorrFlag( ACFlags::ChgToEnEmDash ) )
{
FnChgToEnEmDash( rDoc, aPara, nCapLttrPos, nEnd, eLang );
}
}
break;
}
}
if( ( IsAutoCorrFlag( ACFlags::ChgOrdinalNumber ) &&
(nInsPos >= 2 ) && // fdo#69762 avoid autocorrect for 2e-3
( '-' != cChar || 'E' != rtl::toAsciiUpperCase(rTxt[nInsPos-1]) || '0' > rTxt[nInsPos-2] || '9' < rTxt[nInsPos-2] ) &&
FnChgOrdinalNumber( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) ||
( IsAutoCorrFlag( ACFlags::SetINetAttr ) &&
( ' ' == cChar || '\t' == cChar || 0x0a == cChar || !cChar ) &&
FnSetINetAttr( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) )
;
else
{
bool bLockKeyOn = pFrameWin && (pFrameWin->GetIndicatorState() & KeyIndicatorState::CAPSLOCK);
bool bUnsupported = lcl_IsUnsupportedUnicodeChar( rCC, rTxt, nCapLttrPos, nInsPos );
if ( bLockKeyOn && IsAutoCorrFlag( ACFlags::CorrectCapsLock ) &&
FnCorrectCapsLock( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) )
{
// Correct accidental use of cAPS LOCK key (do this only when
// the caps or shift lock key is pressed). Turn off the caps
// lock afterwords.
pFrameWin->SimulateKeyPress( KEY_CAPSLOCK );
}
// Capital letter at beginning of paragraph ?
if( !bUnsupported &&
IsAutoCorrFlag( ACFlags::CapitalStartSentence ) )
{
FnCapitalStartSentence( rDoc, rTxt, true, nCapLttrPos, nInsPos, eLang );
}
// Two capital letters at beginning of word ??
if( !bUnsupported &&
IsAutoCorrFlag( ACFlags::CapitalStartWord ) )
{
FnCapitalStartWord( rDoc, rTxt, nCapLttrPos, nInsPos, eLang );
}
if( IsAutoCorrFlag( ACFlags::ChgToEnEmDash ) )
{
FnChgToEnEmDash( rDoc, rTxt, nCapLttrPos, nInsPos, eLang );
}
}
} while( false );
}
SvxAutoCorrectLanguageLists& SvxAutoCorrect::GetLanguageList_(
LanguageType eLang )
{
LanguageTag aLanguageTag( eLang);
if (m_aLangTable.find(aLanguageTag) == m_aLangTable.end())
(void)CreateLanguageFile(aLanguageTag);
return *(m_aLangTable.find(aLanguageTag)->second);
}
void SvxAutoCorrect::SaveCplSttExceptList( LanguageType eLang )
{
auto const iter = m_aLangTable.find(LanguageTag(eLang));
if (iter != m_aLangTable.end() && iter->second)
iter->second->SaveCplSttExceptList();
else
{
SAL_WARN("editeng", "Save an empty list? ");
}
}
void SvxAutoCorrect::SaveWrdSttExceptList(LanguageType eLang)
{
auto const iter = m_aLangTable.find(LanguageTag(eLang));
if (iter != m_aLangTable.end() && iter->second)
iter->second->SaveWrdSttExceptList();
else
{
SAL_WARN("editeng", "Save an empty list? ");
}
}
// Adds a single word. The list will immediately be written to the file!
bool SvxAutoCorrect::AddCplSttException( const OUString& rNew,
LanguageType eLang )
{
SvxAutoCorrectLanguageLists* pLists = nullptr;
// either the right language is present or it will be this in the general list
auto iter = m_aLangTable.find(LanguageTag(eLang));
if (iter != m_aLangTable.end())
pLists = iter->second.get();
else
{
LanguageTag aLangTagUndetermined( LANGUAGE_UNDETERMINED);
iter = m_aLangTable.find(aLangTagUndetermined);
if (iter != m_aLangTable.end())
pLists = iter->second.get();
else if(CreateLanguageFile(aLangTagUndetermined))
pLists = m_aLangTable.find(aLangTagUndetermined)->second.get();
}
OSL_ENSURE(pLists, "No auto correction data");
return pLists && pLists->AddToCplSttExceptList(rNew);
}
// Adds a single word. The list will immediately be written to the file!
bool SvxAutoCorrect::AddWrtSttException( const OUString& rNew,
LanguageType eLang )
{
SvxAutoCorrectLanguageLists* pLists = nullptr;
//either the right language is present or it is set in the general list
auto iter = m_aLangTable.find(LanguageTag(eLang));
if (iter != m_aLangTable.end())
pLists = iter->second.get();
else
{
LanguageTag aLangTagUndetermined( LANGUAGE_UNDETERMINED);
iter = m_aLangTable.find(aLangTagUndetermined);
if (iter != m_aLangTable.end())
pLists = iter->second.get();
else if(CreateLanguageFile(aLangTagUndetermined))
pLists = m_aLangTable.find(aLangTagUndetermined)->second.get();
}
OSL_ENSURE(pLists, "No auto correction file!");
return pLists && pLists->AddToWrdSttExceptList(rNew);
}
bool SvxAutoCorrect::GetPrevAutoCorrWord( SvxAutoCorrDoc const & rDoc,
const OUString& rTxt, sal_Int32 nPos,
OUString& rWord ) const
{
if( !nPos )
return false;
sal_Int32 nEnde = nPos;
// it must be followed by a blank or tab!
if( ( nPos < rTxt.getLength() &&
!IsWordDelim( rTxt[ nPos ])) ||
IsWordDelim( rTxt[ --nPos ]))
return false;
while( nPos && !IsWordDelim( rTxt[ --nPos ]))
;
// Found a Paragraph-start or a Blank, search for the word shortcut in
// auto.
sal_Int32 nCapLttrPos = nPos+1; // on the 1st Character
if( !nPos && !IsWordDelim( rTxt[ 0 ]))
--nCapLttrPos; // Beginning of pargraph and no Blank!
while( lcl_IsInAsciiArr( sImplSttSkipChars, rTxt[ nCapLttrPos ]) )
if( ++nCapLttrPos >= nEnde )
return false;
if( 3 > nEnde - nCapLttrPos )
return false;
const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos );
SvxAutoCorrect* pThis = const_cast<SvxAutoCorrect*>(this);
CharClass& rCC = pThis->GetCharClass( eLang );
if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nEnde ))
return false;
rWord = rTxt.copy( nCapLttrPos, nEnde - nCapLttrPos );
return true;
}
bool SvxAutoCorrect::CreateLanguageFile( const LanguageTag& rLanguageTag, bool bNewFile )
{
OSL_ENSURE(m_aLangTable.find(rLanguageTag) == m_aLangTable.end(), "Language already exists ");
OUString sUserDirFile( GetAutoCorrFileName( rLanguageTag, true ));
OUString sShareDirFile( sUserDirFile );
SvxAutoCorrectLanguageLists* pLists = nullptr;
tools::Time nMinTime( 0, 2 ), nAktTime( tools::Time::SYSTEM ), nLastCheckTime( tools::Time::EMPTY );
auto nFndPos = aLastFileTable.find(rLanguageTag);
if(nFndPos != aLastFileTable.end() &&
(nLastCheckTime.SetTime(nFndPos->second), nLastCheckTime < nAktTime) &&
nAktTime - nLastCheckTime < nMinTime)
{
// no need to test the file, because the last check is not older then
// 2 minutes.
if( bNewFile )
{
sShareDirFile = sUserDirFile;
pLists = new SvxAutoCorrectLanguageLists( *this, sShareDirFile, sUserDirFile );
LanguageTag aTmp(rLanguageTag); // this insert() needs a non-const reference
m_aLangTable.insert(std::make_pair(aTmp, std::unique_ptr<SvxAutoCorrectLanguageLists>(pLists)));
aLastFileTable.erase(nFndPos);
}
}
else if(
( FStatHelper::IsDocument( sUserDirFile ) ||
FStatHelper::IsDocument( sShareDirFile =
GetAutoCorrFileName( rLanguageTag ) ) ||
FStatHelper::IsDocument( sShareDirFile =
GetAutoCorrFileName( rLanguageTag, false, false, true) )
) ||
( sShareDirFile = sUserDirFile, bNewFile )
)
{
pLists = new SvxAutoCorrectLanguageLists( *this, sShareDirFile, sUserDirFile );
LanguageTag aTmp(rLanguageTag); // this insert() needs a non-const reference
m_aLangTable.insert(std::make_pair(aTmp, std::unique_ptr<SvxAutoCorrectLanguageLists>(pLists)));
if (nFndPos != aLastFileTable.end())
aLastFileTable.erase(nFndPos);
}
else if( !bNewFile )
{
aLastFileTable[rLanguageTag] = nAktTime.GetTime();
}
return pLists != nullptr;
}
bool SvxAutoCorrect::PutText( const OUString& rShort, const OUString& rLong,
LanguageType eLang )
{
LanguageTag aLanguageTag( eLang);
auto const iter = m_aLangTable.find(aLanguageTag);
if (iter != m_aLangTable.end())
return iter->second->PutText(rShort, rLong);
if(CreateLanguageFile(aLanguageTag))
return m_aLangTable.find(aLanguageTag)->second->PutText(rShort, rLong);
return false;
}
void SvxAutoCorrect::MakeCombinedChanges( std::vector<SvxAutocorrWord>& aNewEntries,
std::vector<SvxAutocorrWord>& aDeleteEntries,
LanguageType eLang )
{
LanguageTag aLanguageTag( eLang);
auto const iter = m_aLangTable.find(aLanguageTag);
if (iter != m_aLangTable.end())
{
iter->second->MakeCombinedChanges( aNewEntries, aDeleteEntries );
}
else if(CreateLanguageFile( aLanguageTag ))
{
m_aLangTable.find( aLanguageTag )->second->MakeCombinedChanges( aNewEntries, aDeleteEntries );
}
}
// - return the replacement text (only for SWG-Format, all other
// can be taken from the word list!)
bool SvxAutoCorrect::GetLongText( const OUString&, OUString& )
{
return false;
}
void SvxAutoCorrect::refreshBlockList( const uno::Reference< embed::XStorage >& )
{
}
// Text with attribution (only the SWG - SWG format!)
bool SvxAutoCorrect::PutText( const css::uno::Reference < css::embed::XStorage >&,
const OUString&, const OUString&, SfxObjectShell&, OUString& )
{
return false;
}
OUString EncryptBlockName_Imp(const OUString& rName)
{
OUStringBuffer aName;
aName.append('#').append(rName);
for (sal_Int32 nLen = rName.getLength(), nPos = 1; nPos < nLen; ++nPos)
{
if (lcl_IsInAsciiArr( "!/:.\\", aName[nPos]))
aName[nPos] &= 0x0f;
}
return aName.makeStringAndClear();
}
/* This code is copied from SwXMLTextBlocks::GeneratePackageName */
static void GeneratePackageName ( const OUString& rShort, OUString& rPackageName )
{
OString sByte(OUStringToOString(rShort, RTL_TEXTENCODING_UTF7));
OUStringBuffer aBuf(OStringToOUString(sByte, RTL_TEXTENCODING_ASCII_US));
for (sal_Int32 nPos = 0; nPos < aBuf.getLength(); ++nPos)
{
switch (aBuf[nPos])
{
case '!':
case '/':
case ':':
case '.':
case '\\':
aBuf[nPos] = '_';
break;
default:
break;
}
}
rPackageName = aBuf.makeStringAndClear();
}
static const SvxAutocorrWord* lcl_SearchWordsInList(
SvxAutoCorrectLanguageLists* pList, const OUString& rTxt,
sal_Int32& rStt, sal_Int32 nEndPos)
{
const SvxAutocorrWordList* pAutoCorrWordList = pList->GetAutocorrWordList();
return pAutoCorrWordList->SearchWordsInList( rTxt, rStt, nEndPos );
}
// the search for the words in the substitution table
const SvxAutocorrWord* SvxAutoCorrect::SearchWordsInList(
const OUString& rTxt, sal_Int32& rStt, sal_Int32 nEndPos,
SvxAutoCorrDoc&, LanguageTag& rLang )
{
const SvxAutocorrWord* pRet = nullptr;
LanguageTag aLanguageTag( rLang);
if( aLanguageTag.isSystemLocale() )
aLanguageTag.reset( MsLangId::getSystemLanguage());
/* TODO-BCP47: this is so ugly, should all maybe be a proper fallback
* list instead? */
// First search for eLang, then US-English -> English
// and last in LANGUAGE_UNDETERMINED
if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false))
{
//the language is available - so bring it on
std::unique_ptr<SvxAutoCorrectLanguageLists> const& pList = m_aLangTable.find(aLanguageTag)->second;
pRet = lcl_SearchWordsInList( pList.get(), rTxt, rStt, nEndPos );
if( pRet )
{
rLang = aLanguageTag;
return pRet;
}
}
// If it still could not be found here, then keep on searching
LanguageType eLang = aLanguageTag.getLanguageType();
// the primary language for example EN
aLanguageTag.reset(aLanguageTag.getLanguage());
LanguageType nTmpKey = aLanguageTag.getLanguageType(false);
if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED &&
(m_aLangTable.find(aLanguageTag) != m_aLangTable.end() ||
CreateLanguageFile(aLanguageTag, false)))
{
//the language is available - so bring it on
std::unique_ptr<SvxAutoCorrectLanguageLists> const& pList = m_aLangTable.find(aLanguageTag)->second;
pRet = lcl_SearchWordsInList( pList.get(), rTxt, rStt, nEndPos );
if( pRet )
{
rLang = aLanguageTag;
return pRet;
}
}
if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() ||
CreateLanguageFile(aLanguageTag, false))
{
//the language is available - so bring it on
std::unique_ptr<SvxAutoCorrectLanguageLists> const& pList = m_aLangTable.find(aLanguageTag)->second;
pRet = lcl_SearchWordsInList( pList.get(), rTxt, rStt, nEndPos );
if( pRet )
{
rLang = aLanguageTag;
return pRet;
}
}
return nullptr;
}
bool SvxAutoCorrect::FindInWrdSttExceptList( LanguageType eLang,
const OUString& sWord )
{
LanguageTag aLanguageTag( eLang);
/* TODO-BCP47: again horrible ugliness */
// First search for eLang, then primary language of eLang
// and last in LANGUAGE_UNDETERMINED
if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false))
{
//the language is available - so bring it on
auto const& pList = m_aLangTable.find(aLanguageTag)->second;
if(pList->GetWrdSttExceptList()->find(sWord) != pList->GetWrdSttExceptList()->end() )
return true;
}
// If it still could not be found here, then keep on searching
// the primary language for example EN
aLanguageTag.reset(aLanguageTag.getLanguage());
LanguageType nTmpKey = aLanguageTag.getLanguageType(false);
if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED &&
(m_aLangTable.find(aLanguageTag) != m_aLangTable.end() ||
CreateLanguageFile(aLanguageTag, false)))
{
//the language is available - so bring it on
auto const& pList = m_aLangTable.find(aLanguageTag)->second;
if(pList->GetWrdSttExceptList()->find(sWord) != pList->GetWrdSttExceptList()->end() )
return true;
}
if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() ||
CreateLanguageFile(aLanguageTag, false))
{
//the language is available - so bring it on
auto const& pList = m_aLangTable.find(aLanguageTag)->second;
if(pList->GetWrdSttExceptList()->find(sWord) != pList->GetWrdSttExceptList()->end() )
return true;
}
return false;
}
static bool lcl_FindAbbreviation(const SvStringsISortDtor* pList, const OUString& sWord)
{
OUString sAbk('~');
SvStringsISortDtor::const_iterator it = pList->find( sAbk );
SvStringsISortDtor::size_type nPos = it - pList->begin();
if( nPos < pList->size() )
{
OUString sLowerWord(sWord.toAsciiLowerCase());
OUString sAbr;
for( SvStringsISortDtor::size_type n = nPos;
n < pList->size() &&
'~' == ( sAbr = (*pList)[ n ])[ 0 ];
++n )
{
// ~ and ~. are not allowed!
if( 2 < sAbr.getLength() && sAbr.getLength() - 1 <= sWord.getLength() )
{
OUString sLowerAbk(sAbr.toAsciiLowerCase());
for (sal_Int32 i = sLowerAbk.getLength(), ii = sLowerWord.getLength(); i;)
{
if( !--i ) // agrees
return true;
if( sLowerAbk[i] != sLowerWord[--ii])
break;
}
}
}
}
OSL_ENSURE( !(nPos && '~' == (*pList)[ --nPos ][ 0 ] ),
"Wrongly sorted exception list?" );
return false;
}
bool SvxAutoCorrect::FindInCplSttExceptList(LanguageType eLang,
const OUString& sWord, bool bAbbreviation)
{
LanguageTag aLanguageTag( eLang);
/* TODO-BCP47: did I mention terrible horrible ugliness? */
// First search for eLang, then primary language of eLang
// and last in LANGUAGE_UNDETERMINED
if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false))
{
//the language is available - so bring it on
const SvStringsISortDtor* pList = m_aLangTable.find(aLanguageTag)->second->GetCplSttExceptList();
if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() )
return true;
}
// If it still could not be found here, then keep on searching
// the primary language for example EN
aLanguageTag.reset(aLanguageTag.getLanguage());
LanguageType nTmpKey = aLanguageTag.getLanguageType(false);
if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED &&
(m_aLangTable.find(aLanguageTag) != m_aLangTable.end() ||
CreateLanguageFile(aLanguageTag, false)))
{
//the language is available - so bring it on
const SvStringsISortDtor* pList = m_aLangTable.find(aLanguageTag)->second->GetCplSttExceptList();
if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() )
return true;
}
if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() ||
CreateLanguageFile(aLanguageTag, false))
{
//the language is available - so bring it on
const SvStringsISortDtor* pList = m_aLangTable.find(aLanguageTag)->second->GetCplSttExceptList();
if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() )
return true;
}
return false;
}
OUString SvxAutoCorrect::GetAutoCorrFileName( const LanguageTag& rLanguageTag,
bool bNewFile, bool bTst, bool bUnlocalized ) const
{
OUString sRet, sExt( rLanguageTag.getBcp47() );
if (bUnlocalized)
{
// we don't want variant, so we'll take "fr" instead of "fr-CA" for example
std::vector< OUString > vecFallBackStrings = rLanguageTag.getFallbackStrings(false);
if (!vecFallBackStrings.empty())
sExt = vecFallBackStrings[0];
}
sExt = "_" + sExt + ".dat";
if( bNewFile )
( sRet = sUserAutoCorrFile ) += sExt;
else if( !bTst )
( sRet = sShareAutoCorrFile ) += sExt;
else
{
// test first in the user directory - if not exist, then
( sRet = sUserAutoCorrFile ) += sExt;
if( !FStatHelper::IsDocument( sRet ))
( sRet = sShareAutoCorrFile ) += sExt;
}
return sRet;
}
SvxAutoCorrectLanguageLists::SvxAutoCorrectLanguageLists(
SvxAutoCorrect& rParent,
const OUString& rShareAutoCorrectFile,
const OUString& rUserAutoCorrectFile)
: sShareAutoCorrFile( rShareAutoCorrectFile ),
sUserAutoCorrFile( rUserAutoCorrectFile ),
aModifiedDate( Date::EMPTY ),
aModifiedTime( tools::Time::EMPTY ),
aLastCheckTime( tools::Time::EMPTY ),
rAutoCorrect(rParent),
nFlags(ACFlags::NONE)
{
}
SvxAutoCorrectLanguageLists::~SvxAutoCorrectLanguageLists()
{
}
bool SvxAutoCorrectLanguageLists::IsFileChanged_Imp()
{
// Access the file system only every 2 minutes to check the date stamp
bool bRet = false;
tools::Time nMinTime( 0, 2 );
tools::Time nAktTime( tools::Time::SYSTEM );
if( aLastCheckTime > nAktTime || // overflow?
( nAktTime -= aLastCheckTime ) > nMinTime ) // min time past
{
Date aTstDate( Date::EMPTY ); tools::Time aTstTime( tools::Time::EMPTY );
if( FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile,
&aTstDate, &aTstTime ) &&
( aModifiedDate != aTstDate || aModifiedTime != aTstTime ))
{
bRet = true;
// then remove all the lists fast!
if( (ACFlags::CplSttLstLoad & nFlags) && pCplStt_ExcptLst )
{
pCplStt_ExcptLst.reset();
}
if( (ACFlags::WrdSttLstLoad & nFlags) && pWrdStt_ExcptLst )
{
pWrdStt_ExcptLst.reset();
}
if( (ACFlags::ChgWordLstLoad & nFlags) && pAutocorr_List )
{
pAutocorr_List.reset();
}
nFlags &= ~ACFlags(ACFlags::CplSttLstLoad | ACFlags::WrdSttLstLoad | ACFlags::ChgWordLstLoad );
}
aLastCheckTime = tools::Time( tools::Time::SYSTEM );
}
return bRet;
}
void SvxAutoCorrectLanguageLists::LoadXMLExceptList_Imp(
std::unique_ptr<SvStringsISortDtor>& rpLst,
const sal_Char* pStrmName,
tools::SvRef<SotStorage>& rStg)
{
if( rpLst )
rpLst->clear();
else
rpLst.reset( new SvStringsISortDtor );
{
const OUString sStrmName( pStrmName, strlen(pStrmName), RTL_TEXTENCODING_MS_1252 );
if( rStg.is() && rStg->IsStream( sStrmName ) )
{
tools::SvRef<SotStorageStream> xStrm = rStg->OpenSotStream( sStrmName,
( StreamMode::READ | StreamMode::SHARE_DENYWRITE | StreamMode::NOCREATE ) );
if( ERRCODE_NONE != xStrm->GetError())
{
xStrm.clear();
rStg.clear();
RemoveStream_Imp( sStrmName );
}
else
{
uno::Reference< uno::XComponentContext > xContext =
comphelper::getProcessComponentContext();
xml::sax::InputSource aParserInput;
aParserInput.sSystemId = sStrmName;
xStrm->Seek( 0 );
xStrm->SetBufferSize( 8 * 1024 );
aParserInput.aInputStream = new utl::OInputStreamWrapper( *xStrm );
// get filter
uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SvXMLExceptionListImport ( xContext, *rpLst );
// connect parser and filter
uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create( xContext );
uno::Reference< xml::sax::XFastTokenHandler > xTokenHandler = static_cast< xml::sax::XFastTokenHandler* >( new SvXMLAutoCorrectTokenHandler );
xParser->setFastDocumentHandler( xFilter );
xParser->registerNamespace( "http://openoffice.org/2001/block-list", SvXMLAutoCorrectToken::NAMESPACE );
xParser->setTokenHandler( xTokenHandler );
// parse
try
{
xParser->parseStream( aParserInput );
}
catch( const xml::sax::SAXParseException& )
{
// re throw ?
}
catch( const xml::sax::SAXException& )
{
// re throw ?
}
catch( const io::IOException& )
{
// re throw ?
}
}
}
// Set time stamp
FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile,
&aModifiedDate, &aModifiedTime );
aLastCheckTime = tools::Time( tools::Time::SYSTEM );
}
}
void SvxAutoCorrectLanguageLists::SaveExceptList_Imp(
const SvStringsISortDtor& rLst,
const sal_Char* pStrmName,
tools::SvRef<SotStorage> const &rStg,
bool bConvert )
{
if( rStg.is() )
{
OUString sStrmName( pStrmName, strlen(pStrmName), RTL_TEXTENCODING_MS_1252 );
if( rLst.empty() )
{
rStg->Remove( sStrmName );
rStg->Commit();
}
else
{
tools::SvRef<SotStorageStream> xStrm = rStg->OpenSotStream( sStrmName,
( StreamMode::READ | StreamMode::WRITE | StreamMode::SHARE_DENYWRITE ) );
if( xStrm.is() )
{
xStrm->SetSize( 0 );
xStrm->SetBufferSize( 8192 );
xStrm->SetProperty( "MediaType", Any(OUString( "text/xml" )) );
uno::Reference< uno::XComponentContext > xContext =
comphelper::getProcessComponentContext();
uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext);
uno::Reference < io::XOutputStream> xOut = new utl::OOutputStreamWrapper( *xStrm );
xWriter->setOutputStream(xOut);
uno::Reference < xml::sax::XDocumentHandler > xHandler(xWriter, UNO_QUERY_THROW);
rtl::Reference< SvXMLExceptionListExport > xExp( new SvXMLExceptionListExport( xContext, rLst, sStrmName, xHandler ) );
xExp->exportDoc( XML_BLOCK_LIST );
xStrm->Commit();
if( xStrm->GetError() == ERRCODE_NONE )
{
xStrm.clear();
if (!bConvert)
{
rStg->Commit();
if( ERRCODE_NONE != rStg->GetError() )
{
rStg->Remove( sStrmName );
rStg->Commit();
}
}
}
}
}
}
}
SvxAutocorrWordList* SvxAutoCorrectLanguageLists::LoadAutocorrWordList()
{
if( pAutocorr_List )
pAutocorr_List->DeleteAndDestroyAll();
else
pAutocorr_List.reset( new SvxAutocorrWordList() );
try
{
uno::Reference < embed::XStorage > xStg = comphelper::OStorageHelper::GetStorageFromURL( sShareAutoCorrFile, embed::ElementModes::READ );
uno::Reference < io::XStream > xStrm = xStg->openStreamElement( pXMLImplAutocorr_ListStr, embed::ElementModes::READ );
uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext();
xml::sax::InputSource aParserInput;
aParserInput.sSystemId = pXMLImplAutocorr_ListStr;
aParserInput.aInputStream = xStrm->getInputStream();
// get parser
uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create(xContext);
SAL_INFO("editeng", "AutoCorrect Import" );
uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SvXMLAutoCorrectImport( xContext, pAutocorr_List.get(), rAutoCorrect, xStg );
uno::Reference< xml::sax::XFastTokenHandler > xTokenHandler = static_cast< xml::sax::XFastTokenHandler* >( new SvXMLAutoCorrectTokenHandler );
// connect parser and filter
xParser->setFastDocumentHandler( xFilter );
xParser->registerNamespace( "http://openoffice.org/2001/block-list", SvXMLAutoCorrectToken::NAMESPACE );
xParser->setTokenHandler(xTokenHandler);
// parse
xParser->parseStream( aParserInput );
}
catch ( const uno::Exception& )
{
}
// Set time stamp
FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile,
&aModifiedDate, &aModifiedTime );
aLastCheckTime = tools::Time( tools::Time::SYSTEM );
return pAutocorr_List.get();
}
const SvxAutocorrWordList* SvxAutoCorrectLanguageLists::GetAutocorrWordList()
{
if( !( ACFlags::ChgWordLstLoad & nFlags ) || IsFileChanged_Imp() )
{
LoadAutocorrWordList();
if( !pAutocorr_List )
{
OSL_ENSURE( false, "No valid list" );
pAutocorr_List.reset( new SvxAutocorrWordList() );
}
nFlags |= ACFlags::ChgWordLstLoad;
}
return pAutocorr_List.get();
}
SvStringsISortDtor* SvxAutoCorrectLanguageLists::GetCplSttExceptList()
{
if( !( ACFlags::CplSttLstLoad & nFlags ) || IsFileChanged_Imp() )
{
LoadCplSttExceptList();
if( !pCplStt_ExcptLst )
{
OSL_ENSURE( false, "No valid list" );
pCplStt_ExcptLst.reset( new SvStringsISortDtor );
}
nFlags |= ACFlags::CplSttLstLoad;
}
return pCplStt_ExcptLst.get();
}
bool SvxAutoCorrectLanguageLists::AddToCplSttExceptList(const OUString& rNew)
{
bool bRet = false;
if( !rNew.isEmpty() && GetCplSttExceptList()->insert( rNew ).second )
{
MakeUserStorage_Impl();
tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE );
SaveExceptList_Imp( *pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg );
xStg = nullptr;
// Set time stamp
FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile,
&aModifiedDate, &aModifiedTime );
aLastCheckTime = tools::Time( tools::Time::SYSTEM );
bRet = true;
}
return bRet;
}
bool SvxAutoCorrectLanguageLists::AddToWrdSttExceptList(const OUString& rNew)
{
bool bRet = false;
SvStringsISortDtor* pExceptList = LoadWrdSttExceptList();
if( !rNew.isEmpty() && pExceptList && pExceptList->insert( rNew ).second )
{
MakeUserStorage_Impl();
tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE );
SaveExceptList_Imp( *pWrdStt_ExcptLst, pXMLImplWrdStt_ExcptLstStr, xStg );
xStg = nullptr;
// Set time stamp
FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile,
&aModifiedDate, &aModifiedTime );
aLastCheckTime = tools::Time( tools::Time::SYSTEM );
bRet = true;
}
return bRet;
}
SvStringsISortDtor* SvxAutoCorrectLanguageLists::LoadCplSttExceptList()
{
try
{
tools::SvRef<SotStorage> xStg = new SotStorage( sShareAutoCorrFile, StreamMode::READ | StreamMode::SHARE_DENYNONE );
OUString sTemp ( pXMLImplCplStt_ExcptLstStr );
if( xStg.is() && xStg->IsContained( sTemp ) )
LoadXMLExceptList_Imp( pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg );
}
catch (const css::ucb::ContentCreationException&)
{
}
return pCplStt_ExcptLst.get();
}
void SvxAutoCorrectLanguageLists::SaveCplSttExceptList()
{
MakeUserStorage_Impl();
tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE );
SaveExceptList_Imp( *pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg );
xStg = nullptr;
// Set time stamp
FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile,
&aModifiedDate, &aModifiedTime );
aLastCheckTime = tools::Time( tools::Time::SYSTEM );
}
SvStringsISortDtor* SvxAutoCorrectLanguageLists::LoadWrdSttExceptList()
{
try
{
tools::SvRef<SotStorage> xStg = new SotStorage( sShareAutoCorrFile, StreamMode::READ | StreamMode::SHARE_DENYNONE );
OUString sTemp ( pXMLImplWrdStt_ExcptLstStr );
if( xStg.is() && xStg->IsContained( sTemp ) )
LoadXMLExceptList_Imp( pWrdStt_ExcptLst, pXMLImplWrdStt_ExcptLstStr, xStg );
}
catch (const css::ucb::ContentCreationException &e)
{
SAL_WARN("editeng", "SvxAutoCorrectLanguageLists::LoadWrdSttExceptList: Caught " << e);
}
return pWrdStt_ExcptLst.get();
}
void SvxAutoCorrectLanguageLists::SaveWrdSttExceptList()
{
MakeUserStorage_Impl();
tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE );
SaveExceptList_Imp( *pWrdStt_ExcptLst, pXMLImplWrdStt_ExcptLstStr, xStg );
xStg = nullptr;
// Set time stamp
FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile,
&aModifiedDate, &aModifiedTime );
aLastCheckTime = tools::Time( tools::Time::SYSTEM );
}
SvStringsISortDtor* SvxAutoCorrectLanguageLists::GetWrdSttExceptList()
{
if( !( ACFlags::WrdSttLstLoad & nFlags ) || IsFileChanged_Imp() )
{
LoadWrdSttExceptList();
if( !pWrdStt_ExcptLst )
{
OSL_ENSURE( false, "No valid list" );
pWrdStt_ExcptLst.reset( new SvStringsISortDtor );
}
nFlags |= ACFlags::WrdSttLstLoad;
}
return pWrdStt_ExcptLst.get();
}
void SvxAutoCorrectLanguageLists::RemoveStream_Imp( const OUString& rName )
{
if( sShareAutoCorrFile != sUserAutoCorrFile )
{
tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE );
if( xStg.is() && ERRCODE_NONE == xStg->GetError() &&
xStg->IsStream( rName ) )
{
xStg->Remove( rName );
xStg->Commit();
xStg = nullptr;
}
}
}
void SvxAutoCorrectLanguageLists::MakeUserStorage_Impl()
{
// The conversion needs to happen if the file is already in the user
// directory and is in the old format. Additionally it needs to
// happen when the file is being copied from share to user.
bool bError = false, bConvert = false, bCopy = false;
INetURLObject aDest;
INetURLObject aSource;
if (sUserAutoCorrFile != sShareAutoCorrFile )
{
aSource = INetURLObject ( sShareAutoCorrFile );
aDest = INetURLObject ( sUserAutoCorrFile );
if ( SotStorage::IsOLEStorage ( sShareAutoCorrFile ) )
{
aDest.SetExtension ( "bak" );
bConvert = true;
}
bCopy = true;
}
else if ( SotStorage::IsOLEStorage ( sUserAutoCorrFile ) )
{
aSource = INetURLObject ( sUserAutoCorrFile );
aDest = INetURLObject ( sUserAutoCorrFile );
aDest.SetExtension ( "bak" );
bCopy = bConvert = true;
}
if (bCopy)
{
try
{
OUString sMain(aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ));
sal_Int32 nSlashPos = sMain.lastIndexOf('/');
sMain = sMain.copy(0, nSlashPos);
::ucbhelper::Content aNewContent( sMain, uno::Reference< XCommandEnvironment >(), comphelper::getProcessComponentContext() );
TransferInfo aInfo;
aInfo.NameClash = NameClash::OVERWRITE;
aInfo.NewTitle = aDest.GetName();
aInfo.SourceURL = aSource.GetMainURL( INetURLObject::DecodeMechanism::ToIUri );
aInfo.MoveData = false;
aNewContent.executeCommand( "transfer", Any(aInfo));
}
catch (...)
{
bError = true;
}
}
if (bConvert && !bError)
{
tools::SvRef<SotStorage> xSrcStg = new SotStorage( aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ), StreamMode::READ );
tools::SvRef<SotStorage> xDstStg = new SotStorage( sUserAutoCorrFile, StreamMode::WRITE );
if( xSrcStg.is() && xDstStg.is() )
{
OUString sXMLWord ( pXMLImplWrdStt_ExcptLstStr );
OUString sXMLSentence ( pXMLImplCplStt_ExcptLstStr );
std::unique_ptr<SvStringsISortDtor> pTmpWordList;
if (xSrcStg->IsContained( sXMLWord ) )
LoadXMLExceptList_Imp( pTmpWordList, pXMLImplWrdStt_ExcptLstStr, xSrcStg );
if (pTmpWordList)
{
SaveExceptList_Imp( *pTmpWordList, pXMLImplWrdStt_ExcptLstStr, xDstStg, true );
pTmpWordList.reset();
}
if (xSrcStg->IsContained( sXMLSentence ) )
LoadXMLExceptList_Imp( pTmpWordList, pXMLImplCplStt_ExcptLstStr, xSrcStg );
if (pTmpWordList)
{
SaveExceptList_Imp( *pTmpWordList, pXMLImplCplStt_ExcptLstStr, xDstStg, true );
pTmpWordList->clear();
}
GetAutocorrWordList();
MakeBlocklist_Imp( *xDstStg );
sShareAutoCorrFile = sUserAutoCorrFile;
xDstStg = nullptr;
try
{
::ucbhelper::Content aContent ( aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ), uno::Reference < XCommandEnvironment >(), comphelper::getProcessComponentContext() );
aContent.executeCommand ( "delete", makeAny ( true ) );
}
catch (...)
{
}
}
}
else if( bCopy && !bError )
sShareAutoCorrFile = sUserAutoCorrFile;
}
bool SvxAutoCorrectLanguageLists::MakeBlocklist_Imp( SotStorage& rStg )
{
bool bRet = true, bRemove = !pAutocorr_List || pAutocorr_List->empty();
if( !bRemove )
{
tools::SvRef<SotStorageStream> refList = rStg.OpenSotStream( pXMLImplAutocorr_ListStr,
( StreamMode::READ | StreamMode::WRITE | StreamMode::SHARE_DENYWRITE ) );
if( refList.is() )
{
refList->SetSize( 0 );
refList->SetBufferSize( 8192 );
refList->SetProperty( "MediaType", Any(OUString( "text/xml" )) );
uno::Reference< uno::XComponentContext > xContext =
comphelper::getProcessComponentContext();
uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext);
uno::Reference < io::XOutputStream> xOut = new utl::OOutputStreamWrapper( *refList );
xWriter->setOutputStream(xOut);
uno::Reference<xml::sax::XDocumentHandler> xHandler(xWriter, uno::UNO_QUERY);
rtl::Reference< SvXMLAutoCorrectExport > xExp( new SvXMLAutoCorrectExport( xContext, pAutocorr_List.get(), pXMLImplAutocorr_ListStr, xHandler ) );
xExp->exportDoc( XML_BLOCK_LIST );
refList->Commit();
bRet = ERRCODE_NONE == refList->GetError();
if( bRet )
{
refList.clear();
rStg.Commit();
if( ERRCODE_NONE != rStg.GetError() )
{
bRemove = true;
bRet = false;
}
}
}
else
bRet = false;
}
if( bRemove )
{
rStg.Remove( pXMLImplAutocorr_ListStr );
rStg.Commit();
}
return bRet;
}
bool SvxAutoCorrectLanguageLists::MakeCombinedChanges( std::vector<SvxAutocorrWord>& aNewEntries, std::vector<SvxAutocorrWord>& aDeleteEntries )
{
// First get the current list!
GetAutocorrWordList();
MakeUserStorage_Impl();
tools::SvRef<SotStorage> xStorage = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE );
bool bRet = xStorage.is() && ERRCODE_NONE == xStorage->GetError();
if( bRet )
{
for (SvxAutocorrWord & aWordToDelete : aDeleteEntries)
{
std::unique_ptr<SvxAutocorrWord> pFoundEntry = pAutocorr_List->FindAndRemove( &aWordToDelete );
if( pFoundEntry )
{
if( !pFoundEntry->IsTextOnly() )
{
OUString aName( aWordToDelete.GetShort() );
if (xStorage->IsOLEStorage())
aName = EncryptBlockName_Imp(aName);
else
GeneratePackageName ( aWordToDelete.GetShort(), aName );
if( xStorage->IsContained( aName ) )
{
xStorage->Remove( aName );
bRet = xStorage->Commit();
}
}
}
}
for (SvxAutocorrWord & aNewEntrie : aNewEntries)
{
std::unique_ptr<SvxAutocorrWord> pWordToAdd(new SvxAutocorrWord( aNewEntrie.GetShort(), aNewEntrie.GetLong(), true ));
std::unique_ptr<SvxAutocorrWord> pRemoved = pAutocorr_List->FindAndRemove( pWordToAdd.get() );
if( pRemoved )
{
if( !pRemoved->IsTextOnly() )
{
// Still have to remove the Storage
OUString sStorageName( pWordToAdd->GetShort() );
if (xStorage->IsOLEStorage())
sStorageName = EncryptBlockName_Imp(sStorageName);
else
GeneratePackageName ( pWordToAdd->GetShort(), sStorageName);
if( xStorage->IsContained( sStorageName ) )
xStorage->Remove( sStorageName );
}
}
bRet = pAutocorr_List->Insert( std::move(pWordToAdd) );
if ( !bRet )
{
break;
}
}
if ( bRet )
{
bRet = MakeBlocklist_Imp( *xStorage );
}
}
return bRet;
}
bool SvxAutoCorrectLanguageLists::PutText( const OUString& rShort, const OUString& rLong )
{
// First get the current list!
GetAutocorrWordList();
MakeUserStorage_Impl();
tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE );
bool bRet = xStg.is() && ERRCODE_NONE == xStg->GetError();
// Update the word list
if( bRet )
{
std::unique_ptr<SvxAutocorrWord> pNew(new SvxAutocorrWord( rShort, rLong, true ));
std::unique_ptr<SvxAutocorrWord> pRemove = pAutocorr_List->FindAndRemove( pNew.get() );
if( pRemove )
{
if( !pRemove->IsTextOnly() )
{
// Still have to remove the Storage
OUString sStgNm( rShort );
if (xStg->IsOLEStorage())
sStgNm = EncryptBlockName_Imp(sStgNm);
else
GeneratePackageName ( rShort, sStgNm);
if( xStg->IsContained( sStgNm ) )
xStg->Remove( sStgNm );
}
}
if( pAutocorr_List->Insert( std::move(pNew) ) )
{
bRet = MakeBlocklist_Imp( *xStg );
xStg = nullptr;
}
else
{
bRet = false;
}
}
return bRet;
}
void SvxAutoCorrectLanguageLists::PutText( const OUString& rShort,
SfxObjectShell& rShell )
{
// First get the current list!
GetAutocorrWordList();
MakeUserStorage_Impl();
OUString sLong;
try
{
uno::Reference < embed::XStorage > xStg = comphelper::OStorageHelper::GetStorageFromURL( sUserAutoCorrFile, embed::ElementModes::READWRITE );
bool bRet = rAutoCorrect.PutText( xStg, sUserAutoCorrFile, rShort, rShell, sLong );
xStg = nullptr;
// Update the word list
if( bRet )
{
std::unique_ptr<SvxAutocorrWord> pNew(new SvxAutocorrWord( rShort, sLong, false ));
if( pAutocorr_List->Insert( std::move(pNew) ) )
{
tools::SvRef<SotStorage> xStor = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE );
MakeBlocklist_Imp( *xStor );
}
}
}
catch ( const uno::Exception& )
{
}
}
// Keep the list sorted ...
struct CompareSvxAutocorrWordList
{
bool operator()( SvxAutocorrWord* const& lhs, SvxAutocorrWord* const& rhs ) const
{
CollatorWrapper& rCmp = ::GetCollatorWrapper();
return rCmp.compareString( lhs->GetShort(), rhs->GetShort() ) < 0;
}
};
namespace {
// can't use std::unique_ptr until we have C++14
typedef std::set<SvxAutocorrWord*, CompareSvxAutocorrWordList> AutocorrWordSetType;
typedef std::unordered_map<OUString, std::unique_ptr<SvxAutocorrWord>> AutocorrWordHashType;
}
struct SvxAutocorrWordList::Impl
{
// only one of these contains the data
mutable AutocorrWordSetType maSet;
mutable AutocorrWordHashType maHash; // key is 'Short'
void DeleteAndDestroyAll()
{
maHash.clear();
for (auto const& elem : maSet)
delete elem;
maSet.clear();
}
};
SvxAutocorrWordList::SvxAutocorrWordList() : mpImpl(new Impl) {}
SvxAutocorrWordList::~SvxAutocorrWordList()
{
mpImpl->DeleteAndDestroyAll();
}
void SvxAutocorrWordList::DeleteAndDestroyAll()
{
mpImpl->DeleteAndDestroyAll();
}
// returns true if inserted
bool SvxAutocorrWordList::Insert(std::unique_ptr<SvxAutocorrWord> pWord) const
{
if ( mpImpl->maSet.empty() ) // use the hash
{
OUString aShort( pWord->GetShort() );
return mpImpl->maHash.insert( std::pair<OUString, std::unique_ptr<SvxAutocorrWord> >( aShort, std::move(pWord) ) ).second;
}
else
return mpImpl->maSet.insert( pWord.release() ).second;
}
void SvxAutocorrWordList::LoadEntry(const OUString& sWrong, const OUString& sRight, bool bOnlyTxt)
{
std::unique_ptr<SvxAutocorrWord> pNew(new SvxAutocorrWord( sWrong, sRight, bOnlyTxt ));
(void)Insert(std::move(pNew));
}
bool SvxAutocorrWordList::empty() const
{
return mpImpl->maHash.empty() && mpImpl->maSet.empty();
}
std::unique_ptr<SvxAutocorrWord> SvxAutocorrWordList::FindAndRemove(SvxAutocorrWord *pWord)
{
std::unique_ptr<SvxAutocorrWord> pMatch;
if ( mpImpl->maSet.empty() ) // use the hash
{
AutocorrWordHashType::iterator it = mpImpl->maHash.find( pWord->GetShort() );
if( it != mpImpl->maHash.end() )
{
pMatch = std::move(it->second);
mpImpl->maHash.erase (it);
}
}
else
{
AutocorrWordSetType::iterator it = mpImpl->maSet.find( pWord );
if( it != mpImpl->maSet.end() )
{
pMatch = std::unique_ptr<SvxAutocorrWord>(*it);
mpImpl->maSet.erase (it);
}
}
return pMatch;
}
// return the sorted contents - defer sorting until we have to.
SvxAutocorrWordList::Content SvxAutocorrWordList::getSortedContent() const
{
Content aContent;
// convert from hash to set permanently
if ( mpImpl->maSet.empty() )
{
// This beast has some O(N log(N)) in a terribly slow ICU collate fn.
for (auto & elem : mpImpl->maHash)
mpImpl->maSet.insert( elem.second.release() );
mpImpl->maHash.clear();
}
for (auto const& elem : mpImpl->maSet)
aContent.push_back(elem);
return aContent;
}
const SvxAutocorrWord* SvxAutocorrWordList::WordMatches(const SvxAutocorrWord *pFnd,
const OUString &rTxt,
sal_Int32 &rStt,
sal_Int32 nEndPos) const
{
const OUString& rChk = pFnd->GetShort();
sal_Int32 left_wildcard = rChk.startsWith( ".*" ) ? 2 : 0; // ".*word" pattern?
sal_Int32 right_wildcard = rChk.endsWith( ".*" ) ? 2 : 0; // "word.*" pattern?
sal_Int32 nSttWdPos = nEndPos;
// direct replacement of keywords surrounded by colons (for example, ":name:")
bool bColonNameColon = rTxt.getLength() > nEndPos &&
rTxt[nEndPos] == ':' && rChk[0] == ':' && rChk.endsWith(":");
if ( nEndPos + (bColonNameColon ? 1 : 0) >= rChk.getLength() - left_wildcard - right_wildcard )
{
bool bWasWordDelim = false;
sal_Int32 nCalcStt = nEndPos - rChk.getLength() + left_wildcard;
if (bColonNameColon)
nCalcStt++;
if( !right_wildcard && ( !nCalcStt || nCalcStt == rStt || left_wildcard || bColonNameColon ||
( nCalcStt < rStt &&
IsWordDelim( rTxt[ nCalcStt - 1 ] ))) )
{
TransliterationWrapper& rCmp = GetIgnoreTranslWrapper();
OUString sWord = rTxt.copy(nCalcStt, rChk.getLength() - left_wildcard);
if( (!left_wildcard && rCmp.isEqual( rChk, sWord )) || (left_wildcard && rCmp.isEqual( rChk.copy(left_wildcard), sWord) ))
{
rStt = nCalcStt;
if (!left_wildcard)
{
// fdo#33899 avoid "1/2", "1/3".. to be replaced by fractions in dates, eg. 1/2/14
if (rTxt.getLength() > nEndPos && rTxt[nEndPos] == '/' && rChk.indexOf('/') != -1)
return nullptr;
return pFnd;
}
// get the first word delimiter position before the matching ".*word" pattern
while( rStt && !(bWasWordDelim = IsWordDelim( rTxt[ --rStt ])))
;
if (bWasWordDelim) rStt++;
OUString left_pattern = rTxt.copy(rStt, nEndPos - rStt - rChk.getLength() + left_wildcard);
// avoid double spaces before simple "word" replacement
left_pattern += (left_pattern.getLength() == 0 && pFnd->GetLong()[0] == 0x20) ? pFnd->GetLong().copy(1) : pFnd->GetLong();
SvxAutocorrWord* pNew = new SvxAutocorrWord(rTxt.copy(rStt, nEndPos - rStt), left_pattern);
if( Insert( std::unique_ptr<SvxAutocorrWord>(pNew) ) )
return pNew;
}
} else
// match "word.*" or ".*word.*" patterns, eg. "i18n.*", ".*---.*", TODO: add transliteration support
if ( right_wildcard )
{
OUString sTmp( rChk.copy( left_wildcard, rChk.getLength() - left_wildcard - right_wildcard ) );
// Get the last word delimiter position
bool not_suffix;
while( nSttWdPos && !(bWasWordDelim = IsWordDelim( rTxt[ --nSttWdPos ])))
;
// search the first occurrence (with a left word delimitation, if needed)
sal_Int32 nFndPos = -1;
do {
nFndPos = rTxt.indexOf( sTmp, nFndPos + 1);
if (nFndPos == -1)
break;
not_suffix = bWasWordDelim && (nSttWdPos >= (nFndPos + sTmp.getLength()));
} while ( (!left_wildcard && nFndPos && !IsWordDelim( rTxt[ nFndPos - 1 ])) || not_suffix );
if ( nFndPos != -1 )
{
sal_Int32 extra_repl = nFndPos + sTmp.getLength() > nEndPos ? 1: 0; // for patterns with terminating characters, eg. "a:"
if ( left_wildcard )
{
// get the first word delimiter position before the matching ".*word.*" pattern
while( nFndPos && !(bWasWordDelim = IsWordDelim( rTxt[ --nFndPos ])))
;
if (bWasWordDelim) nFndPos++;
}
if (nEndPos + extra_repl <= nFndPos)
{
return nullptr;
}
// store matching pattern and its replacement as a new list item, eg. "i18ns" -> "internationalizations"
OUString aShort = rTxt.copy(nFndPos, nEndPos - nFndPos + extra_repl);
OUString aLong;
rStt = nFndPos;
if ( !left_wildcard )
{
sal_Int32 siz = nEndPos - nFndPos - sTmp.getLength();
aLong = pFnd->GetLong() + (siz > 0 ? rTxt.copy(nFndPos + sTmp.getLength(), siz) : "");
} else {
OUStringBuffer buf;
do {
nSttWdPos = rTxt.indexOf( sTmp, nFndPos);
if (nSttWdPos != -1)
{
sal_Int32 nTmp(nFndPos);
while (nTmp < nSttWdPos && !IsWordDelim(rTxt[nTmp]))
nTmp++;
if (nTmp < nSttWdPos)
break; // word delimiter found
buf.appendCopy(rTxt, nFndPos, nSttWdPos - nFndPos).append(pFnd->GetLong());
nFndPos = nSttWdPos + sTmp.getLength();
}
} while (nSttWdPos != -1);
if (nEndPos - nFndPos > extra_repl)
buf.appendCopy(rTxt, nFndPos, nEndPos - nFndPos);
aLong = buf.makeStringAndClear();
}
SvxAutocorrWord* pNew = new SvxAutocorrWord(aShort, aLong);
if ( Insert( std::unique_ptr<SvxAutocorrWord>(pNew) ) )
{
if ( (rTxt.getLength() > nEndPos && IsWordDelim(rTxt[nEndPos])) || rTxt.getLength() == nEndPos )
return pNew;
}
}
}
}
return nullptr;
}
const SvxAutocorrWord* SvxAutocorrWordList::SearchWordsInList(const OUString& rTxt, sal_Int32& rStt,
sal_Int32 nEndPos) const
{
for (auto const& elem : mpImpl->maHash)
{
if( const SvxAutocorrWord *aTmp = WordMatches( elem.second.get(), rTxt, rStt, nEndPos ) )
return aTmp;
}
for (auto const& elem : mpImpl->maSet)
{
if( const SvxAutocorrWord *aTmp = WordMatches( elem, rTxt, rStt, nEndPos ) )
return aTmp;
}
return nullptr;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V547 Expression '!bAtStart' is always true.
↑ V1019 Compound assignment expression 'nAktTime -= aLastCheckTime' is used inside condition.
↑ V572 It is odd that the object which was created using 'new' operator is immediately cast to another type.
↑ V572 It is odd that the object which was created using 'new' operator is immediately cast to another type.