C#でHTMLパーサを作ろうとする

ちょっと必要になったので、[ここ](https://codezine.jp/article/detail/145)の記事を参考にしました。
随分簡易的な物になってしまいました。
***投稿用に改めて修正したのですが、目的の情報([ここ](http://www.jikokuhyo.co.jp/search/detail/line_is/kanto_takasaki)から遅延情報の取得)が取れた事以外確認してないです。その為、しっかりとした検証が出来てないです。参考程度にしてください***

#ちょっと説明
+ pythonのHTMLparserクラスのような使い勝手の物を目指しました。
+ タグ、属性、データそれぞれが読みこまれた時に各々のメソッドが呼ばれます。
+ 属性値はディクショナリで渡されます。
+ コメント、空要素はタグとして認識しないようにしています。

#コード
```csharp:paser.cs
class parser
{
	private string html_data;
	private int data_index = 0;
	private int reverse_index = 0;
	private string html_tag;
	private string attr_name;
	private string tag_attr;
	private string main_data;
	private char delim;
	private Dictionary<string, string> attribute_list = new Dictionary<string, string>();
	private string[] empty_tag_list = new [] {"area","base","basefont","br","col","frame","hr","img","input","isindex","link","meta","param"};

	//value reset
	public void reset()
	{
		html_data = "";
		data_index = 0;
		reverse_index = 0;
		html_tag = "";
		attr_name = "";
		tag_attr = "";
		main_data = "";
	}

	//check empty tag
	public bool empty_tag()
	{
		bool tag_flg = true;
		foreach (string n in empty_tag_list) 
		{
			if (n.ToLower() == html_tag) 
			{
				tag_flg = false;
			}
		}
		return tag_flg;
	}

	//check end tag
	public void end_tag_check()
	{
		string e_tag = "";
		int tmp_index = data_index;
		data_index++;
		if (html_data [data_index] == '/') 
		{
			data_index++;
			blank_remove ();
			while ((html_data [data_index] != '>') && (html_data [data_index] != ' ') && (!check_eof ())) 
			{
				e_tag += html_data [data_index];
				data_index++;
			}
		}

		//reverse index
		data_index = tmp_index;

		if (e_tag != html_tag) 
		{
			main_data += html_data [data_index];
			data_index++;
		}
	}


	//check end of file
	public bool check_eof()
	{
		if (data_index >= (html_data.Length - 1))
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	//remove blank
	public void blank_remove()
	{
		while ((html_data[data_index] == ' ') && (!check_eof()))
		{
			data_index++;
		}

	}

	//check tag 
	public void check()
	{

		while ((html_data[data_index] != '<') && (!check_eof()))
		{
			data_index++;
			if ((html_data.Length - data_index) > 3)
			{
				if ((html_data[data_index] == '<')
					&& (html_data[data_index + 1] == '!')
					&& (html_data[data_index + 2] == '-')
					&& (html_data[data_index + 3] == '-'))
				{
					while ((html_data[data_index] != '-')
						&& (html_data[data_index + 1] != '-')
						&& (html_data[data_index + 2] != '>'))
					{
						data_index++;
					}
					data_index += 3;
				}
			}

		}

		//end tag check
		if ((html_data.Length - data_index) > 1) {
			if (html_data[data_index + 1] == '/' || (html_data[data_index + 1] == '!'))
			{
				while ((html_data[data_index] != '>') && (!check_eof()))
				{
					data_index++;
				}
				data_index++;
			}
		}
	}

	//get tag
	public void get_tag()
	{
		while ((html_data[data_index] != '>') && (html_data[data_index] != ' ') && (!check_eof()))
		{
			html_tag += html_data[data_index];
			data_index++;
		}
                //html_tag = html_tag.ToLower(); こっちの方がいいかもしれない
		find_tag(html_tag);
	}

	//get attribute
	public void get_attribute()
	{
		while( (html_data[data_index] != '>') && (html_data[data_index] != '/') && (!check_eof() ) )
        {
		while ((html_data[data_index] != '=') && (!check_eof()))
		{
			attr_name += html_data[data_index];
			data_index++;
		}

		data_index++;
		delim = html_data[data_index];
		data_index++;

		while ((html_data[data_index] != delim) && (!check_eof()))
		{
			tag_attr += html_data[data_index];
			data_index++;
		}
		attribute_list.Add(attr_name, tag_attr);
		attr_name = "";
		tag_attr = "";
			data_index++;
		blank_remove();
		}
		find_attribute(html_tag, attribute_list);
	}

	//get data
	public void get_data()
	{
		while ((html_data[data_index] != '<') &&(html_data[data_index+1] != '/') && (!check_eof()))
		{
			main_data += html_data[data_index];
			data_index++;

			if ((html_data[data_index] == '<'))
			{
				end_tag_check();
			}

		}
		find_data(html_tag, attribute_list, main_data);
	}

    //find tag
    public virtual void find_tag(string tag)
    {
    }

    //find atrribute
    public virtual void find_attribute(string tag, Dictionary<string, string> attr_list)
    {
    }

    //find data
    public virtual void find_data(string tag, Dictionary<string, string> attr_list, string data)
    {
    }

	public void parse()
	{
		while (!check_eof())
		{
			check();
			if (!check_eof())
			{
				if ((html_data[data_index] == '<') && (html_data[data_index+1] != '/'))
				{
					data_index++;
					blank_remove();
					get_tag();
					blank_remove();
					if (html_data[data_index] != '>' && empty_tag())
					{
						get_attribute();
						blank_remove();
					}
					if (html_data[data_index] == '>')
					{
						data_index++;
						blank_remove();
						if (html_data[data_index] != '<')
						{
							reverse_index = data_index;
							get_data();
							data_index = reverse_index;
						}
					}
					if(html_data[data_index] == '/')
					{
						data_index++;
					}
				}
				else if (html_data[data_index] == '>'  && empty_tag())
				{
					data_index++;
					reverse_index = data_index;
					get_data();
					data_index = reverse_index;
				}
			}
			attribute_list.Clear();
			html_tag = "";
			main_data = "";
		}
	}


	public string paga_data
	{
		get
		{
			return html_data;
		}
		set
		{
			reset();
			html_data = value;
		}
	}
}
```

#実装方法
下記をオーバーライドしてください。

```csharp:paser.cs
//～～～～～～～～～～
//find tag method
public virtual void find_tag(string tag)
{
}

//find atrribute 
public virtual void find_attribute(string tag, Dictionary<string, string> attr_list)
{
}

//find data
public virtual void find_data(string tag, Dictionary<string, string> attr_list, string data)
{
}
//～～～～～～～～～～
```

[ここ](http://www.jikokuhyo.co.jp/search/detail/line_is/kanto_takasaki)から遅延情報を取り出す例です。
呼び出し元でこんな感じに実装します。

```csharp:main.cs

//～～～～～～～～～～

    //使いたいところで下記の"parse();"を呼び出す
    public void parse()
    {
        Dictionary<string, string> url_list = new Dictionary<string, string>();
        string page;
        parser1 ps = new parser1();
        page = GetPage("http://www.jikokuhyo.co.jp/search/detail/line_is/kanto_takasaki");
        ps.paga_data = page;
        ps.parse();
    }

    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();
        }
    }

//～～～～～～～～～～

class parser1 : parser
{
	private bool state;

	private string comment;

	public parser1()
	{
		state = true;
	}

	public bool status
	{
		get
		{
			return this.state;
		}
		set
		{
			this.state = value;
		}

	}

	public string m_comment
	{
		set
		{
			this.comment = value;
		}
		get
		{
			return this.comment;
		}
	}

	//find tag
	public override void find_tag(string tag)
	{
	}

	//find atrribute
	public override void find_attribute(string tag, Dictionary<string, string> attr_list)
	{
	}

	//find data
	public override void find_data(string tag, Dictionary<string, string> attr_list, string data)
	{
		if (tag == "div")
		{
			foreach (KeyValuePair<string, string> sPair in attr_list)
			{
				if (sPair.Key == "class" && sPair.Value == "corner_block_row_detail_d")
				{
					if ( (data != "\r\n現在、平常通り運転しています。") && (data !="\r\n情報提供時間は4：00～翌2：00となっています。") )
					{
						//なにかする
						m_comment = data;
						status = false;

					}
					else
					{
					}
				}
			}

		}
	}
}
//～～～～～～～～～～
```

後々役に立つと思ったけど、今のところ遅延情報の取得以外に使ってないです。