문자열에서 UTF8이 아닌 문자 제거


112

제대로 표시되지 않는 문자열에서 UTF8이 아닌 문자를 제거하는 데 문제가 있습니다. 문자는 다음과 같습니다. 0x97 0x61 0x6C 0x6F (16 진수 표현)

제거하는 가장 좋은 방법은 무엇입니까? 정규 표현식이나 다른 것?


1
: 여기에 나열된 솔루션은 내가 섹션 "문자 확인"여기에 내 대답을 찾을 수 있도록 나를 위해 일을하지 않았다 webcollab.sourceforge.net/unicode.html
bobef

이 관련 있지만 반드시 중복, 더 가까운 사촌 :) 같은
웨인 Weibel

답변:


87

정규식 접근 방식 사용 :

$regex = <<<'END'
/
  (
    (?: [\x00-\x7F]                 # single-byte sequences   0xxxxxxx
    |   [\xC0-\xDF][\x80-\xBF]      # double-byte sequences   110xxxxx 10xxxxxx
    |   [\xE0-\xEF][\x80-\xBF]{2}   # triple-byte sequences   1110xxxx 10xxxxxx * 2
    |   [\xF0-\xF7][\x80-\xBF]{3}   # quadruple-byte sequence 11110xxx 10xxxxxx * 3 
    ){1,100}                        # ...one or more times
  )
| .                                 # anything else
/x
END;
preg_replace($regex, '$1', $text);

UTF-8 시퀀스를 검색하여 그룹 1로 캡처합니다. 또한 UTF-8 시퀀스의 일부로 식별 할 수없는 단일 바이트와 일치하지만 캡처하지는 않습니다. 대체는 그룹 1로 캡처 된 모든 것입니다. 이는 유효하지 않은 모든 바이트를 효과적으로 제거합니다.

유효하지 않은 바이트를 UTF-8 문자로 인코딩하여 문자열을 복구 할 수 있습니다. 그러나 오류가 무작위이면 이상한 기호가 남을 수 있습니다.

$regex = <<<'END'
/
  (
    (?: [\x00-\x7F]               # single-byte sequences   0xxxxxxx
    |   [\xC0-\xDF][\x80-\xBF]    # double-byte sequences   110xxxxx 10xxxxxx
    |   [\xE0-\xEF][\x80-\xBF]{2} # triple-byte sequences   1110xxxx 10xxxxxx * 2
    |   [\xF0-\xF7][\x80-\xBF]{3} # quadruple-byte sequence 11110xxx 10xxxxxx * 3 
    ){1,100}                      # ...one or more times
  )
| ( [\x80-\xBF] )                 # invalid byte in range 10000000 - 10111111
| ( [\xC0-\xFF] )                 # invalid byte in range 11000000 - 11111111
/x
END;
function utf8replacer($captures) {
  if ($captures[1] != "") {
    // Valid byte sequence. Return unmodified.
    return $captures[1];
  }
  elseif ($captures[2] != "") {
    // Invalid byte of the form 10xxxxxx.
    // Encode as 11000010 10xxxxxx.
    return "\xC2".$captures[2];
  }
  else {
    // Invalid byte of the form 11xxxxxx.
    // Encode as 11000011 10xxxxxx.
    return "\xC3".chr(ord($captures[3])-64);
  }
}
preg_replace_callback($regex, "utf8replacer", $text);

편집하다:

  • !empty(x)비어 있지 않은 값과 일치합니다 ( "0"비어있는 것으로 간주 됨).
  • x != ""를 포함하여 비어 있지 않은 값과 일치 "0"합니다.
  • x !== ""를 제외한 모든 항목과 일치 ""합니다.

x != "" 이 경우에 사용하기에 가장 좋은 것 같습니다.

나는 또한 경기 속도를 약간 높였습니다. 각 문자를 개별적으로 일치시키는 대신 유효한 UTF-8 문자 시퀀스와 일치시킵니다.


$regex = <<<'END'PHP <5.3.x 대신 무엇을 사용해야 합니까?
serhio 2010

가독성에 약간의 불이익을 주면서 대신 heredoc 형식으로 변환 할 수 있습니다. 또 다른 가능성은 작은 따옴표 문자열을 사용하는 것이지만 주석을 제거해야합니다.
Markus Jarderot 2010

