Skip to content

Instantly share code, notes, and snippets.

@scagood
Last active January 17, 2018 10:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scagood/bd99371c072d49a4fee29d193252f5fc to your computer and use it in GitHub Desktop.
Save scagood/bd99371c072d49a4fee29d193252f5fc to your computer and use it in GitHub Desktop.
Match quotes inside strings. Images generated with (https://jex.im/regulex)

Escaped Quote Matcher

This is a javascript function that matches quotes while disregarding all escaped quotes.

Calling

outputArray = matchQuotes(
  inputString,
  chars,
  lengths,
  internals
);

Options:

const chars = {
  open: '\\1',
  close: '"\'',
  esc: '\\'
};
const lengths = {
  min: 0,
  max: Infinity
}
// internals is very dynamic (see: matchQuotes.js line 60)
Returns an array of matches.

Internals: matchQuotes.js line 60

Code...

Start quote: '[' End quote: ']' Escape Char: '\'

let out = matchQuotes(
  'test[key\\[sub\\]]', [
    '[', // Start Quote
    ']', // End Quote
    '\\' // Escape Quote
  ]
);

Generated regex:

image1

/
([\]])(?!(?:[\\]{2})*[\\](?![\\]))
((?:
  (?:[\]](?=(?:[\\]{2})*[\\](?![\\])))|
  (?:[\[](?=(?:[\\]{2})*[\\](?![\\])))|
  (?:[^\[\]])
){0,})
([\[])(?!(?:[\\]{2})*[\\](?![\\]))
/g

Output

[
  "key[sub]"
]

Code...

Start quote: '"' or "'" End quote: '"' or "'" Escape Char: '\'

let out = matchQuotes('my \\"new\\" string and \"this should \\"be\\" matched\"');

Generated regex:

image2

/
(['"])(?!(?:[\\]{2})*[\\](?![\\]))
((?:
  (?:\1(?=(?:[\\]{2})*[\\](?![\\])))|
  (?:(?!\1).)
){0,})
(\1)(?!(?:[\\]{2})*[\\](?![\\]))
/g

Output

[
  "this should \"be\" matched"
]

Code...

let out = matchQuotes(
	'my \\"new\\" “strin\\“g\\” and” \"this should \\"be\\" matched\"', [
		'“', // Start
		'”', // End
		'\\' // Escape
	]
);

Generated regex:

image3

/
([])(?!(?:[\\]{2})*[\\](?![\\]))
((?:
	(?:[](?=(?:[\\]{2})*[\\](?![\\])))|
	(?:[](?=(?:[\\]{2})*[\\](?![\\])))|
	(?:[^])
){0,})
([])(?!(?:[\\]{2})*[\\](?![\\]))
/g

Output

[
	"strin“g” and"
]
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;
}
function matchQuotes(string, chars, lens, internals) {
const reversed = string.split('').reverse().join('');
const matches = [];
let match;
let open, close, esc;
let min, max;
open = typeof chars === 'string' ? null : (typeof chars == 'object' ? chars.open || chars[0] : null);
close = typeof chars === 'string' ? chars : (typeof chars == 'object' ? chars.close || chars[1] : null);
esc = typeof chars === 'string' ? null : (typeof chars == 'object' ? chars.esc || chars[2] : null);
min = Number.isInteger(lens) ? lens : (typeof lens == 'object' ? (lens.min || lens[0] || '0') : '0');
max = Number.isInteger(lens) ? '' : (typeof lens == 'object' ? (lens.max || lens[1] || '' ) : '');
esc = typeof esc === 'string' ? esc.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : '\\\\';
open = typeof open === 'string' ? open.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : '\\1';
close = typeof close === 'string' ? close.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : '\'"';
const oddEsc = '(?:[' + esc + ']{2})*[' + esc + '](?![' + esc + '])';
const approve = '(?=' + oddEsc + ')';
internals = typeof internals === 'string' ? internals : (
open === '\\1' ? '(?:(?:\\1' + approve + ')|(?:(?!\\1).))' : (
'(?:(?:['+close+']'+approve+')|(?:['+open+']'+approve+')|(?:[^'+open+close+']))'
)
);
open = open === '\\1' ? '\\1' : '[' + open + ']';
const regex = new RegExp(
'([' + close + '])(?!' + oddEsc + ')' +
'(' + internals + '{' + min + ',' + max + '}' + ')' +
'(' + open + ')(?!' + oddEsc + ')',
'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;
}
function matchQuotes(a,b,c,d){const e=a.split('').reverse().join(''),f=[];let g,h,i,j,k,l;h='string'==typeof b?null:'object'==typeof b?b.open||b[0]:null,i='string'==typeof b?b:'object'==typeof b?b.close||b[1]:null,j='string'==typeof b?null:'object'==typeof b?b.esc||b[2]:null,k=Number.isInteger(c)?c:'object'==typeof c?c.min||c[0]||'0':'0',l=Number.isInteger(c)?'':'object'==typeof c?c.max||c[1]||'':'',j='string'==typeof j?j.replace(/[-\/\\^$*+?.()|[\]{}]/g,'\\$&'):'\\\\',h='string'==typeof h?h.replace(/[-\/\\^$*+?.()|[\]{}]/g,'\\$&'):'\\1',i='string'==typeof i?i.replace(/[-\/\\^$*+?.()|[\]{}]/g,'\\$&'):'\'"';const m='(?:['+j+']{2})*['+j+'](?!['+j+'])',n='(?='+m+')';d='string'==typeof d?d:'\\1'===h?'(?:(?:\\1'+n+')|(?:(?!\\1).))':'(?:(?:['+i+']'+n+')|(?:['+h+']'+n+')|(?:[^'+h+i+']))',h='\\1'===h?'\\1':'['+h+']';for(const o=new RegExp('(['+i+'])(?!'+m+')('+d+'{'+k+','+l+'})('+h+')(?!'+m+')','g');g=o.exec(e);)f.push(g[2].split('').reverse().join('').replace(new RegExp(j+g[1].replace(/[-\/\\^$*+?.()|[\]{}]/g,'\\$&'),'g'),g[1]).replace(new RegExp(j+g[3].replace(/[-\/\\^$*+?.()|[\]{}]/g,'\\$&'),'g'),g[3]));return f}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment