@burger-editor/frozen-patty 4.0.0-alpha.3 → 4.0.0-alpha.5
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 +548 -21
- package/dist/field-name-stringifier.d.ts +14 -0
- package/dist/field-name-stringifier.js +26 -0
- package/dist/frozen-patty.d.ts +7 -0
- package/dist/frozen-patty.js +18 -5
- package/dist/get-component.d.ts +9 -0
- package/dist/get-component.js +29 -0
- package/dist/get-values.js +50 -31
- package/dist/handle-picture-element.d.ts +6 -0
- package/dist/handle-picture-element.js +31 -0
- package/dist/parse-fields.d.ts +6 -0
- package/dist/parse-fields.js +38 -0
- package/dist/set-comonent.d.ts +10 -0
- package/dist/set-comonent.js +71 -0
- package/dist/set-component.d.ts +10 -0
- package/dist/set-component.js +81 -0
- package/dist/set-value.d.ts +4 -2
- package/dist/set-value.js +93 -66
- package/dist/stringify-fields.d.ts +14 -0
- package/dist/stringify-fields.js +26 -0
- package/dist/types.d.ts +7 -0
- package/dist/utils.d.ts +72 -5
- package/dist/utils.js +246 -6
- package/package.json +7 -5
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -2,65 +2,592 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/@burger-editor%2Ffrozen-patty)
|
|
4
4
|
|
|
5
|
-
Pure HTML to JSON converter that not use template engine.
|
|
5
|
+
Pure HTML to JSON converter that does not use a template engine.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`frozen-patty`は、HTMLとJSONデータの間でシームレスな変換を行う軽量ライブラリです。テンプレートエンジンを必要とせず、HTMLからデータを抽出したり、データをHTMLに適用したりすることができます。BurgerEditorのコンテンツデータとHTML構造を担保するためのコア機能として開発されました。
|
|
8
|
+
|
|
9
|
+
## 特徴
|
|
10
|
+
|
|
11
|
+
- 🔄 HTMLからJSONへの抽出とJSONからHTMLへの適用
|
|
12
|
+
- 🪶 軽量で依存関係が少ない
|
|
13
|
+
- 🌟 テンプレートエンジン不要
|
|
14
|
+
- 📝 カスタム属性名のサポート
|
|
15
|
+
- 🧩 データ型の自動変換オプション
|
|
16
|
+
- 🔧 高度なカスタマイズが可能
|
|
17
|
+
- 📋 複数のフィールドと配列データの対応
|
|
18
|
+
- 🛡️ XSS対策機能
|
|
19
|
+
|
|
20
|
+
## インストール
|
|
8
21
|
|
|
9
22
|
```
|
|
10
23
|
$ npm install -D @burger-editor/frozen-patty
|
|
11
24
|
```
|
|
12
25
|
|
|
13
|
-
##
|
|
26
|
+
## 基本的な使い方
|
|
14
27
|
|
|
15
|
-
###
|
|
28
|
+
### データの抽出 (HTML → JSON)
|
|
16
29
|
|
|
17
30
|
```js
|
|
18
31
|
import frozenPatty from '@burger-editor/frozen-patty';
|
|
19
32
|
|
|
33
|
+
// 基本的な使い方
|
|
20
34
|
frozenPatty('<div data-field="text">value</div>').toJSON(); // => { text: 'value' }
|
|
21
35
|
frozenPatty('<div data-field="field-name">value</div>').toJSON(); // => { 'field-name': 'value' }
|
|
36
|
+
|
|
37
|
+
// 属性値の抽出
|
|
22
38
|
frozenPatty('<a href="http://localhost" data-field="href:href">link</a>').toJSON(); // => { 'href': 'http://localhost' }
|
|
23
39
|
|
|
40
|
+
// カスタム属性名の使用
|
|
24
41
|
frozenPatty('<div data-bge="text">value</div>', { attr: 'bge' }).toJSON(); // => { text: 'value' }
|
|
25
42
|
```
|
|
26
43
|
|
|
27
|
-
###
|
|
44
|
+
### データの適用 (JSON → HTML)
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
// 既存のHTMLにデータをマージする
|
|
48
|
+
frozenPatty('<div data-field="text">value</div>').merge({ text: 'merged' }).toHTML();
|
|
49
|
+
// => "<div data-field="text">merged</div>";
|
|
50
|
+
|
|
51
|
+
// 複数のフィールドを持つHTMLにデータをマージする
|
|
52
|
+
const html = `
|
|
53
|
+
<div>
|
|
54
|
+
<h1 data-field="title">Old Title</h1>
|
|
55
|
+
<p data-field="description">Old description</p>
|
|
56
|
+
<a data-field="link:href" href="#">Click here</a>
|
|
57
|
+
</div>
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const data = {
|
|
61
|
+
title: 'New Title',
|
|
62
|
+
description: 'Updated description',
|
|
63
|
+
link: 'https://example.com',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
frozenPatty(html).merge(data).toHTML();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
各フィールドが更新されたHTMLが返されます
|
|
70
|
+
|
|
71
|
+
結果のHTML:
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<div>
|
|
75
|
+
<h1 data-field="title">New Title</h1>
|
|
76
|
+
<p data-field="description">Updated description</p>
|
|
77
|
+
<a data-field="link:href" href="https://example.com">Click here</a>
|
|
78
|
+
</div>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## data-field属性のシンタックス
|
|
82
|
+
|
|
83
|
+
`data-field`属性(またはカスタム属性名)は、HTMLからデータを抽出する際のマッピングと、JSONデータをHTMLに適用する際の対象を定義します。
|
|
84
|
+
|
|
85
|
+
### 基本形式
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
data-field="フィールド名"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**抽出時**:要素の内容を「フィールド名」というキーでJSONに抽出します。
|
|
92
|
+
|
|
93
|
+
- input、select、textareaなどのフォーム要素の場合は`value`プロパティ
|
|
94
|
+
- その他の要素の場合はHTMLを含む`innerHTML`の内容
|
|
95
|
+
|
|
96
|
+
**適用時**:「フィールド名」に対応するJSONの値が要素の内容に反映されます。
|
|
97
|
+
|
|
98
|
+
- input、select、textareaなどのフォーム要素の場合は`value`プロパティに反映
|
|
99
|
+
- その他の要素の場合は`innerHTML`に反映(HTMLタグも解釈される)
|
|
100
|
+
- `innerHTML`に反映されるHTMLは`xssSanitize`オプションに依存し適切にサニタイズされます
|
|
101
|
+
|
|
102
|
+
### 属性値の指定
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
data-field="フィールド名:属性名"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**抽出時**:要素の指定された属性の値を「フィールド名」というキーでJSONに抽出します。
|
|
109
|
+
例: `data-field="href:href"`は`href`属性の値をJSONの`href`キーに抽出します。
|
|
110
|
+
|
|
111
|
+
**適用時**:「フィールド名」に対応するJSONの値が要素の指定された属性に反映されます。
|
|
112
|
+
例: `data-field="href:href"`でJSONに`{ href: "https://example.com" }`がある場合、要素の`href`属性が更新されます。
|
|
113
|
+
|
|
114
|
+
特別な属性名:
|
|
115
|
+
|
|
116
|
+
- `text` - 要素のテキストコンテンツを取得/設定(HTMLタグを除いたコンテンツ)
|
|
117
|
+
- `html` - 要素のHTML内容を取得/設定(`innerHTML`と同じ)
|
|
118
|
+
- `node` - 要素名(タグ名)を取得/設定(**注意**: 設定時は要素そのものを置き換える強力な操作のため、イベントハンドラや参照は失われます)
|
|
119
|
+
|
|
120
|
+
### 属性名の省略
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
data-field=":属性名"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**抽出時**:フィールド名を省略すると、属性名自体がフィールド名として使用されます。
|
|
127
|
+
例: `data-field=":href"`は`href`属性の値をJSONの`href`キーに抽出します。
|
|
128
|
+
|
|
129
|
+
**適用時**:属性名と同じキーを持つJSONの値が、対応する属性に反映されます。
|
|
130
|
+
例: `data-field=":href"`でJSONに`{ href: "https://example.com" }`がある場合、要素の`href`属性が更新されます。
|
|
131
|
+
|
|
132
|
+
#### data-field-\* ショートハンド
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
data-field=":foo"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`:foo`のような指定の場合、要素に`foo`属性があるか探しにいきますが、要素に属性(正確にはIDL属性として)が存在しない場合、且つ要素に`data-field-foo`属性があれば、`data-field-foo`と見做されます。
|
|
139
|
+
|
|
140
|
+
**抽出時**:`:foo`のように指定すると、元の`data-field-foo`属性から値を抽出します。
|
|
141
|
+
例: `data-field=":foo"`は`data-field-foo`属性の値をJSONの`foo`キーに抽出します。
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<div data-field=":foo" data-field-foo="bar"></div>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
これは `{ foo: "bar" }` というJSONに変換されます。
|
|
148
|
+
|
|
149
|
+
**適用時**:フィールド名と同じ名前の`data-field-*`属性に値が反映されます。
|
|
150
|
+
|
|
151
|
+
### 複数のフィールド指定
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
data-field="フィールド名1:属性名1, フィールド名2:属性名2"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**抽出時**:同じ要素から複数のデータを抽出します。カンマで区切ることで複数のフィールドを指定できます。
|
|
158
|
+
|
|
159
|
+
**適用時**:各フィールド名に対応するJSONの値が、それぞれ指定された属性または内容に反映されます。
|
|
160
|
+
|
|
161
|
+
例:
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<a href="/url" title="ヒント" data-field="link:href, tooltip:title">リンクテキスト</a>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
これは `{ link: "/url", tooltip: "ヒント" }` というJSONに変換されます。
|
|
168
|
+
逆に、`{ link: "/new-url", tooltip: "新しいヒント" }` というJSONを適用すると、hrefとtitle属性がそれぞれ更新されます。
|
|
169
|
+
|
|
170
|
+
### 配列データの指定
|
|
171
|
+
|
|
172
|
+
```html
|
|
173
|
+
<ul data-field-list>
|
|
174
|
+
<li data-field="items">Item 1</li>
|
|
175
|
+
<li data-field="items">Item 2</li>
|
|
176
|
+
</ul>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**抽出時**:同じ`data-field`値を持つ複数の要素からデータが配列として抽出されます。
|
|
180
|
+
|
|
181
|
+
**適用時**:配列データが対応する要素に反映されます。
|
|
182
|
+
|
|
183
|
+
- データの数が既存の要素より多い場合:最初の要素をテンプレートとして新しい要素が追加されます
|
|
184
|
+
- データの数が既存の要素より少ない場合:余分な要素は削除されます
|
|
185
|
+
|
|
186
|
+
例: `{ items: ["新アイテム1", "新アイテム2", "新アイテム3"] }` というデータを適用すると、リストが3つの項目を持つように更新されます。
|
|
187
|
+
|
|
188
|
+
### picture要素の特別処理
|
|
189
|
+
|
|
190
|
+
`picture`要素は、レスポンシブ画像を実現するためのHTML要素で、`frozen-patty`では特別な処理を行っています:
|
|
191
|
+
|
|
192
|
+
**抽出時**:HTMLから次のようなpicture要素がある場合を考えます。
|
|
193
|
+
|
|
194
|
+
```html
|
|
195
|
+
<!-- 元のHTML構造(HTML仕様に従い、source要素が先、img要素が後) -->
|
|
196
|
+
<picture data-field-list>
|
|
197
|
+
<source
|
|
198
|
+
data-field="path:srcset, :width, :height, :media"
|
|
199
|
+
srcset="/path/to/large.jpg"
|
|
200
|
+
width="1200"
|
|
201
|
+
height="900"
|
|
202
|
+
media="(min-width: 1000px)" />
|
|
203
|
+
<source
|
|
204
|
+
data-field="path:srcset, :width, :height, :media"
|
|
205
|
+
srcset="/path/to/medium.jpg"
|
|
206
|
+
width="800"
|
|
207
|
+
height="600"
|
|
208
|
+
media="(min-width: 600px)" />
|
|
209
|
+
<img
|
|
210
|
+
data-field="path:src, :alt, :width, :height"
|
|
211
|
+
src="/path/to/default.jpg"
|
|
212
|
+
alt="代替テキスト"
|
|
213
|
+
width="400"
|
|
214
|
+
height="300" />
|
|
215
|
+
</picture>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
このHTMLから抽出する際に、以下の特別な考慮がされます:
|
|
219
|
+
|
|
220
|
+
1. 配列データの順序:
|
|
221
|
+
|
|
222
|
+
- HTML内の`picture`要素では一般的に`source`要素が先に配置され、`img`要素が最後に配置されます
|
|
223
|
+
- 抽出されるデータの配列では逆順になり、img要素のデータが先、source要素のデータが後
|
|
224
|
+
|
|
225
|
+
つまり、DOM内の配置順序(source要素が先、img要素が後)と、配列データ内の順序(img要素のデータが先、source要素のデータが後)は逆になります。
|
|
226
|
+
|
|
227
|
+
上記のHTMLを変換すると、以下のようなJSONが生成されます:
|
|
28
228
|
|
|
29
229
|
```js
|
|
30
|
-
|
|
230
|
+
// HTMLからJSONへの変換
|
|
231
|
+
const htmlString = document.querySelector('picture').outerHTML;
|
|
232
|
+
const extractedData = frozenPatty(htmlString).toJSON();
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
抽出結果:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"path": [
|
|
240
|
+
"/path/to/default.jpg", // インデックス0: img要素のsrc
|
|
241
|
+
"/path/to/medium.jpg", // インデックス1: 1つ目のsource要素のsrcset
|
|
242
|
+
"/path/to/large.jpg" // インデックス2: 2つ目のsource要素のsrcset
|
|
243
|
+
],
|
|
244
|
+
"alt": ["代替テキスト"], // 変換機構との一貫性のため配列
|
|
245
|
+
"width": [400, 800, 1200],
|
|
246
|
+
"height": [300, 600, 900],
|
|
247
|
+
"media": [
|
|
248
|
+
null, // 変換機構との一貫性のため配列となり、img要素にはmedia属性を持たないためnull固定
|
|
249
|
+
"(min-width: 600px)",
|
|
250
|
+
"(min-width: 1000px)"
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`picture`要素内の各要素(`source`や`img`)は内部的にリストとして扱われ、それぞれの属性(`srcset`, `width`, `media`など)も対応するインデックスを持つ配列としてデータが格納されます。そのため、`img`要素にしか通常存在しない`alt`属性や、`source`要素にしか存在しない`media`属性も、このデータ構造に合わせて**配列形式で表現されます**(対応する要素がない場合はnullが入ります)。
|
|
256
|
+
|
|
257
|
+
**適用時**:JSONデータをHTMLに適用する際も特別な処理が行われます。例えば以下のJSONデータを適用すると:
|
|
258
|
+
|
|
259
|
+
```js
|
|
260
|
+
// 適用するJSONデータ
|
|
261
|
+
const data = {
|
|
262
|
+
path: [
|
|
263
|
+
'/path/to/default.jpg', // インデックス0:img要素のsrc属性に設定(DOM内では最後)
|
|
264
|
+
'/path/to/medium.jpg', // インデックス1:source要素のsrcset属性に設定
|
|
265
|
+
'/path/to/large.jpg', // インデックス2:source要素のsrcset属性に設定
|
|
266
|
+
'/path/to/x-large.jpg', // インデックス3:source要素のsrcset属性に設定(DOM内では最初)
|
|
267
|
+
],
|
|
268
|
+
alt: ['レスポンシブ画像の説明'], // 抽出機構との互換性のためにimg要素でしか扱わないが配列として指定
|
|
269
|
+
width: [400, 800, 1200, 1600],
|
|
270
|
+
height: [300, 600, 900, 1200],
|
|
271
|
+
media: [
|
|
272
|
+
null, // img要素に対応するインデックスなのでnullを指定
|
|
273
|
+
'(min-width: 600px)', // source要素のmedia属性
|
|
274
|
+
'(min-width: 1000px)', // source要素のmedia属性
|
|
275
|
+
'(min-width: 1400px)', // source要素のmedia属性
|
|
276
|
+
],
|
|
277
|
+
};
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
データを適用した結果、以下のようなHTMLが生成されます
|
|
281
|
+
|
|
282
|
+
```html
|
|
283
|
+
<picture data-field-list>
|
|
284
|
+
<source
|
|
285
|
+
data-field="path:srcset, :width, :height, :media"
|
|
286
|
+
srcset="/path/to/x-large.jpg"
|
|
287
|
+
width="1600"
|
|
288
|
+
height="1200"
|
|
289
|
+
media="(min-width: 1400px)" />
|
|
290
|
+
<source
|
|
291
|
+
data-field="path:srcset, :width, :height, :media"
|
|
292
|
+
srcset="/path/to/large.jpg"
|
|
293
|
+
width="1200"
|
|
294
|
+
height="900"
|
|
295
|
+
media="(min-width: 1000px)" />
|
|
296
|
+
<source
|
|
297
|
+
data-field="path:srcset, :width, :height, :media"
|
|
298
|
+
srcset="/path/to/medium.jpg"
|
|
299
|
+
width="800"
|
|
300
|
+
height="600"
|
|
301
|
+
media="(min-width: 600px)" />
|
|
302
|
+
<img
|
|
303
|
+
data-field="path:src, :alt, :width, :height"
|
|
304
|
+
src="/path/to/default.jpg"
|
|
305
|
+
alt="レスポンシブ画像の説明"
|
|
306
|
+
width="400"
|
|
307
|
+
height="300" />
|
|
308
|
+
</picture>
|
|
31
309
|
```
|
|
32
310
|
|
|
311
|
+
適用時には以下の特別処理が行われます:
|
|
312
|
+
|
|
313
|
+
1. 要素の配置と変換:
|
|
314
|
+
|
|
315
|
+
- 配列の最初の要素(インデックス0)は`img`要素に変換され、DOMでは最後に配置されます
|
|
316
|
+
- 配列の2番目以降の要素は`source`要素に変換され、配列の逆順にDOMに挿入されます(配列の末尾から先頭方向に)
|
|
317
|
+
|
|
318
|
+
2. 属性の自動変換:
|
|
319
|
+
|
|
320
|
+
- `img`要素:`srcset`属性は`src`属性に変換され、`sizes`属性は削除されます
|
|
321
|
+
- `source`要素:`src`属性は`srcset`属性に変換され、`alt`属性と`loading`属性は削除されます
|
|
322
|
+
- `data-field`属性のシンタックスは、それぞれの要素に対応する属性名に再設定されます
|
|
323
|
+
- `img`要素: `data-field="path:src, :alt, :width, :height"`
|
|
324
|
+
- `source`要素: `data-field="path:srcset, :width, :height, :media"`
|
|
325
|
+
|
|
326
|
+
3. 順序の反転:
|
|
327
|
+
- 配列データ内の順序:[0, 1, 2, 3]
|
|
328
|
+
- 生成されるHTML内の順序:[3, 2, 1, 0](0がimg要素)
|
|
329
|
+
|
|
330
|
+
この特別処理により、HTML標準に準拠した`picture`要素が正しく生成され、ブラウザのレスポンシブ画像機能が適切に動作します。
|
|
331
|
+
|
|
332
|
+
## 高度な使い方
|
|
333
|
+
|
|
334
|
+
### データ型の自動変換
|
|
335
|
+
|
|
336
|
+
文字列を適切なデータ型に自動変換するオプションがあります:
|
|
337
|
+
|
|
338
|
+
```js
|
|
339
|
+
const html = `
|
|
340
|
+
<div data-field="isActive">true</div>
|
|
341
|
+
<div data-field="count">5</div>
|
|
342
|
+
<div data-field="price">10.5</div>
|
|
343
|
+
`;
|
|
344
|
+
|
|
345
|
+
const data = frozenPatty(html, {
|
|
346
|
+
typeConvert: true,
|
|
347
|
+
}).toJSON();
|
|
348
|
+
// => { isActive: true, count: 5, price: 10.5 }
|
|
349
|
+
// 文字列ではなく、適切な型に変換されます
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### 値フィルタの使用
|
|
353
|
+
|
|
354
|
+
抽出または適用時に値をカスタマイズするフィルタ関数を提供できます:
|
|
355
|
+
|
|
356
|
+
```js
|
|
357
|
+
const valueFilter = (value) => {
|
|
358
|
+
if (typeof value === 'string') {
|
|
359
|
+
return value.toUpperCase();
|
|
360
|
+
}
|
|
361
|
+
return value;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const html = '<div data-field="text">hello world</div>';
|
|
365
|
+
const data = frozenPatty(html, {
|
|
366
|
+
valueFilter,
|
|
367
|
+
}).toJSON();
|
|
368
|
+
// => { text: 'HELLO WORLD' }
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
これは`merge()`と`toJSON()`どちらの場合でも適用されますが、`toJSON`だけは`toJSON(false)`とすると無効化できます。
|
|
372
|
+
|
|
373
|
+
### 複数の属性と値を同時に扱う
|
|
374
|
+
|
|
375
|
+
同じ要素から複数の属性や値を抽出することができます:
|
|
376
|
+
|
|
377
|
+
```js
|
|
378
|
+
const html =
|
|
379
|
+
'<div data-attr="attr-value" data-field="text, attr:data-attr">text-value</div>';
|
|
380
|
+
frozenPatty(html).toJSON();
|
|
381
|
+
// => { text: 'text-value', attr: 'attr-value' }
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### リスト(配列)データの取得
|
|
385
|
+
|
|
386
|
+
`data-field-list`属性を使用して、同じフィールド名の要素から配列データを抽出できます:
|
|
387
|
+
|
|
388
|
+
```js
|
|
389
|
+
const html = `
|
|
390
|
+
<ul data-field-list>
|
|
391
|
+
<li data-field="items">Item 1</li>
|
|
392
|
+
<li data-field="items">Item 2</li>
|
|
393
|
+
<li data-field="items">Item 3</li>
|
|
394
|
+
</ul>
|
|
395
|
+
`;
|
|
396
|
+
|
|
397
|
+
frozenPatty(html).toJSON();
|
|
398
|
+
// => { items: ['Item 1', 'Item 2', 'Item 3'] }
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### 複合的なデータの抽出と適用
|
|
402
|
+
|
|
403
|
+
複雑なHTMLから構造化されたデータを抽出する例:
|
|
404
|
+
|
|
405
|
+
```js
|
|
406
|
+
const html = `
|
|
407
|
+
<div>
|
|
408
|
+
<div data-field="title">商品タイトル</div>
|
|
409
|
+
<div data-field="content"><span>HTML内容</span></div>
|
|
410
|
+
<div data-field="meta:data-custom" data-custom="メタデータ"></div>
|
|
411
|
+
<ul data-field-list>
|
|
412
|
+
<li data-field="tags">タグ1</li>
|
|
413
|
+
<li data-field="tags">タグ2</li>
|
|
414
|
+
</ul>
|
|
415
|
+
<ul data-field-list>
|
|
416
|
+
<li><a data-field="links:href, linkTexts" href="/page1">リンク1</a></li>
|
|
417
|
+
<li><a data-field="links:href, linkTexts" href="/page2">リンク2</a></li>
|
|
418
|
+
</ul>
|
|
419
|
+
</div>
|
|
420
|
+
`;
|
|
421
|
+
|
|
422
|
+
const data = frozenPatty(html).toJSON();
|
|
423
|
+
// => {
|
|
424
|
+
// title: '商品タイトル',
|
|
425
|
+
// content: '<span>HTML内容</span>',
|
|
426
|
+
// meta: 'メタデータ',
|
|
427
|
+
// tags: ['タグ1', 'タグ2'],
|
|
428
|
+
// links: ['/page1', '/page2'],
|
|
429
|
+
// linkTexts: ['リンク1', 'リンク2']
|
|
430
|
+
// }
|
|
431
|
+
|
|
432
|
+
// データを更新してHTMLを生成
|
|
433
|
+
const updatedData = {
|
|
434
|
+
title: '新しいタイトル',
|
|
435
|
+
tags: ['新タグ1', '新タグ2', '新タグ3'], // 要素が増えても対応
|
|
436
|
+
links: ['/new1', '/new2'],
|
|
437
|
+
linkTexts: ['新リンク1', '新リンク2'],
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
const updatedHtml = frozenPatty(html).merge(updatedData).toHTML();
|
|
441
|
+
// => 更新されたHTMLが返されます
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### DOM要素の取得
|
|
445
|
+
|
|
446
|
+
HTML文字列ではなくDOM要素として結果を取得することもできます:
|
|
447
|
+
|
|
448
|
+
```js
|
|
449
|
+
const element = frozenPatty(html).merge(data).toDOM();
|
|
450
|
+
// => DOM Element
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### XSS対策機能
|
|
454
|
+
|
|
455
|
+
`frozen-patty`はデフォルトでDOM-based XSS攻撃を防止するセキュリティ機能を備えています。
|
|
456
|
+
|
|
457
|
+
```js
|
|
458
|
+
// デフォルトでXSS対策が有効
|
|
459
|
+
const fp = frozenPatty('<div data-field="content">コンテンツ</div>');
|
|
460
|
+
|
|
461
|
+
// XSS対策を無効化したい場合
|
|
462
|
+
const fpUnsafe = frozenPatty('<div data-field="content">コンテンツ</div>', {
|
|
463
|
+
xssSanitize: false,
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// 危険なHTMLを含むデータを適用する例
|
|
467
|
+
const data = {
|
|
468
|
+
content: '<script>alert("XSS");</script><p>安全なコンテンツ</p>',
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// 安全に処理(scriptタグは削除される)
|
|
472
|
+
fp.merge(data).toHTML();
|
|
473
|
+
// 結果: <div data-field="content"><p>安全なコンテンツ</p></div>
|
|
474
|
+
|
|
475
|
+
// XSS対策を無効化した場合(非推奨)
|
|
476
|
+
fpUnsafe.merge(data).toHTML();
|
|
477
|
+
// 結果: <div data-field="content"><script>alert("XSS");</script><p>安全なコンテンツ</p></div>
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
以下のセキュリティ対策が実装されています:
|
|
481
|
+
|
|
482
|
+
1. 危険な要素の削除:
|
|
483
|
+
|
|
484
|
+
- `script`, `style`, `template`, `object`, `embed`, `iframe`, `frame`, `frameset`, `applet`
|
|
485
|
+
|
|
486
|
+
2. 危険な属性の削除:
|
|
487
|
+
- on\*で始まるイベントハンドラ属性
|
|
488
|
+
- javascript:, data:, vbscript: で始まるURLを受け取る`href`、`src`、`action`などの属性
|
|
489
|
+
|
|
490
|
+
信頼できない外部データを扱う場合は、必ずデフォルト設定(XSS対策有効)を使用してください。
|
|
491
|
+
|
|
492
|
+
### 要素の再生成と参照の扱いに関する注意事項
|
|
493
|
+
|
|
494
|
+
`frozen-patty`はデータ適用時に新しい要素を生成します。これにより以下の点に注意が必要です:
|
|
495
|
+
|
|
496
|
+
```js
|
|
497
|
+
// 元のHTML要素
|
|
498
|
+
const containerEl = document.querySelector('.container');
|
|
499
|
+
const myElement = containerEl.querySelector('.my-element');
|
|
500
|
+
|
|
501
|
+
// JavaScriptで要素に対してイベントハンドラを追加
|
|
502
|
+
myElement.addEventListener('click', () => {
|
|
503
|
+
console.log('クリックされました');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// frozenPattyでデータを適用
|
|
507
|
+
const htmlString = myElement.outerHTML;
|
|
508
|
+
const newHtml = frozenPatty(htmlString).merge({ text: '新しいテキスト' }).toHTML();
|
|
509
|
+
|
|
510
|
+
// 新しいHTML文字列で要素を更新する方法1: innerHTML + replaceChild
|
|
511
|
+
const tempDiv = document.createElement('div');
|
|
512
|
+
tempDiv.innerHTML = newHtml;
|
|
513
|
+
containerEl.replaceChild(tempDiv.firstElementChild, myElement);
|
|
514
|
+
|
|
515
|
+
// ⚠️ 警告: この時点でイベントハンドラは失われています
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
または、insertAdjacentHTMLを使う方法もあります:
|
|
519
|
+
|
|
520
|
+
```js
|
|
521
|
+
// 方法2: insertAdjacentHTML + remove
|
|
522
|
+
myElement.insertAdjacentHTML('afterend', newHtml);
|
|
523
|
+
const newElement = myElement.nextElementSibling;
|
|
524
|
+
myElement.remove();
|
|
525
|
+
|
|
526
|
+
// こちらの方法でも同様にイベントハンドラは失われます
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
この要素の再生成は`toHTML`だけでなく`toDOM`を使用したときにも同様です。
|
|
530
|
+
|
|
531
|
+
特に以下の点に注意してください:
|
|
532
|
+
|
|
533
|
+
1. **イベントハンドラの喪失**: JavaScript で動的に追加したイベントハンドラは、HTML操作(`outerHTML`/`innerHTML`)では保持されません
|
|
534
|
+
|
|
535
|
+
2. **オブジェクト参照の更新**: 元の要素への参照(変数)は、新しい要素を生成した後も自動的に更新されません
|
|
536
|
+
|
|
537
|
+
3. **nodeディレクティブの影響**: `node`ディレクティブは要素そのものを置き換えるため、変更が特に大きくなります
|
|
538
|
+
|
|
539
|
+
これらの問題を回避するには、以下の方法があります:
|
|
540
|
+
|
|
541
|
+
- HTML操作後に再度要素を取得し、イベントハンドラを再設定する
|
|
542
|
+
- イベントの委譲(親要素でのイベント管理)を利用する
|
|
543
|
+
- イベント管理とHTML操作を分離した設計にする
|
|
544
|
+
|
|
33
545
|
## API
|
|
34
546
|
|
|
35
547
|
### `frozenPatty(html[, options])`
|
|
36
548
|
|
|
37
|
-
|
|
549
|
+
HTMLからデータを抽出するメインの関数です。
|
|
38
550
|
|
|
39
551
|
#### Parameters
|
|
40
552
|
|
|
41
|
-
| parameter | type | required | descriptions
|
|
42
|
-
| --------- | -------- | -------- |
|
|
43
|
-
| html | `string` | required |
|
|
44
|
-
| options | `Object` | optional |
|
|
553
|
+
| parameter | type | required | descriptions |
|
|
554
|
+
| --------- | -------- | -------- | -------------------------- |
|
|
555
|
+
| html | `string` | required | 元のHTML文字列 |
|
|
556
|
+
| options | `Object` | optional | オプション設定(以下参照) |
|
|
45
557
|
|
|
46
|
-
#### Options
|
|
558
|
+
#### Options
|
|
47
559
|
|
|
48
|
-
| options
|
|
49
|
-
|
|
|
50
|
-
| attr
|
|
560
|
+
| options | type | default | descriptions |
|
|
561
|
+
| ----------- | ------------------------------- | --------- | ------------------------------------------------------------------------------- |
|
|
562
|
+
| attr | string | `"field"` | フィールドとして扱うノードを指定するための**データ属性**名 |
|
|
563
|
+
| typeConvert | boolean | `false` | データ属性の値の自動型変換を有効にします("true"→true, "5"→5, "10.5"→10.5など) |
|
|
564
|
+
| valueFilter | Function (`<T>(value: T) => T`) | - | 値を処理するカスタムフィルタ関数 |
|
|
565
|
+
| xssSanitize | boolean | `true` | XSS対策機能を有効にします。危険なHTMLコードを自動的に除去します |
|
|
51
566
|
|
|
52
567
|
### `merge(data)`
|
|
53
568
|
|
|
54
|
-
|
|
569
|
+
JSONデータをHTMLにマージします。
|
|
55
570
|
|
|
56
571
|
| args | type | required | descriptions |
|
|
57
572
|
| ---- | -------- | -------- | ------------ |
|
|
58
|
-
| data | `Object` | required |
|
|
573
|
+
| data | `Object` | required | 新しいデータ |
|
|
574
|
+
|
|
575
|
+
### `toJSON([filtering])`
|
|
576
|
+
|
|
577
|
+
HTMLからJSONデータに変換します。
|
|
578
|
+
|
|
579
|
+
| args | type | default | descriptions |
|
|
580
|
+
| --------- | --------- | ------- | ------------------------------------------------------------------------- |
|
|
581
|
+
| filtering | `boolean` | `true` | `true`の場合、コンストラクタで指定した`valueFilter`オプションを適用します |
|
|
582
|
+
|
|
583
|
+
### `toHTML()`
|
|
584
|
+
|
|
585
|
+
HTML文字列に変換します。
|
|
59
586
|
|
|
60
|
-
### `
|
|
587
|
+
### `toDOM()`
|
|
61
588
|
|
|
62
|
-
|
|
589
|
+
DOM要素に変換します。
|
|
63
590
|
|
|
64
|
-
|
|
591
|
+
## ライセンス
|
|
65
592
|
|
|
66
|
-
|
|
593
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FieldDefinition } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* フィールド名と属性名からフィールド定義文字列を生成します
|
|
4
|
+
* @param fieldName フィールド名
|
|
5
|
+
* @param propName 属性名(オプション)
|
|
6
|
+
* @returns フィールド定義文字列
|
|
7
|
+
*/
|
|
8
|
+
export declare function fieldNameStringify(fieldName: string, propName?: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* 複数のフィールド定義をカンマ区切りで結合します
|
|
11
|
+
* @param fields フィールド定義の配列
|
|
12
|
+
* @returns カンマ区切りのフィールド定義文字列
|
|
13
|
+
*/
|
|
14
|
+
export declare function fieldsStringify(fields: readonly FieldDefinition[]): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* フィールド名と属性名からフィールド定義文字列を生成します
|
|
3
|
+
* @param fieldName フィールド名
|
|
4
|
+
* @param propName 属性名(オプション)
|
|
5
|
+
* @returns フィールド定義文字列
|
|
6
|
+
*/
|
|
7
|
+
export function fieldNameStringify(fieldName, propName) {
|
|
8
|
+
if (!propName) {
|
|
9
|
+
return fieldName;
|
|
10
|
+
}
|
|
11
|
+
// fieldNameとpropNameが同じ場合は省略記法を使用
|
|
12
|
+
if (fieldName === propName) {
|
|
13
|
+
return `:${propName}`;
|
|
14
|
+
}
|
|
15
|
+
return `${fieldName}:${propName}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 複数のフィールド定義をカンマ区切りで結合します
|
|
19
|
+
* @param fields フィールド定義の配列
|
|
20
|
+
* @returns カンマ区切りのフィールド定義文字列
|
|
21
|
+
*/
|
|
22
|
+
export function fieldsStringify(fields) {
|
|
23
|
+
return fields
|
|
24
|
+
.map((field) => fieldNameStringify(field.fieldName, field.propName))
|
|
25
|
+
.join(', ');
|
|
26
|
+
}
|
package/dist/frozen-patty.d.ts
CHANGED
|
@@ -34,4 +34,11 @@ export interface FrozenPattyOptions {
|
|
|
34
34
|
* Value filter
|
|
35
35
|
*/
|
|
36
36
|
valueFilter?: Filter;
|
|
37
|
+
/**
|
|
38
|
+
* Enable XSS protection
|
|
39
|
+
* - When enabled, removes potentially malicious code that may be contained in HTML
|
|
40
|
+
* - Prevents script elements, dangerous event handler attributes, javascript protocol, etc.
|
|
41
|
+
* @default `true`
|
|
42
|
+
*/
|
|
43
|
+
xssSanitize?: boolean;
|
|
37
44
|
}
|