Skip to content

Commit ea1ee1c

Browse files
Intl: Add IntlListFormatter class
1 parent 8376904 commit ea1ee1c

File tree

11 files changed

+726
-0
lines changed

11 files changed

+726
-0
lines changed

ext/intl/config.m4

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ if test "$PHP_INTL" != "no"; then
3939
locale/locale_class.c
4040
locale/locale_methods.c
4141
locale/locale.c
42+
listformatter/listformatter_class.c
43+
listformatter/listformatter_data.c
4244
msgformat/msgformat_attr.c
4345
msgformat/msgformat_class.c
4446
msgformat/msgformat_data.c
@@ -119,6 +121,7 @@ if test "$PHP_INTL" != "no"; then
119121
$ext_builddir/grapheme
120122
$ext_builddir/idn
121123
$ext_builddir/locale
124+
$ext_builddir/listformatter
122125
$ext_builddir/msgformat
123126
$ext_builddir/normalizer
124127
$ext_builddir/resourcebundle
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
/** @generate-class-entries */
4+
5+
6+
class IntlListFormatter {
7+
8+
/** @cvalue ULISTFMT_TYPE_OR */
9+
public const int TYPE_OR = UNKNOWN;
10+
11+
/** @cvalue ULISTFMT_TYPE_UNITS */
12+
public const int TYPE_UNITS = UNKNOWN;
13+
14+
/** @cvalue ULISTFMT_TYPE_AND */
15+
public const int TYPE_AND = UNKNOWN;
16+
17+
/** @cvalue ULISTFMT_WIDTH_WIDE */
18+
public const int WIDTH_WIDE = UNKNOWN;
19+
20+
/** @cvalue ULISTFMT_WIDTH_SHORT */
21+
public const int WIDTH_SHORT = UNKNOWN;
22+
23+
/** @cvalue ULISTFMT_WIDTH_NARROW */
24+
public const int WIDTH_NARROW = UNKNOWN;
25+
26+
/**
27+
* @param string $locale
28+
* @param int $type
29+
* @param int $width
30+
*/
31+
public function __construct(string $locale, int $type = IntlListFormatter::TYPE_AND, int $width = IntlListFormatter::WIDTH_WIDE) {}
32+
33+
/**
34+
* @param array $strings
35+
*
36+
* @return string|false
37+
*/
38+
public function format(array $strings): string|false {}
39+
40+
/**
41+
* @return int
42+
*/
43+
public function getErrorCode(): int {}
44+
45+
/**
46+
* @return string
47+
*/
48+
public function getErrorMessage(): string {}
49+
}

ext/intl/listformatter/listformatter_arginfo.h

