产生拼写检查器

Posted

技术标签:

【中文标题】产生拼写检查器【英文标题】:Yielding spellchecker 【发布时间】:2012-09-05 10:59:09 【问题描述】:

标题可能有点误导;我的拼写检查器更关注格式而不是拼写(大写字母、标点符号和空格、撇号、将互联网俚语转换为完整单词、经常打乱的单词等)。但是,基本原则适用。

基本上,我正在构建的 JS/jQuery 检查器会在输入单词时纠正它们(在单词后输入空格或标点符号之后)。

但是,就像任何自动更正一样,它必然会出错。我什至没有考虑创建功能来确定“它”或“它的”在给定情况下是否更合适(尽管如果存在这样的插件或代码 sn-p,请给我指出一个)。

所以我想让它成为一个“屈服”的自动更正(因为缺乏更好的名字的知识)。基本上;

    用户键入一个会触发检查器的单词,然后键入一个 空间。 检查员更正单词。 用户认为这是一个错误 并将其改正(通过将整个单词或部分单词退格, 或突出显示它,或者他们觉得编辑它很舒服)。 该 用户继续输入,检查器不会再次触摸该单词的该实例。

现在最简单的当然是完全禁用对该单词的检查,但我希望检查器能够更正它的未来实例。我正在寻找的是检测用户将自动更正的单词(无论是在键入之后还是之后)编辑回自动更正之前的状态,然后学会不理会该单词的特定实例。

我什至不知道从哪里开始。我正在考虑一个 contenteditable,每个单词都包含在一个 span 中,自动更正的单词具有特殊的类和包含原始单词的 data-* 属性,监听自动更正单词的编辑,如果它被编辑回等于 data-*值,添加一个类,使其不参与未来的自动更正轮次。

我认为这可能是不必要的复杂,或者至少不是阻力最小的路径。这样做最聪明的方法是什么?

【问题讨论】:

你的方法对我来说似乎很好。您是否在寻找内容可编辑的替代方案,或者是什么让您怀疑? 我总是怀疑自己的处理方法。似乎跟踪这些跨度,尤其是在中间编辑或删除内容时,可能会变得混乱。 对于它的价值,当我的打字为我自动更正时,我不感激。是的,手动修复我拼错的东西很麻烦,但自动更正常常与我的意图不符,直到我离开几个字时我才注意到(因为我以 90 wpm+ 的速度触摸打字) .这个系统对我来说太烦人了。最好让我看到有问题并评估。 @ErikE 完全同意,这个系统并不适合所有人,禁用它的选项是必须的。这让我想起了您可以输入的不同方式,例如,在文本编辑器中输入日语文本:您可以在平假名中键入每个单词,将其转换为汉字,然后移动到下一个单词,重复;或者您在平假名中键入整个句子或段落,然后继续快速连续地将其转换为单词。快速打字员确实更喜欢后者,恕我直言,它也适用于拼写检查。 正如我所提到的,我本身并不是在构建拼写检查器。我也发现它们的不准确性很烦人,更不用说它是多余的,因为浏览器通常都有自己的内置检查器。我想要做的是修复标点符号、大写字母等方面的错误——语言风格更正通常不符合一个人的意见或偏好,我敢说,自动更正更容易正确。但是,我是自定义的忠实支持者,因此用户可以在自动更正、错误突出显示和什么都没有之间进行选择。 【参考方案1】:

乍一看,您建议的方法(将span 中的每个单词分开并在其中存储其他数据)似乎是最明智的方法。在编辑器级别,您只需要确保所有文本都在某个 span 中,并且每个文本只包含一个单词(如有必要,将其拆分)。在单词级别上,只需监听spans 的变化(绑定inputpropertyChange)并根据其类/数据采取行动。

然而,真正的痛苦是保持插入符号的位置一致。当您更改 textarea 或带有 contentEditable 的元素的内容时,插入符号的移动相当不可预测,并且没有简单的(跨浏览器)方法来跟踪插入符号。我在 SO 和其他地方都搜索了解决方案,我找到的最简单的工作解决方案是 this blog post。不幸的是它只适用于textarea,因此无法使用“跨度中的每个单词”的解决方案。

所以,我建议采用以下方法:

Array 中保留单词列表,其中每个单词都存储当前值和原始值; 当textarea的内容发生变化时,保持不变的单词集,其余的重做; 仅当插入符号紧跟在非单词字符之后(有改进空间)并且您没有点击 backspace 时才应用拼写检查; 如果用户对更正不满意,点击一次backspace 将撤消它,并且不会再次检查它除非修改。 如果一次进行了多项更正(例如,如果复制粘贴了大量文本),则每个 backspace 都会撤消一次更正,直到没有人留下为止。 点击任何其他键将提交更正,因此如果用户仍然不满意,他将不得不返回并再次更改。 注意:与OP要求不同,如果用户输入非单词字符,更改后的版本再次自动更正;他需要点击一次backspace 来“保护”它。

