Разбор html-кода ссылки -> href + text

Sluggard

Новичок
PHP:
preg_match('#<a\s+(?:href=(["\']?)([^" \'>]*)(\1))(?:(?:[^"\']*?)(?:["\'][^"\']*["\'])?)*>(.+)</a>#mis', $text, $t);
 

svetasmirnova

маленький монстрик
Я не помню точной цитаты из Фридла, но задачка считается нетривиальной. Но вот тебе пока твои атрибуты. Остальное попроще будет выудить ;)
PHP:
$result = array();
function callback($matches)
{
	global $result;
	$result[] = $matches[0];
}
preg_replace_callback('/(\s+\w+=\'[^\']*?\')|' .
			   '(\s+\w+="[^"]*?")|' .
			   '(\s+\w+=[^\s]+)/', 'callback', $text);
var_dump($result);
-~{}~ 15.03.05 10:35:

Подумавши, решила запостить целиком скрипт, но без обработки вложенных тэгов:
PHP:
$attrs = array();
$link_text = '';
function callback($matches)
{
	global $attrs;
	$attrs[] = $matches[0];
}
if (preg_match('/<(\w+)((' .
			   '(\s+\w+=\'[^\']*?\')|' .
			   '(\s+\w+="[^"]*?")|' .
			   '(\s+\w+=[^\s]+)' .
				')*)>(.*?)<\/\\1>/', $text, $matches)) {
	$link_text = $matches[7];
	preg_replace_callback('/(\s+\w+=\'[^\']*?\')|' .
						  '(\s+\w+="[^"]*?")|' .
						  '(\s+\w+=[^\s]+)/', 'callback', $matches[2]);
}
var_dump($link_text);
var_dump($attrs);
Для того, чтобы тоже самое сделать одним регулярным выражением подскажите-ка мне решение "простой" задачки:
Дана строка
PHP:
$string = 'aabc';
Следующий код выдаст нам такой результат:
PHP:
$matches = array();
preg_match('/(\w)+/', $string, $matches);
var_dump($matches);
---------------------
$matches = array(
[0] =>"aabc",
[1] => 'c'
)
А мне нужно одним лишь регулярным выражением получить следующий результат:
PHP:
$matches = array(
[0] =>"aabc",
[1] => 'a',
[2] => 'a',
[3] => 'b',
[4] => 'c'
)
при произвольном количестве букв
 

young

Новичок
svetasmirnova
Спасибки за соучастие в проблеме ;)
Для себя я решил ее пока путем упрощения задачи (простенький regexp для выкусывания href + strip_tags для самого содержимого ссылки)

Но сама проблема меня волнует и в дальнейшем.

Сорри, вникать в твой код по глубже еще не было времени, но вот на таком примере он не работает

$text = '<a href="a.com" alt="" ><b>test</b></a>';
 

Фанат

oncle terrible
Команда форума
young
пока ты словами не составишь алгоритм, у тебя ничего не получится.
все, что здесь творится - это тыканье слепого котенка.
в таком деле надо однозначно составлять алгоритм.
а потом уже из него строить - хошь рег, хошь - посимвольный парсер.
лично я не представляю себе рег, который учтет ВСЕ капризы невалидного хтмла.
к примеру < /a> c пробелом.
в любом случае - пока ты внятно не сформулируешь, чего хочешь (на уровне алгоритма, разумеется, а не тыканья пальцем) - у тебя ничего не выйдет.
 

young

Новичок
Для себя использую следующее решение:

1) режем html на ссылки
preg_match_all("#<a\s.+?/a>#ims", $result, $allLinks);

Далее для каждой ссылки
2) режем кривые теги типа <a href=""> <a href=""> test </a>

preg_match("#<a\s(?!.+<a\s).+?/a>#ims", $item, $tmp);

3) Выкусываем href
preg_match("#href\s*=\s*(?:'|\"|)([^\s'\">]+)#mis", $item, $tmp);

4) выкусываем текст ссылки
$linkText = strip_tags($item);

-~{}~ 15.03.05 11:46:

к примеру < /a> c пробелом.
см. решение выше
Учитывает на ура :)

-~{}~ 15.03.05 11:47:

Но опять же, вопрос актуален - как сделать это "правильно"?
 

Фанат

oncle terrible
Команда форума
ну ну.
ответ в стиле "чего думать - кидать надо".
я удаляюсь
 

svetasmirnova

