자료구조, 알고리즘

연결리스트 스택 이용해 노재귀 형태로 html 소스 뽑아보기

디버그정 2009. 11. 11. 15:38

자료구조, 알고리즘 둘러보다가 퍼뜩 떠올라서 구성해 보았다.

스택을 이용하니 노재귀형태로 구성할 수 있었다.
재귀의 오버플로우는 뭐 요즘같이 스레드에 할당되는 기본 스택이 1메가? 정도되는 상태에서
웬만큼 흘리는 코딩을 하지 않으면 거의 발생할 일이 없겠지만서두,,,

이와 별도로 하나의 함수에서 기타 처리를 모두 할 수 있으니 깔끔해진 듯 하다.
이전에 재귀 형태로 돌리는 경우 기타 처리를 하려면
보통 재귀함수 + 기타처리를 하는 함수,,, 2개로 구성하는 경우가 많았다.



// 재귀함수 사용하지 않고 스택으로 해결
// 연결리스트 스택으로 구성한다.
// 이 함수 성공시 호출부에서는 사용하고 난 후 결과값을 잊지말고 free해줘야 된다.
LPWSTR __stdcall GetHtmlSourceNoRecur(IHTMLDocument2 *pDoc)
{
 // 스택 구조상 pPre로 하는 것이 pop시 새로운 top 접근이 용이.
 struct Node{
  IHTMLDocument2 *pDoc;
  Node *pPre;
 };
 Node *pTop=NULL, *pNew=NULL, *pTemp=NULL;


 IHTMLDocument2 *pRootDoc=pDoc; // 밑의 루프에서 해제 방지
 //pDoc->AddRef(); // 옆 방식은 해제 전 에러나서 리턴해 버린 경우 +1이 되므로 부적합
 LPWSTR lpszBuf;
 BOOL bSuc=FALSE;

 
 // 전달된 인수 검사
 if(!pDoc) return NULL;


 // 노드 할당 및 첫번째 요소 push
 pTop=(Node*)malloc(sizeof(Node));
 if(!pTop){
  MessageBox(NULL, _T("Node 메모리 할당이 실패하였습니다."), _T("GetHtmlSourceNoRecur 에러"), MB_ICONERROR|MB_TOPMOST);
  return NULL;
 }
 pTop->pDoc=pDoc;
 pTop->pPre=NULL;


 // 소스버퍼 할당,,, 밑의 루프에서 일관성 있는 코드를 위해 한 글자 할당
 lpszBuf=(LPWSTR)malloc(sizeof(WCHAR));
 if(!lpszBuf){
  MessageBox(NULL, _T("초기 메모리 할당이 실패하였습니다."), _T("GetHtmlSourceNoRecur 에러"), MB_ICONERROR|MB_TOPMOST);
  return NULL;
 }
 memset(lpszBuf, 0, sizeof(WCHAR));


 ////////// pTop->pDoc 소스 추출 후 pop하고 하위 프레임 검사해서 push 그리고 Release한다.
 ////////// pTop가 NULL이면 스택이 빈 상태이므로 빠져나간다.
 while(pTop){
  // 참고로 팝하면 변수가 없어지므로 Release 과정을 위해 아래처럼 저장해놔야 된다.
  pDoc=pTop->pDoc;

 
  // 각각의 프레임을 구분하기 쉽게 URL에 아래와 같이 스트링을 붙여준다.
  LPCWSTR lpszUrlPreStr=L"<!-- ############################### URL ############################### \r\n"; // 주석시작 표시와 앞에 붙일 스트링
  LPCWSTR lpszUrlPostStr=L"\r\n######################################################################## -->\r\n"; // 주석끝 표시와 뒤에 붙일 스트링
  BSTR bstrUrl;
  bSuc=FALSE;
  if(S_OK == pDoc->get_URL(&bstrUrl)){
   lpszBuf = (LPWSTR)realloc(lpszBuf, (lstrlenW(lpszBuf)+lstrlenW(bstrUrl)+lstrlenW(lpszUrlPreStr)+lstrlenW(lpszUrlPostStr)+1)*sizeof(WCHAR));
   if(lpszBuf){
    lstrcatW(lpszBuf, lpszUrlPreStr);
    lstrcatW(lpszBuf, bstrUrl);
    lstrcatW(lpszBuf, lpszUrlPostStr);
    bSuc = TRUE;
   } else{
    MessageBox(NULL, _T("URL을 위한 메모리 할당이 실패하였습니다."), _T("GetHtmlSourceNoRecur 에러"), MB_ICONERROR|MB_TOPMOST);
   }
   SysFreeString(bstrUrl);
  }
  if(!bSuc){
   free(lpszBuf);
   MessageBox(NULL, _T("URL을 얻는 데 실패하였습니다."), _T("GetHtmlSourceNoRecur 에러"), MB_ICONERROR|MB_TOPMOST);
   return NULL;
  }

  
  // 도큐먼트의 소스 추출,,, <body>의 부모는 <html> 원리 이용
  IHTMLElement *pBodyElem=NULL;
  IHTMLElement *pParentElem=NULL;
  BSTR bstrSource;
  LPCWSTR lpszSourcePostStr=L"\r\n\r\n\r\n\r\n"; // 뒤에 붙일 스트링,,, 보기 좋게 개행 좀 해 준다.
  bSuc=FALSE;
  if(S_OK==pDoc->get_body(&pBodyElem)){
   if(S_OK==pBodyElem->get_parentElement(&pParentElem)){
    if(S_OK == pParentElem->get_outerHTML(&bstrSource)){
     lpszBuf = (LPWSTR)realloc(lpszBuf, (lstrlenW(lpszBuf)+lstrlenW(bstrSource)+lstrlenW(lpszSourcePostStr)+1)*sizeof(WCHAR));
     if(lpszBuf){
      lstrcatW(lpszBuf, bstrSource);
      lstrcatW(lpszBuf, lpszSourcePostStr);
      bSuc = TRUE;
     } else{
      MessageBox(NULL, _T("소스를 위한 메모리 할당이 실패하였습니다."), _T("GetHtmlSourceNoRecur 에러"), MB_ICONERROR|MB_TOPMOST);
      bSuc = FALSE;
     }
     SysFreeString(bstrSource);
    }
    pParentElem->Release();
   }
   pBodyElem->Release();
  }
  if(!bSuc){
   free(lpszBuf);
   MessageBox(NULL, _T("도큐먼트 소스를 얻는 데 실패하였습니다."), _T("GetHtmlSourceNoRecur 에러"), MB_ICONERROR|MB_TOPMOST);
   return NULL;
  }
  

  // 스택에서 pop
  pTemp=pTop;
  if(pTop->pPre){pTop=pTop->pPre;} // 이전 노드가 있는 경우
  else{pTop=NULL;}     // 이전 노드가 없는 경우
  free(pTemp);


  // 하위 프레임 검사
  IHTMLFramesCollection2 *pFramesCol=NULL;
  IHTMLWindow2 *pWin=NULL;
  IDispatch *pDisp=NULL;
  IHTMLDocument2 *pSubDoc=NULL; 
  long i, cFrame=0;
  VARIANT varIndex; varIndex.vt=VT_I4;
  VARIANT varDisp; varDisp.vt=VT_DISPATCH;
  HRESULT hr;

  if(S_OK == pDoc->get_frames(&pFramesCol)){
   if(S_OK == pFramesCol->get_length(&cFrame)){
    for(i=cFrame-1 ; i>=0 ; i--){  // 뒤부터 얻어야 보기좋게 출력됨
     varIndex.lVal=i;
     if(S_OK == pFramesCol->item(&varIndex, &varDisp)){
      if(S_OK == varDisp.pdispVal->QueryInterface(IID_IHTMLWindow2,(void**)&pWin)){
       if(S_OK == (hr=pWin->get_document(&pSubDoc))){
        // 스택에 push
        if(pNew=(Node*)malloc(sizeof(Node))){
         pNew->pDoc=pSubDoc;
         pNew->pPre=pTop;
         pTop=pNew;
        } else {
         MessageBox(NULL, _T("Node 메모리 할당이 실패하였습니다."), _T("GetHtmlSourceNoRecur 에러"), MB_ICONERROR|MB_TOPMOST);
        }

       // 아래 주의) 도메인이 다른 경우 등은 아래와 같은 에러메시지를 뱉는다.
       //            이 경우는 해당 프레임과 연결된 웹브라우져 컨트롤을 구해야 한다.
       } else if(hr == E_ACCESSDENIED){
        IServiceProvider *pSP=NULL;
        IWebBrowser2 *pSubWB=NULL;
        if(S_OK == pWin->QueryInterface(IID_IServiceProvider, (void**)&pSP)){
         if(S_OK == pSP->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, (void**)&pSubWB)){
          if(S_OK == pSubWB->get_Document(&pDisp)){
           if(S_OK == pDisp->QueryInterface(IID_IHTMLDocument2,(void**)&pSubDoc)){
            // 스택에 push
            if(pNew=(Node*)malloc(sizeof(Node))){
             pNew->pDoc=pSubDoc;
             pNew->pPre=pTop;
             pTop=pNew;
            } else {
             MessageBox(NULL, _T("Node 메모리 할당이 실패하였습니다."), _T("GetHtmlSourceNoRecur 에러"), MB_ICONERROR|MB_TOPMOST);
            }
           }
           pDisp->Release();
          }
          pSubWB->Release();
         }
         pSP->Release();
        }
       }
       pWin->Release();
      }
      varDisp.pdispVal->Release();
     }
    }
   }
   pFramesCol->Release();
  }


  // 루트 도큐먼트가 아니면 해제
  if(pRootDoc != pDoc) pDoc->Release();
 }

 return lpszBuf;
}