이 줄에 작은 오타가 있으며 비어있는 것으로 간주 되므로 비어있는 대신 elseif (!empty($captures([2])) {사용해야 !== ""합니다 "0". 또한이 기능은 매우 느립니다. 더 빨리 할 수 ​​있습니까?
Kendall Hopkins 2012

2
이 표현에는 중요한 메모리 문제가 있습니다 . 여기를 참조 하십시오 .
Ja͢ck 2013

1
@MarkusJarderot, Regex ....... 음,이 함수는 생산 준비가 되었습니까? 이 기능에 대한 테스트 케이스가 있습니까?
Pacerier

132

utf8_encode()이미 UTF8 문자열에 적용 하면 잘못된 UTF8 출력이 반환됩니다.

이 모든 문제를 해결하는 기능을 만들었습니다. 라고 Encoding::toUTF8()합니다.

문자열의 인코딩이 무엇인지 알 필요가 없습니다. Latin1 (ISO8859-1), Windows-1252 또는 UTF8이거나 문자열에 이들을 혼합 할 수 있습니다. Encoding::toUTF8()모든 것을 UTF8로 변환합니다.

서비스가 데이터 피드를 모두 엉망으로 제공하여 동일한 문자열에서 인코딩을 혼합했기 때문에 그렇게했습니다.

용법:

require_once('Encoding.php'); 
use \ForceUTF8\Encoding;  // It's namespaced now.

$utf8_string = Encoding::toUTF8($mixed_string);

$latin1_string = Encoding::toLatin1($mixed_string);

또 다른 함수 인 Encoding :: fixUTF8 ()을 포함 시켰는데, 이는 UTF8로 여러 번 인코딩 된 왜곡 된 제품으로 보이는 모든 UTF8 문자열을 수정합니다.

용법:

require_once('Encoding.php'); 
use \ForceUTF8\Encoding;  // It's namespaced now.

$utf8_string = Encoding::fixUTF8($garbled_utf8_string);

예 :

echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("FÃÂédÃÂération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");

다음을 출력합니다.

Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football

다운로드 :

https://github.com/neitanod/forceutf8


13
뛰어난 물건! 다른 모든 솔루션은 유효하지 않은 문자를 삭제하지만이 솔루션은이를 수정합니다. 대박.
giorgio79

4
당신은 훌륭한 기능을 수행했습니다! 저는 과거에 XML 피드로 많은 작업을했고 항상 인코딩에 문제가있었습니다. 감사합니다.
Kostanos

5
사랑해. 당신은 나에게 나쁜 UTF8 문자에 대한 "bloomoin"작업의 시간을 절약했습니다. 감사.
John Ballinger 2013

4
환상적이다. 감사합니다
EdgeCaseBerg 2014 년

2
훌륭 해요, 잘 했어요! 이걸 발견해서 다행입니다. 나는 +100으로 투표 할 수 있으면 좋겠다 ;-)
Codebeat

61

mbstring을 사용할 수 있습니다.

$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');

... 유효하지 않은 문자를 제거합니다.

참조 : 유효하지 않은 UTF-8 문자를 물음표로 대체하면 mbstring.substitute_character가 무시 된 것 같습니다.


1
@Alliswell 어느 것? 예를 들어 주시겠습니까?
Frosty Z

물론,<0x1a>
Alliswell

1
@Alliswell 내가 착각 <0x1a>하지 않았다면 인쇄 가능한 문자는 아니지만 완벽하게 유효한 UTF-8 시퀀스입니다. 인쇄 할 수없는 문자에 문제가 있습니까? 이 확인 stackoverflow.com/questions/1176904/...
서리가 내린 Z

네, 그렇습니다. 고마워, 친구!
Alliswell

mb convert를 호출하기 전에 mbstring 대체 문자를 없음으로 설정해야했습니다. ini_set('mbstring.substitute_character', 'none');그렇지 않으면 결과에 물음표가 표시되었습니다.
cby016 19.11.07

21

이 함수는 모든 비 ASCII 문자를 제거합니다. 유용하지만 질문을 해결하지는 못합니다.
이것은 인코딩에 관계없이 항상 작동하는 내 함수입니다.

function remove_bs($Str) {  
  $StrArr = str_split($Str); $NewStr = '';
  foreach ($StrArr as $Char) {    
    $CharNo = ord($Char);
    if ($CharNo == 163) { $NewStr .= $Char; continue; } // keep £ 
    if ($CharNo > 31 && $CharNo < 127) {
      $NewStr .= $Char;    
    }
  }  
  return $NewStr;
}

작동 원리 :

echo remove_bs('Hello õhowå åare youÆ?'); // Hello how are you?

8
왜 모두 대문자로 함수 이름을 사용합니까? Ewww.
Chris Baker

5
그것은 ASCII이며 질문이 원하는 것에 가깝지 않습니다.
misaxi

1
이것은 작동했습니다. Google Maps API가 API 요청 URL에서 'UTF-8이 아닌 문자'로 인해 오류를보고했을 때 문제가 발생했습니다. 범인은 í유효한 UTF-8 문자 인 주소 필드의 문자 였습니다 . 표 참조 . 사기 : API 오류 메시지를 신뢰하지 마십시오 :)
Valentine Shi