我在jsFiddle 创建了一个简单的概念验证。详情如下。请注意,您可以将其与其他方法结合使用(例如,检测“向下箭头”键并显示带有一些自动更正选项的菜单)等。


proof-of-concept的步骤详解:

Array 中保留一个单词列表,其中每个单词都存储当前值和原始值;

var words = [];

此正则表达式将文本拆分为单词(每个单词都有一个 word 属性和一个 sp 属性;后者立即存储非单词字符)

delimiter:/^(\w+)(\W+)(.*)$/,
...
regexSplit:function(regex,text) 
    var ret = [];
    for ( var match = regex.exec(text) ; match ; match = regex.exec(text) ) 
        ret.push(
            word:match[1],
            sp:match[2],
            length:match[1].length + match[2].length
        );
        text = match[3];
    
    if ( text )
        ret.push(word:text, sp:'', length:text.length);
     return ret;

textarea的内容发生变化时,保持不变的单词集合,其余的重做;

    // Split all the text
    var split = $.autocorrect.regexSplit(options.delimiter, $this.val());
    // Find unchanged words in the beginning of the field
    var start = 0;
    while ( start < words.length && start < split.length ) 
        if ( !words[start].equals(split[start]) )
            break;
        start++;
    
    // Find unchanged words in the end of the field
    var end = 0;
    while ( 0 < words.length - end && 0 < split.length - end ) 
        if ( !words[words.length-end-1].equals(split[split.length-end-1]) ||
             words.length-end-1 < start )
            break;
        end++;
    
    // Autocorrects words in-between
    var toSplice = [start, words.length-end - start];
    for ( var i = start ; i < split.length-end ; i++ )
        toSplice.push(
            word:check(split[i], i),
            sp:split[i].sp,
            original:split[i].word,
            equals:function(w) 
                return this.word == w.word && this.sp == w.sp;
            
        );
    words.splice.apply(words, toSplice);
    // Updates the text, preserving the caret position
    updateText();

仅当插入符号就在非单词字符之后(有改进的空间)并且您没有点击 backspace 时才应用拼写检查;

var caret = doGetCaretPosition(this);
var atFirstSpace = caret >= 2 &&
                   /\w\W/.test($this.val().substring(caret-2,caret));
function check(word, index) 
    var w = (atFirstSpace && !backtracking ) ?
            options.checker(word.word) :
            word.word;
    if ( w != word.word )
        stack.push(index); // stack stores a list of auto-corrections
    return w;

如果用户对更正不满意,点击一次backspace 将撤消它,并且不会再次检查它除非修改

$(this).keydown(function(e) 
    if ( e.which == 8 ) 
        if ( stack.length > 0 ) 
            var last = stack.pop();
            words[last].word = words[last].original;
            updateText(last);
            return false;
        
        else
            backtracking = true;
        stack = [];
    
);

updateText 的代码只是将所有单词再次连接成一个字符串,并将值设置回textarea。如果没有任何更改,或者在最后一次完成/撤消的自动更正之后放置插入符号,则会保留插入符号,以说明文本长度的变化:

function updateText(undone) 
    var caret = doGetCaretPosition(element);
    var text = "";
    for ( var i = 0 ; i < words.length ; i++ )
        text += words[i].word + words[i].sp;
    $this.val(text);
    // If a word was autocorrected, put the caret right after it
    if ( stack.length > 0 || undone !== undefined ) 
        var last = undone !== undefined ? undone : stack[stack.length-1];
        caret = 0;
        for ( var i = 0 ; i < last ; i++ )
            caret += words[i].word.length + words[i].sp.length;
        caret += words[last].word.length + 1;
    
    setCaretPosition(element,caret);

最终的插件结构:

$.fn.autocorrect = function(options) 
    options = $.extend(
        delimiter:/^(\w+)(\W+)(.*)$/,
        checker:function(x)  return x; 
    , options);
    return this.each(function() 
        var element = this, $this = $(this);
        var words = [];
        var stack = [];
        var backtracking = false;
        function updateText(undone)  ... 
        $this.bind("input propertyChange", function() 
            stack = [];
            // * Only apply the spell check if the caret...
            // * When the contents of the `textarea` changes...
            backtracking = false;
        );
        // * If the user was unsatisfied with the correction...
    );
;
$.autocorrect = 
    regexSplit:function(regex,text)  ... 
;

【讨论】:

多么有趣的项目,你有幸有时间玩!【参考方案2】:

假设您只提交插入符号左侧的单词,您是否可以禁用拼写检查器,直到键入空格字符或移动文本框插入符号?

我不确定这是否是您想要的答案。

【讨论】:

以上是关于产生拼写检查器的主要内容,如果未能解决你的问题,请参考以下文章

VS Code:启用内联拼写检查,但在“问题”面板中禁用拼写检查

我的拼写检查器无法正确比较单词

贝叶斯拼写检查器

技巧118 对你的工作进行拼写检查

打开 Office 拼写检查器/Java API

最适合拼写检查器、字典和词库的算法和数据结构