|
function matchQuotes(string, chars, lengths, internals) { |
|
const reversed = string.split('').reverse().join(''); |
|
const matches = []; |
|
let match; |
|
let open, close, esc; |
|
let min, max; |
|
|
|
if (typeof chars === 'string') |
|
close = chars; |
|
|
|
else if (chars instanceof Array) { |
|
open = chars[0] || undefined; |
|
close = chars[1] || undefined; |
|
esc = chars[2] || undefined; |
|
} |
|
|
|
else if (chars instanceof Object) { |
|
open = chars.open || undefined; |
|
close = chars.close || undefined; |
|
esc = chars.esc || undefined; |
|
} |
|
|
|
if (Number.isInteger(lengths)) |
|
min = lengths; |
|
|
|
else if (lengths instanceof Array) { |
|
min = lengths[0] || undefined; |
|
max = lengths[1] || undefined; |
|
} |
|
|
|
else if (lengths instanceof Object) { |
|
min = lengths.min || undefined; |
|
max = lengths.max || undefined; |
|
} |
|
|
|
// Regex escape the chars or set default |
|
esc = typeof esc === 'string' ? esc.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : '\\\\'; |
|
open = typeof open === 'string' ? open.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : '\\1'; |
|
close = typeof close === 'string' ? close.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : '\'"'; |
|
|
|
// const evenEsc = '(?:[' + esc + ']{2})*(?![' + esc + '])'; |
|
const oddEsc = '(?:[' + esc + ']{2})*[' + esc + '](?![' + esc + '])'; |
|
|
|
const approve = '(?=' + oddEsc + ')'; |
|
// Approve with '\' as the escape char |
|
// (?= # Positive lookahead |
|
// (?:[\\]{2})* # Match Pairs of escape char |
|
// [\\] # Match single escape |
|
// (?![\\]) # Ensure no following escape char |
|
// ) |
|
|
|
const negate = '(?!' + oddEsc + ')'; |
|
// Negate with '\' as the escape char |
|
// (?! # Negative lookahead |
|
// (?:[\\]{2})* # Match Pairs of escape char |
|
// [\\] # Match single escape |
|
// (?![\\]) # Ensure no following escape char |
|
// ) |
|
|
|
if (typeof internals !== 'string') { |
|
if (open === '\\1') { |
|
internals = ( |
|
'(?:' + |
|
'(?:\\1' + approve + ')|' + |
|
'(?:(?!\\1).)' + |
|
')' |
|
); |
|
} else { |
|
internals = ( |
|
'(?:' + |
|
'(?:[' + close + ']' + approve + ')|' + |
|
'(?:[' + open + ']' + approve + ')|' + |
|
'(?:[^' + open + close + '])' + |
|
')' |
|
); |
|
} |
|
} |
|
|
|
// Open is a back reference |
|
// (?: |
|
// (?: |
|
// \1 # Match any close quote |
|
// approve # Not followed by an escape |
|
// )| |
|
// (?:(?!\1).) # Match any char that is no the close quote |
|
// ) |
|
|
|
// Open is not a back reference |
|
// (?: |
|
// (?:[“](?!(?=[\\]{2})*[\\](?![\\])))| # Allow '“' as long as it's escaped with '\' |
|
// (?:[”](?!(?=[\\]{2})*[\\](?![\\])))| # Allow '”' as long as it's escaped with '\' |
|
// (?:[^“”]) # Match anything that is not '“' or '”' |
|
// ) |
|
|
|
const length = ( |
|
'{' + |
|
(min || '0') + |
|
',' + |
|
(max || '') + |
|
'}' |
|
); |
|
// Min = undefined, Max = undefined |
|
// {0,} # Length >= 0 |
|
|
|
// Min = 5, Max = undefined |
|
// {5,} # Length >= 5 |
|
|
|
// Min = undefined, Max = 10 |
|
// {0,10} # 0 <= Length <= 10 |
|
|
|
// Min = 5, Max = 10 |
|
// {5,10} # 5 <= Length <= 10 |
|
|
|
const regex = new RegExp( |
|
// Match the closing quote |
|
'([' + close + '])' + negate + |
|
// Match the string inside the quotes |
|
'(' + internals + length + ')' + |
|
// Match the opening quote |
|
'(' + (open === '\\1' ? '\\1' : ('[' + open + ']')) + ')' + negate, |
|
'g' |
|
); |
|
|
|
while(match = regex.exec(reversed)) { |
|
matches.push( |
|
match[2] |
|
.split('') |
|
.reverse() |
|
.join('') |
|
.replace(new RegExp(esc + match[1].replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g'), match[1]) |
|
.replace(new RegExp(esc + match[3].replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g'), match[3]) |
|
); |
|
} |
|
|
|
return matches; |
|
} |