bahttext 2.3.1 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintignore ADDED
File without changes
package/.eslintrc ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "overrides": [
3
+ {
4
+ "files": [
5
+ "**/*.test.js"
6
+ ],
7
+ "env": {
8
+ "jest": true
9
+ }
10
+ }
11
+ ]
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bahttext",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
4
4
  "description": "Change number to Thai pronunciation string",
5
5
  "main": "src/index.js",
6
6
  "typings": "src/index.d.ts",
@@ -37,14 +37,13 @@
37
37
  "devDependencies": {
38
38
  "@stryker-mutator/core": "^8.7.1",
39
39
  "@stryker-mutator/jest-runner": "^8.7.1",
40
- "@to-da-moon/thai-baht-lib": "^0.0.12",
41
40
  "baht": "^0.7.1",
41
+ "bahttext230": "npm:bahttext@2.3.0",
42
+ "bahttext231": "npm:bahttext@2.3.1",
42
43
  "jest": "^29.7.0",
43
44
  "jest-expect-message": "^1.1.3",
44
45
  "semantic-release": "^24.2.2",
45
46
  "standard": "^17.1.2",
46
- "thai-baht-text": "^2.0.5",
47
- "thai-baht-text-ts": "^1.1.0",
48
47
  "typescript": "*"
49
48
  },
