Модераторы: LSD, AntonSaburov
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> HTML Parser HTMLEditorKit, даем бой неправильному HTML 
:(
    Опции темы
Platon
Дата 20.9.2008, 13:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1801
Регистрация: 25.4.2006

Репутация: 16
Всего: 40



Здравствуйте, уважаемые.

Java HTML Parser: Введение

Очередное утро - очередная задача - очередная порция новой информации - очередной FAQ.
На этот раз взбрело мне в голову ковыряться в содержимом html документа не с помощью RegExp, хотя возможно он был бы менее ресурсозатратным(?), а с помощью более подходящего для этих целей инструмента HTMLEditorKit
Сразу оговорю, чем HTMLEditorKit выгодно отличается, например, от того же SAXParser, он может работать с неправильным html кодом, т.е. скрадывает ошибки, допущенные верстальщиком при написании html кода.

HTMLEditorKit  сравнил с SAXParser не спроста. Дело в том, что оба этих инструмента используют похожую модель обработки данных. Так, что если вы знакомы с SAXParser, то вам не составит труда познать таинства HTMLEditorKit.

HTMLEditorKit поставляется с jdk5 и используется для разбора html кода в компоненте JEditorPane

HTMLEditorKit работает со стандартным DTD (HTML докуметами), но также может принять и пользовательский DTD, тем самым становится мощным и гибким инструментом для работы с частично неправильными xml документами

Уверенным программистам рекомендую почитать информацию, предоставленную Sun Swing HTML Parser

Java HTML Parser: Задача.

Из содержимого html документа извлечь все ссылки(часто встречаемая в быту).

Java HTML Parser: Теоретическая часть.

Сердцем всего HTML парсера является класс HTMLEditorKit.ParserCallback окинем взглядом его методы:
Код

    public void flush() throws BadLocationException
    public void handleText(char[] data, int pos)
    public void handleComment(char[] data, int pos)
    public void handleStartTag(HTML.Tag t, 
                               MutableAttributeSet a, int pos)
    public void handleEndTag(HTML.Tag t, int pos)
    public void handleSimpleTag(HTML.Tag t, 
                                MutableAttributeSet a, int pos)
    public void handleError(String errorMsg, int pos)
    public void handleEndOfLineString(String eol)


flush() - как и обычный потоковый flush(), этот метод сигнализирует нам, что парсинг HTML документа закончен и необходимо завершить все вычисления, слить все промежуточные данные.
handleText(char[] data, int pos) - обработка текста <a href="...">текст</a> <p>текст</p> и т.д. data содержит текст, pos позиция в документе
handleComment(char[] data, int pos) - обработка встречаемых комментариев. data содержит текст, pos позиция в документе
handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) - обработка открытия тега, пример <a href="test.html" rel="nofollow"> t - тег (в нашем примере HTML.Tag.A), a содержит набор атрибутов (в нашем случае HTML.Attribute.HREF и HTML.Attribute.REL)
handleEndTag(HTML.Tag t, int pos) - сигнализатор закрытия тега
handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) - обработка тега, не предусматривающего закрытие (одиночки), пример <img src="img.png" border="0" />, t - тег (в нашем примере HTML.Tag.IMG), a содержит набор атрибутов (в нашем случае HTML.Attribute.SRC и HTML.Attribute.BORDER)
handleError(String errorMsg, int pos) - обрабатывается ошибка, пример "end.missing div ? ?", pos - позиция в документе, где определена ошибка
handleEndOfLineString(String eol) - обработка новой строки в документе.

