From 8519a01c5a2a420aba4a9b390eae21eb949d08b3 Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Fri, 25 Jul 2025 01:43:26 +0300 Subject: [PATCH] Intl: Add IntlNumberRangeFormatter --- ext/intl/config.m4 | 2 + ext/intl/config.w32 | 3 + ext/intl/php_intl.c | 4 + .../rangeformatter/rangeformatter.stub.php | 40 ++++ .../rangeformatter/rangeformatter_arginfo.h | 86 +++++++++ .../rangeformatter/rangeformatter_class.cpp | 180 ++++++++++++++++++ .../rangeformatter/rangeformatter_class.h | 54 ++++++ ext/intl/tests/rangeformatter/basic.phpt | 20 ++ 8 files changed, 389 insertions(+) create mode 100644 ext/intl/rangeformatter/rangeformatter.stub.php create mode 100644 ext/intl/rangeformatter/rangeformatter_arginfo.h create mode 100644 ext/intl/rangeformatter/rangeformatter_class.cpp create mode 100644 ext/intl/rangeformatter/rangeformatter_class.h create mode 100644 ext/intl/tests/rangeformatter/basic.phpt diff --git a/ext/intl/config.m4 b/ext/intl/config.m4 index 20adc3a4ce3a7..2aab518fabaa1 100644 --- a/ext/intl/config.m4 +++ b/ext/intl/config.m4 @@ -73,6 +73,7 @@ if test "$PHP_INTL" != "no"; then dateformat/datepatterngenerator_class.cpp \ dateformat/datepatterngenerator_methods.cpp \ msgformat/msgformat_helpers.cpp \ + rangeformatter/rangeformatter_class.cpp \ timezone/timezone_class.cpp \ timezone/timezone_methods.cpp \ calendar/calendar_class.cpp \ @@ -123,6 +124,7 @@ if test "$PHP_INTL" != "no"; then $ext_builddir/listformatter $ext_builddir/msgformat $ext_builddir/normalizer + $ext_builddir/rangeformatter $ext_builddir/resourcebundle $ext_builddir/spoofchecker $ext_builddir/timezone diff --git a/ext/intl/config.w32 b/ext/intl/config.w32 index b8161865d2540..c14a778d2042d 100644 --- a/ext/intl/config.w32 +++ b/ext/intl/config.w32 @@ -63,6 +63,9 @@ if (PHP_INTL != "no") { normalizer_class.c \ normalizer_normalize.c \ ", "intl"); + ADD_SOURCES(configure_module_dirname + "/rangeformatter", "\ + rangeformatter_class.cpp \ + ", "intl"); ADD_SOURCES(configure_module_dirname + "/dateformat", "\ dateformat.c \ dateformat_class.c \ diff --git a/ext/intl/php_intl.c b/ext/intl/php_intl.c index 68fd2dedfba85..7a92390990d7b 100644 --- a/ext/intl/php_intl.c +++ b/ext/intl/php_intl.c @@ -42,6 +42,7 @@ #include "locale/locale_class.h" #include "listformatter/listformatter_class.h" +#include "rangeformatter/rangeformatter_class.h" #include "dateformat/dateformat.h" #include "dateformat/dateformat_class.h" @@ -161,6 +162,9 @@ PHP_MINIT_FUNCTION( intl ) /* Register 'ListFormatter' PHP class */ listformatter_register_class( ); + /* Register 'NumberRangeFormatter' PHP class */ + rangeformatter_register_class( ); + /* Register 'Normalizer' PHP class */ normalizer_register_Normalizer_class( ); diff --git a/ext/intl/rangeformatter/rangeformatter.stub.php b/ext/intl/rangeformatter/rangeformatter.stub.php new file mode 100644 index 0000000000000..2cd947ecab07d --- /dev/null +++ b/ext/intl/rangeformatter/rangeformatter.stub.php @@ -0,0 +1,40 @@ + | + +----------------------------------------------------------------------+ +*/ + +#include +#include +#include +#include +#include "../intl_convertcpp.h" + +extern "C" { + #include "php.h" + #include "../php_intl.h" + #include "../intl_data.h" + #include "rangeformatter_arginfo.h" + #include "rangeformatter_class.h" +#include "intl_convert.h" +} + +using icu::number::NumberRangeFormatter; +using icu::number::NumberFormatter; +using icu::number::UnlocalizedNumberFormatter; +using icu::number::LocalizedNumberRangeFormatter; +using icu::UnicodeString; +using icu::MeasureUnit; + +static zend_object_handlers rangeformatter_handlers; +zend_class_entry *class_entry_IntlNumberRangeFormatter; + +zend_object *IntlNumberRangeFormatter_object_create(zend_class_entry *ce) +{ + IntlNumberRangeFormatter_object* intern; + + intern = (IntlNumberRangeFormatter_object*)zend_object_alloc(sizeof(IntlNumberRangeFormatter_object), ce); + zend_object_std_init(&intern->zo, ce); + object_properties_init(&intern->zo, ce); + + return &intern->zo; +} + +U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, __construct) +{ + +} + +U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) +{ + char* skeleton; + char* locale; + size_t locale_len; + size_t skeleton_len; + zend_long collapse; + zend_long identityFallback; + + ZEND_PARSE_PARAMETERS_START(4,4) + Z_PARAM_STRING(skeleton, skeleton_len) + Z_PARAM_STRING(locale, locale_len) + Z_PARAM_LONG(collapse) + Z_PARAM_LONG(identityFallback) + ZEND_PARSE_PARAMETERS_END(); + + if (locale_len == 0) { + locale = (char *)intl_locale_get_default(); + } + + if (skeleton_len == 0) { + zend_argument_value_error(1, "Skeleton string cannot be empty"); + RETURN_THROWS(); + } + + if (locale_len > INTL_MAX_LOCALE_LEN) { + zend_argument_value_error(2, "Locale string too long, should be no longer than %d characters", INTL_MAX_LOCALE_LEN); + RETURN_THROWS(); + } + + if (strlen(uloc_getISO3Language(locale)) == 0) { + zend_argument_value_error(2, "\"%s\" is invalid", locale); + RETURN_THROWS(); + } + + if (collapse != UNUM_RANGE_COLLAPSE_AUTO && collapse != UNUM_RANGE_COLLAPSE_NONE && collapse != UNUM_RANGE_COLLAPSE_UNIT && collapse != UNUM_RANGE_COLLAPSE_ALL) { + zend_argument_value_error(3, "Invalid collapse value"); + RETURN_THROWS(); + } + + if (identityFallback != UNUM_IDENTITY_FALLBACK_SINGLE_VALUE && identityFallback != UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE && identityFallback != UNUM_IDENTITY_FALLBACK_APPROXIMATELY && identityFallback != UNUM_IDENTITY_FALLBACK_RANGE) { + zend_argument_value_error(4, "Invalid identity fallback value"); + RETURN_THROWS(); + } + + UErrorCode error = U_ZERO_ERROR; + + UnicodeString skeleton_ustr(skeleton, skeleton_len); + + UnlocalizedNumberFormatter nf = NumberFormatter::forSkeleton(skeleton_ustr, error); + + LocalizedNumberRangeFormatter* nrf = new LocalizedNumberRangeFormatter( + NumberRangeFormatter::with() + .locale(locale) + .numberFormatterBoth(nf) + .collapse(static_cast(collapse)) + .identityFallback(static_cast(identityFallback)) + ); + + zend_object* obj = IntlNumberRangeFormatter_object_create(class_entry_IntlNumberRangeFormatter); + obj->handlers = &rangeformatter_handlers; + + RANGEFORMATTER_OBJECT(php_intl_numberrangeformatter_fetch_object(obj)) = nrf; + + RETURN_OBJ(obj); + +} + +U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, format) +{ + double start; + double end; + + IntlNumberRangeFormatter_object* obj = Z_INTL_RANGEFORMATTER_P(ZEND_THIS); + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_DOUBLE(start) + Z_PARAM_DOUBLE(end) + ZEND_PARSE_PARAMETERS_END(); + + UErrorCode error = U_ZERO_ERROR; + + UnicodeString result = RANGEFORMATTER_OBJECT(obj)->formatFormattableRange(start, end, error).toString(error); + + if (U_FAILURE(error)) { + intl_error_set(NULL, error, "Failed to format number range", 0); + + return; + } + + zend_string *ret = intl_charFromString(result, &error); + + if (!ret) { + intl_error_set(NULL, error, "Failed to convert result to UTF-8", 0); + // RETVAL_FALSE; + return; + } + + RETVAL_NEW_STR(ret); +} + +void IntlNumberRangeFormatter_object_free(zend_object *object) +{ + IntlNumberRangeFormatter_object* nfo = php_intl_numberrangeformatter_fetch_object(object); + + if (nfo->nrf_data.unumrf) { + delete nfo->nrf_data.unumrf; + nfo->nrf_data.unumrf = nullptr; + } + + intl_error_reset(&nfo->nrf_data.error); + + zend_object_std_dtor(&nfo->zo); +} + +void rangeformatter_register_class(void) +{ + class_entry_IntlNumberRangeFormatter = register_class_IntlNumberRangeFormatter(); + class_entry_IntlNumberRangeFormatter->create_object = IntlNumberRangeFormatter_object_create; + + memcpy(&rangeformatter_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + rangeformatter_handlers.offset = XtOffsetOf(IntlNumberRangeFormatter_object, zo); + rangeformatter_handlers.free_obj = IntlNumberRangeFormatter_object_free; + rangeformatter_handlers.clone_obj = NULL; +} diff --git a/ext/intl/rangeformatter/rangeformatter_class.h b/ext/intl/rangeformatter/rangeformatter_class.h new file mode 100644 index 0000000000000..cf8a08aefb4eb --- /dev/null +++ b/ext/intl/rangeformatter/rangeformatter_class.h @@ -0,0 +1,54 @@ +/* + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Bogdan Ungureanu | + +----------------------------------------------------------------------+ +*/ + +#ifndef RANGEFORMATTER_CLASS_H +#define RANGEFORMATTER_CLASS_H + +#include + +#ifdef __cplusplus +using icu::number::LocalizedNumberRangeFormatter; +#else +typedef void LocalizedNumberRangeFormatter; +#endif + +typedef struct { + // error handling + intl_error error; + + // formatter handling + LocalizedNumberRangeFormatter* unumrf; +} rangeformatter_data; + +typedef struct { + rangeformatter_data nrf_data; + zend_object zo; +} IntlNumberRangeFormatter_object; + +static inline IntlNumberRangeFormatter_object *php_intl_numberrangeformatter_fetch_object(zend_object *obj) { + return (IntlNumberRangeFormatter_object *)((char*)(obj) - XtOffsetOf(IntlNumberRangeFormatter_object, zo)); +} + +#define Z_INTL_RANGEFORMATTER_P(zv) php_intl_numberrangeformatter_fetch_object(Z_OBJ_P(zv)) + +#define RANGEFORMATTER_ERROR(nfo) (nfo)->nrf_data.error +#define RANGEFORMATTER_ERROR_P(nfo) &(RANGEFORMATTER_ERROR(nfo)) + +#define RANGEFORMATTER_OBJECT(nfo) (nfo)->nrf_data.unumrf + + +void rangeformatter_register_class(void); + + +#endif \ No newline at end of file diff --git a/ext/intl/tests/rangeformatter/basic.phpt b/ext/intl/tests/rangeformatter/basic.phpt new file mode 100644 index 0000000000000..42f29ba12c708 --- /dev/null +++ b/ext/intl/tests/rangeformatter/basic.phpt @@ -0,0 +1,20 @@ +--TEST-- +Basic test for IntlNumberRangeFormatter +--EXTENSIONS-- +intl +--FILE-- +format(1.1, 2.2)); +var_dump($nrf->format(100, 200)); +var_dump($nrf->format(-5, 5)); +?> +--EXPECT-- +string(11) "1,1 - 2,2 m" +string(11) "100 - 200 m" +string(8) "-5 - 5 m"