Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions boxwood.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

}

/**
Expand Down Expand Up @@ -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
Expand Down
105 changes: 92 additions & 13 deletions php_boxwood.c
Original file line number Diff line number Diff line change
Expand Up @@ -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[] */
};
/* }}} */
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}
1 change: 1 addition & 0 deletions php_boxwood.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
33 changes: 33 additions & 0 deletions tests/014.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
Test EXISTS function
--SKIPIF--
<?php if (!extension_loaded("boxwood")) echo "skip"; ?>
--FILE--
<?php
$r = boxwood_new();
boxwood_add_text($r, "airplane");
boxwood_add_text($r, "train");

$tests = [
'airplane norway',
'my norway train',
'toast ting',
'no way',
'drive my train to norway',
'fly airplane to tokyo'
];

foreach ($tests as $test) {
echo (boxwood_exists($r, $test) === true ? "yes" : "no"), "\n";
}

echo (boxwood_exists($r, $tests) === true ? "yes" : "no"), "\n";
?>
--EXPECT--
yes
yes
no
no
yes
yes
yes
29 changes: 29 additions & 0 deletions tests/015.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
Test Multi Add Text function
--SKIPIF--
<?php if (!extension_loaded("boxwood")) echo "skip"; ?>
--FILE--
<?php
$r = boxwood_new();
boxwood_add_text($r, ["airplane","train"]);

$tests = array(
'airplane norway',
'my norway train',
'toast ting',
'no way',
'drive my train to norway',
'fly airplane to tokyo'
);

foreach ($tests as $test) {
echo (boxwood_exists($r, $test) === true ? "yes" : "no"), "\n";
}
?>
--EXPECT--
yes
yes
no
no
yes
yes