50
49
  "jest": {
package/src/index.d.ts CHANGED
@@ -3,33 +3,43 @@ declare namespace _default {
3
3
  }
4
4
  export default _default;
5
5
  /**
6
- * Change number to Thai pronunciation string
6
+ * Convert number Thai Baht text (supports number or numeric string)
7
+ * Mirrors behaviour of previous implementation but with faster core.
7
8
  *
8
9
  * @public
9
- * @param {number} num
10
+ * @param {number|string} input
10
11
  * @returns {string}
11
12
  */
12
- export function bahttext(num: number): string;
13
+ export function bahttext(input: number | string): string;
13
14
  /**
14
15
  * @private
15
- * @param {number[]} nums
16
- * @returns {string}
16
+ * Handle numeric input and extract baht/satang values
17
+ * @param {number} input - numeric input (positive or negative)
18
+ * @returns {{baht: number, bahtStr: string, satang: number, isNegative: boolean} | false}
17
19
  */
18
- export function bahtxtNum2Word(nums: number[]): string;
20
+ export function handleNumericInput(input: number): {
21
+ baht: number;
22
+ bahtStr: string;
23
+ satang: number;
24
+ isNegative: boolean;
25
+ } | false;
19
26
  /**
20
27
  * @private
21
- * @todo improve performance
22
- * @param {string} str
23
- * @returns {string}
28
+ * Handle string input and extract baht/satang values
29
+ * @param {string} input - string input (numeric string, positive or negative)
30
+ * @returns {{baht: number, bahtStr: string, satang: number, isNegative: boolean} | false}
24
31
  */
25
- export function bahtxtGrammarFix(str: string): string;
32
+ export function handleStringInput(input: string): {
33
+ baht: number;
34
+ bahtStr: string;
35
+ satang: number;
36
+ isNegative: boolean;
37
+ } | false;
26
38
  /**
27
- * bahtxtCombine baht and satang
28
- * and also adding unit
29
- *
30
39
  * @private
31
- * @param {string} baht
32
- * @param {string} satang
33
- * @returns {string}
40
+ * Format satang portion of the output
41
+ * @param {number} baht - baht amount
42
+ * @param {number} satang - satang amount
43
+ * @returns {string} formatted satang string
34
44
  */
35
- export function bahtxtCombine(baht: string, satang: string): string;
45
+ export function formatSatang(baht: number, satang: number): string;
package/src/index.js CHANGED
@@ -1,150 +1,258 @@
1
- const bahtxtConst = {
2
- defaultResult: 'ศูนย์บาทถ้วน',
3
- singleUnitStrs: ['', 'หนึ่ง', 'สอง', 'สาม', 'สี่', 'ห้า', 'หก', 'เจ็ด', 'แปด', 'เก้า'],
4
- placeNameStrs: ['', 'สิบ', 'ร้อย', 'พัน', 'หมื่น', 'แสน', 'ล้าน']
5
- }
1
+ const EMPTY = ''
2
+ const ONE = 'หนึ่ง'
3
+ const TWO = 'สอง'
4
+ const THREE_TO_NINE = ['สาม', 'สี่', 'ห้า', 'หก', 'เจ็ด', 'แปด', 'เก้า']
5
+ const ED = 'เอ็ด'
6
+ const YEE = 'ยี่'
7
+ const LAN = 'ล้าน'
8
+
9
+ // Index 0-5 correspond to units,สิบ,ร้อย,พัน,หมื่น,แสน
10
+ const DIGIT = [EMPTY, 'สิบ', 'ร้อย', 'พัน', 'หมื่น', 'แสน']
6
11
 
7
- const GrammarFixs = [
8
- { pat: /หนึ่งสิบ/g, replace: 'สิบ' },
9
- { pat: /สองสิบ/g, replace: 'ยี่สิบ' },
10
- { pat: /สิบหนึ่ง/g, replace: 'สิบเอ็ด' }
12
+ // Helpers to build word tables quickly
13
+ const ONES = [EMPTY, ED, TWO, ...THREE_TO_NINE]
14
+ const TENS = [
15
+ EMPTY,
16
+ ...[EMPTY, YEE, ...THREE_TO_NINE].map(t => t + DIGIT[1]) // "สิบ" family
11
17
  ]
18
+ // 0-99 lookup (fast satang conversion)
19
+ const SUB_HUNDRED = TENS.flatMap(t => ONES.map(o => t + o))
20
+ // Special case: 1 should read "หนึ่ง" not "เอ็ด"
21
+ SUB_HUNDRED[1] = ONE
22
+
23
+ // Single-digit lookup for generic conversion (no suffix)
24
+ const SUB_TEN = [EMPTY, ONE, TWO, ...THREE_TO_NINE]
25
+
26
+ // Pre-compiled constants for frequently used combinations
27
+ const YEE_SIB = YEE + DIGIT[1] // "ยี่สิบ"
28
+ const SIB = DIGIT[1] // "สิบ"
29
+
30
+ // Default fallback text used throughout tests
31
+ const DEFAULT_RESULT = 'ศูนย์บาทถ้วน'
12
32
 
13
33
  /**
14
- * @private
15
- * @param {number[]} nums
34
+ * Convert number → Thai Baht text (supports number or numeric string)
35
+ * Mirrors behaviour of previous implementation but with faster core.
36
+ *
37
+ * @public
38
+ * @param {number|string} input
16
39
  * @returns {string}
17
40
  */
18
- function bahtxtNum2Word (nums) {
19
- /**
20
- * Converts a numeric string (or legacy digit array) into Thai words without unit suffix.
21
- * The new implementation iterates over the input string directly, eliminating
22
- * the intermediate `Array.from(...).map(Number)` allocations previously used.
23
- *
24
- * @param {string|number[]} nums – string of digits ("123") or array \[1,2,3].
25
- * @returns {string}
26
- */
27
- let numStr = ''
28
-
29
- if (typeof nums === 'string') {
30
- numStr = nums
31
- } else if (Array.isArray(nums)) {
32
- numStr = nums.join('')
33
- } else {
34
- return ''
41
+ function bahttext (input) {
42
+ // Early invalidation – keep behaviour expected by test suite
43
+ if (input === null || input === undefined ||
44
+ typeof input === 'boolean' || Array.isArray(input) ||
45
+ (typeof input !== 'number' && typeof input !== 'string')) {
46
+ return DEFAULT_RESULT
35
47
  }
36
48
 
37
- // Trim leading zeros but leave at least one digit so "0" stays "0"
38
- numStr = numStr.replace(/^0+/, '') || '0'
49
+ const converted = convertInternal(input)
50
+ return converted === false ? DEFAULT_RESULT : converted
51
+ }
39
52
 
40
- const len = numStr.length
41
- const maxLen = 7 // handle up to 6-digit group + 1-digit look-ahead for "ล้าน" logic
53
+ /**
54
+ * @private
55
+ * @param {string} numStr – e.g. "123" (no sign, may include leading zeros)
56
+ * @returns {string}
57
+ */
58
+ function numberToWords (numStr) {
59
+ // Short-circuit trivial "0"
60
+ if (numStr === '0') return ''
42
61
 
43
- if (len > maxLen) {
44
- const overflowIndex = len - maxLen + 1
45
- const overflowStr = numStr.slice(0, overflowIndex)
46
- const remainingStr = numStr.slice(overflowIndex)
47
- return bahtxtNum2Word(overflowStr) + 'ล้าน' + bahtxtNum2Word(remainingStr)
48
- }
62
+ let output = EMPTY
63
+ const len = numStr.length
49
64
 
50
- let result = ''
51
65
  for (let i = 0; i < len; i++) {
52
- const digit = numStr.charCodeAt(i) - 48 // faster than parseInt(numStr[i], 10)
53
- if (digit > 0) {
54
- result +=
55
- bahtxtConst.singleUnitStrs[digit] +
56
- bahtxtConst.placeNameStrs[len - i - 1]
66
+ const d = numStr[i]
67
+ const di = len - i - 1 // distance from rightmost digit
68
+ const diMod = di % 6 // position inside a 6-digit group
69
+
70
+ if (d === '0') {
71
+ // skip zero – no wording
72
+ } else if (diMod === 1) {
73
+ // tens column inside group
74
+ if (d === '1') {
75
+ // "สิบ"
76
+ output += SIB
77
+ } else if (d === '2') {
78
+ // "ยี่สิบ"
79
+ output += YEE_SIB
80
+ } else {
81
+ const digitCode = d.charCodeAt(0) - 48
82
+ output += SUB_TEN[digitCode] + DIGIT[diMod]
83
+ }
84
+ } else if (diMod === 0 && d === '1' && i !== 0) {
85
+ // trailing 1 (in ones place, not first digit in overall number) → "เอ็ด"
86
+ // BUT: if it's a stand-alone digit in hundreds place (like 201), use "หนึ่ง"
87
+ const isStandAloneInHundreds = (di === 0 && len > 1 && numStr[i - 1] === '0')
88
+ output += isStandAloneInHundreds ? ONE : ED
89
+ } else {
90
+ const digitCode = d.charCodeAt(0) - 48 // faster than Number(d)
91
+ output += SUB_TEN[digitCode] + DIGIT[diMod]
92
+ }
93
+
94
+ // add "ล้าน" after finishing each 6-digit block (except last group)
95
+ if (diMod === 0 && di) {
96
+ output += LAN
57
97
  }
58
98
  }
59
99
 
60
- return result
100
+ return output
61
101
  }
62
102
 
63
103
  /**
64
104
  * @private
65
- * @todo improve performance
66
- * @param {string} str
67
- * @returns {string}
105
+ * Handle numeric input and extract baht/satang values
106
+ * @param {number} input - numeric input (positive or negative)
107
+ * @returns {{baht: number, bahtStr: string, satang: number, isNegative: boolean} | false}
68
108
  */
69
- function bahtxtGrammarFix (str) {
70
- for (const GrammarFix of GrammarFixs) {
71
- str = str.replace(GrammarFix.pat, GrammarFix.replace)
109
+ function handleNumericInput (input) {
110
+ if (!Number.isFinite(input)) return false
111
+ if (input < Number.MIN_SAFE_INTEGER || input > Number.MAX_SAFE_INTEGER) {
112
+ return false
113
+ }
114
+
115
+ const isNegative = input < 0
116
+ if (isNegative) {
117
+ input = -input
72
118
  }
73
- return str
119
+
120
+ const baht = Math.floor(input)
121
+ const satang = Number.isInteger(input)
122
+ ? 0
123
+ : Math.floor(((input + Number.EPSILON * (baht || 1)) * 100) % 100)
124
+ const bahtStr = baht.toString() // slightly faster than String(baht)
125
+
126
+ return { baht, bahtStr, satang, isNegative }
74
127
  }
75
128
 
76
129
  /**
77
- * bahtxtCombine baht and satang
78
- * and also adding unit
79
- *
80
130
  * @private
81
- * @param {string} baht
82
- * @param {string} satang
83
- * @returns {string}
131
+ * Handle string input and extract baht/satang values
132
+ * @param {string} input - string input (numeric string, positive or negative)
133
+ * @returns {{baht: number, bahtStr: string, satang: number, isNegative: boolean} | false}
84
134
  */
85
- function bahtxtCombine (baht, satang) {
86
- if (!baht && !satang) {
87
- return bahtxtConst.defaultResult
88
- } else if (baht && !satang) {
89
- return baht + 'บาท' + 'ถ้วน'
90
- } else if (!baht && satang) {
91
- return satang + 'สตางค์'
135
+ function handleStringInput (input) {
136
+ let formatted = input.trim()
137
+
138
+ if (formatted.startsWith('-')) {
139
+ formatted = formatted.replace(/^-0+/, '-') // keep sign, drop leading zeros
140
+ if (formatted === '-') formatted = '0' // catch "-" or "-0"
92
141
  } else {
93
- return baht + 'บาท' + satang + 'สตางค์'
142
+ formatted = formatted.replace(/^0+/, '') || '0' // combine with empty check
94
143
  }
144
+
145
+ // Not a valid number?
146
+ if (formatted === '') formatted = '0'
147
+ let inputNum = Number(formatted)
148
+ if (Number.isNaN(inputNum)) return false
149
+
150
+ const isNegative = inputNum < 0
151
+ if (isNegative) {
152
+ inputNum = -inputNum
153
+ // remove sign for string slicing later
154
+ formatted = formatted.slice(1)
155
+ }
156
+
157
+ // Safe-range check after absolute value
158
+ if (inputNum < Number.MIN_SAFE_INTEGER || inputNum > Number.MAX_SAFE_INTEGER) {
159
+ return false
160
+ }
161
+
162
+ let baht, bahtStr, satang, satangStr
163
+
164
+ // Handle decimal part by string slicing to avoid FP errors
165
+ const periodIdx = formatted.lastIndexOf('.')
166
+ if (periodIdx !== -1) {
167
+ bahtStr = formatted.slice(0, periodIdx) || '0'
168
+ baht = Number(bahtStr)
169
+
170
+ satangStr = formatted.slice(periodIdx + 1)
171
+ satang = satangStr
172
+ ? Number(satangStr.slice(0, 2)) * (satangStr.length >= 2 ? 1 : [100, 10][satangStr.length])
173
+ : 0
174
+ } else {
175
+ baht = inputNum
176
+ bahtStr = formatted
177
+ satang = 0
178
+ }
179
+
180
+ return { baht, bahtStr, satang, isNegative }
95
181
  }
96
182
 
97
183
  /**
98
- * Change number to Thai pronunciation string
99
- *
100
- * @public
101
- * @param {number} num
102
- * @returns {string}
184
+ * @private
185
+ * Format satang portion of the output
186
+ * @param {number} baht - baht amount
187
+ * @param {number} satang - satang amount
188
+ * @returns {string} formatted satang string
103
189
  */
104
- function bahttext (num) {
105
- if (num === null || num === undefined || // explicit null/undefined check, allow 0
106
- typeof num === 'boolean' || // no boolean
107
- isNaN(Number(num)) || // must be numeric coercible
108
- num < Number.MIN_SAFE_INTEGER || // outside JS safe integer range
109
- num > Number.MAX_SAFE_INTEGER
110
- ) {
111
- return bahtxtConst.defaultResult
190
+ function formatSatang (baht, satang) {
191
+ if (satang) {
192
+ let output = ''
193
+ if (baht) output += 'บาท'
194
+ output += SUB_HUNDRED[satang] + 'สตางค์'
195
+ return output
196
+ } else {
197
+ return 'บาทถ้วน'
112
198
  }
199
+ }
113
200
 
114
- // normalise sign & prepare parts
115
- const positiveNum = Math.abs(Number(num))
201
+ /**
202
+ * @private
203
+ * Returns false when input cannot be parsed as numeric.
204
+ */
205
+ function convertInternal (input) {
206
+ let baht, bahtStr, satang
207
+ let isNegative = false
116
208
 
117
- const bahtPart = Math.floor(positiveNum)
209
+ if (typeof input === 'number') {
210
+ // Numeric input handling
211
+ const result = handleNumericInput(input)
212
+ if (result === false) return false
213
+
214
+ baht = result.baht
215
+ bahtStr = result.bahtStr
216
+ satang = result.satang
217
+ isNegative = result.isNegative
218
+ } else if (typeof input === 'string') {
219
+ // String input handling
220
+ const result = handleStringInput(input)
221
+ if (result === false) return false
222
+
223
+ baht = result.baht
224
+ bahtStr = result.bahtStr
225
+ satang = result.satang
226
+ isNegative = result.isNegative
227
+ } else {
228
+ // unreachable due to early guard, but for safety
229
+ return false
230
+ }
118
231
 
119
- const bahtStr = String(bahtPart)
120
- // Keep original rounding behaviour ("toFixed(0)") to avoid output drift
121
- const satangStr = (positiveNum % 1 * 100).toFixed(0)
232
+ // No value at all → zero
233
+ if (!baht && !satang) {
234
+ return DEFAULT_RESULT
235
+ }
122
236
 
123
- // Convert directly without creating intermediate digit arrays
124
- let baht = bahtxtNum2Word(bahtStr)
125
- let satang = bahtxtNum2Word(satangStr)
237
+ // Build output
238
+ let output = isNegative ? 'ลบ' : EMPTY
126
239
 
127
- // grammar
128
- baht = bahtxtGrammarFix(baht)
129
- satang = bahtxtGrammarFix(satang)
240
+ // Baht part – using fast converter
241
+ const bahtWords = numberToWords(bahtStr)
242
+ output += bahtWords
130
243
 
131
- // combine
132
- const result = bahtxtCombine(baht, satang)
244
+ // Satang
245
+ output += formatSatang(baht, satang)
133
246
 
134
- return num >= 0 ? result : 'ลบ' + result
247
+ return output
135
248
  }
136
249
 
137
- if (typeof module !== 'undefined' &&
138
- module.exports != null) {
139
- module.exports = {
140
- bahttext,
141
-
142
- // export for testing only
143
- bahtxtNum2Word,
144
- bahtxtGrammarFix,
145
- bahtxtCombine
146
- }
147
- exports.default = {
148
- bahttext
149
- }
250
+ module.exports = {
251
+ bahttext,
252
+ // Export private functions for testing
253
+ handleNumericInput,
254
+ handleStringInput,
255
+ formatSatang
150
256
  }
257
+
258
+ exports.default = { bahttext }