17
$text = iconv("UTF-8", "UTF-8//IGNORE", $text);

이것이 내가 사용하는 것입니다. 꽤 잘 작동하는 것 같습니다. http://planetozh.com/blog/2005/01/remove-invalid-characters-in-utf-8/ 에서 가져옴


나를 위해 일하지 않았다. 테스트 된 줄을 첨부 할 수 있기를 원하지만 불행히도 잘못된 문자가 있습니다.
Nir O.

3
죄송합니다. 몇 번 더 테스트 한 후 이것이 제가 생각한대로 작동하지 않는다는 것을 깨달았습니다. 나는 지금 stackoverflow.com/a/8215387/138023을
Znarkus

14

이 시도:

$string = iconv("UTF-8","UTF-8//IGNORE",$string);

iconv manual 에 따르면 이 함수는 첫 번째 매개 변수를 입력 문자 집합으로, 두 번째 매개 변수를 출력 문자 집합으로, 세 번째 매개 변수를 실제 입력 문자열로 사용합니다.

입력 및 출력 문자 집합을 모두 UTF-8로 설정하고 출력 문자 집합 에 //IGNORE플래그를 추가 하면 함수는 출력 문자 집합 으로 표시 할 수없는 입력 문자열의 모든 문자를 삭제 (스트립)합니다. 따라서 실제로 입력 문자열을 필터링합니다.


코드 스 니펫을 덤프하는 대신 답변이 무엇을하는지 설명하십시오.
Tomasz Kowalczyk

3
나는 이것을 시도했지만 //IGNORE유효하지 않은 UTF-8이 있다는 알림을 억제하지 않는 것 같습니다 (물론 내가 알고 있고 수정하고 싶습니다). 매뉴얼에서 높은 평가를받은 코멘트 그것이 몇 년 동안 버그라고 생각하는 것 같습니다.
2015 년

항상 사용하는 것이 좋습니다 iconv. @halfer 아마도 입력 데이터가 utf-8이 아닐 수 있습니다. 또 다른 옵션은 ascii로 다시 변환 한 다음 다시 utf-8로 다시 변환하는 것입니다. 내 경우에는 내가 사용 않았다 iconv처럼$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
m3nda

@ erm3nda :이 사용 사례를 정확히 기억하지 못합니다. 잘못된 문자 집합으로 선언 된 UTF-8 웹 사이트를 구문 분석했을 수 있습니다. 참고해 주셔서 감사합니다. 향후 독자에게 유용 할 것이라고 확신합니다.
halfer

예, 당신은 그것을 위해 무언가, 단지 테스트를 모르고 마지막으로 당신은 키 ;-) 맞는다 경우
m3nda


6

UConverter는 PHP 5.5부터 사용할 수 있습니다. intl 확장자를 사용하고 mbstring을 사용하지 않는 경우 UConverter가 더 나은 선택입니다.

function replace_invalid_byte_sequence($str)
{
    return UConverter::transcode($str, 'UTF-8', 'UTF-8');
}

function replace_invalid_byte_sequence2($str)
{
    return (new UConverter('UTF-8', 'UTF-8'))->convert($str);
}

htmlspecialchars는 PHP 5.4부터 유효하지 않은 바이트 시퀀스를 제거하는 데 사용할 수 있습니다. Htmlspecialchars는 큰 크기의 바이트와 정확성을 처리하기 위해 preg_match보다 낫습니다. 정규 표현식을 사용하여 잘못된 구현을 많이 볼 수 있습니다.

