草庐IT

javascript - 产生拼写检查器

coder 2024-05-14 原文

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

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

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

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

  1. 用户输入一个会触发检查器的词,然后输入一个 空间。
  2. 检查员纠正了这个词。
  3. 用户认为这是一个错误 并更正它(通过退格整个单词或部分单词, 或突出显示它,或者以他们觉得舒服的方式对其进行编辑)。
  4. 的 用户继续输入,检查器不会再次触及该单词的那个实例。

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

我什至不知道从哪里开始。我在想一个 contenteditable,每个单词都包含在一个 span 中,自动更正的单词有一个特殊的类和一个包含原始单词的 data-* 属性,听取对自动更正单词的编辑,如果它被编辑回等于 data-*值,添加一个将其排除在未来自动更正轮次之外的类。

我在想这可能会不必要地复杂化,或者至少不是阻力最小的路径。这样做最明智的方法是什么?

最佳答案

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

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

因此,我建议采用以下方法:

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

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


proof-of-concept 的步骤详细解释:

  • 数组中保存单词列表,其中每个单词都存储当前值和原始值;

    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) { ... }
    };
    

关于javascript - 产生拼写检查器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12387910/

有关javascript - 产生拼写检查器的更多相关文章

  1. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  2. ruby - 检查数组是否在增加 - 2

    这个问题在这里已经有了答案:Checktoseeifanarrayisalreadysorted?(8个答案)关闭9年前。我只是想知道是否有办法检查数组是否在增加?这是我的解决方案,但我正在寻找更漂亮的方法:n=-1@arr.flatten.each{|e|returnfalseife

  3. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  4. ruby - 检查字符串是否包含散列中的任何键并返回它包含的键的值 - 2

    我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案

  5. ruby-on-rails - Ruby 检查日期时间是否为 iso8601 并保存 - 2

    我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby​​是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查

  6. ruby - 检查日期是否在过去 7 天内 - 2

    我的日期格式如下:"%d-%m-%Y"(例如,今天的日期为07-09-2015),我想看看是不是在过去的七天内。谁能推荐一种方法? 最佳答案 你可以这样做:require"date"Date.today-7 关于ruby-检查日期是否在过去7天内,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/32438063/

  7. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

  8. css - 用 watir 检查标签类? - 2

    我有一个div,它根据表单是否正确提交而改变。我想知道是否可以检查类的特定元素?开始元素看起来像这样。如果输入不正确,添加错误类。 最佳答案 试试这个:browser.div(:id=>"myerrortest").class_name更多信息:http://watir.github.com/watir-webdriver/doc/Watir/HTMLElement.html#class_name-instance_method另一种选择是只查看具有您期望的类的div是否存在browser.div((:id=>"myerrortes

  9. ruby-on-rails - rspec - 如何检查方法是否存在? - 2

    我的模型有defself.empty_building//stuffend我怎样才能对这个现有的进行rspec?,已经尝试过:describe"empty_building"dosubject{Building.new}it{shouldrespond_to:empty_building}endbutgetting:Failure/Error:it{shouldrespond_to:empty_building}expected#torespondto:empty_building 最佳答案 你有一个类方法self.empty_bu

  10. 屏幕录制为什么没声音?检查这2项,轻松解决 - 2

    相信很多人在录制视频的时候都会遇到各种各样的问题,比如录制的视频没有声音。屏幕录制为什么没声音?今天小编就和大家分享一下如何录制音画同步视频的具体操作方法。如果你有录制的视频没有声音,你可以试试这个方法。 一、检查是否打开电脑系统声音相信很多小伙伴在录制视频后会发现录制的视频没有声音,屏幕录制为什么没声音?如果当时没有打开音频录制,则录制好的视频是没有声音的。因此,建议在录制前进行检查。屏幕上没有声音,很可能是因为你的电脑系统的声音被禁止了。您只需打开电脑系统的声音,即可录制音频和图画同步视频。操作方法:步骤1:点击电脑屏幕右下侧的“小喇叭”图案,在上方的选项中,选择“声音”。 步骤2:在“声

随机推荐