web-dev-qa-db-ja.com

PHPのマルチバイトトリム?

どうやらmb_trim の中に - mb_* family なので、自分用に実装しようとしています。

私は最近この正規表現をコメントで見つけました php.net

/(^\s+)|(\s+$)/u

したがって、次のように実装します。

function multibyte_trim($str)
{
    if (!function_exists("mb_trim") || !extension_loaded("mbstring")) {
        return preg_replace("/(^\s+)|(\s+$)/u", "", $str);
    } else {
        return mb_trim($str);
    }
}

正規表現は私には正しいようですが、正規表現にはまったく慣れていません。これは文字列の最初/最後にあるanyUnicodeスペースを効果的に削除しますか?

34
federico-t

標準のtrim関数は、いくつかのスペースとスペースのような文字をトリムします。これらは、ASCII文字として定義されています。これは、0から0100 0000までの特定のバイトを意味します。

Proper UTF-8入力には、バイト0xxx xxxxで構成されるマルチバイト文字が含まれることはありません。 proper UTF-8マルチバイト文字のすべてのバイトは、1xxx xxxxで始まります。

つまり、proper UTF-8シーケンスでは、バイト0xxx xxxxはシングルバイト文字しか参照できません。したがって、PHPのtrim関数は「文字の半分」を削除しません仮定proper UTF-8シーケンスがあります。 (非常に注意してください --不適切 UTF-8シーケンスに注意してください 。)


ASCII=正規表現の\sは、ほとんどがtrimと同じ文字に一致します。

/u修飾子を含むpreg関数は、UTF-8でエンコードされた正規表現でのみ機能し、/\s/uはUTF8にも一致します- nbsp 。改行なしスペースでのこの動作は、それを使用する唯一の利点です。

ASCIIと互換性のない他のエンコーディングのスペース文字を置き換える場合、どちらの方法も機能しません。

つまり、通常のスペースをASCII互換の文字列でトリミングする場合は、trimを使用します。 /\s/uを使用するときは、テキストのnbspの意味に注意してください。


気を付けて:

  $s1 = html_entity_decode(" Hello   "); // the NBSP
  $s2 = " ???? exotic test ホ ???? ";

  echo "\nCORRECT trim: [". trim($s1) ."], [".  trim($s2) ."]";
  echo "\nSAME: [". trim($s1) ."] == [". preg_replace('/^\s+|\s+$/','',$s1) ."]";
  echo "\nBUT: [". trim($s1) ."] != [". preg_replace('/^\s+|\s+$/u','',$s1) ."]";

  echo "\n!INCORRECT trim: [". trim($s2,'???? ') ."]"; // DANGER! not UTF8 safe!
  echo "\nSAFE ONLY WITH preg: [". 
       preg_replace('/^[????\s]+|[????\s]+$/u', '', $s2) ."]";
46
deceze

定義している無限の再帰関数で何をしようとしているのかはわかりませんが、マルチバイトセーフなトリムが必要な場合はこれでうまくいきます。

function mb_trim($str) {
  return preg_replace("/(^\s+)|(\s+$)/us", "", $str); 
}
20
kba

このバージョンは、2番目のオプションパラメータ$ charlistをサポートしています。

function mb_trim ($string, $charlist = null) 
{   
    if (is_null($charlist)) {
        return trim ($string);
    } 

    $charlist = str_replace ('/', '\/', preg_quote ($charlist));
    return preg_replace ("/(^[$charlist]+)|([$charlist]+$)/us", '', $string);
}

ただし、範囲の「..」はサポートされていません。

6
Edson Medina

preg_replace('/^\p{Z}+|\p{Z}+$/u','',$str);を使用して、UTF-8文字列の非ASCII互換スペース(たとえば、改行しないスペース)をトリムすることもできます

\sは、「ascii互換」のスペース文字u修飾子を使用した場合でもにのみ一致します。
だが \p{Z}は、既知のすべてのUnicodeスペース文字に一致します

4
Opty

では、@ edson-medinaのソリューションを使用してバグを修正し、ユニットテストをいくつか追加しました。 mbの対応物をtrim、rtrim、ltrimに与えるために使用する3つの関数を次に示します。

////////////////////////////////////////////////////////////////////////////////////
//Add some multibyte core functions not in PHP
////////////////////////////////////////////////////////////////////////////////////
function mb_trim($string, $charlist = null) {
    if (is_null($charlist)) {
        return trim($string);
    } else {
        $charlist = preg_quote($charlist, '/');
        return preg_replace("/(^[$charlist]+)|([$charlist]+$)/us", '', $string);
    }
}
function mb_rtrim($string, $charlist = null) {
    if (is_null($charlist)) {
        return rtrim($string);
    } else {
        $charlist = preg_quote($charlist, '/');
        return preg_replace("/([$charlist]+$)/us", '', $string);
    }
}
function mb_ltrim($string, $charlist = null) {
    if (is_null($charlist)) {
        return ltrim($string);
    } else {
        $charlist = preg_quote($charlist, '/');
        return preg_replace("/(^[$charlist]+)/us", '', $string);
    }
}
////////////////////////////////////////////////////////////////////////////////////

