Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace double quotes by quotation marks

I am looking for a way to replace the quotes with “corrected” quotations marks in an user input.

The idea

Here is a snippet briefly showing the principle:
For quotes, the “correct” ones have an opening and a closing , so it needs to be replaced in the good way.

$('#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>

But the above is not working in all cases.
For example, when the "quoted word" is at the very beginning or the very end of a sentence or a line.

Examples

Possible inputs (beware, french inside! :)) :
⋅ 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!"

Correct outputs:
⋅ 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!”

Incorrect outputs:
⋅ The sign says: “Some text ”some text“ some text.” and […]
Why it is incorrect:
→ There should be no space between the end of a quotation and its closing mark.
→ There should be a space between a closing quotation mark and a word.
→ There should be a space between a word and an opening quotation mark.
→ There should be no space between an opening quotation mark and its quotation.

The need

How could it be possible to effectively and easily replace the quotes in all those cases?
If possible, I'd also like the solution to be able to "correct" the quotes even if we add them after the typing of the whole sentence.

Note that I don't (can't) use the word delimiter "\b" in a regex because the “accented characters, such as "é" or "ü" are, unfortunately, treated as word breaks.” (source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)

Of course, if there is no other solution, I'll come up with a list of what I consider a word delimiter and use it in a regex. But I'd prefer to have a nice working function rather than a list!

Any idea would be appreciated.

like image 669
Takit Isy Avatar asked Apr 13 '18 11:04

Takit Isy


People also ask

Can you replace double quotes with single quotes?

Use the String. replace() method to replace double with single quotes, e.g. const replaced = str. replace(/"/g, "'"); . The replace method will return a new string where all occurrences of double quotes are replaced with single quotes.

How do you replace a double quote in a blank string?

You can use String#replace() for this. Note that you can also use an empty string "" instead to replace with. Else the spaces would double up.

How do you get rid of double quotes?

To remove double quotes from a string:Use the join() method to join the array of strings. The new string won't contain any double quotes.


2 Answers

It is working for many of the cases, at the exception of when the "word" is at the very beginning or the very end of a sentence or a line.

To solve that problem, you can use an alternation of a beginning/end of line assertion and the space, capture that, and use it in the replacement:

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

The alternation is ^| / $|. The capture group will be "" if it matched the assertion, or " " if it matched the 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>

However, you've said you want to avoid "escaping" characters on user input. I'm not sure where you're planning to use it, but something like the above is almost never the approach to use to a problem with that sort of description.

like image 144
T.J. Crowder Avatar answered Sep 28 '22 15:09

T.J. Crowder


I got a solution that finally fits all my needs.
I admit it is a lot more complicated than T.J.'s one, which can be perfect for simple cases.

Remember, my main problem was the impossilibity to use \b because of the accented characters.
I was able to get rid of that issue by using the solution from this topic:
Remove accents/diacritics in a string in JavaScript

After that, I used a modified function highly inspired from the answer here…
How do I replace a character at a particular index in JavaScript?

… and had a very hard time, playing a lot with RegEx to finally get to that solution:

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);

And below is a snippet of a working example with a textarea.
I've just created a function of the code of the first snippet, and I'm using a substring around the caret position to filter the calling of the function (that avoids calling it on every character input):

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;
}
<textarea id="myInput"></textarea>

It seems to work with all I can imagine right now.
The function correctly replaces the quotes when:
⋅ typing regularly,
⋅ adding quotes after we typed the text,
⋅ pasting text.

It replaces both the double and the singles quotes.

Anyway, as I am not a RegEx expert at all, please feel free to comment if you notice a behaviour that may be unwanted, or a way to improve the expressions.

like image 21
Takit Isy Avatar answered Sep 28 '22 14:09

Takit Isy