HeyCHのブログ

慢性疲労のへいちゃんです

【C#】5ちゃんねるビューアーを作ろう Final

今回の内容は前回の内容の続きとして書いていきます。
heych.hatenablog.com

今回は「5ちゃんねるビューアーを作ろう Final」という事で、コードのまとめを書いてシリーズの完結としたいと思います。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;

namespace _5chViewer {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
            string html = "";
            //まずHTMLの取得
            HttpWebRequest req = WebRequest.CreateHttp("https://www2.5ch.net/5ch.html");
            using (var res = req.GetResponse()) {
                using (var r = res.GetResponseStream()) {
                    using (var sr = new StreamReader(r, Encoding.GetEncoding(932))) {
                        html = sr.ReadToEnd();
                    }
                }
            }
            //Treeの作成
            bool stTag = false;
            bool edTag = false;
            bool isAttr = false;
            string currentTag = null;
            string currentAttr = null;
            string tmp = null;
            Regex regex = new Regex("(?:href=\")(.*)(?:\")");
            for (int i = 0; i < html.Length; i++) {
                if (html[i] == '<') {
                    if (html[i + 1] == '/') {
                        if (tmp != null) {
                            if (currentTag == "B") {
                                TreeNode tn = new TreeNode(tmp);
                                treeView1.Nodes.Add(tn);
                            } else if (currentTag == "a" && treeView1.Nodes.Count > 0) {
                                string href = "https:" + regex.Match(currentAttr).Groups[1].Value+ "subback.html";
                                TreeNode tn = new TreeNode(tmp);
                                tn.Tag = href;
                                treeView1.Nodes[treeView1.Nodes.Count - 1].Nodes.Add(tn);
                            }
                        }
                        edTag = true;
                    } else {
                        stTag = true;
                        tmp = null;
                        currentTag = null;
                        currentAttr = null;
                    }
                } else if (html[i] == '>') {
                    if (edTag) currentTag = null;
                    stTag = false;
                    edTag = false;
                    isAttr = false;
                } else if (html[i] == ' ' && stTag) {
                    isAttr = true;
                } else {
                    if (isAttr) currentAttr += html[i];
                    else if (stTag) currentTag += html[i];
                    else if(!edTag &&(currentTag=="B" || currentTag=="a")) {
                        tmp += html[i];
                        if (tmp == "他のサイト") break;
                    }
                }
            }
        }

        private void treeView1_AfterSelect(object sender, TreeViewEventArgs e) {
            if (e.Node.Tag == null) return;
            string html = "";
            Regex regex = new Regex("(?:<a href=\")(.*)(?:\">)(.*)(?:</a>)");
            //まずHTMLの取得
            try {
                HttpWebRequest req = WebRequest.CreateHttp((string)e.Node.Tag);
                using (var res = req.GetResponse()) {
                    using (var r = res.GetResponseStream()) {
                        using (var sr = new StreamReader(r, Encoding.GetEncoding(932))) {
                            html = sr.ReadToEnd();
                        }
                    }
                }
            } catch (Exception ex) {
                MessageBox.Show("HTMLの取得に失敗しました。","エラー",MessageBoxButtons.OK,MessageBoxIcon.Error);
            }
            //正規表現でhref属性値とスレッドタイトルを取得する
            if (listView1.Columns.Count <= 0) listView1.Columns.Add("スレッドタイトル");
            listView1.Items.Clear();
            try {
                var st = html.LastIndexOf("<small id=\"trad\">");
                var ed = html.IndexOf("</small>", st);
                var ms = regex.Matches(html);
                foreach (Match m in ms) {
                    if (m.Index < st || m.Index > ed) continue;
                    var item = new ListViewItem(m.Groups[2].Value);
                    item.Tag = m.Groups[1].Value;
                    listView1.Items.Add(item);
                }
            } catch (Exception ex) {
                MessageBox.Show("HTMLの読取に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            listView1.Columns[0].Width = -1;
        }

        string html = "";
        private void listView1_SelectedIndexChanged(object sender, EventArgs e) {
            if (listView1.SelectedItems.Count <= 0) return;
            var url = (string)treeView1.SelectedNode.Tag;
            //https://hayabusa9.5ch.net/newsの形にする
            url = url.Substring(0, url.Length - ("/subback.html").Length);
            ////https://hayabusa9.5ch.net/test/read.cgi/news/の形にする
            url = url.Substring(0, url.LastIndexOf("/")) + "/test/read.cgi" + url.Substring(url.LastIndexOf("/")) + "/";
            var sub= (string)listView1.SelectedItems[0].Tag;
            sub = sub.Substring(0, sub.Length - ("150").Length);
            url = url + sub;
            //まずHTMLの取得
            try {
                HttpWebRequest req = WebRequest.CreateHttp(url);
                using (var res = req.GetResponse()) {
                    using (var r = res.GetResponseStream()) {
                        using (var sr = new StreamReader(r, Encoding.GetEncoding(932))) {
                            html = sr.ReadToEnd();
                        }
                    }
                }
            } catch (Exception ex) {
                MessageBox.Show("HTMLの取得に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            richTextBox1.Text = "";
            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += Bw_DoWork;
            bw.RunWorkerAsync();
        }

        private void Bw_DoWork(object sender, DoWorkEventArgs e) {
            Regex numberReg = new Regex("(<span class=\"number\">)(.*?)(</span>)");
            Regex nameReg = new Regex("(<span class=\"name\">)(.*?)(</span>)");
            Regex dateReg = new Regex("(<span class=\"date\">)(.*?)(</span>)");
            Regex uidReg = new Regex("(<span class=\"uid\">)(.*?)(</span>)");
            Regex escapedReg = new Regex("(<span class=\"escaped\">)(.*?)(</span>)");
            int index = 0;
            string tmp = "";
            while (true) {
                //numberの取得
                var m = numberReg.Match(html, index);
                if (!m.Success) break;
                var number = m.Groups[2].Value;
                index = m.Index;
                //nameの取得
                m = nameReg.Match(html, index);
                if (!m.Success) break;
                var name = m.Groups[2].Value;
                index = m.Index;
                //dateの取得
                m = dateReg.Match(html, index);
                if (!m.Success) break;
                var date = m.Groups[2].Value;
                index = m.Index;
                //uidの取得
                m = uidReg.Match(html, index);
                if (!m.Success) break;
                var uid = m.Groups[2].Value;
                index = m.Index;
                //escapedの取得
                m = escapedReg.Match(html, index);
                if (!m.Success) break;
                var escaped = m.Groups[2].Value;
                escaped = escaped.Replace("<br>", "\r\n");
                index = m.Index;

                //nameとescapedの調整
                name = Regex.Replace(name, "<[^>]*?>", "");
                escaped = Regex.Replace(escaped, "<[^>]*?>", "").Replace("&gt;", ">").Replace("&lt;", "<").Replace("&amp;", "&");

                tmp += number + " " + name + " " + date + " " + uid + "\r\n\r\n" + escaped + "\r\n\r\n\r\n";
            }
            this.Invoke((MethodInvoker)delegate () { richTextBox1.Text = tmp; });
        }

        private void richTextBox1_LinkClicked(object sender, LinkClickedEventArgs e) {
            System.Diagnostics.Process.Start(e.LinkText);
        }
    }
}

ポイント

  • もっといいコードの書き方があると思われる
  • 5chのHTMLはShift-JISで記述されているので、文字コードは「Encoding.GetEncoding(932)」を使用する
  • 正規表現C#正規表現として、別途覚える(調べる)必要がある
  • ListViewをListBoxのような感じで使用したい場合、ListViewのビューを「Details」にし、FullRowSelectを「True」に、HeaderStyleを「None」に、Item追加後に「listView1.Columns[0].Width = -1;」を実行する
  • しれっと追加してますが、richTextBox1に「richTextBox1_LinkClicked」イベントを追加しています