SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

Microsoft C#でのHTML構文解析

HTMLパーサの作成方法と利用例

  • このエントリーをはてなブックマークに追加

Web上のデータのほとんどはHTML形式で保存されていますが、.NET FrameworkにはHTMLの構文解析を簡単に行うための方法がありません。そこで本稿では、HTMLパーサの作成方法を示し、それを読者のアプリケーションでどう利用できるかを説明します。

  • このエントリーをはてなブックマークに追加

はじめに

 Web上のデータのほとんどはHTML形式で保存されています。そのため、C#アプリケーションでHTMLの構文解析ができたらと思うことも多いでしょうが、.NET FrameworkにはHTMLの構文解析を簡単に行うための方法がありません。その証拠に、どこのフォーラムでも、HTML構文の簡単な解析方法を知らないかというC#プログラマの質問をよく見かけます。

 Microsoft .NET Frameworkでは、XMLが強力にサポートされています。XMLとHTMLは外見こそよく似ていますが、あまり互換性はありません。XMLとHTMLの大きな違いは次のとおりです。

  • XMLには終了タグが必要である
  • すべてのXML属性値は、一重引用符か二重引用符で完全に囲む必要がある
  • XMLタグは正確にネストさせる必要がある
  • XMLタグ名では大文字/小文字を区別する
  • XMLでは属性を重複して指定できない
  • XMLでは空の属性を指定できない

 実際のコードでこれらの違いを確認しておきましょう。XMLでは、すべての開始タグに対して終了タグが必要になります。次のHTMLをXMLパーサで解析すると、問題が起こります。

<p>This is line 1<br>
This is line 2</p>

 これは多くの違いのうちのほんの1つにすぎません。もちろん、XMLとの互換性を重視した書き方もできます。たとえば、上のHTMLは次のようにも書けるでしょう。

<p>This is line 1<br/>
This is line 2</p>

 これを理解できないブラウザはありませんし、XMLパーサもこれなら理解できます。しかし、HTMLソースの書き方を解析側が制御することはできないので、これは有効な解決策にはなりません。必要なのは、どのソースからのHTMLでも処理できるプログラムです。そこで、このような条件を満たすHTMLパーサを自分で作成することにしました。以降では、このHTMLパーサの作成方法を示し、それを読者のアプリケーションでどう利用できるかを説明します。

HTMLパーサの作成

 まず、HTMLパーサを構成する主コンポーネントを示し、最後に簡単な例を通じてその使い方をお見せしま す。今回のHTMLパーサは次の4つのクラスからできています。

  • Attribute――HTMLタグの個々の属性を格納するクラスです。
  • AttributeList――個々のHTMLタグとその全属性を格納するクラスです。
  • Parse――テキスト解析の汎用ルーチンを含んでいるクラスです。
  • ParseHTML――インターフェイスとして使われるメインクラスです。解析したいテキストをこのParseHTMLクラスに渡します。

 では、各クラスの機能とその使い方を見ていきましょう。まずはAttributeクラスです。

Attributeクラス

 Attributeクラスは、個々のHTML属性を格納するのに使用されます。Attributeクラスのソースコードをリスト1に示します。

リスト1 Attributeクラス
using System;

namespace HTML
{
  /// <summary>
  /// Attribute holds one attribute, as is normally stored in an
  /// HTML or XML file. This includes a name, value and delimiter.
  /// This source code may be used freely under the
  /// Limited GNU Public License(LGPL).
  ///
  /// Written by Jeff Heaton (http://www.jeffheaton.com)
  /// </summary>
  public class Attribute: ICloneable
  {
    /// <summary>
    /// The name of this attribute
    /// </summary>
    private string m_name;

    /// <summary>
    /// The value of this attribute
    /// </summary>
    private string m_value;

    /// <summary>
    /// The delimiter for the value of this attribute(i.e. " or ').
    /// </summary>
    private char m_delim;

    /// <summary>
    /// Construct a new Attribute.  The name, delim, and value
    /// properties can be specified here.
    /// </summary>
    /// <param name="name">The name of this attribute.</param>
    /// <param name="value">The value of this attribute.</param>
    /// <param name="delim">The delimiter character for the value.
    /// </param>
    public Attribute(string name,string value,char delim)
    {
      m_name  = name;
      m_value = value;
      m_delim = delim;
    }