маленький монстрик
young
>но вот на таком примере он не работает
Он и не должен работать с последним пробелом перед >, решается добавлением \s* перед тем самым >
>Но опять же, вопрос актуален - как сделать это "правильно"?
Имхо, у тебя правильно. Взять хотя бы мой пример: сначала проверяем соответсвие регулярному выражению, а потом им же тянем атрибуты: некрасивые тормоза. И даже при решении "простой задачки", которая позволит всё сделать одним регулярным выражением, возникнет второй плохой момент:
>вникать в твой код по глубже еще не было времени
При том, что сюде можно добавить обработку вложенных тэгов и много чего ещё, в такой код нужно "глубже вникать", а значит сложнее править :)
 

young

Новичок
Имхо, у тебя правильно
Как оказалось не совсем
регулярка выкусывания href обломалась на

<a href="javascript::follow('http://domain.com')">link text</a>

Я знаю как это решить применив 2ве регулярки _для выкусывания href_ вместо
preg_match("#href\s*=\s*(?:'|\"|)([^\s'\">]+)#mis", $item, $tmp);

у кого-то есть более красивое решение?
 

svetasmirnova

маленький монстрик
Я свой вариант упростила:
PHP:
preg_match('/href\s*=\s*((\'|").*?\\2)/', $text, $matches);
-~{}~ 15.03.05 22:19:

И опять я со своей девичьей памятью ;)
PHP:
preg_match('/href\s*=\s*(((\'|"|\s)?).*?\\2)(\s|>)/', $text, $matches);
Теперь и ссылки вида <a href = xxx.html>
 

young

Новичок
у мя опять же своя версия

1) ищем href в кавычках
preg_match("#href\s*=\s*('|\")(.+?)(?<!\\\\)\\1#mis", $text, $tmp)
суть - до первой такой-же кавычки которая была открыта при условии что перед ней нет слеша

2) ищем href без кавычек
preg_match("#href\s*=\s*([^\s'\">]+)#mis", $text, $tmp);
 

neko

tеam neko
и правду говорят
что только не придумают русские, чтобы не использовать спецально написанный для таких целей софт
 

neko

tеam neko
вот уж недумал что мне придется объяснять модераторам, что им таки тоже надо читать ответы на их же собственные вопросы
 

valyala

Новичок
Re: Разбор html-кода ссылки -> href + text

Автор оригинала: young
Возможно, уже придуманы способы разбора таких вот строк

<a href="http://site.com" alt="link end with </a>" title="another href <a>example</a>" target=link1>link text</b></a>

Надо выдрать собсно href и сам текст ссылки
Вот какого непробиваемого "монстра" придумал я на основе более старого "монстра" - http://phpclub.ru/talk/showthread.php?postid=426656#post426656 :)
Попробуйте его обмануть.
PHP:
<?
function get_all_urls($str)
{
    $tag_name = "(?>(?i)[!a-z](?!--)[-:_a-z]*)";
    $quotes = "(?>\"(?>[^\"]*)\"|'(?>[^']*)')";
    $param_name = "(?>${quotes}|[^\"'\\s>](?:[^\\s>=/]+|/(?!>))*)";
    $param_value = "(?>${quotes}|[^\"'\\s>](?:[^\\s>/]+|/(?!>))*)";
    $param_pair = "(?>${param_name}(?:\\s*=\\s*${param_value}?|(?!\\s*=)))";
    $params = "(?>(?:\\s+${param_pair})*\\s*)";
    $tag = "(?></?${tag_name}${params}/?>)";
    $script = "(?>(?i)<script${params}(?:(?s)/>|>.*?</script${params}>))";
    $style = "(?>(?i)<style${params}(?:(?s)/>|>.*?</style${params}>))";
    $comment = "(?>(?s)<!--.*?-->)";
    $text = "(?>(?:[^<]+|(?!${tag})(?!${comment})<)+)";
    $html_element = "(?>${text}|${comment}|${script}|${style}|${tag})";
    $html_doc = "(?>(?s)^${html_element}*$)";

    // разбиваем строку $str на отдельные html-элементы (тэги и текст)
    preg_match_all('#' . $html_element . '#', $str, $matches);

    $tmp = $matches[0];
    $n = sizeof($tmp);

    // обрабатываем полученные html-элементы
    $urls = array();
    $in_a = false; // флаг того, что мы находимся внутри тэга <a>
    $content = ''; // тут будет храниться содержимое тэга <a>
    $href = ''; // тут будет храниться содержимое параметра href тэга <a>
    for ($i = 0; $i < $n; $i++) {
        switch (true) {
        case preg_match('#^<a.*/>$#is', $tmp[$i]) :
            // короткий тэг <a> (без внутреннего текста)
            if ($in_a) {
                // не допускаются вложенные тэги <a>
                $content .= $tmp[$i];
                break;
            }
            $href = get_href($tmp[$i], $param_pair, $param_value);
            if (is_string($href)) {
                $urls[] = array(
                    'href' => $href,
                    'content' => '',
                );
            }
            break;
        case preg_match('#^<a.*>$#is', $tmp[$i]) :
            // открывающий тэг <a>
            if ($in_a) {
                // не допускаются вложенные тэги <a>
                $content .= $tmp[$i];
                break;
            }
            $in_a = true; // находимся внтри тэга <a>
            $content = ''; // обнуляем содержимое тэга
            $href = get_href($tmp[$i], $param_pair, $param_value);
            break;
        case preg_match('#^</a.*>$#is', $tmp[$i]) :
            // закрывающий тэг </a>
            if (!$in_a) {
                // не допускаются закрывающие тэги </a> без открывающего
                $content .= $tmp[$i];
                break;
            }
            $in_a = false;
            if (is_string($href)) {
                $urls[] = array(
                    'href' => $href,
                    'content' => $content,
                );
            }
            break;
        default :
            $content .= $tmp[$i];
            break;
        } // switch ($tmp[$i])
    } // for ($i = 0; $i < $n; $i++)

    return $urls;
}

