# Newbie question: zend_hash_find

- Thread: https://externals.io/message/26421
- Original archive: https://news-web.php.net/php.internals/26421
- Started by Arnold Daniels on 2006-11-08T18:33:46+00:00
- Messages: 3
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #26421 — Arnold Daniels — 2006-11-08T18:33:46+00:00

URL: https://externals.io/message/26421#26421

Hi,

I'm new at PHP internals and have read the 'Extension Writing' tutorial 
(part 1-3) of devzone.zend.com. I'm writing a set of parsers, to parse 
different kind of configuration formats. The first is an .ini parser. 
The code almost works correctly. The only problem I have is that 
sections get overwritten if appear multiple times in the .ini content.

[abc]
test = 1
[abc]
xyz = 2

Should result in: array('abc'=>array('test'=>1, 'xyz'=>2))
but instead returns: array('abc'=>array('xyz'=>2))

The logical conclusion is that my zend_hash_find call doesn't work 
correctly and is always returning FAILURE. Though I've got no clue 
what's wrong. Perhaps someone could have a quick look at the code.

Thanks a lot,
Arnold


--------------------

/**
 * Parse .ini content into a zend array
 */
PHP_FUNCTION(qconf_ini_parse)
{
    char *content;
    int len;

    char *c;
    char *ln_start;
    char key[ASCIILINESZ+1];
    char val[ASCIILINESZ+1];
    zval *cur_out;
    zval **fnd_sec;

    char t; // boolean: trim value yes/no

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &content, 
&len) == FAILURE) {
        RETURN_NULL();
    }

    array_init(return_value);

    cur_out = return_value;
    ln_start = content;

    for (c=content; c <= content+len; c++) {
        if (*c == '\n' || *c == '\0' || c == content+len) {
            *c = '\0';

            t = 0;
            ln_start = strskp(ln_start); /* Skip leading spaces */
            if (*ln_start!=';' && *ln_start!='#' && *ln_start!=0) { /* 
Skip comment lines */
                if (sscanf(ln_start, "[%[^]]", key)==1) {
                    /* Valid section name */
                    if (zend_hash_find(Z_ARRVAL_P(return_value), key, 
strlen(key), (void**)&fnd_sec) == FAILURE) {
                        ALLOC_INIT_ZVAL(cur_out);
                        array_init(cur_out);
                        add_assoc_zval(return_value, key, cur_out);
                    } else {
                        cur_out = *fnd_sec;
                    }
                } else if (sscanf (ln_start, "%[^=] = \"%[^\"]\"", key, 
val) == 2
                       ||  sscanf (ln_start, "%[^=] = '%[^\']'", key, 
val) == 2
                       ||  (t=1 && sscanf (ln_start, "%[^=] = %[^;#]", 
key, val) == 2)) {
                    strcpy(key, strlwc(strcrop(key)));
                    /*
                     * sscanf cannot handle "" or '' as empty value,
                     * this is done here
                     */
                    if (!strcmp(val, "\"\"") || !strcmp(val, "''")) {
                        val[0] = (char)0;
                    } else if (t)  {
                        strcpy(val, strcrop(val));
                    }
                    add_assoc_string(cur_out, key, val, 1);
                }
            }

            ln_start = c+1;
        }
    }
}

---

## #26422 — M.Sokolewicz — 2006-11-08T18:40:13+00:00

URL: https://externals.io/message/26421#26422 · In reply to: #26421

hi,

Have you tried looking at how the parse_ini_file function works? that 
should answer your questions on this.

- tul

Arnold Daniels wrote:
> Hi,
> 
> I'm new at PHP internals and have read the 'Extension Writing' tutorial 
> (part 1-3) of devzone.zend.com. I'm writing a set of parsers, to parse 
> different kind of configuration formats. The first is an .ini parser. 
> The code almost works correctly. The only problem I have is that 
> sections get overwritten if appear multiple times in the .ini content.
> 
> [abc]
> test = 1
> [abc]
> xyz = 2
> 
> Should result in: array('abc'=>array('test'=>1, 'xyz'=>2))
> but instead returns: array('abc'=>array('xyz'=>2))
> 
> The logical conclusion is that my zend_hash_find call doesn't work 
> correctly and is always returning FAILURE. Though I've got no clue 
> what's wrong. Perhaps someone could have a quick look at the code.
> 
> Thanks a lot,
> Arnold
> 
> 
> --------------------
> 
> /**
> * Parse .ini content into a zend array
> */
> PHP_FUNCTION(qconf_ini_parse)
> {
>    char *content;
>    int len;
> 
>    char *c;
>    char *ln_start;
>    char key[ASCIILINESZ+1];
>    char val[ASCIILINESZ+1];
>    zval *cur_out;
>    zval **fnd_sec;
> 
>    char t; // boolean: trim value yes/no
> 
>    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &content, 
> &len) == FAILURE) {
>        RETURN_NULL();
>    }
> 
>    array_init(return_value);
> 
>    cur_out = return_value;
>    ln_start = content;
> 
>    for (c=content; c <= content+len; c++) {
>        if (*c == '\n' || *c == '\0' || c == content+len) {
>            *c = '\0';
> 
>            t = 0;
>            ln_start = strskp(ln_start); /* Skip leading spaces */
>            if (*ln_start!=';' && *ln_start!='#' && *ln_start!=0) { /* 
> Skip comment lines */
>                if (sscanf(ln_start, "[%[^]]", key)==1) {
>                    /* Valid section name */
>                    if (zend_hash_find(Z_ARRVAL_P(return_value), key, 
> strlen(key), (void**)&fnd_sec) == FAILURE) {
>                        ALLOC_INIT_ZVAL(cur_out);
>                        array_init(cur_out);
>                        add_assoc_zval(return_value, key, cur_out);
>                    } else {
>                        cur_out = *fnd_sec;
>                    }
>                } else if (sscanf (ln_start, "%[^=] = \"%[^\"]\"", key, 
> val) == 2
>                       ||  sscanf (ln_start, "%[^=] = '%[^\']'", key, 
> val) == 2
>                       ||  (t=1 && sscanf (ln_start, "%[^=] = %[^;#]", 
> key, val) == 2)) {
>                    strcpy(key, strlwc(strcrop(key)));
>                    /*
>                     * sscanf cannot handle "" or '' as empty value,
>                     * this is done here
>                     */
>                    if (!strcmp(val, "\"\"") || !strcmp(val, "''")) {
>                        val[0] = (char)0;
>                    } else if (t)  {
>                        strcpy(val, strcrop(val));
>                    }
>                    add_assoc_string(cur_out, key, val, 1);
>                }
>            }
> 
>            ln_start = c+1;
>        }
>    }
> }