これは私が興味のある人のために書いた単体テストです:

public function test_trim() {
    $this->assertEquals(trim(' foo '), mb_trim(' foo '));
    $this->assertEquals(trim(' foo ', ' o'), mb_trim(' foo ', ' o'));
    $this->assertEquals('foo', mb_trim(' Åfooホ ', ' Åホ'));
}

public function test_rtrim() {
    $this->assertEquals(rtrim(' foo '), mb_rtrim(' foo '));
    $this->assertEquals(rtrim(' foo ', ' o'), mb_rtrim(' foo ', ' o'));
    $this->assertEquals('foo', mb_rtrim('fooホ ', ' ホ'));
}

public function test_ltrim() {
    $this->assertEquals(ltrim(' foo '), mb_ltrim(' foo '));
    $this->assertEquals(ltrim(' foo ', ' o'), mb_ltrim(' foo ', ' o'));
    $this->assertEquals('foo', mb_ltrim(' Åfoo', ' Å'));
}
4
Michael Taggart

mb_ereg_replaceはそれを回避するようです:

function mb_trim($str,$regex = "(^\s+)|(\s+$)/us") {
    return mb_ereg_replace($regex, "", $str);
}

..しかし、正規表現について十分に理解していないため、「charlist」パラメーターを追加して、人々がtrim()(つまり、トリミングする文字のリスト)にフィードできることを期待できると思います。正規表現をパラメータにしました。

特殊文字の配列を用意してから、charlist内の各文字についてそれをステップ実行し、正規表現文字列を構築するときにそれに応じてエスケープすることができます。

2
trapper_hag

私の2セント

あなたの質問に対する実際の解決策は、外部入力文字列を変更する前に、まずエンコーディングチェックを行う必要があるということです。多くの人は、入力データの「サニタイズと検証」についてはすぐに習得できますが、初期の段階で作業している文字列の基本的な性質(文字エンコーディング)を特定する手順を習得するには時間がかかります。

各文字を表すために何バイトが使用されますか?適切にフォーマットされたUTF-8では、1(trimが扱う文字)、2、3、または4バイトにすることができます。問題は、UTF-8のレガシー表現または不正な表現が登場したときに発生します。バイト文字の境界が期待どおりに整列しない場合があります(素人が話す)。

PHPでは、すべての文字列を適切なUTF-8エンコーディング(1文字あたり1、2、3、または4バイト)に強制的に準拠させる必要があると主張する人もいます。この場合、trim()などの関数は、バイト/それが扱う文字の文字境界は、拡張ASCII/trim()が文字列の最初と最後から削除しようとする1バイトの値に対して一致します(- トリムマニュアルページ )。

ただし、コンピュータプログラミングは多様な分野であるため、すべてのシナリオで機能する包括的なアプローチを実現することはできません。そうは言っても、適切に機能するために必要な方法でアプリケーションを作成してください。フォーム入力でデータベース駆動の基本的なWebサイトを実行するだけですか? はい、私のお金はすべてがUTF-8であることを強制します。

:UTF-8の問題が安定している場合でも、国際化の問題があります。どうして?英語以外の多くの文字セットが2、3、または4バイトのスペースに存在します(コードポイントなど)。明らかに、中国語、日本語、ロシア語、アラビア語、またはヘブライ語のスクリプトを処理する必要があるコンピューターを使用する場合は、すべてが2、3、および4バイトでも機能する必要があります。 PHP trim関数は、デフォルトの文字、またはユーザー指定の文字をトリミングできます。これは特にtrimが一部の漢字を説明するために必要な場合に重要です。

私はむしろ、誰かが私のサイトにアクセスできないという問題に対処し、その後、発生してはならないアクセスと応答の問題に対処します。考えてみると、これは最小限の特権(セキュリティ)とユニバーサルデザイン(アクセシビリティ)の原則に沿っています。

概要

入力データが適切なUTF-8エンコーディングに準拠しない場合は、 例外をスローする を使用できます。 PHPマルチバイト関数 を使用して、エンコーディングや他のマルチバイトライブラリを特定することができます。 PHPがUnicodeを完全にサポートするように記述されている場合(Perl、Java ...)、PHPはPHPユニコードの取り組みは数年前に終了したため、UTF-8マルチバイト文字列を適切に処理するために追加のライブラリを使用する必要があります。/uを追加するだけpreg_replace()へのフラグが全体像を見ていません。

更新:

そうは言っても、次のマルチバイトトリムは、URLのパスコンポーネントからRESTリソースを抽出しようとする場合に役立ちます(当然、クエリ文字列はありません。注:これは便利です。パス文字列をサニタイズして検証した後。

function mb_path_trim($path)
{
    return preg_replace("/^(?:\/)|(?:\/)$/u", "", $path);
}
0