본문 바로가기

it관련

문자열 파싱과, 구문인식

반응형

문자열 파싱과, 구문인식


문자열 파싱(parsing)이란,

 

일반 별다른 의미없는 텍스트에서, 상하의 관계를 이해하고

 

별도로 가공된 데이터를 뽑아오는 기술입니다.

 

이런 파싱은 여러분이 많이 사용하는 웹 브라우져(흔히들 인터넷이라 말합니다.)에서 사용하며

 

여러분이 보시고 계시는 이 지식인도, 사실 인터넷 브라우져나 크롬, 파이어폭스에서

 

HTML[Hypertext Markup Language]이라는 문서를 DTD[Document Type Definition]에 규칙에 맞게

 

추출하고, 그것으로 각종 버튼과, 아이콘, 집필하기 창도 만들어서 여러분께 보여주는 겁니다.

 

사실 여러분이 보고있는 이 페이지도

[code]

<DIV id=wrap><A onclick="nhn.Kin.Utility.skipNavigation('au_lnb');return false;" class=skip href="#gnb">메인 메뉴 바로가기</A> <A onclick="nhn.Kin.Utility.skipNavigation('content');return false;" class=skip href="#">본문 바로가기</A> <!-- Header -->

<DIV id=header>

<DIV class=gnb_wrap>

<DIV id=gnb class=skin01>

<DIV id=gnb_utility style="Z-INDEX: 104">

<!-- 생략 -->

[/code]

  

이렇게 구성되고 있습니다.(상당히 어려워 보이네요)

 

인터넷 브라우져는 이런 의미없는 글귀에서 <DIV>라는 단어를 인식하고,

 

</DIV>(DIV라는 태그가 끝나는 위치를 나타냅니다.)를 찾아서, 그 안에 있는 텍스트를

 

DIV라는 그룹 안쪽에 있다고 인지합니다.(구문을 인식하게 되는 것이지요)

 

사실 이런 파싱 기술은, 개발에 있어서 절대 없어서는 안되는 기술이지만

 

상당히 고난이도 기술이며, 이런 기술을 설명하는 저로서도 상당히 난해한 사항입니다.

 

 

왜 이런 기술이 어렵냐고 생각하시는 분들이 있어서 말해드립니다.

 

'a' 라는 텍스트가 있습니다.

 

이 'a'라는 텍스트는 '/a'라는 텍스트가 있으면, 그 안에 있는 내용을 'a' 라는 그룹안에 넣습니다.

 

자, 컴퓨터 입장에서 봅시다.

 

'abacc/a/a'이런 글귀가 있으면 아래 그림과 같이 이해 이해해야 합니다.

 

하지만 직접 이런 프로그램을 짠다고 하면

  

이렇게 잡아버릴 수 있습니다.

 

 

따라서 이런 상황에 고려하여, 깊이(depth)라는 개념을 추가하여 패턴을 다양하게 잡아 처리해야 합니다.

 

"빨강색 a 다음부터 깊이가 1번이다, 이 다음에 a가 또다시 등장하면 깊이는 2가 된다!"

 

"깊이 2는 파랑색이다! 다음에 a가 나오면 깊이가 3이고, /a가 나오면 깊이가 1로 변한다!"

 

라는 구문을 추가해야 합니다.

 

그럼 이젠 어떨까요?

 

우리는 '/'라는 문자를 문장 양쪽에 감싸면, 보이지 않도록 룰을 추가했습니다.

 

/랄랄라/ 하면, "/랄랄라/" 전체가 보이지 않게 되는거죠

 

이번엔 '/a'의 기능이 덮혀버립니다.

 

뭔가가 잘못된거 같아요,

 

구문을 이해하기 위해서는 각각의, 규칙들의 "순서"를 정해서 관리해야 합니다.

 

즉 먼저 '/a'라는 완성된 문자가 아닌가? 를 먼저 물어보고나서,

 

아닐 경우에만 /를 인식해주는 식이죠

 

지금은 간단한 예만을 들었는데 사실 더 복잡한 알고리즘을 구성해야 합니다.

 

 

간단하게 c++언어의 cout함수를 예로 들어볼게요

 

cout함수는

 

cout << "어쩌구 저짜고" ; 라고 입력하면,

 

"어쩌구 저짜고" 만 출력되는 함수입니다.

 

 

 

출력될 문자는 "(쌍따옴표)로 감싸주셔야 하고,

 

쌍따옴표 사이에 \"라는 문자가 있으면, 이거는 쌍따옴표로 인식하지 않고 구문에 반영하지 않아야 합니다.

 

이것을 c언어로 만들어 볼텐데요.(사진처럼)

 

코드는 아래와 같습니다.

 

 

[code]

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

char* getString(char*, int);

