Skip to content

Commit bd09c73

Browse files
authored
Update grapheme_mask.c
1 parent 017feb9 commit bd09c73

1 file changed

Lines changed: 179 additions & 191 deletions

File tree

ext/intl/grapheme/grapheme_mask.c

Lines changed: 179 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
| obtain it through the world-wide-web, please send a note to |
1111
| license@php.net so we can mail you a copy immediately. |
1212
+----------------------------------------------------------------------+
13-
| Authors: Sepehr mahmoodi <sepehrphpr@gmail.com> |
13+
| Author: Sepehr <sepehrphpr@gmail.com> |
1414
+----------------------------------------------------------------------+
1515
*/
1616

@@ -19,207 +19,195 @@
1919
#endif
2020

2121
#include "php.h"
22+
#include "ext/standard/info.h"
2223
#include "php_intl.h"
24+
#include "intl_common.h"
2325
#include "intl_error.h"
24-
#include "grapheme.h"
25-
#include "grapheme_util.h"
26-
#include "grapheme_mask.h"
26+
#include "grapheme/grapheme.h"
27+
#include "grapheme/grapheme_util.h"
2728

28-
#include <unicode/ubrk.h>
29-
#include <unicode/utext.h>
3029
#include <unicode/utypes.h>
3130
#include <unicode/utf8.h>
31+
#include <unicode/utext.h>
32+
#include <unicode/ubrk.h>
33+
#include <unicode/ustring.h>
3234

33-
/* {{{ grapheme_mask_validate_mask_char */
34-
static UBool grapheme_mask_validate_mask_char(const char *mask_char, size_t mask_char_len)
35+
/* {{{ Checks if a string is valid UTF-8 */
36+
static zend_bool grapheme_mask_is_valid_utf8(const char *str, size_t str_len)
3537
{
36-
if (mask_char_len == 0) {
37-
return 0;
38-
}
39-
40-
UErrorCode status = U_ZERO_ERROR;
41-
UText *ut = utext_openUTF8(NULL, mask_char, mask_char_len, &status);
42-
if (U_FAILURE(status)) {
43-
return 0;
44-
}
45-
46-
/* Use the existing grapheme_count_graphemes function */
47-
int32_t count = grapheme_count_graphemes(ut);
48-
utext_close(ut);
49-
50-
return (count == 1);
38+
UChar32 c;
39+
int32_t i = 0;
40+
41+
while (i < (int32_t)str_len) {
42+
U8_NEXT(str, i, (int32_t)str_len, c);
43+
if (c < 0) {
44+
return 0;
45+
}
46+
}
47+
return 1;
5148
}
5249
/* }}} */
5350

