12.3.3 구현

RSS 브라우저에서는 EHGlobal 클래스를 정의하여 프로그램의 데이터를 관리하도록 합시다. EHGlobal 클래스는 IDisposable 인터페이스를 구현 약속하고 단일체 클래스로 정의할게요.

internal class EHGlobal
{
    static EHGlobal eg = null;
    internal static EHGlobal EG
    {
        get
        {
            return eg;
        }
    }
    EHGlobal()
    {
    }
}

그리고 EHGlobal 개체는 RSS 시드 주소를 키로 하고 RSSFavorite 개체를 값으로 하는 사전 개체를 두어 즐겨찾기에 등록한 RSS 시드를 관리합시다.

Dictionary<string, RSSFavorite> rdic = new Dictionary<string, RSSFavorite>();

또한 RSS 시드를 즐겨찾기에 등록할 때 아이템 목록도 사전 개체에 등록합시다.

Dictionary<string, List<Item>> ridic = new Dictionary<string, List<Item>>();

마찬가지로 RSS 시드의 아이템 목록을 형태소 분석한 정보도 사전 개체를 이용하여 등록합시다. 여기에서는 RSS 시드 주소를 키로 합니다. 값으로 들어갈 것은 링크 주소를 키로하고 분석한 SubItem 개체를 값으로하는 사전 개체입니다.

Dictionary<string, Dictionary<string,SubItem>> risdic =
            new Dictionary<string, Dictionary<string,SubItem>>();

또한 역 파일 정보도 사이트 주소를 키로 하는 사전 개체에 보관합시다. 값으로 사용할 것은 형태소 이름을 키로 하고 InvElem 개체를 값으로 하는 사전 개체입니다.

Dictionary<string, Dictionary<string, InvElem>> invdic =
             new Dictionary<string, Dictionary<string, InvElem>>();

프로그램의 메인 창을 닫을 때 EHGlobal 개체를 직렬화하기로 하게 합시다.

[Serializable]
internal class EHGlobal
{
}

직렬화하는 메서드 이름은 Save로 정의하기로 할게요.

internal void Save()
{
    BinaryFormatter bf = new BinaryFormatter();
    FileStream fs = File.Create("demo.dat");
    bf.Serialize(fs, eg);
    fs.Close();
}

또한 정적 생성자에서 EHGlobal 개체를 역직렬화합시다. 물론 역직렬화를 하기 위한 원본 파일이 없을 때는 빈 EHGlobal 개체를 생성합니다.

static EHGlobal()
{
    if (File.Exists("demo.dat"))
    {
        BinaryFormatter bf = new BinaryFormatter();
        FileStream fs = File.OpenRead("demo.dat");
        eg = bf.Deserialize(fs) as EHGlobal;
        fs.Close();
    }
    else
    {
        eg = new EHGlobal();
    }
}

MainForm에서는 EHGlobal 단일체 개체를 쉽게 사용할 수 있게 속성을 추가할게요.

EHGlobal EG
{
    get
    {
        return EHGlobal.EG;
    }
}

MainForm의 Load 이벤트 핸들러에서는 EHGlobal 개체에 있는 즐겨찾기 목록을 얻어와서 목록 컬렉션에 추가합니다.

private void MainForm_Load(object sender, EventArgs e)
{
    spcon3.SplitterDistance = 10;
    List<string> flist = EG.GetFavoriteList();
    foreach (string flink in flist)
    {
        clbox_fav.Items.Add(flink);
    }
}

검색 버튼의 클릭 이벤트를 추가합시다. 검색 버튼을 클릭하면 입력한 사이트 주소로 탐색합니다.

private void btn_nav_Click(object sender, EventArgs e)
{
    string site = tbox_addr.Text;
    if (site.StartsWith("http://") == false)
    {
        site = "http://" + site;
    }
    wb.Navigate(site);
}

그리고 웹 브라우저 컨트롤의 문서 완료 이벤트 핸들러를 추가하여 웹 브라우저 컨트롤의 주소로 주소 입력 컨트롤의 Text 속성을 설정합니다.

private void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    tbox_addr.Text = wb.Url.ToString();
}

그리고 웹 브라우저의 탐색 완료 이벤트 핸들러에서는 문서 내용 중에 RSS 시드가 있는지 확인하는 작업을 수행해야 합니다. 그리고 웹 문서가 Frame 구조일 때는 각 프레임에 있는 창의 문서에도 RSS 시드가 있는지 확인하는 작업을 수행합니다. 문서 내용에 RSS 시드가 있는지 확인하는 작업은 ParsingDocument 이름의 메서드로 작성해서 사용합시다.

