@burger-editor/frozen-patty 4.0.0-alpha.70 → 4.0.0-alpha.71

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