Форум: Форум PHPФорум ApacheФорум Регулярные ВыраженияФорум MySQLHTML+CSS+JavaScriptФорум FlashРазное
Новые темы: 0000000
Объектно-ориентированное программирование на PHP. Авторы: Кузнецов М.В., Симдянов И.В. PHP. Практика создания Web-сайтов (второе издание). Авторы: Кузнецов М.В., Симдянов И.В. PHP 5. На примерах. Авторы: Кузнецов М.В., Симдянов И.В., Голышев С.В. Программирование. Ступени успешной карьеры. Авторы: Кузнецов М.В., Симдянов И.В. PHP на примерах (2 издание). Авторы: Кузнецов М.В., Симдянов И.В.
ВСЕ НАШИ КНИГИ
Консультационный центр SoftTime

Форум Регулярные Выражения

Выбрать другой форум

 

Здравствуйте, Посетитель!

вид форума:
Линейный форум Структурный форум

тема: Валидация списков UL OL LI
 
 автор: Igorek   (20.03.2013 в 20:06)   письмо автору
 
 

Необходимо проверить текст на наличие списков, если есть - проверить на валидность.
Примеры невалидных текстов:
text...
<li>111</li>
<li>222</li>
text...

text...
<ol><li>111</li>
<li>222</li>
text...

text...
<ol><li>111
<li>222</li>
</ol>
text...


может кто подскажет регулярочку или другой путь решения? Чет я пока ничего приемлемого не могу придумать

  Ответить  
 
 автор: confirm   (20.03.2013 в 20:32)   письмо автору
 
   для: Igorek   (20.03.2013 в 20:06)
 

Просто ищите все в тексте между <> и </>, помещая открывающие теги в массив как в стек. Если находите закрывающий тег, то вершина вашего стека должна быть этого тега, и если так удаляете открывающий тег из стека (значит верно, вершина стека теперь на другой ранее найденный тег).
Для тегов типа OL-LI, UL-LI, SELECT-OPTIONS и прочим нужно задать отдельные правила - что и как может следовать.
Таким образом можно проверить корректность вложения тегов.

  Ответить  
 
 автор: confirm   (21.03.2013 в 12:07)   письмо автору
 
   для: Igorek   (20.03.2013 в 20:06)
 

Вроде бы я здесь кому-то уже предлагал так поступать при проверки вложения, но это только идея была, которую как-то не было необходимости решать реально. Хотя то, что нужно использовать для этого массив имитирующий стек FILO (первым вошел - последним вышел), это не вызывает сомнения, так как лучшего для такого контроля, чем стек, и не придумать.

Для простых тегов, типа параграфа, ссылки и т.п., все просто, а вот для тегов имеющих дочерние элементы, необходимо описывать правила. Вот появилось время немного, написал как можно это реализовать. Объявим массив, который будет описывать правила структуры вложений для некоторых тегов (в примере только те, что у вас - UL, OL, LI).

Сначала открывающие теги. Тегам OL, LI может предшествовать только тег LI, или же отсутствовать. Следовательно для них указываем правило - "пустой" или ''li". А так как мы всегда ожидаем в обязательном порядке вначале открывающий тег, то значение "пустой" загрузим в вершину стека при его инициализации. Это единственное значение стека, которое никогда не будет из него извлечено, и при верном вложении тегов, каждый новый открывающийся тег (вне вложений) будет будет соответствовать этому правилу. А в случае, если открывающему тегу списка предшествует тег не описанный в условии (вершина стека не равна ни одному тегу условий, а ключ проверяемого тега есть в условиях), то действует правило - первый элемент массива правил тега должен быть равен "пусто". Тегу LI всегда должен предшествовать тег указанный в условиях.

И так:
<?
$order 
= array(
  
'ul' => array('','li'),
  
'ol' => array('','li'),
  
'li' => array('ul','ol')
);
$stack = array(''); //инициализация стека

$s 'text... 
<li>111</li> 
<li>222</li>
text...'
;
//разобьем проверяемый текст на строки,
//так удобнее производить поиск и вывести строку с ошибкой 
$s explode("\r\n"$s);
$i 1//условно начальный номер строк
$error null;    
foreach(
$s as $v) {
   
$match preg_match_all("/(<.*?>)/i"$v$tag);
   if(
$match) {
     
$tag array_map('strtolower'$tag[0]);
     foreach(
$tag as $t) {
        
$src $t[1]!='/' 0//определяем тип тега
        
$t trim($t,'</> '); //получаем имя тега, в примере не учитываются его атрибуты, иначе нужно удалять их
        
if(!$t) {
           
$error 'Ошибка в строке '.$i.' - некорректное имя тега!';
           break 
2;
        }
        if(
$src) { //открывающий тег
           //если тег соответствует правилу вложения, то помещаем его в стек, вершина стека растет
           
if(!array_key_exists($t$order) || 
               
array_key_exists($t$order) && in_array($stack[0], $order[$t]) ||
               
array_key_exists($t$order) && !$order[$t][0]
           ) 
array_unshift($stack$t); 
           else {
              
$error 'Ошибка в строке '.$i.' - отсутствует предшествующий тег '.implode(' или 'array_diff($order[$t],array(''))).'!';
              break 
2;
           }    
        } else { 
//закрывающий тег
           
if($t==$stack[0]) array_shift($stack); //если тег соответствует открывающему, то извлекаем открывающий тег из стека, вершина стека убывает
           
else {
              
$error 'Ошибка в строке '.$i.' - незакрыт предшествующий тег '.$stack[0].'!';
              break 
2;
           }    
        }
     }
   }
   
$i++;
}
//вывод ошибок вложения
//и при их отсутствии проверка "чистоты" стека - отсутствие одиночного открытого тега   
echo $error $error count($stack)=='Вложение корректное.' 'Ошибка - отсутствует закрывающий тег '.$stack[0].'!';
//контроль стека
echo '<br><pre>';
print_r($stack);

