seraphyの日記

日記というよりは過去を振り返るときのための単なる備忘録

HTMLParserを使ってみる(CyberNekoHTML)*1

用意するもの

URLを指定してHTMLを読み込みドキュメントツリーを見るテスト

package jp.seraphyware.htmlparsertest;

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

import org.cyberneko.html.parsers.DOMParser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

public class NekoHTMLTest {

    public static void main(final String[] args) throws Exception {
        final URL url = new URL("http://java.sun.com/");
        final URLConnection urlConnection = url.openConnection();
        final DOMParser parser = new DOMParser();
        final InputStream is = urlConnection.getInputStream();
        try {
            parser.parse(new InputSource(is));
        }
        finally {
            is.close();
        }
        final Document doc = parser.getDocument();
        final Element root = doc.getDocumentElement();
        walkTree("", root);
    }
    
    private static void walkTree(final String level,
            final Element elm) throws Exception {
        System.out.println(level + "<" + elm.getTagName() + ">");
        final NodeList children = elm.getChildNodes();
        if (children != null) {
            final int len = children.getLength();
            for (int idx = 0; idx < len; idx++) {
                final Node child = (Node) children.item(idx);
                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    walkTree(level + " ", (Element) child);
                }
                else if (child.getNodeType() == Node.TEXT_NODE) {
                    final String txt = child.getNodeValue();
                    if (txt.trim().length() > 0) {
                        System.out.println(level + txt);
                    }
                }
            }
        }
    }
}

所感

NekoHTMLXMLノードとしてHTMLを扱えるため、XML関連ツールの恩恵をそのまま受けられるところが利点っぽい。
しかし、なぜか、「&nbsp;」のようなエスケープされた文字を読み込むことができないようだ。ただの「?」になる。使い方の問題だとは思うが、ちょっと、この挙動はよく分からない。
「yahoo.co.jp」などのEUC-JPで書かれたページも文字化けすることなく読まれたので、エンコードに関して何かしなければならない、ということはなさそう。

HTMLParserを使ってみる(HTMLParser)*2

用意するもの

URLを指定してHTMLを読み込みドキュメントツリーを見るテスト

package jp.seraphyware.htmlparsertest;

import java.net.URL;
import java.net.URLConnection;

import org.htmlparser.Node;
import org.htmlparser.Parser;
import org.htmlparser.Tag;
import org.htmlparser.Text;
import org.htmlparser.util.NodeList;

public class HTMLParserTest {

    public static void main(final String[] args) throws Exception {
        final URL url = new URL("http://java.sun.com/");
        final URLConnection urlConnection = url.openConnection();
        
        final Parser parser = new Parser(urlConnection);
        final NodeList lst = parser.parse(null);
        walkTree("", lst);
    }
    
    private static void walkTree(final String level,
            final NodeList lst) throws Exception {
        if (lst == null) {
            return;
        }
        final int cnt = lst.size();
        for (int idx = 0; idx < cnt; idx++) {
            final Node node = lst.elementAt(idx);
            if (node instanceof Tag) {
                final String tagName = ((Tag) node).getTagName();
                System.out.println(level + "<" + tagName + ">");
                walkTree(level + " ", node.getChildren());
            }
            else if (node instanceof Text) {
                final String txt = node.getText();
                if (txt.trim().length() > 0) {
                    System.out.println(level + txt);
                }
            }
        }
    }
    
}

所感

簡単だしコンパクトにかけそうだが、JAXP互換ではないし、そもそもメソッド名、クラス名も違うので、このあたりはJAVADOCをみながらアタリをつけてゆかなければならないぽい。
使った感じでは特に難しい点はなさそう。
NekoHTMLよりもいいかも。

MSHTMLを使ってHTMLを解析してみる*3

URLを指定してHTMLを読み込みドキュメントツリーを見るテスト

#include "stdafx.h"
#include <mshtml.h>

