You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
4.7 KiB
148 lines
4.7 KiB
// Enclose abbreviations in <abbr> tags |
|
// |
|
'use strict'; |
|
|
|
|
|
module.exports = function sub_plugin(md) { |
|
var escapeRE = md.utils.escapeRE, |
|
arrayReplaceAt = md.utils.arrayReplaceAt; |
|
|
|
// ASCII characters in Cc, Sc, Sm, Sk categories we should terminate on; |
|
// you can check character classes here: |
|
// http://www.unicode.org/Public/UNIDATA/UnicodeData.txt |
|
var OTHER_CHARS = ' \r\n$+<=>^`|~'; |
|
|
|
var UNICODE_PUNCT_RE = md.utils.lib.ucmicro.P.source; |
|
var UNICODE_SPACE_RE = md.utils.lib.ucmicro.Z.source; |
|
|
|
|
|
function abbr_def(state, startLine, endLine, silent) { |
|
var label, title, ch, labelStart, labelEnd, |
|
pos = state.bMarks[startLine] + state.tShift[startLine], |
|
max = state.eMarks[startLine]; |
|
|
|
if (pos + 2 >= max) { return false; } |
|
|
|
if (state.src.charCodeAt(pos++) !== 0x2A/* * */) { return false; } |
|
if (state.src.charCodeAt(pos++) !== 0x5B/* [ */) { return false; } |
|
|
|
labelStart = pos; |
|
|
|
for (; pos < max; pos++) { |
|
ch = state.src.charCodeAt(pos); |
|
if (ch === 0x5B /* [ */) { |
|
return false; |
|
} else if (ch === 0x5D /* ] */) { |
|
labelEnd = pos; |
|
break; |
|
} else if (ch === 0x5C /* \ */) { |
|
pos++; |
|
} |
|
} |
|
|
|
if (labelEnd < 0 || state.src.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { |
|
return false; |
|
} |
|
|
|
if (silent) { return true; } |
|
|
|
label = state.src.slice(labelStart, labelEnd).replace(/\\(.)/g, '$1'); |
|
title = state.src.slice(labelEnd + 2, max).trim(); |
|
if (label.length === 0) { return false; } |
|
if (title.length === 0) { return false; } |
|
if (!state.env.abbreviations) { state.env.abbreviations = {}; } |
|
// prepend ':' to avoid conflict with Object.prototype members |
|
if (typeof state.env.abbreviations[':' + label] === 'undefined') { |
|
state.env.abbreviations[':' + label] = title; |
|
} |
|
|
|
state.line = startLine + 1; |
|
return true; |
|
} |
|
|
|
|
|
function abbr_replace(state) { |
|
var i, j, l, tokens, token, text, nodes, pos, reg, m, regText, regSimple, |
|
currentToken, |
|
blockTokens = state.tokens; |
|
|
|
if (!state.env.abbreviations) { return; } |
|
|
|
regSimple = new RegExp('(?:' + |
|
Object.keys(state.env.abbreviations).map(function (x) { |
|
return x.substr(1); |
|
}).sort(function (a, b) { |
|
return b.length - a.length; |
|
}).map(escapeRE).join('|') + |
|
')'); |
|
|
|
regText = '(^|' + UNICODE_PUNCT_RE + '|' + UNICODE_SPACE_RE + |
|
'|[' + OTHER_CHARS.split('').map(escapeRE).join('') + '])' |
|
+ '(' + Object.keys(state.env.abbreviations).map(function (x) { |
|
return x.substr(1); |
|
}).sort(function (a, b) { |
|
return b.length - a.length; |
|
}).map(escapeRE).join('|') + ')' |
|
+ '($|' + UNICODE_PUNCT_RE + '|' + UNICODE_SPACE_RE + |
|
'|[' + OTHER_CHARS.split('').map(escapeRE).join('') + '])'; |
|
|
|
reg = new RegExp(regText, 'g'); |
|
|
|
for (j = 0, l = blockTokens.length; j < l; j++) { |
|
if (blockTokens[j].type !== 'inline') { continue; } |
|
tokens = blockTokens[j].children; |
|
|
|
// We scan from the end, to keep position when new tags added. |
|
for (i = tokens.length - 1; i >= 0; i--) { |
|
currentToken = tokens[i]; |
|
if (currentToken.type !== 'text') { continue; } |
|
|
|
pos = 0; |
|
text = currentToken.content; |
|
reg.lastIndex = 0; |
|
nodes = []; |
|
|
|
// fast regexp run to determine whether there are any abbreviated words |
|
// in the current token |
|
if (!regSimple.test(text)) { continue; } |
|
|
|
while ((m = reg.exec(text))) { |
|
if (m.index > 0 || m[1].length > 0) { |
|
token = new state.Token('text', '', 0); |
|
token.content = text.slice(pos, m.index + m[1].length); |
|
nodes.push(token); |
|
} |
|
|
|
token = new state.Token('abbr_open', 'abbr', 1); |
|
token.attrs = [ [ 'title', state.env.abbreviations[':' + m[2]] ] ]; |
|
nodes.push(token); |
|
|
|
token = new state.Token('text', '', 0); |
|
token.content = m[2]; |
|
nodes.push(token); |
|
|
|
token = new state.Token('abbr_close', 'abbr', -1); |
|
nodes.push(token); |
|
|
|
reg.lastIndex -= m[3].length; |
|
pos = reg.lastIndex; |
|
} |
|
|
|
if (!nodes.length) { continue; } |
|
|
|
if (pos < text.length) { |
|
token = new state.Token('text', '', 0); |
|
token.content = text.slice(pos); |
|
nodes.push(token); |
|
} |
|
|
|
// replace current node |
|
blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); |
|
} |
|
} |
|
} |
|
|
|
md.block.ruler.before('reference', 'abbr_def', abbr_def, { alt: [ 'paragraph', 'reference' ] }); |
|
|
|
md.core.ruler.after('linkify', 'abbr_replace', abbr_replace); |
|
};
|
|
|