이번 포스팅은 특정사이트의 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 참고

 

 

 

 

 

# 결과화면

 

 

 

 

이번 포스팅에서는 아래의 항목을 시도하겠습니다.

 

1. 모든 TAG 출력

 

2. 각 태그에 들어있는 text 출력

 

 

 

# 화면 구성

 

 

전 포스팅과 다른 점은 아래와 같습니다.

 

1. GetHTML 버튼 : 해당 사이트의 HTML 전체 출력

2. scan : 모든 태그를 tree contol에 출력

3. tree contol 추가

 

 

 

# IHTMLElementCollection

 

이전 포스팅이 기억 나시나요?

 

 

HTML을 출력하기 위하여

 

web bowser의 document를 가져왔고 : m_pDispDocument = ctl_WebBrowser.get_Document();

 

document를 토대로 실질적인 IHTMLDocument2를 가져왔습니다. : m_pDispDocument->QueryInterface( IID_IHTMLDocument2,  (LPVOID *)&m_pHTMLDocument2);

 

m_pHTMLDocument2가 실질적인 HTML의 document과 동일하다라고 보셔도 됩니다.

 

생성한 Document를 이용하여 HTML 소스를 가져와서 : pDocumentElement->get_outerHTML(&bstrHTML);

 

디버깅 부분에 출력을 했었죠

 

 

 

html은 Document밑에 수많은 Tag로 하나의 페이지를 이룹니다.

 

Document 가 MSHTML에서는 IHTMLDocument와 동일하고

 

각 태그들이 IHTMLElement 와 동일합니다.

 

태그의 집합을 IHTMLElementCollection와 동일하다고 보시면됩니다.

 

제 말은 반만 믿으세요. 공부한지 3일 된 놈입니다. 잘못된 점 알려주시면 수정하도록 하겠습니다.

 

 

 

MSDN을 보면 IHTMLDocument가 1, 2, 3, 4 등과 같이 여러개 있습니다.

 

아직 하나하나 확실히 보지는 않았지만, 프로그램이 진화함에 따라 번호도 증가된게 아닌가 유추를 해봅니다.

 

현재로서는 정확한 앎 없이 좋아하는 숫자 사용하고 있습니다. -_-;;;

 

 

 

 

# scan 버튼

 

우선 소스부터 보시죠 

 

 

void CHTML_PaserDlg::OnBnClickedButton2()
{
if ( !m_pHTMLDocument2 )
return;

// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
IHTMLElementCollection *htmlElementCollection;
m_pHTMLDocument2->get_all(&htmlElementCollection);

long tagCnt;
htmlElementCollection->get_length(&tagCnt);

if(tagCnt > 0){
for(int i=0; i<tagCnt; i++){
IDispatch *dispatchElement;
VARIANT varIndex;
VARIANT var2;

varIndex.vt = VT_UINT;
varIndex.lVal = i;
VariantInit(&var2);

IHTMLElement *htmlElem;
htmlElementCollection->item(varIndex, var2, &dispatchElement);

dispatchElement->QueryInterface(IID_IHTMLElement, (LPVOID *)&htmlElem);
dispatchElement->Release();
BSTR htmlStr;
BSTR htmlTextStr;
htmlElem->get_tagName(&htmlStr);
htmlElem->get_innerText(&htmlTextStr);
htmlElem->Release();
CString str;
//str.Format(“%s = %s”, (LPCTSTR)htmlStr, (LPCTSTR)htmlTextStr);
str.Format(“%s = %s”, (LPCTSTR)bstr_t(htmlStr), (LPCTSTR)bstr_t(htmlTextStr) );
ctr_treeCtl1.InsertItem(str, 0, 1);
SysFreeString(htmlStr);
SysFreeString(htmlTextStr);
}
}
htmlElementCollection->Release();

}

 

 

복사를 하면 인덴트가 왜 저 모양인지 모르겠습니다. 귀찮아서 수정하지 않습니다.

 

시작함에 있어 말씀드린것 같이 scan버튼을 누르면

 

해당 사이트의 모든 태그를 tree view에 [태그 = 텍스트]로 출력합니다.

 

 

m_pHTMLDocument2->get_all(&htmlElementCollection);

 

document2에서 모든 element를 htmlElementCollection에 등록합니다.

 

 

htmlElementCollection->item(varIndex, var2, &dispatchElement);

 

element 갯수만큼 루프를 돌면서

 

htmlElementCollection의 요소를 element로 추출을 하죠

 

 

이를 tree control에 부모자식 구분없이 상놈의 자식(?)처럼 집어넣고 있습니다.

 

 

 

 

# 결과화면

 

 

보모자식 없는 상놈의자식 보이나요?

 

정말 트리형태로 보이게 하고 싶었으나, 아는 지식이 없는지라…;;;

 

 

사실 그부분 좀 확인하고 포스팅 하려고 했으나, 오늘 갑자기 약속이 생기는 바람에 어제 정리해놓은 자료로 급하게 포스팅 올립니다.

 

다음 포스팅에서는 원하는 부분 search, css 컨트롤, dom제어 부분을 나가겠습니다.

 

 

 

 

지식이 전무한 상태로 짧은 기간 학습하고 진행하는지라 잘못된 내용이 많을 수 도 있습니다.