private void wb_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
    HtmlDocument hdoc = wb.Document;
    string title = hdoc.Title;
    if (title.Length > 10)
    {
        title = title.Substring(0, 7)+"...";
    }
    tp_web.Text = title;
    ParsingDocument(hdoc);
    foreach (HtmlWindow hw in hdoc.Window.Frames)
    {
        try
        {
            ParsingDocument(hw.Document);
        }
        catch
        {
        }
    }
}

문서 내용에 RSS 시드가 있는지 분석하는 ParsingDocument를 구현합시다. 문서의 내용에서 링크 컬렉션 속성인 Links의 각 목록에 관하여 href 속성을 얻어와 /rss/를 포함하고 있고 http://로 시작하고 있으면 이를 발견한 RSS 시드 목록을 보여주는 콤보 박스 컨트롤의 항목에 추가합니다.

private void ParsingDocument(HtmlDocument hdoc)
{
    foreach (HtmlElement he in hdoc.Links)
    {
        string str = he.GetAttribute("href");
        if (str.Contains("/rss/") && (str.IndexOf("http://") == 0))
        {
            if (clbox_rsslink.Items.Contains(str) == false)
            {
                clbox_rsslink.Items.Add(str);
            }
        }
    }
}

RSS 탭 페이지의 RSS 목록에서 원하는 항목을 즐겨찾기에 추가하거나 항목에서 삭제하기 위해 RSS 목록을 체크박스 컨트롤로 배치하였습니다. 또한 btn_sel_alllink(전체 선택) 버튼을 제공하여 전체를 선택할 수 있는 기능을 제공합시다.

private void btn_sel_alllink_Click(object sender, EventArgs e)
{
    int cnt = clbox_rsslink.Items.Count;
    for(int i = 0; i<cnt;i++)
    {
        clbox_rsslink.SetItemChecked(i, true);
    }
}

btn_add_rsslink (즐겨찾기에 RSS 시드 추가)버튼을 클릭했을 때 이벤트 핸들러를 추가하세요.

private void btn_add_rsslink_Click(object sender, EventArgs e)

RSS 목록에서 체크 상태인 항목이 이미 즐겨 찾기에 추가하고 RSS 목록에서는 항목을 제거합니다. 반복문에서 각 항목을 순차적으로 이와 같은 작업을 수행하는데 만약 삭제하고 나면 다음 인덱스로 이동하지 않아야 하므로 이를 제어하는 부분을 신경써야 합니다.

int cnt = clbox_rsslink.Items.Count;
for (int i = 0; i < cnt; i++)
{
    if (clbox_rsslink.GetItemChecked(i))
    {
        AddFavorite(clbox_rsslink.Items[i] as string);
        clbox_rsslink.Items.RemoveAt(i);
        i--;
        cnt = clbox_rsslink.Items.Count;
    }
}

즐겨 찾기에 추가하는 부분은 EHGlobal 단일개체를 이용합니다. 만약 추가했으면 즐겨찾기 항목을 보여주는 체크박스 항목에도 추가합니다.

private void AddFavorite(string rsslink)
{
    if (EG.AddFavorite(rsslink))
    {
        clbox_fav.Items.Add(rsslink);
    }
}

EHGlobal 클래스에 즐겨찾기 항목을 추가하는 AddFavorite 메서드를 추가합니다.

internal bool AddFavorite(string rsslink)

여기에서는 즐겨찾기를 보관하는 사전 개체에 이미 있는지 확인합니다. 만약 있다면 거짓을 반환합니다.

if (rdic.ContainsKey(rsslink))
{
    return false;
}

그렇지 않다면 RSS 시드 페이지의 항목을 보관할 컬렉션을 생성한 후에 RSSFavorite 클래스의 정적 메서드 MakeRSSFavorite를 이용하여 RSSFavorite 개체를 만들고 이 과정에서 목록을 생성한 컬렉션에 추가할 수 있게 입력 인자로 전달합니다. 그리고 항목을 보관한 컬렉션은 사이트 주소를 키로하고 항목 컬렉션을 값으로 하는 사전에 설정합니다. 그러고 참을 반환합니다.