Lines changed: 77 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| This source file is subject to version 3.01 of the PHP license, |
4+
| that is bundled with this package in the file LICENSE, and is |
5+
| available through the world-wide-web at the following url: |
6+
| https://www.php.net/license/3_01.txt |
7+
| If you did not receive a copy of the PHP license and are unable to |
8+
| obtain it through the world-wide-web, please send a note to |
9+
| [email protected] so we can mail you a copy immediately. |
10+
+----------------------------------------------------------------------+
11+
| Authors: Bogdan Ungureanu <[email protected]> |
12+
+----------------------------------------------------------------------+
13+
*/
14+
15+
#include "php.h"
16+
#include "php_intl.h"
17+
#include <unicode/ulistformatter.h>
18+
#include "listformatter_arginfo.h"
19+
#include "listformatter_class.h"
20+
#include "intl_convert.h"
21+
22+
23+
static zend_object_handlers listformatter_handlers;
24+
25+
/* {{{ listformatter_free_obj */
26+
static void listformatter_free_obj(zend_object *object)
27+
{
28+
ListFormatter_object *obj = php_intl_listformatter_fetch_object(object);
29+
listformatter_data_free(&obj->lf_data);
30+
zend_object_std_dtor(&obj->zo);
31+
}
32+
/* }}} */
33+
34+
/* {{{ listformatter_create_object */
35+
static zend_object *listformatter_create_object(zend_class_entry *class_type)
36+
{
37+
ListFormatter_object *obj;
38+
obj = zend_object_alloc(sizeof(ListFormatter_object), class_type);
39+
listformatter_data_init(&obj->lf_data);
40+
zend_object_std_init(&obj->zo, class_type);
41+
object_properties_init(&obj->zo, class_type);
42+
obj->zo.handlers = &listformatter_handlers;
43+
return &obj->zo;
44+
}
45+
/* }}} */
46+
47+
/* {{{ listformatter_create_object */
48+
PHP_METHOD(IntlListFormatter, __construct)
49+
{
50+
ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS);
51+
zend_string *locale;
52+
zend_long type = ULISTFMT_TYPE_AND;
53+
zend_long width = ULISTFMT_WIDTH_WIDE;
54+
ZEND_PARSE_PARAMETERS_START(1, 3)
55+
Z_PARAM_STR(locale)
56+
Z_PARAM_OPTIONAL
57+
Z_PARAM_LONG(type)
58+
Z_PARAM_LONG(width)
59+
ZEND_PARSE_PARAMETERS_END();
60+
61+
if (strlen(uloc_getISO3Language(ZSTR_VAL(locale))) == 0) {
62+
zend_argument_value_error(1, "\"%s\" is invalid", ZSTR_VAL(locale));
63+
RETURN_THROWS();
64+
}
65+
66+
UErrorCode status = U_ZERO_ERROR;
67+
if (U_ICU_VERSION_MAJOR_NUM >= 67) {
68+
69+
LISTFORMATTER_OBJECT(obj) = ulistfmt_openForType(ZSTR_VAL(locale), type, width, &status);
70+
} else {
71+
if (type != ULISTFMT_TYPE_AND) {
72+
zend_argument_value_error(2, "ICU 66 and below only support IntlListFormatter::TYPE_AND");
73+
RETURN_THROWS();
74+
}
75+
76+
if (width != ULISTFMT_WIDTH_WIDE) {
77+
zend_argument_value_error(3, "ICU 66 and below only support IntlListFormatter::WIDTH_WIDE");
78+
RETURN_THROWS();
79+
}
80+
81+
LISTFORMATTER_OBJECT(obj) = ulistfmt_open(ZSTR_VAL(locale), &status);
82+
}
83+
if (U_FAILURE(status)) {
84+
intl_error_set(NULL, status, "Constructor failed", 0);
85+
zend_throw_exception(IntlException_ce_ptr, "Constructor failed", 0);
86+
RETURN_THROWS();
87+
}
88+
}
89+
/* }}} */
90+
91+
/* {{{ listformatter_format */
92+
PHP_METHOD(IntlListFormatter, format)
93+
{
94+
ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS);
95+
zval *strings;
96+
97+
ZEND_PARSE_PARAMETERS_START(1, 1)
98+
Z_PARAM_ARRAY(strings)
99+
ZEND_PARSE_PARAMETERS_END();
100+
101+
if (!LISTFORMATTER_OBJECT(obj)) {
102+
intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "ListFormatter not properly constructed", 0);
103+
RETURN_FALSE;
104+
}
105+
106+
HashTable *ht = Z_ARRVAL_P(strings);
107+
uint32_t count = zend_array_count(ht);
108+
if (count == 0) {
109+
RETURN_EMPTY_STRING();
110+
}
111+
112+
const UChar **items = (const UChar **)safe_emalloc(count, sizeof(const UChar *), 0);
113+
int32_t *itemLengths = (int32_t *)safe_emalloc(count, sizeof(int32_t), 0);
114+
uint32_t i = 0;
115+
zval *val;
116+
117+
ZEND_HASH_FOREACH_VAL(ht, val) {
118+
zend_string *str_val;
119+
120+
str_val = zval_get_string(val);
121+
122+
// Convert PHP string to UTF-16
123+
UChar *ustr = NULL;
124+
int32_t ustr_len = 0;
125+
UErrorCode status = U_ZERO_ERROR;
126+
127+
intl_convert_utf8_to_utf16(&ustr, &ustr_len, ZSTR_VAL(str_val), ZSTR_LEN(str_val), &status);
128+
if (U_FAILURE(status)) {
129+
efree(items);
130+
efree(itemLengths);
131+
zend_string_release(str_val);
132+
intl_error_set(NULL, status, "Failed to convert string to UTF-16", 0);
133+
RETURN_FALSE;
134+
}
135+
136+
items[i] = ustr;
137+
itemLengths[i] = ustr_len;
138+
zend_string_release(str_val);
139+
i++;
140+
} ZEND_HASH_FOREACH_END();
141+
142+
UErrorCode status = U_ZERO_ERROR;
143+
int32_t resultLength;
144+
UChar *result = NULL;
145+
146+
resultLength = ulistfmt_format(LISTFORMATTER_OBJECT(obj), items, itemLengths, count, NULL, 0, &status);
147+
148+
if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
149+
intl_error_set(NULL, status, "Failed to format list", 0);
150+
RETURN_FALSE;
151+
}
152+
153+
// Allocate buffer and try again
154+
status = U_ZERO_ERROR;
155+
result = (UChar *)emalloc((resultLength + 1) * sizeof(UChar));
156+
ulistfmt_format(LISTFORMATTER_OBJECT(obj), items, itemLengths, count, result, resultLength, &status);
157+
158+
// Clean up input strings
159+
for (i = 0; i < count; i++) {
160+
efree((void *)items[i]);
161+
}
162+
efree(items);
163+
efree(itemLengths);
164+
165+
if (U_FAILURE(status)) {
166+
if (result) {
167+
efree(result);
168+
}
169+
intl_error_set(NULL, status, "Failed to format list", 0);
170+
RETURN_FALSE;
171+
}
172+
173+
// Convert result back to UTF-8
174+
zend_string *ret = intl_convert_utf16_to_utf8(result, resultLength, &status);
175+
efree(result);
176+
177+
if (!ret) {
178+
intl_error_set(NULL, status, "Failed to convert result to UTF-8", 0);
179+
RETURN_FALSE;
180+
}
181+
182+
RETURN_STR(ret);
183+
}
184+
/* }}} */
185+
186+
/* {{{ listformatter_getErrorCode */
187+
PHP_METHOD(IntlListFormatter, getErrorCode)
188+
{
189+
ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS);
190+
191+
UErrorCode status = intl_error_get_code(LISTFORMATTER_ERROR_P(obj));
192+
193+
RETURN_LONG(status);
194+
}
195+
/* }}} */
196+
197+
/* {{{ listformatter_getErrorMessage */
198+
PHP_METHOD(IntlListFormatter, getErrorMessage)
199+
{
200+
ListFormatter_object *obj = Z_INTL_LISTFORMATTER_P(ZEND_THIS);
201+
202+
zend_string *message = intl_error_get_message(LISTFORMATTER_ERROR_P(obj));
203+
RETURN_STR(message);
204+
}
205+
/* }}} */
206+
207+
/* {{{ listformatter_register_class */
208+
void listformatter_register_class(void)
209+
{
210+
zend_class_entry *class_entry = register_class_IntlListFormatter();
211+
class_entry->create_object = listformatter_create_object;
212+
213+
memcpy(&listformatter_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
214+
listformatter_handlers.offset = XtOffsetOf(ListFormatter_object, zo);
215+
listformatter_handlers.free_obj = listformatter_free_obj;
216+
}
217+
/* }}} */

0 commit comments

Comments
 (0)