/* -*- 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 .
*/
// possible delimiter characters within URLs for word breaking staticbool lcl_IsDelim( const sal_Unicode c )
{ return'#' == c || '$' == c || '%' == c || '&' == c || '+' == c || ',' == c || '-' == c || '.' == c || '/' == c || ':' == c || ';' == c || '=' == c || '?' == c || '@' == c || '_' == c;
}
// allow to check normal text with hyperlink by recognizing (parts of) URLs staticbool lcl_IsURL(std::u16string_view rWord, const SwTextNode &rNode, sal_Int32 nBegin, sal_Int32 nLen)
{ // not a text with hyperlink if ( !rNode.GetTextAttrAt(nBegin, RES_TXTATR_INETFMT) ) returnfalse;
// there is a dot in the word, which is not a period ("example.org") const size_t nPosAt = rWord.find('.'); if (nPosAt != std::u16string_view::npos && nPosAt < rWord.length() - 1) returntrue;
// an e-mail address ("user@example") if ( rWord.find('@') != std::u16string_view::npos ) returntrue;
const OUString& rText = rNode.GetText();
// scheme (e.g. "http" in "http://" or "mailto" in "mailto:address"): // word is followed by 1) ':' + an alphanumeric character; 2) or ':' + a delimiter if ( nBegin + nLen + 2 <= rText.getLength() && ':' == rText[nBegin + nLen] )
{
sal_Unicode c = rText[nBegin + nLen + 1]; if ( u_isalnum(c) || lcl_IsDelim(c) ) returntrue;
}
// path, query, fragment (e.g. "path" in "example.org/path"): // word is preceded by 1) an alphanumeric character + a delimiter; 2) or two delimiters if ( 2 <= nBegin && lcl_IsDelim(rText[nBegin - 1]) )
{
sal_Unicode c = rText[nBegin - 2]; if ( u_isalnum(c) || lcl_IsDelim(c) ) returntrue;
}
returnfalse;
}
/* * This has basically the same function as SwScriptInfo::MaskHiddenRanges, * only for deleted redlines
*/
// If called from word count or from spell checking, deleted redlines // should be masked: if ( bShowChg )
{
nRedlinesMasked = lcl_MaskRedlines( rNode, rText, nStt, nEnd, cChar );
}
// If called from word count, we want to mask the hidden ranges even // if they are visible: if ( bHideHidden )
{
nHiddenCharsMasked =
SwScriptInfo::MaskHiddenRanges( rNode, rText, nStt, nEnd, cChar );
}
/** * Used for automatic styles. Used during RstAttr.
*/ staticbool lcl_HaveCommonAttributes( IStyleAccess& rStyleAccess, const SfxItemSet* pSet1,
sal_uInt16 nWhichId, const SfxItemSet& rSet2,
std::shared_ptr<SfxItemSet>& pStyleHandle )
{ bool bRet = false;
std::unique_ptr<SfxItemSet> pNewSet;
if ( !pSet1 )
{
OSL_ENSURE( nWhichId, "lcl_HaveCommonAttributes not used correctly" ); if ( SfxItemState::SET == rSet2.GetItemState( nWhichId, false ) )
{
pNewSet = rSet2.Clone();
pNewSet->ClearItem( nWhichId );
}
} elseif ( pSet1->Count() )
{
SfxItemIter aIter( *pSet1 ); const SfxPoolItem* pItem = aIter.GetCurItem(); do
{ if ( SfxItemState::SET == rSet2.GetItemState( pItem->Which(), false ) )
{ if ( !pNewSet )
pNewSet = rSet2.Clone();
pNewSet->ClearItem( pItem->Which() );
}
pItem = aIter.NextItem();
} while (pItem);
}
if ( pNewSet )
{ if ( pNewSet->Count() )
pStyleHandle = rStyleAccess.getAutomaticStyle( *pNewSet, IStyleAccess::AUTO_STYLE_CHAR );
bRet = true;
}
return bRet;
}
/** Delete all attributes * * 5 cases: * 1) The attribute is completely in the deletion range: * -> delete it * 2) The end of the attribute is in the deletion range: * -> delete it, then re-insert it with new end * 3) The start of the attribute is in the deletion range: * -> delete it, then re-insert it with new start * 4) The attribute contains the deletion range: * Split, i.e., * -> Delete, re-insert from old start to start of deletion range * -> insert new attribute from end of deletion range to old end * 5) The attribute is outside the deletion range * -> nothing to do * * @param nStt starting position * @param nLen length of the deletion * @param nWhich ??? * @param pSet ??? * @param bInclRefToxMark ???
*/
sal_Int32 nEnd = nStt + nLen;
{ // enlarge range for the reset of text attributes in case of an overlapping input field const SwTextInputField* pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(nStt, RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent)); if ( pTextInputField == nullptr )
{
pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(nEnd, RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
} if ( pTextInputField != nullptr )
{ if ( nStt > pTextInputField->GetStart() )
{
nStt = pTextInputField->GetStart();
} if ( nEnd < *(pTextInputField->End()) )
{
nEnd = *(pTextInputField->End());
}
}
}
bool bChanged = false;
// nMin and nMax initialized to maximum / minimum (inverse)
sal_Int32 nMin = m_Text.getLength();
sal_Int32 nMax = nStt; constbool bNoLen = nMin == 0;
// We have to remember the "new" attributes that have // been introduced by splitting surrounding attributes (case 2,3,4).
std::vector<SwTextAttr *> newAttributes;
std::vector<SwTextAttr *> delAttributes;
// iterate over attribute array until start of attribute is behind deletion range
m_pSwpHints->SortIfNeedBe(); // trigger sorting now, we don't want it during iteration
size_t i = 0;
sal_Int32 nAttrStart = sal_Int32();
SwTextAttr *pHt = nullptr; while ( (i < m_pSwpHints->Count())
&& ( ( ( nAttrStart = m_pSwpHints->GetWithoutResorting(i)->GetStart()) < nEnd )
|| nLen==0 || (nEnd == nAttrStart && nAttrStart == m_Text.getLength()))
&& !bExactRange)
{
pHt = m_pSwpHints->GetWithoutResorting(i);
// attributes without end stay in! // but consider <bInclRefToxMark> used by Undo const sal_Int32* const pAttrEnd = pHt->GetEnd(); constbool bKeepAttrWithoutEnd =
pAttrEnd == nullptr
&& ( !bInclRefToxMark
|| ( RES_TXTATR_REFMARK != pHt->Which()
&& RES_TXTATR_TOXMARK != pHt->Which()
&& RES_TXTATR_META != pHt->Which()
&& RES_TXTATR_METAFIELD != pHt->Which() ) ); if ( bKeepAttrWithoutEnd )
{
i++; continue;
} // attributes with content stay in if ( pHt->HasContent() )
{
++i; continue;
}
// Default behavior is to process all attributes: bool bSkipAttr = false;
std::shared_ptr<SfxItemSet> pStyleHandle;
// 1. case: We want to reset only the attributes listed in pSet: if ( pSet )
{
bSkipAttr = SfxItemState::SET != pSet->GetItemState( pHt->Which(), false ); if ( bSkipAttr && RES_TXTATR_AUTOFMT == pHt->Which() )
{ // if the current attribute is an autostyle, we have to check if the autostyle // and pSet have any attributes in common. If so, pStyleHandle will contain // a handle to AutoStyle / pSet:
bSkipAttr = !lcl_HaveCommonAttributes( getIDocumentStyleAccess(), pSet, 0, *static_cast<const SwFormatAutoFormat&>(pHt->GetAttr()).GetStyleHandle(), pStyleHandle );
}
} elseif ( nWhich )
{ // 2. case: We want to reset only the attributes with WhichId nWhich:
bSkipAttr = nWhich != pHt->Which(); if ( bSkipAttr && RES_TXTATR_AUTOFMT == pHt->Which() )
{
bSkipAttr = !lcl_HaveCommonAttributes( getIDocumentStyleAccess(), nullptr, nWhich, *static_cast<const SwFormatAutoFormat&>(pHt->GetAttr()).GetStyleHandle(), pStyleHandle );
}
} elseif ( !bInclRefToxMark )
{ // 3. case: Reset all attributes except from ref/toxmarks: // skip hints with CH_TXTATR here // (deleting those is ONLY allowed for UNDO!)
bSkipAttr = RES_TXTATR_REFMARK == pHt->Which()
|| RES_TXTATR_TOXMARK == pHt->Which()
|| RES_TXTATR_META == pHt->Which()
|| RES_TXTATR_METAFIELD == pHt->Which();
}
if ( bSkipAttr )
{
i++; continue;
}
if (nStt <= nAttrStart) // Case: 1,3,5
{ const sal_Int32 nAttrEnd = pAttrEnd != nullptr
? *pAttrEnd
: nAttrStart; if (nEnd > nAttrStart
|| (nEnd == nAttrEnd && nEnd == nAttrStart)) // Case: 1,3
{ if ( nMin > nAttrStart )
nMin = nAttrStart; if ( nMax < nAttrEnd )
nMax = nAttrEnd; // If only a no-extent hint is deleted, no resorting is needed
bChanged = bChanged || nEnd > nAttrStart || bNoLen; if (nAttrEnd <= nEnd) // Case: 1
{
delAttributes.push_back(pHt);
if ( pStyleHandle )
{
SwTextAttr* pNew = MakeTextAttr( GetDoc(),
*pStyleHandle, nAttrStart, nAttrEnd );
newAttributes.push_back(pNew);
}
} else// Case: 3
{
bChanged = true;
m_pSwpHints->NoteInHistory( pHt ); // UGLY: this may temporarily destroy the sorting!
pHt->SetStart(nEnd);
m_pSwpHints->NoteInHistory( pHt, true );
if (bExactRange)
{ // Only delete the hints which start at nStt and end at nEnd. for (i = 0; i < m_pSwpHints->Count(); ++i)
{
SwTextAttr* pHint = m_pSwpHints->Get(i); if ( (isTXTATR_WITHEND(pHint->Which()) && RES_TXTATR_AUTOFMT != pHint->Which())
|| pHint->GetStart() != nStt) continue;
// delay deleting the hints because it re-sorts the hints array for (SwTextAttr *const pDel : delAttributes)
{
m_pSwpHints->Delete(pDel);
DestroyAttr(pDel);
}
// delay inserting the hints because it re-sorts the hints array for (SwTextAttr *const pNew : newAttributes)
{
InsertHint(pNew, SetAttrMode::NOHINTADJUST);
}
TryDeleteSwpHints();
if (!bChanged) return;
if ( HasHints() )
{ // possibly sometimes Resort would be sufficient, but...
m_pSwpHints->MergePortions(*this);
}
// TextFrame's respond to aHint, others to aNew
SwUpdateAttr aHint(
nMin,
nMax,
0);
// Return current word: // Search from left to right, so find the word before nPos. // Except if at the start of the paragraph, then return the first word. // If the first word consists only of whitespace, return an empty string.
OUString SwTextFrame::GetCurWord(SwPosition const& rPos) const
{
TextFrameIndex const nPos(MapModelToViewPos(rPos));
SwTextNode *const pTextNode(rPos.GetNode().GetTextNode());
assert(pTextNode);
OUString const& rText(GetText());
assert(sal_Int32(nPos) <= rText.getLength()); // invalid index
if (rText.isEmpty() || IsHiddenNow()) return OUString();
// if no word was found use previous word (if any) if (aBndry.startPos == aBndry.endPos)
{
aBndry = rxBreak->previousWord(rText, sal_Int32(nPos), aLocale, nWordType);
}
// check if word was found and if it uses a symbol font, if so // enforce returning an empty string if (aBndry.endPos != aBndry.startPos
&& IsSymbolAt(TextFrameIndex(aBndry.startPos)))
{
aBndry.endPos = aBndry.startPos;
}
// can have -1 as start/end of bounds not found
aBndry.startPos = clipIndexBounds(rText, aBndry.startPos);
aBndry.endPos = clipIndexBounds(rText, aBndry.endPos);
//MSWord f.e has special emdash and endash behaviour in that they break //words for the purposes of word counting, while a hyphen etc. doesn't.
//The default configuration treats emdash/endash as a word break, but //additional ones can be added in under tools->options if (m_nWordType == i18n::WordType::WORD_COUNT)
{
OUString sDashes = officecfg::Office::Writer::WordCount::AdditionalSeparators::get();
OUStringBuffer aBuf(m_aPreDashReplacementText); for (sal_Int32 i = m_nStartPos; i < m_nEndPos; ++i)
{ if (i < 0) continue;
sal_Unicode cChar = aBuf[i]; if (sDashes.indexOf(cChar) != -1)
{
aBuf[i] = ' ';
++m_nOverriddenDashCount;
}
}
m_aText = aBuf.makeStringAndClear();
} else
m_aText = m_aPreDashReplacementText;
// First character is Asian if (nCurrScript == i18n::ScriptType::ASIAN)
{ auto aModelBeginPos = pModelToView->ConvertToModelPosition(nBegin); auto aCurrentLang = fnGetLangOfChar(aModelBeginPos.mnPos, nCurrScript, false);
// tdf#150621 Korean words must be counted as-is if (primary(aCurrentLang) == primary(LANGUAGE_KOREAN))
{ return nLen;
}
// Word is Chinese or Japanese, and must be truncated to a single character return indexUtf16 - nBegin;
}
// First character was not Asian, consider appearance of any Asian character // to be the end of the word while (indexUtf16 < nBegin + nLen)
{
nCurrScript = rxBreak->getScriptType(rText, indexUtf16); if (nCurrScript == i18n::ScriptType::ASIAN)
{ auto aModelBeginPos = pModelToView->ConvertToModelPosition(indexUtf16); auto aCurrentLang = fnGetLangOfChar(aModelBeginPos.mnPos, nCurrScript, false);
// tdf#150621 Korean words must be counted as-is. // Note that script changes intentionally do not delimit words for counting. if (primary(aCurrentLang) == primary(LANGUAGE_KOREAN))
{ return nLen;
}
// Word tail contains Chinese or Japanese, and must be truncated return indexUtf16 - nBegin;
}
rText.iterateCodePoints(&indexUtf16);
}
} return nLen;
}
}
// get the word boundaries
aBound = g_pBreakIt->GetBreakIter()->getWordBoundary( m_aText, m_nBegin,
g_pBreakIt->GetLocale( m_aCurrentLang ), m_nWordType, true );
OSL_ENSURE( aBound.endPos >= aBound.startPos, "broken aBound result" );
// we don't want to include preceding text // to count words in text with mixed script punctuation correctly, // but we want to include preceding symbols (eg. percent sign, section sign, // degree sign defined by dict_word_hu to spell check their affixed forms). if (m_nWordType == i18n::WordType::WORD_COUNT && aBound.startPos < m_nBegin)
aBound.startPos = m_nBegin;
//no word boundaries could be found if(aBound.endPos == aBound.startPos) returnfalse;
//if a word before is found it has to be searched for the next if(aBound.endPos == m_nBegin)
++m_nBegin; else break;
} // end while( true )
// #i89042, as discussed with HDU: don't evaluate script changes for word count. Use whole word. if ( m_nWordType == i18n::WordType::WORD_COUNT )
{
m_nBegin = std::max(aBound.startPos, m_nBegin);
m_nLength = 0; if (aBound.endPos > m_nBegin)
m_nLength = aBound.endPos - m_nBegin;
} else
{ // we have to differentiate between these cases: if ( aBound.startPos <= m_nBegin )
{
OSL_ENSURE( aBound.endPos >= m_nBegin, "Unexpected aBound result" );
// Note: this is a clone of SwTextFrame::AutoSpell_, so keep them in sync when fixing things! bool SwTextNode::Spell(SwSpellArgs* pArgs, bool bIsReadOnly)
{ // modify string according to redline information and hidden text const OUString aOldText( m_Text );
OUStringBuffer buf(m_Text); constbool bContainsComments = lcl_HasComments(*this); constbool bRestoreString =
lcl_MaskRedlinesAndHiddenText(*this, buf, 0, m_Text.getLength()); if (bRestoreString)
{ // ??? UGLY: is it really necessary to modify m_Text here?
m_Text = buf.makeStringAndClear();
}
bool bIsEditableSect = false; if (bIsReadOnly)
{ // Enable spell checking in editable sections in read-only mode. if (SwSectionNode* pSectNode = GetTextNode()->FindSectionNode())
{
bIsEditableSect = pSectNode->GetSection().IsEditInReadonly();
}
}
// 4 cases:
// 1. IsWrongDirty = 0 and GetWrong = 0 // Everything is checked and correct // 2. IsWrongDirty = 0 and GetWrong = 1 // Everything is checked and errors are identified in the wrong list // 3. IsWrongDirty = 1 and GetWrong = 0 // Nothing has been checked // 4. IsWrongDirty = 1 and GetWrong = 1 // Text has been checked but there is an invalid range in the wrong list
// Nothing has to be done for case 1. if ((IsWrongDirty() || GetWrong()) && (!bIsReadOnly || bIsEditableSect) && m_Text.getLength())
{ if (nBegin > m_Text.getLength())
{
nBegin = m_Text.getLength();
} if (nEnd > m_Text.getLength())
{
nEnd = m_Text.getLength();
}
// In case 2. we pass the wrong list to the scanned, because only // the words in the wrong list have to be checked
SwScanner aScanner( *this, m_Text, nullptr, ModelToViewHelper(),
WordType::DICTIONARY_WORD,
nBegin, nEnd ); bool bNextWord = aScanner.NextWord(); while( !pArgs->xSpellAlt.is() && bNextWord )
{ bool bCalledNextWord = false;
const OUString& rWord = aScanner.GetWord();
// get next language for next word, consider language attributes // within the word
LanguageType eActLang = aScanner.GetCurrentLanguage();
DetectAndMarkMissingDictionaries( GetTextNode()->GetDoc(), pArgs->xSpeller, eActLang );
if( rWord.getLength() > 0 && LANGUAGE_NONE != eActLang &&
!lcl_IsURL(rWord, *this, aScanner.GetBegin(), aScanner.GetLen() ) )
{ if (pArgs->xSpeller.is())
{
SvxSpellWrapper::CheckSpellLang( pArgs->xSpeller, eActLang );
pArgs->xSpellAlt = pArgs->xSpeller->spell( rWord, static_cast<sal_uInt16>(eActLang),
Sequence< PropertyValue >() );
} if( pArgs->xSpellAlt.is() )
{ if ( IsSymbolAt(aScanner.GetBegin()) || // redlines can leave "in word" character within word, // we must remove them before spell checking // to avoid false alarm
( (bRestoreString || bContainsComments) && pArgs->xSpeller->isValid( rWord.replaceAll(OUStringChar(CH_TXTATR_INWORD), ""), static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ) ) )
{
pArgs->xSpellAlt = nullptr;
} else
{
OUString sPrevWord = aScanner.GetPrevWord(); auto nWordBegin = aScanner.GetBegin(); auto nWordEnd = aScanner.GetEnd();
bNextWord = aScanner.NextWord(); const OUString& rActualWord = aScanner.GetPrevWord();
bCalledNextWord = true; // check space separated word pairs in the dictionary, e.g. "vice versa" if ( !((bNextWord && !linguistic::HasDigits(aScanner.GetWord()) &&
pArgs->xSpeller->isValid( rActualWord + " " + aScanner.GetWord(), static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() )) ||
( !sPrevWord.isEmpty() && !linguistic::HasDigits(sPrevWord) &&
pArgs->xSpeller->isValid( sPrevWord + " " + rActualWord, static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ))) )
{ // make sure the selection build later from the data // below does not include "in word" character to the // left and right in order to preserve those. Therefore // count those "in words" in order to modify the // selection accordingly. const sal_Unicode* pChar = aScanner.GetPrevWord().getStr();
sal_Int32 nLeft = 0; while (*pChar++ == CH_TXTATR_INWORD)
++nLeft;
pChar = rActualWord.getLength() ? rActualWord.getStr() + rActualWord.getLength() - 1 : nullptr;
sal_Int32 nRight = 0; while (pChar && *pChar-- == CH_TXTATR_INWORD)
++nRight;
GetDoc().getIDocumentContentOperations().InsertItemSet( rPaM, aSet ); // SetAttr( aSet ); <- Does not set language attribute of empty paragraphs correctly, // <- because since there is no selection the flag to garbage // <- collect all attributes is set, and therefore attributes spanned // <- over empty selection are removed.
}
bool SwTextNode::Convert( SwConversionArgs &rArgs )
{ // get range of text within node to be converted // (either all the text or the text within the selection // when the conversion was started) const sal_Int32 nTextBegin = ( &rArgs.pStartPos->GetNode() == this )
? std::min(rArgs.pStartPos->GetContentIndex(), m_Text.getLength())
: 0;
// modify string according to redline information and hidden text const OUString aOldText( m_Text );
OUStringBuffer buf(m_Text); constbool bRestoreString =
lcl_MaskRedlinesAndHiddenText(*this, buf, 0, m_Text.getLength()); if (bRestoreString)
{ // ??? UGLY: is it really necessary to modify m_Text here?
m_Text = buf.makeStringAndClear();
}
bool bFound = false;
sal_Int32 nBegin = nTextBegin;
sal_Int32 nLen = 0;
LanguageType nLangFound = LANGUAGE_NONE; if (m_Text.isEmpty())
{ if (rArgs.bAllowImplicitChangesForNotConvertibleText)
{ // create SwPaM with mark & point spanning empty paragraph //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different
SwPaM aCurPaM( *this, 0 );
// Implicit changes require setting new attributes, which in turn destroys // the attribute sequence on that aIter iterates. We store the necessary // coordinates and apply those changes after iterating through the text. typedef std::pair<sal_Int32, sal_Int32> ImplicitChangesRange;
std::vector<ImplicitChangesRange> aImplicitChanges;
// find non zero length text portion of appropriate language do {
nLangFound = aIter.GetLanguage(); bool bLangOk = (nLangFound == rArgs.nConvSrcLang) ||
(editeng::HangulHanjaConversion::IsChinese( nLangFound ) &&
editeng::HangulHanjaConversion::IsChinese( rArgs.nConvSrcLang ));
sal_Int32 nChPos = aIter.GetChgPos(); // the position at the end of the paragraph is COMPLETE_STRING and // thus must be cut to the end of the actual string.
assert(nChPos != -1); if (nChPos == -1 || nChPos == COMPLETE_STRING)
{
nChPos = m_Text.getLength();
}
nLen = nChPos - nBegin;
bFound = bLangOk && nLen > 0; if (!bFound)
{ // create SwPaM with mark & point spanning the attributed text //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different
SwPaM aCurPaM( *this, nBegin );
aCurPaM.SetMark();
aCurPaM.GetPoint()->SetContent(nBegin + nLen);
// check script type of selected text if (SwEditShell *pEditShell = GetDoc().GetEditShell())
{
pEditShell->Push(); // save current cursor on stack
pEditShell->SetSelection( aCurPaM ); bool bIsAsianScript = (SvtScriptType::ASIAN == pEditShell->GetScriptType());
pEditShell->Pop(SwCursorShell::PopMode::DeleteCurrent); // restore cursor from stack
if (!bIsAsianScript && rArgs.bAllowImplicitChangesForNotConvertibleText)
{ // Store for later use
aImplicitChanges.emplace_back(nBegin, nBegin+nLen);
}
}
nBegin = nChPos; // start of next language portion
}
} while (!bFound && aIter.Next()); /* loop while nothing was found and still sth is left to be searched */
// Apply implicit changes, if any, now that aIter is no longer used for (constauto& rImplicitChange : aImplicitChanges)
{
SwPaM aPaM( *this, rImplicitChange.first );
aPaM.SetMark();
aPaM.GetPoint()->SetContent( rImplicitChange.second );
SetLanguageAndFont( aPaM, rArgs.nConvTargetLang, RES_CHRATR_CJK_LANGUAGE, rArgs.pTargetFont, RES_CHRATR_CJK_FONT );
}
}
// keep resulting text within selection / range of text to be converted if (nBegin < nTextBegin)
nBegin = nTextBegin; if (nBegin + nLen > nTextEnd)
nLen = nTextEnd - nBegin; bool bInSelection = nBegin < nTextEnd;
if (bFound && bInSelection) // convertible text found within selection/range?
{
OSL_ENSURE( !m_Text.isEmpty(), "convertible text portion missing!" );
rArgs.aConvText = m_Text.copy(nBegin, nLen);
rArgs.nConvTextLang = nLangFound;
// position where to start looking in next iteration (after current ends)
rArgs.pStartPos->Assign(*this, nBegin + nLen ); // end position (when we have travelled over the whole document)
rArgs.pEndPos->Assign(*this, nBegin );
}
// restore original text if ( bRestoreString )
{
m_Text = aOldText;
}
return !rArgs.aConvText.isEmpty();
}
// Note: this is a clone of SwTextNode::Spell, so keep them in sync when fixing things!
SwRect SwTextFrame::AutoSpell_(SwTextNode & rNode, sal_Int32 nActPos)
{
SwRect aRect;
assert(sw::FrameContainsNode(*this, rNode.GetIndex()));
SwTextNode *const pNode(&rNode); if (!nActPos)
nActPos = COMPLETE_STRING;
// modify string according to redline information and hidden text const OUString aOldText( pNode->GetText() );
OUStringBuffer buf(pNode->m_Text); constbool bContainsComments = lcl_HasComments(rNode); constbool bRestoreString =
lcl_MaskRedlinesAndHiddenText(*pNode, buf, 0, pNode->GetText().getLength()); if (bRestoreString)
{ // ??? UGLY: is it really necessary to modify m_Text here? just for GetLang()?
pNode->m_Text = buf.makeStringAndClear();
}
// a change of data indicates that at least one word has been modified
// get the position in the wrong list
nInsertPos = pNode->GetWrong()->GetWrongPos( nBegin );
// sometimes we have to skip one entry if( nInsertPos < pNode->GetWrong()->Count() &&
nBegin == pNode->GetWrong()->Pos( nInsertPos ) +
pNode->GetWrong()->Len( nInsertPos ) )
nInsertPos++;
}
// get next language for next word, consider language attributes // within the word
LanguageType eActLang = aScanner.GetCurrentLanguage();
DetectAndMarkMissingDictionaries( rDoc, xSpell, eActLang );
bool bSpell = xSpell.is() && xSpell->hasLanguage( static_cast<sal_uInt16>(eActLang) ); if( bSpell && !rWord.isEmpty() && !lcl_IsURL(rWord, *pNode, nBegin, nLen) )
{ // check for: bAlter => xHyphWord.is()
OSL_ENSURE(!bSpell || xSpell.is(), "NULL pointer"); if( !xSpell->isValid( rWord, static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ) && // redlines can leave "in word" character within word, // we must remove them before spell checking // to avoid false alarm
((!bRestoreString && !bContainsComments) || !xSpell->isValid( rWord.replaceAll(OUStringChar(CH_TXTATR_INWORD), ""), static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ) ) )
{
OUString sPrevWord = aScanner.GetPrevWord();
bNextWord = aScanner.NextWord();
bCalledNextWord = true; // check space separated word pairs in the dictionary, e.g. "vice versa" if ( !((bNextWord && !linguistic::HasDigits(aScanner.GetWord()) &&
xSpell->isValid( aScanner.GetPrevWord() + " " + aScanner.GetWord(), static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() )) ||
(!sPrevWord.isEmpty() && !linguistic::HasDigits(sPrevWord) &&
xSpell->isValid( sPrevWord + " " + aScanner.GetPrevWord(), static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ))) )
{
sal_Int32 nSmartTagStt = nBegin;
sal_Int32 nDummy = 1; if ( !pNode->GetSmartTags() || !pNode->GetSmartTags()->InWrongWord( nSmartTagStt, nDummy ) )
{ if( !pNode->GetWrong() )
{
pNode->SetWrong( std::make_unique<SwWrongList>( WRONGLIST_SPELL ) );
pNode->GetWrong()->SetInvalid( 0, nEnd );
}
SwWrongList::FreshState const eState(pNode->GetWrong()->Fresh(
nChgStart, nChgEnd, nBegin, nLen, nInsertPos, nActPos)); switch (eState)
{ case SwWrongList::FreshState::FRESH:
pNode->GetWrong()->Insert(OUString(), nullptr, nBegin, nLen, nInsertPos++); break; case SwWrongList::FreshState::CURSOR:
bPending = true;
[[fallthrough]]; // to mark as invalid case SwWrongList::FreshState::NOTHING:
nInvStart = nBegin;
nInvEnd = nBegin + nLen; break;
}
}
} elseif( bAddAutoCmpl && rACW.GetMinWordLen() <= aScanner.GetPrevWord().getLength() )
{ // tdf#119695 only add the word if the cursor position is outside the word // so that the incomplete words are not added as autocomplete candidates bool bCursorOutsideWord = nActPos > nBegin + nLen || nActPos < nBegin; if (bCursorOutsideWord)
rACW.InsertWord(aScanner.GetPrevWord(), rDoc);
}
} elseif( bAddAutoCmpl && rACW.GetMinWordLen() <= rWord.getLength() )
{ // tdf#119695 only add the word if the cursor position is outside the word // so that the incomplete words are not added as autocomplete candidates bool bCursorOutsideWord = nActPos > nBegin + nLen || nActPos < nBegin; if (bCursorOutsideWord)
rACW.InsertWord(rWord, rDoc);
}
}
if ( !bCalledNextWord )
bNextWord = aScanner.NextWord();
}
}
// reset original text // i63141 before calling GetCharRect(..) with formatting! if ( bRestoreString )
{
pNode->m_Text = aOldText;
} if( pNode->GetWrong() )
{ if( bFresh )
pNode->GetWrong()->Fresh( nChgStart, nChgEnd,
nEnd, 0, nInsertPos, nActPos );
Function scans words in current text and checks them in the smarttag libraries. If the check returns true to bounds of the recognized words are stored into a list that is used later for drawing the underline.
// clear smart tag list between nBegin and nEnd: if ( 0 != nNumberOfEntries )
{
sal_Int32 nChgStart = COMPLETE_STRING;
sal_Int32 nChgEnd = 0; const sal_uInt16 nCurrentIndex = pSmartTagList->GetWrongPos( nBegin );
pSmartTagList->Fresh( nChgStart, nChgEnd, nBegin, nEnd - nBegin, nCurrentIndex, COMPLETE_STRING );
nNumberOfRemovedEntries = nNumberOfEntries - pSmartTagList->Count();
}
if ( nBegin < nEnd )
{ // Expand the string: const ModelToViewHelper aConversionMap(*pNode, getRootFrame() /*TODO - replace or expand fields for smart tags?*/); const OUString& aExpandText = aConversionMap.getViewText();
// Ownership ov ConversionMap is passed to SwXTextMarkup object!
uno::Reference<text::XTextMarkup> const xTextMarkup = new SwXTextMarkup(pNode, aConversionMap);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.