]> Raphaël G. Git Repositories - bbcode/blobdiff - bbcode.c
Remove stdbool, seems a deprecaded c99 feature
[bbcode] / bbcode.c
index 5810aae38435468b394260e30b05e66298035a6a..9271a960895afa81d83f46a97eebeb537e5f0d2e 100644 (file)
--- a/bbcode.c
+++ b/bbcode.c
@@ -9,7 +9,11 @@
 #include "Zend/zend_exceptions.h"
 #include "php_bbcode.h"
 
-#include <stdbool.h>
+/* XXX: only required for php_debug_zval_dump */
+#include "ext/standard/php_var.h"
+
+/* XXX: required for pcre_cache_entry */
+#include "ext/pcre/php_pcre.h"
 
 /* If you declare any globals in php_bbcode.h uncomment this:
 ZEND_DECLARE_MODULE_GLOBALS(bbcode)
@@ -18,7 +22,8 @@ ZEND_DECLARE_MODULE_GLOBALS(bbcode)
 typedef struct _bbcode_object {
        HashTable *tag;
        HashTable *smiley;
-       zval flag;
+       zend_string *root;
+       zend_long flag;
        zend_object std;
 } bbcode_object;
 
@@ -44,7 +49,9 @@ zend_class_entry *bbcode_exception_ce;
        GEN(TYPE_ROOT) /*BBCODE_TYPE_ROOT*/\
        GEN(TYPE_MULTI) /*BBCODE_TYPE_ARG|BBCODE_TYPE_OPTARG|BBCODE_TYPE_NOARG*/\
        GEN(TYPE_SINGLE) /*BBCODE_TYPE_SINGLE*/\
-       \
+
+/* TODO: add again each feature when implemented (before beta) */
+#if 0
        /* Quotes */ \
        GEN(QUOTE_DOUBLE) /*BBCODE_ARG_DOUBLE_QUOTE*/\
        GEN(QUOTE_SIMPLE) /*BBCODE_ARG_SINGLE_QUOTE*/\
@@ -65,6 +72,7 @@ zend_class_entry *bbcode_exception_ce;
        GEN(REMOVE_EMPTY) /*BBCODE_FLAGS_REMOVE_IF_EMPTY*/\
        GEN(REMOVE_CONTENT) /*BBCODE_FLAGS_CDATA_NOT_ALLOWED*/\
        GEN(REMOVE_REOPEN) /*BBCODE_FLAGS_DENY_REOPEN_CHILD*/
+#endif
 
 
 /* Generate enum version */
@@ -104,12 +112,18 @@ enum {
 };
 
 /* Set default flag 
- * TODO: Set it in an ini ? */
+ * TODO: Implement it in an ini file ? (later maybe) */
+#define BBCODE_DEFAULT_FLAG 0
+#if 0
 #define BBCODE_DEFAULT_FLAG BBCODE_QUOTE_DOUBLE|BBCODE_QUOTE_SIMPLE|BBCODE_QUOTE_ESCAPE|BBCODE_CORRECT_AUTO|BBCODE_CORRECT_REOPEN|BBCODE_SMILEY_ON|BBCODE_CLOSE_AUTO|BBCODE_REMOVE_EMPTY
+#endif
+
+/* Set debug flag */
+#define BBCODE_DEBUG 0
 
 /* {{{ Function prototypes (bbcode_destroy|bbcode_free|bbcode_clone|bbcode_create) */
-static void bbcode_destroy(bbcode_object *obj TSRMLS_DC);
-static void bbcode_free(bbcode_object *obj TSRMLS_DC);
+static void bbcode_destroy(zend_object *obj TSRMLS_DC);
+static void bbcode_free(zend_object *obj TSRMLS_DC);
 static zend_object *bbcode_clone(zval *obj TSRMLS_DC);
 static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC);
 /* }}} */
