matarilloの雑記

GitHubに公開したソフトウェアなどについて書きます。

HTML Parser "dcsoup" を公開しました。

Javaで書かれたHTML Parser、“jsoup” .NET (C#) に移植中です。なんとなく動く感じなのでとりあえず公開しました。

https://github.com/matarillo/dcsoup

これは何?

HTMLパーサーです。こんな感じで使います。

using System;
using System.Globalization;
using System.Net.Http;
using Supremes;

class Program
{
    public static void Main(string[] args)
    {
        // 株価を取得したいサイトのURL
        var code = "7984.T";
        var urlstring = string.Format("http://stocks.finance.yahoo.co.jp/stocks/detail/?code={0}", code);

        using (var client = new HttpClient())
        {
            // 指定したサイトのHTMLを取得する
            var message = client.GetAsync(urlstring).Result;
            // dcsoupでパースする
            var document = message.Parse();
            // jQueryライクなセレクタで株価部分を取得する
            var priceNode = document.Select("td[class=stoksPrice]")[0];
            // 取得した株価がstring型なのでint型にパースする
            var price = int.Parse(priceNode.Text, NumberStyles.AllowThousands);
            Console.WriteLine("コクヨ(7984.T)の株価: {0}円", price);
        }
    }
}

(酢酸さんインスパイア)

あ、何やってるかが分かりにくいですね。using Supremes;System.Net.Http.HttpResponseMessageクラスに対してParse()拡張メソッドが有効になってます。

文字列、ファイル、ストリームなどからパースするときは Supremes.Dcsoup 静的クラスを使うとよいです。

HTML Agility Packと何が違うの?

jQueryライクなセレクタ」のとこが、XPathよりもっとjQueryライクです。

using System;
using System.Net.Http;
using Supremes;

class Program
{
    public static void Main(string[] args)
    {
        using (var client = new HttpClient())
        {
            var doc = client.GetAsync("http://ja.wikipedia.org/").Result.Parse();
            var featuredArticles = doc.Select("#mf-tfa b a");
            Console.WriteLine(featuredArticles.Text);
        }
    }
}

パーサーはHTMLの構造を知っています。たとえば次のようなHTMLをパースすると……

using System;
using Supremes;

class Program
{
    public static void Main(string[] args)
    {
        var doc = Dcsoup.Parse("<table> <tr> <td>ABC <td>DEF <td>GHI </tr></table>");
        Console.WriteLine(doc);
    }
}

出力はこうなります。

<html>
 <head></head>
 <body>
  <table>

   <tbody>
    <tr>

     <td>ABC </td>
     <td>DEF </td>
     <td>GHI </td>
    </tr>
   </tbody>
  </table>
 </body>
</html>

さらに、jQueryライクにパース結果のDOMを変更できます。上のコードに1行追加してみましょう。

using System;
using Supremes;

class Program
{
    public static void Main(string[] args)
    {
        var doc = Dcsoup.Parse("<table> <tr> <td>ABC <td>DEF <td>GHI </tr></table>");
        doc.Select("td").AddClass("foo bar"); // クラスを追加
        Console.WriteLine(doc);
    }
}

その結果、出力はこうなりました。

<html>
 <head></head>
 <body>
  <table>

   <tbody>
    <tr>

     <td class=" foo bar">ABC </td>
     <td class=" foo bar">DEF </td>
     <td class=" foo bar">GHI </td>
    </tr>
   </tbody>
  </table>
 </body>
</html>

既知の問題

  • テストコードがまだコンバートされていません。 テストコードをコンバート中です。 テストのコンバートが終わってバグ修正もしました。テストに通ります。
  • 元のjsoupにはないバグが多く含まれています。すぐ例外を吐いて死にます。 目立つバグは潰したので、わりとちゃんと動くと思います。ただし、元のjsoupとはAPIレベルでの差異や、細かい挙動の違いはあります。いずれGitHubにまとめます。
  • ライブラリに含まれるクラスのプロパティが、Javaっぽい部分と、jQueryっぽい部分と、.NETっぽい部分がぐちゃぐちゃになってます。いずれきちんとした指針を立てた上で整理したいのですが。 .NETのプロパティに基本的に寄せました。あとで記事を書きます。
  • まだNuGetで配布してません。 テストがまともに通るようになったら考えます。 テストに通るようになったので着手します。 できました。