[MFC] HTML 파서 만들기 #1


[MFC] HTML 파서 만들기 #1

http://tackin.tistory.com/entry/MFC-HTML-%ED%8C%8C%EC%84%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-1




이번 포스팅은 특정사이트의 HTML 태그를 가져와서 출력하는 것까지 다룹니다.

 

개발 툴은 Visual Studio 2012를 사용했습니다.

 

 

 

# 화면 구성

- url 영역

- url 이동 버튼

- html 가져오기 버튼

- webBrowser 영역

- 디버깅1 영역

- 디버깅2 영역

 

 

 

결과물는 간단합니다.

 

해당 URL에 이동하여 webBrowser의 후킹(?) 콜백(?) 정보를 출력(디버깅1)하고

 

scan버튼을 통해 html내용을 출력(디버깅2)하는 겁니다.

 

 

 

 

 

본 편은(#1) 인터넷 검색 중 Tcube님의 포스팅을 몇번씩 읽어가면서 따라가기 방식으로 진행했습니다.

 

다음편(#2) 를 이어가기 위하여 피치못하게 모방된 글을 작성하게 되었습니다.

 

TCube님게 지식 공유에 대한 감사의 말씀드리며, 문제가 발생한다면 바로 포스팅 내리도록 하겠습니다.

 

 

아래의 부분은 제가 기억하기 위함으로 작성한 글이기에 본 포스팅을 보시는 분은  Tcube님의 글을 보시는게 도움이 더 되실겁니다.

 

다음편 #2는 저의 창작물로 찾아뵙도록하겠습니다.

 

 

TCUBE

 

 

접기

 

# 프로젝트 생성

 

 

 

 

 

 

MFC 응용프로그램 선택 -> 대화상자기반, 유니코드 라이브러리사용 체크 해제 -> 이후는 디폴트

 

유니코드 라이브러리 사용은 체크를 하는것이 좋은지 나쁜지는 정확히 알 수 없습니다.

 

처음 체크한 상태로 프로젝트를 생성하니 계속 _T()를 사용해야하는 점이 귀찮아서 프로젝트 다시 만들었습니다.

 

 

 

# 레이아웃 설정

 

 

도구상자에서 web browser를 눈 씻고 찾아도 안 나올 겁니다.

 

 

 

 

Dialog 아무곳이나 마우스 오른쪽 클릭을 하시면 ActiveX 컨트롤 삽입을 클릭하시면

 

 

 

이런 화면이 나옵니다. 중간 넘어서  Microsoft Web Browser를 선택하시면 됩니다.

 

문법도 익숙하지 않을 텐데 툴 사용 자체가 저의 발목을 많이 잡네요.

 

 

 

 

 

 

 

MFC를 만져본게 10년이 넘은 것 같습니다. 그 당시에는 Visual Studio 6.0 이였던걸로 기억납니다.

 

가물가물한 기억으로 2012를 실행하고 적지않게 놀랐죠.. 어떻게 사용하는거야 이거??

 

 

 

우선 변수 등록은 Ctrl을 누른상태로 해당 컨퍼넌트를 더블클릭하면 멤버변수 추가 마법사가 나옵니다. 그곳에서 위에 보이는 화면처럼 입력 하시면 됩니다.

 

 

주의하실점은

 

web browser의 변수 형식을 CWebbrowser2로 직접입력하셔야 합니다. 잘 등록되었다면

 

클래스뷰 창에 CWebbrowser2가 등록되신것을 확인 할 수 있습니다.

 

 

 

TCube 참고

 

 

 

 

# CWebbrowser2 이벤트 등록

 

다음으로 web browser의 이벤트를 등록합니다.

 

등록하기 앞서 webbrowser에는 무슨 이벤트가 있는걸까요?

 

예를 들어,

 

요청한 페이지 로딩이 완료되었다. 하면 DocumentComplete 이벤트,

 

이미지 등을 다운받는 경우에는 DownloadBegin 이벤트

 

윈도우 창이 뜨기 전에 발생하는 NewWindow2 이벤트 등이 있습니다.

 

자세한 내용은  Tcube 블로그를 봐주세요.

 

TCube 참고

 

 

 

 

 

캘래스 뷰의 CHTEML_PaserDlg를 클릭하면 속성 탭을 확인 할 수 있습니다.

 

번개모양을 클릭하여 이벤트 창에서 IDC_EXPLORER1의 이벤트 함수를 등록합니다.

 

 

 

 /**
 * 인터넷 탐색하기 전에 발생
 */
void CHTML_PaserDlg::BeforeNavigate2Webbrowser(LPDISPATCH pDisp, VARIANT* URL, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, BOOL* Cancel)
{
    // TODO: 여기에 메시지 처리기 코드를 추가합니다.
    USES_CONVERSION;
    CString strEvent("BeforeNavigate2 : ");
    strEvent += OLE2T(URL->bstrVal);

    ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가
}

 

 

 

//브라우저 조작 등에 따른 상태 변화 이벤트 

void CHTML_PaserDlg::CommandStateChangeWebbrowser(long Command, BOOL Enable)
{
    // TODO: 여기에 메시지 처리기 코드를 추가합니다.
     if (Enable == FALSE)
        return;

    CString strEvent;
 
    switch(Command)
    {
        case CSC_UPDATECOMMANDS:
             // 도구 모음 버튼의 활성화 상태
            strEvent.Format("CommandStateChange : Command=UpdateCommands(%ld)   Enabled=%d", Command, Enable);
            if (Enable)
                strEvent = "CommandStateChange : 도구모음 활성화";
            break;
        case CSC_NAVIGATEFORWARD:
            // 앞으로 버튼의 활성화 상태
            //strEvent.Format("CommandStateChange : Command=NavigateForward(%ld)   Enabled=%d", Command, Enable);
            if (Enable)
                strEvent = "CommandStateChange : 앞으로 이동 버튼 활성화";
            break;
  case CSC_NAVIGATEBACK:
    // 뒤로 버튼의 활성화 상태
    //strEvent.Format("CommandStateChange : Command=NavigateBack(%ld)   Enabled=%d", Command, Enable);
    if (Enable)
     strEvent = "CommandStateChange : 뒤로 이동 버튼 활성화";   
    break;
    }

    ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가
}

 

 

 

 

 

 

//웹페이지 로딩이 완료되었을 경우에 발생 

void CHTML_PaserDlg::DocumentCompleteWebbowser(LPDISPATCH pDisp, VARIANT* URL)
{
 // TODO: 여기에 메시지 처리기 코드를 추가합니다.
 USES_CONVERSION;
    CString strEvent("DocumentComplete : ");
    strEvent += OLE2T(URL->bstrVal);
 ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가
 
   

 if (m_pDispCurrent && m_pDispCurrent == pDisp){
   strEvent = "====================>Document is done";
   ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가
  m_pDispCurrent = NULL;


  m_pDispDocument = ctl_WebBrowser.get_Document(); // 브라우저에서 웹페이지를 받는다.
  
  // 새로운 웹페이지가 로딩되었으므로 기존의 웹페이지 데이터를 모두 해제한다.
  if (m_pHTMLDocument2 != NULL){
   m_pHTMLDocument2->Release();
   m_pHTMLDocument2 = NULL;
  }
  
  // 받아온 HTML을 IHTMLDocument2 형식으로 내보낸다.
  HRESULT hr = m_pDispDocument->QueryInterface( IID_IHTMLDocument2,  (LPVOID *)&m_pHTMLDocument2);
  
  // 정상적으로 HTMLDocument를 받아왔다면, 내보내기 변수를 해제한다.
  if (hr == S_OK){
   m_pDispDocument->Release();
   m_pDispDocument = NULL;
  }
 }
 
}

 

 

 

// 이미지 등 파운로드 할 때 발생

void CHTML_PaserDlg::DownloadBeginWebbowser()
{
 // TODO: 여기에 메시지 처리기 코드를 추가합니다.
 CString strEvent("DownloadBegin");
    ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가

 

 

 

 

// 다운로드 완료 되었을때 발생

void CHTML_PaserDlg::DownloadCompleteWebbowser()
{
 // TODO: 여기에 메시지 처리기 코드를 추가합니다.
 CString strEvent("DownloadComplete");
 

    ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가

 

 

 

 

 

// 서버에 웹페이지 요청이 완료되었을때 호출

 void CHTML_PaserDlg::NavigateComplete2Webbowser(LPDISPATCH pDisp, VARIANT* URL)
{
 // TODO: 여기에 메시지 처리기 코드를 추가합니다.
 USES_CONVERSION;
    CString strEvent("NavigateComplete2 : ");
    strEvent += OLE2T(URL->bstrVal);

    ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가

 // NULL 인지 확인하여, 값이 없다면 최상위 페이지를 요청하는 것이므로
    // 인자(파라미터) 값 pDisp 를 최상위 페이지로 값으로 간주     
    if (m_pDispCurrent == NULL){
        m_pDispCurrent = pDisp;
    }
}

 

 

 

 

 

void CHTML_PaserDlg::NewWindow2Webbowser(LPDISPATCH* ppDisp, BOOL* Cancel)
{
    // TODO: 여기에 메시지 처리기 코드를 추가합니다.
    CString strEvent("NewWindow2");
 
    ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가

 

 

 

 

 

 void CHTML_PaserDlg::ProgressChangeWebbowser(long Progress, long ProgressMax)
{
 // TODO: 여기에 메시지 처리기 코드를 추가합니다.
 CString strEvent;
    strEvent.Format("ProgressChange : Progress=%ld   ProgressMax=%ld", Progress, ProgressMax);

    ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가
}

 

 

 

 

 

 void CHTML_PaserDlg::StatusTextChangeWebbowser(LPCTSTR Text)
{
 // TODO: 여기에 메시지 처리기 코드를 추가합니다.
 CString strEvent = Text;
    if ( strEvent.IsEmpty() )
        return;

    strEvent = "StatusTextChange : ";
    strEvent += Text;
 
    ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가
}

 

 

 

 

 

 void CHTML_PaserDlg::TitleChangeWebbowser(LPCTSTR Text)
{
 // TODO: 여기에 메시지 처리기 코드를 추가합니다.
 CString strEvent("TitleChange : ");
    strEvent += Text;
 
    ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가
}

 

 

 

 

 

 

void CHTML_PaserDlg::NavigateErrorWebbowser(LPDISPATCH pDisp, VARIANT* URL, VARIANT* Frame, VARIANT* StatusCode, BOOL* Cancel)
{
 // TODO: 여기에 메시지 처리기 코드를 추가합니다.
 USES_CONVERSION;
    CString strEvent("NavigateError : ");
    strEvent += OLE2T(URL->bstrVal);

    ctl_listEvent.InsertString(0, strEvent); // 리스트에 추가

 

 

 

 

// move 버튼 클릭 시 호출

 void CHTML_PaserDlg::OnBnClickedButton1()
{
 // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
 UpdateData();  
 ctl_WebBrowser.Navigate(m_urlStr, NULL, NULL, NULL, NULL);
 UpdateData(FALSE);
}

 

 

 

 

 

 

 void CHTML_PaserDlg::OnBnClickedButton2()
{

 IHTMLDocument3 * pHTMLDocument3;
 HRESULT hr = m_pHTMLDocument2->QueryInterface(IID_IHTMLDocument3,  (LPVOID *)&pHTMLDocument3);

 IHTMLElement * pDocumentElement;
 hr = pHTMLDocument3->get_documentElement(&pDocumentElement);
 pHTMLDocument3->Release();
 
 BSTR bstrHTML;
 pDocumentElement->get_outerHTML(&bstrHTML);
 // pDocumentElement->get_outerText(&bstrHTML);  // text 전체 가져오기
 pDocumentElement->Release();
 
 USES_CONVERSION;
 ctl_strHTML = OLE2T(bstrHTML);
 
 UpdateData(FALSE);
 SysFreeString(bstrHTML);

// m_pHTMLDocument2 = NULL;


}

 

 

TCube 참고

 

 

 

 

 

# 결과화면