@@ -126,80 +140,137 @@ static inline bbcode_object* bbcode_fetch(zend_object *obj TSRMLS_DC) {
 #define Z_BBCODE_P(zv) bbcode_fetch(Z_OBJ_P((zv)))
 /* }}} */
 
-/* {{{ static void bbcode_destroy(bbcode_object *obj TSRMLS_DC) {
+/* {{{ static void bbcode_destroy(zend_object *obj TSRMLS_DC) {
  * BBCode object destroy call */
-static void bbcode_destroy(bbcode_object *obj TSRMLS_DC) {
-       //zend_objects_destroy_object(&obj->std);
-       zend_objects_destroy_object((zend_object *)obj);
-
-       //zend_object_std_dtor(&obj->std TSRMLS_CC);
-
-       /*if (obj->flag) {
-               efree(obj->flag);
-       }*/
-
-       //efree(obj);
+static void bbcode_destroy(zend_object *obj TSRMLS_DC) {
+       /* Trigger user land destructor on obj */
+       zend_objects_destroy_object(obj);
 }
 /* }}} */
 
-/* {{{ static void bbcode_free(bbcode_object *obj TSRMLS_DC) {
+/* {{{ static void bbcode_free(zend_object *obj TSRMLS_DC) {
  * BBCode object free call */
-static void bbcode_free(bbcode_object *obj TSRMLS_DC) {
+static void bbcode_free(zend_object *obj TSRMLS_DC) {
+       /* Retrieve bbcode object from zend object */
+       bbcode_object *bbobj = bbcode_fetch(obj);
+
+       /* Check if root is present */
+       if (bbobj->root) {
+               /* Release the root string */
+               zend_string_release(bbobj->root);
+       }
 
-       /*if (obj->flag) {
-               efree(obj->flag);
-       }*/
+       /* Check if tag is present */
+       if (bbobj->tag) {
+               /* Release the tag hash */
+               zend_array_destroy(bbobj->tag);
+       }
+
+       /* Check if smiley is present */
+       if (bbobj->smiley) {
+               /* Release the smiley hash */
+               zend_array_destroy(bbobj->smiley);
+       }
 
-       //efree(obj);
-       zend_object_std_dtor((zend_object *)obj);
+       /* Trigger zend object std destructor on obj */
+       zend_object_std_dtor(obj);
+
+       /* Free the bbobj */
+       efree(bbobj);
 }
 /* }}} */
 
 /* {{{ static zend_object *bbcode_clone(zval *obj TSRMLS_DC) {
  * BBCode object clone call */
 static zend_object *bbcode_clone(zval *obj TSRMLS_DC) {
+       /* Fetch pointer to old bbcode object */
+       bbcode_object *oldbbobj = Z_BBCODE_P(obj);
 
-       /* Fetch pointer on old obj */
-       zend_object *oldobj = Z_OBJ_P(obj);
-       /* Create pointer on nex obj */
-       zend_object *newobj = bbcode_create(oldobj->ce);
+       /* Create pointer on new obj */
+       //bbcode_object *newbbobj = bbcode_fetch(bbcode_create(oldbbobj->std.ce));
+       bbcode_object *newbbobj = bbcode_fetch(bbcode_create(Z_OBJ_P(obj)->ce));
 
        /* Call members cloning function */
-       zend_objects_clone_members(newobj, oldobj);
+       zend_objects_clone_members(&newbbobj->std, &oldbbobj->std);
+
+       /* Duplicate flag value */
+       newbbobj->flag = oldbbobj->flag;
+
+       /* Set root as null */
+       newbbobj->root = NULL;
+
+       /* Check if root is available */
+       if (oldbbobj->root) {
+               /* Duplicate root zend string */
+               newbbobj->root = zend_string_copy(oldbbobj->root);
+       }
+
+       /* Set tag as null */
+       newbbobj->tag = NULL;
 
-       /* Retrieve from zend object old bbcode */
-       bbcode_object *bboldobj = Z_BBCODE_P(obj);
+       /* Check if tag is available */
+       if (oldbbobj->tag) {
+               /* Duplicate tag zend array */
+               newbbobj->tag = zend_array_dup(oldbbobj->tag);
+       }
 
-       /* Retrieve from zend object new bbcode */
-       bbcode_object *bbnewobj = bbcode_fetch(newobj);
+       /* Set smiley as null */
+       newbbobj->smiley = NULL;
 
-       /* Restore object member values */
-       //TODO: deal with the other one (tag|smiley)
-       Z_LVAL(bbnewobj->flag) = Z_LVAL(bboldobj->flag);
+       /* Check if smiley is available */
+       if (oldbbobj->smiley) {
+               /* Duplicate smiley zend array */
+               newbbobj->smiley = zend_array_dup(oldbbobj->smiley);
+       }
 
        /* Return the new object */
-       return newobj;
+       return &newbbobj->std;
 }
 /* }}} */
 
 /* {{{ static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC) {
  * BBCode object create call */
 static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC) {
-       bbcode_object *obj = (bbcode_object *)ecalloc(1, sizeof(bbcode_object) + zend_object_properties_size(ce));//emalloc(sizeof(bbcode_object));
+       /* Allocate object */
+       bbcode_object *obj = (bbcode_object *)ecalloc(1, sizeof(bbcode_object) + zend_object_properties_size(ce));
 
+       /* Init std member */
        zend_object_std_init(&obj->std, ce TSRMLS_CC);
+
+       /* Set object properties */
        object_properties_init(&obj->std, ce TSRMLS_CC);
 
+       /* Duplicate standard object handler */
        memcpy(&bbcode_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+
+       /* Set object offset */
        bbcode_handlers.offset = XtOffsetOf(bbcode_object, std);
+
+       /* Set destructor function */
        bbcode_handlers.dtor_obj = (zend_object_dtor_obj_t) bbcode_destroy;
+
+       /* Set free function */
        bbcode_handlers.free_obj = (zend_object_free_obj_t) bbcode_free;
+
+       /* Set clone function */
        bbcode_handlers.clone_obj = (zend_object_clone_obj_t) bbcode_clone;
 
+       /* Assign custom handler */
        obj->std.handlers = &bbcode_handlers;
 
-       Z_LVAL(obj->flag) = 0;
+       /* Init tag */
+       obj->tag = NULL;
+
+       /* Init smiley */
+       obj->smiley = NULL;
+
+       /* Init root */
+       obj->root = NULL;
+
+       /* Init flag */
+       obj->flag = BBCODE_DEFAULT_FLAG;
 
+       /* Return the std member address */
        return &obj->std;
 }
 /* }}} */
@@ -208,7 +279,7 @@ static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC) {
 // ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)
 ZEND_BEGIN_ARG_INFO_EX(bbcode_construct_arginfo, 0, 0, 1)
        // ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null)
-       ZEND_ARG_ARRAY_INFO(0, tag, 1)
+       ZEND_ARG_ARRAY_INFO(0, tag, 0)
        ZEND_ARG_ARRAY_INFO(0, smiley, 1)
        // ZEND_ARG_INFO(pass_by_ref, name)
        ZEND_ARG_INFO(0, flag)
@@ -223,13 +294,157 @@ ZEND_BEGIN_ARG_INFO_EX(bbcode_parse_arginfo, 0, 0, 1)
 ZEND_END_ARG_INFO()
 /* }}} */
 
-/* { { { static zend_bool *bbcode_check_tag(HashTable *tag TSRMLS_DC) {
+/* {{{ static HashTable *bbcode_gen_child(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) { */
+static HashTable *bbcode_gen_child(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) {
+       /* Key string */
+       zend_string *key;
+       /* Value */
+       zval *val;
+
+       /* Iterate on each key val until hash end is reached */
+       ZEND_HASH_FOREACH_STR_KEY_VAL(tag, key, val) {
+               /* Check if key is not a string, value is null or not an array */
+               if (key == NULL || val == NULL || Z_TYPE_P(val) != IS_ARRAY) {
+                       /* Return failure, useless to generate a hash when we will trigger an error later */
+                       return NULL;
+               }
+
+               /* Init type val */
+               zval *type;
+
+               /* Check that we have no type or that it's not root type */
+               /* XXX: it's not possible to have in child the root tag */
+               if ((type = zend_hash_str_find(Z_ARR_P(val), "type", strlen("type"))) == NULL || Z_LVAL_P(type) != BBCODE_TYPE_ROOT) {
+                       /* Check that we are not on cur key */
+                       /* XXX: not really required, but it's stupid to allow same tag in child */
+                       //if (strcmp(ZSTR_VAL(cur), ZSTR_VAL(key)) != 0) {
+                               /* Init parent val */
+                               zval *parent;
+
+                               /* Init toInsert bool */
+                               unsigned char toInsert = 0;
+
+                               /* Check if we find parent key */
+                               if ((parent = zend_hash_str_find(Z_ARR_P(val), "parent", strlen("parent"))) != NULL) {
+                                       /* Parent value */
+                                       zval *pval;
+
+                                       /* Iterate on each val until hash end is reached */
+                                       ZEND_HASH_FOREACH_VAL(Z_ARR_P(parent), pval) {
+                                               /* Check that parent val is a string */
+                                               if (Z_TYPE_P(pval) != IS_STRING) {
+                                                       return NULL;
+                                               }
+
+                                               /* Check that tkey is a string and is identical */
+                                               if (strcmp(Z_STRVAL_P(pval), ZSTR_VAL(cur)) == 0) {
+                                                       toInsert = 1;
+                                               }
+                                       } ZEND_HASH_FOREACH_END();
+                               /* No parent key */
+                               } else {
+                                       toInsert = 1;
+                               }
+
+                               /* Add the key */
+                               if (toInsert) {
+                                       /* Init child val */
+                                       zval cval;
+
+                                       /* Set child val */
+                                       ZVAL_STR(&cval, key);
+
+                                       /* Insert the child val in return array */
+                                       ZEND_ASSERT(zend_hash_next_index_insert_new(ret, &cval) != NULL);
+                               }
+                       //}
+               }
+       } ZEND_HASH_FOREACH_END();
+
+       /* Return value */
+       return ret;
+}
+/* }}} */
+
+/* {{{ static HashTable *bbcode_gen_parent(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) { */
+static HashTable *bbcode_gen_parent(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) {
+       /* Key string */
+       zend_string *key;
+       /* Value */
+       zval *val;
+
+       /* Iterate on each key val until hash end is reached */
+       ZEND_HASH_FOREACH_STR_KEY_VAL(tag, key, val) {
+               /* Check if key is not a string, value is null or not an array */
+               if (key == NULL || val == NULL || Z_TYPE_P(val) != IS_ARRAY) {
+                       /* Return failure, useless to generate a hash when we will trigger an error later */
+                       return NULL;
+               }
+
+               /* Init type val */
+               zval *type;
+
+               /* Check that we have no type or that it's not single type */
+               /* XXX: it's not possible to have in child the root tag */
+               if ((type = zend_hash_str_find(Z_ARR_P(val), "type", strlen("type"))) == NULL || Z_LVAL_P(type) != BBCODE_TYPE_SINGLE) {
+                       /* Check that we are not on cur key */
+                       /* XXX: not really required, but it's stupid to allow same tag in child */
+                       //if (strcmp(ZSTR_VAL(cur), ZSTR_VAL(key)) != 0) {
+                               /* Init child val */
+                               zval *child;
+
+                               /* Init toInsert bool */
+                               unsigned char toInsert = 0;
+
+                               /* Check if we find child key */
+                               if ((child = zend_hash_str_find(Z_ARR_P(val), "child", strlen("child"))) != NULL) {
+                                       /* Child value */
+                                       zval *cval;
+
+                                       /* Iterate on each val until hash end is reached */
+                                       ZEND_HASH_FOREACH_VAL(Z_ARR_P(child), cval) {
+                                               /* Check that child val is a string */
+                                               if (Z_TYPE_P(cval) != IS_STRING) {
+                                                       return NULL;
+                                               }
+
+                                               /* Check that tkey is a string and is identical */
+                                               if (strcmp(Z_STRVAL_P(cval), ZSTR_VAL(cur)) == 0) {
+                                                       toInsert = 1;
+                                               }
+                                       } ZEND_HASH_FOREACH_END();
+                               /* No child key */
+                               } else {
+                                       toInsert = 1;
+                               }
+
+                               /* Add the key */
+                               if (toInsert) {
+                                       /* Init parent val */
+                                       zval pval;
+
+                                       /* Set parent val */
+                                       ZVAL_STR(&pval, key);
+
+                                       /* Insert the parent val in return array */
+                                       ZEND_ASSERT(zend_hash_next_index_insert_new(ret, &pval) != NULL);
+                               }
+                       //}
+               }
+       } ZEND_HASH_FOREACH_END();
+
+       /* Return value */
+       return ret;
+}
+/* }}} */
+
+/* {{{ static zend_bool *bbcode_check_tag(HashTable *tag TSRMLS_DC) {
  * Check that tag hash is valid */
-static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
+static zend_string *bbcode_check_tag(HashTable *tag TSRMLS_DC) {
        /* Key numeric value */
        zend_long idx;
-       /* Key string */
-       zend_string *key;
+       /* Key and root string */
+       zend_string *key, *root = NULL;
        /* Value */
        zval *val;
 
@@ -241,30 +456,36 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
 
        /* Iterate on each key val until hash end is reached */
        ZEND_HASH_FOREACH_KEY_VAL(tag, idx, key, val) {
-               /* Check if key is a string */
-               if (!key) {
+               /* Check if key is not a string */
+               if (key == NULL) {
                        /* Display error about long key */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %ld[%d] from argument tag is not an expected string", idx, pos);
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %ld[%d] from argument tag is not an expected string", idx, pos);
                        /* Return failure */
-                       return 0;
+                       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_ERROR, "Value for key %s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos);
+                       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 0;
+                       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_ERROR, "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));
+                       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 0;
+                       return NULL;
                }
 
                /* Store child tag array */
-               HashTable *ctag = Z_ARRVAL_P(val);
+               HashTable *ctag = Z_ARR_P(val);
                /* Child key numeric value */
                zend_long cidx;
                /* Child key string */
@@ -272,28 +493,31 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                /* Child value */
                zval *cval;
 
+               /* Buffer */
+               char *buf;
+
                /* Child key position */
-               int cpos = 0, type;
+               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 */
-               bool hasType = false, hasParent = false, hasChild = false, hasOpen = false, hasClose = false, hasDefault = false, hasArg = false;
+               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_ERROR, "Key %s[%d]/%ld[%d] from argument tag is not an expected string", ZSTR_VAL(key), pos, cidx, cpos);
+                               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 0;
+                               return NULL;
                        }
 
                        /* Check if ckey is empty */
                        if (ZSTR_LEN(ckey) == 0) {
                                /* Display error about long key */
-                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "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);
+                               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 0;
+                               return NULL;
                        /* Check if current ckey is type */
                        } else if (strcmp("type", ZSTR_VAL(ckey)) == 0) {
                                /* Extract type */
@@ -306,9 +530,9 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                        type != BBCODE_TYPE_SINGLE
                                ) {
                                        /* Display error about unexpected type value */
-                                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "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);
+                                       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 0;
+                                       return NULL;
                                /* Check if root */
                                } else if (type == BBCODE_TYPE_ROOT) {
                                        /* Grow hasRoot */
@@ -316,21 +540,27 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                }
 
                                /* Set hasType */
-                               hasType = true;
+                               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_ERROR, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                       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 0;
+                                       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_ERROR, "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));
+                                       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 0;
+                                       return NULL;
                                }
 
                                /* Parent value */
@@ -340,36 +570,37 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                int ppos = 0;
 
                                /* Iterate on each val until hash end is reached */
-                               ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(cval), pval) {
+                               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_ERROR, "Parent value %s[%d] for key %s[%d]/%s[%d] is not an expected string", Z_STRVAL_P(pval), ppos, ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                               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 0;
+                                               return NULL;
                                        }
 
                                        /* Tag key */
                                        zend_string *tkey;
 
                                        /* Reset found flag */
-                                       bool found = false;
+                                       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 = true;
+                                                       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_ERROR, "Parent value %s for key %s[%d]/%s[%d] is not present in tag keys", Z_STRVAL_P(pval), ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                               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 0;
+                                               return NULL;
                                        }
 
                                        /* Grow ppos */
@@ -377,21 +608,21 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                } ZEND_HASH_FOREACH_END();
 
                                /* Set hasParent */