function replace_invalid_byte_sequence3($str)
{
    return htmlspecialchars_decode(htmlspecialchars($str, ENT_SUBSTITUTE, 'UTF-8'));
}

세 가지 좋은 솔루션이 있지만 사용자가 그 중에서 선택하는 방법은 명확하지 않습니다.
Bob Ray

6

문자열에서 잘못된 UTF-8 문자를 삭제하는 함수를 만들었습니다. XML 내보내기 파일을 생성하기 전에 27000 제품에 대한 설명을 지우는 데 사용하고 있습니다.

public function stripInvalidXml($value) {
    $ret = "";
    $current;
    if (empty($value)) {
        return $ret;
    }
    $length = strlen($value);
    for ($i=0; $i < $length; $i++) {
        $current = ord($value{$i});
        if (($current == 0x9) || ($current == 0xA) || ($current == 0xD) || (($current >= 0x20) && ($current <= 0xD7FF)) || (($current >= 0xE000) && ($current <= 0xFFFD)) || (($current >= 0x10000) && ($current <= 0x10FFFF))) {
                $ret .= chr($current);
        }
        else {
            $ret .= "";
        }
    }
    return $ret;
}

위의 모든 복잡한 답변 중에서 이것은 나를 위해 트릭을했습니다! 감사.
Emin Özlem 2016

이 기능에 혼란 스럽습니다. ord()0-255 범위의 결과를 반환합니다. if이 함수 의 거인 ord()은 절대 반환되지 않는 유니 코드 범위를 테스트합니다 . 누군가이 기능이 작동하는 이유를 명확히하고 싶다면 통찰력을 고맙게 생각합니다.
i336_

4

2019에 오신 것을 환영합니다. /uUTF-8 멀티 바이트 문자를 처리 할 정규 표현식 의 수정 자입니다.

사용하는 경우에만 mb_convert_encoding($value, 'UTF-8', 'UTF-8')문자열에 인쇄 할 수없는 문자가 표시됩니다.

이 방법은 다음을 수행합니다.

  • 다음을 사용하여 잘못된 UTF-8 멀티 바이트 문자를 모두 제거하십시오. mb_convert_encoding
  • \r, \x00(NULL-byte) 및 기타 제어 문자와 같은 인쇄 할 수없는 문자를 모두 제거하십시오.preg_replace

방법:

function utf8_filter(string $value): string{
    return preg_replace('/[^[:print:]\n]/u', '', mb_convert_encoding($value, 'UTF-8', 'UTF-8'));
}

[:print:]모든 인쇄 가능한 문자 및 줄 \n바꿈을 일치시키고 다른 모든 것을 제거하십시오.

아래의 ASCII 테이블을 볼 수 있습니다. 인쇄 가능한 문자는 32에서 127까지 범위이지만 newline \n은 0에서 31까지 범위의 제어 문자의 일부이므로 정규식에 개행 문자를 추가해야합니다./[^[:print:]\n]/u

https://cdn.shopify.com/s/files/1/1014/5789/files/Standard-ASCII-Table_large.jpg?10669400161723642407

\x7F(DEL), \x1B(Esc) 등과 같이 인쇄 가능한 범위를 벗어난 문자로 정규식을 통해 문자열을 보내려고 할 수 있습니다 .

function utf8_filter(string $value): string{
    return preg_replace('/[^[:print:]\n]/u', '', mb_convert_encoding($value, 'UTF-8', 'UTF-8'));
}

$arr = [
    'Danish chars'          => 'Hello from Denmark with æøå',
    'Non-printable chars'   => "\x7FHello with invalid chars\r \x00"
];

foreach($arr as $k => $v){
    echo "$k:\n---------\n";
    
    $len = strlen($v);
    echo "$v\n(".$len.")\n";
    
    $strip = utf8_decode(utf8_filter(utf8_encode($v)));
    $strip_len = strlen($strip);
    echo $strip."\n(".$strip_len.")\n\n";
    
    echo "Chars removed: ".($len - $strip_len)."\n\n\n";
}

https://www.tehplayground.com/q5sJ3FOddhv1atpR


2047 년에 환영합니다 php-mbstring. 기본적으로는 PHP로 포장되지 않습니다.
NVRM

3
$string = preg_replace('~&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i', '$1', htmlentities($string, ENT_COMPAT, 'UTF-8'));

2

최근 패치에서 Drupal의 피드 JSON 파서 모듈까지 :