В общем примерно такой механизм, остается проверить на "узкие места", если есть, а также дополнить отсутствующими, но необходимыми проверками. В массиве $order также необходимо прописать правила для одиночных тегов, таких как IMG (если писать это для всех тегов). Для более полного описания правил различных тегов можно применить массив описывающий допустимые теги и т.п., и функцию preg_grep().

  Ответить  
 
 автор: Igorek   (21.03.2013 в 14:11)   письмо автору
 
   для: confirm   (21.03.2013 в 12:07)
 

Спасибо за идею использования стэка. Действительно подходящее применение. Ваш пример обязательно подробно рассмотрю попозже. Вот, что у меня получилось пока:
<?php

echo '<pre>';
$html = <<<HTML
<p>consectetur <b>adipiscing </b>elit. Maecenas sit amet molestie enim. Nulla gravida mi sed ipsum </p>
<p>pellentesque ut pulvinar orci consequat. Donec tempus rutrum urna at iaculis. Ut sagittis, tellus eu venenatis imperdiet,</p>

<ol>
<Li>
sem dolor mattis leo,
  <ol>
    <li>sda
    f</li>
  </ol>
</li> 
<li>in consequat elit lorem ut ligula.</li>
</ol>
Praessadf

HTML;

var_dump(check_html($html));

function 
check_html($html) {
  
$tags = array();
  
preg_match_all('#<(/?[a-zA-Z]+)\b.*?>#im'$html$tags);
  
$tags array_map('strtolower'$tags[1]);

  
$lists = array('ul''ol');

  
$tags_stack = new SplStack();
  
$lists_stack = new SplStack();

  foreach (
$tags as $tag) {

    if (
strpos($tag'/') === false) { // open tag
      
if (in_array($tag$lists)) {
        echo 
"New list starts\n";
        
$lists_stack->push($tag); // new list starts
      
}
      echo 
"Push $tag\n";
      
$tags_stack->push($tag);
    } else { 
// close tag
      
if (!($tags_stack->count())) {
        echo 
"closed tag without opened one\n";
        return 
false// closed tag without opened one
      
}

      
// handle lists
      
switch ($tag) {
        case 
'/ol':
        case 
'/ul':
          if (!
$lists_stack->count()) {
            echo 
"there are no opened lists\n";
            return 
false// there are no opened lists
          
}
          if (
$lists_stack->pop() != trim($tag'/')) {
            echo 
"unmatching lists tag \n";
            return 
false// unmatching lists tag
          
}
          break;
        case 
'/li':
          if (!
$lists_stack->count()) {
            echo 
"there are no opened lists\n";
            return 
false// there are no opened lists
          
}
          break;
      }
      
      
$t $tags_stack->pop();
      
      echo 
"Pop $t. compare with $tag\n";
      if (
$t != trim($tag'/')) {
        echo 
"unclosed tag\n";
        return 
false// unclosed tag
      
}
    }
  }

  return !
$tags_stack->count();
}


Выводит:
Push p
Push b
Pop b. compare with /b
Pop p. compare with /p
Push p
Pop p. compare with /p
New list starts
Push ol
Push li
New list starts
Push ol
Push li
Pop li. compare with /li
Pop ol. compare with /ol
Pop li. compare with /li
Push li
Pop li. compare with /li
Pop ol. compare with /ol
bool(true)



В текущей реализации для списков использую отдельный стэк, что мне кажется не лучшим решением, если в дальнейшем дорабатывать алгоритм для других "сложных" тегов (чтобы не создавать под каждый случай отдельный стэк).

  Ответить  
 
 автор: confirm   (21.03.2013 в 14:27)   письмо автору
 
   для: Igorek   (21.03.2013 в 14:11)
 

switch ... case уж точно не к лицу такой задаче, неудобно.

  Ответить  
 
 автор: Igorek   (21.03.2013 в 14:37)   письмо автору
 
   для: confirm   (21.03.2013 в 14:27)
 

Да, согласен. Чего-нить вроде массива правил, как у вас, надо будет добавить и переписать эту часть.

  Ответить  
 
 автор: confirm   (21.03.2013 в 14:54)   письмо автору
 
   для: Igorek   (21.03.2013 в 14:37)
 

И кроме этого есть лишнее, например зачем грузить в стек весь тег с потрохами, а затем избавляться постоянно от них при проверках? Запускать strpos для поиска "/" тоже ни к чему, это всегда второй символ в теге, иначе ошибка.

  Ответить  
Rambler's Top100
вверх

Rambler's Top100 Яндекс.Метрика Яндекс цитирования