    /// <summary>
    /// The default constructor.  Construct a blank attribute.
    /// </summary>
    public Attribute():this("","",(char)0)
    {
    }

    /// <summary>
    /// Construct an attribute without a delimiter.
    /// </summary>
    /// <param name="name">The name of this attribute.</param>
    /// <param name="value">The value of this attribute.</param>
    public Attribute(String name,String value):this(name,value,
                                                   (char)0)
    {
    }

    /// <summary>
    /// The delimiter for this attribute.
    /// </summary>
    public char Delim
    {
      get
      {
        return m_delim;
      }

      set
      {
        m_delim = value;
      }
    }

    /// <summary>
    /// The name for this attribute.
    /// </summary>
    public string Name
    {
      get
      {
        return m_name;
      }

      set
      {
        m_name = value;
      }
    }

    /// <summary>
    /// The value for this attribute.
    /// </summary>
    public string Value
    {
      get
      {
        return m_value;
      }

      set
      {
        m_value = value;
      }
    }

    #region ICloneable Members
    public virtual object Clone()
    {
      return new Attribute(m_name,m_value,m_delim);
    }
    #endregion
  }
}

 HTMLタグの属性の例を次に示します。

<img src="picture.gif" alt="Some Picture">

 このHTMLタグにはsrcaltという2つの属性が含まれています。属性値はそれぞれ「picture.gif」と「Some Picture」です。

 Attributeクラスは、namevaluedelimという3つのプロパティから構成されています。nameプロパティは属性の名前を格納し、valueプロパティは属性の持つべき値を格納します。delimプロパティは、値の区切りとして用いる文字を表すプロパティで、値の区切りに何を用いるかに応じて、引用符(")またはアポストロフィ(')を格納します(区切り文字を使用しない場合は何も格納しません)。

AttributeListクラス

 1つのHTMLタグが複数の属性を含むことも珍しくありません。そのようなとき、属性のリストを格納する目的で用いられるのがAttributeListクラスです。AttributeListクラスのソースコードをリスト2に示します。

リスト2 AttributeListクラス
using System;
using System.Collections;

namespace HTML
{
  /// <summary>
  /// The AttributeList class is used to store list of
  /// Attribute classes.
  /// This source code may be used freely under the
  /// Limited GNU Public License(LGPL).
  ///
  /// Written by Jeff Heaton (http://www.jeffheaton.com)
  /// </summary>
  ///
  public class AttributeList:Attribute
  {
    /// <summary>
    /// An internally used Vector.  This vector contains
    /// the entire list of attributes.
    /// </summary>
    protected ArrayList m_list;
    /// <summary>
    /// Make an exact copy of this object using the cloneable
    /// interface.
    /// </summary>
    /// <returns>A new object that is a clone of the specified
    /// object.</returns>
    public override Object Clone()
    {
      AttributeList rtn = new AttributeList();

      for ( int i=0;i<m_list.Count;i++ )
        rtn.Add( (Attribute)this[i].Clone() );

      return rtn;
    }

    /// <summary>
    /// Create a new, empty, attribute list.
    /// </summary>
    public AttributeList():base("","")
    {
      m_list = new ArrayList();
    }


    /// <summary>
    /// Add the specified attribute to the list of attributes.
    /// </summary>
    /// <param name="a">An attribute to add to this
    /// AttributeList.</paramv
    public void Add(Attribute a)
    {
      m_list.Add(a);
    }


    /// <summary>
    /// Clear all attributes from this AttributeList and return
    /// it to a empty state.
    /// </summary>
    public void Clear()
    {
      m_list.Clear();
    }

    /// <summary>
    /// Returns true of this AttributeList is empty, with no
    /// attributes.
    /// </summary>
    /// <returns>True if this AttributeList is empty, false
    /// otherwise.</returns>
    public bool IsEmpty()
    {
      return( m_list.Count<=0);
    }

    /// <summary>
    /// If there is already an attribute with the specified name,
    /// it will have its value changed to match the specified
    /// value. If there is no Attribute with the specified name,
    /// one will be created. This method is case-insensitive.
    /// </summary>
    /// <param name="name">The name of the Attribute to edit or
    /// create.  Case-insensitive.</param>
    /// <param name="value">The value to be held in this
    /// attribute.</param>
    public void Set(string name,string value)
    {
      if ( name==null )
        return;
      if ( value==null )
        value="";

      Attribute a = this[name];

      if ( a==null )

      {
        a = new Attribute(name,value);
        Add(a);
      }

      else
        a.Value = value;
    }

    /// <summary>
    /// How many attributes are in this AttributeList?
    /// </summary>
    public int Count
    {
      get
      {
        return m_list.Count;
      }
    }

    /// <summary>
    /// A list of the attributes in this AttributeList
    /// </summary>
    public ArrayList List
    {
      get
      {
        return m_list;
      }
    }

    /// <summary>
    /// Access the individual attributes
    /// </summary>
    public Attribute this[int index]
    {
      get
      {
        if ( index<m_list.Count )
          return(Attribute)m_list[index];
        else
          return null;
      }
    }

    /// <summary>
    /// Access the individual attributes by name.
    /// </summary>
    public Attribute this[string index]
    {
      get
      {
        int i=0;

        while ( this[i]!=null )
        {
          if ( this[i].Name.ToLower().Equals( (index.ToLower()) ))
            return this[i];
          i++;
        }

        return null;

      }
    }
  }
}

 AttributeListクラスは、1つの名前といくつかの属性の集まりから構成されます。AttributeListnameプロパティに格納されている名前は、タグの名前を表します。パーサから返されるタグは、AttributeListオブジェクトの形をとります。

 AttributeListクラスは、C#のインデックスを利用します。個々の属性へのアクセスには、数値インデックスと文字列インデックスの両方を使用できます。たとえば、theTagというAttributeListオブジェクトにsrc属性が含まれている場合、そのsrc属性にアクセスするには次の2種類の方法があります。

theTag[0]    // assuming "src" were the first attribute
theTag["src"]

 どちらの方法を使っても、タグの属性にアクセスできます。

ParseクラスとParseHTMLクラス

 HTMLの構文解析をするだけなら、Parseクラスは忘れてかまいません。ParseクラスはHTMLパーサの内部で使用され、属性/値ベースのファイル(HTML、SGML、XML、さらにはHTTPヘッダも含む)に対する低レベルのサポートを提供します。Parseクラスのソースコードをリスト3に示します。

リスト3 Parseクラス
using System;

namespace HTML
{
  /// <summary>
  /// Base class for parsing tag based files, such as HTML,
  /// HTTP headers, or XML.
  ///
  /// This source code may be used freely under the
  /// Limited GNU Public License(LGPL).
  ///
  /// Written by Jeff Heaton (http://www.jeffheaton.com)
  /// </summary>
  public class Parse:AttributeList
  {
    /// <summary>
    /// The source text that is being parsed.
    /// </summary>
    private string m_source;

    /// <summary>
    /// The current position inside of the text that
    /// is being parsed.
    /// </summary>
    private int m_idx;

    /// <summary>
    /// The most recently parsed attribute delimiter.
    /// </summary>
    private char m_parseDelim;

    /// <summary>
    /// This most recently parsed attribute name.
    /// </summary>
    private string m_parseName;

    /// <summary>
    /// The most recently parsed attribute value.
    /// </summary>
    private string m_parseValue;

    /// <summary>
    /// The most recently parsed tag.
    /// </summary>
    public string m_tag;

    /// <summary>
    /// Determine if the specified character is whitespace or not.
    /// </summary>
    /// <param name="ch">A character to check</param>
    /// <returns>true if the character is whitespace</returns>
    public static bool IsWhiteSpace(char ch)
    {
      return( "\t\n\r ".IndexOf(ch) != -1 );
    }


    /// <summary>
    /// Advance the index until past any whitespace.
    /// </summary>
    public void EatWhiteSpace()
    {
      while ( !Eof() )
      {
        if ( !IsWhiteSpace(GetCurrentChar()) )
          return;
        m_idx++;
      }
    }

    /// <summary>
    /// Determine if the end of the source text has been reached.
    /// </summary>
    /// <returns>True if the end of the source text has been
    /// reached.</returns>
    public bool Eof()
    {
      return(m_idx>=m_source.Length );
    }

    /// <summary>
    /// Parse the attribute name.
    /// </summary>
    public void ParseAttributeName()
    {
      EatWhiteSpace();
      // get attribute name
      while ( !Eof() )
      {
        if ( IsWhiteSpace(GetCurrentChar()) ||
          (GetCurrentChar()=='=') ||
          (GetCurrentChar()=='>') )
          break;
        m_parseName+=GetCurrentChar();
        m_idx++;
      }

      EatWhiteSpace();
    }


    /// <summary>
    /// Parse the attribute value
    /// </summary>
    public void ParseAttributeValue()
    {
      if ( m_parseDelim!=0 )
        return;

      if ( GetCurrentChar()=='=' )
      {
        m_idx++;
        EatWhiteSpace();
        if ( (GetCurrentChar()=='\'') ||
          (GetCurrentChar()=='\"') ) 
        {
          m_parseDelim = GetCurrentChar();
          m_idx++;
          while ( GetCurrentChar()!=m_parseDelim )
          {
            m_parseValue+=GetCurrentChar();
            m_idx++;
          }
          m_idx++;
        }
        else
        {
          while ( !Eof() &&
            !IsWhiteSpace(GetCurrentChar()) &&
            (GetCurrentChar()!='>') )

          {
            m_parseValue+=GetCurrentChar();
            m_idx++;
          }
        }
        EatWhiteSpace();
      }
    }

    /// <summary>
    /// Add a parsed attribute to the collection.
    /// </summary>
    public void AddAttribute()
    {
      Attribute a = new Attribute(m_parseName,
        m_parseValue,m_parseDelim);
      Add(a);
    }


    /// <summary>
    /// Get the current character that is being parsed.
    /// </summary>
    /// <returns></returns>
    public char GetCurrentChar()

    {

      return GetCurrentChar(0);

    }



    /// <summary>
    /// Get a few characters ahead of the current character.
    /// </summary>
    /// <param name="peek">How many characters to peek ahead
    /// for.</param>
    /// <returns>The character that was retrieved.</returns>
    public char GetCurrentChar(int peek)

    {
      if( (m_idx+peek)<m_source.Length )
        return m_source[m_idx+peek];
      else
        return (char)0;
    }



    /// <summary>
    /// Obtain the next character and advance the index by one.
    /// </summary>
    /// <returns>The next character</returns>
    public char AdvanceCurrentChar()

    {
      return m_source[m_idx++];
    }



    /// <summary>
    /// Move the index forward by one.
    /// </summary>
    public void Advance()
    {
      m_idx++;
    }


    /// <summary>
    /// The last attribute name that was encountered.
    /// <summary>
    public string ParseName
    {
      get
      {
        return m_parseName;
      }

      set
      {
        m_parseName = value;
      }
    }

    /// <summary>
    /// The last attribute value that was encountered.
    /// <summary>
    public string ParseValue
    {
      get
      {
        return m_parseValue;
      }

      set
      {
        m_parseValue = value;
      }
    }

    /// <summary>
    /// The last attribute delimeter that was encountered.
    /// <summary>
    public char ParseDelim
    {
      get
      {
        return m_parseDelim;
      }

      set
      {
        m_parseDelim = value;
      }
    }

    /// <summary>
    /// The text that is to be parsed.
    /// <summary>
    public string Source
    {
      get
      {
        return m_source;
      }

      set
      {
        m_source = value;
      }
    }
  }
}

 この記事ではParseクラスの詳しい説明を省きますが、コードリストではすべてのメソッドにコメントを付けてあるので、興味のある方はそちらをご覧ください。

 ParseHTMLクラスはParseクラスのサブクラスであり、パーサがHTMLを扱う上で必要となるHTML固有のコードを含んでいます。ParseHTMLクラスのソースコードをリスト4に示します。

リスト4 ParseHTMLクラス
using System;

namespace HTML
{
  /// <summary>
  /// Summary description for ParseHTML.
  /// </summary>

  public class ParseHTML:Parse
  {
    public AttributeList GetTag()
    {
      AttributeList tag = new AttributeList();
      tag.Name = m_tag;

      foreach(Attribute x in List)
      {
        tag.Add((Attribute)x.Clone());
      }

      return tag;
    }

    public String BuildTag()
    {
      String buffer="<";
      buffer+=m_tag;
      int i=0;
      while ( this[i]!=null )

      {// has attributes
        buffer+=" ";
        if ( this[i].Value == null )
        {
          if ( this[i].Delim!=0 )
            buffer+=this[i].Delim;
          buffer+=this[i].Name;
          if ( this[i].Delim!=0 )
            buffer+=this[i].Delim;
        }
        else
        {
          buffer+=this[i].Name;
          if ( this[i].Value!=null )
          {
            buffer+="=";
            if ( this[i].Delim!=0 )
              buffer+=this[i].Delim;
            buffer+=this[i].Value;
            if ( this[i].Delim!=0 )
              buffer+=this[i].Delim;
          }
        }
        i++;
      }
      buffer+=">";
      return buffer;
    }

    protected void ParseTag()
    {
      m_tag="";
      Clear();

      // Is it a comment?
      if ( (GetCurrentChar()=='!') &&
        (GetCurrentChar(1)=='-')&&
        (GetCurrentChar(2)=='-') )
      {
        while ( !Eof() )
        {
          if ( (GetCurrentChar()=='-') &&
            (GetCurrentChar(1)=='-')&&
            (GetCurrentChar(2)=='>') )
            break;
          if ( GetCurrentChar()!='\r' )
            m_tag+=GetCurrentChar();
          Advance();
        }
        m_tag+="--";
        Advance();
        Advance();
        Advance();
        ParseDelim = (char)0;
        return;
      }

      // Find the tag name
      while ( !Eof() )
      {
        if ( IsWhiteSpace(GetCurrentChar()) ||
                         (GetCurrentChar()=='>') )
          break;
        m_tag+=GetCurrentChar();
        Advance();
      }

      EatWhiteSpace();

      // Get the attributes
      while ( GetCurrentChar()!='>' )
      {
        ParseName  = "";
        ParseValue = "";
        ParseDelim = (char)0;

        ParseAttributeName();

        if ( GetCurrentChar()=='>' ) 

        {
          AddAttribute();
          break;
        }

        // Get the value(if any)
        ParseAttributeValue();
        AddAttribute();
      }
      Advance();
    }


    public char Parse()
    {
      if( GetCurrentChar()=='<' )
      {
        Advance();

        char ch=char.ToUpper(GetCurrentChar());
        if ( (ch>='A') && (ch<='Z') || (ch=='!') || (ch=='/') ) 
        {
          ParseTag();
          return (char)0;
        }

        else return(AdvanceCurrentChar());
      }
      else return(AdvanceCurrentChar());
    }
  }
}

 HTMLパーサとユーザーを結ぶメインインターフェイスとなるのがこのParseHTMLクラスですが、HTMLパーサの使い方は次項で取り上げることにします。主に使われるメソッドは次の2つです。

public char Parse()
public AttributeList GetTag()

 Parse()メソッドは、呼び出されると、解析中のHTMLファイルから次の1文字を取り出し、その文字がタグの一部だと判明した場合は、値としてゼロを返します。したがって、Parse()がゼロを返してきたときは、HTMLタグの処理が必要です。そのタグにアクセスするにはGetTag()メソッドを呼び出します。GetTag()メソッドはArrayListオブジェクトを返します。このオブジェクトに、処理対象のタグとその全属性が含まれています。

このHTMLパーサの使い方

 では、このHTMLパーサの使い方の一例を紹介しましょう。今回のサンプルプログラムはユーザーにURLの指定を求め、指定されたURLのHTMLファイル内部にあるすべてのハイパーリンクを表示します。このサンプルでは、HTMLデータを指すURLしか使用できないことに注意してください。画像やその他のバイナリデータはうまく扱えません。この例のソースコードをリスト5に示します。

リスト5 FindLinksクラス
using System;
using System.Net;
using System.IO;


namespace HTML
{
  /// <summary>
  /// FindLinks is a class that will test the HTML parser.
  /// This short example will prompt for a URL and then
  /// scan that URL for links.
  /// This source code may be used freely under the
  /// Limited GNU Public License(LGPL).
  ///
  /// Written by Jeff Heaton (http://www.jeffheaton.com)
  /// </summary>
  class FindLinks
  {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
      System.Console.Write("Enter a URL address:");
      string url = System.Console.ReadLine();
      System.Console.WriteLine("Scanning hyperlinks at: " + url );
      string page = GetPage(url);
      if(page==null)
      {
        System.Console.WriteLine("Can't process that type of file,"
                                  +
                                  "please specify an HTML file URL."
                                  );
        return;
      }

      ParseHTML parse = new ParseHTML();
      parse.Source = page;
      while( !parse.Eof() )
      {
        char ch = parse.Parse();
        if(ch==0)
        {
          AttributeList tag = parse.GetTag();
          if( tag["href"]!=null )
            System.Console.WriteLine( "Found link: " +
                                       tag["href"].Value );
        }
      }
    }


    public static string GetPage(string url)
    {
      WebResponse response = null;
      Stream stream = null;
      StreamReader
        reader = null;

      try
      {
        HttpWebRequest request =
                       (HttpWebRequest)WebRequest.Create(url);

        response = request.GetResponse();
        stream = response.GetResponseStream();

        if( !response.ContentType.ToLower().StartsWith("text/") )
          return null;

        string buffer = "",line = "";

        reader = new StreamReader(stream);

        while( (line = reader.ReadLine())!=null )
        {
          buffer+=line+"\r\n";
        }

        return buffer;
      }
      catch(WebException e)
      {
        System.Console.WriteLine("Can't download:" + e);
        return null;
      }
      catch(IOException e)
      {
        System.Console.WriteLine("Can't download:" + e);
        return null;
      }
      finally
      {
        if( reader!=null )
          reader.Close();

        if( stream!=null )
          stream.Close();

        if( response!=null )
          response.Close();
      }
    }
  }
}

 サンプルプログラムの動作を見るには、何らかのURLアドレスを入れてください。例えば、「http://www.developer.com」と入力すれば、Developer.comのホームページに含まれているすべてのハイパーリンクが表示されます。

 ページの処理を行うループは次のとおりです。