List<Item> rilist = new List<Item>();
rdic[rsslink] = RSSFavorite.MakeRSSFavorite(rsslink,rilist);
ridic[rsslink] = rilist;
return true;

btn_rem_rsslink(RSS 항목 삭제) 버튼 클릭 이벤트 핸들러에서는 체크 상태의 항목을 제거합니다.

private void btn_rem_rsslink_Click(object sender, EventArgs e)
{
    int cnt = clbox_rsslink.Items.Count;
    for (int i = 0; i < cnt; i++)
    {
        if (clbox_rsslink.GetItemChecked(i))
        {
            clbox_rsslink.Items.RemoveAt(i);
            i--;
            cnt = clbox_rsslink.Items.Count;
        }
    }
}

즐겨찾기 목록을 전체 선택하는 btn_sel_allfav의 클릭 이벤트 핸들러를 추가하여 즐겨찾기 목록을 보여주는 체크박스 컨트롤의 전체 항목을 선택하게 합시다.

private void btn_sel_allfav_Click(object sender, EventArgs e)
{
    int cnt = clbox_fav.Items.Count;
    for (int i = 0; i < cnt; i++)
    {
        clbox_fav.SetItemChecked(i, true);
    }
}

즐겨찾기 목록 체크박스 컨트롤에 체크 상태의 항목을 RSS 항목 체크박스 컨트롤로 이동하는 btn_mov_rss(이전으로)의 클릭 이벤트 핸들러를 작성합시다. 여기에서는 단순히 즐겨찾기 목록 체크박스 컨트롤의 항목에서 제거하고 RSS 항목 체크박스 컨트롤 항목에 추가합니다. 이미 EHGlobal 개체에 등록한 정보를 제거하는 것은 하지 않기로 할게요.

private void btn_mov_rss_Click(object sender, EventArgs e)
{
    int cnt = clbox_fav.Items.Count;
    for (int i = 0; i < cnt; i++)
    {
        if (clbox_fav.GetItemChecked(i))
        {
            clbox_rsslink.Items.Add(clbox_fav.Items[i]);
            clbox_fav.Items.RemoveAt(i);
            i--;
            cnt = clbox_fav.Items.Count;
        }
    }
}

즐겨찾기 항목 체크박스 컨트롤에 체크한 항목을 제거할 때 사용하는 btn_rem_fav의 클릭 이벤트 핸들러를 추가합니다. 여기에서도 체크한 항목을 제거하고 EHGlobal 개체는 아무런 변화를 주지 않기로 합시다.

private void btn_rem_fav_Click(object sender, EventArgs e)
{
    int cnt = clbox_fav.Items.Count;
    for (int i = 0; i < cnt; i++)
    {
        if (clbox_fav.GetItemChecked(i))
        {
            clbox_fav.Items.RemoveAt(i);
            i--;
            cnt = clbox_fav.Items.Count;
        }
    }
}

즐겨찾기 항목 체크박스의 항목 선택 변경 이벤트 핸들러를 추가합니다.

private void clbox_fav_SelectedIndexChanged(object sender, EventArgs e)

만약 선택 항목의 인덱스가 -1이면 선택 항목이 없는 것이므로 메서드를 종료합니다.

if (clbox_fav.SelectedIndex == -1)
{
    return;
}

그렇지 않다면 선택한 항목의 문자열을 참조하여 EHGlobal 단일개체에게 RSSFavorite 개체를 요청합니다.

string rsslink = clbox_fav.SelectedItem as string;
RSSFavorite rssfavorite = EG.FindRSSFavorite(rsslink);

검색한 RSSFavorite 개체가 거짓이 아니면 각 속성 정보로 화면에 표시할 각 컨트롤 개체의 속성을 설정합니다.

if (rssfavorite != null)
{
    lb_rss_title.Text = rssfavorite.Title;
    lb_rss_folder.Text = rssfavorite.Folder;
    lb_rss_link.Text = rssfavorite.Link;
    tbox_rss_desc.Text = rssfavorite.Description;
}

EHGlobal 클래스에 RSSFavorite 개체를 검색하는 메서드를 추가합니다. 이 메서드에서는 입력받은 주소로 사전에 보관한 RSSFavorite 개체를 반환합니다. 만약 없을 때는 null을 반환합니다.

internal RSSFavorite FindRSSFavorite(string rsslink)
{
    if (rdic.ContainsKey(rsslink))
    {
        return rdic[rsslink];
    }
    return null;
}

