2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




 
 BBCode и "умный" перебор
Сообщение25.05.2021, 15:27 
Возникла весьма интересная задача.
Я сейчас пишу BBCode-парсер, который, по сути, конвертирует обычный текст с BBCode в HTML. Допустим, пользователь выделяет некоторую часть конвертированного сообщения и хочет её процитировать. При этом надо, чтобы выделенный им HTML преобразовался обратно в BBCode.
Есть очевидное решение - взять исходный текст сообщения, перебрать все его подстроки, каждую отббкодить, и проверить, совпадает ли с выделенной пользователем. Но, очевидно, это весьма неэффективно.
Есть ли способы сократить такой поиск? У меня есть идея искать сначала по просто чистому тексту из HTML (т.е. выбросив теги), но я не совсем уверен, как её можно реализовать.
Если что, то вот код моего парсера BBCode:
код: [ скачать ] [ спрятать ]
Используется синтаксис Javascript
function repeatWhileDifferent(text, func) {
    let oldText;
    do
        oldText = text;
    while ((text = func(text)) != oldText);
    return text;
}

function applyBBCode(text) {
    let map = {'&': '&amp;', '<': '&lt;', '>': '&gt;', '\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('&amp;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 = {"
'": '&apos;', '"': '&quot;', ';': '&semi;', ':': '&colon;'};
    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.

 
 
 
 Re: BBCode и "умный" перебор
Сообщение26.05.2021, 02:03 
В процессе перевода из BBCode в HTML запоминайте какой по номеру символ из BBCode отображается в какой по номеру символ в HTML. Получившееся отображение - монотонная функция. Обратная функция вычисляется за логарифм двоичным поиском. Тут срезаны некоторые углы (эти функции не для всех номеров определены), но должно работать.

 
 
 [ Сообщений: 2 ] 


Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group