-                               hasParent = true;
+                               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_ERROR, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                       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 0;
+                                       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_ERROR, "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));
+                                       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 0;
+                                       return NULL;
                                }
 
                                /* Child value */
@@ -401,36 +632,37 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                int ccpos = 0;
 
                                /* Iterate on each val until hash end is reached */
-                               ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(cval), ccval) {
+                               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_ERROR, "Child value %s[%d] for key %s[%d]/%s[%d] is not an expected string", Z_STRVAL_P(ccval), ccpos, ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                               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 0;
+                                               return NULL;
                                        }
 
                                        /* Tag key */
                                        zend_string *tkey;
 
                                        /* Reset found flag */
-                                       bool found = false;
+                                       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 = true;
+                                                       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_ERROR, "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);
+                                               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 0;
+                                               return NULL;
                                        }
 
                                        /* Grow ccpos */
@@ -438,54 +670,59 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                } ZEND_HASH_FOREACH_END();
 
                                /* Set hasChild */
-                               hasChild = true;
+                               hasChild = 1;
                        /* Check if current key is open */
                        } else if (strcmp("open", ZSTR_VAL(ckey)) == 0) {
                                /* Set hasOpen */
-                               hasOpen = true;
+                               hasOpen = 1;
                        /* Check if current key is close */
                        } else if (strcmp("close", ZSTR_VAL(ckey)) == 0) {
                                /* Set hasClose */
-                               hasClose = true;
+                               hasClose = 1;
                        /* Check if current key is default */
                        } else if (strcmp("default", ZSTR_VAL(ckey)) == 0) {
                                /* Set hasDefault */
-                               hasDefault = true;
+                               hasDefault = 1;
                        /* Check if current key is arg */
                        } else if (strcmp("arg", ZSTR_VAL(ckey)) == 0) {
                                /* Set hasArg */
-                               hasArg = true;
+                               hasArg = 1;
                        /* Check if current key is unknown */
                        } else {
                                /* Display error about long key */
-                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "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);
+                               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 0;
+                               return NULL;
                        }
 
                        /* Grow cpos */
                        cpos++;
                } ZEND_HASH_FOREACH_END();
 