---

## #26424 — Matt Wilmas — 2006-11-09T00:34:05+00:00

URL: https://externals.io/message/26421#26424 · In reply to: #26421

Hi Arnold,

zend_hash_find() is always returning FAILURE because its "key_length"
parameter *includes* the terminating '\0'. :-)  So you need strlen(key) + 1.

BTW, are any of your keys going to have numeric values ([123] sections
etc.)?  Then you'll also have a problem with hash_find() as it doesn't
handle numeric string keys since PHP 5 (converting them to a true number
index like when you use $arr['123']), but the add_assoc_* functions do. If
that's an issue, you have to use zend_symtable_find() since PHP 5, which
works exactly like hash_find() in PHP 4.

Hope that helps.


Matt


----- Original Message -----
From: "Arnold Daniels"
Sent: Wednesday, November 08, 2006


> Hi,
>
> I'm new at PHP internals and have read the 'Extension Writing' tutorial
> (part 1-3) of devzone.zend.com. I'm writing a set of parsers, to parse
> different kind of configuration formats. The first is an .ini parser.
> The code almost works correctly. The only problem I have is that
> sections get overwritten if appear multiple times in the .ini content.
>
> [abc]
> test = 1
> [abc]
> xyz = 2
>
> Should result in: array('abc'=>array('test'=>1, 'xyz'=>2))
> but instead returns: array('abc'=>array('xyz'=>2))
>
> The logical conclusion is that my zend_hash_find call doesn't work
> correctly and is always returning FAILURE. Though I've got no clue
> what's wrong. Perhaps someone could have a quick look at the code.
>
> Thanks a lot,
> Arnold
>
>
> --------------------
>
> /**
>  * Parse .ini content into a zend array
>  */
> PHP_FUNCTION(qconf_ini_parse)
> {
>     char *content;
>     int len;
>
>     char *c;
>     char *ln_start;
>     char key[ASCIILINESZ+1];
>     char val[ASCIILINESZ+1];
>     zval *cur_out;
>     zval **fnd_sec;
>
>     char t; // boolean: trim value yes/no
>
>     if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &content,
> &len) == FAILURE) {
>         RETURN_NULL();
>     }
>
>     array_init(return_value);
>
>     cur_out = return_value;
>     ln_start = content;
>
>     for (c=content; c <= content+len; c++) {
>         if (*c == '\n' || *c == '\0' || c == content+len) {
>             *c = '\0';
>
>             t = 0;
>             ln_start = strskp(ln_start); /* Skip leading spaces */
>             if (*ln_start!=';' && *ln_start!='#' && *ln_start!=0) { /*
> Skip comment lines */
>                 if (sscanf(ln_start, "[%[^]]", key)==1) {
>                     /* Valid section name */
>                     if (zend_hash_find(Z_ARRVAL_P(return_value), key,
> strlen(key), (void**)&fnd_sec) == FAILURE) {
>                         ALLOC_INIT_ZVAL(cur_out);
>                         array_init(cur_out);
>                         add_assoc_zval(return_value, key, cur_out);
>                     } else {
>                         cur_out = *fnd_sec;
>                     }
>                 } else if (sscanf (ln_start, "%[^=] = \"%[^\"]\"", key,
> val) == 2
>                        ||  sscanf (ln_start, "%[^=] = '%[^\']'", key,
> val) == 2
>                        ||  (t=1 && sscanf (ln_start, "%[^=] = %[^;#]",
> key, val) == 2)) {
>                     strcpy(key, strlwc(strcrop(key)));
>                     /*
>                      * sscanf cannot handle "" or '' as empty value,
>                      * this is done here
>                      */
>                     if (!strcmp(val, "\"\"") || !strcmp(val, "''")) {
>                         val[0] = (char)0;
>                     } else if (t)  {
>                         strcpy(val, strcrop(val));
>                     }
>                     add_assoc_string(cur_out, key, val, 1);
>                 }
>             }
>
>             ln_start = c+1;
>         }
>     }
> }
