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    | license@php.net so we can mail you a copy immediately.               |
10    +----------------------------------------------------------------------+
11    | Authors: Stanislav Malyshev <stas@zend.com>                          |
12    +----------------------------------------------------------------------+
13  */
14 
15 #ifdef HAVE_CONFIG_H
16 #include "config.h"
17 #endif
18 
19 #include "php_intl.h"
20 
21 #include <unicode/ustring.h>
22 #include <locale.h>
23 
24 #include "formatter_class.h"
25 #include "formatter_format.h"
26 #include "intl_convert.h"
27 
28 #define ICU_LOCALE_BUG 1
29 
30 /* {{{ Parse a number. */
PHP_FUNCTION(numfmt_parse)31 PHP_FUNCTION( numfmt_parse )
32 {
33 	zend_long type = FORMAT_TYPE_DOUBLE;
34 	UChar* sstr = NULL;
35 	int32_t sstr_len = 0;
36 	char* str = NULL;
37 	size_t str_len;
38 	int32_t val32, position = 0;
39 	int64_t val64;
40 	double val_double;
41 	int32_t* position_p = NULL;
42 	zval *zposition = NULL;
43 	char *oldlocale;
44 	FORMATTER_METHOD_INIT_VARS;
45 
46 	/* Parse parameters. */
47 	if (zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Os|lz!",
48 		&object, NumberFormatter_ce_ptr,  &str, &str_len, &type, &zposition ) == FAILURE )
49 	{
50 		RETURN_THROWS();
51 	}
52 
53 	if (zposition) {
54 		position = (int32_t) zval_get_long(zposition);
55 		position_p = &position;
56 	}
57 
58 	/* Fetch the object. */
59 	FORMATTER_METHOD_FETCH_OBJECT;
60 
61 	/* Convert given string to UTF-16. */
62 	intl_convert_utf8_to_utf16(&sstr, &sstr_len, str, str_len, &INTL_DATA_ERROR_CODE(nfo));
63 	INTL_METHOD_CHECK_STATUS( nfo, "String conversion to UTF-16 failed" );
64 
65 #if ICU_LOCALE_BUG && defined(LC_NUMERIC)
66 	/* need to copy here since setlocale may change it later */
67 	oldlocale = estrdup(setlocale(LC_NUMERIC, NULL));
68 	setlocale(LC_NUMERIC, "C");
69 #endif
70 
71 	switch(type) {
72 		case FORMAT_TYPE_INT32:
73 			val32 = unum_parse(FORMATTER_OBJECT(nfo), sstr, sstr_len, position_p, &INTL_DATA_ERROR_CODE(nfo));
74 			RETVAL_LONG(val32);
75 			break;
76 		case FORMAT_TYPE_INT64:
77 			val64 = unum_parseInt64(FORMATTER_OBJECT(nfo), sstr, sstr_len, position_p, &INTL_DATA_ERROR_CODE(nfo));
78 			if(val64 > ZEND_LONG_MAX || val64 < ZEND_LONG_MIN) {
79 				RETVAL_DOUBLE(val64);
80 			} else {
81 				RETVAL_LONG((zend_long)val64);
82 			}
83 			break;
84 		case FORMAT_TYPE_DOUBLE:
85 			val_double = unum_parseDouble(FORMATTER_OBJECT(nfo), sstr, sstr_len, position_p, &INTL_DATA_ERROR_CODE(nfo));
86 			RETVAL_DOUBLE(val_double);
87 			break;
88 		case FORMAT_TYPE_CURRENCY:
89 			if (getThis()) {
90 				const char *space;
91 				const char *class_name = get_active_class_name(&space);
92 				zend_argument_value_error(2, "cannot be NumberFormatter::TYPE_CURRENCY constant, "
93 					"use %s%sparseCurrency() method instead", class_name, space);
94 			} else {
95 				zend_argument_value_error(3, "cannot be NumberFormatter::TYPE_CURRENCY constant, use numfmt_parse_currency() function instead");
96 			}
97 			goto cleanup;
98 		default:
99 			zend_argument_value_error(hasThis() ? 2 : 3, "must be a NumberFormatter::TYPE_* constant");
100 			goto cleanup;
101 	}
102 
103 	if (zposition) {
104 		ZEND_TRY_ASSIGN_REF_LONG(zposition, position);
105 	}
106 
107 cleanup:
108 
109 #if ICU_LOCALE_BUG && defined(LC_NUMERIC)
110 	setlocale(LC_NUMERIC, oldlocale);
111 	efree(oldlocale);
112 #endif
113 
114 	if (sstr) {
115 		efree(sstr);
116 	}
117 
118 	INTL_METHOD_CHECK_STATUS( nfo, "Number parsing failed" );
119 }
120 /* }}} */
121 
122 /* {{{ Parse a number as currency. */
PHP_FUNCTION(numfmt_parse_currency)123 PHP_FUNCTION( numfmt_parse_currency )
124 {
125 	double number;
126 	UChar currency[5] = {0};
127 	UChar* sstr = NULL;
128 	int32_t sstr_len = 0;
129 	zend_string *u8str;
130 	char *str;
131 	size_t str_len;
132 	int32_t* position_p = NULL;
133 	int32_t position = 0;
134 	zval *zcurrency, *zposition = NULL;
135 	FORMATTER_METHOD_INIT_VARS;
136 
137 	/* Parse parameters. */
138 	if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Osz/|z!",
139 		&object, NumberFormatter_ce_ptr,  &str, &str_len, &zcurrency, &zposition ) == FAILURE )
140 	{
141 		RETURN_THROWS();
142 	}
143 
144 	/* Fetch the object. */
145 	FORMATTER_METHOD_FETCH_OBJECT;
146 
147 	/* Convert given string to UTF-16. */
148 	intl_convert_utf8_to_utf16(&sstr, &sstr_len, str, str_len, &INTL_DATA_ERROR_CODE(nfo));
149 	INTL_METHOD_CHECK_STATUS( nfo, "String conversion to UTF-16 failed" );
150 
151 	if(zposition) {
152 		position = (int32_t) zval_get_long(zposition);
153 		position_p = &position;
154 	}
155 
156 	number = unum_parseDoubleCurrency(FORMATTER_OBJECT(nfo), sstr, sstr_len, position_p, currency, &INTL_DATA_ERROR_CODE(nfo));
157 	if(zposition) {
158 		ZEND_TRY_ASSIGN_REF_LONG(zposition, position);
159 	}
160 	if (sstr) {
161 		efree(sstr);
162 	}
163 	INTL_METHOD_CHECK_STATUS( nfo, "Number parsing failed" );
164 
165 	/* Convert parsed currency to UTF-8 and pass it back to caller. */
166 	u8str = intl_convert_utf16_to_utf8(currency, u_strlen(currency), &INTL_DATA_ERROR_CODE(nfo));
167 	INTL_METHOD_CHECK_STATUS( nfo, "Currency conversion to UTF-8 failed" );
168 	zval_ptr_dtor( zcurrency );
169 	ZVAL_NEW_STR(zcurrency, u8str);
170 
171 	RETVAL_DOUBLE( number );
172 }
173 /* }}} */
174