btn_navi의 클릭 이벤트 핸들러를 추가하여 rss 주소로 탐색합니다.

private void btn_navi_Click(object sender, EventArgs e)
{
    wb.Navigate(lb_rss_link.Text);
}

즐겨찾기 항목의 정보를 상세하게 보기위해 btn_go_verify의 클릭 이벤트 핸들러를 추가합시다. 여기서는 단순히 탭 컨틀로의 선택 페이지로 tp_verify 탭 페이지로 설정합니다.

private void btn_go_verify_Click(object sender, EventArgs e)
{
    tc.SelectedTab = tp_verify;
}

tp_verify 탭 페이지의 Enter이벤트 핸들러를 추가합시다. 여기에서는 즐겨찾기 항목을 비운 다음 tp_rss 탭 페이지의 즐겨찾기 항목들로 새롭게 항목을 추가하고 선택 항목도 지정합니다.

private void tp_verify_Enter(object sender, EventArgs e)
{
    clbox_fav2.Items.Clear();
    clbox_fav2.Items.AddRange(clbox_fav.Items);
    clbox_fav2.SelectedIndex = clbox_fav.SelectedIndex;
}

clbox_fav2의 항목 변경 이벤트 핸들러를 추가합니다.

private void clbox_fav2_SelectedIndexChanged(object sender, EventArgs e)

만약 선택 항목의 인덱스가 -1이면 선택한 것이 없으므로 메서드를 종료합니다.

if (clbox_fav2.SelectedIndex == -1)
{
    return;
}

선택한 것이 있으면 해당 항목의 내용으로 rss 주소를 참조합니다.

string rsslink = clbox_fav2.SelectedItem as string;

그리고 EHGlobal 단일개체의 FindRSSFavorite 메서드를 호출하여 RSSFavorite 개체를 참조합니다.

RSSFavorite rssfavorite = EG.FindRSSFavorite(rsslink);

검색한 RSSFavorite 개체의 속성으로 표시할 컨트롤의 속성을 설정합니다.

if (rssfavorite != null)
{
    lb_rss_title2.Text = rssfavorite.Title;
    lb_rss_folder2.Text = rssfavorite.Folder;
    lb_rss_link2.Text = rssfavorite.Link;
    tbox_rss_desc2.Text = rssfavorite.Description;
}

그리고 EHGlobal 개체에게 RSS 주소의 항목 컬렉션을 얻어와 lbox_item의 데이터 소스로 사용합니다. 이처럼 항목을 보관하는 컨트롤은 데이터를 보관하는 컨트롤을 데이터 소스로 지정하는 것으로 데이터 바인딩을 할 수도 있습니다.

lbox_item.DataSource = EG.FindRSSItemList(rsslink);

EHGlobal 클래스에 FindRSSItemList 메서드를 추가합니다. 여기서는 RSS 주소를 키로 하고 아이템 컬렉션을 값으로 하는 사전 개체에서 아이템 컬렉션을 찾아 반환합니다. 만약 입력 인자로 받은 RSS 주소를 키로 하는 요소가 없으면 빈 아이템 컬렉션을 생성하여 반환합니다.

internal List<Item> FindRSSItemList(string rsslink)
{
    if(ridic.ContainsKey(rsslink))
    {
        return ridic[rsslink];
    }
    return new List<Item>();
}

btn_navi2의 클릭 이벤트 핸들러를 추가합니다. 이벤트 핸들러에서는 rss 주소로 탐색 요청합니다. 그리고 컨텐츠 탭 컨트롤(tc_content)의 선택을 tp_web으로 변경합니다.

private void btn_navi2_Click(object sender, EventArgs e)
{
    wb.Navigate(lb_rss_link2.Text);
    tc_content.SelectedTab = tp_web;
}

item 리스트 박스(lbox_item)의 선택 변경 이벤트 핸들러를 추가합니다.

private void lbox_item_SelectedIndexChanged(object sender, EventArgs e)

선택 항목이 없으면 메서드를 종료합니다.

if (lbox_item.SelectedIndex == -1)
{
    return;
}

선택 항목이 있으면 tc_content 컨트롤의 선택 탭 페이지를 tp_item_verify로 설정합니다.

tc_content.SelectedTab = tp_item_verify;

그리고 선택 Item을 변경합니다. 이 부분은 별도의 메서드로 작성합시다.

ChangeItem();

ChangeItem 메서드를 구현합시다.