int parse(char* _str, char* _status){

 int len=0, idx=0, dps=0;

 char stc[3][255] = {"cout", "<<", '\"'}, *str;

 int stcLen = 4;

 while(_str[len] != '\0'){

  if(_str[len] == stc[0][idx] && dps == 0){

   if(++idx >= stcLen){

    dps = 1;

    idx = 0;

    len++;

    continue;

   }

  }

  else if(dps == 0){

   idx = 0;

  }

  if(_str[len] == ' ' && (dps == 1 || dps == 2)){

   dps = 2;

   stcLen = 2;

  }

  else if(_str[len] == stc[1][idx] && dps == 2){

   if(++idx >= stcLen){

    dps = 3;

    idx = 0;

    len++;

    continue;

   }

  }

  else if(dps == 2){

   idx = 0;

  }

  if(_str[len] == ' ' && (dps == 3 || dps == 4)){

   dps = 4;

   stcLen = 1;

  }

  else if(_str[len] == stc[2][idx] && dps == 4){

   if(++idx >= stcLen){

    str = getString(_str, len+1);

    break;

   }

  }

  len++;

 }

 if(dps >= 4 && str){

  printf("cout 함수 작동\n%s\n\n", str);

  free(str);

 }

 else{

  switch(dps){

  case 0:

   strcpy(_status, "에러: 출력 함수가 없습니다.");

   break;

  case 1:

   strcpy(_status, "에러: cout 함수명 기입 후 적어도 한칸 띄우시기 바랍니다.");

   break;

  case 2:

   strcpy(_status, "에러: cout 함수 사용 시 \"<<\"를 입력해주셔야 합니다.");

   break;

  case 3:

   strcpy(_status, "에러: cout 함수 사용 시 \"<<\"후 적어도 한칸 띄우시기 바랍니다.");   

   break;

  default:

   if(dps == 4 && !str){

    strcpy(_status, "에러: cout 함수 쌍따옴표 안에 문자가 없습니다.");   

   }

   break;

  }

  printf("%s\n\n", _status);

 }

 return 0;

}

char* getString(char* _str, int _pos){

 char *sp;

 int len=0, endPos=-1, i,j;

 while(_str[len + _pos] != '\0'){

  if(_str[len + _pos] == '\"'){

   if(len > 0){

    if(_str[len+_pos-1] != '\\'){

     endPos = len+_pos;

     break;

    }

   }

   else{

    endPos = len-1;

    break;

   }

  }

  len++;

 }

 if(endPos-_pos > 0){

  sp = (char*)malloc(sizeof(char)*(endPos-_pos+1));

 }

 else

 {

  return 0;

 }

 if(endPos>0 && endPos>_pos){

  for(i=_pos,j=0;i<endPos;i++,j++){

   sp[j] = _str[i];

  }

  sp[j] = '\0';

 }

 return sp;

}

int main(){

 char str[255], status[255];

 while(1){

  printf("cout << \"문자열\"; 형태로 입력해 주세요. : 종료는 'ctrl' + 'c'\n");

  fflush(stdin);

  fgets(str, sizeof(str), stdin);

  fflush(stdin);

  parse(str, status);

 }

 return 0;

}

[/code]

 

문자열을 사용자에게 받아와서,

 

parse() 함수에서 입력받은 문자열을 분석합니다.

 

 

사실 cout의 구조는 아래와 같아요

 

 

마지막엔 반드시 세미콜론이 들어가야 하지만,

 

지금은 cout만 인식하는 것이니 한줄 명령이니 세미콜론 검사는 넘어가주도록 하겠습니다 하하

 

 

cout함수는 사실 아래와 같이 구문을 인식해야해요

 

1. cout 함수명이 사용됬는가?

 

2. cout 함수 다음에 스페이스가 한칸 이상 있는가

 

3. cout 함수 스페이스 다음에 ">>"라는 문자가 있는가.

 

4. cout 함수 ">>" 문자 다음에 스페이스가 한칸 이상 있는가.

 

5. cout 함수 ">>" 스페이스 다음에 따옴표가 있는가.

 

6. cout 함수 ">>" 스페이스 따옴표 다음 따옴표를 제외한 문자들이 적어도 한개이상 있는가.

 

이때, 따옴표가 있더라도, 따옴표 전에 \(역슬래시)가 있다면 패스한다. (\"를 말하는 거에요!)

 

7. cout 함수 ">>" 스페이스 따옴표 문자들 다음에 따옴표가 있는가.

  

이렇게 비교를 한 후, 양 따옴표(\"는 아니에요!) 사이에

 

문자를 "추출"하고, 이를 출력하는 기능이죠.

 

  

parse 기능은 사실, 프로그램을 만드는 도구에 없어서는 안될 중요한 기능이니

 

알고있으면 유용합니다.

반응형