@digitalwalletcorp/utils 0.2.0 → 0.4.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.md +13 -13
- package/lib/crypto-util-DFJqfmuw.js +46 -0
- package/lib/crypto-util-DFJqfmuw.js.map +1 -0
- package/lib/crypto-util-DeZSGetI.cjs +2 -0
- package/lib/crypto-util-DeZSGetI.cjs.map +1 -0
- package/lib/crypto-util.cjs +2 -0
- package/lib/crypto-util.cjs.map +1 -0
- package/lib/crypto-util.d.cts +28 -0
- package/lib/crypto-util.d.ts +28 -0
- package/lib/crypto-util.js +7 -0
- package/lib/crypto-util.js.map +1 -0
- package/lib/date-util-CG7L8nSP.js +184 -0
- package/lib/date-util-CG7L8nSP.js.map +1 -0
- package/lib/date-util-DopNKy3u.cjs +2 -0
- package/lib/date-util-DopNKy3u.cjs.map +1 -0
- package/lib/date-util.cjs +2 -0
- package/lib/date-util.cjs.map +1 -0
- package/lib/date-util.d.cts +231 -0
- package/lib/date-util.d.ts +231 -0
- package/lib/date-util.js +23 -0
- package/lib/date-util.js.map +1 -0
- package/lib/decimal.cjs +2 -0
- package/lib/decimal.cjs.map +1 -0
- package/lib/decimal.d.cts +197 -0
- package/lib/decimal.d.ts +197 -0
- package/lib/decimal.js +227 -0
- package/lib/decimal.js.map +1 -0
- package/lib/file-util-D09BvDYY.cjs +2 -0
- package/lib/file-util-D09BvDYY.cjs.map +1 -0
- package/lib/file-util-H-c_eQcd.js +140 -0
- package/lib/file-util-H-c_eQcd.js.map +1 -0
- package/lib/file-util.cjs +2 -0
- package/lib/file-util.cjs.map +1 -0
- package/lib/file-util.d.cts +119 -0
- package/lib/file-util.d.ts +119 -0
- package/lib/file-util.js +18 -0
- package/lib/file-util.js.map +1 -0
- package/lib/html-util-Bxn5YDSK.cjs +2 -0
- package/lib/html-util-Bxn5YDSK.cjs.map +1 -0
- package/lib/html-util-CgMIuOsf.js +49 -0
- package/lib/html-util-CgMIuOsf.js.map +1 -0
- package/lib/html-util.cjs +2 -0
- package/lib/html-util.cjs.map +1 -0
- package/lib/html-util.d.cts +71 -0
- package/lib/html-util.d.ts +71 -0
- package/lib/html-util.js +8 -0
- package/lib/html-util.js.map +1 -0
- package/lib/index.cjs +1 -1
- package/lib/index.d.cts +8 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +18 -2
- package/lib/index.js.map +1 -1
- package/lib/json-util-BvrFYh5E.cjs +3 -0
- package/lib/json-util-BvrFYh5E.cjs.map +1 -0
- package/lib/json-util-YX7xSF6R.js +136 -0
- package/lib/json-util-YX7xSF6R.js.map +1 -0
- package/lib/json-util.cjs +2 -0
- package/lib/json-util.cjs.map +1 -0
- package/lib/json-util.d.cts +58 -0
- package/lib/json-util.d.ts +58 -0
- package/lib/json-util.js +8 -0
- package/lib/json-util.js.map +1 -0
- package/lib/number-util-BA4KVFyF.js +198 -0
- package/lib/number-util-BA4KVFyF.js.map +1 -0
- package/lib/number-util-BaSf1wWH.cjs +2 -0
- package/lib/number-util-BaSf1wWH.cjs.map +1 -0
- package/lib/number-util.cjs +1 -1
- package/lib/number-util.d.cts +65 -51
- package/lib/number-util.d.ts +65 -51
- package/lib/number-util.js +19 -14
- package/lib/object-util-DhGi-PmA.js +204 -0
- package/lib/object-util-DhGi-PmA.js.map +1 -0
- package/lib/object-util-Z0Ah5J8A.cjs +2 -0
- package/lib/object-util-Z0Ah5J8A.cjs.map +1 -0
- package/lib/object-util.cjs +2 -0
- package/lib/object-util.cjs.map +1 -0
- package/lib/object-util.d.cts +157 -0
- package/lib/object-util.d.ts +157 -0
- package/lib/object-util.js +16 -0
- package/lib/object-util.js.map +1 -0
- package/lib/string-util-Cw0VgbIH.js +52 -0
- package/lib/string-util-Cw0VgbIH.js.map +1 -0
- package/lib/string-util-w2I5EYuY.cjs +2 -0
- package/lib/string-util-w2I5EYuY.cjs.map +1 -0
- package/lib/string-util.cjs +2 -0
- package/lib/string-util.cjs.map +1 -0
- package/lib/string-util.d.cts +51 -0
- package/lib/string-util.d.ts +51 -0
- package/lib/string-util.js +8 -0
- package/lib/string-util.js.map +1 -0
- package/package.json +26 -8
- package/src/crypto-util.ts +84 -0
- package/src/date-util.ts +448 -0
- package/src/decimal.ts +294 -0
- package/src/file-util.ts +281 -0
- package/src/html-util.ts +196 -0
- package/src/index.ts +8 -0
- package/src/json-util.ts +271 -0
- package/src/number-util.ts +141 -194
- package/src/object-util.ts +482 -0
- package/src/string-util.ts +115 -0
- package/lib/number-util-DHq-ux31.js +0 -253
- package/lib/number-util-DHq-ux31.js.map +0 -1
- package/lib/number-util-DiMi-j7B.cjs +0 -2
- package/lib/number-util-DiMi-j7B.cjs.map +0 -1
- package/lib/reg-exp-matcher.cjs +0 -2
- package/lib/reg-exp-matcher.cjs.map +0 -1
- package/lib/reg-exp-matcher.d.cts +0 -85
- package/lib/reg-exp-matcher.d.ts +0 -85
- package/lib/reg-exp-matcher.js +0 -94
- package/lib/reg-exp-matcher.js.map +0 -1
- package/src/reg-exp-matcher.ts +0 -154
package/src/decimal.ts
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* このクラス Decimal の役割は、算術演算の実体としての Big を内部に閉じ込め、利用者側に露出させないことにある。
|
|
3
|
+
* 2026-10-08時点では big.js に依存しているが、将来的に演算演算のライブラリを変更した場合に
|
|
4
|
+
* 安全・簡単に移行できることを目的としている。
|
|
5
|
+
*/
|
|
6
|
+
import { Big } from 'big.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 数値として扱える値の型
|
|
10
|
+
*/
|
|
11
|
+
export type NumberSource = number | string | bigint;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 丸めモード
|
|
15
|
+
*/
|
|
16
|
+
export const ROUNDING_MODE = {
|
|
17
|
+
/**
|
|
18
|
+
* Rounds towards zero.
|
|
19
|
+
* I.e. truncate, no rounding.
|
|
20
|
+
*/
|
|
21
|
+
roundDown: 0,
|
|
22
|
+
/**
|
|
23
|
+
* Rounds towards nearest neighbour.
|
|
24
|
+
* If equidistant, rounds away from zero.
|
|
25
|
+
*/
|
|
26
|
+
roundHalfUp: 1,
|
|
27
|
+
/**
|
|
28
|
+
* Rounds towards nearest neighbour.
|
|
29
|
+
* If equidistant, rounds towards even neighbour.
|
|
30
|
+
*/
|
|
31
|
+
roundHalfEven: 2,
|
|
32
|
+
/**
|
|
33
|
+
* Rounds away from zero.
|
|
34
|
+
*/
|
|
35
|
+
roundUp: 3
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
export type RoundingMode = typeof ROUNDING_MODE[keyof typeof ROUNDING_MODE];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Decimalの生成・演算に渡せる値の型
|
|
42
|
+
* null/undefined/空文字/NaNは0とみなす(divの割る値は1とみなす)
|
|
43
|
+
*/
|
|
44
|
+
export type DecimalSource = NumberSource | Decimal | null | undefined;
|
|
45
|
+
|
|
46
|
+
/** 除算で保持する小数桁数 */
|
|
47
|
+
const DIVISION_SCALE = 20;
|
|
48
|
+
|
|
49
|
+
/** 数値として解釈できる文字列のパターン(符号・小数点・指数表記) */
|
|
50
|
+
const NUMERIC_STRING_PATTERN = /^[+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i;
|
|
51
|
+
|
|
52
|
+
// 独立したコンストラクタにすることで、利用側がグローバルの Big.DP / Big.RM を書き換えても影響を受けないようにする
|
|
53
|
+
const BigNumber = Big();
|
|
54
|
+
BigNumber.DP = DIVISION_SCALE;
|
|
55
|
+
BigNumber.RM = ROUNDING_MODE.roundHalfUp;
|
|
56
|
+
|
|
57
|
+
// null/undefined/空文字/NaNはtrueを返す
|
|
58
|
+
const isEmpty = (value: DecimalSource): value is null | undefined | '' => value == null || value === '' || (typeof value === 'number' && Number.isNaN(value));
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 任意精度の10進数を表す不変オブジェクト
|
|
62
|
+
* 演算メソッドは自身を変更せず、結果を新しいインスタンスで返す
|
|
63
|
+
* 除算は小数20桁(roundHalfUp)まで保持し、それ以降の桁は切り捨てではなく丸める
|
|
64
|
+
*/
|
|
65
|
+
export class Decimal {
|
|
66
|
+
/** 丸めモード */
|
|
67
|
+
public static readonly ROUNDING_MODE = ROUNDING_MODE;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 算術演算インスタンス
|
|
71
|
+
*/
|
|
72
|
+
private big: Big;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {DecimalSource} value 数値、数値文字列、bigint、Decimal
|
|
76
|
+
* null/undefined/空文字/NaNは0とみなす
|
|
77
|
+
* 数値として解釈できない文字列・Infinityは TypeError をスローする
|
|
78
|
+
*/
|
|
79
|
+
constructor(value: DecimalSource) {
|
|
80
|
+
this.big = Decimal.toBig(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private static toBig(value: DecimalSource): Big {
|
|
84
|
+
if (isEmpty(value)) {
|
|
85
|
+
return BigNumber(0);
|
|
86
|
+
}
|
|
87
|
+
if (value instanceof Decimal) {
|
|
88
|
+
return value.big;
|
|
89
|
+
}
|
|
90
|
+
switch (typeof value) {
|
|
91
|
+
case 'number':
|
|
92
|
+
if (!Number.isFinite(value)) {
|
|
93
|
+
throw new TypeError(`Invalid number: ${value}`);
|
|
94
|
+
}
|
|
95
|
+
return BigNumber(value);
|
|
96
|
+
case 'bigint':
|
|
97
|
+
return BigNumber(value.toString());
|
|
98
|
+
default: {
|
|
99
|
+
const trimmed = value.trim();
|
|
100
|
+
if (!NUMERIC_STRING_PATTERN.test(trimmed)) {
|
|
101
|
+
throw new TypeError(`Invalid number: ${value}`);
|
|
102
|
+
}
|
|
103
|
+
// 先頭の '+' は数値として解釈できる表記だが、内部の算術ライブラリが受け付けないため取り除く
|
|
104
|
+
return BigNumber(trimmed.replace(/^\+/, ''));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private static wrap(big: Big): Decimal {
|
|
110
|
+
const decimal = new Decimal(0);
|
|
111
|
+
decimal.big = big;
|
|
112
|
+
return decimal;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 加算した結果を返す
|
|
117
|
+
* null/undefined/空文字/NaNは0とみなす
|
|
118
|
+
*
|
|
119
|
+
* @param {DecimalSource} value
|
|
120
|
+
* @returns {Decimal}
|
|
121
|
+
*/
|
|
122
|
+
public add(value: DecimalSource): Decimal {
|
|
123
|
+
return Decimal.wrap(this.big.add(Decimal.toBig(value)));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 減算した結果を返す
|
|
128
|
+
* null/undefined/空文字/NaNは0とみなす
|
|
129
|
+
*
|
|
130
|
+
* @param {DecimalSource} value
|
|
131
|
+
* @returns {Decimal}
|
|
132
|
+
*/
|
|
133
|
+
public sub(value: DecimalSource): Decimal {
|
|
134
|
+
return Decimal.wrap(this.big.sub(Decimal.toBig(value)));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 乗算した結果を返す
|
|
139
|
+
* null/undefined/空文字/NaNは0とみなすため、結果は0になる
|
|
140
|
+
* 元の値を保持したい場合は times(discount ?? 1) のようにフォールバック値を指定する
|
|
141
|
+
*
|
|
142
|
+
* @param {DecimalSource} value
|
|
143
|
+
* @returns {Decimal}
|
|
144
|
+
*/
|
|
145
|
+
public times(value: DecimalSource): Decimal {
|
|
146
|
+
// 掛ける値が空のとき、0とみなすべきか元の値を保持すべきかは計算対象によって異なる
|
|
147
|
+
// 手数料 x 件数: 件数が未定義なら総額は0が適切
|
|
148
|
+
// 商品価格 x 割引率: 割引率が未定義なら元の価格が適切
|
|
149
|
+
// 1とみなすと、new Decimal(null).times(3) は 0、new Decimal(3).times(null) は 3 になり、引数の順序で結果が変わる
|
|
150
|
+
// そのため常に0とみなし、元の値を保持したい場合は呼び出し側でフォールバック値を指定する
|
|
151
|
+
return Decimal.wrap(this.big.times(Decimal.toBig(value)));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 除算した結果を返す
|
|
156
|
+
* 結果は小数20桁(roundHalfUp)まで保持する
|
|
157
|
+
* 割る値のnull/undefined/空文字/NaNは1とみなすため、結果は元の値と同じになる
|
|
158
|
+
* 割る値に0を指定した場合は RangeError をスローする
|
|
159
|
+
*
|
|
160
|
+
* @param {DecimalSource} value
|
|
161
|
+
* @returns {Decimal}
|
|
162
|
+
*/
|
|
163
|
+
public div(value: DecimalSource): Decimal {
|
|
164
|
+
// 割る値が空のとき、0とみなすとすべてゼロ除算エラーになり計算が成り立たないため1とみなす
|
|
165
|
+
if (isEmpty(value)) {
|
|
166
|
+
return Decimal.wrap(this.big);
|
|
167
|
+
}
|
|
168
|
+
const divisor = Decimal.toBig(value);
|
|
169
|
+
if (divisor.eq(0)) {
|
|
170
|
+
throw new RangeError('Division by zero');
|
|
171
|
+
}
|
|
172
|
+
return Decimal.wrap(this.big.div(divisor));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 指定した小数桁で丸めた結果を返す
|
|
177
|
+
* 負の桁数を指定すると整数部を丸める(-2 なら百の位)
|
|
178
|
+
*
|
|
179
|
+
* @param {number} [scale] 小数桁数 デフォルトは0
|
|
180
|
+
* @param {RoundingMode} [roundingMode] 丸めモード デフォルトはROUNDING_MODE.roundHalfUp(四捨五入・half-away-from-zero)
|
|
181
|
+
* @returns {Decimal}
|
|
182
|
+
*/
|
|
183
|
+
public round(scale: number = 0, roundingMode: RoundingMode = ROUNDING_MODE.roundHalfUp): Decimal {
|
|
184
|
+
return Decimal.wrap(this.big.round(scale, roundingMode));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 絶対値を返す
|
|
189
|
+
*
|
|
190
|
+
* @returns {Decimal}
|
|
191
|
+
*/
|
|
192
|
+
public abs(): Decimal {
|
|
193
|
+
return Decimal.wrap(this.big.abs());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 値を比較する
|
|
198
|
+
* null/undefined/空文字/NaNは0とみなす
|
|
199
|
+
*
|
|
200
|
+
* @param {DecimalSource} value
|
|
201
|
+
* @returns {-1 | 0 | 1} 自身が小さければ-1、等しければ0、大きければ1
|
|
202
|
+
*/
|
|
203
|
+
public compareTo(value: DecimalSource): -1 | 0 | 1 {
|
|
204
|
+
return this.big.cmp(Decimal.toBig(value));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 等しいか判定する
|
|
209
|
+
* null/undefined/空文字/NaNは0とみなす
|
|
210
|
+
*
|
|
211
|
+
* @param {DecimalSource} value
|
|
212
|
+
* @returns {boolean}
|
|
213
|
+
*/
|
|
214
|
+
public eq(value: DecimalSource): boolean {
|
|
215
|
+
return this.compareTo(value) === 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 自身が小さいか判定する
|
|
220
|
+
* null/undefined/空文字/NaNは0とみなす
|
|
221
|
+
*
|
|
222
|
+
* @param {DecimalSource} value
|
|
223
|
+
* @returns {boolean}
|
|
224
|
+
*/
|
|
225
|
+
public lt(value: DecimalSource): boolean {
|
|
226
|
+
return this.compareTo(value) < 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 自身が小さいか等しいか判定する
|
|
231
|
+
* null/undefined/空文字/NaNは0とみなす
|
|
232
|
+
*
|
|
233
|
+
* @param {DecimalSource} value
|
|
234
|
+
* @returns {boolean}
|
|
235
|
+
*/
|
|
236
|
+
public lte(value: DecimalSource): boolean {
|
|
237
|
+
return this.compareTo(value) <= 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 自身が大きいか判定する
|
|
242
|
+
* null/undefined/空文字/NaNは0とみなす
|
|
243
|
+
*
|
|
244
|
+
* @param {DecimalSource} value
|
|
245
|
+
* @returns {boolean}
|
|
246
|
+
*/
|
|
247
|
+
public gt(value: DecimalSource): boolean {
|
|
248
|
+
return 0 < this.compareTo(value);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 自身が大きいか等しいか判定する
|
|
253
|
+
* null/undefined/空文字/NaNは0とみなす
|
|
254
|
+
*
|
|
255
|
+
* @param {DecimalSource} value
|
|
256
|
+
* @returns {boolean}
|
|
257
|
+
*/
|
|
258
|
+
public gte(value: DecimalSource): boolean {
|
|
259
|
+
return 0 <= this.compareTo(value);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* numberに変換する
|
|
264
|
+
* Number.MAX_SAFE_INTEGERを超える値や、numberで表現できない桁数の小数は精度が落ちる
|
|
265
|
+
*
|
|
266
|
+
* @returns {number}
|
|
267
|
+
*/
|
|
268
|
+
public toNumber(): number {
|
|
269
|
+
return this.big.toNumber();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 指定した小数桁で丸め、小数桁を0で埋めた文字列に変換する
|
|
274
|
+
* 指数表記にはならず、通常の表記で返す
|
|
275
|
+
* 丸めた結果が0になる場合は符号を付けない
|
|
276
|
+
*
|
|
277
|
+
* @param {number} scale 小数桁数
|
|
278
|
+
* @param {RoundingMode} [roundingMode] 丸めモード デフォルトはROUNDING_MODE.roundHalfUp(四捨五入・half-away-from-zero)
|
|
279
|
+
* @returns {string}
|
|
280
|
+
*/
|
|
281
|
+
public toFixed(scale: number, roundingMode: RoundingMode = ROUNDING_MODE.roundHalfUp): string {
|
|
282
|
+
return this.big.round(scale, roundingMode).toFixed(scale);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 文字列に変換する
|
|
287
|
+
* 指数表記にはならず、通常の表記で返す
|
|
288
|
+
*
|
|
289
|
+
* @returns {string}
|
|
290
|
+
*/
|
|
291
|
+
public toString(): string {
|
|
292
|
+
return this.big.toFixed();
|
|
293
|
+
}
|
|
294
|
+
}
|
package/src/file-util.ts
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/** 先頭バイトの署名から判定したファイルの種類 */
|
|
2
|
+
export interface FileType {
|
|
3
|
+
/** 拡張子(ドットなし) */
|
|
4
|
+
ext: string;
|
|
5
|
+
/** MIMEタイプ */
|
|
6
|
+
mime: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** バイナリとして受け付ける値。Node.js の Buffer も ArrayBufferView として渡せる */
|
|
10
|
+
export type BinaryInput = ArrayBuffer | ArrayBufferView;
|
|
11
|
+
|
|
12
|
+
const DATA_URL_PATTERN = /^data:([^;,]+)?(?:;[^,]*)?;base64,(.*)$/s;
|
|
13
|
+
|
|
14
|
+
const DEFAULT_MIME_TYPE = 'application/octet-stream';
|
|
15
|
+
|
|
16
|
+
/** 種類の判定に使う先頭の base64 の文字数。ZIP の中のエントリ名を探すため数KB分を見る */
|
|
17
|
+
const SIGNATURE_BASE64_LENGTH = 16384;
|
|
18
|
+
|
|
19
|
+
const SVG_PATTERN = /^\ufeff?\s*(?:<\?xml[^>]*>\s*)?(?:<!--[\s\S]*?-->\s*)*(?:<!DOCTYPE[^>]*>\s*)?<svg[\s>]/i;
|
|
20
|
+
|
|
21
|
+
const OFFICE_FILE_TYPES: { entry: string; type: FileType; }[] = [
|
|
22
|
+
{ entry: 'word/', type: { ext: 'docx', mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' } },
|
|
23
|
+
{ entry: 'xl/', type: { ext: 'xlsx', mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } },
|
|
24
|
+
{ entry: 'ppt/', type: { ext: 'pptx', mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' } }
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const toBytes = (data: BinaryInput): Uint8Array => {
|
|
28
|
+
return data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const toArrayBuffer = (data: BinaryInput): ArrayBuffer => toBytes(data).slice().buffer as ArrayBuffer;
|
|
32
|
+
|
|
33
|
+
const startsWith = (bytes: Uint8Array, signature: number[] | string, offset: number = 0): boolean => {
|
|
34
|
+
const expected = typeof signature === 'string' ? Array.from(signature, c => c.charCodeAt(0)) : signature;
|
|
35
|
+
if (bytes.length < offset + expected.length) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return expected.every((value, i) => bytes[offset + i] === value);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const detectFileType = (bytes: Uint8Array): FileType | null => {
|
|
42
|
+
if (startsWith(bytes, [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) {
|
|
43
|
+
return { ext: 'png', mime: 'image/png' };
|
|
44
|
+
}
|
|
45
|
+
if (startsWith(bytes, [0xFF, 0xD8, 0xFF])) {
|
|
46
|
+
return { ext: 'jpg', mime: 'image/jpeg' };
|
|
47
|
+
}
|
|
48
|
+
if (startsWith(bytes, 'GIF87a') || startsWith(bytes, 'GIF89a')) {
|
|
49
|
+
return { ext: 'gif', mime: 'image/gif' };
|
|
50
|
+
}
|
|
51
|
+
if (startsWith(bytes, 'RIFF') && startsWith(bytes, 'WEBP', 8)) {
|
|
52
|
+
return { ext: 'webp', mime: 'image/webp' };
|
|
53
|
+
}
|
|
54
|
+
if (startsWith(bytes, 'BM')) {
|
|
55
|
+
return { ext: 'bmp', mime: 'image/bmp' };
|
|
56
|
+
}
|
|
57
|
+
if (startsWith(bytes, [0x49, 0x49, 0x2A, 0x00]) || startsWith(bytes, [0x4D, 0x4D, 0x00, 0x2A])) {
|
|
58
|
+
return { ext: 'tif', mime: 'image/tiff' };
|
|
59
|
+
}
|
|
60
|
+
if (startsWith(bytes, '%PDF-')) {
|
|
61
|
+
return { ext: 'pdf', mime: 'application/pdf' };
|
|
62
|
+
}
|
|
63
|
+
if (startsWith(bytes, [0x50, 0x4B, 0x03, 0x04]) || startsWith(bytes, [0x50, 0x4B, 0x05, 0x06]) || startsWith(bytes, [0x50, 0x4B, 0x07, 0x08])) {
|
|
64
|
+
// Office Open XML も ZIP なので、ZIP に含まれるエントリ名で見分ける
|
|
65
|
+
const text = new TextDecoder('latin1').decode(bytes);
|
|
66
|
+
const office = OFFICE_FILE_TYPES.find(a => text.includes(a.entry));
|
|
67
|
+
return office ? office.type : { ext: 'zip', mime: 'application/zip' };
|
|
68
|
+
}
|
|
69
|
+
if (SVG_PATTERN.test(new TextDecoder().decode(bytes))) {
|
|
70
|
+
return { ext: 'svg', mime: 'image/svg+xml' };
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* base64 データと MIME タイプから DataURL を生成する
|
|
77
|
+
*
|
|
78
|
+
* @param {string} base64
|
|
79
|
+
* @param {string} [mimeType] 省略時は 'application/octet-stream'
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
export function createDataURL(base64: string, mimeType?: string): string {
|
|
83
|
+
return `data:${mimeType || DEFAULT_MIME_TYPE};base64,${base64}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* DataURL を base64 データと MIME タイプに分解する
|
|
88
|
+
* MIME タイプは DataURL に書かれた値ではなく、データの先頭バイトから判定した値を返す。判定できない場合は DataURL に書かれた値、それもなければ 'application/octet-stream'
|
|
89
|
+
* DataURL 形式でない文字列は base64 データとみなす
|
|
90
|
+
*
|
|
91
|
+
* @param {string} dataURL
|
|
92
|
+
* @returns {[string, string]} [base64, mimeType]
|
|
93
|
+
*/
|
|
94
|
+
export function parseDataURL(dataURL: string): [string, string] {
|
|
95
|
+
const matcher = DATA_URL_PATTERN.exec(dataURL);
|
|
96
|
+
const base64 = matcher ? matcher[2] : dataURL;
|
|
97
|
+
return [base64, analyzeFileType(base64)?.mime ?? matcher?.[1] ?? DEFAULT_MIME_TYPE];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* データを読み込み DataURL を返す
|
|
102
|
+
* 文字列は、DataURL 形式ならそのまま、そうでなければ base64 データとみなす
|
|
103
|
+
* Blob は Blob の type を、それがなければ先頭バイトから判定した MIME タイプを使う
|
|
104
|
+
*
|
|
105
|
+
* @param {string | Blob | BinaryInput} data
|
|
106
|
+
* @returns {Promise<string>}
|
|
107
|
+
*/
|
|
108
|
+
export async function readAsDataURL(data: string | Blob | BinaryInput): Promise<string> {
|
|
109
|
+
if (typeof data === 'string') {
|
|
110
|
+
return DATA_URL_PATTERN.test(data) ? data : base64ToDataURL(data);
|
|
111
|
+
}
|
|
112
|
+
if (data instanceof Blob) {
|
|
113
|
+
const buffer = await data.arrayBuffer();
|
|
114
|
+
return arrayBufferToDataURL(buffer, data.type || analyzeFileType(buffer)?.mime);
|
|
115
|
+
}
|
|
116
|
+
return arrayBufferToDataURL(data, analyzeFileType(data)?.mime);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* データを読み込み ArrayBuffer を返す
|
|
121
|
+
* 文字列は、DataURL 形式ならその base64 部分を、そうでなければ base64 データとみなして変換する
|
|
122
|
+
*
|
|
123
|
+
* @param {string | Blob | BinaryInput} data
|
|
124
|
+
* @returns {Promise<ArrayBuffer>}
|
|
125
|
+
*/
|
|
126
|
+
export async function readAsArrayBuffer(data: string | Blob | BinaryInput): Promise<ArrayBuffer> {
|
|
127
|
+
if (typeof data === 'string') {
|
|
128
|
+
return dataURLToArrayBuffer(data);
|
|
129
|
+
}
|
|
130
|
+
if (data instanceof Blob) {
|
|
131
|
+
return data.arrayBuffer();
|
|
132
|
+
}
|
|
133
|
+
return toArrayBuffer(data);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* base64 データを DataURL に変換する
|
|
138
|
+
* MIME タイプを省略した場合はデータの先頭バイトから判定する
|
|
139
|
+
*
|
|
140
|
+
* @param {string} base64
|
|
141
|
+
* @param {string} [mimeType]
|
|
142
|
+
* @returns {string} base64 が空の場合はそのまま返す
|
|
143
|
+
*/
|
|
144
|
+
export function base64ToDataURL(base64: string, mimeType?: string): string {
|
|
145
|
+
if (!base64) {
|
|
146
|
+
return base64;
|
|
147
|
+
}
|
|
148
|
+
return createDataURL(base64, mimeType || analyzeFileType(base64)?.mime);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* base64 データを ArrayBuffer に変換する
|
|
153
|
+
*
|
|
154
|
+
* @param {string} base64
|
|
155
|
+
* @returns {ArrayBuffer}
|
|
156
|
+
*/
|
|
157
|
+
export function base64ToArrayBuffer(base64: string): ArrayBuffer {
|
|
158
|
+
const binary = atob(base64);
|
|
159
|
+
const bytes = new Uint8Array(binary.length);
|
|
160
|
+
for (let i = 0; i < binary.length; i++) {
|
|
161
|
+
bytes[i] = binary.charCodeAt(i);
|
|
162
|
+
}
|
|
163
|
+
return bytes.buffer;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* DataURL から base64 データを取り出す
|
|
168
|
+
*
|
|
169
|
+
* @param {string} dataURL
|
|
170
|
+
* @returns {string} DataURL 形式でない場合はそのまま返す
|
|
171
|
+
*/
|
|
172
|
+
export function dataURLToBase64(dataURL: string): string {
|
|
173
|
+
return DATA_URL_PATTERN.exec(dataURL)?.[2] ?? dataURL;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* DataURL を ArrayBuffer に変換する
|
|
178
|
+
*
|
|
179
|
+
* @param {string} dataURL
|
|
180
|
+
* @returns {ArrayBuffer}
|
|
181
|
+
*/
|
|
182
|
+
export function dataURLToArrayBuffer(dataURL: string): ArrayBuffer {
|
|
183
|
+
return base64ToArrayBuffer(dataURLToBase64(dataURL));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* バイナリを base64 データに変換する
|
|
188
|
+
*
|
|
189
|
+
* @param {BinaryInput} data
|
|
190
|
+
* @returns {string}
|
|
191
|
+
*/
|
|
192
|
+
export function arrayBufferToBase64(data: BinaryInput): string {
|
|
193
|
+
const bytes = toBytes(data);
|
|
194
|
+
let binary = '';
|
|
195
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
196
|
+
binary += String.fromCharCode(bytes[i]);
|
|
197
|
+
}
|
|
198
|
+
return btoa(binary);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* バイナリを DataURL に変換する
|
|
203
|
+
*
|
|
204
|
+
* @param {BinaryInput} data
|
|
205
|
+
* @param {string} [mimeType] 省略時は 'application/octet-stream'
|
|
206
|
+
* @returns {string}
|
|
207
|
+
*/
|
|
208
|
+
export function arrayBufferToDataURL(data: BinaryInput, mimeType?: string): string {
|
|
209
|
+
return createDataURL(arrayBufferToBase64(data), mimeType);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* base64 データまたは DataURL が表すデータのバイト数を返す
|
|
214
|
+
*
|
|
215
|
+
* @param {string} data
|
|
216
|
+
* @returns {number}
|
|
217
|
+
*/
|
|
218
|
+
export function calcSize(data: string): number {
|
|
219
|
+
return dataURLToArrayBuffer(data).byteLength;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 文字列またはバイナリを、BOM 付き UTF-16 の CSV の Blob にする
|
|
224
|
+
* バイナリはすでに UTF-16LE で符号化されているものとして BOM だけを付ける
|
|
225
|
+
*
|
|
226
|
+
* @param {string | BinaryInput} data
|
|
227
|
+
* @returns {Blob}
|
|
228
|
+
*/
|
|
229
|
+
export function convertUTF16withBOM(data: string | BinaryInput): Blob {
|
|
230
|
+
if (!data) {
|
|
231
|
+
return new Blob([]);
|
|
232
|
+
}
|
|
233
|
+
let parts: BlobPart[];
|
|
234
|
+
if (typeof data === 'string') {
|
|
235
|
+
const text = `\ufeff${data}`;
|
|
236
|
+
const codes = new Uint16Array(text.length);
|
|
237
|
+
for (let i = 0; i < text.length; i++) {
|
|
238
|
+
codes[i] = text.charCodeAt(i);
|
|
239
|
+
}
|
|
240
|
+
parts = [codes];
|
|
241
|
+
} else {
|
|
242
|
+
// 文字列で BOM を渡すと Blob が UTF-8 で符号化してしまうため、UTF-16LE の BOM をバイト列で付ける
|
|
243
|
+
parts = [new Uint8Array([0xFF, 0xFE]), toArrayBuffer(data)];
|
|
244
|
+
}
|
|
245
|
+
return new Blob(parts, { type: 'text/csv;charset=utf-16;' });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* データの先頭バイトの署名からファイルの種類を判定する
|
|
250
|
+
* 判定できる種類: PNG / JPEG / GIF / WebP / BMP / TIFF / SVG / PDF / ZIP / XLSX / DOCX / PPTX
|
|
251
|
+
* 文字列は、DataURL 形式ならその base64 部分を、そうでなければ base64 データとみなす
|
|
252
|
+
*
|
|
253
|
+
* @param {string | BinaryInput} data
|
|
254
|
+
* @returns {FileType | null} 判定できない場合は null
|
|
255
|
+
*/
|
|
256
|
+
export function analyzeFileType(data: string | BinaryInput): FileType | null {
|
|
257
|
+
let bytes: Uint8Array;
|
|
258
|
+
if (typeof data === 'string') {
|
|
259
|
+
const base64 = dataURLToBase64(data).slice(0, SIGNATURE_BASE64_LENGTH);
|
|
260
|
+
try {
|
|
261
|
+
// 判定に必要な先頭だけを復号する。4文字単位で切らないと末尾がパディング不正になる
|
|
262
|
+
bytes = toBytes(base64ToArrayBuffer(base64.slice(0, base64.length - base64.length % 4)));
|
|
263
|
+
} catch (error) {
|
|
264
|
+
// base64 として復号できない文字列は判定できない
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
bytes = toBytes(data);
|
|
269
|
+
}
|
|
270
|
+
return detectFileType(bytes);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* ファイル名に使えない文字(\ / : * ? " < > |)を取り除く
|
|
275
|
+
*
|
|
276
|
+
* @param {string} name
|
|
277
|
+
* @returns {string}
|
|
278
|
+
*/
|
|
279
|
+
export function sanitizeFileName(name: string): string {
|
|
280
|
+
return name.replace(/[\\/:*?"<>|]/g, '');
|
|
281
|
+
}
|