diff --git a/boxwood.c b/boxwood.c index 821f47f..b76b9a3 100644 --- a/boxwood.c +++ b/boxwood.c @@ -392,6 +392,69 @@ static int bw_replace_proper(struct bw_node_t *root, byte *modified_bytes, byte return len - shrunk_bytes; +} + + /* + start at root of trie and first byte + if byte is not in trie, go to next byte and repeat + if byte is in trie, save position as bad_word_start_maybe, advance byte and advance down trie + while byte is in trie, continue advancing each + if we get to the point where there is nothing in the next[] array on the trie node, we've reached the end of the + word, so replace from bad_word_start_maybe+1 to current byte with *s, and resume with root of trie and next byte + if we get to the point where the next[] array is not empty but does not contain an entry for the current byte + then we've diverged from the stem that matches, so don't do any replacement and start re-parsing at + bad_word_start_maybe+1 + + */ +static int bw_exists_proper(struct bw_node_t *root, byte *bytes_to_walk, int len, int check_multibyte, byte *word_boundary_chars) { + int match_start_maybe, i = 0, shrunk_bytes = 0, previous_terminal = 0; + int endok = 0; + struct bw_node_t *current_node; + + while (i < len) { + if ((! bw_node_has_next(root, bytes_to_walk[i])) // no matches at start + || (! boundary_before_word(word_boundary_chars, i, bytes_to_walk))) + { + i++; + } + else { + match_start_maybe = i; + current_node = root; + previous_terminal = 0; + do { + current_node = current_node->next[bytes_to_walk[i]]; + i++; + if (current_node->is_terminal + && boundary_after_word(word_boundary_chars, i, len, bytes_to_walk)) + { + previous_terminal = i; + } + } + while (bw_node_has_next(current_node, bytes_to_walk[i])); + + /* If we've stopped advancing because we've reached a character in the + * text which doesn't match the next byte in the trie -- then check to + * see if there was a previous node on our traversal that was marked + * as terminal -- that means even though we didn't match the longer + * word, there was a previous shorter prefix that we should treat as + * a match + */ + if ((! current_node->is_terminal) && previous_terminal) { + i = previous_terminal; + } + + /* if we've reached the end of the word, replace */ + if (((! current_node->has_next) && boundary_after_word(word_boundary_chars, i, len, bytes_to_walk)) || previous_terminal) { + return 1; + } + else { /* diverging from stem, start reparsing */ + i = match_start_maybe + 1; + } + } + } + + return 0; + } /** @@ -425,6 +488,28 @@ byte *bw_replace_text(struct bw_trie_t *trie, byte *text, byte replacement, int return modified_text; } +/* +* Return 1 is any are found, 0 for false +*/ +int bw_exists_text(struct bw_trie_t *trie, byte *text, int wordbound) { + int text_len = strlen((char *)text); + int result = 0; + + byte *text_to_walk; + if (trie->case_insensitive && trie->folding_trie) { + text_to_walk = case_fold_lower(trie->folding_trie, text, text_len); + } else { + text_to_walk = text; + } + + result = bw_exists_proper(trie->root, text_to_walk, text_len, 1, wordbound ? trie->word_boundary_chars : NULL); + + if (text_to_walk != text) { + free(text_to_walk); + } + + return result; +} /** * Replace sub-sequences in the byte sequence that match words added to the trie. No diff --git a/php_boxwood.c b/php_boxwood.c index 5b31c47..4a53e3b 100644 --- a/php_boxwood.c +++ b/php_boxwood.c @@ -35,6 +35,7 @@ zend_function_entry boxwood_functions[] = { PHP_FE(boxwood_add_text, NULL) PHP_FE(boxwood_replace_text, NULL) PHP_FE(boxwood_set_word_boundary_bytes, NULL) + PHP_FE(boxwood_exists, NULL) {NULL, NULL, NULL} /* Must be the last line in boxwood_functions[] */ }; /* }}} */ @@ -120,18 +121,33 @@ PHP_FUNCTION(boxwood_add_text) { struct bw_trie_t *trie; zval *znode; - char *text; - int text_len; + zval *ztext; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &znode, &text, &text_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rz", &znode, &ztext) == FAILURE) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(trie, struct bw_trie_t*, &znode, -1, PHP_BOXWOOD_TRIE_RES_NAME, le_bw_trie); - int added = bw_add_text(trie, (byte *) text); - RETURN_LONG(added); + int added = 0; + + if (Z_TYPE_P(ztext) == IS_STRING) { + added = bw_add_text(trie, (byte *) Z_STRVAL_P(ztext)); + } + else if (Z_TYPE_P(ztext) == IS_ARRAY) { + zval **one_text; + HashTable *arr = Z_ARRVAL_P(ztext); + HashPosition ptr; + + for (zend_hash_internal_pointer_reset_ex(arr, &ptr); + zend_hash_get_current_data_ex(arr, (void **) &one_text, &ptr) == SUCCESS; + zend_hash_move_forward_ex(arr, &ptr)) { + added += bw_add_text(trie, (byte *) Z_STRVAL_PP(one_text)); + } + } + + RETURN_LONG(added); } PHP_FUNCTION(boxwood_replace_text) @@ -235,11 +251,74 @@ PHP_FUNCTION(boxwood_set_word_boundary_bytes) bw_set_word_boundary_bytes(trie, text); } -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - * vim600: noet sw=4 ts=4 fdm=marker - * vim<600: noet sw=4 ts=4 - */ +PHP_FUNCTION(boxwood_exists) +{ + struct bw_trie_t *trie; + zval *znode; + zval *ztext; + zval *zwordbound; + int result; + + int wordbound = 0; + + int num_args = ZEND_NUM_ARGS(); + + if (num_args == 2) { + if (zend_parse_parameters(num_args TSRMLS_CC, "zz", &znode, &ztext) == FAILURE) { + WRONG_PARAM_COUNT; + } + } else if (num_args == 3) { + if (zend_parse_parameters(num_args TSRMLS_CC, "zzz", &znode, &ztext,&zwordbound) == FAILURE) { + WRONG_PARAM_COUNT; + } + } else { + WRONG_PARAM_COUNT; + } + + if (Z_TYPE_P(znode) != IS_RESOURCE) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Parameter mismatch: first argument must be a Boxwood resource"); + RETURN_FALSE; + } + if (Z_TYPE_P(ztext) != IS_STRING && Z_TYPE_P(ztext) != IS_ARRAY) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Parameter mismatch: second argument must be a string or array"); + RETURN_FALSE; + } + + if (num_args == 3) { + if (Z_TYPE_P(zwordbound) != IS_BOOL) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Parameter mismatch: fourth argument must be a boolean"); + RETURN_FALSE; + } + wordbound = Z_BVAL_P(zwordbound) ? 1 : 0; + } + + ZEND_FETCH_RESOURCE(trie, struct bw_trie_t*, &znode, -1, PHP_BOXWOOD_TRIE_RES_NAME, le_bw_trie); + + if (Z_TYPE_P(ztext) == IS_STRING) { + result = bw_exists_text(trie, (byte *) Z_STRVAL_P(ztext), wordbound); + if(result == 1) { + RETURN_TRUE; + } + else { + RETURN_FALSE; + } + } + else if (Z_TYPE_P(ztext) == IS_ARRAY) { + zval **one_text; + HashTable *arr = Z_ARRVAL_P(ztext); + HashPosition ptr; + for (zend_hash_internal_pointer_reset_ex(arr, &ptr); + zend_hash_get_current_data_ex(arr, (void **) &one_text, &ptr) == SUCCESS; + zend_hash_move_forward_ex(arr, &ptr)) { + + result = bw_exists_text(trie, (byte *) Z_STRVAL_PP(one_text), wordbound); + if(result == 1) { + RETURN_TRUE; + } + } + + RETURN_FALSE; + } + + RETURN_FALSE; +} diff --git a/php_boxwood.h b/php_boxwood.h index 1308e88..04a5e03 100644 --- a/php_boxwood.h +++ b/php_boxwood.h @@ -39,6 +39,7 @@ PHP_FUNCTION(boxwood_new); PHP_FUNCTION(boxwood_add_text); PHP_FUNCTION(boxwood_replace_text); PHP_FUNCTION(boxwood_set_word_boundary_bytes); +PHP_FUNCTION(boxwood_exists); ZEND_BEGIN_MODULE_GLOBALS(boxwood) struct case_fold_branch_t *folding_trie; diff --git a/tests/014.phpt b/tests/014.phpt new file mode 100644 index 0000000..a4460ab --- /dev/null +++ b/tests/014.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test EXISTS function +--SKIPIF-- + +--FILE-- + +--EXPECT-- +yes +yes +no +no +yes +yes +yes \ No newline at end of file diff --git a/tests/015.phpt b/tests/015.phpt new file mode 100644 index 0000000..d4a34b9 --- /dev/null +++ b/tests/015.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test Multi Add Text function +--SKIPIF-- + +--FILE-- + +--EXPECT-- +yes +yes +no +no +yes +yes