private void ChangeItem()

아이템 리스트박스의 선택 항목을 참조합니다.

Item item = null;
if (lbox_item.SelectedIndex != -1)
{
    item = lbox_item.SelectedItem as Item;
}

그리고 선택한 아이템 정보로 컨트롤 정보를 설정합니다. 이 부분도 별도의 메서드로 작성합시다.

SetItem(item);

아이템 정보로 컨트롤 정보를 설정하는 SetItem 메서드를 구현합시다.

private void SetItem(Item item)

만약 입력 인잘로 받은 Item 개체가 null이면 디폴트 값으로 컨트롤의 속성을 설정합니다. 그리고 Item 개체가 null이 아니면 아이템의 속성을 이용하여 컨트롤의 속성을 설정합니다. 특히 EHGlobal 개체에게 아이템의 주소를 입력 인자로 SubItem을 추가할 수 있는 설정을 해 줍니다.

참고로 Item 개체가 null일 때 이미지를 표현하기 위한 NoImage.jpg를 만들어 출력 폴더에 추가하세요.

if (item == null)
{
    lb_item_title.Text = "선택 안 함";
    lb_item_author.Text = "선택 안 함";
    lb_item_date.Text = "선택 안 함";
    lb_item_link.Text = "선택 안 함";
    tbox_item_description.Text = string.Empty;
    pb_item.Image = Image.FromFile("NoImage.jpg");
    wb_item.DocumentText = "<html><head/><body/><html>";
    btn_navi3.Enabled = false;
}
else
{
    lb_item_title.Text = item.Title;
    lb_item_author.Text = item.Author;
    lb_item_date.Text = item.PubDate.ToString();
    lb_item_link.Text = item.Link;
    tbox_item_description.Text = item.Description;
    pb_item.ImageLocation = item.Image;
    EG.ReadyAddSubItem(item.Link);
    wb_item.Navigate(item.Link);
    btn_navi3.Enabled = true;
}

EHGlobal의 SubItem을 추가할 준비를 하는 메서드인 ReadyAddSubItem 메서드를 구현합시다.

internal void ReadyAddSubItem(string link)

먼저 risdic 사전 개체에 입력 인자로 받은 주소가 있는지 확인하여 없다면 새로운 사전 개체를 생성하여 설정합니다.

if (risdic.ContainsKey(link) == false)
{
    risdic[link] = new Dictionary<string, SubItem>();
}

tp_item_verify 컨트롤의 Enter 이벤트 핸들러를 추가하세요. 여기서도 아이템을 변경하는 메서드를 호출합니다.

private void tp_item_verify_Enter(object sender, EventArgs e)
{
    ChangeItem();
}

이번에는 검색 질의를 하는 btn_search의 클릭 이벤트 핸들러를 추가합시다.

private void btn_search_Click(object sender, EventArgs e)

이벤트 핸들러에서는 질의 내용으로 검색을 요청합니다.

List<RankItem> ri_list = EG.Request(tbox_query.Text);

그리고 검색 결과인 RankItem 컬렉션을 결과 리스트박스의 데이터 소스로 설정합니다. 그리고 tc 컨트롤의 선택 탭 페이지를 tp_result로 설정합니다.

lbox_result.DataSource = ri_list;
tc.SelectedTab = tp_result;

EHGlobal 클래스의 검색 요청 메서드인 Request를 구현합시다.

internal List<RankItem> Request(string query)

먼저 검색 결과를 보관할 사전 개체를 생성합니다.

Dictionary<string, RankItem> res_dic = new Dictionary<string, RankItem>();

그리고 MorphemeParser의 정적 메서드 Parse를 호출하여 질의를 형태소 분석합니다.

List<Morpheme> mlist = MorphemeParser.Parse(query);

질의를 형태소 분석한 각 항목으로 역파일을 통해 점수를 부여합니다. 형태소 이름으로 역파일을 통해 점수를 분석하는 부분은 별도의 메서드로 구현합시다.

foreach (Morpheme mo in mlist)
{
    CalculateScore(mo, res_dic);
}

그리고 반환할 검색 결과를 보관할 컬렉션을 생성하여 검색 결과를 보관한 사전 개체의 값 항목을 복사하고 정렬한 후에 반환합니다.

List<RankItem> rlist = new List<RankItem>();
rlist.AddRange(res_dic.Values);
rlist.Sort();
return rlist;

