2010-06-03 3 views
2

파일 시스템에서 임의의 데이터를 읽고 유니 코드로 결과를 출력하는 프로그램이 있습니다. 내가 가지고있는 문제는 때때로 파일 이름이 유효한 유니 코드이고 때로는 그렇지 않다는 것입니다. 그래서 (C 또는 C++에서) 문자열을 검사하고 유효한 UTF-8 인코딩인지 말해 줄 수있는 함수가 필요합니다. 그렇지 않은 경우 유효하지 않은 문자를 이스케이프 처리하여 올바른 UTF-8 인코딩이되게하고 싶습니다. 이것은 XML에서 탈출하는 것과는 다릅니다 --- 나는 또한 그렇게해야합니다. 하지만 먼저 유니 코드가 올바른지 확인해야합니다.유니 코드가 유효하지 않은 경우 유니 코드 문자열 및 이스케이프 유효성 확인 (C/C++)

나는 이것을 해킹 할 수있는 코드를 보았지만, 존재하는 경우 작업 코드를 사용하려고합니다. 당신이 (Windows의 MultiByteToWideChar 또는 WideCharToMultiByte 등) 문자 인코딩 변환 기능이있는 경우

+1

ICU 등의 라이브러리를 사용할 수 있습니까? – kennytm

+1

"이스케이프 처리"는 일반적으로 "다음 토큰이 다른 의미로 사용된다는 신호를 나타내는 프리픽스 삽입"을 의미합니다. 예 : ''\ t ''은't '가 문자't '가 아니라 TAB이라는 것을 의미합니다. 이것은 아마 당신이 찾고있는 것이 아닙니다. 예를 들어, 'FF'바이트는 UTF-8로 나타나지 않습니다. 따라서 "FF"문자열이 UTF-8이 아닌 한 "escape"를 삽입해도 상관 없습니다. – MSalters

+0

오른쪽. 바이트 FF가 바이너리에서 발생하면, 그것을 바꿀 필요가있다. 나는 BASE64와 노드에 XML을 BASE64로 인코딩 한 노드를 넣을 수도 있지만 그렇지 않을 것이다. 흠. 내가 8 진수로 탈출 할 수 ... – vy32

답변

1

다음 코드는 잠시 동안 작업 한 IRI 라이브러리를 기반으로합니다.의 섹션 3.2 ("URI를 IRI로 변환")는 유효하지 않은 UTF-8 옥텟을 유효한 UTF-8로 변환하는 것을 다룹니다.

#define IS_IN_RANGE(c, f, l) (((c) >= (f)) && ((c) <= (l))) 

int UTF8BufferToUTF32Buffer(char *Data, int DataLen, unsigned long *Buffer, int BufLen, int *Eaten) 
{ 
    if(Eaten) 
    { 
     *Eaten = 0; 
    } 

    int Result = 0; 

    unsigned char b, b2; 
    unsigned char *ptr = (unsigned char*) Data; 
    unsigned long uc; 

    int i = 0; 
    int seqlen; 

    while(i < DataLen) 
    { 
     if((Buffer) && (!BufLen)) 
      break; 

     b = ptr[i]; 

     if((b & 0x80) == 0) 
     { 
      uc = (unsigned long)(b & 0x7F); 
      seqlen = 1; 
     } 
     else if((b & 0xE0) == 0xC0) 
     { 
      uc = (unsigned long)(b & 0x1F); 
      seqlen = 2; 
     } 
     else if((b & 0xF0) == 0xE0) 
     { 
      uc = (unsigned long)(b & 0x0F); 
      seqlen = 3; 
     } 
     else if((b & 0xF8) == 0xF0) 
     { 
      uc = (unsigned long)(b & 0x07); 
      seqlen = 4; 
     } 
     else 
     { 
      uc = 0; 
      return -1; 
     } 

     if((i+seqlen) > DataLen) 
     { 
      return -1; 
     } 

     for(int j = 1; j < seqlen; ++j) 
     { 
      b = ptr[i+j]; 

      if((b & 0xC0) != 0x80) 
      { 
       return -1; 
      } 
     } 

     switch(seqlen) 
     { 
      case 2: 
      { 
       b = ptr[i]; 

       if(!IS_IN_RANGE(b, 0xC2, 0xDF)) 
       { 
        return -1; 
       } 

       break; 
      } 

      case 3: 
      { 
       b = ptr[i]; 
       b2 = ptr[i+1]; 

       if(((b == 0xE0) && !IS_IN_RANGE(b2, 0xA0, 0xBF)) || 
        ((b == 0xED) && !IS_IN_RANGE(b2, 0x80, 0x9F)) || 
        (!IS_IN_RANGE(b, 0xE1, 0xEC) && !IS_IN_RANGE(b, 0xEE, 0xEF))) 
       { 
        return -1; 
       } 

       break; 
      } 

      case 4: 
      { 
       b = ptr[i]; 
       b2 = ptr[i+1]; 

       if(((b == 0xF0) && !IS_IN_RANGE(b2, 0x90, 0xBF)) || 
        ((b == 0xF4) && !IS_IN_RANGE(b2, 0x80, 0x8F)) || 
        !IS_IN_RANGE(b, 0xF1, 0xF3)) 
       { 
        return -1; 
       } 

       break; 
      } 
     } 

     for(int j = 1; j < seqlen; ++j) 
     { 
      uc = ((uc << 6) | (unsigned long)(ptr[i+j] & 0x3F)); 
     } 

     if(Buffer) 
     { 
      *Buffer++ = uc; 
      --BufLen; 
     } 

     ++Result; 
     i += seqlen; 
    } 

    if(Eaten) 
    { 
     *Eaten = i; 
    } 

    return Result; 
} 