//remove everything except valid letters (from any language)
$raw = preg_replace('/(?:\\\\u[\pL\p{Zs}])+/', '', $raw);

걱정된다면 공백을 유효한 문자로 유지합니다.

내가 필요한 것을 했어요. MySQL의 'utf8'문자 집합에 맞지 않고 "SQLSTATE [HY000] : 일반 오류 : 1366 잘못된 문자열 값"과 같은 오류를 발생시킨 요즘 널리 퍼져있는 이모 지 문자를 제거합니다.

자세한 내용은 https://www.drupal.org/node/1824506#comment-6881382를 참조 하십시오.


iconv기반 구식 정규 표현식보다 훨씬 더 나은 preg_replace현재 사용되지 않습니다 느릅 나무.
m3nda


1
당신은 완전히 옳습니다 ereg_replace().
m3nda

2

아마도 가장 정확한 솔루션은 아니지만 한 줄의 코드로 작업을 완료합니다.

echo str_replace("?","",(utf8_decode($str)));

utf8_decode문자를 물음표로 변환합니다.
str_replace물음표를 제거합니다.


수백 개의 솔루션을 시도한 후 효과가 있었던 유일한 솔루션은 귀하의 것입니다.
Haritsinh Gohil

1

따라서 규칙은 첫 번째 UTF-8 옥 틀릿이 마커로 높은 비트를 설정 한 다음 추가 옥 틀릿 수를 나타내는 1 ~ 4 비트입니다. 그런 다음 각 추가 8 진수는 상위 2 비트를 10으로 설정해야합니다.

의사 파이썬은 다음과 같습니다.

newstring = ''
cont = 0
for each ch in string:
  if cont:
    if (ch >> 6) != 2: # high 2 bits are 10
      # do whatever, e.g. skip it, or skip whole point, or?
    else:
      # acceptable continuation of multi-octlet char
      newstring += ch
    cont -= 1
  else:
    if (ch >> 7): # high bit set?
      c = (ch << 1) # strip the high bit marker
      while (c & 1): # while the high bit indicates another octlet
        c <<= 1
        cont += 1
        if cont > 4:
           # more than 4 octels not allowed; cope with error
      if !cont:
        # illegal, do something sensible
      newstring += ch # or whatever
if cont:
  # last utf-8 was not terminated, cope

이 동일한 논리를 php로 번역 할 수 있어야합니다. 그러나 잘못된 캐릭터가 생기면 어떤 종류의 스트리핑을해야하는지 명확하지 않습니다.


c = (ch << 1)(c & 1)루프를 건너 뛰고 처음에는 0이 됩니다 . 테스트는 아마도(c & 128)
Markus Jarderot

1

유니 코드 기본 언어 영역 외부의 모든 유니 코드 문자를 제거하려면 :

$str = preg_replace("/[^\\x00-\\xFFFF]/", "", $str);

0

질문과 약간 다르지만 내가하는 일은 HtmlEncode (string)을 사용하는 것입니다.

여기에 의사 코드

var encoded = HtmlEncode(string);
encoded = Regex.Replace(encoded, "&#\d+?;", "");
var result = HtmlDecode(encoded);

입력과 출력

"Headlight\x007E Bracket, &#123; Cafe Racer<> Style, Stainless Steel 中文呢?"
"Headlight~ Bracket, &#123; Cafe Racer<> Style, Stainless Steel 中文呢?"

나는 그것이 완벽하지 않다는 것을 알고 있지만 나를 위해 일합니다.


0
static $preg = <<<'END'
%(
[\x09\x0A\x0D\x20-\x7E]
| [\xC2-\xDF][\x80-\xBF]
| \xE0[\xA0-\xBF][\x80-\xBF]
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
| \xED[\x80-\x9F][\x80-\xBF]
| \xF0[\x90-\xBF][\x80-\xBF]{2}
| [\xF1-\xF3][\x80-\xBF]{3}
| \xF4[\x80-\x8F][\x80-\xBF]{2}
)%xs
END;
if (preg_match_all($preg, $string, $match)) {
    $string = implode('', $match[0]);
} else {
    $string = '';
}

그것은 우리의 서비스에서 작동합니다


2
코드 전용 답변 대신이 질문에 대한 답변을 설명하기 위해 컨텍스트를 추가 할 수 있습니까?
Arun Vinoth

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.