Есть пара моментов для уточнения:
1. handleText и handleEndOfLineString. handleText получает в качестве параметра символьный массив, в котором последовательность так называемых Whitespace символов заменяется на 1 пробельный символ. handleEndOfLineString поможет нам обработать перенос строки, склеив с текстом, а также поможет вести статистику строка/позиция, а не позиция.
2. handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos). Интересен тут следующий факт: обычно в передаваемом объекте MutableAttributeSet содержатся ссылки на объекты HTML.Attribute, такие как HTML.Attribute.HREF, HTML.Attribute.SRC, ... если в html документе встречаются недокументированные атрибуты, они передаются в MutableAttributeSet как строки, т.е. <a href="test.html" myattr="Доктор Хаус уже на экранах!">, в данном случае MutableAttributeSet будет содержать 2 атрибута HTTP.Attribute.HREF@4564(href) => "test.html" и String@4565(myattr) => "Доктор Хаус уже на экранах!"

Java HTML Parser: Основная часть.


Засекаем секундомеры.
LinkCollector.java
Код

package ru.vingrad.platon.htmlparser;

import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.parser.ParserDelegator;
import javax.swing.text.BadLocationException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;

public class LinkCollector extends HTMLEditorKit.ParserCallback {

    private Link currentLink;

    private LinkObjectStream objectStream;


    public LinkCollector(LinkObjectStream objectStream) {
        this.objectStream = objectStream;
    }

    public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
        if (HTML.Tag.A == t)
            currentLink = new Link("", (String)a.getAttribute(HTML.Attribute.HREF), (String)a.getAttribute(HTML.Attribute.ALT),
                    (String)a.getAttribute(HTML.Attribute.TITLE), isNoFollow(a.getAttribute(HTML.Attribute.REL)));
    }

    private static boolean isNoFollow(Object attribute) {
        return ("nofollow".equals(attribute));
    }


    public void handleEndTag(HTML.Tag t, int pos) {
        if (HTML.Tag.A == t) {
            objectStream.write(currentLink);
            currentLink = null;
        }
    }

    public void handleText(char[] data, int pos) {
        if (currentLink != null)
            currentLink.text += new String(data);
    }

    public void flush() throws BadLocationException {
        if (currentLink != null) {
            objectStream.write(currentLink);
            currentLink = null;
        }
    }

    public static void main(String[] args) throws IOException {
        new ParserDelegator().parse(new InputStreamReader(new URL("http://vingrad.ru").openStream()), new LinkCollector(new ConsoleStream()), true);
    }
}

LinkObjectStream.java
Код

package ru.vingrad.platon.htmlparser;

public interface LinkObjectStream {
    void write(Link currentLink);
}

Link.java
Код

package ru.vingrad.platon.htmlparser;

public class Link {
    public String text;
    public String path;
    public String alt;
    public String title;
    public boolean noFollow;


    public Link(String text, String path, String alt, String title, boolean noFollow) {
        this.text = text;
        this.path = path;
        this.alt = alt;
        this.title = title;
        this.noFollow = noFollow;
    }


    public Link() {
    }
}


ConsoleStream.java
Код

package ru.vingrad.platon.htmlparser;

public class ConsoleStream implements LinkObjectStream {

    public void write(Link currentLink) {
        System.out.println("Link: " + currentLink.path + "; description: " + currentLink.text);
    }
}


LinkCollector - сердце нашей программы, в мы определяем поведение и состояние сборки ссылок.
3 метода делают всю работу:

Код

    public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
        if (HTML.Tag.A == t)
            currentLink = new Link("", (String)a.getAttribute(HTML.Attribute.HREF), (String)a.getAttribute(HTML.Attribute.ALT),
                    (String)a.getAttribute(HTML.Attribute.TITLE), isNoFollow(a.getAttribute(HTML.Attribute.REL)));
    }

    public void handleEndTag(HTML.Tag t, int pos) {
        if (HTML.Tag.A == t) {
            objectStream.write(currentLink);
            currentLink = null;
        }
    }

    public void handleText(char[] data, int pos) {
        if (currentLink != null)
            currentLink.text += new String(data);
    }