/**
    возвращает значение параметра href в тэге <a>.
    Если такого параметра не найдено, возвращает null
*/
function get_href($tag_a, $param_pair, $param_value)
{
    if (preg_match("#^(?i)<a(?:\\s+(?!href)${param_pair})*\\s+href\\s*=\\s*(${param_value}?)#", $tag_a, $matches)) {
        $href = $matches[1];
        if ($href == '') {
            return '';
        }
        if ($href{0} == "'" || $href{0} == '"') {
            $href = substr($href, 1, -1);
        }
        return html_entity_decode($href, ENT_QUOTES); 
    }
    return null; // не найден параметр href в тэге a
}

/**********************************************************************/
function do_tests($tests)
{
    $n = sizeof($tests);
    for ($i = 0; $i < $n; $i++) {
        echo '<div style="padding:10px; margin:10px; border: 1px solid; background: #ddd">';
        echo '<h2>test number ', ($i + 1), '</h2>';
        echo 'Test string: <pre style="font-weight:bold; border:1px solid; margin: 5px 0px; background:#ccc">', htmlspecialchars($tests[$i]), '</pre><br/>';
        echo 'Result:';
        echo '<pre style="font-weight:bold; border:1px solid; margin: 5px 0px; background:#ccc">';
        ob_start();
        var_dump(get_all_urls($tests[$i]));
        $tmp = ob_get_contents();
        ob_end_clean();
        echo htmlspecialchars($tmp);
        echo '</pre>';
        echo '</div>';
    }
}

$tests = array(
    // это тесты, которые были встречены в этом топике
    '<a href="http://site.com" alt="link end with </a>" title="another href <a>example</a>" target=link1>link text</b></a>',
    '<a href="1" alt=""><b>2</b></a>',
    '<a href="dite.com" alt="1">bb</a>',
    '<a href="1"><b>text</b></a>',
    '<a href=a.com>test</a>',
    '<a href="a.com" alt="" ><b>test</b></a>',
    '<a href="java script::follow(\'http://domain.com\')">link text</a>',

    // а это тесты от меня лично :)
    '<a alt="" href="a.com"  ><b>test</b></a>', // href не на первом месте
    '</a><a title href="abc"/><a href=dfdf >te<a href=aaa> st</a tsfd d >', // много тэгов <a>
    'test<a href= >test</a>', // пустая ссылка
    // не достаем ссылки из яваскрипта
    '<script>document.write("<a href=\'aaa.com\'>test</a>");</script><a href = bbb.com><img src="abc.test" /></a>',
    '<div title="<a href=abcd>test</a>">test</div>', // не достаем ссылки из свойств тэгов
);

do_tests($tests);

?>
 

svetasmirnova

маленький монстрик
Автор оригинала: neko
и правду говорят
что только не придумают русские, чтобы не использовать спецально написанный для таких целей софт
Мы так развлекаемся;)
 

young

Новичок
вроде симпатишно
хотя

<a href="javascript::eek:pen('tes\"t')"> test </a>

он не съел
 

valyala

Новичок
Автор оригинала: young
вроде симпатишно
хотя

<a href="javascript::eek:pen('tes\"t')"> test </a>

он не съел
Да, такого я не предусмотрел :) Замени в строчке
PHP:
$params = "(?>(?:\\s+${param_pair})*\\s*)";
плюсик на звездочку - и будет тебе счастье.
Кстати, в html'е нельзя использовать бэкслэши для экранирования символов. Вместо этого рекомендовано использовать html entities. Проверь свой пример в разных браузерах, если не веришь мне :)
 
Сверху