taiwan-validator 1.0.0 → 1.1.0

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/README.en.md CHANGED
@@ -16,6 +16,7 @@ A comprehensive TypeScript validator for Taiwan identification numbers and codes
16
16
  - ✅ Citizen Digital Certificate validation (自然人憑證)
17
17
  - ✅ e-Invoice Mobile Barcode validation (電子發票手機條碼)
18
18
  - ✅ e-Invoice Donation Code validation (電子發票捐贈碼)
19
+ - ✅ License Plate Number validation (車牌號碼) - Supports cars, motorcycles, electric vehicles, and more
19
20
  - 📘 Full TypeScript support with type definitions
20
21
  - 🧪 Thoroughly tested with 100% coverage
21
22
  - 📦 Tree-shakeable ESM and CommonJS support
@@ -38,6 +39,7 @@ import {
38
39
  validateCitizenCertificate,
39
40
  validateEInvoiceMobileBarcode,
40
41
  validateEInvoiceDonationCode,
42
+ validateLicensePlate,
41
43
  } from "taiwan-validator";
42
44
 
43
45
  // National ID (身分證字號)
@@ -63,6 +65,11 @@ validateEInvoiceMobileBarcode("/ABCD123"); // { isValid: true }
63
65
 
64
66
  // e-Invoice Donation Code (電子發票捐贈碼)
65
67
  validateEInvoiceDonationCode("12345"); // { isValid: true }
68
+
69
+ // License Plate Number (車牌號碼)
70
+ validateLicensePlate("ABC-1235"); // { isValid: true, plateType: 'car' }
71
+ validateLicensePlate("EAB-1235"); // { isValid: true, plateType: 'electric-car' }
72
+ validateLicensePlate("AB1-234"); // { isValid: true, plateType: 'motorcycle' }
66
73
  ```
67
74
 
68
75
  ## API Documentation
@@ -144,6 +151,60 @@ Validates Taiwan e-Invoice Donation Code (電子發票捐贈碼).
144
151
  validateEInvoiceDonationCode("12345");
145
152
  ```
146
153
 
154
+ ### `validateLicensePlate(plate: string, options?: { type?: LicensePlateType, detectType?: boolean }): LicensePlateValidationResult`
155
+
156
+ Validates Taiwan license plate numbers, supporting multiple vehicle types.
157
+
158
+ - **Supported Formats**:
159
+ - **New car** (`car`): 3 letters - 4 digits (e.g., `ABC-1235`)
160
+ - Letters I and O are not used
161
+ - Digit 4 is not used
162
+ - **Old car** (`car-old`): 1 digit + 1 letter - 4 digits (e.g., `1A-2345`)
163
+ - **Electric car** (`electric-car`): E + 2 letters - 4 digits (e.g., `EAB-1235`)
164
+ - Letters I and O are not used
165
+ - Digit 4 is not used
166
+ - **Small motorcycle** (`motorcycle-small`):
167
+ - 3 digits - 3 letters (e.g., `123-ABC`)
168
+ - 3 letters - 3 digits (e.g., `ABC-123`)
169
+ - **Regular motorcycle** (`motorcycle`): 2 letters + 1 digit - 3 digits (e.g., `AB1-234`)
170
+
171
+ ```typescript
172
+ // Basic validation (auto-detect plate type)
173
+ validateLicensePlate("ABC-1235");
174
+ // { isValid: true, plateType: 'car' }
175
+
176
+ // Electric car
177
+ validateLicensePlate("EAB-1235");
178
+ // { isValid: true, plateType: 'electric-car' }
179
+
180
+ // Old car format
181
+ validateLicensePlate("1A-2345");
182
+ // { isValid: true, plateType: 'car-old' }
183
+
184
+ // Small motorcycle
185
+ validateLicensePlate("123-ABC");
186
+ // { isValid: true, plateType: 'motorcycle-small' }
187
+
188
+ validateLicensePlate("ABC-123");
189
+ // { isValid: true, plateType: 'motorcycle-small' }
190
+
191
+ // Regular motorcycle
192
+ validateLicensePlate("AB1-234");
193
+ // { isValid: true, plateType: 'motorcycle' }
194
+
195
+ // Validate specific plate type
196
+ validateLicensePlate("ABC-1235", { type: "car" });
197
+ // { isValid: true, plateType: 'car' }
198
+
199
+ // Disable type detection
200
+ validateLicensePlate("ABC-1235", { detectType: false });
201
+ // { isValid: true }
202
+
203
+ // Handles lowercase and whitespace
204
+ validateLicensePlate(" abc-1235 ");
205
+ // { isValid: true, plateType: 'car' }
206
+ ```
207
+
147
208
  ### Return Type
148
209
 
149
210
  All validation functions return a `ValidationResult` object:
@@ -155,6 +216,19 @@ interface ValidationResult {
155
216
  }
156
217
  ```
157
218
 
219
+ License plate validation returns a `LicensePlateValidationResult` with additional plate type information:
220
+
221
+ ```typescript
222
+ interface LicensePlateValidationResult extends ValidationResult {
223
+ plateType?:
224
+ | "car"
225
+ | "car-old"
226
+ | "electric-car"
227
+ | "motorcycle-small"
228
+ | "motorcycle";
229
+ }
230
+ ```
231
+
158
232
  ## Development
159
233
 
160
234
  ### Setup
package/README.md CHANGED
@@ -16,6 +16,7 @@
16
16
  - ✅ 自然人憑證驗證
17
17
  - ✅ 電子發票手機條碼驗證
18
18
  - ✅ 電子發票捐贈碼驗證
19
+ - ✅ 車牌號碼驗證 - 支援汽車、機車、電動車等多種格式
19
20
  - 📘 完整的 TypeScript 型別定義
20
21
  - 🧪 完整測試覆蓋率
21
22
  - 📦 支援 ESM 和 CommonJS 且可 Tree-shaking
@@ -38,6 +39,7 @@ import {
38
39
  validateCitizenCertificate,
39
40
  validateEInvoiceMobileBarcode,
40
41
  validateEInvoiceDonationCode,
42
+ validateLicensePlate,
41
43
  } from "taiwan-validator";
42
44
 
43
45
  // 身分證字號
@@ -63,6 +65,11 @@ validateEInvoiceMobileBarcode("/ABCD123"); // { isValid: true }
63
65
 
64
66
  // 電子發票捐贈碼
65
67
  validateEInvoiceDonationCode("12345"); // { isValid: true }
68
+
69
+ // 車牌號碼
70
+ validateLicensePlate("ABC-1235"); // { isValid: true, plateType: 'car' }
71
+ validateLicensePlate("EAB-1235"); // { isValid: true, plateType: 'electric-car' }
72
+ validateLicensePlate("AB1-234"); // { isValid: true, plateType: 'motorcycle' }
66
73
  ```
67
74
 
68
75
  ## API 文件
@@ -144,6 +151,60 @@ validateEInvoiceMobileBarcode("/ABCD123");
144
151
  validateEInvoiceDonationCode("12345");
145
152
  ```
146
153
 
154
+ ### `validateLicensePlate(plate: string, options?: { type?: LicensePlateType, detectType?: boolean }): LicensePlateValidationResult`
155
+
156
+ 驗證台灣車牌號碼,支援多種車輛類型。
157
+
158
+ - **支援格式**:
159
+ - **新制汽車** (`car`):3個英文字母 - 4個數字(例如:`ABC-1235`)
160
+ - 不使用字母 I、O
161
+ - 不使用數字 4
162
+ - **舊制汽車** (`car-old`):1個數字 + 1個英文字母 - 4個數字(例如:`1A-2345`)
163
+ - **電動汽車** (`electric-car`):E + 2個英文字母 - 4個數字(例如:`EAB-1235`)
164
+ - 不使用字母 I、O
165
+ - 不使用數字 4
166
+ - **小型機車** (`motorcycle-small`):
167
+ - 3個數字 - 3個英文字母(例如:`123-ABC`)
168
+ - 3個英文字母 - 3個數字(例如:`ABC-123`)
169
+ - **一般機車** (`motorcycle`):2個英文字母 + 1個數字 - 3個數字(例如:`AB1-234`)
170
+
171
+ ```typescript
172
+ // 基本驗證(自動偵測車牌類型)
173
+ validateLicensePlate("ABC-1235");
174
+ // { isValid: true, plateType: 'car' }
175
+
176
+ // 電動汽車
177
+ validateLicensePlate("EAB-1235");
178
+ // { isValid: true, plateType: 'electric-car' }
179
+
180
+ // 舊制汽車
181
+ validateLicensePlate("1A-2345");
182
+ // { isValid: true, plateType: 'car-old' }
183
+
184
+ // 小型機車
185
+ validateLicensePlate("123-ABC");
186
+ // { isValid: true, plateType: 'motorcycle-small' }
187
+
188
+ validateLicensePlate("ABC-123");
189
+ // { isValid: true, plateType: 'motorcycle-small' }
190
+
191
+ // 一般機車
192
+ validateLicensePlate("AB1-234");
193
+ // { isValid: true, plateType: 'motorcycle' }
194
+
195
+ // 指定車牌類型驗證
196
+ validateLicensePlate("ABC-1235", { type: "car" });
197
+ // { isValid: true, plateType: 'car' }
198
+
199
+ // 不偵測車牌類型
200
+ validateLicensePlate("ABC-1235", { detectType: false });
201
+ // { isValid: true }
202
+
203
+ // 處理小寫和空格
204
+ validateLicensePlate(" abc-1235 ");
205
+ // { isValid: true, plateType: 'car' }
206
+ ```
207
+
147
208
  ### 回傳型別
148
209
 
149
210
  所有驗證函式都會回傳 `ValidationResult` 物件:
@@ -155,6 +216,19 @@ interface ValidationResult {
155
216
  }
156
217
  ```
157
218
 
219
+ 車牌驗證函式會回傳 `LicensePlateValidationResult`,包含額外的車牌類型資訊:
220
+
221
+ ```typescript
222
+ interface LicensePlateValidationResult extends ValidationResult {
223
+ plateType?:
224
+ | "car"
225
+ | "car-old"
226
+ | "electric-car"
227
+ | "motorcycle-small"
228
+ | "motorcycle";
229
+ }
230
+ ```
231
+
158
232
  ## 開發
159
233
 
