読者です 読者をやめる 読者になる 読者になる

nl2brのソースを読んでみる

今日も何かphpのソースを読んでみようと思ったが、適当な関数が思い浮かばなかったので、nl2brにしてみた。
ついこの間、nl2brをはじめて知りましたっぽいことを言われてショックだったのを思い出したので。


例によって、cscopeで検索。
Find this text string: に nl2brと入れて検索。

Text string: nl2br

  File              Line
0 basic_functions.c 2741 ZEND_BEGIN_ARG_INFO(arginfo_nl2br, 0)
1 basic_functions.c 3165 PHP_FE(nl2br,               arginfo_nl2br)
2 php_string.h        71 PHP_FUNCTION(nl2br);
3 string.c          3822 /* {{{ proto string nl2br(string str)
4 string.c          3824 PHP_FUNCTION(nl2br)

.hのヘッダファイルはよくわからないので、無視して string.cを開く。
目印は「PHP_FUNCTION」

/* {{{ proto string nl2br(string str)
   Converts newlines to HTML line breaks */
PHP_FUNCTION(nl2br)
{
    /* in brief this inserts <br /> before matched regexp \n\r?|\r\n? */
    zval    **zstr;
    char    *tmp, *str;
    int new_length;
    char    *end, *target;
    int repl_cnt = 0;

    if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &zstr) == FAILURE) {
        WRONG_PARAM_COUNT;
    }

    convert_to_string_ex(zstr);

    str = Z_STRVAL_PP(zstr);
    end = str + Z_STRLEN_PP(zstr);

関数宣言部分とパラメータの取得。
convert_to_string_exとか、Z_STRVAL_PPとかよくわからないので、あとで調べてみよう。

    /* it is really faster to scan twice and allocate mem once insted scanning once
       and constantly reallocing */
    while (str < end) {
        if (*str == '\r') {
            if (*(str+1) == '\n') {
                str++;
            }
            repl_cnt++;
        } else if (*str == '\n') {
            if (*(str+1) == '\r') {
                str++;
            }
            repl_cnt++;
        }

        str++;
    }

    if (repl_cnt == 0) {
        RETURN_STRINGL(Z_STRVAL_PP(zstr), Z_STRLEN_PP(zstr), 1);
    }

次に、\nや\rがあるかどうかを調べてみる。
ここで、ん?と疑問に思ったのが「\r\n」はWindows系の改行コードだが、「\n\r」も対象にしている。
どこで使用されている改行コードなのだろうか??


次に実際に変換処理

    new_length = Z_STRLEN_PP(zstr) + repl_cnt * (sizeof("<br />") - 1);
    tmp = target = emalloc(new_length + 1);

    str = Z_STRVAL_PP(zstr);

    while (str < end) {
        switch (*str) {
            case '\r':
            case '\n':
                *target++ = '<';
                *target++ = 'b';
                *target++ = 'r';
                *target++ = ' ';
                *target++ = '/';
                *target++ = '>';

                if ((*str == '\r' && *(str+1) == '\n') || (*str == '\n' && *(str+1) == '\r')) {
                    *target++ = *str++;
                }
                /* lack of a break; is intentional */
            default:
                *target++ = *str;
        }

        str++;
    }

    *target = '\0';

一度、改行コードがあるかどうか調べてから、再度ループで文字作成処理をしている。
なんとなく2重にループするほうが無駄に思えてしまうが、targetを作成したりするほうが処理が重いんだろうなぁと想像。


最後に値を返す処理

    RETURN_STRINGL(tmp, new_length, 0);
}
/* }}} */


特に目的があって調べ始めたわけではないが、「\n\r」があるんだーという新しい発見があったりした。
で、それを踏まえてドキュメントみたら確かに\n\rについても変換処理がかかれてたりする。
http://jp.php.net/nl2br