2017-07-17 16:21:47 -04:00
|
|
|
export class TextPostProcessRuler {
|
|
|
|
constructor() {
|
|
|
|
this.rules = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
getRules() {
|
|
|
|
return this.rules;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO error handling
|
|
|
|
getMatcher() {
|
|
|
|
if (this.matcher) {
|
|
|
|
return this.matcher;
|
|
|
|
}
|
|
|
|
|
2017-07-20 15:33:36 -04:00
|
|
|
this.matcherIndex = [];
|
|
|
|
|
2019-04-23 06:22:47 -04:00
|
|
|
const rules = [];
|
|
|
|
const flags = new Set("g");
|
|
|
|
|
|
|
|
this.rules.forEach((r) => {
|
|
|
|
const matcher = r.rule.matcher;
|
|
|
|
rules.push(`(${matcher.source})`);
|
|
|
|
matcher.flags.split("").forEach((f) => flags.add(f));
|
|
|
|
});
|
2017-07-20 15:33:36 -04:00
|
|
|
|
|
|
|
let i;
|
|
|
|
let regexString = "";
|
|
|
|
let last = 1;
|
|
|
|
|
|
|
|
// this code is a bit tricky, our matcher may have multiple capture groups
|
|
|
|
// we want to dynamically determine how many
|
|
|
|
for (i = 0; i < rules.length; i++) {
|
|
|
|
this.matcherIndex[i] = last;
|
|
|
|
|
|
|
|
if (i === rules.length - 1) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i > 0) {
|
|
|
|
regexString = regexString + "|";
|
|
|
|
}
|
|
|
|
regexString = regexString + rules[i];
|
2017-07-17 16:21:47 -04:00
|
|
|
|
2017-07-20 15:33:36 -04:00
|
|
|
let regex = new RegExp(regexString + "|(x)");
|
|
|
|
last = "x".match(regex).length - 1;
|
|
|
|
}
|
|
|
|
|
2019-04-23 06:22:47 -04:00
|
|
|
this.matcher = new RegExp(rules.join("|"), [...flags].join(""));
|
2017-07-17 16:21:47 -04:00
|
|
|
return this.matcher;
|
|
|
|
}
|
|
|
|
|
|
|
|
applyRule(buffer, match, state) {
|
|
|
|
let i;
|
|
|
|
for (i = 0; i < this.rules.length; i++) {
|
2017-07-20 15:33:36 -04:00
|
|
|
let index = this.matcherIndex[i];
|
|
|
|
|
|
|
|
if (match[index]) {
|
|
|
|
this.rules[i].rule.onMatch(
|
|
|
|
buffer,
|
|
|
|
match.slice(index, this.matcherIndex[i + 1]),
|
|
|
|
state
|
|
|
|
);
|
2017-07-17 16:21:47 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO validate inputs
|
|
|
|
push(name, rule) {
|
|
|
|
this.rules.push({ name, rule });
|
|
|
|
this.matcher = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function allowedBoundary(content, index, utils) {
|
|
|
|
let code = content.charCodeAt(index);
|
|
|
|
return (
|
|
|
|
utils.isWhiteSpace(code) || utils.isPunctChar(String.fromCharCode(code))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function textPostProcess(content, state, ruler) {
|
2019-08-22 08:47:25 -04:00
|
|
|
let result = null;
|
|
|
|
let match;
|
|
|
|
let pos = 0;
|
2017-07-17 16:21:47 -04:00
|
|
|
|
|
|
|
const matcher = ruler.getMatcher();
|
|
|
|
|
2019-08-22 09:27:45 -04:00
|
|
|
while ((match = matcher.exec(content))) {
|
2019-08-22 08:47:25 -04:00
|
|
|
// something is wrong
|
2020-09-22 10:28:28 -04:00
|
|
|
if (match.index < pos) {
|
|
|
|
break;
|
|
|
|
}
|
2019-08-22 08:47:25 -04:00
|
|
|
|
2017-07-17 16:21:47 -04:00
|
|
|
// check boundary
|
|
|
|
if (match.index > 0) {
|
|
|
|
if (!allowedBoundary(content, match.index - 1, state.md.utils)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check forward boundary as well
|
|
|
|
if (match.index + match[0].length < content.length) {
|
|
|
|
if (
|
|
|
|
!allowedBoundary(content, match.index + match[0].length, state.md.utils)
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-22 08:47:25 -04:00
|
|
|
result = result || [];
|
|
|
|
|
2017-07-17 16:21:47 -04:00
|
|
|
if (match.index > pos) {
|
|
|
|
let token = new state.Token("text", "", 0);
|
|
|
|
token.content = content.slice(pos, match.index);
|
|
|
|
result.push(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
ruler.applyRule(result, match, state);
|
|
|
|
|
|
|
|
pos = match.index + match[0].length;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result && pos < content.length) {
|
|
|
|
let token = new state.Token("text", "", 0);
|
|
|
|
token.content = content.slice(pos);
|
|
|
|
result.push(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setup(helper) {
|
|
|
|
helper.registerPlugin((md) => {
|
|
|
|
const ruler = md.core.textPostProcess.ruler;
|
|
|
|
const replacer = (content, state) => textPostProcess(content, state, ruler);
|
|
|
|
|
|
|
|
md.core.ruler.push("text-post-process", (state) =>
|
|
|
|
md.options.discourse.helpers.textReplace(state, replacer, true)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|