Сначала мы создаем новую ссылку(Link), устанавливаем доступные нам атрибуты, за исключением текста, он пока остается пустым. затем, если будет текст внутри ссылки(в html коде), то вызывается метод обработки текста handleText и присваивается к тексту объекта текущей ссылки(Link). Как только мы получаем команду закрытия тега (а она будет, даже если тег в HTML тексте не прописан), мы сливаем объект ссылки (Link) высокоуровневому обработчику LinkObjectStream, или его конкретной реализации ConsoleStream.

Результат работы программы над винградовской главной страницей:

Цитата

Link: /index.php; description: 
Link: /reg.php; description: Регистрация
Link: ; description: Восстановить пароль
Link: /index.php; description: ГЛАВНАЯ
Link: /shop.php; description: МАГАЗИН
Link: http://forum.vingrad.ru; description: ФОРУМ ПРОГРАММИСТОВ
Link: http://base.vingrad.ru; description: БАЗА ЗНАНИЙ
Link: /list.php?section=15; description: Менеджер проектов (8)
Link: /list.php?section=16; description: Менеджер по продажам (2)
Link: /list.php?section=17; description: Арт-директор (2)
Link: /list.php?section=18; description: Менеджер по персоналу (1)
Link: /list.php?section=19; description: Менеджмент проектов (8)
Link: /list.php?section=20; description: Сайт "под ключ" (17)
Link: /list.php?section=21; description: Копирайтинг (3)
Link: /list.php?section=22; description: Дизайн сайтов (10)
Link: /list.php?section=23; description: Верстка (14)
Link: /list.php?section=24; description: Веб-программирование (27)
Link: /list.php?section=25; description: Системы администрирования (CMS) (11)
Link: /list.php?section=26; description: QA (тестирование) (1)
Link: /list.php?section=27; description: Wap/PDA-сайты (1)
Link: /list.php?section=28; description: Флеш-сайты (4)
Link: /list.php?section=29; description: Логотипы (3)
Link: /list.php?section=30; description: Фирменный стиль (2)
Link: /list.php?section=31; description: Баннеры (2)
Link: /list.php?section=32; description: Рисунки и иллюстрации (6)
Link: /list.php?section=33; description: Технический дизайн (2)
Link: /list.php?section=34; description: Иконки (4)
Link: /list.php?section=35; description: Анимация (4)
Link: /list.php?section=36; description: Полиграфия (1)
Link: /list.php?section=37; description: Дизайн упаковки (1)
Link: /list.php?section=38; description: Наружная реклама (2)
Link: /list.php?section=39; description: Дизайн выставочных стендов (0)
Link: /list.php?section=40; description: Презентации (2)
Link: /list.php?section=41; description: Дизайн интерьеров (0)
Link: /list.php?section=42; description: Ландшафтный дизайн (0)
Link: /list.php?section=43; description: Интерфейсы (0)
Link: /list.php?section=44; description: Промышленный дизайн (0)
Link: /list.php?section=45; description: Разработка шрифтов (0)
Link: /list.php?section=46; description: Пиксел-арт (0)
Link: /list.php?section=47; description: Комиксы (1)
Link: /list.php?section=48; description: Чертежи (0)
Link: /list.php?section=49; description: Полиграфическая верстка (1)
Link: /list.php?section=50; description: 2D Персонажи (2)
Link: /list.php?section=51; description: Векторная графика (4)
Link: /list.php?section=52; description: Предпечатная подготовка (0)
Link: /list.php?section=53; description: Архитектура/Инжиниринг (1)
Link: /list.php?section=54; description: Флеш-сайты (3)
Link: /list.php?section=55; description: Флеш-графика (5)
Link: /list.php?section=56; description: Анимация (4)
Link: /list.php?section=57; description: Flash/Flex-программирование (5)
Link: /list.php?section=58; description: Баннеры (3)
Link: /list.php?section=59; description: Веб-программирование (27)
Link: /list.php?section=60; description: Базы данных (32)
Link: /list.php?section=61; description: Прикладное программирование (44)
Link: /list.php?section=62; description: Системное программирование (21)
Link: /list.php?section=63; description: 1С-программирование (2)
<...>


Программа для определения позиций сайта SESpider