54-
/* {{{ PHP_FUNCTION(grapheme_mask) */
51+
/* {{{ Masks grapheme clusters in a UTF-8 string */
5552
PHP_FUNCTION(grapheme_mask)
5653
{
57-
zend_string *str;
58-
zend_string *mask_char = NULL;
59-
zend_long offset = 0;
60-
zend_long length = 0;
61-
zend_bool length_is_null = 1;
62-
int32_t total_graphemes = 0;
63-
int32_t start = 0, mask_len = 0;
64-
int32_t *boundaries = NULL;
65-
int32_t boundaries_capacity = 16;
66-
int32_t start_byte_offset, end_byte_offset;
67-
zend_string *result;
68-
char *p;
69-
70-
ZEND_PARSE_PARAMETERS_START(1, 4)
71-
Z_PARAM_STR(str)
72-
Z_PARAM_OPTIONAL
73-
Z_PARAM_STR_DEFAULT(mask_char, "*")
74-
Z_PARAM_LONG(offset)
75-
Z_PARAM_LONG_OR_NULL(length, length_is_null)
76-
ZEND_PARSE_PARAMETERS_END();
77-
78-
/* empty string -> return as is */
79-
if (ZSTR_LEN(str) == 0) {
80-
RETURN_STR_COPY(str);
81-
}
82-
83-
/* validate mask_char: must be exactly one grapheme cluster */
84-
if (!grapheme_mask_validate_mask_char(ZSTR_VAL(mask_char), ZSTR_LEN(mask_char))) {
85-
zend_argument_value_error(2, "must be exactly one grapheme cluster");
86-
RETURN_THROWS();
87-
}
88-
89-
/* validate UTF-8 input - using existing helper */
90-
if (!grapheme_ascii_check(ZSTR_VAL(str), ZSTR_LEN(str))) {
91-
/* Not ASCII, need to validate UTF-8 */
92-
UErrorCode status = U_ZERO_ERROR;
93-
UText *ut = utext_openUTF8(NULL, ZSTR_VAL(str), ZSTR_LEN(str), &status);
94-
if (U_FAILURE(status)) {
95-
intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "Invalid UTF-8 string", 1);
96-
RETURN_FALSE;
97-
}
98-
utext_close(ut);
99-
}
100-
101-
/* build cache of all grapheme boundaries */
102-
{
103-
UErrorCode status = U_ZERO_ERROR;
104-
UText *ut = NULL;
105-
UBreakIterator *bi = NULL;
106-
107-
ut = utext_openUTF8(NULL, ZSTR_VAL(str), ZSTR_LEN(str), &status);
108-
if (U_FAILURE(status)) {
109-
intl_error_set(NULL, status, "utext_openUTF8 failed", 1);
110-
RETURN_FALSE;
111-
}
112-
113-
bi = grapheme_get_break_iterator(&status);
114-
if (U_FAILURE(status)) {
115-
utext_close(ut);
116-
intl_error_set(NULL, status, "grapheme_get_break_iterator failed", 1);
117-
RETURN_FALSE;
118-
}
119-
120-
ubrk_setUText(bi, ut, &status);
121-
if (U_FAILURE(status)) {
122-
ubrk_close(bi);
123-
utext_close(ut);
124-
intl_error_set(NULL, status, "ubrk_setUText failed", 1);
125-
RETURN_FALSE;
126-
}
127-
128-
boundaries = emalloc(boundaries_capacity * sizeof(int32_t));
129-
130-
/* collect all boundaries */
131-
int32_t pos = ubrk_first(bi);
132-
while (pos != UBRK_DONE) {
133-
if (total_graphemes >= boundaries_capacity) {
134-
boundaries_capacity *= 2;
135-
int32_t *new_boundaries = erealloc(boundaries, boundaries_capacity * sizeof(int32_t));
136-
if (!new_boundaries) {
137-
ubrk_close(bi);
138-
utext_close(ut);
139-
efree(boundaries);
140-
zend_error(E_ERROR, "Memory allocation failed");
141-
}
142-
boundaries = new_boundaries;
143-
}
144-
boundaries[total_graphemes] = pos;
145-
total_graphemes++;
146-
pos = ubrk_next(bi);
147-
}
148-
149-
ubrk_close(bi);
150-
utext_close(ut);
151-
}
152-
153-
/* Adjust offset */
154-
if (offset < 0) {
155-
offset = total_graphemes + offset;
156-
if (offset < 0) offset = 0;
157-
} else if (offset > total_graphemes) {
158-
offset = total_graphemes;
159-
}
160-
start = (int32_t)offset;
161-
162-
/* Determine mask_len */
163-
if (length_is_null) {
164-
mask_len = total_graphemes - start;
165-
} else {
166-
if (length < 0) {
167-
/* negative length from end */
168-
length = total_graphemes - start + length;
169-
if (length < 0) length = 0;
170-
}
171-
mask_len = (int32_t)length;
172-
if (start + mask_len > total_graphemes) {
173-
mask_len = total_graphemes - start;
174-
}
175-
}
176-
177-
/* No-op if nothing to mask */
178-
if (mask_len <= 0 || start >= total_graphemes) {
179-
efree(boundaries);
180-
RETURN_STR_COPY(str);
181-
}
182-
183-
/* Compute byte offsets */
184-
start_byte_offset = boundaries[start];
185-
if (start + mask_len == total_graphemes) {
186-
end_byte_offset = (int32_t)ZSTR_LEN(str);
187-
} else {
188-
end_byte_offset = boundaries[start + mask_len];
189-
}
190-
191-
/* Build result string: prefix + mask_char repeated per grapheme + suffix */
192-
{
193-
size_t mask_char_len = ZSTR_LEN(mask_char);
194-
size_t prefix_len = start_byte_offset;
195-
size_t masked_len = mask_len * mask_char_len;
196-
size_t suffix_len = ZSTR_LEN(str) - end_byte_offset;
197-
size_t final_len = prefix_len + masked_len + suffix_len;
198-
199-
result = zend_string_alloc(final_len, 0);
200-
p = ZSTR_VAL(result);
201-
202-
/* Copy prefix (bytes before masking) */
203-
if (prefix_len > 0) {
204-
memcpy(p, ZSTR_VAL(str), prefix_len);
205-
p += prefix_len;
206-
}
207-
208-
/* Write mask_char for each grapheme to be masked */
209-
for (int32_t i = 0; i < mask_len; i++) {
210-
memcpy(p, ZSTR_VAL(mask_char), mask_char_len);
211-
p += mask_char_len;
212-
}
213-
214-
/* Copy suffix (bytes after masking) */
215-
if (suffix_len > 0) {
216-
memcpy(p, ZSTR_VAL(str) + end_byte_offset, suffix_len);
217-
}
218-
219-
ZSTR_VAL(result)[final_len] = '\0';
220-
}
221-
222-
efree(boundaries);
223-
RETURN_NEW_STR(result);
54+
char *str, *mask_str;
55+
size_t str_len, mask_len;
56+
zend_long start = 0, length = 0;
57+
zend_bool start_is_null = 1, length_is_null = 1;
58+
59+
/* Parse parameters: string, mask_string, [start, [length]] */
60+
ZEND_PARSE_PARAMETERS_START(2, 4)
61+
Z_PARAM_STRING(str, str_len)
62+
Z_PARAM_STRING(mask_str, mask_len)
63+
Z_PARAM_OPTIONAL
64+
Z_PARAM_ZVAL_OR_LONG(start, start_is_null)
65+
Z_PARAM_ZVAL_OR_LONG(length, length_is_null)
66+
ZEND_PARSE_PARAMETERS_END();
67+
68+
/* Validate UTF-8 for the input string */
69+
if (str_len > 0 && !grapheme_mask_is_valid_utf8(str, str_len)) {
70+
php_error_docref(NULL, E_WARNING, "Input string is not valid UTF-8");
71+
RETURN_FALSE;
72+
}
73+
74+
/* If start is beyond string, return empty string */
75+
if (start >= (zend_long)str_len) {
76+
RETURN_EMPTY_STRING();
77+
}
78+
79+
if (mask_len == 0) {
80+
php_error_docref(NULL, E_WARNING, "Mask string must not be empty");
81+
RETURN_FALSE;
82+
}
83+
84+
/* Validate UTF-8 for the mask string */
85+
if (!grapheme_mask_is_valid_utf8(mask_str, mask_len)) {
86+
php_error_docref(NULL, E_WARNING, "Mask string is not valid UTF-8");
87+
RETURN_FALSE;
88+
}
89+
90+
/* Open UText for the input string */
91+
UErrorCode status = U_ZERO_ERROR;
92+
UText *ut = utext_openUTF8(NULL, str, (int64_t)str_len, &status);
93+
if (U_FAILURE(status)) {
94+
php_error_docref(NULL, E_WARNING, "Failed to open UText for input string");
95+
RETURN_FALSE;
96+
}
97+
98+
/* Get grapheme break iterator */
99+
UBreakIterator *bi = grapheme_get_break_iterator(NULL, &status);
100+
if (U_FAILURE(status)) {
101+
utext_close(ut);
102+
php_error_docref(NULL, E_WARNING, "Failed to create grapheme break iterator");
103+
RETURN_FALSE;
104+
}
105+
106+
/* Set the break iterator text */
107+
ubrk_setUText(bi, ut, &status);
108+
if (U_FAILURE(status)) {
109+
ubrk_close(bi);
110+
utext_close(ut);
111+
php_error_docref(NULL, E_WARNING, "Failed to set text for break iterator");
112+
RETURN_FALSE;
113+
}
114+
115+
/* Collect grapheme boundaries */
116+
int32_t *boundaries;
117+
int32_t boundary_count;
118+
int32_t buflen = 128;
119+
int32_t pos;
120+
121+
boundaries = (int32_t *)emalloc(sizeof(int32_t) * (size_t)buflen);
122+
boundary_count = 0;
123+
boundaries[boundary_count++] = 0;
124+
125+
pos = ubrk_first(bi);
126+
while (pos != UBRK_DONE) {
127+
pos = ubrk_next(bi);
128+
if (pos != UBRK_DONE) {
129+
if (boundary_count >= buflen) {
130+
buflen *= 2;
131+
boundaries = (int32_t *)erealloc(boundaries, sizeof(int32_t) * (size_t)buflen);
132+
}
133+
boundaries[boundary_count++] = pos;
134+
}
135+
}
136+
137+
/* Determine range to mask */
138+
int32_t range_start, range_end;
139+
if (start_is_null) {
140+
range_start = 0;
141+
} else {
142+
if (start < 0) {
143+
range_start = boundary_count - 1 + (int32_t)start;
144+
if (range_start < 0) range_start = 0;
145+
} else {
146+
range_start = (int32_t)start;
147+
if (range_start >= boundary_count) range_start = boundary_count - 1;
148+
}
149+
}
150+
151+
if (length_is_null) {
152+
range_end = boundary_count - 1;
153+
} else {
154+
if (length < 0) {
155+
range_end = boundary_count - 1 + (int32_t)length;
156+
if (range_end < 0) range_end = 0;
157+
} else {
158+
range_end = range_start + (int32_t)length;
159+
if (range_end > boundary_count - 1) range_end = boundary_count - 1;
160+
}
161+
}
162+
163+
/* Build result */
164+
zend_string *result;
165+
166+
/* If no graphemes to mask, return original string */
167+
if (range_start >= range_end || range_start >= boundary_count - 1) {
168+
efree(boundaries);
169+
ubrk_close(bi);
170+
utext_close(ut);
171+
RETURN_STRINGL(str, str_len);
172+
}
173+
174+
/* Prefix: from byte 0 to the start of the range */
175+
int32_t prefix_len = boundaries[range_start];
176+
/* Suffix: from the end of the range to the end of the string */
177+
int32_t suffix_start = boundaries[range_end];
178+
int32_t suffix_len = (int32_t)str_len - suffix_start;
179+
180+
/* Calculate number of graphemes to mask */
181+
int32_t graphemes_to_mask = range_end - range_start;
182+
183+
/* Calculate total result length */
184+
size_t result_len = (size_t)prefix_len + ((size_t)graphemes_to_mask * mask_len) + (size_t)suffix_len;
185+
186+
result = zend_string_alloc(result_len, 0);
187+
188+
/* Copy prefix */
189+
if (prefix_len > 0) {
190+
memcpy(ZSTR_VAL(result), str, (size_t)prefix_len);
191+
}
192+
193+
/* Copy mask for each grapheme */
194+
char *dest = ZSTR_VAL(result) + prefix_len;
195+
for (int32_t i = 0; i < graphemes_to_mask; i++) {
196+
memcpy(dest, mask_str, mask_len);
197+
dest += mask_len;
198+
}
199+
200+
/* Copy suffix */
201+
if (suffix_len > 0) {
202+
memcpy(dest, str + suffix_start, (size_t)suffix_len);
203+
}
204+
205+
ZSTR_VAL(result)[result_len] = '\0';
206+
207+
efree(boundaries);
208+
ubrk_close(bi);
209+
utext_close(ut);
210+
211+
RETURN_STR(result);
224212
}
225213
/* }}} */

0 commit comments

Comments
 (0)