|
|
@ -16,136 +16,139 @@ function replaceAt(str, index, ch) { |
|
|
|
return str.substr(0, index) + ch + str.substr(index + 1); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
module.exports = function smartquotes(state) { |
|
|
|
/*eslint max-depth:0*/ |
|
|
|
function process_inlines(tokens, state) { |
|
|
|
var i, token, text, t, pos, max, thisLevel, item, lastChar, nextChar, |
|
|
|
isLastPunctChar, isNextPunctChar, isLastWhiteSpace, isNextWhiteSpace, |
|
|
|
canOpen, canClose, j, isSingle, blkIdx, tokens, stack; |
|
|
|
|
|
|
|
if (!state.md.options.typographer) { return; } |
|
|
|
canOpen, canClose, j, isSingle, stack; |
|
|
|
|
|
|
|
stack = []; |
|
|
|
|
|
|
|
for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { |
|
|
|
|
|
|
|
if (state.tokens[blkIdx].type !== 'inline' || |
|
|
|
!QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
tokens = state.tokens[blkIdx].children; |
|
|
|
stack.length = 0; |
|
|
|
|
|
|
|
for (i = 0; i < tokens.length; i++) { |
|
|
|
token = tokens[i]; |
|
|
|
for (i = 0; i < tokens.length; i++) { |
|
|
|
token = tokens[i]; |
|
|
|
|
|
|
|
if (token.type !== 'text' || QUOTE_TEST_RE.test(token.text)) { continue; } |
|
|
|
if (token.type !== 'text' || QUOTE_TEST_RE.test(token.text)) { continue; } |
|
|
|
|
|
|
|
thisLevel = tokens[i].level; |
|
|
|
thisLevel = tokens[i].level; |
|
|
|
|
|
|
|
for (j = stack.length - 1; j >= 0; j--) { |
|
|
|
if (stack[j].level <= thisLevel) { break; } |
|
|
|
} |
|
|
|
stack.length = j + 1; |
|
|
|
|
|
|
|
text = token.content; |
|
|
|
pos = 0; |
|
|
|
max = text.length; |
|
|
|
|
|
|
|
/*eslint no-labels:0,block-scoped-var:0*/ |
|
|
|
OUTER: |
|
|
|
while (pos < max) { |
|
|
|
QUOTE_RE.lastIndex = pos; |
|
|
|
t = QUOTE_RE.exec(text); |
|
|
|
if (!t) { break; } |
|
|
|
|
|
|
|
canOpen = canClose = true; |
|
|
|
pos = t.index + 1; |
|
|
|
isSingle = (t[0] === "'"); |
|
|
|
|
|
|
|
lastChar = t.index - 1 >= 0 ? text.charCodeAt(t.index - 1) : -1; |
|
|
|
nextChar = pos < max ? text.charCodeAt(pos) : -1; |
|
|
|
|
|
|
|
isLastPunctChar = lastChar >= 0 && |
|
|
|
(isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar))); |
|
|
|
isNextPunctChar = nextChar >= 0 && |
|
|
|
(isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar))); |
|
|
|
|
|
|
|
// begin/end of the line counts as a whitespace too
|
|
|
|
isLastWhiteSpace = lastChar < 0 || isWhiteSpace(lastChar); |
|
|
|
isNextWhiteSpace = nextChar < 0 || isWhiteSpace(nextChar); |
|
|
|
|
|
|
|
if (isNextWhiteSpace) { |
|
|
|
for (j = stack.length - 1; j >= 0; j--) { |
|
|
|
if (stack[j].level <= thisLevel) { break; } |
|
|
|
} |
|
|
|
stack.length = j + 1; |
|
|
|
|
|
|
|
text = token.content; |
|
|
|
pos = 0; |
|
|
|
max = text.length; |
|
|
|
|
|
|
|
/*eslint no-labels:0,block-scoped-var:0*/ |
|
|
|
OUTER: |
|
|
|
while (pos < max) { |
|
|
|
QUOTE_RE.lastIndex = pos; |
|
|
|
t = QUOTE_RE.exec(text); |
|
|
|
if (!t) { break; } |
|
|
|
|
|
|
|
canOpen = canClose = true; |
|
|
|
pos = t.index + 1; |
|
|
|
isSingle = (t[0] === "'"); |
|
|
|
|
|
|
|
lastChar = t.index - 1 >= 0 ? text.charCodeAt(t.index - 1) : -1; |
|
|
|
nextChar = pos < max ? text.charCodeAt(pos) : -1; |
|
|
|
|
|
|
|
isLastPunctChar = lastChar >= 0 && |
|
|
|
(isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar))); |
|
|
|
isNextPunctChar = nextChar >= 0 && |
|
|
|
(isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar))); |
|
|
|
|
|
|
|
// begin/end of the line counts as a whitespace too
|
|
|
|
isLastWhiteSpace = lastChar < 0 || isWhiteSpace(lastChar); |
|
|
|
isNextWhiteSpace = nextChar < 0 || isWhiteSpace(nextChar); |
|
|
|
|
|
|
|
if (isNextWhiteSpace) { |
|
|
|
canOpen = false; |
|
|
|
} else if (isNextPunctChar) { |
|
|
|
if (!(isLastWhiteSpace || isLastPunctChar)) { |
|
|
|
canOpen = false; |
|
|
|
} else if (isNextPunctChar) { |
|
|
|
if (!(isLastWhiteSpace || isLastPunctChar)) { |
|
|
|
canOpen = false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (isLastWhiteSpace) { |
|
|
|
if (isLastWhiteSpace) { |
|
|
|
canClose = false; |
|
|
|
} else if (isLastPunctChar) { |
|
|
|
if (!(isNextWhiteSpace || isNextPunctChar)) { |
|
|
|
canClose = false; |
|
|
|
} else if (isLastPunctChar) { |
|
|
|
if (!(isNextWhiteSpace || isNextPunctChar)) { |
|
|
|
canClose = false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (nextChar === 0x22 /* " */ && t[0] === '"') { |
|
|
|
if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) { |
|
|
|
// special case: 1"" - count first quote as an inch
|
|
|
|
canClose = canOpen = false; |
|
|
|
} |
|
|
|
if (nextChar === 0x22 /* " */ && t[0] === '"') { |
|
|
|
if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) { |
|
|
|
// special case: 1"" - count first quote as an inch
|
|
|
|
canClose = canOpen = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (canOpen && canClose) { |
|
|
|
// treat this as the middle of the word
|
|
|
|
canOpen = canClose = false; |
|
|
|
} |
|
|
|
if (canOpen && canClose) { |
|
|
|
// treat this as the middle of the word
|
|
|
|
canOpen = canClose = false; |
|
|
|
} |
|
|
|
|
|
|
|
if (!canOpen && !canClose) { |
|
|
|
// middle of word
|
|
|
|
if (isSingle) { |
|
|
|
token.content = replaceAt(token.content, t.index, APOSTROPHE); |
|
|
|
} |
|
|
|
continue; |
|
|
|
if (!canOpen && !canClose) { |
|
|
|
// middle of word
|
|
|
|
if (isSingle) { |
|
|
|
token.content = replaceAt(token.content, t.index, APOSTROPHE); |
|
|
|
} |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if (canClose) { |
|
|
|
// this could be a closing quote, rewind the stack to get a match
|
|
|
|
for (j = stack.length - 1; j >= 0; j--) { |
|
|
|
if (canClose) { |
|
|
|
// this could be a closing quote, rewind the stack to get a match
|
|
|
|
for (j = stack.length - 1; j >= 0; j--) { |
|
|
|
item = stack[j]; |
|
|
|
if (stack[j].level < thisLevel) { break; } |
|
|
|
if (item.single === isSingle && stack[j].level === thisLevel) { |
|
|
|
item = stack[j]; |
|
|
|
if (stack[j].level < thisLevel) { break; } |
|
|
|
if (item.single === isSingle && stack[j].level === thisLevel) { |
|
|
|
item = stack[j]; |
|
|
|
if (isSingle) { |
|
|
|
tokens[item.token].content = replaceAt( |
|
|
|
tokens[item.token].content, item.pos, state.md.options.quotes[2]); |
|
|
|
token.content = replaceAt( |
|
|
|
token.content, t.index, state.md.options.quotes[3]); |
|
|
|
} else { |
|
|
|
tokens[item.token].content = replaceAt( |
|
|
|
tokens[item.token].content, item.pos, state.md.options.quotes[0]); |
|
|
|
token.content = replaceAt(token.content, t.index, state.md.options.quotes[1]); |
|
|
|
} |
|
|
|
stack.length = j; |
|
|
|
continue OUTER; |
|
|
|
if (isSingle) { |
|
|
|
tokens[item.token].content = replaceAt( |
|
|
|
tokens[item.token].content, item.pos, state.md.options.quotes[2]); |
|
|
|
token.content = replaceAt( |
|
|
|
token.content, t.index, state.md.options.quotes[3]); |
|
|
|
} else { |
|
|
|
tokens[item.token].content = replaceAt( |
|
|
|
tokens[item.token].content, item.pos, state.md.options.quotes[0]); |
|
|
|
token.content = replaceAt(token.content, t.index, state.md.options.quotes[1]); |
|
|
|
} |
|
|
|
stack.length = j; |
|
|
|
continue OUTER; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (canOpen) { |
|
|
|
stack.push({ |
|
|
|
token: i, |
|
|
|
pos: t.index, |
|
|
|
single: isSingle, |
|
|
|
level: thisLevel |
|
|
|
}); |
|
|
|
} else if (canClose && isSingle) { |
|
|
|
token.content = replaceAt(token.content, t.index, APOSTROPHE); |
|
|
|
} |
|
|
|
if (canOpen) { |
|
|
|
stack.push({ |
|
|
|
token: i, |
|
|
|
pos: t.index, |
|
|
|
single: isSingle, |
|
|
|
level: thisLevel |
|
|
|
}); |
|
|
|
} else if (canClose && isSingle) { |
|
|
|
token.content = replaceAt(token.content, t.index, APOSTROPHE); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
module.exports = function smartquotes(state) { |
|
|
|
/*eslint max-depth:0*/ |
|
|
|
var blkIdx; |
|
|
|
|
|
|
|
if (!state.md.options.typographer) { return; } |
|
|
|
|
|
|
|
for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { |
|
|
|
|
|
|
|
if (state.tokens[blkIdx].type !== 'inline' || |
|
|
|
!QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
process_inlines(state.tokens[blkIdx].children, state); |
|
|
|
} |
|
|
|
}; |
|
|
|