형태소로 점수를 부여하는 CalculateScore 메서드를 구현합시다.

private void CalculateScore(Morpheme mo, Dictionary<string, RankItem> res_dic)

만약 역파일에 형태소 이름이 없으면 메서드를 종료합니다.

if (invdic.ContainsKey(mo.Name) == false)
{
    return;
}

역파일에 형태소 이름이 있으면 역파일 정보를 관리하는 사전 개체에서 역파일 요소 사전 개체를 참조합니다.

Dictionary<string, InvElem> iev = invdic[mo.Name];

먼저 전체 문서 개수를 얻어와 문서의 희귀성 점수인 df값을 도출합니다. 전체 문서 개수를 얻어오는 메서드는 별도로 작성합시다.

double df = (double)iev.Count / GetTotalDocCount();

사전 개체의 값 항목 컬렉션의 역 파일 요소마다 tf 값을 구하고 앞에서 구한 df 값과 혼합하여 tf idf 값을 산출합니다. 그리고 사이트 주소의 Item 개체를 조사하여 점수와 함께 RankItem 개체를 생성합니다. 결과 항목에 같은 결과가 있으면 점수를 합산하고 없으면 사전 개체에 등록합니다.

foreach (InvElem ie in iev.Values)
{
    int tcount = GetTotalCount(ie.Link);
    double tf = (double)ie.RefCount / tcount;
    double score = tf * Math.Log(1 / df, 2);
    Item item = FindItem(ie.Link);
    if (item != null)
    {
        RankItem ri = new RankItem(item, score);
        if (res_dic.ContainsKey(ri.Link))
        {
            RankItem s_ri = res_dic[ri.Link];
            s_ri.AddScore(ri);
        }
        else
        {
            res_dic[ri.Link] = ri;
        }
    }
}

전체 문서의 개수를 구하는 메서드인 GetTotalDocCount를 구현합시다.

private int GetTotalDocCount()

ridic의 값 컬렉션의 각 항목을 참조하여 개수의 합계를 구하여 반환합니다.

int dcount = 0;
foreach (List<Item> ilist in ridic.Values)
{
    dcount += ilist.Count;
}
return dcount;

주소로 Item 개체를 찾는 메서드에서는 ridic 사전 개체의 값 컬렉션에서 항목 리스트를 얻어온 후에 다시 한 번 항목 리스트에서 입력 인자로 받은 주소가 있는지 확인하여 결과를 반환합니다.

private Item FindItem(string link)
{
    foreach (List<Item> ilist in ridic.Values)
    {
        foreach (Item item in ilist)
        {
            if (item.Link == link)
            {
                return item;
            }
        }
    }
    return null;
}

수집한 사이트의 전체 형태소 개수를 얻어오는 GetTotalCount 메서드를 구현합시다.

private int GetTotalCount(string link)

먼저 risdic 개체에서 서브 요소 사전 개체를 얻어옵니다.

Dictionary<string,SubItem> sdic = risdic[link];

그리고 사전 개체의 값 컬렉션의 각 항목의 형태소 개수를 합하여 이를 반환합니다.

int tcount = 0;
foreach (SubItem si in sdic.Values)
{
    tcount += si.TotalCount;
}
return tcount;

결과 항목 리스트 상자인 lbox_result의 선택 변경 이벤트 핸들러를 추가합시다.

private void lbox_result_SelectedIndexChanged(object sender, EventArgs e)

선택 항목이 없으면 디폴트 값으로 컨트롤의 속성을 설정합니다. 선택한 항목이 있으면 선택한 항목을 RankItem 개체로 참조 변환하여 컨트롤 속성을 설정합니다.

if (lbox_result.SelectedIndex == -1)
{
    lb_r_title.Text = string.Empty;
    lb_r_link.Text = string.Empty;
    lb_r_date.Text = string.Empty;
    lb_r_author.Text = string.Empty;
    lb_r_score.Text = string.Empty;
    tbox_r_description.Text = string.Empty;
    pb_r.Image = Image.FromFile("NoImage.jpg");
    return;
}
RankItem ri = lbox_result.SelectedItem as RankItem;
lb_r_title.Text = ri.Title;
lb_r_link.Text = ri.Link;
lb_r_date.Text = ri.PubDate.ToString();
lb_r_author.Text = ri.Author;
lb_r_score.Text = ri.Score.ToString();
tbox_r_description.Text = ri.Description;
pb_r.ImageLocation = ri.Image;