ParseHTML parse = new ParseHTML();
parse.Source = page;
while( !parse.Eof() )
{
  char ch = parse.Parse();
  if(ch==0)
  {
    AttributeList tag = parse.GetTag();
    if( tag["href"]!=null )
      System.Console.WriteLine( 
        "Found link: " + tag["href"].Value );
  }
}

 ParseHTMLオブジェクトがインスタンス化され、このオブジェクトのSourceプロパティに、解析すべきHTMLページが設定されます。ページの終わりに達するまで、このループが繰り返されます。ここでは、通常文字は無視し、タグだけを探しています(ch変数がゼロのときは、現在の文字がタグの一部であることを表します)。検出されたタグごとに、HREF属性があるかどうかを調べ、HREF属性がある場合は、そのリンクを表示します。

まとめ

 ご覧いただいたとおり、これらのクラスはHTML構文解析の枠組みとしてたいへん使いやすく、どのようなMicrosoft .NETアプリケーションでも利用できます。今回のサンプルプログラムでは、リンクを表示する目的にしかこのパーサを使用していませんが、私自身は複雑なHTML解析アプリケーションにもこのパーサを使用しています。

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Jeff Heaton(Jeff Heaton)

ライター、人工知能(AI)研究者、元大学教員。AI、仮想世界、スパイダー、ボットなどの話題を取り上げて執筆した書籍は10冊以上。Java、.Net、Silverlightを対象に、高度なニューラルネットワークおよびAIフレームワークの提供を目的とするオープンソースイニシアチブ、Encogプロジェクトを統括している。また、個人のWebサイトを管理し、人工知能とスパイダー/ボットプログラミングをはじめとする話題について情報発信を行っている。メールの宛先はjheaton@heatonresearch.com。Sun認定Javaプログラマ兼IEEEシニアメンバー。ミズーリ州セントルイスのワシントン大学情報管理修士号を持ち、セントルイス在住。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/145 2005/12/06 17:55

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング