開発環境 Microsoft Visual C# 2010 Express (SP1)
実行環境 Microsoft Windows XP Home Edition (SP3)
プロジェクトの種類 Windows フォーム アプリケーション
プロジェクト名 Jien2ch

プロジェクト


プロジェクトの保存
[ソリューションのディレクトリを作成]にチェックを付けておくと、
ソリューションに新しいプロジェクトを追加する時にプロジェクトの階層が整う。

フォーム


名前:Form1
プロパティ Text:Jien2ch Size:800, 600

メニューとツール バー/StatusStripをForm1上にドロップ。
名前:statusStrip1

statusStrip1にStatusLabelを追加。
名前:toolStripStatusLabel1
プロパティ Textをリセット(空にする)

コンテナー/SplitContainerをForm1上にドロップ。
名前:splitContainer1
プロパティ FixedPanel:Panel1

コンテナー/SplitContainerをsplitContainer1.Panel2上にドロップ。
名前:splitContainer2
プロパティ FixedPanel:Panel1 Orientation:Horizontal

コモン コントロール/TreeViewをsplitContainer1.Panel1上にドロップ。
名前:treeView1
プロパティ Dock:Fill

Jien2chプロジェクトにクラスを追加。
テンプレート:クラス
名前:ListViewEx WebBrowserEx
それぞれSystem.Windows.Forms.ListView/WebBrowserを継承した後、
ソリューションのビルド(F6)を行うとツールボックスに追加される。

Jien2ch コンポーネント/ListViewExをsplitContainer2.Panel1上にドロップ。
名前:listViewEx1
プロパティ Dock:Fill

Jien2ch コンポーネント/WebBrowserExをsplitContainer2.Panel2上にドロップ。
名前:webBrowserEx1

イベントハンドラ


treeView1にAfterSelectイベントハンドラを追加。
listViewEx1にSelectedIndexChangedイベントハンドラを追加。

クラスライブラリ


ソリューションに新しいプロジェクトを追加。
テンプレート:クラス ライブラリ
名前:Lib2ch
Class1.csは削除。
Lib2chプロジェクトに以下の新しいフォルダーを追加。

Data
IO
Model

各種クラスを追加。

+Jien2ch\
 +Form1.cs
  +Form1.Designer.cs
 +ListViewEx.cs
 +Program.cs
 +WebBrowserEx.cs
+Lib2ch\
 +Data\
  +Data.cs
 +IO\
  +IO.cs
 +Model\
  +Model.cs

Jien2chプロジェクトの参照設定にプロジェクトLib2chを追加。

仕様


板一覧
http;//menu.2ch.net/ bbsmenu.html

スレッド一覧
http://uni.2ch.net/newsplus/ subject.txt

スレッド
http://uni.2ch.net/newsplus/dat/ 1234567890.dat

+Logs\
 +bbsmenu.html
 +カテゴリ\
  +板名\
   +subject.txt
   +1234567890.dat


Jien2ch


Form1.cs
using System.Collections.Generic;
using System.Windows.Forms;
using Lib2ch.Data;
using Lib2ch.Model;
 
namespace Jien2ch
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
 
            #region 板一覧
            string status;
            List<BbsMenuItem> bbsMenuList = BbsMenu.GetMenuList(out status);
            toolStripStatusLabel1.Text = status;
 
            foreach (BbsMenuItem item in bbsMenuList)
            {
                TreeNode childNode = new TreeNode();
                childNode.Text = item.BbsName;
                childNode.Tag = item;
                // 親ノード検索
                TreeNode[] node = treeView1.Nodes.Find(item.Category, false);
                if (node.Length == 0)
                {
                    TreeNode parent = new TreeNode();
                    parent.Name = item.Category;	// 検索用
                    parent.Text = item.Category;	// 表示用
                    parent.Nodes.Add(childNode);
                    treeView1.Nodes.Add(parent);
                }
                else
                {
                    node[0].Nodes.Add(childNode);
                }
            }
            #endregion
        }
 
        #region 板一覧アイテム選択時
        private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
        {
            if (e.Node.Tag != null)
            {
                ThreadListAdd((BbsMenuItem)e.Node.Tag);
            }
        }
 
        // スレッド一覧
        void ThreadListAdd(BbsMenuItem bbsMenuItem)
        {
            string status;
            SubjectList subjectList = Subject.Get(out status, bbsMenuItem);
            toolStripStatusLabel1.Text = status;
            listViewEx1.ItemSet(subjectList);
        }
        #endregion
 
        #region スレッド一覧アイテム選択時
        private void listViewEx1_SelectedIndexChanged(object sender, System.EventArgs e)
        {
            ListViewEx listView = (ListViewEx)sender;
            if (listView.SelectedItems.Count == 0) return;
 
            SubjectData subjectData = (SubjectData)listView.SelectedItems[0].Tag;
            ThreadViewAdd(listView.bmi, subjectData);
        }
 
        void ThreadViewAdd(BbsMenuItem bmi, SubjectData subjectData)
        {
            string status;
            Dat dat = ThreadDat.Get(out status, bmi, subjectData);
            toolStripStatusLabel1.Text = status;
            webBrowserEx1.ItemSet(dat);
        }
        #endregion
    }
}
 

ListViewEx.cs
using System.Windows.Forms;
using Lib2ch.Data;
 
namespace Jien2ch
{
    class ListViewEx : ListView
    {
        public BbsMenuItem bmi;
 
        public ListViewEx()
        {
            // プロパティ
            View = View.Details;
            FullRowSelect = true;
            GridLines = true;
 
            // 列ヘッダ
            Columns.Add("!", 20);	// 1列目は右寄せできないのでダミー
            Columns.Add("番号", 40, HorizontalAlignment.Right);
            Columns.Add("タイトル", 450);
            Columns.Add("レス", 40, HorizontalAlignment.Right);
        }
 
        public void ItemSet(SubjectList subjectList)
        {
            bmi = new BbsMenuItem(subjectList);
 
            ListViewItem[] item = new ListViewItem[subjectList.List.Count];
            int num = 0;	// 番号
            foreach (SubjectData data in subjectList.List)
            {
                string[] line = new string[4];
                line[0] = "";
                line[1] = (num + 1).ToString();
                line[2] = data.Title;
                line[3] = data.Count.ToString();
                item[num] = new ListViewItem(line);
                item[num].Tag = data;
                num++;
            }
            Items.AddRange(item);
            Tag = subjectList;
        }
    }
}
 

WebBrowserEx.cs
using System.Text;
using System.Windows.Forms;
using Lib2ch.Data;
 
namespace Jien2ch
{
    class WebBrowserEx : WebBrowser
    {
        const string crlf = "\r\n";
 
        public void ItemSet(Dat dat)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<html>" + crlf);
            sb.Append("<body bgcolor=#efefef style=\"font-family: MS Pゴシック\">" + crlf);
            sb.Append("<dl>" + crlf);
            int num = 0;
            foreach (DatRes datRes in dat.Res)
            {
                num++;
                string[] s = datRes.Data;
                sb.Append("<dt><font color=\"blue\"><u>" + num.ToString() + "</u></font>");
                sb.Append(" 名前:<font color=\"green\"><b>" + s[0] + "</b></font>");
                sb.Append("[" + s[1] + "]");
                sb.Append(" 投稿日:" + s[2]);
                sb.Append("<dd>" + s[3] + "<br><br>" + crlf);
            }
            sb.Append("</dl></body></html>" + crlf);
            DocumentText = sb.ToString();
        }
    }
}
 


Lib2ch


Data.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
 
namespace Lib2ch.Data
{
    // ファイル結果
    class FileResult
    {
        public string[] Line;	// 行リスト
        public HttpStatusCode StatusCode;
        public string StatusDesc;
    }
 
    // 索引ファイル
    class IdxFile
    {
        public string LastMod;  // 最終修正時間
 
        string _Path;
 
        public IdxFile(string path)
        {
            _Path = path;
        }
 
        public bool Load()
        {
            try
            {
                using (StreamReader sr = new StreamReader(_Path))
                {
                    LastMod = sr.ReadLine();
                }
                return true;
            }
            catch
            {
                LastMod = null;
                return false;
            }
        }
 
