如何将嵌套在双引号内的双引号替换为单引号

3

我目前正在使用替换脚本来自动修复单引号和双引号。

但是,我无法找到解决方案来更改嵌套在其他双引号中的双引号

所以:

“这里有一些额外的文字在开头“abc”和结尾”

应该是

“这里有一些额外的文字在开头‘abc’和结尾”

目前,我只能使用简单的替换脚本自动修复如果旁边有其他引号的情况(例如““abc””)

.replaceAll("““", "“‘").replaceAll("””", "’”")

是否可能使用正则表达式来针对任何嵌套在其他双引号内的双引号?

注意: 引号是弯曲的(“”和‘’),而不是直的(""和'')。

function fixTextarea(textarea) {
  textarea.value = textarea.value.replace(" ,", ",")
    .replaceAll(" ;", ";")
    .replaceAll(" .", ".")
    .replaceAll("  ", " ")
    .replaceAll("   ", " ")
    .replaceAll("“ ", "“")
    .replaceAll(" ”", "”")
    .replaceAll("““", "“‘")
    .replaceAll("””", "’”")
    .replaceAll(/(^|[-\u2014\s(\["])'/g, "$1\u2018")
    .replaceAll(/'/g, "\u2019")
    .replaceAll(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c")
    .replaceAll(/"/g, "\u201d")
};

function fixtext() {
  let textarea = document.getElementById("textarea1");
  textarea.select();
  fixTextarea(textarea);
}

window.addEventListener('DOMContentLoaded', function(e) {
  var area = document.getElementById("textarea1");

  var getCount = function(str, search) {
    return str.split(search).length - 1;
  };

  var replace = function(search, replaceWith) {
    if (typeof(search) == "object") {
      area.value = area.value.replace(search, replaceWith);
      return;
    }
    if (area.value.indexOf(search) >= 0) {
      var start = area.selectionStart;
      var end = area.selectionEnd;
      var textBefore = area.value.substr(0, end);
      var lengthDiff = (replaceWith.length - search.length) * getCount(textBefore, search);
      area.value = area.value.replace(search, replaceWith);
      area.selectionStart = start + lengthDiff;
      area.selectionEnd = end + lengthDiff;
    }
  };

});
<textarea class="lined" id="textarea1" name="textarea1" spellcheck="true" placeholder="" onpaste="console.log('onpastefromhtml')"></textarea>
<br><br>
<button onclick="fixtext()"> Fixit</button>


3
如果你展示的输入是代表性的,那么最简单的解决方案可能是获取外部引号内的字符串(通过从索引1到长度-2获取子字符串),替换内部字符串中的所有引号,并放回外部引号。 - Pac0
非常抱歉,由于我刚开始学习JavaScript和正则表达式,无法理解您的建议。您能否详细说明一下? - ixcode
3个回答

1
你可以跟踪引用嵌套的深度(无论是双引号还是单引号),并以这样的方式替换它们,使得在偶数深度时它们为双引号,在奇数深度时它们为单引号。一个预防措施是排除 ’s 中的撇号(可能还有其他一些例外情况,其中撇号不应被视为引用的结尾):

let s = "“Here’s some extra text at the beginning “abc” and at the end”";

let depth = 0;
let result = s.replace(/[“”‘]|’(?!s)/g, m => 
    "“‘".includes(m) ? "“‘"[depth++] : "”’"[--depth]);

console.log(result);

NB:这甚至可以修复引号错误配对的一些情况。

它可以像这样集成到您当前的函数中:

function fixTextarea(textarea) {
    let depth = 0; // <-- added
    textarea.value = textarea.value.replace(" ,", ",")
        .replaceAll(" ;", ";")
        .replaceAll(" .", ".")
        .replaceAll("  ", " ")
        .replaceAll("   ", " ")
        .replaceAll("“ ", "“")
        .replaceAll(" ”", "”")
        // (removed two lines here)
        .replaceAll(/(^|[-\u2014\s(\["])'/g, "$1\u2018")
        .replaceAll(/'/g, "\u2019")
        .replaceAll(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c")
        .replaceAll(/"/g, "\u201d")
        // added:
        .replace(/[“”‘]|’(?!s)/g, m => 
             "“‘".includes(m) ? "“‘"[depth++] : "”’"[--depth])
};

我没有详细检查你正在进行的其他替换操作,但对这两个操作有一些疑问:

.replaceAll(/'/g, "\u2019") 
.replaceAll(/"/g, "\u201d")

这些调用会将直引号替换为卷曲的结束引号,这显然会导致没有匹配开头引号的结果...
我对靠近这两个替换的另外两个替换也有类似的担忧。

这似乎是一个非常好的解决方案,但你能否请将其更改以匹配我的示例代码?我试图将它混合在一起,但由于某些原因失败了。我真的想尝试一下。(不太擅长JavaScript) - ixcode
1
刚才我刚刚在我的回答中添加了那个。 - trincot
太棒了!它对我现在尝试的任何样本都有效。我也不确定那两个是什么,但它可以帮助我将直引号自动修正为卷曲引号。如果我尝试删除它们,它不会被修复。我目前脚本遇到的唯一问题是,如果单词和闭引号之间有空格,它会变成开引号而不是闭引号。(例如,“This closing quote will turn into an opening quote because there's a space before it. ”) - ixcode

0

试试这个:

console.log("“Here’s some extra text at the beginning “abc” and at the end”"
  .replace(/(“.*?)“(.*?)”(.*”)/, "$1‘$2’$3"));

示例在regex101


2
现在尝试使用一个包含两个或更多带引号单词的字符串来测试你的正则表达式。你可能需要在循环中进行替换,直到没有匹配项为止。 - Wiktor Stribiżew
@WiktorStribiżew 是的,考虑过了。 - kishkin

0

好的,我的上一个答案很糟糕。

现在只是为了好玩:一个嵌套引用解析工具,可以处理直引号和弯引号,而不需要任何RegExp

const {
  nestedDoubleQuotes2Single,
  nestedSingleQuotes2Double
} = nestedQuotesParser();
const singleCurly2Double = `‘Here’s some extra ‘text’ at the beginning ‘abc’ and at the end ’`;
const doubleCurly2Single = "“Here’s some extra “text” at the beginning “abc” and at the end ”";
const doubleStraight2Single = '"Here\'s some extra "text" at the beginning "abc" and at the end. "';
const singleStraight2Double = "'Here's some extra 'text' at the beginning 'abc' and at the end'";

console.log(nestedDoubleQuotes2Single(doubleCurly2Single, true));
console.log(nestedDoubleQuotes2Single(doubleStraight2Single));
console.log(nestedSingleQuotes2Double(singleStraight2Double));
console.log(nestedSingleQuotes2Double(singleCurly2Double, true));

function nestedQuotesParser() {
  let isCurly = false;
  const quoting = {
    get all() {
      return isCurly ? `“”‘’` : `""'`;
    },
    get single() {
      return isCurly ? `‘’` : `''`;
    },
    get double() {
      return isCurly ? `“”` : `""`;
    },
  };
  const reQuotDouble = m => quoting.single[quoting.all.indexOf(m)] || m;
  const reQuotSingle = m => quoting.double[quoting.all.indexOf(m) - 2] || m;
  const checkNestedSingle = (s, chr, i) =>
    ~quoting.all.indexOf(chr) && (i < s.length - 2 && (s[i - 1] === " " || s[i + 1] === " "));

  return {
    nestedDoubleQuotes2Single: (s, curly = false) => {
      isCurly = curly;
      return s.split("")
        .reduce((acc, chr, i) =>
          acc + (i > 0 && i < s.length - 1 &&
            ~quoting.all.indexOf(chr) ? reQuotDouble(chr) : chr), '');
    },
    nestedSingleQuotes2Double: (s, curly = false) => {
      isCurly = curly;
      return s.split("").reduce((acc, chr, i) =>
        acc + (s.length - i < s.length - 1 && checkNestedSingle(s, chr, i) ?
          curly && reQuotSingle(chr) || '"' :
          chr), "");
    },
  };
}

使用此工具的您的代码:

function fixTextarea(textarea) {
    let depth = 0; // <-- added
    const value = textarea.value.replace(" ,", ",")
        .replaceAll(" ;", ";")
        .replaceAll(" .", ".")
        .replaceAll("  ", " ")
        .replaceAll("   ", " ")
        .replaceAll("“ ", "“")
        .replaceAll(" ”", "”")
        .replaceAll(/(^|[-\u2014\s(\["])'/g, "$1\u2018")
        .replaceAll(/'/g, "\u2019")
        .replaceAll(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c")
        .replaceAll(/"/g, "\u201d");
    textarea.value = nestedDoubleQuotes2Single(value, true);
};

3
对于当前的问题,/[^^“]“|”[^”$]/g 是一个错误的正则表达式。你的正则表达式会移除 前面和 后面的字符。另外,[^^“] 是一个错误的模式,它匹配除了 ^“” 以外的任何字符,同样的问题也出现在 ”[^”$] 中。^$ 在字符类中不是特殊字符。 - Wiktor Stribiżew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接