ФорумПрограммированиеPHP для идиотов → Нужен парсер простого HTML

Нужен парсер простого HTML

  • kostyl

    Сообщения: 5210 Репутация: N Группа: Джедаи

    Spritz 21 декабря 2010 г. 10:46, спустя 11 минут 18 секунд

    только есть еще прикол - нужно учитвыать много херни браузеров, которые дописывают незакрытые теги…
  • artoodetoo

    Сообщения: 5147 Репутация: N Группа: в ухо

    Spritz 21 декабря 2010 г. 10:53, спустя 7 минут 21 секунду

    Спасибо kostyl, автомат может и хороший, но не тот. Мне надо HTML в 100 строк.
    ιιlllιlllι унц-унц
  • kostyl

    Сообщения: 5210 Репутация: N Группа: Джедаи

    Spritz 21 декабря 2010 г. 11:23, спустя 29 минут 45 секунд

    artoodetoo, да можно сделать автомат и в несколько строчек… я привёл подобие пировского…
  • artoodetoo

    Сообщения: 5147 Репутация: N Группа: в ухо

    Spritz 21 декабря 2010 г. 11:34, спустя 10 минут 40 секунд

    Вот сам набросал
    
    <?php
    
    error_reporting(-1);
    
    class MyHtmlTidy
    {
        const
            TAG  = '<(?:"[^"]*"|\'[^\']*\'|[^\'">])*>',
            ATTR = '\w++\s*=\s*"[^"]++"|\w++\s*=\s*\'[^\']++\'|\w++\s*=\s*[^\s]++';
    
        private
            $_goodTags  = array('b', 'i', 'u', 's', 'p', 'a', 'img', 'br', 'hr'),
            $_selfClose = array('img', 'br', 'hr'),
            $_goodAttrs = array(
                          'a'   => array('href', 'title'), 
                          'img' => array('src', 'alt')),
            $_nest      = array();
    
        public 
            $errors = array();
    
        public function preparse($html)
        {
            $this->_nest = array();
            $this->errors = array();
            $text = preg_replace_callback('/('.self::TAG.')/Uus', array($this, '_replace'), $html);
            if (!empty($this->_nest)) {
                $this->errors[] = 'Unclosed tags ' . implode(', ', $this->_nest);
                $text .= '</' . implode('></', array_reverse($this->_nest)) . '>';
            }
            return $text;
        }
    
        private function _replace($matches)
        {
            $tag = $matches[1];
    
            preg_match('/^<\/?(\w++)/', $tag, $m);
            $tagName = strtolower($m[1]);
            $isSelfClosed = $tag{strlen($tag) - 2} == '/';
            $attrs = trim(substr($tag, strlen($m[0]), ($isSelfClosed ? -2 : -1)));
    
            if (!in_array($tagName, $this->_goodTags)) {
                $this->errors[] = 'Tag ' . $tagName . ' is deprecated';
                return '';
            }
    
            // Closing tag
            if ($tag{1} == '/') {
                if (empty($this->_nest) || end($this->_nest) != $tagName) {
                    $this->errors[] = 'Odd close tag ' . $tagName;
                    return '<' . $tagName . '></' . $tagName . '>';
                }
                array_pop($this->_nest);
                return '</' . $tagName . '>';
            }
    
            // Open tag or self-closing tag
            $isSelfClosed = $isSelfClosed || in_array($tagName, $this->_selfClose);
    
            if (!$isSelfClosed) {
                $this->_nest[] = $tagName;
            }                        
    
            if (!isset($this->_goodAttrs[$tagName])) {
                // No attributes at all
                if (strlen($attrs)) {
                    $this->errors[] = 'Tag ' . $tagName . ' cannot have attributes';
                }
                $attrs = '';
            } else {
                // Check every attribute
                preg_match_all('/'.self::ATTR.'/Uus', $attrs, $m);
                $attrs = $m[0];
                foreach ($attrs as $i => $attr) {
                    $p = strpos($attr, '=');
                    $attrName = strtolower(trim(substr($attr, 0, $p)));
                    if (!in_array($attrName, $this->_goodAttrs[$tagName])) {
                        $this->errors[] = 'Wrong ' . $tagName . ' attribute ' . $attrName;
                        unset($attrs[$i]);
                    } else {
                        $attrs[$i] = $attrName . '=' . trim(substr($attr, $p + 1));
                    }
                }
                $attrs = count($attrs) ? (' ' . implode(' ', $attrs)) : '';
            }
        
            return '<' . $tagName . $attrs . ($isSelfClosed ? '/>' : '>');
        }
    }
    
    $t = new MyHtmlTidy();
    
    $html = <<<HTML
    <p class='blabla'>dslkldsldslsd<br>
    kjksdjsdk<a href="http://thesite.name/path" target="_new" title="ololo" onclick="javascript:doit('xxx')">djdkjdk</a>
    <img src=0.gif alt='pysh-pysh'>
    ds;lsd;; <b>skjskjsk kjdkjdkd
    HTML;
    
    header('Content-type: text/plain');
    
    echo $html;
    echo "\n===========================\n";
    
    $preparsed = $t->preparse($html);
    if (!empty($t->errors)) {
        echo implode("\n", $t->errors);
        echo "\n===========================\n";
    }
    echo $preparsed;
    


    вывод:
    
    <p class='blabla'>dslkldsldslsd<br>
    kjksdjsdk<a href="http://thesite.name/path" target="_new" title="ololo" onclick="javascript:doit('xxx')">djdkjdk</a>
    <img src=0.gif alt='pysh-pysh'>
    ds;lsd;; <b>skjskjsk kjdkjdkd
    ===========================
    Tag p cannot have attributes
    Wrong a attribute target
    Wrong a attribute onclick
    Unclosed tags p, b
    ===========================
    <p>dslkldsldslsd<br/>
    kjksdjsdk<a href="http://thesite.name/path" title="ololo">djdkjdk</a>
    <img src=0.gif alt='pysh-pysh'/>
    ds;lsd;; <b>skjskjsk kjdkjdkd</b></p>
    


    Кому нелениво, поищите дыры, пожалуйста.
    Спустя 114 сек.
    Не знаю как мне с тем же preg_replace_callback ухитриться еще переводы строк в br превращать.
    Наверное preg_split надо.
    ιιlllιlllι унц-унц
  • phpdude

    Сообщения: 26646 Репутация: N Группа: в ухо

    Spritz 21 декабря 2010 г. 12:44, спустя 1 час 10 минут 33 секунды

    суров. домой придет поищу :-)
    Сапожник без сапог
  • AndryG

    Сообщения: 237 Репутация: N Группа: Адекваты

    Spritz 27 декабря 2010 г. 14:12, спустя 6 дней 1 час 27 минут

    Использовать автомат - оно, конечно, правильней регулярки. Но автомат нужно построить почти такой же, как в браузеры юзают.
    Напрашивается идея его и юзать.

    Загрузить код в DOM (он сам закрывает теги и отбросить прочие разночтения.)
    За тем пройтись по дереву рекурсивным обходчиком … для разрешенных тегов, забирать и тег и содержимое. Для остальных - только голый текст брать.
    И код будет небольшой, и 100% никакой жук не пролезет.

    Спустя 89 сек.
    Дайте редактировать свои посты … хрен ошибки исправишь!
  • phpdude

    Сообщения: 26646 Репутация: N Группа: в ухо

    Spritz 27 декабря 2010 г. 16:15, спустя 2 часа 3 минуты 31 секунду

    а ты не ошибайся
    Сапожник без сапог

Пожалуйста, авторизуйтесь, чтобы написать комментарий!