        public void Save()
        {
            using (StreamWriter sw = new StreamWriter(_Path))
            {
                sw.WriteLine(LastMod);
            }
        }
    }
 
    // 板一覧アイテム
    public class BbsMenuItem
    {
        public string Category;	// カテゴリ
        public string BbsName;	// 板名
        public string Url;      // URL
 
        public BbsMenuItem() { }
        public BbsMenuItem(BbsMenuItem item)
        {
            Category = item.Category;
            BbsName = item.BbsName;
            Url = item.Url;
        }
 
        public string Directory
        {
            get { return Category + "/" + BbsName + "/"; }
        }
    }
 
    // スレッド一覧データ
    public class SubjectData
    {
        public string FileName;	// スレッドファイル名(X(10).dat)
        public string Title;	// タイトル
        public int Count;       // レス数
 
        // [FileName.dat]<>[Title]([Count])
        public SubjectData(string line)
        {
            if (line == "") return;
            FileName = line.Substring(0, 14);
            int start = line.LastIndexOf("(");
            int end = line.LastIndexOf(")");
            Title = line.Substring(16, start - 16);
            start++;
            Count = Convert.ToInt32(line.Substring(start, end - start));
        }
    }
 
    // スレッド一覧
    public class SubjectList : BbsMenuItem
    {
        public List<SubjectData> List = new List<SubjectData>();
 
        public SubjectList(BbsMenuItem item) : base(item) { }
 
        public void Parsing(string[] data)
        {
            foreach (string line in data)
            {
                SubjectData subjectData = new SubjectData(line);
                List.Add(subjectData);
            }
        }
    }
 
    // レス
    public class DatRes
    {
        public string[] Data;
 
        public void Res(string line)
        {
            Data = line.Split(new string[] { "<>" }, StringSplitOptions.None);
        }
    }
 
    // スレッドデータ
    public class Dat
    {
        public List<DatRes> Res = new List<DatRes>();
 
        public void Parsing(string[] data)
        {
            foreach (string line in data)
            {
                DatRes datRes = new DatRes();
                datRes.Res(line);
                Res.Add(datRes);
            }
        }
    }
}
 

IO.cs
using System.IO;
using System.Net;
using System.Text;
using Lib2ch.Data;
 
namespace Lib2ch.IO
{
    static class FileMan
    {
        static public FileResult StandardCheck(string url, string file, string dir)
        {
            FileResult fileResult = new FileResult();
 
            // ファイル名と拡張子の分離
            int index = file.LastIndexOf('.');
            string fname = file.Substring(0, index);
            string ext = file.Substring(index);
 
            // 索引ファイルの読み込み
            IdxFile idxFile = new IdxFile(dir + fname + ".idx");
            idxFile.Load();
 
            // GETメソッド
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url + fname + ext);
            if (idxFile.LastMod != null && File.Exists(dir + file))
            {
                req.IfModifiedSince = System.DateTime.Parse(idxFile.LastMod);
            }
            HttpWebResponse res = null;
            try
            {
                res = (HttpWebResponse)req.GetResponse();
                fileResult.StatusCode = res.StatusCode;
                fileResult.StatusDesc = res.StatusDescription;
 
                // ファイルのダウンロード
                idxFile.LastMod = res.LastModified.ToString();
                Stream stream = res.GetResponseStream();
                Encoding enc = Encoding.GetEncoding(res.CharacterSet);	// ISO-8859-1
                string buf;
                using (StreamReader sr = new StreamReader(stream, enc))
                {
                    buf = sr.ReadToEnd();
                }
                stream.Close();
 
                // ディレクトリがなければ作る
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
                using (StreamWriter sw = new StreamWriter(dir + file, false, enc))
                {
                    sw.Write(buf);
                }
 
                // 索引ファイルの書き込み
                idxFile.Save();
            }
            catch (WebException e)
            {
                if (e.Status == WebExceptionStatus.ProtocolError)
                {
                    // (304) Not Modified
                    fileResult.StatusCode = ((HttpWebResponse)e.Response).StatusCode;
                    fileResult.StatusDesc = ((HttpWebResponse)e.Response).StatusDescription;
                    if (fileResult.StatusCode != HttpStatusCode.NotModified)
                    {
                        return fileResult;
                    }
                }
                else
                {
                    return fileResult;
                }
            }
 
