Последний раз редактировалось kotenok gav 25.05.2021, 16:09, всего редактировалось 2 раз(а).
Возникла весьма интересная задача. Я сейчас пишу BBCode-парсер, который, по сути, конвертирует обычный текст с BBCode в HTML. Допустим, пользователь выделяет некоторую часть конвертированного сообщения и хочет её процитировать. При этом надо, чтобы выделенный им HTML преобразовался обратно в BBCode. Есть очевидное решение - взять исходный текст сообщения, перебрать все его подстроки, каждую отббкодить, и проверить, совпадает ли с выделенной пользователем. Но, очевидно, это весьма неэффективно. Есть ли способы сократить такой поиск? У меня есть идея искать сначала по просто чистому тексту из HTML (т.е. выбросив теги), но я не совсем уверен, как её можно реализовать. Если что, то вот код моего парсера BBCode:
function repeatWhileDifferent(text, func) {
let oldText;
do
oldText = text;
while ((text = func(text)) != oldText);
return text;
}
function applyBBCode(text) {
let map = {'&': '&', '<': '<', '>': '>', '\n': '<br>', '\x01': '\x03', '\x02': '\x03'};
let lastSpoilerID = 0;
let noCode = [];
let regExpList = [
[/\[nocode\](.*?)\[\/nocode\]/isg, (s, p) => '\x01' + noCode.push(p) + '\x02'],
[/\[(b|i|u|s|left|center|right|code)\](.+?)\[\/\1\]/isg, "<span class='text_$1'>$2</span>"],
[/\[url=['\"]*(http\:\/\/.+?|https\:\/\/.+?|\/.*?)['\"]*\](.+?)\[\/url\]/isg, (s, p1, p2) => `<a href='${eqsc(p1)}' rel='nofollow' target='_blank'>${p2}</a>`],
[/\[url\](http\:\/\/.+?|https\:\/\/.+?|\/.*?)\[\/url\]/isg, (s, p1) => `<a href='${eqsc(p1)}' rel='nofollow' target='_blank'>${p1}</a>`],
[/\[img\](http\:\/\/.+?|https\:\/\/.+?|\/.*?)\[\/img\]/isg, (s, p1) => `<img src='${eqsc(p1)}' alt='Изображение'>`],
[/\[spoiler=(.+)](.+?)\[\/spoiler\]/isg, (s, p1, p2) => {
let id = ++window.bbCode_lastSpoilerID;
return `<a href='' onclick='return toggleSpoiler("${id}");'>${p1}</a><div id='${id}' style='display: none;'>${p2}</div>`;
}],
[/\[font=([^'\"]+)\](.+?)\[\/font\]/isg, `<span style='font-family: "$1", serif;'>$2</span>`],
[/\[size=([1-9]|[1-9]\d|[1-4]\d\d)\](.+?)\[\/size\]/isg, "<span style='font-size: $1%;'>$2</span>"],
[/\[color=([a-zA-Z]+|\#?[0-9a-fA-F]{6})\](.+?)\[\/color\]/isg, "<span style='color: $1;'>$2</span>"],
[/\[youtube\](.+?)\[\/youtube\]/isg, (s, p) => {
let lastAmpersand = p.length - 1;
while ((lastAmpersand >= 0) && (p[lastAmpersand] != '&'))
lastAmpersand--;
if ((lastAmpersand >= 0) && p.slice(lastAmpersand).startsWith('&t='))
p = p.slice(0, lastAmpersand);
let id = p.slice(-11);
if (!/^[a-zA-Z0-9]+$/.test(id))
return "";
else
return `<iframe allowfullscreen allow='fullscreen; picture-in-picture' title='YouTube video player' width='640' height='385' src='https://youtube.com/embed/${id}'></iframe>`;
}],
[/\[table\](.+?)\[\/table\]/isg, "<table class='normal_table'>$1</table>"],
[/\[table=0\](.+?)\[\/table\]/isg, "<table>$1</table>"],
[/\[(tr|td)\](.*?)\[\/\1\]/isg, "<$1>$2</$1>"],
[/\x01(\d+)\x02/g, (s, num) => noCode[num - 1]]
];
HTML = text.replace(/[&<>\n]/g, s => map[s]);
for (let regexp of regExpList)
HTML = repeatWhileDifferent(HTML, (str) => str.replace(regexp[0], regexp[1]));
return HTML;
}
function eqsc(str) {
let quotesSemiColonMap = {"'": ''', '"': '"', ';': ';', ':': ':'};
return str.replace(/['\";:]/g, s => quotesSemiColonMap[s]);
}
function toggleSpoiler(id) {
let element = document.getElementById(id);
element.style.display = (element.style.display == 'block') ? 'none' : 'block';
return false;
}
-- 25 май 2021, 22:14 --Ещё, кстати, может быть проблема с частичным выделением - если изначально было сообщение [b]abc[/b] [b]def[/b], то оно станет <span class='text_b'>abc</span> <span class='text_b'>def</span>, и если пользователь захочет выделить строку bc de, он выделит HTML <span class='text_b'>bc</span> <span class='text_b'>de</span>, и это должно будет стать [b]bc[/b] [b]de[/b], несмотря на то, что в исходном тексте такой строки нет. -- 25 май 2021, 22:39 --В принципе, самый простой способ - просто сделать анти-BBCode, т.е. превращения HTML-тегов обратно в BBCode.
|