btn_r_navi의 클릭 이벤트 핸들러를 추가하여 사이트 주소로 탐색하게 합시다.

private void btn_r_navi_Click(object sender, EventArgs e)
{
    wb.Navigate(lb_r_link.Text);
}

btn_navi3의 클릭 이벤트 핸들러를 추가합니다. 여기서는 아이템 주소로 탐색 요청합니다. 그리고 tc_content의 선택 탭 페이지를 tp_web으로 변경합니다.

private void btn_navi3_Click(object sender, EventArgs e)
{
    wb.Navigate(lb_item_link.Text);
    tc_content.SelectedTab = tp_web;
}

이제 수집 정보 탭 페이지인 tp_getinfo의 Enter 이벤트 핸들러를 추가합시다.

private void tp_getinfo_Enter(object sender, EventArgs e)

먼저 역파일 목록을 보여주는 리스트뷰 컨트롤의 항목을 지워줍니다.

lview_inv.Items.Clear();

EHGlobal 개체를 통해 역파일 사전 개체를 참조합니다.

Dictionary<string, int> idi_dic = EG.InvDicInfoDic;

얻어온 사전 개체의 각 항목으로 ListViewItem 개체를 생성하여 리스트뷰 컨트롤 항목 컬렉션에 추가합니다.

foreach (KeyValuePair<string, int> kvp in idi_dic)
{
    ListViewItem lvi = new ListViewItem(
          new string[] { kvp.Key, kvp.Value.ToString() }
          );
    lview_inv.Items.Add(lvi);
}

EHGlobal 클래스에 역파일 사전 정보를 제공하는 가져오기 속성을 구현합시다. 여기에서는 invdic의 항목을 새로운 사전 개체에 추가하여 이를 반환합니다.

internal Dictionary<string, int> InvDicInfoDic
{
    get
    {
        Dictionary<string, int> idi = new Dictionary<string, int>();
        foreach (KeyValuePair<string, Dictionary<string, InvElem>> kvp in invdic)
        {
            idi[kvp.Key] = kvp.Value.Count;
        }
        return idi;
    }
}

역파일 항목을 보여주는 lvie_inv 리스트뷰의 선택 변경 이벤트 핸들러를 추가합시다.

private void lview_inv_SelectedIndexChanged(object sender, EventArgs e)

먼저 역파일의 항목인 사이트 정보 목록을 보여주는 lview_msite의 항목 컬렉션을 지워줍니다.

lview_msite.Items.Clear();

만약 lview_inv 컨트롤의 선택 항목 개수가 0이면 메서드를 종료합니다.

if (lview_inv.SelectedItems.Count == 0)
{
    return;
}

선택한 항목 컬렉션의 첫번째 요소의 형태소 이름을 참조합니다.

string mname = lview_inv.SelectedItems[0].Text;

그리고 EHGlobal 개체를 이용하여 역파일 사이트 정보 컬렉션을 얻어옵니다.

List<InvSiteInfo> lsi_list= EG.GetInvElemList(mname);

역파일 사이트 정보 컬렉션의 항목으로 ListViewItem 개체를 생성하여 lview_msite 컨트롤의 항목 컬렉션에 추가합니다.

foreach (InvSiteInfo lsi in lsi_list)
{
    ListViewItem lvi = new ListViewItem(
        new string[] { lsi.Link, lsi.TotalCount.ToString(), lsi.RefCount.ToString() });
    lview_msite.Items.Add(lvi);
}

EHGlobal 클래스의 GetInvElemList 메서드를 구현합시다.

internal List<InvSiteInfo> GetInvElemList(string mname)

먼저 결과를 보관할 컬렉션을 생성합니다.

List<InvSiteInfo> isi_list = new List<InvSiteInfo>();

형태소 이름으로 invdic에 보관한 역파일 사전 개체를 참조합니다.

Dictionary<string, InvElem> iev = invdic[mname];

얻어온 사전 개체의 항목으로 사이트의 전체 형태소 개수를 얻어오고 이들 정보를 이용하여 InvSiteInfo 개체를 생성하여 결과 컬렉션에 추가합니다. 그리고 결과를 반환합니다.

foreach (InvElem ie in iev.Values)
{
    int tcount = GetTotalCount(ie.Link);
    InvSiteInfo isi = new InvSiteInfo(ie.Link, tcount, ie.RefCount);
    isi_list.Add(isi);
}
return isi_list;