            // ローカルファイルの読み込み
            try
            {
                fileResult.Line = File.ReadAllLines(dir + file, Encoding.Default);
            }
            catch
            {
            }
 
            return fileResult;
        }
    }
}
 

Model.cs
using System.Collections.Generic;
using Lib2ch.Data;
using Lib2ch.IO;
 
namespace Lib2ch.Model
{
    // 定数
    static class Com
    {
        public const string BaseDir = "./Logs/";    // 基準ディレクトリ
    }
 
    // 板一覧
    static public class BbsMenu
    {
        const string _CateStart = "<BR><BR><B>";
        const string _CateEnd = "</B>";
        const string _BbsStart = "<A HREF=";
        const string _UrlEnd1 = " TARGET=_blank>";
        const string _UrlEnd2 = ">";
        const string _BbsEnd = "</A>";
 
        static public List<BbsMenuItem> GetMenuList(out string status)
        {
            FileResult fileResult = FileMan.StandardCheck(
                "http://menu.2ch.net/", "bbsmenu.html", Com.BaseDir);
            status = fileResult.StatusDesc;
 
            // 解析処理
            return BbsMenuParsing(fileResult.Line);
        }
 
        static List<BbsMenuItem> BbsMenuParsing(string[] data)
        {
            List<BbsMenuItem> list = new List<BbsMenuItem>();
            string category = null;
            foreach (string line in data)
            {
                // カテゴリ
                // <BR><BR><B>(Category)</B>
                int index1 = line.IndexOf(_CateStart);
                if (index1 != -1)
                {
                    int index2 = line.IndexOf(_CateEnd);
                    if (index2 != -1)
                    {
                        int start = index1 + _CateStart.Length;
                        category = line.Substring(start, index2 - start);
                    }
                }
                if (category == null) continue;
                BbsMenuItem item = BbsMenuFindLine(line, _BbsStart, _UrlEnd1, _BbsEnd);
                if (item == null)
                {
                    item = BbsMenuFindLine(line, _BbsStart, _UrlEnd2, _BbsEnd);
                }
                if (item != null)
                {
                    item.Category = category;
                    list.Add(item);
                }
            }
            return list;
        }
 
        // <A HREF=(Url)>(BbsName)</A>
        // <A HREF=(Url) TARGET=_blank>(BbsName)</A>
        static BbsMenuItem BbsMenuFindLine(string line, string word1, string word2, string word3)
        {
            int index1 = line.IndexOf(word1);
            if (index1 == -1) return null;
            int start1 = index1 + word1.Length;
 
            int index2 = line.IndexOf(word2, start1);
            if (index2 == -1) return null;
            int start2 = index2 + word2.Length;
 
            int index3 = line.IndexOf(word3, start2);
            if (index3 == -1) return null;
 
            BbsMenuItem item = new BbsMenuItem();
            item.Url = line.Substring(start1, index2 - start1);
            item.BbsName = line.Substring(start2, index3 - start2);
            return item;
        }
    }
 
    // スレッド一覧
    static public class Subject
    {
        static public SubjectList Get(out string status, BbsMenuItem bbsMenuItem)
        {
            FileResult fileResult = FileMan.StandardCheck(
                bbsMenuItem.Url, "subject.txt", Com.BaseDir + bbsMenuItem.Directory);
            status = fileResult.StatusDesc;
 
            SubjectList subjectList = new SubjectList(bbsMenuItem);
            subjectList.Parsing(fileResult.Line);
            return subjectList;
        }
    }
 
    // スレッド
    static public class ThreadDat
    {
        static public Dat Get(out string status, BbsMenuItem bmi, SubjectData subjectData)
        {
            FileResult fileResult = FileMan.StandardCheck(
                bmi.Url + "dat/", subjectData.FileName, Com.BaseDir + bmi.Directory);
            status = fileResult.StatusDesc;
 
            Dat dat = new Dat();
            dat.Parsing(fileResult.Line);
            return dat;
        }
    }
}
 
最終更新:2012年11月19日 16:46