+/* {{{ static zend_bool *bbcode_check_tag(HashTable *tag TSRMLS_DC) {
+ * Check that tag hash is valid */
+static zend_string *bbcode_check_tag(HashTable *tag TSRMLS_DC) {
+ /* Key numeric value */
+ zend_long idx;
+ /* Key and root string */
+ zend_string *key, *root = NULL;
+ /* Value */
+ zval *val;
+
+ /* Key position */
+ int pos = 0;
+
+ /* Has a root */
+ int hasRoot = 0;
+
+ /* Iterate on each key val until hash end is reached */
+ ZEND_HASH_FOREACH_KEY_VAL(tag, idx, key, val) {
+ /* Check if key is not a string */
+ if (key == NULL) {
+ /* Display error about long key */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %ld[%d] from argument tag is not an expected string", idx, pos);
+ /* Return failure */
+ return NULL;
+ /* Check that key do not contains a special character */
+ } else if (strchr(ZSTR_VAL(key), '=') != NULL || strchr(ZSTR_VAL(key), '[') != NULL || strchr(ZSTR_VAL(key), ']') != NULL || strchr(ZSTR_VAL(key), '(') != NULL || strchr(ZSTR_VAL(key), ')') != NULL || strchr(ZSTR_VAL(key), '/') != NULL || strchr(ZSTR_VAL(key), '\\') != NULL) {
+ /* Display error about key containing a special char */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag contains a special forbidden character: =[]()/\\", ZSTR_VAL(key), pos);
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Check that value exists */
+ if (val == NULL) {
+ /* Display error about long key */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos);
+ /* Return failure */
+ return NULL;
+ /* Check that value is an array */
+ } else if (Z_TYPE_P(val) != IS_ARRAY) {
+ /* Display error about long key */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, BBCODE_GET_TYPE(Z_TYPE_P(val)), Z_TYPE_P(val));
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Store child tag array */
+ HashTable *ctag = Z_ARR_P(val);
+ /* Child key numeric value */
+ zend_long cidx;
+ /* Child key string */
+ zend_string *ckey;
+ /* Child value */
+ zval *cval;
+
+ /* Buffer */
+ char *buf;
+
+ /* Child key position */
+ int cpos = 0, type, len;
+
+ /* Detect if entry has a type, parent, child, open, close, default or arg key to display error if mandatory field is missing */
+ unsigned char hasType = 0, hasParent = 0, hasChild = 0, hasOpen = 0, hasClose = 0, hasDefault = 0, hasArg = 0;
+
+ /* Iterate on each ckey cval until hash end is reached */
+ ZEND_HASH_FOREACH_KEY_VAL(ctag, cidx, ckey, cval) {
+ /* Check if ckey is a string */
+ if (!ckey) {
+ /* Display error about long ckey */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d]/%ld[%d] from argument tag is not an expected string", ZSTR_VAL(key), pos, cidx, cpos);
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Check if ckey is empty */
+ if (ZSTR_LEN(ckey) == 0) {
+ /* Display error about long key */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d]/%s[%d] from argument tag is empty not one of expected type|parent|child|open|close|default string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+ /* Return failure */
+ return NULL;
+ /* Check if current ckey is type */
+ } else if (strcmp("type", ZSTR_VAL(ckey)) == 0) {
+ /* Extract type */
+ type = Z_LVAL_P(cval);
+
+ /* Check that current type is valid */
+ if (
+ type != BBCODE_TYPE_ROOT &&
+ type != BBCODE_TYPE_MULTI &&
+ type != BBCODE_TYPE_SINGLE
+ ) {
+ /* Display error about unexpected type value */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value '%d' for key %s[%d]/%s[%d] from argument tag is not one of expected BBCODE::TYPE_(ROOT|MULTI|SINGLE) constant", type, ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+ /* Return failure */
+ return NULL;
+ /* Check if root */
+ } else if (type == BBCODE_TYPE_ROOT) {
+ /* Grow hasRoot */
+ hasRoot++;
+ }
+
+ /* Set hasType */
+ hasType = 1;
+ /* Check if current key is parent */
+ } else if (strcmp("parent", ZSTR_VAL(ckey)) == 0) {
+ /* Check that value exists */
+ if (cval == NULL) {
+ /* Display error about long key */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+ /* Return failure */
+ return NULL;
+ /* Check that value is an array */
+ } else if (Z_TYPE_P(cval) != IS_ARRAY) {
+ /* Display error about long key */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, BBCODE_GET_TYPE(Z_TYPE_P(cval)), Z_TYPE_P(cval));
+ /* Return failure */
+ return NULL;
+ /* Check that value is an non empty array */
+ } else if (zend_array_count(Z_ARR_P(cval)) == 0) {
+ /* Display error about empty parent array */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is an empty array instead of expected filled array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Parent value */
+ zval *pval;
+
+ /* Child key position */
+ int ppos = 0;
+
+ /* Iterate on each val until hash end is reached */
+ ZEND_HASH_FOREACH_VAL(Z_ARR_P(cval), pval) {
+ /* Check that parent val is a string */
+ /* XXX: pval == NULL case is catched here too */
+ if (Z_TYPE_P(pval) != IS_STRING) {
+ /* Display error about not string */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d]/[%d] from argument tag is %s[%d] instead of expected string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, ppos, BBCODE_GET_TYPE(Z_TYPE_P(pval)), Z_TYPE_P(pval));
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Tag key */
+ zend_string *tkey;
+
+ /* Reset found flag */
+ unsigned char found = 0;
+
+ /* Iterate on each key until hash end is reached */
+ ZEND_HASH_FOREACH_STR_KEY(tag, tkey) {
+ /* Check that tkey is a string and is identical */
+ if (tkey != NULL && strcmp(Z_STRVAL_P(pval), ZSTR_VAL(tkey)) == 0) {
+ /* We found parent value in tag keys*/
+ found = 1;
+ }
+ } ZEND_HASH_FOREACH_END();
+
+ /* Check if we found the key */
+ if (!found) {
+ /* Display error about long key */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d]/%s[%d] from argument tag is not present in tag keys", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, Z_STRVAL_P(pval), ppos);
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Grow ppos */
+ ppos++;
+ } ZEND_HASH_FOREACH_END();
+
+ /* Set hasParent */
+ hasParent = 1;
+ /* Check if current key is child */
+ } else if (strcmp("child", ZSTR_VAL(ckey)) == 0) {
+ /* Check that value exists */
+ if (cval == NULL) {
+ /* Display error about long key */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+ /* Return failure */
+ return NULL;
+ /* Check that value is an array */
+ } else if (Z_TYPE_P(cval) != IS_ARRAY) {
+ /* Display error about long key */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, BBCODE_GET_TYPE(Z_TYPE_P(cval)), Z_TYPE_P(cval));
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Child value */
+ zval *ccval;
+
+ /* Child key position */
+ int ccpos = 0;
+
+ /* Iterate on each val until hash end is reached */
+ ZEND_HASH_FOREACH_VAL(Z_ARR_P(cval), ccval) {
+ /* Check that child val is a string */
+ /* XXX: ccval == NULL case is catched here too */
+ if (Z_TYPE_P(ccval) != IS_STRING) {
+ /* Display error about not string */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d]/[%d] from argument tag is %s[%d] instead of expected string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, ccpos, BBCODE_GET_TYPE(Z_TYPE_P(ccval)), Z_TYPE_P(ccval));
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Tag key */
+ zend_string *tkey;
+
+ /* Reset found flag */
+ unsigned char found = 0;
+
+ /* Iterate on each key until hash end is reached */
+ ZEND_HASH_FOREACH_STR_KEY(tag, tkey) {
+ /* Check that tkey is a string and is identical */
+ if (tkey != NULL && strcmp(Z_STRVAL_P(ccval), ZSTR_VAL(tkey)) == 0) {
+ /* We found child value in tag keys*/
+ found = 1;
+ }
+ } ZEND_HASH_FOREACH_END();
+
+ /* Check if we found the key */
+ if (!found) {
+ /* Display error about long key */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Child value %s for key %s[%d]/%s[%d] is not present in tag keys", Z_STRVAL_P(ccval), ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Grow ccpos */
+ ccpos++;
+ } ZEND_HASH_FOREACH_END();
+
+ /* Set hasChild */
+ hasChild = 1;
+ /* Check if current key is open */
+ } else if (strcmp("open", ZSTR_VAL(ckey)) == 0) {
+ /* Set hasOpen */
+ hasOpen = 1;
+ /* Check if current key is close */
+ } else if (strcmp("close", ZSTR_VAL(ckey)) == 0) {
+ /* Set hasClose */
+ hasClose = 1;
+ /* Check if current key is default */
+ } else if (strcmp("default", ZSTR_VAL(ckey)) == 0) {
+ /* Set hasDefault */
+ hasDefault = 1;
+ /* Check if current key is arg */
+ } else if (strcmp("arg", ZSTR_VAL(ckey)) == 0) {
+ /* Set hasArg */
+ hasArg = 1;
+ /* Check if current key is unknown */
+ } else {
+ /* Display error about long key */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d]/%s[%d] from argument tag is not one of expected type|parent|child|open|close|default string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Grow cpos */
+ cpos++;
+ } ZEND_HASH_FOREACH_END();
+
+ /* Check if val array is immutable */
+ /* XXX: this strange case happen with empty val array, the zend engine don't initialize a usable hashtable, to avoid triggering a segfault later when using the array, we fix it here */
+ if ((Z_GC_FLAGS_P(val) & GC_IMMUTABLE) == GC_IMMUTABLE) {
+ /* Allocate hash table */
+ /* XXX: this is required to avoid segfault in zend_hash_add when the array is immutable */
+ ALLOC_HASHTABLE(Z_ARR_P(val));
+
+ /* Init hash table */
+ zend_hash_init(Z_ARR_P(val), 0, NULL, ZVAL_PTR_DTOR, 0);
+
+ /* Copy old values in new array */
+ //XXX: disabled for now, array shoudln't need to be copied, immutable flag seems only present on empty arrays
+ //zend_hash_copy(Z_ARR_P(val), ctag, (copy_ctor_func_t) zval_add_ref);
+ }
+
+ /* Check if entry has no type */
+ /* TODO: make depend this code on a parse flag ? */
+ if (!hasType) {
+ /* Type key */
+ zend_string *tkey = zend_string_init("type", strlen("type"), 0);
+
+ /* Type value */
+ zval tval;
+
+ /* Check if key is '' */
+ if (ZSTR_LEN(key) == 0) {
+ /* Set tval value to root */
+ ZVAL_LONG(&tval, (type = BBCODE_TYPE_ROOT));
+
+ /* Grow hasRoot */
+ hasRoot++;
+ /* Check if key is img */
+ } else if (strcmp("img", ZSTR_VAL(key)) == 0) {
+ /* Set tval value to single */
+ ZVAL_LONG(&tval, (type = BBCODE_TYPE_SINGLE));
+ /* Handle other key as multi */
+ } else {
+ /* Set tval value to multi */
+ ZVAL_LONG(&tval, (type = BBCODE_TYPE_MULTI));
+ }
+
+ /* Add new type key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), tkey, &tval) != NULL);
+
+ /* Free type key */
+ zend_string_release(tkey);
+
+ /* Set hasType */
+ hasType = 1;
+ }
+
+ /* Check for root type */
+ if (type == BBCODE_TYPE_ROOT) {
+ /* Set root */
+ root = key;
+
+ /* Check if has parent */
+ if (hasParent) {
+ /* Display error about parent entry */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_ROOT cannot have a parent entry", ZSTR_VAL(key), pos);
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Check if has open entry */
+ if (!hasOpen) {
+ /* Key string */
+ zend_string *mkey = zend_string_init("open", strlen("open"), 0);
+
+ /* Value */
+ zval mval;
+
+ /* Check if tag is empty */
+ if (ZSTR_LEN(key) == 0) {
+ /* Init new value */
+ ZVAL_EMPTY_STRING(&mval);
+ /* Non empty tag */
+ } else {
+ /* Init buffer and length */
+ buf = (char *)malloc((len = strlen("<>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0))*sizeof(char));
+
+ /* Generate string in */
+ ZEND_ASSERT(sprintf(buf, "<%s%s>", ZSTR_VAL(key), hasArg?"%s":"") == len);
+
+ /* Init new value */
+ ZVAL_STRINGL(&mval, buf, len);
+
+ /* Free buffer */
+ free(buf);
+ }
+
+ /* Add new key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL);
+
+ /* Free key */
+ zend_string_release(mkey);
+
+ /* Set hasOpen */
+ hasOpen = 1;
+ }
+
+ /* Check if has close entry */
+ if (!hasClose) {
+ /* Key string */
+ zend_string *mkey = zend_string_init("close", strlen("close"), 0);
+
+ /* Value */
+ zval mval;
+
+ /* Check if tag is empty */
+ if (ZSTR_LEN(key) == 0) {
+ /* Init new value */
+ ZVAL_EMPTY_STRING(&mval);
+ /* Non empty tag */
+ } else {
+ /* Init buffer and length */
+ buf = (char *)malloc((len = strlen("</>") + ZSTR_LEN(key))*sizeof(char));
+
+ /* Generate string in */
+ ZEND_ASSERT(sprintf(buf, "</%s>", ZSTR_VAL(key)) == len);
+
+ /* Init new value */
+ ZVAL_STRINGL(&mval, buf, len);
+
+ /* Free buffer */
+ free(buf);
+ }
+
+ /* Add new key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL);
+
+ /* Free key */
+ zend_string_release(mkey);
+
+ /* Set hasClose */
+ hasClose = 1;
+ }
+
+ /* Check if has not default */
+ if (!hasDefault) {
+ /* Key string */
+ zend_string *mkey = zend_string_init("default", strlen("default"), 0);
+
+ /* Value */
+ zval mval;
+
+ /* Set value */
+ ZVAL_STRING(&mval, "%s");
+
+ /* Add new key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL);
+
+ /* Free key */
+ zend_string_release(mkey);
+
+ /* Set hasDefault */
+ hasDefault = 1;
+ }
+
+ /* Compute child here */
+ if (!hasChild) {
+ /* Child value */
+ zval cval;
+
+ /* Init new child val array */
+ ZVAL_NEW_ARR(&cval);
+
+ /* Init hash table */
+ zend_hash_init(Z_ARR(cval), 0, NULL, ZVAL_PTR_DTOR, 0);
+
+ /* Compute and set child here */
+ /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
+ if (bbcode_gen_child(Z_ARR(cval), tag, key) != NULL) {
+ /* Child key string */
+ zend_string *ckey = zend_string_init("child", strlen("child"), 0);
+
+ /* Add new child key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), ckey, &cval) != NULL);
+
+ /* Free type key */
+ zend_string_release(ckey);
+
+ /* Set hasChild */
+ hasChild = 1;
+ /* Child generation reached an error case */
+ /* XXX: it will trigger error later */
+ } else {
+ /* Release hash table */
+ zend_array_destroy(Z_ARR(cval));
+ }
+ }
+ /* Check for multi type */
+ } else if (type == BBCODE_TYPE_MULTI) {
+ /* Check if has open entry */
+ if (!hasOpen) {
+ /* Key string */
+ zend_string *mkey = zend_string_init("open", strlen("open"), 0);
+
+ /* Value */
+ zval mval;
+
+ /* Check if tag is empty */
+ if (ZSTR_LEN(key) == 0) {
+ /* Init new value */
+ ZVAL_EMPTY_STRING(&mval);
+ /* Non empty tag */
+ } else {
+ /* Init buffer and length */
+ buf = (char *)malloc((len = strlen("<>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0))*sizeof(char));
+
+ /* Generate string in */
+ ZEND_ASSERT(sprintf(buf, "<%s%s>", ZSTR_VAL(key), hasArg?"%s":"") == len);
+
+ /* Init new value */
+ ZVAL_STRINGL(&mval, buf, len);
+
+ /* Free buffer */
+ free(buf);
+ }
+
+ /* Add new key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL);
+
+ /* Free key */
+ zend_string_release(mkey);
+
+ /* Set hasOpen */
+ hasOpen = 1;
+ }
+
+ /* Check if has close entry */
+ if (!hasClose) {
+ /* Key string */
+ zend_string *mkey = zend_string_init("close", strlen("close"), 0);
+
+ /* Value */
+ zval mval;
+
+ /* Check if tag is empty */
+ if (ZSTR_LEN(key) == 0) {
+ /* Init new value */
+ ZVAL_EMPTY_STRING(&mval);
+ /* Non empty tag */
+ } else {
+ /* Init buffer and length */
+ buf = (char *)malloc((len = strlen("</>") + ZSTR_LEN(key))*sizeof(char));
+
+ /* Generate string in */
+ ZEND_ASSERT(sprintf(buf, "</%s>", ZSTR_VAL(key)) == len);
+
+ /* Init new value */
+ ZVAL_STRINGL(&mval, buf, len);
+
+ /* Free buffer */
+ free(buf);
+ }
+
+ /* Add new key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL);
+
+ /* Free key */
+ zend_string_release(mkey);
+
+ /* Set hasClose */
+ hasClose = 1;
+ }
+
+ /* Check if has not default */
+ if (!hasDefault) {
+ /* Key string */
+ zend_string *mkey = zend_string_init("default", strlen("default"), 0);
+
+ /* Value */
+ zval mval;
+
+ /* Set value */
+ ZVAL_STRING(&mval, "%s");
+
+ /* Add new key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL);
+
+ /* Free key */
+ zend_string_release(mkey);
+
+ /* Set hasDefault */
+ hasDefault = 1;
+ }
+
+ /* Compute parent here */
+ if (!hasParent) {
+ /* Parent key string */
+ zend_string *pkey = zend_string_init("parent", strlen("parent"), 0);
+
+ /* Parent value */
+ zval pval;
+
+ /* Init new parent val array */
+ ZVAL_NEW_ARR(&pval);
+
+ /* Init hash table */
+ zend_hash_init(Z_ARR(pval), 0, NULL, ZVAL_PTR_DTOR, 0);
+
+ /* Compute and set parent here */
+ /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
+ /* TODO: change this assert to a if() and free pval on error, error will trigger in later loop (code coverage tests to do) */
+ ZEND_ASSERT(bbcode_gen_parent(Z_ARR(pval), tag, key) != NULL);
+
+ /* Add new parent key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), pkey, &pval) != NULL);
+
+ /* Free type key */
+ zend_string_release(pkey);
+
+ /* Set hasParent */
+ hasParent = 1;
+ }
+
+ /* Compute child here */
+ if (!hasChild) {
+ /* Child value */
+ zval cval;
+
+ /* Init new child val array */
+ ZVAL_NEW_ARR(&cval);
+
+ /* Init hash table */
+ zend_hash_init(Z_ARR(cval), 0, NULL, ZVAL_PTR_DTOR, 0);
+
+ /* Compute and set child here */
+ /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
+ if (bbcode_gen_child(Z_ARR(cval), tag, key) != NULL) {
+ /* Child key string */
+ zend_string *ckey = zend_string_init("child", strlen("child"), 0);
+
+ /* Add new child key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), ckey, &cval) != NULL);
+
+ /* Free type key */
+ zend_string_release(ckey);
+
+ /* Set hasChild */
+ hasChild = 1;
+ /* Child generation reached an error case */
+ /* XXX: it will trigger error later */
+ } else {
+ /* Release hash table */
+ zend_array_destroy(Z_ARR(cval));
+ }
+ }
+ /* Check for single type */
+ } else if (type == BBCODE_TYPE_SINGLE) {
+ /* Check if has open entry */
+ if (hasOpen) {
+ /* Display error about open entry */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have an open entry", ZSTR_VAL(key), pos);
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Check if has close entry */
+ if (hasClose) {
+ /* Display error about close entry */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a close entry", ZSTR_VAL(key), pos);
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Check if has not default */
+ if (!hasDefault) {
+ /* Key string */
+ zend_string *skey = zend_string_init("default", strlen("default"), 0);
+
+ /* Value */
+ zval sval;
+
+ /* Check if key is img */
+ if (strcmp("img", ZSTR_VAL(key)) == 0) {
+ /* Init buffer and length */
+ buf = (char *)malloc((len = strlen("<img src=\"%s\"/>") + (hasArg?strlen("%s"):0))*sizeof(char));
+
+ /* Generate string in */
+ ZEND_ASSERT(sprintf(buf, "<img src=\"%%s\"%s/>", hasArg?"%s":"") == len);
+ } else {
+ /* Init buffer and length */
+ buf = (char *)malloc((len = strlen("</>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0))*sizeof(char));
+
+ /* Generate string in */
+ ZEND_ASSERT(sprintf(buf, "<%s%s/>", ZSTR_VAL(key), hasArg?"%s":"") == len);
+ }
+
+ /* Init new value */
+ ZVAL_STRINGL(&sval, buf, len);
+
+ /* Free buffer */
+ free(buf);
+
+ /* Add new key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), skey, &sval) != NULL);
+
+ /* Free key */
+ zend_string_release(skey);
+
+ /* Set hasDefault */
+ hasDefault = 1;
+ }
+
+ /* Check if has child */
+ if (hasChild) {
+ /* Display error about child entry */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a child entry", ZSTR_VAL(key), pos);
+ /* Return failure */
+ return NULL;
+ }
+
+ /* Compute parent here */
+ if (!hasParent) {
+ /* Parent key string */
+ zend_string *pkey = zend_string_init("parent", strlen("parent"), 0);
+
+ /* Parent value */
+ zval pval;
+
+ /* Init new parent val array */
+ ZVAL_NEW_ARR(&pval);
+
+ /* Init hash table */
+ zend_hash_init(Z_ARR(pval), 0, NULL, ZVAL_PTR_DTOR, 0);
+
+ /* Compute and set parent here */
+ /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
+ /* TODO: change this asser to a if() and free pval on error, error will trigger in later loop (code coverage tests to do) */
+ ZEND_ASSERT(bbcode_gen_parent(Z_ARR(pval), tag, key) != NULL);
+
+ /* Add new parent key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), pkey, &pval) != NULL);
+
+ /* Free type key */
+ zend_string_release(pkey);
+
+ /* Set hasParent */
+ hasParent = 1;
+ }
+ }
+
+ /* Generate open and close pattern for tag with child */
+ if (hasChild) {
+ /* Child array and value */
+ zval *child, *ccval;
+
+ /* Close pattern length */
+ int len2, cpos2;
+
+ /* Close pattern buf */
+ char *buf2;
+
+ /* Retrieve child array */
+ /* XXX: child should exists here and be an array */
+ ZEND_ASSERT(((child = zend_hash_str_find(Z_ARR_P(val), "child", strlen("child"))) != NULL) && Z_TYPE_P(child) == IS_ARRAY);
+
+ /* Init open pattern buffer and length */
+ /* XXX: lagest bbcode tag length seems to be 7, we guess that 10 is enough to contain every tag and pipe */
+ buf = (char *)malloc((len = strlen("/\\[()(?:=([^\\]]*))?\\]/") + zend_array_count(Z_ARR_P(child))*10)*sizeof(char));
+
+ /* Init close pattern buffer and length */
+ /* XXX: lagest bbcode tag length seems to be 7, we guess that 10 is enough to contain every tag and pipe */
+ buf2 = (char *)malloc((len2 = strlen("/\\[\\/()\\]/") + zend_array_count(Z_ARR_P(child))*10)*sizeof(char));
+
+ /* Init cpos and cpos2 */
+ cpos = 4; cpos2 = 6;
+
+ /* Assign open pattern begin */
+ ZEND_ASSERT(memcpy(buf, &"/\\[(", 4) != NULL);
+
+ /* Assign close pattern begin */
+ ZEND_ASSERT(memcpy(buf2, &"/\\[\\/(", 6) != NULL);
+
+ /* Iterate on each val until hash end is reached */
+ ZEND_HASH_FOREACH_VAL(Z_ARR_P(child), ccval) {
+ /* Check that child val is a string */
+ /* XXX: ccval should be an array here */
+ ZEND_ASSERT(Z_TYPE_P(ccval) == IS_STRING);
+
+ /* Init zval node and type */
+ zval *znode, *ztype;
+
+ /* Init zend_long type */
+ zend_long ltype;
+
+ /* Retrieve the node */
+ ZEND_ASSERT((znode = zend_hash_find(tag, Z_STR_P(ccval))) != NULL && Z_TYPE_P(znode) == IS_ARRAY);
+
+ /* Try to retrieve the type */
+ //ZEND_ASSERT((ztype = zend_hash_str_find(Z_ARR_P(znode), "type", strlen("type"))) != NULL && Z_TYPE_P(ztype) == IS_LONG);
+ /* XXX: the type may not yet available here, it is fixed when checking the tag itself later */
+ if ((ztype = zend_hash_str_find(Z_ARR_P(znode), "type", strlen("type"))) == NULL || Z_TYPE_P(ztype) != IS_LONG) {
+ /* Check if empty tag */
+ if (Z_STRLEN_P(ccval) == 0) {
+ /* Set as root */
+ ltype = BBCODE_TYPE_ROOT;
+ } else if (strcmp("img", Z_STRVAL_P(ccval)) == 0) {
+ /* Set as single */
+ ltype = BBCODE_TYPE_SINGLE;
+ } else {
+ /* Set as multi */
+ ltype = BBCODE_TYPE_MULTI;
+ }
+ /* Type zval available and usable */
+ } else {
+ /* Set type from zval value */
+ ltype = Z_LVAL_P(ztype);
+ }
+
+ /* Check if not first string to add */
+ if (cpos > 4) {
+ /* Set pipe before next tag */
+ *(buf+cpos) = '|';
+ /* Move position */
+ cpos++;
+ /* Check if not single type */
+ if (ltype != BBCODE_TYPE_SINGLE) {
+ /* Set pipe before next tag */
+ *(buf2+cpos2) = '|';
+ /* Move position */
+ cpos2++;
+ }
+ }
+
+ /* Check if not single type */
+ if (ltype != BBCODE_TYPE_SINGLE) {
+ /* Grow buf if necessary */
+ if (cpos + Z_STRLEN_P(ccval) + 18 > len) {
+ /* We resize buf to current length(cpos), the string size plus eighteen char for trailing pattern */
+ buf = (char *)realloc(buf, (len = cpos + Z_STRLEN_P(ccval) + 18)*sizeof(char));
+ }
+
+ /* Copy string in buf */
+ ZEND_ASSERT(memcpy(buf+cpos, Z_STRVAL_P(ccval), Z_STRLEN_P(ccval)) != NULL);
+
+ /* Move cpos */
+ cpos += Z_STRLEN_P(ccval);
+
+ /* Grow buf2 if necessary */
+ if (cpos2 + Z_STRLEN_P(ccval) + 4 > len2) {
+ /* We resize buf2 to current length(cpos2), the string size plus four char for trailing pattern */
+ buf2 = (char *)realloc(buf2, (len2 = cpos2 + Z_STRLEN_P(ccval) + 4)*sizeof(char));
+ }
+
+ /* Copy string in buf2 */
+ ZEND_ASSERT(memcpy(buf2+cpos2, Z_STRVAL_P(ccval), Z_STRLEN_P(ccval)) != NULL);
+
+ /* Move cpos2 */
+ cpos2 += Z_STRLEN_P(ccval);
+ /* Single type case */
+ } else {
+ /* Grow buf if necessary */
+ if (cpos + Z_STRLEN_P(ccval) + strlen("(?:\\/)?") + 18 > len) {
+ /* We resize buf to current length(cpos), the string size plus three plus eighteen char for trailing pattern */
+ buf = (char *)realloc(buf, (len = cpos + Z_STRLEN_P(ccval) + strlen("(?:\\/)?") + 18)*sizeof(char));
+ }
+
+ /* Copy string in buf */
+ ZEND_ASSERT(memcpy(buf+cpos, Z_STRVAL_P(ccval), Z_STRLEN_P(ccval)) != NULL);
+
+ /* Append pattern in buf */
+ ZEND_ASSERT(memcpy(buf+cpos+Z_STRLEN_P(ccval), &"(?:\\/)?", strlen("(?:\\/)?")) != NULL);
+
+ /* Move cpos */
+ cpos += Z_STRLEN_P(ccval) + strlen("(?:\\/)?");
+ }
+ } ZEND_HASH_FOREACH_END();
+
+ /* Assign open pattern begin */
+ ZEND_ASSERT(memcpy(buf+cpos, &")(?:=([^\\]]*))?\\]/", 18) != NULL);
+
+ /* Assign open pattern begin */
+ ZEND_ASSERT(memcpy(buf2+cpos2, &")\\]/", 4) != NULL);
+
+ /* Open key string */
+ zend_string *okey = zend_string_init("opattern", strlen("opattern"), 0);
+
+ /* Close key string */
+ zend_string *ckey = zend_string_init("cpattern", strlen("cpattern"), 0);
+
+ /* Open and close value */
+ zval oval, cval;
+
+ /* Set open value */
+ ZVAL_STRINGL(&oval, buf, cpos + 18);
+
+ /* Set close value */
+ ZVAL_STRINGL(&cval, buf2, cpos2 + 4);
+
+ /* Free buffers */
+ free(buf); free(buf2);
+
+ /* Add open key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), okey, &oval) != NULL);
+
+ /* Add close key */
+ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), ckey, &cval) != NULL);
+
+ /* Free open key */
+ zend_string_release(okey);
+
+ /* Free close key */
+ zend_string_release(ckey);
+ }
+
+ /* Grow pos */
+ pos++;
+ } ZEND_HASH_FOREACH_END();
+
+ /* Check if not one unique root */
+ if (hasRoot != 1) {
+ /* Display error about multiple root */
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Tag array has %d BBCODE::TYPE_ROOT entry instead of expected unique one", hasRoot);
+ /* Return failure */
+ return NULL;