Это сообщение отредактировал(а) Platon - 19.2.2009, 19:10
PM MAIL ICQ   Вверх
Platon
Дата 23.9.2008, 14:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1801
Регистрация: 25.4.2006

Репутация: 16
Всего: 40



Также часто встречаемое решение по сбору ссылок в документе:

Код

import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

public class FullDocumentParser {
    static HTMLDocument doc = null;
    public static void main(String[] args) throws IOException {

        HTMLEditorKit kit = new HTMLEditorKit();
        URL ura = new URL("http://vingrad.ru");

        try {
            InputStream in = ura.openStream();
            doc = (HTMLDocument) kit.createDefaultDocument();
            doc.putProperty("IgnoreCharsetDirective",Boolean.TRUE);
            kit.read(in, doc, 0);
        } catch (BadLocationException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        //Получение атрибутов тега <A>
        HTMLDocument.Iterator it = doc.getIterator(HTML.Tag.A);
        while (it.isValid()) {
            AttributeSet attrs = it.getAttributes();
            Object linkAttr = attrs.getAttribute(HTML.Attribute.HREF);
            System.out.println(linkAttr);
            it.next();
        }
    }
}


Но этот подход не может обрабатывать ссылки "на лету", т.е. документ придется загрузить полностью + этот подход генерирует всё дерево тегов в документе, что отразится на увеличении потребляемой памяти.

Это сообщение отредактировал(а) Platon - 25.9.2008, 07:33
PM MAIL ICQ   Вверх
unkis
Дата 23.9.2008, 19:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 802
Регистрация: 8.9.2004

Репутация: нет
Всего: 1



Есть один вопрос, что по вашему мнению будет работать быстрее, парсинье страниц с помощью RegEx или же с помощью HTMLEditorKit?
PM MAIL WWW   Вверх
Platon
Дата 24.9.2008, 15:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1801
Регистрация: 25.4.2006

Репутация: 16
Всего: 40



Цитата(Platon @  20.9.2008,  14:11 Найти цитируемый пост)
RegExp, хотя возможно он был бы менее ресурсозатратным(?)

Я об этом упомянул, честно сказать не проверял.

Но тут дело даже в другом - представьте себе такую схему: <body><a href="">test me</body>, просто напросто забыл закрыть тег a, в итоге, ваше регулярное выражение аля <a.*?href="(.*?)".*?>(.+?)</a> уже не сможет учесть эту ссылку. А HTMLEditorKit как раз-таки сможет.

Проверил быстродействие, да HTMLEditorKit проигрывает по скорости в 2-3 раза. Его средний замер 350, средний замер регулярного выражения 125.



Это сообщение отредактировал(а) Platon - 24.9.2008, 15:26
PM MAIL ICQ   Вверх
fotig
Дата 25.9.2008, 04:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 5
Регистрация: 3.9.2008

Репутация: нет
Всего: нет



LinkCollector.java в процессе компиляции выдает сообщение:

--------------------Configuration: <Default>--------------------
C:\LinkCollector.java:45: cannot find symbol
symbol  : class BadLocationException
location: class ru.vingrad.platon.htmlparser.LinkCollector
    public void flush() throws BadLocationException {
                               ^
1 error

Process completed.



Это сообщение отредактировал(а) fotig - 25.9.2008, 04:39
PM MAIL   Вверх
nerezus
Дата 25.9.2008, 08:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Вселенский отказник
****


Профиль
Группа: Участник
Сообщений: 3330
Регистрация: 15.6.2005

Репутация: нет
Всего: 43



Цитата

Есть один вопрос, что по вашему мнению будет работать быстрее, парсинье страниц с помощью RegEx или же с помощью HTMLEditorKit?
 Алгоритм на State Machine(типа указанного здесь). Т.к. он будет работать, а решение на регекспах не будет. Следовательно он быстрее, ибо нен с кем соревноваться в скорости  ;)


--------------------
Сообщество художников Artsociety.ru
PM MAIL WWW   Вверх
LSD
Дата 25.9.2008, 12:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Leprechaun Software Developer
****


Профиль
Группа: Модератор
Сообщений: 15718
Регистрация: 24.3.2004
Где: Dublin

Репутация: 210
Всего: 538



Цитата(fotig @  25.9.2008,  05:17 Найти цитируемый пост)
LinkCollector.java в процессе компиляции выдает сообщение:

Какая версия JDK используется? Чтобы скомпилировать этот код нужна 1.5 и старше.


--------------------
Disclaimer: this post contains explicit depictions of personal opinion. So, if it sounds sarcastic, don't take it seriously. If it sounds dangerous, do not try this at home or at all. And if it offends you, just don't read it.
PM MAIL WWW   Вверх
fotig
Дата 25.9.2008, 13:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 5
Регистрация: 3.9.2008

Репутация: нет
Всего: нет



Цитата(LSD @  25.9.2008,  12:07 Найти цитируемый пост)
Какая версия JDK используется? Чтобы скомпилировать этот код нужна 1.5 и старше. 


компиляция делается на JCreator 4 c JDK 1.6.0_07

PM MAIL   Вверх
Platon
Дата 25.9.2008, 14:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1801
Регистрация: 25.4.2006

Репутация: 16
Всего: 40



LSD, уважаемый, ну что ж вы пособничаете. Я писал письмо, чтоб все посты fotig в отдельную тему отцепить :(
PM MAIL ICQ   Вверх
LSD
Дата 25.9.2008, 16:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Leprechaun Software Developer
****


Профиль
Группа: Модератор
Сообщений: 15718
Регистрация: 24.3.2004
Где: Dublin

Репутация: 210
Всего: 538



Цитата(Platon @ 25.9.2008,  15:01)
LSD, уважаемый, ну что ж вы пособничаете. Я писал письмо, чтоб все посты fotig в отдельную тему отцепить :(

Ну прям чуствую себя преступником smile

Для того обсуждение статьи и сделано, чтобы ее можно было улучшить или исправить ошибки в ней. В данном случае в статье действительно есть ошибка, в классе LinkCollector нехватает импорта для javax.swing.text.BadLocationException.


--------------------
Disclaimer: this post contains explicit depictions of personal opinion. So, if it sounds sarcastic, don't take it seriously. If it sounds dangerous, do not try this at home or at all. And if it offends you, just don't read it.
PM MAIL WWW   Вверх
armixx
Дата 6.9.2010, 10:43 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 33
Регистрация: 16.8.2010

Репутация: нет
Всего: нет



Всем добрый день.
Уважаемый Platon.
А можно ли с помощью описанных выше методов решить следующую проблему:
JEditorPane при выводе HTML-документа не выделяет ссылки. Так же в режиме 
setEditable(false) отсутствует клавиатурная навигация.
Я бы мог делать это сам, если бы знал соответствие начальную и конечную CaretPosition расположения текста ссылок в JEditorPane.
Подозреваю, что решение где-то рядом, но моего опыта общения с JEditorPane, увы, пока не хватает.
Или эту проблему можно решить как-то иначе?
Документ в JEditorPane я загружаю так:
       
        ByteArrayInputStream bas=new ByteArrayInputStream(br);
        ep.read(bas,new HTMLDocument());
        ew.setEditable(false);
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Java"
LSD   AntonSaburov
powerOn   tux
javastic
  • Прежде, чем задать вопрос, прочтите это!
  • Книги по Java собираются здесь.
  • Документация и ресурсы по Java находятся здесь.
  • Используйте теги [code=java][/code] для подсветки кода. Используйтe чекбокс "транслит", если у Вас нет русских шрифтов.
  • Помечайте свой вопрос как решённый, если на него получен ответ. Ссылка "Пометить как решённый" находится над первым постом.
  • Действия модераторов можно обсудить здесь.
  • FAQ раздела лежит здесь.

Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux, javastic.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Java: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1339 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.