-               /* Check if val array is initialized */
-               /*XXX: this step is required to workaround zend engine half initialized hashtable when val array is empty that trigger a segfault*/
-               if ((GC_FLAGS(Z_ARR_P(val)) & HASH_FLAG_INITIALIZED) != HASH_FLAG_INITIALIZED) {
+               /* 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 empty */
+                       /* 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);
-                       zend_hash_copy(Z_ARR_P(val), ctag, (copy_ctor_func_t) zval_add_ref);
+
+                       /* 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 zval */
-                       zval tval;
-
-                       /* Type key string */
+                       /* 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 */
@@ -510,25 +747,148 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                        zend_string_release(tkey);
 
                        /* Set hasType */
-                       hasType = true;
+                       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_ERROR, "Key %s[%d] from argument tag of type BBCODE::TYPE_ROOT cannot have a parent entry", ZSTR_VAL(key), pos);
+                               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 0;
+                               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;
                        }
 
-                       /* TODO: Compute child here */
-                       //TODO: set child if not provided for ROOT (has to loop on each possible type and check if a parent array is present with this tag inside, if none set to all possible tag except '' one)
-                       //TODO: display error if type=root and child was not provided| XXX: we can compute that shit here for lazy morons
+                       /* Compute child here */
                        if (!hasChild) {
-                               //TODO compute child by looping on each non root element and element with no parent or with root in parent
-                               //XXX: it seems that a better approach would be to save root element en push each element one by one ?
+                               /* 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) {
@@ -540,26 +900,33 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                /* Value */
                                zval mval;
 
-                               /* Tag */
-                               zend_string *mtag = zend_string_init("<>", strlen("<>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0), 0);
-                               memcpy(ZSTR_VAL(mtag)+sizeof(char), ZSTR_VAL(key), ZSTR_LEN(key));
-                               if (hasArg) {
-                                       memcpy(ZSTR_VAL(mtag)+(1+ZSTR_LEN(key))*sizeof(char), "%s>", strlen("%s>"));
+                               /* Check if tag is empty */
+                               if (ZSTR_LEN(key) == 0) {
+                                       /* Init new value */
+                                       ZVAL_EMPTY_STRING(&mval);
+                               /* Non empty tag */
                                } else {
-                                       memcpy(ZSTR_VAL(mtag)+(1+ZSTR_LEN(key))*sizeof(char), ">", strlen(">"));
-                               }
+                                       /* Init buffer and length */
+                                       buf = (char *)malloc((len = strlen("<>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0))*sizeof(char));
 
-                               /* Set value */
-                               ZVAL_STRING(&mval, ZSTR_VAL(mtag));
+                                       /* 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 tag */
-                               zend_string_release(mtag);
-
                                /* Free key */
                                zend_string_release(mkey);
+
+                               /* Set hasOpen */
+                               hasOpen = 1;
                        }
 
                        /* Check if has close entry */
@@ -570,22 +937,33 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                /* Value */
                                zval mval;
 
-                               /* Tag */
-                               zend_string *mtag = zend_string_init("</>", strlen("</>") + ZSTR_LEN(key), 0);
-                               memcpy(ZSTR_VAL(mtag)+2*sizeof(char), ZSTR_VAL(key), ZSTR_LEN(key));
-                               memcpy(ZSTR_VAL(mtag)+(2+ZSTR_LEN(key))*sizeof(char), ">", strlen(">"));
+                               /* 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));
 
-                               /* Set value */
-                               ZVAL_STRING(&mval, ZSTR_VAL(mtag));
+                                       /* 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 tag */
-                               zend_string_release(mtag);
-
                                /* Free key */
                                zend_string_release(mkey);
+
+                               /* Set hasClose */
+                               hasClose = 1;
                        }
 
                        /* Check if has not default */
@@ -604,26 +982,88 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
 
                                /* Free key */
                                zend_string_release(mkey);
+
+                               /* Set hasDefault */
+                               hasDefault = 1;
                        }
 
-                       //TODO: set parent&child if not provided for ARG|OPTARG|NOARG|SINGLE (has to loop on each possible type and check if no child array or present in child array if available)
+                       /* 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_ERROR, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have an open entry", ZSTR_VAL(key), pos);
+                               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 0;
+                               return NULL;
                        }
 
                        /* Check if has close entry */
                        if (hasClose) {
                                /* Display error about close entry */
-                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a close entry", ZSTR_VAL(key), pos);
+                               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 0;
+                               return NULL;
                        }
 
                        /* Check if has not default */
@@ -634,57 +1074,238 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                /* Value */
                                zval sval;
 
-                               /* Tag */
-                               zend_string *stag;
-
-                               /* Check if jey is img */
+                               /* Check if key is img */
                                if (strcmp("img", ZSTR_VAL(key)) == 0) {
-                                       stag = zend_string_init("<img src=\"%s\"/>", strlen("<img src=\"%s\"/>") + (hasArg?strlen("%s"):0), 0);
-                                       if (hasArg) {
-                                               memcpy(ZSTR_VAL(stag)+strlen("<img src=\"%s\"")*sizeof(char), "%s/>", strlen("%s/>"));
-                                       } else {
-                                               memcpy(ZSTR_VAL(stag)+strlen("<img src=\"%s\"")*sizeof(char), "/>", strlen("/>"));
-                                       }
+                                       /* 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 {
-                                       stag = zend_string_init("</>", strlen("</>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0), 0);
-                                       memcpy(ZSTR_VAL(stag)+sizeof(char), ZSTR_VAL(key), ZSTR_LEN(key));
-                                       if (hasArg) {
-                                               memcpy(ZSTR_VAL(stag)+(1+ZSTR_LEN(key))*sizeof(char), "%s/>", strlen("%s/>"));
-                                       } else {
-                                               memcpy(ZSTR_VAL(stag)+(1+ZSTR_LEN(key))*sizeof(char), "/>", strlen("/>"));
-                                       }
+                                       /* 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);
                                }
 
-                               /* Set value */
-                               ZVAL_STRING(&sval, ZSTR_VAL(stag));
+                               /* 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 tag */
-                               zend_string_release(stag);
-
                                /* 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_ERROR, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a child entry", ZSTR_VAL(key), pos);
+                               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 0;
+                               return NULL;
                        }
 
-                       //TODO: set parent if not provided for ARG|OPTARG|NOARG|SINGLE (has to loop on each possible type and check if no child array or present in child array if available)
+                       /* 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;
+                       }
                }
 
-#ifndef DEBUG
-               printf("key=>%s\n", ZSTR_VAL(key));
-               fflush(NULL);
-               php_debug_zval_dump(val, 0);
-               fflush(NULL);
-#endif
+               /* 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++;
@@ -693,13 +1314,19 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
        /* Check if not one unique root */
        if (hasRoot != 1) {
                        /* Display error about multiple root */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Has %d BBCODE::TYPE_ROOT entry, require one unique", hasRoot);
+                       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 0;
+                       return NULL;
        }
 
+       /* Duplicate root zend string to avoid it beeing freed */
+       return zend_string_copy(root);
+
+#if 0
+
        /* Return success */
-       return 1;
+       return root;
+#endif
 }
 /* }}} */
 
@@ -721,7 +1348,7 @@ static zend_bool bbcode_check_smiley(HashTable *smiley TSRMLS_DC) {
                /* Check that key is a string */
                if (key == NULL) {
                        /* Display error about long key */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %ld[%d] from argument smiley is not an expected string", idx, pos);
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %ld[%d] from argument smiley is not an expected string", idx, pos);
                        /* Return failure */
                        return 0;
                }
@@ -729,13 +1356,13 @@ static zend_bool bbcode_check_smiley(HashTable *smiley TSRMLS_DC) {
                /* Check that value exists */
                if (val == NULL) {
                        /* Display error about long key */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Value for key %s[%d] from argument smiley is NULL instead of expected array", ZSTR_VAL(key), pos);
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument smiley is NULL instead of expected array", ZSTR_VAL(key), pos);
                        /* Return failure */
                        return 0;
                /* Check that value is not an array */
                } else if (Z_TYPE_P(val) == IS_ARRAY) {
                        /* Display error about long key */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Value for key %s[%d] from argument smiley is an array instead of expected string", ZSTR_VAL(key), pos);
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument smiley is an array instead of expected string", ZSTR_VAL(key), pos);
                        /* Return failure */
                        return 0;
                }
@@ -749,109 +1376,420 @@ static zend_bool bbcode_check_smiley(HashTable *smiley TSRMLS_DC) {
 }
 /* }}} */
 
-/* {{{ PHP_METHOD(BBCode, __construct) {
- */
+/* {{{ PHP_METHOD(BBCode, __construct) { */
 PHP_METHOD(BBCode, __construct) {
+       /* Init tag hash */
        HashTable *tag = NULL;
+
+       /* Init smiley hash */
        HashTable *smiley = NULL;
-       zend_long flag = BBCODE_DEFAULT_FLAG;
 
-/*TODO: init tag with [
-       '' => [ ? ]
-]*/
-/* TODO: init smiley with default list
- * TODO: use prefix in an ini parameter ? ini_setable ?
- * TODO: make sure it's not really a global and per-app param ?
- * */
+       /* Init flag long */
+       zend_long flag = BBCODE_DEFAULT_FLAG;
 
-       /* Retrieve the pointer to this object */
-       bbcode_object *obj = Z_BBCODE_P(getThis());
+       /* Zend error handling */
+       zend_error_handling error_handling;
 
-       /* Verify that the pointer is leading somewhere */
-       assert(obj != NULL);
+       /* TODO: set tag array to a default value like this in case of missing/empty tag array :
+        * [ '' => [ ? ] ]
+        * (maybe later)
+        */
 
-       /* Set default values */
-       obj->tag = tag;
-       obj->smiley = smiley;
-       Z_LVAL(obj->flag) = flag;
+       /* TODO: init smiley with default list ? what about img storage ?
+        * TODO: use prefix in an ini parameter ? ini_setable ?
+        * TODO: make sure it's not really a global and per-app param ?
+        * (maybe later)
+        */
 
+       zend_replace_error_handling(EH_THROW, bbcode_exception_ce, &error_handling);
        /* Parse parameters */
+       /*if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "h|hl", &tag, &smiley, &flag) == FAILURE) { return; }*/
        ZEND_PARSE_PARAMETERS_START(1, 3)
+               /* Tag arg is required */
                Z_PARAM_ARRAY_HT(tag)
-               Z_PARAM_ARRAY_HT(smiley)
+               /* We switch to optional args */
+               Z_PARAM_OPTIONAL
+               /* Smiley arg is optional and may be null */
+               Z_PARAM_ARRAY_HT_EX(smiley, 1, 0)
+               /* Flag arg is optional and may be null */
                Z_PARAM_LONG(flag)
+       /* End parameters parse */
        ZEND_PARSE_PARAMETERS_END();
 
-       /*if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "h|hl", &tag, &smiley, &flag) == FAILURE) {
-               return;
-       }*/
+       /* Retrieve the pointer to this object */
+       bbcode_object *obj = Z_BBCODE_P(getThis());
+
+       /* Verify that the pointer is leading somewhere */
+       assert(obj != NULL);
+
+       /* Set flag value */
+       obj->flag = flag;
+
+       /* Duplicate hash to avoid modifications on original argument */
+       obj->tag = zend_array_dup(tag);
 
        /* Save tag argument hash in current object */
-       //TODO: check tag structure
-       if (bbcode_check_tag(tag)) {
-               obj->tag = tag;
-       } else {
-               zend_hash_destroy(tag);
-               zend_hash_destroy(smiley);
+       if ((obj->root = bbcode_check_tag(obj->tag)) == NULL) {
+               /* Restore error handler */
+               zend_restore_error_handling(&error_handling);
+
+               /* Release the tag hash */
+               zend_array_destroy(obj->tag);
+
+               /* Free the zend object */
+               //XXX: seems useless, it don't seems possible to null the contructor return value...
                ZVAL_NULL(getThis() TSRMLS_CC);
+
+               /* Free the object */
+               efree(obj);
+
+               /* Stop here */
                return;
        }
 
-       /* Save smiley argument hash in current object if provided */
-       //TODO: check smiley structure
+       /* Check if smiley argument is available */
        if (smiley) {
-               if (bbcode_check_smiley(smiley)) {
-                       obj->smiley = smiley;
-               } else {
-                       zend_hash_destroy(tag);
-                       zend_hash_destroy(smiley);
+               /* Duplicate hash to avoid modifications on original argument */
+               obj->smiley = zend_array_dup(smiley);
+
+               /* Save smiley argument hash in current object */
+               if (!bbcode_check_smiley(obj->smiley)) {
+                       /* Restore error handler */
+                       zend_restore_error_handling(&error_handling);
+
+                       /* Release the root string */
+                       zend_string_release(obj->root);
+
+                       /* Release the tag hash */
+                       zend_array_destroy(obj->tag);
+
+                       /* Release the smiley hash */
+                       zend_array_destroy(obj->smiley);
+
+                       /* Free the object */
+                       //XXX: seems useless, it don't seems possible to null the contructor return value...
                        ZVAL_NULL(getThis() TSRMLS_CC);
+
+                       /* Free the object */
+                       efree(obj);
+
+                       /* Stop here */
                        return;
                }
        }
 
-       /* Save flag argument long in current object if provided */
-       if (flag) {
-               Z_LVAL(obj->flag) = flag;
-       }
+       /* Restore error handler */
+       zend_restore_error_handling(&error_handling);
 }
 /* }}} */
 
 /* {{{ PHP_METHOD(BBCode, __destruct) {
+ * XXX: this function is called by bbcode_destroy function
  */
 PHP_METHOD(BBCode, __destruct) {
+//XXX: disable this code, we don't need to retrieve obj as we do nothing with userland destructor for now
+#if 0
        /* Retrieve the pointer to this object */
        bbcode_object *obj = Z_BBCODE_P(getThis());
 
        /* Verify that the pointer is leading somewhere */
        assert(obj != NULL);
+#endif
+}
+/* }}} */
+
+/*  */
+//[HashTable *ret][zend_string *current][char *str][int offset][int length]
+static char *bbcode_parse(HashTable *tag, zend_string *cur, char *rs, int *ros, int *rlen, char *str, int len TSRMLS_DC) {
+       /* Check if return string is not initialized or shorter than wanted length */
+       if (rs == NULL || *rlen - *ros <= 1024) {
+               /* Compute new length */
+               *rlen = (rs == NULL) ? 2048 : *rlen + 1024;
+
+               /* Grow the return string */
+               ZEND_ASSERT((rs = (char *)realloc(rs, (*rlen)*sizeof(char))) != NULL);
+       }
+
+       //DEBUG: for reminder
+       //str=skip0[r]string0[a=arg0]string1[/a]string2[b]string3[/b][/r]skip1
+       //len=64
+
+       /* Init zval for current node and type */
+       zval *znode, *ztype;
+
+       /* Retrieve current tag hash as zval */
+       ZEND_ASSERT((znode = zend_hash_find(tag, cur)) != NULL && Z_TYPE_P(znode) == IS_ARRAY);
+
+       /* Retrieve current tag type */
+       ZEND_ASSERT((ztype = zend_hash_str_find(Z_ARR_P(znode), "type", strlen("type"))) != NULL && Z_TYPE_P(ztype) == IS_LONG);
+
+       /* Retrieve current tag open */
+       /* XXX: may be NULL if not present */
+       zval *zopen = zend_hash_str_find(Z_ARR_P(znode), "open", strlen("open"));
+
+       /* Retrieve current tag close */
+       /* XXX: may be NULL if not present */
+       zval *zclose = zend_hash_str_find(Z_ARR_P(znode), "close", strlen("close"));
+
+       /* Retrieve current tag default */
+       /* XXX: may be NULL if not present */
+       zval *zdefault = zend_hash_str_find(Z_ARR_P(znode), "default", strlen("default"));
+
+       /* Retrieve current tag arg */
+       /* XXX: may be NULL if not present */
+       zval *zarg = zend_hash_str_find(Z_ARR_P(znode), "arg", strlen("arg"));
+
+       /* The arg zend string */
+       zend_string *argstr = NULL;
+
+       /* Check if we have a not root and not empty tag */
+       if (Z_LVAL_P(ztype) != BBCODE_TYPE_ROOT && strcmp(ZSTR_VAL(cur), "") != 0) {
+               /* Regular expression */
+               zend_string *regex;
+
+               /* Open and close pattern */
+               char *openstr, *closestr, *found;
+
+               /* Open and close pattern length */
+               int openlen, closelen;
+
+               /* Alloc open pattern buffer and set length */
+               openstr = (char *)malloc(((openlen = strlen("/\\[\\]/") + (zarg == NULL ? strlen("(?:=[^\\]]*)?") : strlen("(?:=([^\\]]*))?")) + ZSTR_LEN(cur)) + 1)*sizeof(char));
+
+               /* Alloc close pattern buffer and set length */
+               closestr = (char *)malloc(((closelen = strlen("[/]") + ZSTR_LEN(cur)) + 1)*sizeof(char));
+
+               /* Create open tag string */
+               ZEND_ASSERT(snprintf(openstr, openlen + 1, "/\\[%s%s\\]/", ZSTR_VAL(cur), (zarg == NULL ? "(?:=[^\\]]*)?" : "(?:=([^\\]]*))?")) == openlen);
+
+               /* Duplicate open tag in regex */
+               regex = zend_string_init(openstr, openlen, 0);
+
+               /* Free the close string */
+               free(closestr);
+
+               /* Compiled regular expression */
+               pcre_cache_entry *pce;
+
+               /* Array for subpatterns and return value */
+               zval rv, subpats;
+
+               /* Init subpats array */
+               /* XXX: if we don't initialize the array first, some strange memory corruption happen inside php_pcre_match_impl call */
+               array_init(&subpats);
+
+               /* Compile regex or get it from cache. */
+               /* TODO: see if we handle the error here ? (maybe later)
+                * XXX: it seems unlikely any error could be recoverable if pcre function throw something
+                * XXX: special char in tag is already protected earlier
+                * XXX: maybe just check that the pcre error trigger an exception or something uncatchable in all case
+                * XXX: see function pcre_get_compiled_regex_cache in ext/pcre/php_pcre.c +530 */
+               ZEND_ASSERT((pce = pcre_get_compiled_regex_cache(regex)) != NULL);
+
+               /* Increase pce refcount */
+               /* XXX: we use this instead of pce->refcount++; to avoid dereferencing compile error  */
+               php_pcre_pce_incref(pce);
+
+               /* Call zend pcre implementation */
+               php_pcre_match_impl(pce, str, len, &rv, &subpats, 0, 0, 0, 0);
+
+               /* Decrease pce refcount */
+               /* XXX: we use this instead of pce->refcount--; to avoid dereferencing compile error  */
+               php_pcre_pce_decref(pce);
+
+               /* Check that we found an occurence */
+               if (Z_TYPE(rv) == IS_LONG && Z_LVAL(rv) == 1) {
+                       /* Init match zval */
+                       zval *match;
+
+                       /* Init the matched tag string */
+                       //char *tstr;
+
+                       /* Test that subpats is an array */
+                       ZEND_ASSERT(Z_TYPE(subpats) == IS_ARRAY);
+
+                       /* Test that subpats members count is right */
+                       ZEND_ASSERT(zend_array_count(Z_ARR(subpats)) == (zarg == NULL ? 1 : 2));
 
-       /* Free tag hashtable */
-       if (obj->tag) {
-               efree(obj->tag);
+                       /* Retrieve the matched full tag string */
+                       ZEND_ASSERT((match = zend_hash_index_find(Z_ARR(subpats), 0)) != NULL);
+
+                       /* Test that match is a string */
+                       ZEND_ASSERT(Z_TYPE_P(match) == IS_STRING);
+
+                       /* Find the tag in string */
+                       if ((found = (char *)zend_memnstr(str, Z_STRVAL_P(match), Z_STRLEN_P(match), str+len))) {
+                               /* Move str to found position + matched tag string */
+                               str = found + Z_STRLEN_P(match);
+                       }
+
+                       /* Check if have and arg to retrieve */
+                       if (zarg != NULL) {
+                               /* Retrieve the matched arg string */
+                               ZEND_ASSERT((match = zend_hash_index_find(Z_ARR(subpats), 1)) != NULL);
+
+                               /* Test that match is a string */
+                               ZEND_ASSERT(Z_TYPE_P(match) == IS_STRING);
+
+                               /* Save arg string */
+                               argstr = Z_STR_P(match);
+                       }
+
+                       //XXX: code unfinished, these todo are normal, work start here :)
+                       //TODO: set the new search position to the current offset plus found subpats[0] string length
+                       //TODO: save argument string for appending in return string if zarg is not NULL
+                       //TODO: see what happen if we find start tag and not end
+                       //TODO: see what happen if we find no start tag and no end
+                       //TODO: see what happen if we find no start tag and no end
+
+               /* No occurence found */
+               } else {
+                       /* XXX: nothing to do ? skip end tag search ??? return to parent level ? */
+               }
+
+               /* Destroy subpats array */
+               zend_array_destroy(Z_ARR(subpats));
+
+               /* Create close tag string */
+               //ZEND_ASSERT(snprintf(closestr, closelen + 1, "/\\[\\/%s\\]/", ZSTR_VAL(cur)) == closelen);
+               ZEND_ASSERT(snprintf(closestr, closelen + 1, "[/%s]", ZSTR_VAL(cur)) == closelen);
+
+               /* Look for close tag */
+               /* XXX: watch out, this is a maximum length to consider, the close tag may be present earlier for current leaf */
+               if ((found = (char *)zend_memnrstr(str, closestr, closelen, str+len))) {
+                       /* Reduce len by the size of trailing string since close tag */
+                       len -= len + str - found;
+               }
+
+               /* Free openstr and closestr */
+               free(openstr);
+       }
+
+       printf("str=%.*s\n", len, str);
+       fflush(NULL);
+
+       return NULL;
+
+#if 0
+       fflush(NULL);
+       printf("str=%s\n", str);
+#endif
+
+       /* XXX: debug */
+       zval ztag;
+       ZVAL_ARR(&ztag, tag);
+       php_debug_zval_dump(&ztag, 0);
+       fflush(NULL);
+       exit(1);
+
+       php_debug_zval_dump(zopen, 0);
+       php_debug_zval_dump(zclose, 0);
+       php_debug_zval_dump(zdefault, 0);
+       fflush(NULL);
+       exit(1);
+
+       if (argstr != NULL) {
+               printf("arg=%s[%ld]\n", ZSTR_VAL(argstr), ZSTR_LEN(argstr));
+               fflush(NULL);
+               exit(1);
        }
 
-       /* Free smiley hashtable */
-       if (obj->smiley) {
-               efree(obj->smiley);
+       //XXX: same here unfinished, code start here :)
+       //TODO: check what happen when we have something like that : [t]s[/t][t]p[/t]
+       //1: compute open and close tag
+       //2: extract end of open tag position and begin of close tag reverse searched in current space
+       //(if we don't find end tag, consider positionning to end of string or rise error ? parse flag involved here)
+       //3: append to return string open tag
+       //4: loop for child tags in subspace and trigger recursion on space between current open and close tags
+       //(loop involved here)
+       //5: duplicate plain strings between each child in return string
+       //6: append the close tag
+
+#if 0
+       /* Check if non empty string (other tag that empty root) */
+       if (strcmp(ZSTR_VAL(cur), "") != 0) {
+               //TODO: generate search patterns for opening tag and closing tag
+               //TODO: search position of opening tag
+               //TODO: search close tag
+               //TODO: search for child tag
+       /* Non root tag */
+       } else {
+               //Nothing to do, search offset for child will be 0 and length of search to len
+               //TODO: search current end tag here ?
        }
+
+       /* Search next child node */
+
+       /**/
+       //bbcode_search_node(ret,
+#endif
+
+       /* Return string by default */
+       return rs;
 }
-/* }}} */
 
 /* { { { PHP_METHOD(BBCode, parse) {
  */
 PHP_METHOD(BBCode, parse) {
+       /* Init string to parse */
        zend_string *str = NULL;
 
-       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) {
-               return;
-       }
+       /* Return string */
+       //TODO: voir si on crée pas une struct pour éviter de trainer 3 arguments
+       char *rs = NULL;
+
+       /* Return offset and length */
+       int ros = 0, rlen = 0;
+
+       /* Parse parameters */
+       /*if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) { return; }*/
+       ZEND_PARSE_PARAMETERS_START(1, 1)
+               Z_PARAM_STR(str)
+       ZEND_PARSE_PARAMETERS_END();
+
+       /* Retrieve the pointer to this object */
+       bbcode_object *obj = Z_BBCODE_P(getThis());
+
+       /* Init return string and set length to string * 2 plus last null char */
+       //ZEND_ASSERT((rs = (char *)malloc((rlen = (2*Z_STRLEN(str)) + 1)*sizeof(char)) != NULL);
+
+       /* Go compute the tree */
+       bbcode_parse(obj->tag, obj->root, rs, &ros, &rlen, ZSTR_VAL(str), ZSTR_LEN(str));
+
+       /* Free return string */
+       free(rs);
+
+#if 0
+       /* Retrieve root node */
+       zval *root = zend_hash_find(obj->tag, obj->root);
+
+       bbcode_
+
+                               /* 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);
+#endif
+
+#if BBCODE_DEBUG
+       /* Tag */
+       zval tag;
+
+       /* Init new child val array */
+       ZVAL_ARR(&tag, obj->tag);
+
+       php_debug_zval_dump(&tag, 0);
+       fflush(NULL);
+#endif
+
+       //printf("flag=%ld\n", obj->flag);
+       //printf("die ici: %s +%d\n", __FILE__, __LINE__);
 
        //TODO: create a tree for storing result
        //TODO: apply recursively search of next possible tags (or a closing parent tag ?) regexp and store result inside
        //TODO: d
        //TODO: apply the smiley parsing on string
 
+       /* TODO: the return string */
        RETURN_STR(str);
 }
 /* }}} */
@@ -870,8 +1808,7 @@ const zend_function_entry bbcode_methods[] = {
 
 /* {{{ PHP_MINIT_FUNCTION
  */
-PHP_MINIT_FUNCTION(bbcode)
-{
+PHP_MINIT_FUNCTION(bbcode) {
        /* If you have INI entries, uncomment these lines
        REGISTER_INI_ENTRIES();
        */
@@ -895,14 +1832,14 @@ PHP_MINIT_FUNCTION(bbcode)
        /* Register BBCodeException class entry */
        bbcode_exception_ce = zend_register_internal_class_ex(&tmp_ce, zend_exception_get_default(TSRMLS_C) TSRMLS_CC);
 
+       /* Send success */
        return SUCCESS;
 }
 /* }}} */
 
 /* {{{ PHP_MSHUTDOWN_FUNCTION
  */
-PHP_MSHUTDOWN_FUNCTION(bbcode)
-{
+PHP_MSHUTDOWN_FUNCTION(bbcode) {
        /* uncomment this line if you have INI entries
        UNREGISTER_INI_ENTRIES();
        */
@@ -912,8 +1849,7 @@ PHP_MSHUTDOWN_FUNCTION(bbcode)
 
 /* {{{ PHP_MINFO_FUNCTION
  */
-PHP_MINFO_FUNCTION(bbcode)
-{
+PHP_MINFO_FUNCTION(bbcode) {
        php_info_print_table_start();
        php_info_print_table_header(2, PHP_BBCODE_NAME " support", "enabled");
        php_info_print_table_row(2, "Extension version", PHP_BBCODE_VERSION);
@@ -929,14 +1865,14 @@ PHP_MINFO_FUNCTION(bbcode)
  */
 zend_module_entry bbcode_module_entry = {
        STANDARD_MODULE_HEADER,
-       PHP_BBCODE_NAME,
-       NULL, /*bbcode_functions,*/
-       PHP_MINIT(bbcode),
-       PHP_MSHUTDOWN(bbcode),
-       NULL,
-       NULL,
-       PHP_MINFO(bbcode),
-       PHP_BBCODE_VERSION,
+       PHP_BBCODE_NAME,                        /* extension name */
+       NULL,                                           /* function list */
+       PHP_MINIT(bbcode),                      /* process startup */
+       PHP_MSHUTDOWN(bbcode),          /* process shutdown */
+       NULL,                                           /* request startup */
+       NULL,                                           /* request shutdown */
+       PHP_MINFO(bbcode),                      /* extension info */
+       PHP_BBCODE_VERSION,                     /* extension version */
        STANDARD_MODULE_PROPERTIES
 };
 /* }}} */