160
234
  ### 設定
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var V={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function R(t){if(!/^[A-Z][12]\d{8}$/.test(t))return false;let s=t[0],i=t.slice(1),r=V[s],e=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [e,a,...i.split("").map(Number)].reduce((d,u,f)=>d+u*o[f],0)%10===0}function v(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let s=t[0],i=t[1],r=t.slice(2),e=V[s],a=V[i],o=Math.floor(e/10),l=e%10,c=Math.floor(a/10),d=a%10,u=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,c,d,...r.split("").map(Number)].reduce((m,p,g)=>m+p*u[g],0)%10===0}function I(t){return /^[A-Z][12]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function x(t,n){if(!t||typeof t!="string")return {isValid:false,message:"\u8EAB\u5206\u8B49\u5B57\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();if(n==="old"){let e=R(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}if(n==="new"){let e=v(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}let i=I(s);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u8EAB\u5206\u8B49\u5B57\u865F\u683C\u5F0F"};let r=i==="old"?R(s):v(s);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u8EAB\u5206\u8B49\u5B57\u865F`}}function A(t){if(!t||typeof t!="string")return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim();if(!/^\d{8}$/.test(n))return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA8\u4F4D\u6578\u5B57"};let i=n.split("").map(Number),r=[1,2,1,2,1,2,4,1],e=0;for(let o=0;o<8;o++){let l=i[o]*r[o];l>=10&&(l=Math.floor(l/10)+l%10),e+=l;}let a=e%10===0||i[6]===7&&e%10===1;return {isValid:a,message:a?void 0:"\u7D71\u4E00\u7DE8\u865F\u6AA2\u67E5\u78BC\u932F\u8AA4"}}var y={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function b(t){if(!/^[A-D][89]\d{8}$/.test(t))return false;let s=t[0],i=t.slice(1),r=y[s],e=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [e,a,...i.split("").map(Number)].reduce((d,u,f)=>d+u*o[f],0)%10===0}function C(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let s=t[0],i=t[1],r=t.slice(2),e=y[s],a=y[i],o=Math.floor(e/10),l=e%10,c=Math.floor(a/10),d=a%10,u=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,c,d,...r.split("").map(Number)].reduce((m,p,g)=>m+p*u[g],0)%10===0}function M(t){return /^[A-D][89]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function $(t,n){if(!t||typeof t!="string")return {isValid:false,message:"\u5C45\u7559\u8B49\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();if(n==="old"){let e=b(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u5C45\u7559\u8B49\u865F"}}if(n==="new"){let e=C(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u5C45\u7559\u8B49\u865F"}}let i=M(s);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u5C45\u7559\u8B49\u865F\u683C\u5F0F"};let r=i==="old"?b(s):C(s);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u5C45\u7559\u8B49\u865F`}}function T(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().replace(/[\s\-()]/g,"");return /^09\d{8}$/.test(n)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u4EE509\u958B\u982D\u4E14\u70BA10\u4F4D\u6578\u5B57"}}function h(t){if(!t||typeof t!="string")return {isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().toUpperCase();return /^[A-Z]{2}\d{14}$/.test(n)?{isValid:true}:{isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA2\u500B\u82F1\u6587\u5B57\u6BCD\u52A0\u4E0A14\u4F4D\u6578\u5B57"}}function L(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().toUpperCase();return /^\/[A-Z0-9+.-]{7}$/.test(n)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u4EE5 / \u958B\u982D\uFF0C\u5F8C\u63A57\u500B\u6709\u6548\u5B57\u5143\uFF08A-Z\u30010-9\u3001+\u3001-\u3001.\uFF09"}}function w(t){if(!t||typeof t!="string")return {isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim();return /^\d{3,7}$/.test(n)?{isValid:true}:{isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA3\u81F37\u4F4D\u6578\u5B57"}}exports.validateBusinessNumber=A;exports.validateCitizenCertificate=h;exports.validateEInvoiceDonationCode=w;exports.validateEInvoiceMobileBarcode=L;exports.validateMobilePhone=T;exports.validateNationalId=x;exports.validateResidentCertificate=$;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';var y={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function b(t){if(!/^[A-Z][12]\d{8}$/.test(t))return false;let e=t[0],i=t.slice(1),r=y[e],n=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [n,a,...i.split("").map(Number)].reduce((c,d,f)=>c+d*o[f],0)%10===0}function R(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let e=t[0],i=t[1],r=t.slice(2),n=y[e],a=y[i],o=Math.floor(n/10),l=n%10,u=Math.floor(a/10),c=a%10,d=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,u,c,...r.split("").map(Number)].reduce((p,m,g)=>p+m*d[g],0)%10===0}function x(t){return /^[A-Z][12]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function M(t,s){if(!t||typeof t!="string")return {isValid:false,message:"\u8EAB\u5206\u8B49\u5B57\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let e=t.trim().toUpperCase();if(s==="old"){let n=b(e);return {isValid:n,message:n?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}if(s==="new"){let n=R(e);return {isValid:n,message:n?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}let i=x(e);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u8EAB\u5206\u8B49\u5B57\u865F\u683C\u5F0F"};let r=i==="old"?b(e):R(e);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u8EAB\u5206\u8B49\u5B57\u865F`}}function Z(t){if(!t||typeof t!="string")return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim();if(!/^\d{8}$/.test(s))return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA8\u4F4D\u6578\u5B57"};let i=s.split("").map(Number),r=[1,2,1,2,1,2,4,1],n=0;for(let o=0;o<8;o++){let l=i[o]*r[o];l>=10&&(l=Math.floor(l/10)+l%10),n+=l;}let a=n%10===0||i[6]===7&&n%10===1;return {isValid:a,message:a?void 0:"\u7D71\u4E00\u7DE8\u865F\u6AA2\u67E5\u78BC\u932F\u8AA4"}}var V={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function v(t){if(!/^[A-D][89]\d{8}$/.test(t))return false;let e=t[0],i=t.slice(1),r=V[e],n=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [n,a,...i.split("").map(Number)].reduce((c,d,f)=>c+d*o[f],0)%10===0}function P(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let e=t[0],i=t[1],r=t.slice(2),n=V[e],a=V[i],o=Math.floor(n/10),l=n%10,u=Math.floor(a/10),c=a%10,d=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,u,c,...r.split("").map(Number)].reduce((p,m,g)=>p+m*d[g],0)%10===0}function I(t){return /^[A-D][89]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function h(t,s){if(!t||typeof t!="string")return {isValid:false,message:"\u5C45\u7559\u8B49\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let e=t.trim().toUpperCase();if(s==="old"){let n=v(e);return {isValid:n,message:n?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u5C45\u7559\u8B49\u865F"}}if(s==="new"){let n=P(e);return {isValid:n,message:n?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u5C45\u7559\u8B49\u865F"}}let i=I(e);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u5C45\u7559\u8B49\u865F\u683C\u5F0F"};let r=i==="old"?v(e):P(e);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u5C45\u7559\u8B49\u865F`}}function w(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().replace(/[\s\-()]/g,"");return /^09\d{8}$/.test(s)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u4EE509\u958B\u982D\u4E14\u70BA10\u4F4D\u6578\u5B57"}}function E(t){if(!t||typeof t!="string")return {isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();return /^[A-Z]{2}\d{14}$/.test(s)?{isValid:true}:{isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA2\u500B\u82F1\u6587\u5B57\u6BCD\u52A0\u4E0A14\u4F4D\u6578\u5B57"}}function z(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();return /^\/[A-Z0-9+.-]{7}$/.test(s)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u4EE5 / \u958B\u982D\uFF0C\u5F8C\u63A57\u500B\u6709\u6548\u5B57\u5143\uFF08A-Z\u30010-9\u3001+\u3001-\u3001.\uFF09"}}function F(t){if(!t||typeof t!="string")return {isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim();return /^\d{3,7}$/.test(s)?{isValid:true}:{isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA3\u81F37\u4F4D\u6578\u5B57"}}function T(t){return /^[A-HJ-NP-Z]{3}-\d{4}$/.test(t)?!t.split("-")[1].includes("4"):false}function L(t){return /^\d[A-Z]-\d{4}$/.test(t)}function N(t){return /^E[A-HJ-NP-Z]{2}-\d{4}$/.test(t)?!t.split("-")[1].includes("4"):false}function C(t){let s=/^\d{3}-[A-Z]{3}$/,e=/^[A-Z]{3}-\d{3}$/;return s.test(t)||e.test(t)}function $(t){return /^[A-Z]{2}\d-\d{3}$/.test(t)}function U(t){return N(t)?"electric-car":T(t)?"car":L(t)?"car-old":C(t)?"motorcycle-small":$(t)?"motorcycle":null}function B(t,s={}){let{type:e,detectType:i=true}=s;if(!t||typeof t!="string")return {isValid:false,message:"\u8ECA\u724C\u865F\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let r=t.trim().toUpperCase();if(e){let a=false;switch(e){case "car":a=T(r);break;case "car-old":a=L(r);break;case "electric-car":a=N(r);break;case "motorcycle-small":a=C(r);break;case "motorcycle":a=$(r);break}return {isValid:a,message:a?void 0:`\u7121\u6548\u7684${D(e)}\u8ECA\u724C\u865F\u78BC`,plateType:a&&i?e:void 0}}let n=U(r);return n?{isValid:true,plateType:i?n:void 0}:{isValid:false,message:"\u7121\u6548\u7684\u8ECA\u724C\u865F\u78BC\u683C\u5F0F"}}function D(t){return {car:"\u6C7D\u8ECA","car-old":"\u6C7D\u8ECA\uFF08\u820A\u5236\uFF09","electric-car":"\u96FB\u52D5\u6C7D\u8ECA","motorcycle-small":"\u5C0F\u578B\u6A5F\u8ECA",motorcycle:"\u6A5F\u8ECA"}[t]}exports.validateBusinessNumber=Z;exports.validateCitizenCertificate=E;exports.validateEInvoiceDonationCode=F;exports.validateEInvoiceMobileBarcode=z;exports.validateLicensePlate=B;exports.validateMobilePhone=w;exports.validateNationalId=M;exports.validateResidentCertificate=h;//# sourceMappingURL=index.cjs.map
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/validators/national-id.ts","../src/validators/business-number.ts","../src/validators/resident-certificate.ts","../src/validators/mobile-phone.ts","../src/validators/citizen-certificate.ts","../src/validators/einvoice-mobile-barcode.ts","../src/validators/einvoice-donation-code.ts"],"names":["LETTER_MAPPING","validateOldFormat","id","letter","numbers","letterValue","d1","d2","weights","acc","digit","index","validateNewFormat","firstLetter","secondLetter","firstLetterValue","secondLetterValue","d3","d4","detectFormat","validateNationalId","format","normalizedId","isValid","detectedFormat","validateBusinessNumber","number","normalized","digits","sum","i","product","validateResidentCertificate","validateMobilePhone","phone","validateCitizenCertificate","certNumber","validateEInvoiceMobileBarcode","barcode","validateEInvoiceDonationCode","code"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ValidationResult, NationalIdType } from \"../types\";\n\n/**\n * 字母對應數字表(用於身分證字號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式身分證字號格式(1個字母 + 9個數字)\n * 格式:A123456789\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(1 = 男性,2 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-Z][12]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式身分證字號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測身分證字號格式類型\n */\nfunction detectFormat(id: string): NationalIdType | null {\n if (/^[A-Z][12]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣身分證字號(支援新舊格式)\n * @param id - 要驗證的身分證字號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateNationalId('A123456789'); // 舊式格式\n * validateNationalId('AA12345678'); // 新式格式\n * ```\n */\nexport function validateNationalId(\n id: string,\n format?: NationalIdType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"身分證字號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式身分證字號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式身分證字號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的身分證字號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}身分證字號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣營利事業統一編號\n * 格式:8位數字\n * 使用加權檢查碼演算法\n *\n * @param number - 要驗證的統一編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateBusinessNumber('12345678');\n * ```\n */\nexport function validateBusinessNumber(number: string): ValidationResult {\n if (!number || typeof number !== \"string\") {\n return {\n isValid: false,\n message: \"統一編號必須為非空字串\",\n };\n }\n\n const normalized = number.trim();\n\n // 檢查是否為8位數字\n const pattern = /^\\d{8}$/;\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"統一編號必須為8位數字\",\n };\n }\n\n const digits = normalized.split(\"\").map(Number);\n const weights = [1, 2, 1, 2, 1, 2, 4, 1];\n\n // 計算加權總和\n let sum = 0;\n for (let i = 0; i < 8; i++) {\n let product = digits[i]! * weights[i]!;\n\n // 如果乘積為兩位數,將十位數和個位數相加\n if (product >= 10) {\n product = Math.floor(product / 10) + (product % 10);\n }\n\n sum += product;\n }\n\n // 特殊情況:第7位數字為7時\n // 如果第7位數字為7且總和除以10的餘數為1,也視為有效\n const isValid = sum % 10 === 0 || (digits[6]! === 7 && sum % 10 === 1);\n\n return {\n isValid,\n message: isValid ? undefined : \"統一編號檢查碼錯誤\",\n };\n}\n","import type { ValidationResult, ResidentCertificateType } from \"../types\";\n\n/**\n * 字母對應數字表(用於居留證號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式居留證號格式(1個字母 + 9個數字)\n * 格式:A800000000\n * - 第一個字元:地區代碼(A、B、C 或 D 表示外國人士)\n * - 第二個字元:性別/類型(8 = 男性,9 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-D][89]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼(與身分證字號相同的演算法)\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式居留證號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測居留證號格式類型\n */\nfunction detectFormat(id: string): ResidentCertificateType | null {\n if (/^[A-D][89]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣居留證號(支援新舊格式)\n * @param id - 要驗證的居留證號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateResidentCertificate('A800000000'); // 舊式格式\n * validateResidentCertificate('AA12345678'); // 新式格式\n * ```\n */\nexport function validateResidentCertificate(\n id: string,\n format?: ResidentCertificateType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"居留證號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式居留證號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式居留證號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的居留證號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}居留證號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣手機號碼\n * 格式:09XXXXXXXX(10位數字,以09開頭)\n *\n * @param phone - 要驗證的手機號碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateMobilePhone('0912345678');\n * validateMobilePhone('0912-345-678'); // 含分隔符號\n * ```\n */\nexport function validateMobilePhone(phone: string): ValidationResult {\n if (!phone || typeof phone !== \"string\") {\n return {\n isValid: false,\n message: \"手機號碼必須為非空字串\",\n };\n }\n\n // 移除常見的分隔符號(空格、破折號、括號)\n const normalized = phone.trim().replace(/[\\s\\-()]/g, \"\");\n\n // 檢查是否符合台灣手機號碼格式\n // 以09開頭且總共10位數字\n const pattern = /^09\\d{8}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機號碼必須以09開頭且為10位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣自然人憑證編號\n * 格式:2個大寫英文字母 + 14位數字\n * 範例:AB12345678901234\n *\n * @param certNumber - 要驗證的自然人憑證編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateCitizenCertificate('AB12345678901234');\n * ```\n */\nexport function validateCitizenCertificate(\n certNumber: string,\n): ValidationResult {\n if (!certNumber || typeof certNumber !== \"string\") {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為非空字串\",\n };\n }\n\n const normalized = certNumber.trim().toUpperCase();\n\n // 檢查格式:2個字母 + 14位數字\n const pattern = /^[A-Z]{2}\\d{14}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為2個英文字母加上14位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票手機條碼\n * 格式:/ + 7個字元(大寫英文字母、數字、+、-、.)\n * 範例:/ABCD123\n *\n * 手機條碼用於將電子發票儲存在手機載具中\n *\n * @param barcode - 要驗證的手機條碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceMobileBarcode('/ABCD123');\n * validateEInvoiceMobileBarcode('/1234567');\n * ```\n */\nexport function validateEInvoiceMobileBarcode(\n barcode: string,\n): ValidationResult {\n if (!barcode || typeof barcode !== \"string\") {\n return {\n isValid: false,\n message: \"手機條碼必須為非空字串\",\n };\n }\n\n const normalized = barcode.trim().toUpperCase();\n\n // 檢查格式:以 / 開頭,後接7個字元\n // 有效字元:A-Z、0-9、+、-、.\n const pattern = /^\\/[A-Z0-9+.-]{7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機條碼必須以 / 開頭,後接7個有效字元(A-Z、0-9、+、-、.)\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票捐贈碼\n * 格式:3-7位數字\n * 範例:123、12345、1234567\n *\n * 捐贈碼用於將電子發票捐贈給已註冊的慈善機構\n *\n * @param code - 要驗證的捐贈碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceDonationCode('123');\n * validateEInvoiceDonationCode('12345');\n * ```\n */\nexport function validateEInvoiceDonationCode(code: string): ValidationResult {\n if (!code || typeof code !== \"string\") {\n return {\n isValid: false,\n message: \"捐贈碼必須為非空字串\",\n };\n }\n\n const normalized = code.trim();\n\n // 檢查格式:3-7位數字\n const pattern = /^\\d{3,7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"捐贈碼必須為3至7位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/validators/national-id.ts","../src/validators/business-number.ts","../src/validators/resident-certificate.ts","../src/validators/mobile-phone.ts","../src/validators/citizen-certificate.ts","../src/validators/einvoice-mobile-barcode.ts","../src/validators/einvoice-donation-code.ts","../src/validators/license-plate.ts"],"names":["LETTER_MAPPING","validateOldFormat","id","letter","numbers","letterValue","d1","d2","weights","acc","digit","index","validateNewFormat","firstLetter","secondLetter","firstLetterValue","secondLetterValue","d3","d4","detectFormat","validateNationalId","format","normalizedId","isValid","detectedFormat","validateBusinessNumber","number","normalized","digits","sum","i","product","validateResidentCertificate","validateMobilePhone","phone","validateCitizenCertificate","certNumber","validateEInvoiceMobileBarcode","barcode","validateEInvoiceDonationCode","code","validateNewCarPlate","plate","validateOldCarPlate","validateElectricCarPlate","validateSmallMotorcyclePlate","pattern1","pattern2","validateMotorcyclePlate","detectPlateType","validateLicensePlate","options","type","detectType","getPlateTypeName","detectedType"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ValidationResult, NationalIdType } from \"../types\";\n\n/**\n * 字母對應數字表(用於身分證字號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式身分證字號格式(1個字母 + 9個數字)\n * 格式:A123456789\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(1 = 男性,2 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-Z][12]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式身分證字號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測身分證字號格式類型\n */\nfunction detectFormat(id: string): NationalIdType | null {\n if (/^[A-Z][12]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣身分證字號(支援新舊格式)\n * @param id - 要驗證的身分證字號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateNationalId('A123456789'); // 舊式格式\n * validateNationalId('AA12345678'); // 新式格式\n * ```\n */\nexport function validateNationalId(\n id: string,\n format?: NationalIdType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"身分證字號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式身分證字號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式身分證字號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的身分證字號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}身分證字號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣營利事業統一編號\n * 格式:8位數字\n * 使用加權檢查碼演算法\n *\n * @param number - 要驗證的統一編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateBusinessNumber('12345678');\n * ```\n */\nexport function validateBusinessNumber(number: string): ValidationResult {\n if (!number || typeof number !== \"string\") {\n return {\n isValid: false,\n message: \"統一編號必須為非空字串\",\n };\n }\n\n const normalized = number.trim();\n\n // 檢查是否為8位數字\n const pattern = /^\\d{8}$/;\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"統一編號必須為8位數字\",\n };\n }\n\n const digits = normalized.split(\"\").map(Number);\n const weights = [1, 2, 1, 2, 1, 2, 4, 1];\n\n // 計算加權總和\n let sum = 0;\n for (let i = 0; i < 8; i++) {\n let product = digits[i]! * weights[i]!;\n\n // 如果乘積為兩位數,將十位數和個位數相加\n if (product >= 10) {\n product = Math.floor(product / 10) + (product % 10);\n }\n\n sum += product;\n }\n\n // 特殊情況:第7位數字為7時\n // 如果第7位數字為7且總和除以10的餘數為1,也視為有效\n const isValid = sum % 10 === 0 || (digits[6]! === 7 && sum % 10 === 1);\n\n return {\n isValid,\n message: isValid ? undefined : \"統一編號檢查碼錯誤\",\n };\n}\n","import type { ValidationResult, ResidentCertificateType } from \"../types\";\n\n/**\n * 字母對應數字表(用於居留證號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式居留證號格式(1個字母 + 9個數字)\n * 格式:A800000000\n * - 第一個字元:地區代碼(A、B、C 或 D 表示外國人士)\n * - 第二個字元:性別/類型(8 = 男性,9 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-D][89]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼(與身分證字號相同的演算法)\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式居留證號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測居留證號格式類型\n */\nfunction detectFormat(id: string): ResidentCertificateType | null {\n if (/^[A-D][89]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣居留證號(支援新舊格式)\n * @param id - 要驗證的居留證號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateResidentCertificate('A800000000'); // 舊式格式\n * validateResidentCertificate('AA12345678'); // 新式格式\n * ```\n */\nexport function validateResidentCertificate(\n id: string,\n format?: ResidentCertificateType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"居留證號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式居留證號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式居留證號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的居留證號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}居留證號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣手機號碼\n * 格式:09XXXXXXXX(10位數字,以09開頭)\n *\n * @param phone - 要驗證的手機號碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateMobilePhone('0912345678');\n * validateMobilePhone('0912-345-678'); // 含分隔符號\n * ```\n */\nexport function validateMobilePhone(phone: string): ValidationResult {\n if (!phone || typeof phone !== \"string\") {\n return {\n isValid: false,\n message: \"手機號碼必須為非空字串\",\n };\n }\n\n // 移除常見的分隔符號(空格、破折號、括號)\n const normalized = phone.trim().replace(/[\\s\\-()]/g, \"\");\n\n // 檢查是否符合台灣手機號碼格式\n // 以09開頭且總共10位數字\n const pattern = /^09\\d{8}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機號碼必須以09開頭且為10位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣自然人憑證編號\n * 格式:2個大寫英文字母 + 14位數字\n * 範例:AB12345678901234\n *\n * @param certNumber - 要驗證的自然人憑證編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateCitizenCertificate('AB12345678901234');\n * ```\n */\nexport function validateCitizenCertificate(\n certNumber: string,\n): ValidationResult {\n if (!certNumber || typeof certNumber !== \"string\") {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為非空字串\",\n };\n }\n\n const normalized = certNumber.trim().toUpperCase();\n\n // 檢查格式:2個字母 + 14位數字\n const pattern = /^[A-Z]{2}\\d{14}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為2個英文字母加上14位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票手機條碼\n * 格式:/ + 7個字元(大寫英文字母、數字、+、-、.)\n * 範例:/ABCD123\n *\n * 手機條碼用於將電子發票儲存在手機載具中\n *\n * @param barcode - 要驗證的手機條碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceMobileBarcode('/ABCD123');\n * validateEInvoiceMobileBarcode('/1234567');\n * ```\n */\nexport function validateEInvoiceMobileBarcode(\n barcode: string,\n): ValidationResult {\n if (!barcode || typeof barcode !== \"string\") {\n return {\n isValid: false,\n message: \"手機條碼必須為非空字串\",\n };\n }\n\n const normalized = barcode.trim().toUpperCase();\n\n // 檢查格式:以 / 開頭,後接7個字元\n // 有效字元:A-Z、0-9、+、-、.\n const pattern = /^\\/[A-Z0-9+.-]{7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機條碼必須以 / 開頭,後接7個有效字元(A-Z、0-9、+、-、.)\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票捐贈碼\n * 格式:3-7位數字\n * 範例:123、12345、1234567\n *\n * 捐贈碼用於將電子發票捐贈給已註冊的慈善機構\n *\n * @param code - 要驗證的捐贈碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceDonationCode('123');\n * validateEInvoiceDonationCode('12345');\n * ```\n */\nexport function validateEInvoiceDonationCode(code: string): ValidationResult {\n if (!code || typeof code !== \"string\") {\n return {\n isValid: false,\n message: \"捐贈碼必須為非空字串\",\n };\n }\n\n const normalized = code.trim();\n\n // 檢查格式:3-7位數字\n const pattern = /^\\d{3,7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"捐贈碼必須為3至7位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 車牌類型\n */\nexport type LicensePlateType =\n | \"car\" // 汽車(新制)\n | \"car-old\" // 汽車(舊制)\n | \"electric-car\" // 電動汽車\n | \"motorcycle-small\" // 小型機車(50cc以下)\n | \"motorcycle\"; // 一般機車\n\n/**\n * 車牌驗證結果(含車牌類型資訊)\n */\nexport interface LicensePlateValidationResult extends ValidationResult {\n plateType?: LicensePlateType | undefined;\n}\n\n/**\n * 驗證新制汽車車牌(2012年12月後)\n * 格式:3個英文字母 - 4個數字\n * 不使用字母 I、O\n * 不使用數字 4\n */\nfunction validateNewCarPlate(plate: string): boolean {\n // 格式:3個英文字母-4個數字(例如:ABC-1234)\n const pattern = /^[A-HJ-NP-Z]{3}-\\d{4}$/;\n if (!pattern.test(plate)) {\n return false;\n }\n\n // 檢查數字部分不包含 4\n const numbers = plate.split(\"-\")[1] as string;\n return !numbers.includes(\"4\");\n}\n\n/**\n * 驗證舊制汽車車牌(1992-2012)\n * 格式:1個數字 + 1個英文字母 - 4個數字\n */\nfunction validateOldCarPlate(plate: string): boolean {\n // 格式:1個數字+1個英文字母-4個數字(例如:1A-2345)\n const pattern = /^\\d[A-Z]-\\d{4}$/;\n return pattern.test(plate);\n}\n\n/**\n * 驗證電動汽車車牌\n * 格式:E + 2個英文字母 - 4個數字\n * 不使用字母 I、O\n * 不使用數字 4\n */\nfunction validateElectricCarPlate(plate: string): boolean {\n // 格式:E + 2個英文字母-4個數字(例如:EAB-1234)\n const pattern = /^E[A-HJ-NP-Z]{2}-\\d{4}$/;\n if (!pattern.test(plate)) {\n return false;\n }\n\n // 檢查數字部分不包含 4\n const numbers = plate.split(\"-\")[1] as string;\n return !numbers.includes(\"4\");\n}\n\n/**\n * 驗證小型機車車牌(50cc以下)\n * 格式:3個數字-3個英文字母 或 3個英文字母-3個數字\n */\nfunction validateSmallMotorcyclePlate(plate: string): boolean {\n // 格式1:3個數字-3個英文字母(例如:123-ABC)\n // 格式2:3個英文字母-3個數字(例如:ABC-123)\n const pattern1 = /^\\d{3}-[A-Z]{3}$/;\n const pattern2 = /^[A-Z]{3}-\\d{3}$/;\n return pattern1.test(plate) || pattern2.test(plate);\n}\n\n/**\n * 驗證一般機車車牌(舊制,50-250cc)\n * 格式:2個英文字母 + 1個數字 - 3個數字\n */\nfunction validateMotorcyclePlate(plate: string): boolean {\n // 格式:2個英文字母+1個數字-3個數字(例如:AB1-234)\n const pattern = /^[A-Z]{2}\\d-\\d{3}$/;\n return pattern.test(plate);\n}\n\n/**\n * 偵測車牌類型\n */\nfunction detectPlateType(plate: string): LicensePlateType | null {\n if (validateElectricCarPlate(plate)) {\n return \"electric-car\";\n }\n if (validateNewCarPlate(plate)) {\n return \"car\";\n }\n if (validateOldCarPlate(plate)) {\n return \"car-old\";\n }\n if (validateSmallMotorcyclePlate(plate)) {\n return \"motorcycle-small\";\n }\n if (validateMotorcyclePlate(plate)) {\n return \"motorcycle\";\n }\n return null;\n}\n\n/**\n * 驗證台灣車牌號碼\n * @param plate - 要驗證的車牌號碼\n * @param options - 驗證選項\n * @param options.type - 可選:指定車牌類型\n * @param options.detectType - 是否偵測車牌類型(預設:true)\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * // 新制汽車\n * validateLicensePlate('ABC-1235');\n *\n * // 電動汽車\n * validateLicensePlate('EAB-1235');\n *\n * // 舊制汽車\n * validateLicensePlate('1A-2345');\n *\n * // 小型機車\n * validateLicensePlate('123-ABC');\n * validateLicensePlate('ABC-123');\n *\n * // 一般機車\n * validateLicensePlate('AB1-234');\n *\n * // 指定類型驗證\n * validateLicensePlate('ABC-1235', { type: 'car' });\n *\n * // 不偵測類型\n * validateLicensePlate('ABC-1235', { detectType: false });\n * ```\n */\nexport function validateLicensePlate(\n plate: string,\n options: { type?: LicensePlateType; detectType?: boolean } = {},\n): LicensePlateValidationResult {\n const { type, detectType = true } = options;\n\n if (!plate || typeof plate !== \"string\") {\n return {\n isValid: false,\n message: \"車牌號碼必須為非空字串\",\n };\n }\n\n // 移除空格並轉換為大寫\n const normalized = plate.trim().toUpperCase();\n\n // 如果指定了類型,只驗證該類型\n if (type) {\n let isValid = false;\n switch (type) {\n case \"car\":\n isValid = validateNewCarPlate(normalized);\n break;\n case \"car-old\":\n isValid = validateOldCarPlate(normalized);\n break;\n case \"electric-car\":\n isValid = validateElectricCarPlate(normalized);\n break;\n case \"motorcycle-small\":\n isValid = validateSmallMotorcyclePlate(normalized);\n break;\n case \"motorcycle\":\n isValid = validateMotorcyclePlate(normalized);\n break;\n }\n\n return {\n isValid,\n message: isValid ? undefined : `無效的${getPlateTypeName(type)}車牌號碼`,\n plateType: isValid && detectType ? type : undefined,\n };\n }\n\n // 自動偵測類型\n const detectedType = detectPlateType(normalized);\n\n if (!detectedType) {\n return {\n isValid: false,\n message: \"無效的車牌號碼格式\",\n };\n }\n\n return {\n isValid: true,\n plateType: detectType ? detectedType : undefined,\n };\n}\n\n/**\n * 取得車牌類型的中文名稱\n */\nfunction getPlateTypeName(type: LicensePlateType): string {\n const typeNames: Record<LicensePlateType, string> = {\n car: \"汽車\",\n \"car-old\": \"汽車(舊制)\",\n \"electric-car\": \"電動汽車\",\n \"motorcycle-small\": \"小型機車\",\n motorcycle: \"機車\",\n };\n return typeNames[type];\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -20,4 +20,13 @@ declare function validateEInvoiceMobileBarcode(barcode: string): ValidationResul
20
20
 
21
21
  declare function validateEInvoiceDonationCode(code: string): ValidationResult;
22
22
 
23
- export { type Gender, type NationalIdType, type ResidentCertificateType, type ValidationResult, validateBusinessNumber, validateCitizenCertificate, validateEInvoiceDonationCode, validateEInvoiceMobileBarcode, validateMobilePhone, validateNationalId, validateResidentCertificate };
23
+ type LicensePlateType = "car" | "car-old" | "electric-car" | "motorcycle-small" | "motorcycle";
24
+ interface LicensePlateValidationResult extends ValidationResult {
25
+ plateType?: LicensePlateType | undefined;
26
+ }
27
+ declare function validateLicensePlate(plate: string, options?: {
28
+ type?: LicensePlateType;
29
+ detectType?: boolean;
30
+ }): LicensePlateValidationResult;
31
+
32
+ export { type Gender, type LicensePlateType, type LicensePlateValidationResult, type NationalIdType, type ResidentCertificateType, type ValidationResult, validateBusinessNumber, validateCitizenCertificate, validateEInvoiceDonationCode, validateEInvoiceMobileBarcode, validateLicensePlate, validateMobilePhone, validateNationalId, validateResidentCertificate };
package/dist/index.d.ts CHANGED
@@ -20,4 +20,13 @@ declare function validateEInvoiceMobileBarcode(barcode: string): ValidationResul
20
20
 
21
21
  declare function validateEInvoiceDonationCode(code: string): ValidationResult;
22
22
 
23
- export { type Gender, type NationalIdType, type ResidentCertificateType, type ValidationResult, validateBusinessNumber, validateCitizenCertificate, validateEInvoiceDonationCode, validateEInvoiceMobileBarcode, validateMobilePhone, validateNationalId, validateResidentCertificate };
23
+ type LicensePlateType = "car" | "car-old" | "electric-car" | "motorcycle-small" | "motorcycle";
24
+ interface LicensePlateValidationResult extends ValidationResult {
25
+ plateType?: LicensePlateType | undefined;
26
+ }
27
+ declare function validateLicensePlate(plate: string, options?: {
28
+ type?: LicensePlateType;
29
+ detectType?: boolean;
30
+ }): LicensePlateValidationResult;
31
+
32
+ export { type Gender, type LicensePlateType, type LicensePlateValidationResult, type NationalIdType, type ResidentCertificateType, type ValidationResult, validateBusinessNumber, validateCitizenCertificate, validateEInvoiceDonationCode, validateEInvoiceMobileBarcode, validateLicensePlate, validateMobilePhone, validateNationalId, validateResidentCertificate };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- var V={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function R(t){if(!/^[A-Z][12]\d{8}$/.test(t))return false;let s=t[0],i=t.slice(1),r=V[s],e=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [e,a,...i.split("").map(Number)].reduce((d,u,f)=>d+u*o[f],0)%10===0}function v(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let s=t[0],i=t[1],r=t.slice(2),e=V[s],a=V[i],o=Math.floor(e/10),l=e%10,c=Math.floor(a/10),d=a%10,u=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,c,d,...r.split("").map(Number)].reduce((m,p,g)=>m+p*u[g],0)%10===0}function I(t){return /^[A-Z][12]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function x(t,n){if(!t||typeof t!="string")return {isValid:false,message:"\u8EAB\u5206\u8B49\u5B57\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();if(n==="old"){let e=R(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}if(n==="new"){let e=v(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}let i=I(s);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u8EAB\u5206\u8B49\u5B57\u865F\u683C\u5F0F"};let r=i==="old"?R(s):v(s);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u8EAB\u5206\u8B49\u5B57\u865F`}}function A(t){if(!t||typeof t!="string")return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim();if(!/^\d{8}$/.test(n))return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA8\u4F4D\u6578\u5B57"};let i=n.split("").map(Number),r=[1,2,1,2,1,2,4,1],e=0;for(let o=0;o<8;o++){let l=i[o]*r[o];l>=10&&(l=Math.floor(l/10)+l%10),e+=l;}let a=e%10===0||i[6]===7&&e%10===1;return {isValid:a,message:a?void 0:"\u7D71\u4E00\u7DE8\u865F\u6AA2\u67E5\u78BC\u932F\u8AA4"}}var y={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function b(t){if(!/^[A-D][89]\d{8}$/.test(t))return false;let s=t[0],i=t.slice(1),r=y[s],e=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [e,a,...i.split("").map(Number)].reduce((d,u,f)=>d+u*o[f],0)%10===0}function C(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let s=t[0],i=t[1],r=t.slice(2),e=y[s],a=y[i],o=Math.floor(e/10),l=e%10,c=Math.floor(a/10),d=a%10,u=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,c,d,...r.split("").map(Number)].reduce((m,p,g)=>m+p*u[g],0)%10===0}function M(t){return /^[A-D][89]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function $(t,n){if(!t||typeof t!="string")return {isValid:false,message:"\u5C45\u7559\u8B49\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();if(n==="old"){let e=b(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u5C45\u7559\u8B49\u865F"}}if(n==="new"){let e=C(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u5C45\u7559\u8B49\u865F"}}let i=M(s);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u5C45\u7559\u8B49\u865F\u683C\u5F0F"};let r=i==="old"?b(s):C(s);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u5C45\u7559\u8B49\u865F`}}function T(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().replace(/[\s\-()]/g,"");return /^09\d{8}$/.test(n)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u4EE509\u958B\u982D\u4E14\u70BA10\u4F4D\u6578\u5B57"}}function h(t){if(!t||typeof t!="string")return {isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().toUpperCase();return /^[A-Z]{2}\d{14}$/.test(n)?{isValid:true}:{isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA2\u500B\u82F1\u6587\u5B57\u6BCD\u52A0\u4E0A14\u4F4D\u6578\u5B57"}}function L(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().toUpperCase();return /^\/[A-Z0-9+.-]{7}$/.test(n)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u4EE5 / \u958B\u982D\uFF0C\u5F8C\u63A57\u500B\u6709\u6548\u5B57\u5143\uFF08A-Z\u30010-9\u3001+\u3001-\u3001.\uFF09"}}function w(t){if(!t||typeof t!="string")return {isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim();return /^\d{3,7}$/.test(n)?{isValid:true}:{isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA3\u81F37\u4F4D\u6578\u5B57"}}export{A as validateBusinessNumber,h as validateCitizenCertificate,w as validateEInvoiceDonationCode,L as validateEInvoiceMobileBarcode,T as validateMobilePhone,x as validateNationalId,$ as validateResidentCertificate};//# sourceMappingURL=index.mjs.map
1
+ var y={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function b(t){if(!/^[A-Z][12]\d{8}$/.test(t))return false;let e=t[0],i=t.slice(1),r=y[e],n=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [n,a,...i.split("").map(Number)].reduce((c,d,f)=>c+d*o[f],0)%10===0}function R(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let e=t[0],i=t[1],r=t.slice(2),n=y[e],a=y[i],o=Math.floor(n/10),l=n%10,u=Math.floor(a/10),c=a%10,d=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,u,c,...r.split("").map(Number)].reduce((p,m,g)=>p+m*d[g],0)%10===0}function x(t){return /^[A-Z][12]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function M(t,s){if(!t||typeof t!="string")return {isValid:false,message:"\u8EAB\u5206\u8B49\u5B57\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let e=t.trim().toUpperCase();if(s==="old"){let n=b(e);return {isValid:n,message:n?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}if(s==="new"){let n=R(e);return {isValid:n,message:n?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}let i=x(e);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u8EAB\u5206\u8B49\u5B57\u865F\u683C\u5F0F"};let r=i==="old"?b(e):R(e);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u8EAB\u5206\u8B49\u5B57\u865F`}}function Z(t){if(!t||typeof t!="string")return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim();if(!/^\d{8}$/.test(s))return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA8\u4F4D\u6578\u5B57"};let i=s.split("").map(Number),r=[1,2,1,2,1,2,4,1],n=0;for(let o=0;o<8;o++){let l=i[o]*r[o];l>=10&&(l=Math.floor(l/10)+l%10),n+=l;}let a=n%10===0||i[6]===7&&n%10===1;return {isValid:a,message:a?void 0:"\u7D71\u4E00\u7DE8\u865F\u6AA2\u67E5\u78BC\u932F\u8AA4"}}var V={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function v(t){if(!/^[A-D][89]\d{8}$/.test(t))return false;let e=t[0],i=t.slice(1),r=V[e],n=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [n,a,...i.split("").map(Number)].reduce((c,d,f)=>c+d*o[f],0)%10===0}function P(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let e=t[0],i=t[1],r=t.slice(2),n=V[e],a=V[i],o=Math.floor(n/10),l=n%10,u=Math.floor(a/10),c=a%10,d=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,u,c,...r.split("").map(Number)].reduce((p,m,g)=>p+m*d[g],0)%10===0}function I(t){return /^[A-D][89]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function h(t,s){if(!t||typeof t!="string")return {isValid:false,message:"\u5C45\u7559\u8B49\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let e=t.trim().toUpperCase();if(s==="old"){let n=v(e);return {isValid:n,message:n?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u5C45\u7559\u8B49\u865F"}}if(s==="new"){let n=P(e);return {isValid:n,message:n?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u5C45\u7559\u8B49\u865F"}}let i=I(e);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u5C45\u7559\u8B49\u865F\u683C\u5F0F"};let r=i==="old"?v(e):P(e);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u5C45\u7559\u8B49\u865F`}}function w(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().replace(/[\s\-()]/g,"");return /^09\d{8}$/.test(s)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u4EE509\u958B\u982D\u4E14\u70BA10\u4F4D\u6578\u5B57"}}function E(t){if(!t||typeof t!="string")return {isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();return /^[A-Z]{2}\d{14}$/.test(s)?{isValid:true}:{isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA2\u500B\u82F1\u6587\u5B57\u6BCD\u52A0\u4E0A14\u4F4D\u6578\u5B57"}}function z(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();return /^\/[A-Z0-9+.-]{7}$/.test(s)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u4EE5 / \u958B\u982D\uFF0C\u5F8C\u63A57\u500B\u6709\u6548\u5B57\u5143\uFF08A-Z\u30010-9\u3001+\u3001-\u3001.\uFF09"}}function F(t){if(!t||typeof t!="string")return {isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim();return /^\d{3,7}$/.test(s)?{isValid:true}:{isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA3\u81F37\u4F4D\u6578\u5B57"}}function T(t){return /^[A-HJ-NP-Z]{3}-\d{4}$/.test(t)?!t.split("-")[1].includes("4"):false}function L(t){return /^\d[A-Z]-\d{4}$/.test(t)}function N(t){return /^E[A-HJ-NP-Z]{2}-\d{4}$/.test(t)?!t.split("-")[1].includes("4"):false}function C(t){let s=/^\d{3}-[A-Z]{3}$/,e=/^[A-Z]{3}-\d{3}$/;return s.test(t)||e.test(t)}function $(t){return /^[A-Z]{2}\d-\d{3}$/.test(t)}function U(t){return N(t)?"electric-car":T(t)?"car":L(t)?"car-old":C(t)?"motorcycle-small":$(t)?"motorcycle":null}function B(t,s={}){let{type:e,detectType:i=true}=s;if(!t||typeof t!="string")return {isValid:false,message:"\u8ECA\u724C\u865F\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let r=t.trim().toUpperCase();if(e){let a=false;switch(e){case "car":a=T(r);break;case "car-old":a=L(r);break;case "electric-car":a=N(r);break;case "motorcycle-small":a=C(r);break;case "motorcycle":a=$(r);break}return {isValid:a,message:a?void 0:`\u7121\u6548\u7684${D(e)}\u8ECA\u724C\u865F\u78BC`,plateType:a&&i?e:void 0}}let n=U(r);return n?{isValid:true,plateType:i?n:void 0}:{isValid:false,message:"\u7121\u6548\u7684\u8ECA\u724C\u865F\u78BC\u683C\u5F0F"}}function D(t){return {car:"\u6C7D\u8ECA","car-old":"\u6C7D\u8ECA\uFF08\u820A\u5236\uFF09","electric-car":"\u96FB\u52D5\u6C7D\u8ECA","motorcycle-small":"\u5C0F\u578B\u6A5F\u8ECA",motorcycle:"\u6A5F\u8ECA"}[t]}export{Z as validateBusinessNumber,E as validateCitizenCertificate,F as validateEInvoiceDonationCode,z as validateEInvoiceMobileBarcode,B as validateLicensePlate,w as validateMobilePhone,M as validateNationalId,h as validateResidentCertificate};//# sourceMappingURL=index.mjs.map
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/validators/national-id.ts","../src/validators/business-number.ts","../src/validators/resident-certificate.ts","../src/validators/mobile-phone.ts","../src/validators/citizen-certificate.ts","../src/validators/einvoice-mobile-barcode.ts","../src/validators/einvoice-donation-code.ts"],"names":["LETTER_MAPPING","validateOldFormat","id","letter","numbers","letterValue","d1","d2","weights","acc","digit","index","validateNewFormat","firstLetter","secondLetter","firstLetterValue","secondLetterValue","d3","d4","detectFormat","validateNationalId","format","normalizedId","isValid","detectedFormat","validateBusinessNumber","number","normalized","digits","sum","i","product","validateResidentCertificate","validateMobilePhone","phone","validateCitizenCertificate","certNumber","validateEInvoiceMobileBarcode","barcode","validateEInvoiceDonationCode","code"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ValidationResult, NationalIdType } from \"../types\";\n\n/**\n * 字母對應數字表(用於身分證字號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式身分證字號格式(1個字母 + 9個數字)\n * 格式:A123456789\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(1 = 男性,2 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-Z][12]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式身分證字號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測身分證字號格式類型\n */\nfunction detectFormat(id: string): NationalIdType | null {\n if (/^[A-Z][12]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣身分證字號(支援新舊格式)\n * @param id - 要驗證的身分證字號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateNationalId('A123456789'); // 舊式格式\n * validateNationalId('AA12345678'); // 新式格式\n * ```\n */\nexport function validateNationalId(\n id: string,\n format?: NationalIdType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"身分證字號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式身分證字號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式身分證字號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的身分證字號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}身分證字號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣營利事業統一編號\n * 格式:8位數字\n * 使用加權檢查碼演算法\n *\n * @param number - 要驗證的統一編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateBusinessNumber('12345678');\n * ```\n */\nexport function validateBusinessNumber(number: string): ValidationResult {\n if (!number || typeof number !== \"string\") {\n return {\n isValid: false,\n message: \"統一編號必須為非空字串\",\n };\n }\n\n const normalized = number.trim();\n\n // 檢查是否為8位數字\n const pattern = /^\\d{8}$/;\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"統一編號必須為8位數字\",\n };\n }\n\n const digits = normalized.split(\"\").map(Number);\n const weights = [1, 2, 1, 2, 1, 2, 4, 1];\n\n // 計算加權總和\n let sum = 0;\n for (let i = 0; i < 8; i++) {\n let product = digits[i]! * weights[i]!;\n\n // 如果乘積為兩位數,將十位數和個位數相加\n if (product >= 10) {\n product = Math.floor(product / 10) + (product % 10);\n }\n\n sum += product;\n }\n\n // 特殊情況:第7位數字為7時\n // 如果第7位數字為7且總和除以10的餘數為1,也視為有效\n const isValid = sum % 10 === 0 || (digits[6]! === 7 && sum % 10 === 1);\n\n return {\n isValid,\n message: isValid ? undefined : \"統一編號檢查碼錯誤\",\n };\n}\n","import type { ValidationResult, ResidentCertificateType } from \"../types\";\n\n/**\n * 字母對應數字表(用於居留證號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式居留證號格式(1個字母 + 9個數字)\n * 格式:A800000000\n * - 第一個字元:地區代碼(A、B、C 或 D 表示外國人士)\n * - 第二個字元:性別/類型(8 = 男性,9 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-D][89]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼(與身分證字號相同的演算法)\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式居留證號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測居留證號格式類型\n */\nfunction detectFormat(id: string): ResidentCertificateType | null {\n if (/^[A-D][89]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣居留證號(支援新舊格式)\n * @param id - 要驗證的居留證號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateResidentCertificate('A800000000'); // 舊式格式\n * validateResidentCertificate('AA12345678'); // 新式格式\n * ```\n */\nexport function validateResidentCertificate(\n id: string,\n format?: ResidentCertificateType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"居留證號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式居留證號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式居留證號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的居留證號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}居留證號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣手機號碼\n * 格式:09XXXXXXXX(10位數字,以09開頭)\n *\n * @param phone - 要驗證的手機號碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateMobilePhone('0912345678');\n * validateMobilePhone('0912-345-678'); // 含分隔符號\n * ```\n */\nexport function validateMobilePhone(phone: string): ValidationResult {\n if (!phone || typeof phone !== \"string\") {\n return {\n isValid: false,\n message: \"手機號碼必須為非空字串\",\n };\n }\n\n // 移除常見的分隔符號(空格、破折號、括號)\n const normalized = phone.trim().replace(/[\\s\\-()]/g, \"\");\n\n // 檢查是否符合台灣手機號碼格式\n // 以09開頭且總共10位數字\n const pattern = /^09\\d{8}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機號碼必須以09開頭且為10位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣自然人憑證編號\n * 格式:2個大寫英文字母 + 14位數字\n * 範例:AB12345678901234\n *\n * @param certNumber - 要驗證的自然人憑證編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateCitizenCertificate('AB12345678901234');\n * ```\n */\nexport function validateCitizenCertificate(\n certNumber: string,\n): ValidationResult {\n if (!certNumber || typeof certNumber !== \"string\") {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為非空字串\",\n };\n }\n\n const normalized = certNumber.trim().toUpperCase();\n\n // 檢查格式:2個字母 + 14位數字\n const pattern = /^[A-Z]{2}\\d{14}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為2個英文字母加上14位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票手機條碼\n * 格式:/ + 7個字元(大寫英文字母、數字、+、-、.)\n * 範例:/ABCD123\n *\n * 手機條碼用於將電子發票儲存在手機載具中\n *\n * @param barcode - 要驗證的手機條碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceMobileBarcode('/ABCD123');\n * validateEInvoiceMobileBarcode('/1234567');\n * ```\n */\nexport function validateEInvoiceMobileBarcode(\n barcode: string,\n): ValidationResult {\n if (!barcode || typeof barcode !== \"string\") {\n return {\n isValid: false,\n message: \"手機條碼必須為非空字串\",\n };\n }\n\n const normalized = barcode.trim().toUpperCase();\n\n // 檢查格式:以 / 開頭,後接7個字元\n // 有效字元:A-Z、0-9、+、-、.\n const pattern = /^\\/[A-Z0-9+.-]{7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機條碼必須以 / 開頭,後接7個有效字元(A-Z、0-9、+、-、.)\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票捐贈碼\n * 格式:3-7位數字\n * 範例:123、12345、1234567\n *\n * 捐贈碼用於將電子發票捐贈給已註冊的慈善機構\n *\n * @param code - 要驗證的捐贈碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceDonationCode('123');\n * validateEInvoiceDonationCode('12345');\n * ```\n */\nexport function validateEInvoiceDonationCode(code: string): ValidationResult {\n if (!code || typeof code !== \"string\") {\n return {\n isValid: false,\n message: \"捐贈碼必須為非空字串\",\n };\n }\n\n const normalized = code.trim();\n\n // 檢查格式:3-7位數字\n const pattern = /^\\d{3,7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"捐贈碼必須為3至7位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/validators/national-id.ts","../src/validators/business-number.ts","../src/validators/resident-certificate.ts","../src/validators/mobile-phone.ts","../src/validators/citizen-certificate.ts","../src/validators/einvoice-mobile-barcode.ts","../src/validators/einvoice-donation-code.ts","../src/validators/license-plate.ts"],"names":["LETTER_MAPPING","validateOldFormat","id","letter","numbers","letterValue","d1","d2","weights","acc","digit","index","validateNewFormat","firstLetter","secondLetter","firstLetterValue","secondLetterValue","d3","d4","detectFormat","validateNationalId","format","normalizedId","isValid","detectedFormat","validateBusinessNumber","number","normalized","digits","sum","i","product","validateResidentCertificate","validateMobilePhone","phone","validateCitizenCertificate","certNumber","validateEInvoiceMobileBarcode","barcode","validateEInvoiceDonationCode","code","validateNewCarPlate","plate","validateOldCarPlate","validateElectricCarPlate","validateSmallMotorcyclePlate","pattern1","pattern2","validateMotorcyclePlate","detectPlateType","validateLicensePlate","options","type","detectType","getPlateTypeName","detectedType"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ValidationResult, NationalIdType } from \"../types\";\n\n/**\n * 字母對應數字表(用於身分證字號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式身分證字號格式(1個字母 + 9個數字)\n * 格式:A123456789\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(1 = 男性,2 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-Z][12]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式身分證字號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測身分證字號格式類型\n */\nfunction detectFormat(id: string): NationalIdType | null {\n if (/^[A-Z][12]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣身分證字號(支援新舊格式)\n * @param id - 要驗證的身分證字號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateNationalId('A123456789'); // 舊式格式\n * validateNationalId('AA12345678'); // 新式格式\n * ```\n */\nexport function validateNationalId(\n id: string,\n format?: NationalIdType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"身分證字號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式身分證字號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式身分證字號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的身分證字號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}身分證字號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣營利事業統一編號\n * 格式:8位數字\n * 使用加權檢查碼演算法\n *\n * @param number - 要驗證的統一編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateBusinessNumber('12345678');\n * ```\n */\nexport function validateBusinessNumber(number: string): ValidationResult {\n if (!number || typeof number !== \"string\") {\n return {\n isValid: false,\n message: \"統一編號必須為非空字串\",\n };\n }\n\n const normalized = number.trim();\n\n // 檢查是否為8位數字\n const pattern = /^\\d{8}$/;\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"統一編號必須為8位數字\",\n };\n }\n\n const digits = normalized.split(\"\").map(Number);\n const weights = [1, 2, 1, 2, 1, 2, 4, 1];\n\n // 計算加權總和\n let sum = 0;\n for (let i = 0; i < 8; i++) {\n let product = digits[i]! * weights[i]!;\n\n // 如果乘積為兩位數,將十位數和個位數相加\n if (product >= 10) {\n product = Math.floor(product / 10) + (product % 10);\n }\n\n sum += product;\n }\n\n // 特殊情況:第7位數字為7時\n // 如果第7位數字為7且總和除以10的餘數為1,也視為有效\n const isValid = sum % 10 === 0 || (digits[6]! === 7 && sum % 10 === 1);\n\n return {\n isValid,\n message: isValid ? undefined : \"統一編號檢查碼錯誤\",\n };\n}\n","import type { ValidationResult, ResidentCertificateType } from \"../types\";\n\n/**\n * 字母對應數字表(用於居留證號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式居留證號格式(1個字母 + 9個數字)\n * 格式:A800000000\n * - 第一個字元:地區代碼(A、B、C 或 D 表示外國人士)\n * - 第二個字元:性別/類型(8 = 男性,9 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-D][89]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼(與身分證字號相同的演算法)\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式居留證號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測居留證號格式類型\n */\nfunction detectFormat(id: string): ResidentCertificateType | null {\n if (/^[A-D][89]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣居留證號(支援新舊格式)\n * @param id - 要驗證的居留證號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateResidentCertificate('A800000000'); // 舊式格式\n * validateResidentCertificate('AA12345678'); // 新式格式\n * ```\n */\nexport function validateResidentCertificate(\n id: string,\n format?: ResidentCertificateType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"居留證號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式居留證號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式居留證號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的居留證號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}居留證號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣手機號碼\n * 格式:09XXXXXXXX(10位數字,以09開頭)\n *\n * @param phone - 要驗證的手機號碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateMobilePhone('0912345678');\n * validateMobilePhone('0912-345-678'); // 含分隔符號\n * ```\n */\nexport function validateMobilePhone(phone: string): ValidationResult {\n if (!phone || typeof phone !== \"string\") {\n return {\n isValid: false,\n message: \"手機號碼必須為非空字串\",\n };\n }\n\n // 移除常見的分隔符號(空格、破折號、括號)\n const normalized = phone.trim().replace(/[\\s\\-()]/g, \"\");\n\n // 檢查是否符合台灣手機號碼格式\n // 以09開頭且總共10位數字\n const pattern = /^09\\d{8}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機號碼必須以09開頭且為10位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣自然人憑證編號\n * 格式:2個大寫英文字母 + 14位數字\n * 範例:AB12345678901234\n *\n * @param certNumber - 要驗證的自然人憑證編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateCitizenCertificate('AB12345678901234');\n * ```\n */\nexport function validateCitizenCertificate(\n certNumber: string,\n): ValidationResult {\n if (!certNumber || typeof certNumber !== \"string\") {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為非空字串\",\n };\n }\n\n const normalized = certNumber.trim().toUpperCase();\n\n // 檢查格式:2個字母 + 14位數字\n const pattern = /^[A-Z]{2}\\d{14}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為2個英文字母加上14位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票手機條碼\n * 格式:/ + 7個字元(大寫英文字母、數字、+、-、.)\n * 範例:/ABCD123\n *\n * 手機條碼用於將電子發票儲存在手機載具中\n *\n * @param barcode - 要驗證的手機條碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceMobileBarcode('/ABCD123');\n * validateEInvoiceMobileBarcode('/1234567');\n * ```\n */\nexport function validateEInvoiceMobileBarcode(\n barcode: string,\n): ValidationResult {\n if (!barcode || typeof barcode !== \"string\") {\n return {\n isValid: false,\n message: \"手機條碼必須為非空字串\",\n };\n }\n\n const normalized = barcode.trim().toUpperCase();\n\n // 檢查格式:以 / 開頭,後接7個字元\n // 有效字元:A-Z、0-9、+、-、.\n const pattern = /^\\/[A-Z0-9+.-]{7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機條碼必須以 / 開頭,後接7個有效字元(A-Z、0-9、+、-、.)\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票捐贈碼\n * 格式:3-7位數字\n * 範例:123、12345、1234567\n *\n * 捐贈碼用於將電子發票捐贈給已註冊的慈善機構\n *\n * @param code - 要驗證的捐贈碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceDonationCode('123');\n * validateEInvoiceDonationCode('12345');\n * ```\n */\nexport function validateEInvoiceDonationCode(code: string): ValidationResult {\n if (!code || typeof code !== \"string\") {\n return {\n isValid: false,\n message: \"捐贈碼必須為非空字串\",\n };\n }\n\n const normalized = code.trim();\n\n // 檢查格式:3-7位數字\n const pattern = /^\\d{3,7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"捐贈碼必須為3至7位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 車牌類型\n */\nexport type LicensePlateType =\n | \"car\" // 汽車(新制)\n | \"car-old\" // 汽車(舊制)\n | \"electric-car\" // 電動汽車\n | \"motorcycle-small\" // 小型機車(50cc以下)\n | \"motorcycle\"; // 一般機車\n\n/**\n * 車牌驗證結果(含車牌類型資訊)\n */\nexport interface LicensePlateValidationResult extends ValidationResult {\n plateType?: LicensePlateType | undefined;\n}\n\n/**\n * 驗證新制汽車車牌(2012年12月後)\n * 格式:3個英文字母 - 4個數字\n * 不使用字母 I、O\n * 不使用數字 4\n */\nfunction validateNewCarPlate(plate: string): boolean {\n // 格式:3個英文字母-4個數字(例如:ABC-1234)\n const pattern = /^[A-HJ-NP-Z]{3}-\\d{4}$/;\n if (!pattern.test(plate)) {\n return false;\n }\n\n // 檢查數字部分不包含 4\n const numbers = plate.split(\"-\")[1] as string;\n return !numbers.includes(\"4\");\n}\n\n/**\n * 驗證舊制汽車車牌(1992-2012)\n * 格式:1個數字 + 1個英文字母 - 4個數字\n */\nfunction validateOldCarPlate(plate: string): boolean {\n // 格式:1個數字+1個英文字母-4個數字(例如:1A-2345)\n const pattern = /^\\d[A-Z]-\\d{4}$/;\n return pattern.test(plate);\n}\n\n/**\n * 驗證電動汽車車牌\n * 格式:E + 2個英文字母 - 4個數字\n * 不使用字母 I、O\n * 不使用數字 4\n */\nfunction validateElectricCarPlate(plate: string): boolean {\n // 格式:E + 2個英文字母-4個數字(例如:EAB-1234)\n const pattern = /^E[A-HJ-NP-Z]{2}-\\d{4}$/;\n if (!pattern.test(plate)) {\n return false;\n }\n\n // 檢查數字部分不包含 4\n const numbers = plate.split(\"-\")[1] as string;\n return !numbers.includes(\"4\");\n}\n\n/**\n * 驗證小型機車車牌(50cc以下)\n * 格式:3個數字-3個英文字母 或 3個英文字母-3個數字\n */\nfunction validateSmallMotorcyclePlate(plate: string): boolean {\n // 格式1:3個數字-3個英文字母(例如:123-ABC)\n // 格式2:3個英文字母-3個數字(例如:ABC-123)\n const pattern1 = /^\\d{3}-[A-Z]{3}$/;\n const pattern2 = /^[A-Z]{3}-\\d{3}$/;\n return pattern1.test(plate) || pattern2.test(plate);\n}\n\n/**\n * 驗證一般機車車牌(舊制,50-250cc)\n * 格式:2個英文字母 + 1個數字 - 3個數字\n */\nfunction validateMotorcyclePlate(plate: string): boolean {\n // 格式:2個英文字母+1個數字-3個數字(例如:AB1-234)\n const pattern = /^[A-Z]{2}\\d-\\d{3}$/;\n return pattern.test(plate);\n}\n\n/**\n * 偵測車牌類型\n */\nfunction detectPlateType(plate: string): LicensePlateType | null {\n if (validateElectricCarPlate(plate)) {\n return \"electric-car\";\n }\n if (validateNewCarPlate(plate)) {\n return \"car\";\n }\n if (validateOldCarPlate(plate)) {\n return \"car-old\";\n }\n if (validateSmallMotorcyclePlate(plate)) {\n return \"motorcycle-small\";\n }\n if (validateMotorcyclePlate(plate)) {\n return \"motorcycle\";\n }\n return null;\n}\n\n/**\n * 驗證台灣車牌號碼\n * @param plate - 要驗證的車牌號碼\n * @param options - 驗證選項\n * @param options.type - 可選:指定車牌類型\n * @param options.detectType - 是否偵測車牌類型(預設:true)\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * // 新制汽車\n * validateLicensePlate('ABC-1235');\n *\n * // 電動汽車\n * validateLicensePlate('EAB-1235');\n *\n * // 舊制汽車\n * validateLicensePlate('1A-2345');\n *\n * // 小型機車\n * validateLicensePlate('123-ABC');\n * validateLicensePlate('ABC-123');\n *\n * // 一般機車\n * validateLicensePlate('AB1-234');\n *\n * // 指定類型驗證\n * validateLicensePlate('ABC-1235', { type: 'car' });\n *\n * // 不偵測類型\n * validateLicensePlate('ABC-1235', { detectType: false });\n * ```\n */\nexport function validateLicensePlate(\n plate: string,\n options: { type?: LicensePlateType; detectType?: boolean } = {},\n): LicensePlateValidationResult {\n const { type, detectType = true } = options;\n\n if (!plate || typeof plate !== \"string\") {\n return {\n isValid: false,\n message: \"車牌號碼必須為非空字串\",\n };\n }\n\n // 移除空格並轉換為大寫\n const normalized = plate.trim().toUpperCase();\n\n // 如果指定了類型,只驗證該類型\n if (type) {\n let isValid = false;\n switch (type) {\n case \"car\":\n isValid = validateNewCarPlate(normalized);\n break;\n case \"car-old\":\n isValid = validateOldCarPlate(normalized);\n break;\n case \"electric-car\":\n isValid = validateElectricCarPlate(normalized);\n break;\n case \"motorcycle-small\":\n isValid = validateSmallMotorcyclePlate(normalized);\n break;\n case \"motorcycle\":\n isValid = validateMotorcyclePlate(normalized);\n break;\n }\n\n return {\n isValid,\n message: isValid ? undefined : `無效的${getPlateTypeName(type)}車牌號碼`,\n plateType: isValid && detectType ? type : undefined,\n };\n }\n\n // 自動偵測類型\n const detectedType = detectPlateType(normalized);\n\n if (!detectedType) {\n return {\n isValid: false,\n message: \"無效的車牌號碼格式\",\n };\n }\n\n return {\n isValid: true,\n plateType: detectType ? detectedType : undefined,\n };\n}\n\n/**\n * 取得車牌類型的中文名稱\n */\nfunction getPlateTypeName(type: LicensePlateType): string {\n const typeNames: Record<LicensePlateType, string> = {\n car: \"汽車\",\n \"car-old\": \"汽車(舊制)\",\n \"electric-car\": \"電動汽車\",\n \"motorcycle-small\": \"小型機車\",\n motorcycle: \"機車\",\n };\n return typeNames[type];\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taiwan-validator",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A comprehensive validator for Taiwan identification numbers and codes (身分證字號、統一編號、居留證號、手機號碼、自然人憑證、電子發票條碼)",
5
5
  "license": "MIT",
6
6
  "author": "Gary Lai <garylai1990@gmail.com>",
@@ -79,18 +79,21 @@
79
79
  "einvoice",
80
80
  "id",
81
81
  "identification",
82
+ "license-plate",
82
83
  "mobile-phone",
83
84
  "national-id",
84
85
  "resident-certificate",
85
86
  "taiwan",
86
87
  "validation",
87
88
  "validator",
89
+ "vehicle",
88
90
  "台灣",
89
91
  "居留證",
90
92
  "手機",
91
93
  "統一編號",
92
94
  "自然人憑證",
93
95
  "身分證",
96
+ "車牌",
94
97
  "電子發票",
95
98
  "驗證"
96
99
  ],
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  * - 自然人憑證
9
9
  * - 電子發票手機條碼
10
10
  * - 電子發票捐贈碼
11
+ * - 車牌號碼
11
12
  */
12
13
 
13
14
  export { validateNationalId } from "./validators/national-id";
@@ -17,6 +18,11 @@ export { validateMobilePhone } from "./validators/mobile-phone";
17
18
  export { validateCitizenCertificate } from "./validators/citizen-certificate";
18
19
  export { validateEInvoiceMobileBarcode } from "./validators/einvoice-mobile-barcode";
19
20
  export { validateEInvoiceDonationCode } from "./validators/einvoice-donation-code";
21
+ export {
22
+ validateLicensePlate,
23
+ type LicensePlateType,
24
+ type LicensePlateValidationResult,
25
+ } from "./validators/license-plate";
20
26
 
21
27
  export type {
22
28
  ValidationResult,
@@ -0,0 +1,336 @@
1
+ import { validateLicensePlate } from "./license-plate";
2
+
3
+ describe("validateLicensePlate", () => {
4
+ describe("基本驗證", () => {
5
+ test("應該拒絕空字串", () => {
6
+ const result = validateLicensePlate("");
7
+ expect(result.isValid).toBe(false);
8
+ expect(result.message).toBe("車牌號碼必須為非空字串");
9
+ });
10
+
11
+ test("應該拒絕非字串輸入", () => {
12
+ const result = validateLicensePlate(null as unknown as string);
13
+ expect(result.isValid).toBe(false);
14
+ expect(result.message).toBe("車牌號碼必須為非空字串");
15
+ });
16
+
17
+ test("應該拒絕無效的格式", () => {
18
+ const result = validateLicensePlate("INVALID");
19
+ expect(result.isValid).toBe(false);
20
+ expect(result.message).toBe("無效的車牌號碼格式");
21
+ });
22
+
23
+ test("應該處理小寫輸入並轉換為大寫", () => {
24
+ const result = validateLicensePlate("abc-1235");
25
+ expect(result.isValid).toBe(true);
26
+ expect(result.plateType).toBe("car");
27
+ });
28
+
29
+ test("應該處理含有空格的輸入", () => {
30
+ const result = validateLicensePlate(" ABC-1235 ");
31
+ expect(result.isValid).toBe(true);
32
+ expect(result.plateType).toBe("car");
33
+ });
34
+ });
35
+
36
+ describe("新制汽車車牌驗證", () => {
37
+ test("應該驗證有效的新制汽車車牌", () => {
38
+ const validPlates = [
39
+ "ABC-1235",
40
+ "XYZ-9876",
41
+ "AAA-0000",
42
+ "ZZZ-9999",
43
+ "ABC-1230",
44
+ ];
45
+
46
+ validPlates.forEach((plate) => {
47
+ const result = validateLicensePlate(plate);
48
+ expect(result.isValid).toBe(true);
49
+ expect(result.plateType).toBe("car");
50
+ });
51
+ });
52
+
53
+ test("應該拒絕包含數字4的車牌", () => {
54
+ const invalidPlates = [
55
+ "ABC-1234", // 包含4
56
+ "ABC-4567", // 包含4
57
+ "ABC-0004", // 包含4
58
+ ];
59
+
60
+ invalidPlates.forEach((plate) => {
61
+ const result = validateLicensePlate(plate);
62
+ expect(result.isValid).toBe(false);
63
+ });
64
+ });
65
+
66
+ test("應該拒絕包含字母I或O的車牌", () => {
67
+ const invalidPlates = [
68
+ "AIC-1235", // 包含I
69
+ "AOC-1235", // 包含O
70
+ "IBC-1235", // 包含I
71
+ "OBC-1235", // 包含O
72
+ ];
73
+
74
+ invalidPlates.forEach((plate) => {
75
+ const result = validateLicensePlate(plate);
76
+ expect(result.isValid).toBe(false);
77
+ });
78
+ });
79
+
80
+ test("應該拒絕格式錯誤的汽車車牌(指定類型驗證)", () => {
81
+ const invalidPlates = [
82
+ "AB-1235", // 只有2個字母
83
+ "ABCD-1235", // 4個字母
84
+ "ABC-123", // 只有3個數字(但這是有效的小型機車格式)
85
+ "ABC-12345", // 5個數字
86
+ "ABC1235", // 缺少破折號
87
+ ];
88
+
89
+ invalidPlates.forEach((plate) => {
90
+ const result = validateLicensePlate(plate, { type: "car" });
91
+ expect(result.isValid).toBe(false);
92
+ });
93
+ });
94
+
95
+ test("當指定類型為car時,應該只驗證新制汽車格式", () => {
96
+ const result = validateLicensePlate("ABC-1235", { type: "car" });
97
+ expect(result.isValid).toBe(true);
98
+ expect(result.plateType).toBe("car");
99
+ });
100
+
101
+ test("當指定類型為car但格式錯誤時,應該返回錯誤訊息", () => {
102
+ const result = validateLicensePlate("1A-2345", { type: "car" });
103
+ expect(result.isValid).toBe(false);
104
+ expect(result.message).toBe("無效的汽車車牌號碼");
105
+ });
106
+ });
107
+
108
+ describe("舊制汽車車牌驗證", () => {
109
+ test("應該驗證有效的舊制汽車車牌", () => {
110
+ const validPlates = ["1A-2345", "9Z-0000", "5B-9999", "3X-1234"];
111
+
112
+ validPlates.forEach((plate) => {
113
+ const result = validateLicensePlate(plate);
114
+ expect(result.isValid).toBe(true);
115
+ expect(result.plateType).toBe("car-old");
116
+ });
117
+ });
118
+
119
+ test("應該拒絕格式錯誤的舊制車牌", () => {
120
+ const invalidPlates = [
121
+ "AA-2345", // 2個字母
122
+ "1AB-2345", // 2個字母
123
+ "1-2345", // 缺少字母
124
+ "1A-234", // 只有3個數字
125
+ "1A-23456", // 5個數字
126
+ ];
127
+
128
+ invalidPlates.forEach((plate) => {
129
+ const result = validateLicensePlate(plate);
130
+ expect(result.isValid).toBe(false);
131
+ });
132
+ });
133
+
134
+ test("當指定類型為car-old時,應該只驗證舊制汽車格式", () => {
135
+ const result = validateLicensePlate("1A-2345", { type: "car-old" });
136
+ expect(result.isValid).toBe(true);
137
+ expect(result.plateType).toBe("car-old");
138
+ });
139
+ });
140
+
141
+ describe("電動汽車車牌驗證", () => {
142
+ test("應該驗證有效的電動汽車車牌", () => {
143
+ const validPlates = [
144
+ "EAB-1235",
145
+ "EXY-9876",
146
+ "EAA-0000",
147
+ "EZZ-9999",
148
+ "EAB-1230",
149
+ ];
150
+
151
+ validPlates.forEach((plate) => {
152
+ const result = validateLicensePlate(plate);
153
+ expect(result.isValid).toBe(true);
154
+ expect(result.plateType).toBe("electric-car");
155
+ });
156
+ });
157
+
158
+ test("應該拒絕包含數字4的電動車牌", () => {
159
+ const invalidPlates = ["EAB-1234", "EAB-4567", "EAB-0004"];
160
+
161
+ invalidPlates.forEach((plate) => {
162
+ const result = validateLicensePlate(plate);
163
+ expect(result.isValid).toBe(false);
164
+ });
165
+ });
166
+
167
+ test("應該拒絕包含字母I或O的電動車牌", () => {
168
+ const invalidPlates = [
169
+ "EIC-1235", // 包含I
170
+ "EOC-1235", // 包含O
171
+ "EIA-1235", // 包含I
172
+ "EOA-1235", // 包含O
173
+ ];
174
+
175
+ invalidPlates.forEach((plate) => {
176
+ const result = validateLicensePlate(plate);
177
+ expect(result.isValid).toBe(false);
178
+ });
179
+ });
180
+
181
+ test("應該拒絕不以E開頭的電動車牌", () => {
182
+ const invalidPlates = ["AAB-1235", "XAB-1235"];
183
+
184
+ invalidPlates.forEach((plate) => {
185
+ const result = validateLicensePlate(plate);
186
+ expect(result.plateType).not.toBe("electric-car");
187
+ });
188
+ });
189
+
190
+ test("當指定類型為electric-car時,應該只驗證電動汽車格式", () => {
191
+ const result = validateLicensePlate("EAB-1235", {
192
+ type: "electric-car",
193
+ });
194
+ expect(result.isValid).toBe(true);
195
+ expect(result.plateType).toBe("electric-car");
196
+ });
197
+ });
198
+
199
+ describe("小型機車車牌驗證", () => {
200
+ test("應該驗證有效的小型機車車牌(數字-字母格式)", () => {
201
+ const validPlates = ["123-ABC", "000-ZZZ", "999-AAA", "456-XYZ"];
202
+
203
+ validPlates.forEach((plate) => {
204
+ const result = validateLicensePlate(plate);
205
+ expect(result.isValid).toBe(true);
206
+ expect(result.plateType).toBe("motorcycle-small");
207
+ });
208
+ });
209
+
210
+ test("應該驗證有效的小型機車車牌(字母-數字格式)", () => {
211
+ const validPlates = ["ABC-123", "ZZZ-000", "AAA-999", "XYZ-456"];
212
+
213
+ validPlates.forEach((plate) => {
214
+ const result = validateLicensePlate(plate);
215
+ expect(result.isValid).toBe(true);
216
+ expect(result.plateType).toBe("motorcycle-small");
217
+ });
218
+ });
219
+
220
+ test("應該拒絕格式錯誤的小型機車車牌", () => {
221
+ const invalidPlates = [
222
+ "12-ABC", // 只有2個數字
223
+ "1234-ABC", // 4個數字
224
+ "123-AB", // 只有2個字母
225
+ "123-ABCD", // 4個字母
226
+ "AB-123", // 字母只有2個
227
+ ];
228
+
229
+ invalidPlates.forEach((plate) => {
230
+ const result = validateLicensePlate(plate);
231
+ expect(result.plateType).not.toBe("motorcycle-small");
232
+ });
233
+ });
234
+
235
+ test("當指定類型為motorcycle-small時,應該只驗證小型機車格式", () => {
236
+ const result1 = validateLicensePlate("123-ABC", {
237
+ type: "motorcycle-small",
238
+ });
239
+ expect(result1.isValid).toBe(true);
240
+ expect(result1.plateType).toBe("motorcycle-small");
241
+
242
+ const result2 = validateLicensePlate("ABC-123", {
243
+ type: "motorcycle-small",
244
+ });
245
+ expect(result2.isValid).toBe(true);
246
+ expect(result2.plateType).toBe("motorcycle-small");
247
+ });
248
+ });
249
+
250
+ describe("一般機車車牌驗證", () => {
251
+ test("應該驗證有效的一般機車車牌", () => {
252
+ const validPlates = ["AB1-234", "XY9-000", "ZZ0-999", "AA5-123"];
253
+
254
+ validPlates.forEach((plate) => {
255
+ const result = validateLicensePlate(plate);
256
+ expect(result.isValid).toBe(true);
257
+ expect(result.plateType).toBe("motorcycle");
258
+ });
259
+ });
260
+
261
+ test("應該拒絕格式錯誤的一般機車車牌", () => {
262
+ const invalidPlates = [
263
+ "A1-234", // 只有1個字母
264
+ "ABC1-234", // 3個字母
265
+ "AB-234", // 缺少數字
266
+ "AB12-234", // 2個數字
267
+ "AB1-23", // 只有2個數字
268
+ "AB1-2345", // 4個數字
269
+ ];
270
+
271
+ invalidPlates.forEach((plate) => {
272
+ const result = validateLicensePlate(plate);
273
+ expect(result.plateType).not.toBe("motorcycle");
274
+ });
275
+ });
276
+
277
+ test("當指定類型為motorcycle時,應該只驗證一般機車格式", () => {
278
+ const result = validateLicensePlate("AB1-234", { type: "motorcycle" });
279
+ expect(result.isValid).toBe(true);
280
+ expect(result.plateType).toBe("motorcycle");
281
+ });
282
+ });
283
+
284
+ describe("車牌類型偵測選項", () => {
285
+ test("當 detectType 為 false 時,不應該回傳 plateType", () => {
286
+ const result = validateLicensePlate("ABC-1235", { detectType: false });
287
+ expect(result.isValid).toBe(true);
288
+ expect(result.plateType).toBeUndefined();
289
+ });
290
+
291
+ test("當 detectType 為 true 時,應該回傳 plateType", () => {
292
+ const result = validateLicensePlate("ABC-1235", { detectType: true });
293
+ expect(result.isValid).toBe(true);
294
+ expect(result.plateType).toBe("car");
295
+ });
296
+
297
+ test("預設應該偵測車牌類型", () => {
298
+ const result = validateLicensePlate("ABC-1235");
299
+ expect(result.isValid).toBe(true);
300
+ expect(result.plateType).toBe("car");
301
+ });
302
+
303
+ test("當指定類型且 detectType 為 false 時,不應該回傳 plateType", () => {
304
+ const result = validateLicensePlate("ABC-1235", {
305
+ type: "car",
306
+ detectType: false,
307
+ });
308
+ expect(result.isValid).toBe(true);
309
+ expect(result.plateType).toBeUndefined();
310
+ });
311
+ });
312
+
313
+ describe("類型優先順序測試", () => {
314
+ test("應該優先識別為電動汽車而非一般汽車", () => {
315
+ const result = validateLicensePlate("EAB-1235");
316
+ expect(result.isValid).toBe(true);
317
+ expect(result.plateType).toBe("electric-car");
318
+ });
319
+
320
+ test("應該正確區分新制和舊制汽車", () => {
321
+ const newCar = validateLicensePlate("ABC-1235");
322
+ expect(newCar.plateType).toBe("car");
323
+
324
+ const oldCar = validateLicensePlate("1A-2345");
325
+ expect(oldCar.plateType).toBe("car-old");
326
+ });
327
+
328
+ test("應該正確區分小型機車和一般機車", () => {
329
+ const smallMotorcycle = validateLicensePlate("123-ABC");
330
+ expect(smallMotorcycle.plateType).toBe("motorcycle-small");
331
+
332
+ const motorcycle = validateLicensePlate("AB1-234");
333
+ expect(motorcycle.plateType).toBe("motorcycle");
334
+ });
335
+ });
336
+ });
@@ -0,0 +1,215 @@
1
+ import type { ValidationResult } from "../types";
2
+
3
+ /**
4
+ * 車牌類型
5
+ */
6
+ export type LicensePlateType =
7
+ | "car" // 汽車(新制)
8
+ | "car-old" // 汽車(舊制)
9
+ | "electric-car" // 電動汽車
10
+ | "motorcycle-small" // 小型機車(50cc以下)
11
+ | "motorcycle"; // 一般機車
12
+
13
+ /**
14
+ * 車牌驗證結果(含車牌類型資訊)
15
+ */
16
+ export interface LicensePlateValidationResult extends ValidationResult {
17
+ plateType?: LicensePlateType | undefined;
18
+ }
19
+
20
+ /**
21
+ * 驗證新制汽車車牌(2012年12月後)
22
+ * 格式:3個英文字母 - 4個數字
23
+ * 不使用字母 I、O
24
+ * 不使用數字 4
25
+ */
26
+ function validateNewCarPlate(plate: string): boolean {
27
+ // 格式:3個英文字母-4個數字(例如:ABC-1234)
28
+ const pattern = /^[A-HJ-NP-Z]{3}-\d{4}$/;
29
+ if (!pattern.test(plate)) {
30
+ return false;
31
+ }
32
+
33
+ // 檢查數字部分不包含 4
34
+ const numbers = plate.split("-")[1] as string;
35
+ return !numbers.includes("4");
36
+ }
37
+
38
+ /**
39
+ * 驗證舊制汽車車牌(1992-2012)
40
+ * 格式:1個數字 + 1個英文字母 - 4個數字
41
+ */
42
+ function validateOldCarPlate(plate: string): boolean {
43
+ // 格式:1個數字+1個英文字母-4個數字(例如:1A-2345)
44
+ const pattern = /^\d[A-Z]-\d{4}$/;
45
+ return pattern.test(plate);
46
+ }
47
+
48
+ /**
49
+ * 驗證電動汽車車牌
50
+ * 格式:E + 2個英文字母 - 4個數字
51
+ * 不使用字母 I、O
52
+ * 不使用數字 4
53
+ */
54
+ function validateElectricCarPlate(plate: string): boolean {
55
+ // 格式:E + 2個英文字母-4個數字(例如:EAB-1234)
56
+ const pattern = /^E[A-HJ-NP-Z]{2}-\d{4}$/;
57
+ if (!pattern.test(plate)) {
58
+ return false;
59
+ }
60
+
61
+ // 檢查數字部分不包含 4
62
+ const numbers = plate.split("-")[1] as string;
63
+ return !numbers.includes("4");
64
+ }
65
+
66
+ /**
67
+ * 驗證小型機車車牌(50cc以下)
68
+ * 格式:3個數字-3個英文字母 或 3個英文字母-3個數字
69
+ */
70
+ function validateSmallMotorcyclePlate(plate: string): boolean {
71
+ // 格式1:3個數字-3個英文字母(例如:123-ABC)
72
+ // 格式2:3個英文字母-3個數字(例如:ABC-123)
73
+ const pattern1 = /^\d{3}-[A-Z]{3}$/;
74
+ const pattern2 = /^[A-Z]{3}-\d{3}$/;
75
+ return pattern1.test(plate) || pattern2.test(plate);
76
+ }
77
+
78
+ /**
79
+ * 驗證一般機車車牌(舊制,50-250cc)
80
+ * 格式:2個英文字母 + 1個數字 - 3個數字
81
+ */
82
+ function validateMotorcyclePlate(plate: string): boolean {
83
+ // 格式:2個英文字母+1個數字-3個數字(例如:AB1-234)
84
+ const pattern = /^[A-Z]{2}\d-\d{3}$/;
85
+ return pattern.test(plate);
86
+ }
87
+
88
+ /**
89
+ * 偵測車牌類型
90
+ */
91
+ function detectPlateType(plate: string): LicensePlateType | null {
92
+ if (validateElectricCarPlate(plate)) {
93
+ return "electric-car";
94
+ }
95
+ if (validateNewCarPlate(plate)) {
96
+ return "car";
97
+ }
98
+ if (validateOldCarPlate(plate)) {
99
+ return "car-old";
100
+ }
101
+ if (validateSmallMotorcyclePlate(plate)) {
102
+ return "motorcycle-small";
103
+ }
104
+ if (validateMotorcyclePlate(plate)) {
105
+ return "motorcycle";
106
+ }
107
+ return null;
108
+ }
109
+
110
+ /**
111
+ * 驗證台灣車牌號碼
112
+ * @param plate - 要驗證的車牌號碼
113
+ * @param options - 驗證選項
114
+ * @param options.type - 可選:指定車牌類型
115
+ * @param options.detectType - 是否偵測車牌類型(預設:true)
116
+ * @returns 驗證結果
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * // 新制汽車
121
+ * validateLicensePlate('ABC-1235');
122
+ *
123
+ * // 電動汽車
124
+ * validateLicensePlate('EAB-1235');
125
+ *
126
+ * // 舊制汽車
127
+ * validateLicensePlate('1A-2345');
128
+ *
129
+ * // 小型機車
130
+ * validateLicensePlate('123-ABC');
131
+ * validateLicensePlate('ABC-123');
132
+ *
133
+ * // 一般機車
134
+ * validateLicensePlate('AB1-234');
135
+ *
136
+ * // 指定類型驗證
137
+ * validateLicensePlate('ABC-1235', { type: 'car' });
138
+ *
139
+ * // 不偵測類型
140
+ * validateLicensePlate('ABC-1235', { detectType: false });
141
+ * ```
142
+ */
143
+ export function validateLicensePlate(
144
+ plate: string,
145
+ options: { type?: LicensePlateType; detectType?: boolean } = {},
146
+ ): LicensePlateValidationResult {
147
+ const { type, detectType = true } = options;
148
+
149
+ if (!plate || typeof plate !== "string") {
150
+ return {
151
+ isValid: false,
152
+ message: "車牌號碼必須為非空字串",
153
+ };
154
+ }
155
+
156
+ // 移除空格並轉換為大寫
157
+ const normalized = plate.trim().toUpperCase();
158
+
159
+ // 如果指定了類型,只驗證該類型
160
+ if (type) {
161
+ let isValid = false;
162
+ switch (type) {
163
+ case "car":
164
+ isValid = validateNewCarPlate(normalized);
165
+ break;
166
+ case "car-old":
167
+ isValid = validateOldCarPlate(normalized);
168
+ break;
169
+ case "electric-car":
170
+ isValid = validateElectricCarPlate(normalized);
171
+ break;
172
+ case "motorcycle-small":
173
+ isValid = validateSmallMotorcyclePlate(normalized);
174
+ break;
175
+ case "motorcycle":
176
+ isValid = validateMotorcyclePlate(normalized);
177
+ break;
178
+ }
179
+
180
+ return {
181
+ isValid,
182
+ message: isValid ? undefined : `無效的${getPlateTypeName(type)}車牌號碼`,
183
+ plateType: isValid && detectType ? type : undefined,
184
+ };
185
+ }
186
+
187
+ // 自動偵測類型
188
+ const detectedType = detectPlateType(normalized);
189
+
190
+ if (!detectedType) {
191
+ return {
192
+ isValid: false,
193
+ message: "無效的車牌號碼格式",
194
+ };
195
+ }
196
+
197
+ return {
198
+ isValid: true,
199
+ plateType: detectType ? detectedType : undefined,
200
+ };
201
+ }
202
+
203
+ /**
204
+ * 取得車牌類型的中文名稱
205
+ */
206
+ function getPlateTypeName(type: LicensePlateType): string {
207
+ const typeNames: Record<LicensePlateType, string> = {
208
+ car: "汽車",
209
+ "car-old": "汽車(舊制)",
210
+ "electric-car": "電動汽車",
211
+ "motorcycle-small": "小型機車",
212
+ motorcycle: "機車",
213
+ };
214
+ return typeNames[type];
215
+ }