class CMSHTMLParserTest
    : public CAtlExeModuleT<CMSHTMLParserTest>
{
public:
    HRESULT PreMessageLoop(int nShowCmd) throw()
    {
        HRESULT hr = CAtlExeModuleT<CMSHTMLParserTest>::PreMessageLoop(nShowCmd);
        if (FAILED(hr)) {
            return hr;
        }

        hr = pHTMLDocument_.CoCreateInstance(__uuidof(HTMLDocument));
        ATLASSERT(SUCCEEDED(hr));
        return hr;
    }

    void RunMessageLoop( ) throw()
    {
        HRESULT hr;

        CComPtr<IMoniker> pMoniker;
        hr = CreateURLMoniker(NULL,
            L"http://www.microsoft.com/japan/msdn/",
            &pMoniker);
        ATLASSERT(SUCCEEDED(hr));

        CComPtr<IBindCtx> pBindCtx;
        hr = CreateBindCtx(0, &pBindCtx);
        ATLASSERT(SUCCEEDED(hr));

        CComQIPtr<IPersistMoniker> pPersistMoniker(pHTMLDocument_);
        hr = pPersistMoniker->Load(FALSE, pMoniker, pBindCtx, STGM_READ);
        ATLASSERT(SUCCEEDED(hr));

        hr = WaitForComplite();
        ATLASSERT(SUCCEEDED(hr));

        CComQIPtr<IHTMLDocument3> pDoc(pHTMLDocument_);

        CComPtr<IHTMLElement> pDocElement;
        hr = pDoc->get_documentElement(&pDocElement);
        ATLASSERT(SUCCEEDED(hr));

        hr = WalkTree(pDocElement);
        ATLASSERT(SUCCEEDED(hr));
    }

private:
    HRESULT WalkTree(IHTMLElement* pElm) throw()
    {
        HRESULT hr;
        try {
            CComBSTR tagName;
            hr = pElm->get_tagName(&tagName);
            ATLASSERT(SUCCEEDED(hr));

            OutputDebugStringW(tagName);
            OutputDebugStringW(L"\n");

            CComPtr<IDispatch> pChildrenDisp;
            hr = pElm->get_children(&pChildrenDisp);
            ATLASSERT(SUCCEEDED(hr));
            CComQIPtr<IHTMLElementCollection> pChildren(pChildrenDisp);

            long cnt;
            hr = pChildren->get_length(&cnt);
            ATLASSERT(SUCCEEDED(hr));

            for (long idx = 0; idx < cnt; idx++) {
                CComVariant name(idx);
                CComVariant dmy;
                CComPtr<IDispatch> pChildDisp;
                hr = pChildren->item(name, dmy, &pChildDisp);
                ATLASSERT(SUCCEEDED(hr));
                CComQIPtr<IHTMLElement> pChild(pChildDisp);
                hr = WalkTree(pChild);
                if (FAILED(hr)) {
                    break;
                }
            }
        }
        catch (...) {
            return E_FAIL;
        }
        return hr;
    }

    HRESULT WaitForComplite() throw()
    {
        try {
            HRESULT hr = E_FAIL;
            int loop = 0;
            for (;;) {
                CComBSTR state;
                hr = pHTMLDocument_->get_readyState(&state);
                ATLASSERT(SUCCEEDED(hr));
                if (FAILED(hr)) {
                    break;
                }
                OutputDebugStringW(state);
                OutputDebugStringW(L"\n");

                if (state != L"uninitialized" && state != L"loading") {
                    hr = S_OK;
                }
                else {
                    if (loop < 1000) {
                        Sleep(50);
                        continue;
                    }
                }
                break;
            }
            return hr;
        }
        catch (...) {
            return E_FAIL;
        }
    }

private:
    CComPtr<IHTMLDocument2> pHTMLDocument_;
};

CMSHTMLParserTest _atlModule;

int _tmain(int argc, _TCHAR* argv[])
{
    return _atlModule.WinMain(SW_SHOWNORMAL);
}

所感

上記コードに自信はないが、とりあえず、動いた。その程度のレベル。

モニカの仕組みが分かっていないのだが、CreateURLMonikerでURLを指定することはできても、CreateFileMonikerでファイルを指定しても正しく読んでくれなかったのだ。
どうやら、IPersistMonikerではなく、IPersistFileを使ってロードしなければダメらしい。
なんで、そんなことになっているのかはわからない。

また非同期的にHTMLを読み込んでくれるので、完了したかどうかをチェックしなければならないのは、単純なシングルスレッドなバッチツールのような用途では、めんどくさいような気がする。(もちろん、コールバックを登録できるようであるが。)
それに、ステートを文字列で比較する以外に方法はないのだろうか?

しかし、この方法を使えばWebBrowserコントロールなどを使わずとも、GUIなしでHTMLを読ませて解析することができるので、覚えておくと重宝するのかもしれない。

使い方は奥が深いというか、調べないと分からないことは多すぎる気がする。