【问题标题】:Replace double quotes by quotation marks用引号替换双引号
【发布时间】:2018-09-23 18:12:40
【问题描述】:

我正在寻找一种在用户输入中用“更正”的引号替换引号的方法。

想法

这里有一个sn-p简单说明原理:
对于引号,“正确”的引号有一个开头 和一个结尾 ,因此需要以好的方式替换它。

$('#myInput').on("keyup", function(e) {
  // The below doesn't work when there's no space before or after.
  this.value = this.value.replace(/ "/g, ' “');
  this.value = this.value.replace(/" /g, '” ');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="myInput"></textarea>

但上述方法并非在所有情况下都有效。
例如,当“引用的单词”位于句子或行的开头或结尾时。

示例

可能的输入(当心,法语在里面!:)):
⋅ 我很“快乐”! Ça y est, j'ai "osé", et mon "âme sœur" était au rendez-vous...
⋅ 标语上写着:“一些文字‘一些文字’一些文字。”和“注意这里的空格!”
⋅ "Inc" 或"rect" 引号"不应被替换。
⋅ 我说:“如果它也适用于'单曲',我会更喜欢它!”

正确的输出:
⋅ 我很“快乐”! Ça y est, j'ai “osé”, et mon “âme sœur” était au rendez-vous...
⋅ 标语上写着:“一些文字‘一些文字’一些文字。”和“注意这里的空格!”
⋅ “Inc”或“rect”引号不应被替换。
⋅ 我说:“如果它也适用于‘单曲’,我会更喜欢它!”

输出不正确:
⋅ 标语上写着:“一些文字”一些文字“一些文字”。和 […]
为什么不正确:
→ 引号的结尾和结束标记之间不应有空格。
→ 右引号和单词之间应该有一个空格。
→ 单词和左引号之间应该有一个空格。
→ 左引号与其引文之间不应有空格。

需要

如何在所有这些情况下有效且轻松地替换引号?
如果可能的话,我还希望解决方案能够“更正”引号,即使我们在输入整个句子之后添加它们。

请注意,我不(不能)在正则表达式中使用单词分隔符“\b”,因为“很遗憾,重音字符,例如“é”或“ü”被视为分词。” (来源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions

当然,如果没有其他解决方案,我会提出一个我认为是单词分隔符的列表,并在正则表达式中使用它。但我更喜欢有一个很好的工作函数而不是一个列表!

任何想法都将不胜感激。

【问题讨论】:

  • 你为什么不直接使用replace(/"/g, '”')
  • @str, 不适合引用的开头。
  • 你能展示一些样本输入和它们的样本输出吗?这样所有的边缘情况都可以处理了吗?
  • @TarunLalwani,我添加了一些示例。当心,法国人!
  • @TakitIsy,当有人输入 "tarun lalwani" 时会发生什么?是不是也变成了“tarun lalwani”

标签: javascript jquery regex


【解决方案1】:

它适用于许多情况,但“单词”位于句子或一行的开头或结尾时除外。

要解决这个问题,您可以使用行首/行尾断言和空格的交替,捕获它,并在替换中使用它:

this.value = this.value.replace(/(^| )"/g, '$1“');
this.value = this.value.replace(/"($| )/g, '”$1');

交替是^| / $|。捕获组将是"",如果它匹配断言,或者" ",如果它匹配sapce。

$('#myInput').on("keyup", function(e) {
  this.value = this.value.replace(/'/g, '’');
  // The below doesn't work when there's no space before or after.
  this.value = this.value.replace(/(^| )"/g, '$1“');
  this.value = this.value.replace(/"($| )/g, '”$1');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="myInput"></textarea>

但是,您曾说过要避免在用户输入中“转义”字符。我不确定你打算在哪里使用它,但是像上面这样的东西几乎从来不是用来解决这种描述问题的方法。

【讨论】:

  • 当开始在行首输入" 时,它会添加一个不必要的空格字符。最后也是一样。 "word". 变成`“单词”。`
  • 你说得对,我删除了我所说的“转义字符”的部分。
  • @TakitIsy:抱歉,没有对替换给予足够的重视。我现在已经在答案中解决了这个问题。
  • 感谢您的更新。这是另一个:当我输入word, 然后想要添加标记时,, 之前的那个不会被替换。我正在考虑另一种方法:您知道我们是否可以轻松“检测”单词的开头和结尾吗? (这样我们就可以避免将所有的标点符号放在正则表达式中)
  • @TakitIsy:见MDN's regex documentation\b 应该断言一个字边界,尽管它认为word-breakd- 之间的边界是一个边界。
【解决方案2】:

因此,我不会使用正则表达式替换方法,而是使用带有引号平衡行为的简单循环。您假设出现的每个单引号都将与另一个引号匹配,并且当匹配时,它将被成对替换。

下面是相同的测试实现

String.prototype.replaceAt=function(index, replacement) {
return this.substr(0, index) + replacement+ this.substr(index + replacement.length);
}

tests  =[
// [`I'm "happy"! J'ai enfin "osé". La rencontre de mon "âme-sœur" a "été" au rendez-vous…
// and how it should look after correction:`, `I'm "happy"! J'ai enfin "osé". La rencontre de mon "âme-sœur" a "été" au rendez-vous…
// and how it should look after correction:`],
[`tarun" lalwani"`, `tarun” lalwani”`],
[`tarun lalwani"`, `tarun lalwani”`],
[`"tarun lalwani`,`“tarun lalwani`],
[`"tarun" lalwani`,`“tarun” lalwani`],
[`"tarun" lalwani"`,`“tarun” lalwani”`],
[`"tarun lalwani"`, `“tarun lalwani”`]
]

function isCharacterSeparator(value) {
return /“, /.test(value)
}

for ([data, output] of tests) {
let qt = "“”"
let qtL = '“'
let qtR = '”'
let bal = 0
let pattern = /["“”]/g
let data_new = data
while (match = pattern.exec(data)) {
    if (bal == 0) {
        if (match.index == 0) {
            data_new = data_new.replaceAt(match.index, qt[bal]);
            bal = 1
        } else {
            if (isCharacterSeparator(data_new[match.index-1])) {
                data_new = data_new.replaceAt(match.index, qtL);
            } else {
                data_new = data_new.replaceAt(match.index, qtR);
            }
        }
    } else {
        if (match.index == data.length - 1) {
            data_new = data_new.replaceAt(match.index, qtR);
        } else if (isCharacterSeparator(data_new[match.index-1])) {
            if (isCharacterSeparator(data_new[match.index+1])) {
                //previous is separator as well as next one too
                // "tarun " lalwani"
                // take a call what needs to be done here?

            } else {
                data_new = data_new.replaceAt(match.index, qtL);
            }
        } else {
            if (isCharacterSeparator(data_new[match.index+1])) {
                data_new = data_new.replaceAt(match.index, qtL);
            } else {
                data_new = data_new.replaceAt(match.index, qtR);
            }
        }
    }


}

console.log(data_new)
if (data_new != output) {
  console.log(`Failed to parse '${data}' Actual='${data_new}' Expected='${output}'`)
} ;
}

更新 1:2018 年 4 月 20 日

我已经更新了功能。仍然可能存在一些边缘情况,但您应该将所有内容都放在测试中并运行它并修复那些不符合预期的情况

【讨论】:

  • 有趣的是,我刚刚发现String.prototype.replaceAt 是一个解决方案的主题。 :) (stackoverflow.com/questions/1431094/…) ⋅⋅⋅ 我正试图用它做点什么。
  • 在您的示例中,您输入了"tarun lalwani,但如果您将引号放在末尾tarun lalwani",则会出现错误更正!
  • 这取决于你如何看待它。我可以输入tarun lalwani"quote"。你需要决定在这种情况下你需要做什么。如果 bal==1 在最后,那么您可以检查 match.index 并额外替换不平衡的 qoute
  • 这也是我希望您发布预期输入与输出的另一个原因。会有像tarun" lalwani" 这样的情况,现在预期的输出是什么?你应该列出你想要处理的所有可能的情况,然后回到解决方案,更像是 TDD 方法
  • tarun" lalwani" 不是正确的书写方式,但结果应该是 tarun” lalwani”,因为引号位于单词的末尾。我将添加它作为示例。 (我暂时只提出了“正确”的使用方式)
【解决方案3】:

我找到了一个最终满足我所有需求的解决方案。
我承认它比 T.J. 的要复杂得多,这对于简单的情况来说是完美的。

请记住,我的主要问题是由于重音字符而无法使用 \b
通过使用本主题的解决方案,我能够摆脱该问题:
Remove accents/diacritics in a string in JavaScript

在那之后,我使用了一个修改后的函数,灵感来自这里的答案……
How do I replace a character at a particular index in JavaScript?

... 度过了一段非常艰难的时光,在 RegEx 中玩了很多次才最终得到了那个解决方案:

var str_orig = `· I'm "happy" ! Ça y est, j'ai "osé", et mon "âme sœur" était au rendez-vous…
· The sign says: "Some text "some text" some text." and "Note the space here !"
⋅ "Inc"or"rect" quo"tes should " not be replaced.
· I said: "If it works on 'singles' too, I'd love it even more!"
word1" word2"
word1 word2"
"word1 word2
"word1" word2
"word1" word2"
"word1 word2"`;

// Thanks, exactly what I needed!
var str_norm = str_orig.normalize('NFD').replace(/[\u0300-\u036f]/g, '');

// Thanks for inspiration
String.prototype.replaceQuoteAt = function(index, shift) {
  const replacers = "“‘”’";
  var offset = 1 * (this[index] == "'") + 2 * (shift);
  return this.substr(0, index) + replacers[offset] + this.substr(index + 1);
}

// Opening quote: not after a boundary, not before a space or at the end
var re_start = /(?!\b)["'](?!(\s|$))/gi;
while ((match = re_start.exec(str_norm)) != null) {
  str_orig = str_orig.replaceQuoteAt(match.index, false);
}

// Closing quote: not at the beginning or after a space, not before a boundary
var re_end = /(?<!(^|\s))["'](?!\b)/gi;
while ((match = re_end.exec(str_norm)) != null) {
  str_orig = str_orig.replaceQuoteAt(match.index, true);
}

console.log("Corrected: \n", str_orig);

下面是带有textarea 的工作示例的sn-p。
我刚刚创建了第一个 sn-p 代码的函数,并且我使用插入符号位置周围的子字符串来过滤函数的调用(避免在每个字符输入上调用它):

String.prototype.replaceQuoteAt = function(index, offset) {
  const replacers = "“‘”’";
  var i = 2 * (offset) + 1 * (this[index] == "'");
  return this.substr(0, index) + replacers[i] + this.substr(index + 1);
}

function replaceQuotes(str) {
  var str_norm = str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
  var re_quote_start = /(?!\b)["'](?!(\s|$))/gi;
  while ((match = re_quote_start.exec(str_norm)) != null) {
    str = str.replaceQuoteAt(match.index, false);
  }
  var re_quote_end = /(?<!(^|\s))["'](?!\b)./gi;
  while ((match = re_quote_end.exec(str_norm)) != null) {
    str = str.replaceQuoteAt(match.index, true);
  }
  return str;
}

var pasted = 0;
document.getElementById("myInput").onpaste = function(e) {
  pasted = 1;
}

document.getElementById("myInput").oninput = function(e) {
  var caretPos = this.selectionStart; // Gets caret position
  var chars = this.value.substring(caretPos - 2, caretPos + 1); // Gets 2 chars before caret (just typed and the one before), and 1 char just after
  if (pasted || chars.includes(`"`) || chars.includes(`'`)) { // Filters the calling of the function
    this.value = replaceQuotes(this.value); // Calls the function
    if (pasted) {
      pasted = 0;
    } else {
      this.setSelectionRange(caretPos, caretPos); // Restores caret position
    }
  }
}
#myInput {
  width: 90%;
  height: 100px;
}
&lt;textarea id="myInput"&gt;&lt;/textarea&gt;

它似乎适用于我现在所能想象的一切。
该函数在以下情况下正确替换引号:
⋅ 定期打字,
⋅ 在我们输入文本后添加引号,
⋅ 粘贴文本。

它替换了双引号和单引号。

无论如何,由于我根本不是 RegEx 专家,如果您发现可能不受欢迎的行为或改进表达方式的方法,请随时发表评论。

【讨论】:

  • 代码看起来不错,我会像我在答案中所做的那样通过所有测试来运行它,以确保它涵盖了您的所有场景并且可以完美运行。它还将确保您所做的任何更改都不会破坏任何现有测试用例
  • @TarunLalwani 感谢您的评论。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-12
  • 1970-01-01
  • 2018-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-26
相关资源
最近更新 更多