За последние 24 часа нас посетили 22448 программистов и 1009 роботов. Сейчас ищут 646 программистов ...

Парсер BBCODE - пишем правильно

Тема в разделе "Прочие вопросы по PHP", создана пользователем artoodetoo, 25 дек 2012.

  1. runcore

    runcore Старожил

    С нами с:
    12 окт 2012
    Сообщения:
    3.625
    Симпатии:
    158
    причем здесь юникод? если мы говорим о исполняемом коде.
    движек регекспов САМ написан НЕ НА ПХП. ферштейн?
    посимвольный разбор строки который ты собираешься сделать будет написан на ПХП. разницу чуешь?
    это разные уровни абстракции. соответственно разный оверхед, разные накладные расходы на хранение одного и тогоже. подумай об этом.
    а я пожалуй продолжу умничать. ибо знаю точно - регулярки нагнут посимвольный анализ.
     
  2. YSandro

    YSandro Старожил

    С нами с:
    7 апр 2011
    Сообщения:
    2.523
    Симпатии:
    2
    Не собираюсь, надоело.
    Именно на PHP? И не будут вызываться нативные функции совсем? А результат регекспа ты получишь в каком виде? Что потом будешь делать с этим массивом... на "нативном" уровне опять?
    Единственный выигрышь тут получится, если обрабатывать действительно большой кусок строки.
     
  3. sobachnik

    sobachnik Старожил

    С нами с:
    20 апр 2007
    Сообщения:
    3.380
    Симпатии:
    13
    Адрес:
    Дмитров, МО
    Скорость будет зависеть от местерства того, кто будет писать регулярки. Скорее всего, регулярки действительно будут работать быстрее, но и регулярку можно написать тормозную :) Кроме того, хватит ли одной регулярки? Или это будет несколько поочерёдно вызываемых регулярок? Если хватит одной - то она должна будет сработать быстрее. Если их будет несколько - то тут уже может по-разному получиться. Применив посимвольный перебор, мы прогоняем текст через парсер всего 1 раз. А в случае нескольких регулярок - каждая регулярка - это прогон текста. Если текст в кодировке utf-8 (а это так), мы не можем в PHP использовать конструкцию
    Код (PHP):
    1. $char = $string[$index]; 
    нам приходится использовать mb_substr для получения каждого символа в строке:
    Код (PHP):
    1. $char = mb_substr($string, $index, 1, 'utf-8'); 
    и это одно из наиболее тормозных мест. Аналогичная конструкция в js:
    Код (PHP):
    1. char = string.substring(index, index + 1); 
    Работает в несколько раз быстрее, чем в PHP.

    Но в принципе, если парсер будет работать только при добавлении нового комментария и записывать в б.д. уже обработанный комментарий, в котором bbcode заменён на html - то скорость работы парсера - не особо важна.
     
  4. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.072
    Симпатии:
    1.237
    Адрес:
    там-сям
    Поправьте меня если что, но кажется и vbulletin и phpbb не хранят распарсенный текст.
    Парсят сообщения при каждом открытии страницы. Давайте думать о быстром парсере, так, на всякий случай )

    Добавлено спустя 8 минут 7 секунд:
    Есть правила как писать быстрые регулярки. Если упрощенно, то так, чтобы не возникало отката в разбираемой строке.
    В первую очередь определяемся с синтаксисом. Например, если аргумент ббкода может быть окужен кавычками, это усложняет регулярку на порядок.
     
  5. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    может проверим как оно на js?
     
  6. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.072
    Симпатии:
    1.237
    Адрес:
    там-сям
    предлагаешь — делай! отличный принцип, imho.
     
  7. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    ну можно взять ченить готовое. нету?
     
  8. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.072
    Симпатии:
    1.237
    Адрес:
    там-сям
    Все встретили НГ? Здоровье восстановили?
    Продолжаем: основные моменты по синтаксису.
    Считаю чем проще, тем лучше. Но не проще чем требуется )

    • У кода может быть только один аргумент или ни одного и аргумент не может быть в кавычках. То есть тупо экранируем кавычки внутри кода! В тексте кавычки не трогаем.
    • Html коды с амперсандом оставляем как есть, не могу как-то придумать чем они могли бы навредить.
    • code разбирается особым образом - подавляет любые коды кроме закрывающего code. подсветку синтаксиса просто не включаем в список целей — пусть этим занимается сторонний JS.
    • Парность требуется, но исключение есть - элемент списка.

    Мне кажется чтобы достичь оптимального компромиса между эффективностью и понятностью надо так: регуляркой искать лексемы, а семантику оставить на PHP.
     
  9. sadex

    sadex Новичок

    С нами с:
    8 июл 2013
    Сообщения:
    6
    Симпатии:
    0
    artoodetoo, приветствую. Неужели тема заглохла? Мне тоже нужен парсер bbcode, но относительно простой, но, например, тег [size=nn] там должен быть. Просмотрел кучу разных скриптов парсеров, пока ничего не выбрал. Зато нашел скрипт парсера bb-кодов на JS, его можно на клиенте применять для предпросмотра. Если не угас интерес к теме, может, подолжим? Я здесь зарегистрировался-то только ради этой темы, по гуглу на нее вышел, случайно.
     
  10. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.072
    Симпатии:
    1.237
    Адрес:
    там-сям
    Привет! Здесь я поддержки не нашел, сейчас у меня есть другие темы для развития.
     
  11. sadex

    sadex Новичок

    С нами с:
    8 июл 2013
    Сообщения:
    6
    Симпатии:
    0
    Жаль, жаль. Но я по мере сил своих хилых продолжу помалу парсером BBCode заниматься. Мне еще нужен будет обратный парсер, из bb- в html, для редактирования, т.е. в БД я все же текста храню в html (упрощенном, без кавычек). Если будет желание, взгляни на JS-скрипт скрипт неплохого парсера bb-кодов, вполне рабочего:
    http://blogs.stonesteps.ca/showpost.aspx?pid=33

    С его помощью можно еще на клиенте, в браузере юзера предварительную валидацию текстов в bb-форматировании делать (предпросмотр и редактирование на клиенте), что разгружает сервер. А это я чуть-чуть с ним поупражнялся, для наглядности:
    http://sadex.p.ht/aa/sample.html

    Вообще в JS-скрипте этого парсера неплохая регулярка воткнута, кое-какие идеи оттуда можно взять и для PHP-парсера bb-кодов.
     
  12. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    Кстати, помнится многих мучал вопрос с заменой рекурсивных тегов, например цитаты. Выдумывали всяких сложные регулярки, даже конечные автоматы юзали ))
    Пару лет назад, я кажется нашел идеальное решение...
    Код (PHP):
    1. <?php
    2. class BBCoder_BBList_Quote implements BBCoder_Interface {
    3.       function Replace($text){
    4.           $openbb=count(preg_split("#\[quote\]#i",$text))-1;
    5.           for($i=0;$i<$openbb;$i++){
    6.                $text=preg_replace("#\[quote\](.+?)\[\/quote\]#is","<div class='bbcode_quote'>\\1</div>",$text);
    7.           }
    8.           $text=preg_replace("#\[(\/?quote)\]#i","&#091\\1&#093",$text);
    9.           return $text;
    10.       }
    11.       function R_Replace($text){
    12.           return preg_replace(array("#<div\sclass='bbcode_quote'>#i","#<\/div>#i"),array("[quote]","[/quote]"),$text);
    13.       }
    14. }
    15. ?>
     
  13. Nasgool

    Nasgool Новичок

    С нами с:
    28 фев 2015
    Сообщения:
    13
    Симпатии:
    0
    Четыре с половиной года прошло а тема для меня оказалась актуальна.
    Не судите строго, в сайтостроении я человек новый, - всего около месяца изучаю PHP HTML CSS JS. При том изучаю все в связке. Опыт в прикладном программировании большой, более 10 лет С++ и Delphi.
    Изложу свои соображения по поводу парсинга BB кода.
    1. Парсинг должен пройти на стороне клиента (дабы не грузить сервер лишней работой)
    2. Обязательно должна быть проверка на корректную вложенность тегов
    3. На стороне сервера нужно вызвать htmlspecialchars непосредственно перед записью поста в базу (дабы никто не смог внедрить ничего на страницу этого поста, JS например)

    Начал реализацию на JS.
    На данном этапе реализовал проверку логики.
    Код достаточно комментирован:
    Код (Text):
    1. // Парсим BB
    2. function ParseBB(BBstr) {
    3.     // Заменяем символы конца строки на неиспользуемые юникоды
    4.     BBstr.replace(/\n/g, "\u00BD");
    5.     // Выделяем все теги BB кода в массив
    6.     var rg = "(\\[(?:\\s*\\u00BD*\\s*)*(B|\\/B|I|\\/I|U|\\/U|COLOR|\\/COLOR|S|\\/S|\\*|LEFT|\\/LEFT|RIGHT|\\/RIGHT|CENTER|\\/CCENTER|INDENT|\\/INDENT|EMAIL|\\/EMAIL|URL|\\/URL|THREAD|\\/THREAD|POST|\\/POST|LIST|\\/LIST|IMG|\\/IMG|VIDEO|\\/VIDEO|AUDUO|\\/AUDIO|SPOILER|\\/SPOILER|CODE|\\/CODE|QUOTE|\\QUOTE)(?:\\s*\\u00BD*\\s*)*(?:=(?:\\s*\\u00BD*\\s*)*([^\\[\\]\\s]*)|(?:\\s*\\u00BD*\\s*)*)?\\])";
    7.     rg = new RegExp(rg , "gi");
    8.     var arr = new Array();
    9.     var i = 0;
    10.     while ((arr[i] = rg.exec(BBstr)) !== null) {
    11.         i++;
    12.     };
    13.     // Создаем стек
    14.     var stk = new Array(arr.length);
    15.     i = 0;
    16.     for (i = 0; i < stk.length; i++) stk[i] = null;
    17.     // Начинаем прогон тегов на вложенность
    18.     i = 1;
    19.     stk[0] = arr[0];
    20.     var idx = 0;
    21.     var tmp = "";
    22.     for (i = 1; i < arr.length; i++) {
    23.         // Если это последний элемент массива то выход
    24.         if (arr[i] == null) break;
    25.         // Если это не парный тег то следующая итерация
    26.         if (arr[i][2] == "*") continue;
    27.         // Если это закрывающий тег, проверяем какой тег был открыт последним
    28.         if (arr[i][2][0] == "/") {
    29.             if (arr[i][2].substring(1, arr[i][2].length) == stk[idx][2]) {
    30.                 // Если зарывающий и последний открывающий теги равны очищаем верхнюю позицию стека
    31.                 stk[idx] = null;
    32.                 idx--;
    33.             // Если закрывающий тег не совпадает с последний открывающим, то ошибка в логике, выход из цикла
    34.             } else break;
    35.         } else {
    36.             // Если это открывающий тег, то записываем его в стек
    37.             idx++;
    38.             stk[idx] = arr[i];
    39.         };
    40.     };
    41.     // Если вся логика верна, то стек в итоге остается пустым
    42.     // если это не так, то ошибка, пишем в результат информацию о ней и выходим
    43.     if (stk[0] != null) return {HTMLstr:undefined, BBteg:stk[idx][2], BBpar:stk[idx][3], Pos:stk[idx].index, Err:"Не соблюдена вложенность тегов или не закрыт тег"};
    44.    
    45.     // тут будем реализовывать уже подмену тегов и втавленных ранее юникодов на HTML
    46.    
    47.     // Сюда запишем получившийся HTML
    48.     return {HTMLstr:undefined, BBteg:undefined, BBpar:undefined, Pos:undefined, Err:undefined};
    49. };
    50.  
    51. // Начинаем парсинг
    52. function Parse(BBstr) {
    53.     var HTMLstr = "";
    54.     var res = ParseBB(BBstr);
    55.     // Вывод результата или ошибки
    56.     if (res.Err == undefined) {
    57.         HTMLstr = res.HTMLstr;
    58.     } else {
    59.         HTMLstr = "Ошибочка: \"" + res.Err + "\", тег \"" + res.BBteg + "\", параметр \"" + res.BBpar + "\", позиция " + res.Pos;
    60.     }
    61.     return HTMLstr;
    62. }

    Кстати что бы было понятнее как выделяются все теги.
    Вводим на входе:
    Код (Text):
    1. [COLOR=#000000]Пр[B]осто те[/B]кст[/COLOR]
    Вот лог из консоли браузера на брейкпойне сразу после заполнения массива arr:
    [​IMG]

    Добавлено спустя 11 минут 6 секунд:
    То есть мы в массиве имеем выделенные:
    • полную версию тега,
    • его чистое название (без скобок и параметров),
    • параметр (то что стоит после =)
    • позицию начальной квадратной скобки данного тега в тексте
     
  14. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    Не, на клиенте не надо. Достаточно один раз спарсить и сохранить спарсеное и нагрузки никакой, и выдача сразу красивая.
     
  15. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.072
    Симпатии:
    1.237
    Адрес:
    там-сям
    Задача трансляции BBCODE в HTML не такая простая как может показаться. Не думаю, что она станет алгоритмически проще если ее реализовать на JS вместо PHP. До сих пор я встретил только один годный парсер кроме тех что вшиты в "официальные" движки форумов. Хотя пыжатся многие — я сам начинал, но не закончил, а сейчас уже и нет желания.

    А как хранить результат и хранить ли вообще это вопрос отдельный.
     
  16. Nasgool

    Nasgool Новичок

    С нами с:
    28 фев 2015
    Сообщения:
    13
    Симпатии:
    0
    В вашем варианте пользователь нокогда не сможет отредактировать свое же сообщение. ИМХО в базе нужно хранить непарсенный ББ код, парсить на стороне клиента при отображении

    Добавлено спустя 1 минуту 47 секунд:
    Вы правы, даже больше, учитывая особенности языка - реализация на PHP была бы проще.

    Добавлено спустя 2 минуты 9 секунд:
    Переделал некоторые моменты в логике, переделал немного регулярку поиска тегов и начал реализацию подмены ББ на HTML:
    Код (Text):
    1. // преобразование символов HTML в спецсимволы HTML
    2. function escapeHtml(text) {
    3.   var map = {
    4.     '&': '&amp;',
    5.     '<': '&lt;',
    6.     '>': '&gt;',
    7.     '"': '&quot;',
    8.     "'": '&#039;'
    9.   };
    10.   return text.replace(/[&<>"']/g, function(m) { return map[m]; });
    11. }
    12.  
    13. // Парсим BB
    14. function ParseBB(BBstr) {
    15.     // Заменяем символы конца строки на неиспользуемые юникоды
    16.     BBstr = BBstr.replace(/\n/g, "\u00BD");
    17.     // Выделяем все теги BB кода в массив
    18.     var rg = "\\[[\\s*\\u00BD*]*(B|\\/B|I|\\/I|U|\\/U|COLOR|\\/COLOR|S|\\/S|\\*|LEFT|\\/LEFT|RIGHT|\\/RIGHT|CENTER|\\/CCENTER|INDENT|\\/INDENT|EMAIL|\\/EMAIL|URL|\\/URL|THREAD|\\/THREAD|POST|\\/POST|LIST|\\/LIST|IMG|\\/IMG|VIDEO|\\/VIDEO|AUDUO|\\/AUDIO|SPOILER|\\/SPOILER|CODE|\\/CODE|QUOTE|\\QUOTE)[\\s*\\u00BD*]*(?:=[\\s*\\u00BD*]*([^\\[\\]\\s]*))?[\\s*\\u00BD*]*\\]";
    19.     rg = new RegExp(rg , "gi");
    20.     var arr = new Array();
    21.     var i = 0;
    22.     while ((arr[i] = rg.exec(BBstr)) !== null) {
    23.         i++;
    24.     };
    25.     arr.pop();
    26.     if (arr.length == 0) {
    27.         return {HTMLstr:escapeHtml(BBstr).replace(/\u00BD/g, "<br>"), BBteg:undefined, BBpar:undefined, Pos:undefined, Err:undefined};
    28.     }
    29.     // Создаем стек
    30.     var stk = new Array();
    31.     // Если первым стоит закрывающий тег то ошибочка
    32.     if (arr[0][1][0] == "/") return {HTMLstr:undefined, BBteg:arr[0][1], BBpar:arr[0][2], Pos:arr[0].index, Err:"Нет предшествующего открывающего тега"};
    33.     // Если первым стоит тег элемента списка, то ошибочка
    34.     if (arr[0][1] == "*") return {HTMLstr:undefined, BBteg:arr[0][1], BBpar:arr[0][2], Pos:arr[0].index, Err:"Элемент списка должен находится между открывающим и закрывающим тегами LIST"};
    35.     // Начинаем прогон тегов на вложенность
    36.     stk.push(arr[0]);
    37.     var tmp = "";
    38.     for (i = 1; i < arr.length; i++) {
    39.         // Если это последний элемент массива то выход
    40.         if (arr[i] == null) break;
    41.         // Если это не парный тег то следующая итерация
    42.         if (arr[i][1] == "*") continue;
    43.         // Если это закрывающий тег, проверяем какой тег был открыт последним
    44.         if (arr[i][1][0] == "/") {
    45.             if (arr[i][1].substring(1, arr[i][1].length) == stk[stk.length - 1][1]) {
    46.                 // Если зарывающий и последний открывающий теги равны то передаем параметр а закрывающий тег
    47.                 arr[i][2] = stk[stk.length - 1][2];
    48.                 // и очищаем верхнюю позицию стека
    49.                 stk.pop();
    50.             // Если закрывающий тег не совпадает с последний открывающим, то ошибка в логике, выход из цикла
    51.             } else break;
    52.         } else {
    53.             // Если это открывающий тег, то записываем его в стек
    54.             stk.push(arr[i]);
    55.         };
    56.     };
    57.     // Если вся логика верна, то стек в итоге остается пустым
    58.     // если это не так, то ошибка, пишем в результат информацию о ней и выходим
    59.     if (stk.length > 0) return {HTMLstr:undefined, BBteg:stk[stk.length - 1][2], BBpar:stk[stk.length - 1][3], Pos:stk[stk.length - 1].index, Err:"Не соблюдена вложенность тегов или не закрыт тег"};
    60.        
    61.     // тут будем реализовывать уже подмену тегов и втавленных ранее юникодов на HTML
    62.     // Заносим начало строки до первого тега в результат
    63.     var HTMLstr = {HTMLstr:"", BBteg:undefined, BBpar:undefined, Pos:undefined, Err:undefined};
    64.     var l;
    65.     if (arr.length == 0) l = BBstr.length
    66.     else l = arr[0].index;
    67.     HTMLstr.HTMLstr = escapeHtml(BBstr.substring(0, l)).replace(/\u00BD/g, "<br>");
    68.     // Перебираем массив тегов, смотрим их позиции в строке и втыкаем соответствующие теги
    69.     while (arr.length > 0) {
    70.         if (arr.length == 1) l = BBstr.length
    71.         else l = arr[1].index;
    72.         switch (arr[0][1].toUpperCase()) {
    73.             case "CODE":
    74.                 break;
    75.             case "*":
    76.                 // Проверяем наличие открытого тега [LIST]
    77.                 break;
    78.             case "B":
    79.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<b>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    80.                 break;
    81.             case "/B":
    82.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</b>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    83.                 break;
    84.             case "I":
    85.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<i>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    86.                 break;
    87.             case "/I":
    88.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</i>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    89.                 break;
    90.             case "U":
    91.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<u>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    92.                 break;
    93.             case "/U":
    94.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</u>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    95.                 break;
    96.             case "S":
    97.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<strike>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    98.                 break;
    99.             case "/S":
    100.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</strike>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    101.                 break;
    102.             case "COLOR":
    103.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<span style=\"color:" + arr[0][2] + "\">" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    104.                 break;
    105.             case "/COLOR":
    106.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</span>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    107.                 break;
    108.             case "URL":
    109.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<a href=\"" + arr[0][2] + "\">" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    110.                 break;
    111.             case "/URL":
    112.                 if (arr[0])
    113.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</a>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    114.                 break;
    115.             case "IMG":
    116.                 if (arr[0][2] != undefined) HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<img src=\"" + arr[0][2] + "\" alt=\"" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>") + "\"/>";
    117.                 else HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<img src=\"" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>") + "\"/>";
    118.                 break;
    119.             case "/IMG":
    120.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    121.                 break;
    122.         case "LEFT":
    123.             break;
    124.         case "/LEFT":
    125.             break;
    126.         case "RIGHT":
    127.             break;
    128.         case "/RIGHT":
    129.             break;
    130.         case "CENTER":
    131.             break;
    132.         case "/CENTER":
    133.             break;
    134.         case "INDENT":
    135.             break;
    136.         case "/INDENT":
    137.             break;
    138.         case "EMAIL":
    139.             break;
    140.         case "/EMAIL":
    141.             break;
    142.         case "THREAD":
    143.             break;
    144.         case "/THREAD":
    145.             break;
    146.         case "POST":
    147.             break;
    148.         case "/POST":
    149.             break;
    150.         case "LIST":
    151.             break;
    152.         case "/LIST":
    153.             break;
    154.         case "VIDEO":
    155.             break;
    156.         case "/VIDEO":
    157.             break;
    158.         case "AUDIO":
    159.             break;
    160.         case "/AUDIO":
    161.             break;
    162.         case "QUOTE":
    163.             break;
    164.         case "/QUOTE":
    165.             break;
    166.         case "SPOILER":
    167.             break;
    168.         case "/SPOILER":
    169.             break;
    170.         }
    171.         arr.shift();
    172.     };
    173.    
    174.    
    175.     // Сюда запишем получившийся HTML
    176.     return HTMLstr
    177. };
    178.  
    179. function Parse(BBstr) {
    180.     var HTMLstr = "";
    181.     var res = ParseBB(BBstr);
    182.     if (res.Err == undefined) {
    183.         HTMLstr = res.HTMLstr;
    184.     } else {
    185.         HTMLstr = "Ошибочка: \"" + res.Err + "\", тег \"" + res.BBteg + "\", параметр \"" + res.BBpar + "\", позиция " + res.Pos;
    186.     }
    187.     return HTMLstr;
    188. }
    Добавлено спустя 12 минут 34 секунды:
    Сегодня вечером сделаю поправку в логику вложенности на тег CODE, ибо внутри него может быть все что угодно кроме неэкранированного закрывающего тега /CODE. Потом продолжу забивать подмену ББ на HTML.
     
  17. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    надо сохранить и оригинал, и закешировать его хтмл код. И усё.
     
  18. Nasgool

    Nasgool Новичок

    С нами с:
    28 фев 2015
    Сообщения:
    13
    Симпатии:
    0
    igordata, в данном случае дело личное каждого на чьей стороне реализовывать парсинг.
    Вы считаете на стороне сервера, я считаю в целях безопасности и стабильности на обеих сторонах.
    Штатный режим работы:
    • парсинг работает на стороне клиента и сервера не касается, на сервер летит POST только когда пользователь подготовил, отредактировал и 100 раз посмотрел свой пост в предварительном просмотре
    • сервер прогоняет парсинг заново (никто же не мешает злоумышленнику отсылать запрос серверу не с вашей страницы или вообще не из браузера)
    • сервер пишет 2 варианта в базу (BB и HTML)
    • при запросах на отображение страницы сервер выдает HTML, при запросе на редактирование своего поста от пользователя - BB

    Ну думаю харОшь теории...

    Вот что у меня получается.
    Оформление тегов BB кода:
    • название тега парсится регистронезависимо, т.е. так будет правильно COLOR, color, cOlOr
    • между открывающей скобкой и названием тега может быть сколько угодно пробелов, табуляций и переносов строки
    • между названием тега и закрывающей скобкой может быть сколько угодно пробелов, табуляций и переносов строки
    • если присутствуют атрибуты тега то между названием тега и знаком равно может быть сколько угодно пробелов, табуляций и переносов строки
    • между знаком равно и атрибутом может быть сколько угодно пробелов, табуляций и переносов строки
    • между атрибутом и закрывающей скобкой может быть сколько угодно пробелов, табуляций и переносов строки
    • в закрывающем теге между слешем и названием тега не должно быть пробелов, слеш как таковой входит в название тега
    Правильные примеры:
    Код (Text):
    1. [ b ]Текст[     /b
    2. ]
    3.  
    4. [
    5. img
    6. =    Текст
    7.            ] Текст[ /img         ]
    Неправильный пример:
    Код (Text):
    1. [b]Текст[/ b]
    На данный момент логическая проверка реализована полностью.
    Проверяется правильная вложенность тегов, обязательное закрытие тегов (кроме пункта списка *).
    Если встречается пункт списка то проверяется, находится ли он внутри открывающего и закрывающего тега LIST
    Если встречается тег CODE то все дальнейшие теги просто игнорируются и пишутся как текст до тех пор пока не встретится закрывающий /CODE. Если в тексте кода необходимо вставить в виде текста закрывающий /CODE то после начальной скобкой экранируем его обратным слешем \CODE.

    Погнали по тегам:
    Код (Text):
    1. [B]Жирный[/B]
    2. [U]Подчеркнутый[/U]
    3. [I]Курсив[/I]
    4. [S]Зачеркнутый[/S]
    5. [COLOR=#00FF00]Текст для выделения цветом[/COLOR]
    6. [URL]http://nasgool.ru[/URL]
    7. [URL=http://nasgool.ru]Ссылка на мой сайт[/URL]
    8. [IMG]Ссылка на картинку[/IMG]
    9. [IMG=Ссылка на картинку]Альт текст если в браузере отключены картинки[/IMG]
    В коде написано преобразование каких тегов еще необходимо реализовать:
    Код (Text):
    1. // преобразование символов HTML в спецсимволы HTML
    2. function escapeHtml(text) {
    3.   var map = {
    4.     '&': '&amp;',
    5.     '<': '&lt;',
    6.     '>': '&gt;',
    7.     '"': '&quot;',
    8.     "'": '&#039;'
    9.   };
    10.   return text.replace(/[&<>"']/g, function(m) { return map[m]; });
    11. }
    12.  
    13. // Парсим BB
    14. function ParseBB(BBstr) {
    15.     // Заменяем символы конца строки на неиспользуемые юникоды
    16.     BBstr = BBstr.replace(/\n/g, "\u00BD");
    17.     // Выделяем все теги BB кода в массив
    18.     var rg = "\\[[\\s*\\u00BD*]*(B|\\/B|I|\\/I|U|\\/U|COLOR|\\/COLOR|S|\\/S|\\*|LEFT|\\/LEFT|RIGHT|\\/RIGHT|CENTER|\\/CCENTER|INDENT|\\/INDENT|EMAIL|\\/EMAIL|URL|\\/URL|THREAD|\\/THREAD|POST|\\/POST|LIST|\\/LIST|IMG|\\/IMG|VIDEO|\\/VIDEO|AUDUO|\\/AUDIO|SPOILER|\\/SPOILER|CODE|\\/CODE|QUOTE|\\QUOTE)[\\s*\\u00BD*]*(?:=[\\s*\\u00BD*]*([^\\[\\]\\s]*))?[\\s*\\u00BD*]*\\]";
    19.     rg = new RegExp(rg , "gi");
    20.     var arr = new Array();
    21.     var i = 0;
    22.     while ((arr[i] = rg.exec(BBstr)) !== null) {
    23.         i++;
    24.     };
    25.     arr.pop();
    26.     if (arr.length == 0) {
    27.         return {HTMLstr:escapeHtml(BBstr).replace(/\u00BD/g, "<br>"), BBteg:undefined, BBpar:undefined, Pos:undefined, Err:undefined};
    28.     }
    29.     // Создаем стек
    30.     var stk = new Array();
    31.     // Если первым стоит закрывающий тег то ошибочка
    32.     if (arr[0][1][0] == "/") return {HTMLstr:undefined, BBteg:arr[0][1], BBpar:arr[0][2], Pos:arr[0].index, Err:"Нет предшествующего открывающего тега"};
    33.     // Если первым стоит закрывающий тег то ошибочка
    34.     if (arr[0][1] == "*") return {HTMLstr:undefined, BBteg:arr[0][1], BBpar:arr[0][2], Pos:arr[0].index, Err:"Элемент списка должен находится между открывающи и закрывающим тегами LIST"};
    35.     // Начинаем прогон тегов на вложенность
    36.     stk.push(arr[0]);
    37.     var cde = (stk[0][1].toUpperCase() == "CODE");
    38.     var j;
    39.     var cl;
    40.     var vl;
    41.     for (i = 1; i < arr.length; i++) {
    42.         // Проверяем открыт ли тег CODE
    43.        if (cde) {
    44.             // Если код открыт то помечаем теги на удаление, до закрытия тега
    45.             if (arr[i][1].toUpperCase() != "/CODE") {
    46.                 arr[i][1] = "del";
    47.                 continue;
    48.             } else {
    49.                 cde = false;
    50.                 stk.pop();
    51.                 continue;
    52.             };
    53.         };
    54.         // Если это тег элемента списка то проверяем, есть ли открытый тег LIST
    55.         if (arr[i][1] == "*") {
    56.             vl = false;
    57.             cl = false;
    58.             for (j = i - 1; j > -1; j--) {
    59.                 if (arr[j][1].toUpperCase() == "/LIST"){
    60.                     cl = true;
    61.                     continue;
    62.                 };
    63.                 if (cl && (arr[j][1] == "LIST")) {
    64.                     cl = false;
    65.                     continue;
    66.                 };
    67.                 if (!cl && (arr[j][1] == "LIST")) {
    68.                     vl = true;
    69.                     break;
    70.                 };
    71.             };
    72.             if (!vl) return {HTMLstr:undefined, BBteg:arr[i][1], BBpar:arr[i][2], Pos:arr[i].index, Err:"Элемент списка должен находится между открывающи и закрывающим тегами LIST"};
    73.         };
    74.         // Если это закрывающий тег, проверяем какой тег был открыт последним
    75.         if (arr[i][1][0] == "/") {
    76.             if (arr[i][1].substring(1, arr[i][1].length) == stk[stk.length - 1][1]) {
    77.                 // Если зарывающий и последний открывающий теги равны то передаем параметр а закрывающий тег
    78.                 arr[i][2] = stk[stk.length - 1][2];
    79.                 // и очищаем верхнюю позицию стека
    80.                 stk.pop();
    81.             // Если закрывающий тег не совпадает с последний открывающим, то ошибка в логике, выход из цикла
    82.             } else break;
    83.         } else {
    84.             // Если это открывающий тег, то записываем его в стек
    85.             stk.push(arr[i]);
    86.         };
    87.     };
    88.     // Удаляем помеченные теги
    89.     for (i = arr.length - 1; i > -1; i--) if (arr[i][1] == "del") arr.splice(i, 1);
    90.     // Если вся логика верна, то стек в итоге остается пустым
    91.     // если это не так, то ошибка, пишем в результат информацию о ней и выходим
    92.     if (stk.length > 0) return {HTMLstr:undefined, BBteg:stk[stk.length - 1][2], BBpar:stk[stk.length - 1][3], Pos:stk[stk.length - 1].index, Err:"Не соблюдена вложенность тегов или не закрыт тег"};
    93.     // тут будем реализовывать уже подмену тегов и втавленных ранее юникодов на HTML
    94.     // Заносим начало строки до первого тега в результат
    95.     var HTMLstr = {HTMLstr:"", BBteg:undefined, BBpar:undefined, Pos:undefined, Err:undefined};
    96.     var l;
    97.     if (arr.length == 0) l = BBstr.length
    98.     else l = arr[0].index;
    99.     HTMLstr.HTMLstr = escapeHtml(BBstr.substring(0, l)).replace(/\u00BD/g, "<br>");
    100.     // Перебираем массив тегов, смотрим их позиции в строке и втыкаем соответствующие теги
    101.     while (arr.length > 0) {
    102.         if (arr.length == 1) l = BBstr.length
    103.         else l = arr[1].index;
    104.         switch (arr[0][1].toUpperCase()) {
    105.             case "CODE":
    106.                 break;
    107.             case "*":
    108.                 break;
    109.             case "B":
    110.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<b>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    111.                 break;
    112.             case "/B":
    113.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</b>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    114.                 break;
    115.             case "I":
    116.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<i>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    117.                 break;
    118.             case "/I":
    119.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</i>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    120.                 break;
    121.             case "U":
    122.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<u>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    123.                 break;
    124.             case "/U":
    125.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</u>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    126.                 break;
    127.             case "S":
    128.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<strike>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    129.                 break;
    130.             case "/S":
    131.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</strike>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    132.                 break;
    133.             case "COLOR":
    134.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<span style=\"color:" + arr[0][2] + "\">" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    135.                 break;
    136.             case "/COLOR":
    137.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</span>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    138.                 break;
    139.             case "URL":
    140.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<a href=\"" + arr[0][2] + "\">" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    141.                 break;
    142.             case "/URL":
    143.                 if (arr[0])
    144.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "</a>" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    145.                 break;
    146.             case "IMG":
    147.                 if (arr[0][2] != undefined) HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<img src=\"" + arr[0][2] + "\" alt=\"" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>") + "\"/>";
    148.                 else HTMLstr.HTMLstr =  HTMLstr.HTMLstr + "<img src=\"" + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>") + "\"/>";
    149.                 break;
    150.             case "/IMG":
    151.                 HTMLstr.HTMLstr =  HTMLstr.HTMLstr + escapeHtml(BBstr.substring(arr[0].index + arr[0][0].length, l)).replace(/\u00BD/g, "<br>");
    152.                 break;
    153.         case "LEFT":
    154.             break;
    155.         case "/LEFT":
    156.             break;
    157.         case "RIGHT":
    158.             break;
    159.         case "/RIGHT":
    160.             break;
    161.         case "CENTER":
    162.             break;
    163.         case "/CENTER":
    164.             break;
    165.         case "INDENT":
    166.             break;
    167.         case "/INDENT":
    168.             break;
    169.         case "EMAIL":
    170.             break;
    171.         case "/EMAIL":
    172.             break;
    173.         case "THREAD":
    174.             break;
    175.         case "/THREAD":
    176.             break;
    177.         case "POST":
    178.             break;
    179.         case "/POST":
    180.             break;
    181.         case "LIST":
    182.             break;
    183.         case "/LIST":
    184.             break;
    185.         case "VIDEO":
    186.             break;
    187.         case "/VIDEO":
    188.             break;
    189.         case "AUDIO":
    190.             break;
    191.         case "/AUDIO":
    192.             break;
    193.         case "QUOTE":
    194.             break;
    195.         case "/QUOTE":
    196.             break;
    197.         case "SPOILER":
    198.             break;
    199.         case "/SPOILER":
    200.             break;
    201.         }
    202.         arr.shift();
    203.     };
    204.    
    205.    
    206.     // Сюда запишем получившийся HTML
    207.     return HTMLstr
    208. };
    209.  
    210. function Parse(BBstr) {
    211.     var HTMLstr = "";
    212.     var res = ParseBB(BBstr);
    213.     if (res.Err == undefined) {
    214.         HTMLstr = res.HTMLstr;
    215.     } else {
    216.         HTMLstr = "Ошибочка: \"" + res.Err + "\", тег \"" + res.BBteg + "\", параметр \"" + res.BBpar + "\", позиция " + res.Pos;
    217.     }
    218.     return HTMLstr;
    219. }
     
  19. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    ммм, нет. Это не дело и не личное. Это вопрос того, что вы хотите дать своим посетителям. Даже если ваша раскраска будет быстрой, один хрен это скажется на рассчёте высоты страницы, что приведёт к дерганью. Так что на мой взгляд это даже не вопрос, а так, предположение, что возможно когда-то, можно будет реализовать парсинг на клиенте.
     
  20. Nasgool

    Nasgool Новичок

    С нами с:
    28 фев 2015
    Сообщения:
    13
    Симпатии:
    0
    Не буду отвлекаться от практики на ненужную теорию. Вы бы конкретнее, с примерами. Аргументируйте.

    ЗЫ Про подергивания... Мне ничто не мешает показывать загрузочную анимацию и просто надпись "Жди... работаю..." вместо контента, пока идет парсинг.

    ЗЗЫ А вобще, вы про верстку а я про кодинг )
     
  21. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    это всё плохо сказывается на поведении посетителей. Поэтому не не вопрос мнений, и не теория. Если вы пилите нечто, что нафик никому не нужно или даже вредит тем, кто будет этим пользоваться - вот это ваше личное дело. Но вы должны это осознавать. =)
     
  22. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Незнаю работает ли оно на последних версиях PHP (поидее должно), но я просто оставлю это здесь: http://pecl.php.net/package/bbcode
     
  23. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.072
    Симпатии:
    1.237
    Адрес:
    там-сям
    скажу коротко - нерабочее говно
    некто Чушкин уже предлагал его (также не читая и не пробуя) ещё на первой странице.
     
  24. Nasgool

    Nasgool Новичок

    С нами с:
    28 фев 2015
    Сообщения:
    13
    Симпатии:
    0
    Жаль что времени на свои заморочки мало, работа все съедает...

    Код (Text):
    1. [B]Жирный[/B]
    2. [U]Подчеркнутый[/U]
    3.  
    4. [I]Курсив[/I]
    5.  
    6. [S]Зачеркнутый[/S]
    7.  
    8. [COLOR=#00FF00]Текст для выделения цветом[/COLOR]
    9.  
    10. [IMG]Ссылка на картинку[/IMG]
    11. [IMG=Ссылка на картинку]Альт текст если в браузере отключены картинки[/IMG]
    12.  
    13. [URL]Ссылка[/URL]
    14. [URL=Ссылка]Видимый текст[/URL]
    15.  
    16. [EMAIL]Почта[/EMAIL]
    17. [EMAIL=Почта]Видимый текст[/EMAIL]
    18.  
    19. [CODE]Код[ /   CODE]
    20.  
    21. [SPOILER=Заголовок спойлера] Скрытый спойлером текст или другие теги[/SPOILER]
    22.  
    23. [LIST]
    24. [*]Первая запись маркированного списка
    25. [*]Вторая запись маркированного списка
    26. [/LIST]
    27.  
    28. [LIST=A]
    29. [*]Первая запись нумерованного списка
    30. [*]Вторая запись нумерованного списка
    31. [*]Значения атрибута 1, A, a, I, i (любой другой атрибут расценивается как 1)
    32. [/LIST]
    33.  
    34. [QUOTE=Заголовок цитаты]Текст цитаты[/QUOTE]
    35.  
    36. [VIDEO]Ссылка на страницу видео[/VIDEO]
    37.  
    38. [AUDIO]Прямая ссылка на файл или поток[/AUDIO]
    Поправил ошибки в регулярке.
    Рализовал оставшиеся теги.
    Видео пока работает только с ютюба.
    В планах добавить разных сервисов к видео (определять будет по атрибуту например VIDEO=YOUTUBE)
    Так же хочу прикрутить атрибут к CODE=PHP/JS/HTML/C/DELPHI и делать соответствующую подсветку кода

    https://yadi.sk/d/5TVXPR6if3azw ссылка на код и css (без него не работает спойлер и плохое оформление),
    если есть конструктивные идеи я весь внимание.

    Добавлено спустя 12 минут 42 секунды:
    Забыл добавить о возвращаемом объекте функцией ParseBB()

    {HTMLstr:string, // HTML полученный в результате парсинга, если были ошибки то undefined
    BBteg:string, // undefined, если случилась ошибка, тут название тега в котором это произошло
    BBpar:string, // undefined, если случилась ошибка, тут атрибут тега в котором это произошло, если нет атрибута то undefined
    Pos:int, // undefined, если случилась ошибка, тут позиция тега в исходной строке в котором это произошло
    Err:string} // undefined, если случилась ошибка, тут сообщение, которое разъясняет ошибку
     
  25. Nasgool

    Nasgool Новичок

    С нами с:
    28 фев 2015
    Сообщения:
    13
    Симпатии:
    0
    Прикрутил я подсветку кода от google. Ссылка тут и в предыдущем посте новая.

    Для его работы необходимо скачать стиль:
    https://code.google.com/p/google-code-prettify/source/browse/trunk/src/prettify.css
    Прописать его на страницу:
    Код (Text):
    1. <link href="css/prettify.css" rel="stylesheet">
    Туда же нужно прописать загрузчик скрипта:
    Код (Text):
    1. <script src="https://google-code-prettify.googlecode.com/svn/loader/prettify.js"></script>
    Далее нужно поправить onload станицы и onclick кнопки предварительного просмотра, добавив туда функцию:
    Код (Text):
    1.     prettyPrint();
    Для того что бы нумерация строк кода была на каждой строке а не каждые 5 строк, нужно в prettify.css добавить:
    Код (Text):
    1. li.L0, li.L1, li.L2, li.L3,
    2. li.L5, li.L6, li.L7, li.L8
    3. { list-style-type: decimal !important }
    Скачать скрипт и стиль:
    https://yadi.sk/d/5TVXPR6if3azw

    На этом я думаю все с этой темой.