589 lines
16 KiB
JavaScript
589 lines
16 KiB
JavaScript
/*
|
|
* (c) Copyright Ascensio System SIA 2010-2024
|
|
*
|
|
* This program is a free software product. You can redistribute it and/or
|
|
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
|
* version 3 as published by the Free Software Foundation. In accordance with
|
|
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
|
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
|
* of any third-party rights.
|
|
*
|
|
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
|
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
|
*
|
|
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
|
* street, Riga, Latvia, EU, LV-1050.
|
|
*
|
|
* The interactive user interfaces in modified source and object code versions
|
|
* of the Program must display Appropriate Legal Notices, as required under
|
|
* Section 5 of the GNU AGPL version 3.
|
|
*
|
|
* Pursuant to Section 7(b) of the License you must retain the original Product
|
|
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
|
* grant you any rights under trademark law for use of our trademarks.
|
|
*
|
|
* All the Product's GUI elements, including illustrations and icon sets, as
|
|
* well as technical writing content are licensed under the terms of the
|
|
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
|
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
|
*
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
(function(window, undefined)
|
|
{
|
|
const DIGITS = {
|
|
0x00B2 : 1,
|
|
0x00B3 : 1,
|
|
0x00B9 : 1,
|
|
0x00BC : 1,
|
|
0x00BD : 1,
|
|
0x00BE : 1
|
|
};
|
|
|
|
function isHindiDigit(code)
|
|
{
|
|
return (0x0660 <= code && code <= 0x0669) || (0x06F0 <= code && code <= 0x06F9);
|
|
}
|
|
|
|
function isDigit(code)
|
|
{
|
|
return AscCommon.IsDigit(code) || isHindiDigit(code) || (!!DIGITS[code]);
|
|
}
|
|
|
|
const IGNORE_UPPERCASE = 0x0001;
|
|
const IGNORE_NUMBERS = 0x0002;
|
|
|
|
/**
|
|
* Класс для хранения элементов проверки орфографии
|
|
* @constructor
|
|
*/
|
|
function CParagraphSpellChecker(oParagraph)
|
|
{
|
|
this.Elements = [];
|
|
this.RecalcId = -1;
|
|
this.Paragraph = oParagraph;
|
|
this.Words = {};
|
|
this.Collector = null;
|
|
this.Flags = IGNORE_UPPERCASE | IGNORE_NUMBERS;
|
|
}
|
|
|
|
CParagraphSpellChecker.prototype.Clear = function()
|
|
{
|
|
for (let nIndex = 0, nCount = this.Elements.length; nIndex < nCount; ++nIndex)
|
|
{
|
|
this.Elements[nIndex].ClearSpellingMarks();
|
|
}
|
|
|
|
this.Elements = [];
|
|
this.Words = {};
|
|
};
|
|
/**
|
|
* @param oSettings {AscCommon.CSpellCheckSettings}
|
|
*/
|
|
CParagraphSpellChecker.prototype.UpdateSettings = function(oSettings)
|
|
{
|
|
this.Flags = 0;
|
|
|
|
if (oSettings.IsIgnoreWordsInUppercase())
|
|
this.Flags |= IGNORE_UPPERCASE;
|
|
|
|
if (oSettings.IsIgnoreWordsWithNumbers())
|
|
this.Flags |= IGNORE_NUMBERS;
|
|
};
|
|
CParagraphSpellChecker.prototype.SetRecalcId = function(nRecalcId)
|
|
{
|
|
this.RecalcId = nRecalcId;
|
|
};
|
|
CParagraphSpellChecker.prototype.Check = function(nRecalcId, isCheckCurrentWord)
|
|
{
|
|
let arrWords = [];
|
|
let arrLangs = [];
|
|
this.private_GetWordsListForRequest(arrWords, arrLangs, isCheckCurrentWord);
|
|
|
|
let isFirst = (this.RecalcId === -1);
|
|
if (undefined !== nRecalcId)
|
|
this.SetRecalcId(nRecalcId);
|
|
|
|
if (0 < arrWords.length
|
|
&& true === this.GetDocumentSpellChecker().AddWaitingParagraph(this.Paragraph, this.RecalcId, arrWords, arrLangs))
|
|
{
|
|
editor.SpellCheckApi.spellCheck({
|
|
"type" : "spell",
|
|
"ParagraphId" : this.Paragraph.GetId(),
|
|
"RecalcId" : this.RecalcId,
|
|
"ElementId" : 0,
|
|
"usrWords" : arrWords,
|
|
"usrLang" : arrLangs
|
|
});
|
|
}
|
|
else
|
|
{
|
|
this.private_ClearMarksForCorrectWords();
|
|
}
|
|
|
|
return (arrWords.length || isFirst);
|
|
};
|
|
CParagraphSpellChecker.prototype.private_GetWordsListForRequest = function(arrWords, arrLangs, isCheckCurrentWord)
|
|
{
|
|
let oCurPos = this.GetCurrentPositionInParagraph();
|
|
|
|
let isSkipCurrentWord = true !== isCheckCurrentWord && oCurPos ? this.SkipCurrentWord() : false;
|
|
for (let nIndex = 0, nCount = this.Elements.length; nIndex < nCount; ++nIndex)
|
|
{
|
|
let oElement = this.Elements[nIndex];
|
|
let sWord = oElement.GetWord();
|
|
let nLang = oElement.GetLang();
|
|
|
|
oElement.ResetCurrent();
|
|
if (!this.HaveDictionary(nLang) || !this.IsNeedCheckWord(sWord))
|
|
{
|
|
oElement.SetCorrect();
|
|
}
|
|
else if (oElement.IsUndefined() && isSkipCurrentWord && oElement.CheckPositionInside(oCurPos))
|
|
{
|
|
oElement.SetCurrent();
|
|
this.AddCurrentParagraph();
|
|
}
|
|
|
|
if (oElement.IsUndefined())
|
|
{
|
|
arrWords.push(sWord);
|
|
arrLangs.push(nLang);
|
|
|
|
let nPrefix = this.Elements[nIndex].GetPrefix();
|
|
let nEnding = this.Elements[nIndex].GetEnding();
|
|
|
|
if (nPrefix)
|
|
{
|
|
arrWords.push(String.fromCharCode(nPrefix) + sWord);
|
|
arrLangs.push(nLang);
|
|
}
|
|
|
|
if (nEnding)
|
|
{
|
|
arrWords.push(sWord + String.fromCharCode(nEnding));
|
|
arrLangs.push(nLang);
|
|
}
|
|
|
|
if (nPrefix && nEnding)
|
|
{
|
|
arrWords.push(String.fromCharCode(nPrefix) + sWord + String.fromCharCode(nEnding));
|
|
arrLangs.push(nLang);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
CParagraphSpellChecker.prototype.Add = function(startRun, startInRunPos, endRun, endInRunPos, Word, Lang, Prefix, Ending, apostrophe)
|
|
{
|
|
if (Word.length > 0)
|
|
{
|
|
if ('\'' === Word.charAt(Word.length - 1))
|
|
Word = Word.substr(0, Word.length - 1);
|
|
if ('\'' === Word.charAt(0))
|
|
Word = Word.substr(1);
|
|
}
|
|
|
|
if (!this.HaveDictionary(Lang) || !this.IsNeedCheckWord(Word))
|
|
return;
|
|
|
|
let oElement = new AscWord.CParagraphSpellCheckerElement(startRun, startInRunPos, endRun, endInRunPos, Word, Lang, Prefix, Ending, apostrophe);
|
|
startRun.AddSpellCheckerElement(new AscWord.SpellMarkStart(oElement));
|
|
endRun.AddSpellCheckerElement(new AscWord.SpellMarkEnd(oElement));
|
|
this.Elements.push(oElement);
|
|
};
|
|
CParagraphSpellChecker.prototype.SpellCheckResponse = function(nRecalcId, usrCorrect)
|
|
{
|
|
let oDocumentSpellChecker = this.GetDocumentSpellChecker();
|
|
oDocumentSpellChecker.RemoveWaitingParagraph(this.Paragraph);
|
|
|
|
if (nRecalcId !== this.RecalcId)
|
|
return;
|
|
|
|
for (let nIndex = 0, nCorrectIndex = 0, nCount = this.Elements.length; nIndex < nCount; ++nIndex)
|
|
{
|
|
let oElement = this.Elements[nIndex];
|
|
if (oElement.IsUndefined())
|
|
{
|
|
let isCorrect = false;
|
|
if (oDocumentSpellChecker.IsIgnored(oElement.GetWord()))
|
|
{
|
|
isCorrect = true;
|
|
}
|
|
else if (oElement.GetPrefix() && oElement.GetEnding())
|
|
{
|
|
isCorrect = usrCorrect[nCorrectIndex] || usrCorrect[nCorrectIndex + 1] || usrCorrect[nCorrectIndex + 2] || usrCorrect[nCorrectIndex + 3];
|
|
nCorrectIndex += 3;
|
|
}
|
|
else if (oElement.GetPrefix() || oElement.GetEnding())
|
|
{
|
|
isCorrect = usrCorrect[nCorrectIndex] || usrCorrect[nCorrectIndex + 1];
|
|
nCorrectIndex++;
|
|
}
|
|
else
|
|
{
|
|
isCorrect = usrCorrect[nCorrectIndex];
|
|
}
|
|
|
|
if (isCorrect)
|
|
oElement.SetCorrect();
|
|
else
|
|
oElement.SetWrong();
|
|
|
|
nCorrectIndex++;
|
|
}
|
|
}
|
|
|
|
this.private_UpdateWordsList();
|
|
this.private_UpdateParagraphState();
|
|
};
|
|
CParagraphSpellChecker.prototype.private_UpdateParagraphState = function()
|
|
{
|
|
let oDocumentSpellChecker = this.GetDocumentSpellChecker();
|
|
|
|
if (this.GetErrorsCount() > 0)
|
|
oDocumentSpellChecker.AddParagraphWithErrors(this.Paragraph);
|
|
else
|
|
oDocumentSpellChecker.RemoveParagraphWithErrors(this.Paragraph);
|
|
};
|
|
CParagraphSpellChecker.prototype.SuggestResponse = function(nRecalcId, sElementId, usrVariants)
|
|
{
|
|
let oElement = this.Elements[sElementId];
|
|
if (nRecalcId === this.RecalcId && oElement)
|
|
{
|
|
oElement.SetVariants(usrVariants);
|
|
|
|
this.private_CheckEASTEGGS(sElementId);
|
|
|
|
let sWord = oElement.GetWord();
|
|
let nLang = oElement.GetLang();
|
|
if (undefined !== this.Words[sWord] && undefined !== this.Words[sWord][nLang])
|
|
this.Words[sWord][nLang] = oElement.GetVariants();
|
|
}
|
|
};
|
|
CParagraphSpellChecker.prototype.GetElementByRange = function(oStartPos, oEndPos)
|
|
{
|
|
let oFoundElement = null;
|
|
for (let nIndex = 0, nCount = this.Elements.length; nIndex < nCount; ++nIndex)
|
|
{
|
|
let oElement = this.Elements[nIndex];
|
|
if (oElement.CheckIntersection(oStartPos, oEndPos) && oElement.IsWrong())
|
|
{
|
|
if (oFoundElement)
|
|
{
|
|
oFoundElement = null;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
oFoundElement = oElement;
|
|
}
|
|
}
|
|
}
|
|
|
|
return oFoundElement;
|
|
};
|
|
CParagraphSpellChecker.prototype.GetIndexByElement = function(oElement)
|
|
{
|
|
for (let nIndex = 0, nCount = this.Elements.length; nIndex < nCount; ++nIndex)
|
|
{
|
|
if (oElement === this.Elements[nIndex])
|
|
return nIndex;
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
CParagraphSpellChecker.prototype.CheckVariants = function(oElement)
|
|
{
|
|
if (this.GetDocumentSpellChecker().IsWaitingParagraph(this.Paragraph))
|
|
return;
|
|
|
|
if (!oElement.IsWrong())
|
|
return;
|
|
|
|
let arrVariants = oElement.GetVariants();
|
|
if (arrVariants)
|
|
return;
|
|
|
|
let nLang = oElement.GetLang();
|
|
if (!this.HaveDictionary(nLang))
|
|
return;
|
|
|
|
let nElementIndex = this.GetIndexByElement(oElement);
|
|
if (-1 === nElementIndex)
|
|
return;
|
|
|
|
editor.SpellCheckApi.spellCheck({
|
|
"type" : "suggest",
|
|
"ParagraphId" : this.Paragraph.GetId(),
|
|
"RecalcId" : this.RecalcId,
|
|
"ElementId" : nElementIndex,
|
|
"usrWords" : [oElement.GetWord()],
|
|
"usrLang" : [nLang]
|
|
});
|
|
};
|
|
CParagraphSpellChecker.prototype.Ignore = function(sWord)
|
|
{
|
|
for (let nIndex = 0, nCount = this.Elements.length; nIndex < nCount; ++nIndex)
|
|
{
|
|
var oElement = this.Elements[nIndex];
|
|
if (oElement.IsWrong() && oElement.GetWord() === sWord)
|
|
oElement.SetCorrect();
|
|
}
|
|
|
|
if (this.Words[sWord])
|
|
delete this.Words[sWord];
|
|
|
|
this.private_UpdateParagraphState();
|
|
};
|
|
CParagraphSpellChecker.prototype.ResetElementsWithCurrentState = function()
|
|
{
|
|
for (let nIndex = 0, nCount = this.Elements.length; nIndex < nCount; ++nIndex)
|
|
{
|
|
let oElement = this.Elements[nIndex];
|
|
if (oElement.IsCurrent())
|
|
oElement.SetUndefined();
|
|
}
|
|
};
|
|
/**
|
|
* Получаем количество элементов проверки орфографии
|
|
* @returns {Number}
|
|
*/
|
|
CParagraphSpellChecker.prototype.GetElementsCount = function()
|
|
{
|
|
return this.Elements.length;
|
|
};
|
|
/**
|
|
* Получаем элемент проверки орфографии по номеру
|
|
* @param nIndex
|
|
* @returns {AscWord.CParagraphSpellCheckerElement}
|
|
*/
|
|
CParagraphSpellChecker.prototype.GetElement = function(nIndex)
|
|
{
|
|
return (nIndex < 0 || nIndex >= this.Elements.length ? null : this.Elements[nIndex]);
|
|
};
|
|
/**
|
|
* Приостанавливаем проверку орфографии, если параграф слишком большой
|
|
* @param {AscWord.CParagraphSpellCheckerCollector} oCollector
|
|
*/
|
|
CParagraphSpellChecker.prototype.Pause = function(oCollector)
|
|
{
|
|
this.Collector = oCollector;
|
|
};
|
|
/**
|
|
* Проверяем приостановлена ли проверка в данном параграфе
|
|
* @return {boolean}
|
|
*/
|
|
CParagraphSpellChecker.prototype.IsPaused = function()
|
|
{
|
|
return !!(this.Collector);
|
|
};
|
|
/**
|
|
* Очищаем остановленное состояние
|
|
*/
|
|
CParagraphSpellChecker.prototype.ClearCollector = function()
|
|
{
|
|
this.Collector = null;
|
|
};
|
|
/**
|
|
* Нужно ли проверять слово
|
|
* @param {string} sWord
|
|
* @returns {boolean}
|
|
*/
|
|
CParagraphSpellChecker.prototype.IsNeedCheckWord = function(sWord)
|
|
{
|
|
if (1 >= sWord.length || ((this.Flags & IGNORE_UPPERCASE) && AscCommon.IsAbbreviation(sWord)))
|
|
return false;
|
|
|
|
if (this.Flags & IGNORE_NUMBERS)
|
|
{
|
|
for (var oIterator = sWord.getUnicodeIterator(); oIterator.check(); oIterator.next())
|
|
{
|
|
let nCharCode = oIterator.value();
|
|
if (isDigit(nCharCode))
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
let nonNumber = false;
|
|
for (let iter = sWord.getUnicodeIterator(); iter.check(); iter.next())
|
|
{
|
|
let codePoint = iter.value();
|
|
if (!isDigit(codePoint))
|
|
{
|
|
nonNumber = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!nonNumber)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
CParagraphSpellChecker.prototype.HaveDictionary = function(nLang)
|
|
{
|
|
return editor.SpellCheckApi.checkDictionary(nLang);
|
|
};
|
|
CParagraphSpellChecker.prototype.SkipCurrentWord = function()
|
|
{
|
|
return (editor.asc_IsSpellCheckCurrentWord() !== true);
|
|
};
|
|
CParagraphSpellChecker.prototype.GetCurrentPositionInParagraph = function()
|
|
{
|
|
let oCurPos = null;
|
|
if (this.Paragraph.IsThisElementCurrent())
|
|
oCurPos = this.Paragraph.Get_ParaContentPos(false, false);
|
|
|
|
return oCurPos;
|
|
};
|
|
CParagraphSpellChecker.prototype.AddCurrentParagraph = function()
|
|
{
|
|
editor.WordControl.m_oLogicDocument.Spelling.AddCurrentParagraph(this.Paragraph);
|
|
};
|
|
CParagraphSpellChecker.prototype.GetErrorsCount = function()
|
|
{
|
|
let nErrorsCount = 0;
|
|
for (let nIndex = 0, nCount = this.Elements.length; nIndex < nCount; ++nIndex)
|
|
{
|
|
if (this.Elements[nIndex].IsWrong())
|
|
nErrorsCount++;
|
|
}
|
|
|
|
return nErrorsCount;
|
|
};
|
|
CParagraphSpellChecker.prototype.GetCollector = function(isForceFullCheck)
|
|
{
|
|
let oCollector;
|
|
if (this.IsPaused())
|
|
{
|
|
oCollector = this.Collector;
|
|
oCollector.SetFindStart(true);
|
|
oCollector.ResetCheckedCounter();
|
|
}
|
|
else
|
|
{
|
|
oCollector = new AscWord.CParagraphSpellCheckerCollector(this, isForceFullCheck);
|
|
this.Elements = [];
|
|
}
|
|
|
|
return oCollector;
|
|
};
|
|
CParagraphSpellChecker.prototype.OnEndCollectingElements = function()
|
|
{
|
|
for (let nIndex = 0, nCount = this.Elements.length; nIndex < nCount; ++nIndex)
|
|
{
|
|
let oElement = this.Elements[nIndex];
|
|
|
|
let sWord = oElement.GetWord();
|
|
let nLang = oElement.GetLang();
|
|
|
|
if (undefined !== this.Words[sWord])
|
|
{
|
|
if (undefined === this.Words[sWord][nLang] || this.Words[sWord].Prefix !== oElement.GetPrefix() || this.Words[sWord].Ending !== oElement.GetEnding())
|
|
oElement.SetUndefined();
|
|
else if (true === this.Words[sWord][nLang])
|
|
oElement.SetCorrect();
|
|
else
|
|
oElement.SetWrong(this.Words[sWord][nLang]);
|
|
}
|
|
}
|
|
|
|
this.private_ClearWordsList();
|
|
this.private_UpdateWordsList();
|
|
};
|
|
CParagraphSpellChecker.prototype.private_ClearWordsList = function()
|
|
{
|
|
this.Words = {};
|
|
};
|
|
CParagraphSpellChecker.prototype.private_UpdateWordsList = function()
|
|
{
|
|
for (let nIndex = this.Elements.length - 1; nIndex >= 0; --nIndex)
|
|
{
|
|
let oElement = this.Elements[nIndex];
|
|
let sWord = oElement.GetWord();
|
|
let nLang = oElement.GetLang();
|
|
|
|
if (oElement.IsCorrect() && !oElement.IsCurrent())
|
|
{
|
|
if (!this.Words[sWord])
|
|
{
|
|
this.Words[sWord] = {
|
|
Prefix : oElement.GetPrefix(),
|
|
Ending : oElement.GetEnding()
|
|
};
|
|
}
|
|
|
|
if (undefined === this.Words[sWord][nLang])
|
|
this.Words[sWord][nLang] = true;
|
|
}
|
|
else if (oElement.IsWrong())
|
|
{
|
|
if (!this.Words[sWord])
|
|
{
|
|
this.Words[sWord] = {
|
|
Prefix : oElement.GetPrefix(),
|
|
Ending : oElement.GetEnding()
|
|
};
|
|
}
|
|
|
|
if (undefined === this.Words[sWord][nLang])
|
|
this.Words[sWord][nLang] = oElement.GetVariants();
|
|
}
|
|
}
|
|
|
|
this.private_ClearMarksForCorrectWords();
|
|
};
|
|
CParagraphSpellChecker.prototype.private_ClearMarksForCorrectWords = function()
|
|
{
|
|
for (let nCount = this.Elements.length, nIndex = nCount - 1; nIndex >= 0; --nIndex)
|
|
{
|
|
let oElement = this.Elements[nIndex];
|
|
if (oElement.IsCorrect() && !oElement.IsCurrent())
|
|
{
|
|
let startRun = oElement.GetStartRun();
|
|
let endRun = oElement.GetEndRun();
|
|
|
|
if (startRun !== endRun)
|
|
{
|
|
startRun.RemoveSpellCheckerElement(oElement);
|
|
endRun.RemoveSpellCheckerElement(oElement);
|
|
}
|
|
else
|
|
{
|
|
endRun.RemoveSpellCheckerElement(oElement);
|
|
}
|
|
|
|
this.Elements.splice(nIndex, 1);
|
|
}
|
|
}
|
|
}
|
|
CParagraphSpellChecker.prototype.private_CheckEASTEGGS = function(sId)
|
|
{
|
|
for (let nIndex = 0, nCount = EASTEGGS.length; nIndex < nCount; ++nIndex)
|
|
{
|
|
if (EASTEGGS[nIndex] === this.Elements[sId].Word)
|
|
{
|
|
this.Elements[sId].Variants = EASTEGGS_VARIANTS[nIndex];
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
CParagraphSpellChecker.prototype.GetDocumentSpellChecker = function()
|
|
{
|
|
return editor.WordControl.m_oLogicDocument.Spelling;
|
|
};
|
|
|
|
const EASTEGGS = [];
|
|
const EASTEGGS_VARIANTS = [];
|
|
|
|
|
|
//--------------------------------------------------------export----------------------------------------------------
|
|
AscWord.CParagraphSpellChecker = CParagraphSpellChecker;
|
|
|
|
})(window);
|