|
|
|
| Необходимо проверить текст на наличие списков, если есть - проверить на валидность.
Примеры невалидных текстов:
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...
|
может кто подскажет регулярочку или другой путь решения? Чет я пока ничего приемлемого не могу придумать | |
|
|
|
|
|
|
|
для: Igorek
(20.03.2013 в 20:06)
| | Просто ищите все в тексте между <> и </>, помещая открывающие теги в массив как в стек. Если находите закрывающий тег, то вершина вашего стека должна быть этого тега, и если так удаляете открывающий тег из стека (значит верно, вершина стека теперь на другой ранее найденный тег).
Для тегов типа OL-LI, UL-LI, SELECT-OPTIONS и прочим нужно задать отдельные правила - что и как может следовать.
Таким образом можно проверить корректность вложения тегов. | |
|
|
|
|
|
|
|
для: 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]!='/' ? 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)==1 ? 'Вложение корректное.' : 'Ошибка - отсутствует закрывающий тег '.$stack[0].'!';
//контроль стека
echo '<br><pre>';
print_r($stack);
|
В общем примерно такой механизм, остается проверить на "узкие места", если есть, а также дополнить отсутствующими, но необходимыми проверками. В массиве $order также необходимо прописать правила для одиночных тегов, таких как IMG (если писать это для всех тегов). Для более полного описания правил различных тегов можно применить массив описывающий допустимые теги и т.п., и функцию preg_grep(). | |
|
|
|
|
|
|
|
для: 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)
|
В текущей реализации для списков использую отдельный стэк, что мне кажется не лучшим решением, если в дальнейшем дорабатывать алгоритм для других "сложных" тегов (чтобы не создавать под каждый случай отдельный стэк). | |
|
|
|
|
|
|
|
для: Igorek
(21.03.2013 в 14:11)
| | switch ... case уж точно не к лицу такой задаче, неудобно. | |
|
|
|
|
|
|
|
для: confirm
(21.03.2013 в 14:27)
| | Да, согласен. Чего-нить вроде массива правил, как у вас, надо будет добавить и переписать эту часть. | |
|
|
|
|
|
|
|
для: Igorek
(21.03.2013 в 14:37)
| | И кроме этого есть лишнее, например зачем грузить в стек весь тег с потрохами, а затем избавляться постоянно от них при проверках? Запускать strpos для поиска "/" тоже ни к чему, это всегда второй символ в теге, иначе ошибка. | |
|
|
|
|