{ 
    std::string filename = "..."; 

    unsigned long ch; 
    int eaten; 

    std::string::size_type i = 0; 
    while(i < filename.length()) 
    { 
     if(UTF8BufferToUTF32Buffer(&filename[i], filename.length()-i, &ch, 1, &eaten) == 1) 
     { 
      i += eaten; 
     } 
     else 
     { 
      // replace the character at filename[i] with your chosen 
      // escaping, and then increment i by the number of 
      // characters used... 
     } 
    } 
} 

당신이해야 할 일은 어떤 종류의 이스케이프를 사용할지 결정하는 것입니다. URI/IRI는 백분율 인코딩 ("NN"은 한 옥텟의 2 자리 16 진수 값)을 사용합니다 ("% NN").

+0

매우 근사합니다. 감사. 나는 이것을 뭉칠 것이다. 인코딩에 대해 결정하지 않았습니다. % NN은 작동하지만 비표준입니다. 한 문자열에서 두 번째 문자열로 처리하고 조작을 위해 C++ 벡터를 사용하면 코드가 좀 더 간단 해집니다. – vy32

+0

코드에서 버그를 발견했습니다. i 번째 문자의 주소를 얻으려면 & filename [i]을 사용하면 안됩니다. 이것은 기본 버퍼의 저장 공간에 대한 가정을합니다. filename.c_str() + i를 사용해야합니다. – vy32

+0

이것은 엄격하게 버그는 아니며 구현 세부 사항입니다. UTF8BufferToUTF32Buffer() 함수 자체는 실제 라이브러리 코드에서 복사 한 것입니다. 나머지는 그 사용법을 보여주는 예제 일뿐입니다. 나는 보통 std :: string을 내 코드에 사용하지 않지만 '[]'연산자의 사용법이 안전한 다른 문자열 클래스를 사용한다. –

0

, 당신은 UTF-32분의 16와 다시에 UTF-8에서 문자열 변환 시도 할 수 있습니다. 원래 문자열이 UTF-8 인 경우 동일한 문자열이 반환됩니다. 그렇지 않으면 오류가 발생하거나 유효성 검사가 필요한 경우 유용하거나 유효하지 않은 UTF-8 바이트로 대체됩니다 (궁극적으로 원하는 것임).

+0

MultiByteToWideChar()의 경우 결과를 확인하기 위해 WideCharToMultiByte()를 호출 할 필요가 없습니다. MultiByteToWideChar()에는 잘못된 문자를 감지하면 GetLastError() = ERROR_NO_UNICODE_TRANSLATION을 사용하여 함수를 실패하게하는 MB_ERR_INVALID_CHARS 플래그가 있습니다. 유효하지 않은 문자를 변환하는 경우 잘못된 문자 만 기본 문자 (대개 '?')로 변환 할 수 있습니다. 이는 결과에서 원래 문자열을 되돌릴 수없는 손실 연산입니다. 변환을 손실이 적게 필요하면 원본을 수동으로 인코딩하십시오. –