@burger-editor/frozen-patty 4.0.0-alpha.7 → 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.
- package/README.md +120 -495
- package/dist/get-values.js +5 -3
- package/dist/normalize-indent.d.ts +19 -0
- package/dist/normalize-indent.js +29 -0
- package/dist/set-component.js +10 -0
- package/dist/set-value.d.ts +6 -5
- package/dist/set-value.js +250 -441
- package/dist/utils.js +18 -10
- package/package.json +10 -4
- package/dist/field-name-parser.d.ts +0 -8
- package/dist/field-name-parser.js +0 -18
- package/dist/field-name-stringifier.d.ts +0 -14
- package/dist/field-name-stringifier.js +0 -26
- package/dist/get.d.ts +0 -9
- package/dist/get.js +0 -19
- package/dist/handle-picture-element.d.ts +0 -6
- package/dist/handle-picture-element.js +0 -31
- package/dist/set-comonent.d.ts +0 -10
- package/dist/set-comonent.js +0 -71
- package/dist/set.d.ts +0 -9
- package/dist/set.js +0 -60
package/README.md
CHANGED
|
@@ -1,54 +1,47 @@
|
|
|
1
|
-
# frozen-patty
|
|
1
|
+
# `@burger-editor/frozen-patty`
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/@burger-editor%2Ffrozen-patty)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**テンプレートエンジン不要**で HTML ⇄ JSON 相互変換を行う軽量ライブラリ。`data-field` 属性ベースのマッピング契約だけで動作する。
|
|
6
6
|
|
|
7
|
-
`
|
|
7
|
+
このパッケージは BurgerEditor のコンテンツ構造の核として組み込まれているが、**BurgerEditor とは独立した汎用ライブラリ**として単体利用できる。`data-field` で注釈した既存 HTML があれば、エディタやランタイムを介さず JSON 抽出・JSON マージが可能。
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Installation
|
|
10
10
|
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
+
## Usage
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
### HTML → JSON
|
|
28
|
+
|
|
29
|
+
```ts
|
|
31
30
|
import frozenPatty from '@burger-editor/frozen-patty';
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
35
|
+
frozenPatty('<a href="http://localhost" data-field="href:href">link</a>').toJSON();
|
|
36
|
+
// => { href: 'http://localhost' }
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
frozenPatty('<div data-bge="text">value</div>', { attr: 'bge' }).toJSON();
|
|
39
|
+
// => { text: 'value' }
|
|
42
40
|
```
|
|
43
41
|
|
|
44
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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>
|
|
53
|
+
frozenPatty(html)
|
|
54
|
+
.merge({
|
|
55
|
+
title: 'New Title',
|
|
56
|
+
description: 'New description',
|
|
57
|
+
link: 'https://example.com',
|
|
58
|
+
})
|
|
59
|
+
.toHTML();
|
|
145
60
|
```
|
|
146
61
|
|
|
147
|
-
|
|
62
|
+
## `data-field` 属性 DSL
|
|
148
63
|
|
|
149
|
-
|
|
64
|
+
`data-field`(または `attr` オプションで指定したカスタム属性)は、HTML ⇄ JSON のマッピング契約を定義する。
|
|
150
65
|
|
|
151
|
-
###
|
|
66
|
+
### 構文一覧
|
|
152
67
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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>` |
|
|
158
75
|
|
|
159
|
-
|
|
76
|
+
`data-field=":foo"` のショートハンドは、要素に IDL 属性として `foo` が存在しない場合に限り、`data-field-foo` 属性を参照する動作にフォールバックする。
|
|
160
77
|
|
|
161
|
-
|
|
78
|
+
### 特別な属性名
|
|
162
79
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
80
|
+
| 属性名 | セマンティクス |
|
|
81
|
+
| ------ | -------------------------------------------------------------------------------------- |
|
|
82
|
+
| `text` | 要素のテキストコンテンツ(HTML タグを除いた文字列) |
|
|
83
|
+
| `html` | 要素の `innerHTML` |
|
|
84
|
+
| `node` | 要素のタグ名。**適用時は要素そのものを置換**するため、イベントハンドラ・参照は失われる |
|
|
166
85
|
|
|
167
|
-
|
|
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,414 +92,123 @@ data-field="フィールド名1:属性名1, フィールド名2:属性名2"
|
|
|
176
92
|
</ul>
|
|
177
93
|
```
|
|
178
94
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
- データの数が既存の要素より多い場合:最初の要素をテンプレートとして新しい要素が追加されます
|
|
184
|
-
- データの数が既存の要素より少ない場合:余分な要素は削除されます
|
|
185
|
-
|
|
186
|
-
例: `{ items: ["新アイテム1", "新アイテム2", "新アイテム3"] }` というデータを適用すると、リストが3つの項目を持つように更新されます。
|
|
95
|
+
- 抽出: 同一 `data-field` 値の要素群を配列としてシリアライズ
|
|
96
|
+
- 適用: 配列長に合わせて DOM 要素数を自動増減
|
|
97
|
+
- データが多い場合: **最初の要素をテンプレート**として複製追加
|
|
98
|
+
- データが少ない場合: 余分な要素を削除
|
|
187
99
|
|
|
188
|
-
### picture要素の特別処理
|
|
100
|
+
### `picture` 要素の特別処理
|
|
189
101
|
|
|
190
|
-
`picture
|
|
102
|
+
`picture` は HTML 仕様(`source` が先、`img` が最後)と配列順序(`img` が先頭、`source` が後)が**逆**になる点に注意。
|
|
191
103
|
|
|
192
|
-
|
|
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, :
|
|
199
|
-
srcset="/
|
|
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, :
|
|
205
|
-
srcset="/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
```
|
|
133
|
+
適用時の自動変換ルール:
|
|
278
134
|
|
|
279
|
-
|
|
135
|
+
| 配列インデックス | 生成要素 | DOM 位置 | 属性変換 |
|
|
136
|
+
| ---------------- | -------- | ------------------------------ | ---------------------------------------- |
|
|
137
|
+
| `0` | `img` | DOM 末尾 | `srcset` → `src`、`sizes` 削除 |
|
|
138
|
+
| `1..n` | `source` | DOM 先頭側(配列の逆順で挿入) | `src` → `srcset`、`alt` / `loading` 削除 |
|
|
280
139
|
|
|
281
|
-
|
|
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要素)
|
|
140
|
+
`data-field` 構文も生成要素に合わせて再設定される(`img`: `path:src, …` / `source`: `path:srcset, …`)。配列要素に該当属性が存在しないスロット(例: `img` の `media`)は `null` で埋める。
|
|
326
141
|
|
|
327
|
-
|
|
142
|
+
## Options
|
|
328
143
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
### データ型の自動変換
|
|
332
|
-
|
|
333
|
-
文字列を適切なデータ型に自動変換するオプションがあります:
|
|
334
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
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' }
|
|
160
|
+
script, style, template, object, embed, iframe, frame, frameset, applet
|
|
379
161
|
```
|
|
380
162
|
|
|
381
|
-
|
|
163
|
+
**属性**:
|
|
382
164
|
|
|
383
|
-
`
|
|
165
|
+
- `on*` で始まるイベントハンドラ属性(`onclick`、`onerror` ほか)
|
|
166
|
+
- `href` / `src` / `action` などの URL 属性のうち、`javascript:` / `data:` / `vbscript:` で始まる値
|
|
384
167
|
|
|
385
|
-
|
|
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
|
-
`;
|
|
168
|
+
信頼できない外部データを扱う場合は、必ずデフォルト(`xssSanitize: true`)のまま使用すること。
|
|
393
169
|
|
|
394
|
-
|
|
395
|
-
// => { items: ['Item 1', 'Item 2', 'Item 3'] }
|
|
396
|
-
```
|
|
170
|
+
## API
|
|
397
171
|
|
|
398
|
-
|
|
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 要素で取得 |
|
|
399
178
|
|
|
400
|
-
|
|
179
|
+
## 要素再生成に関する注意
|
|
401
180
|
|
|
402
|
-
|
|
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
|
-
`;
|
|
181
|
+
`merge()` を経た `toHTML()` / `toDOM()` は**新しい要素を生成**する。元 DOM への参照および JavaScript で追加した**イベントハンドラは引き継がれない**。
|
|
418
182
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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要素の取得
|
|
183
|
+
```ts
|
|
184
|
+
const el = document.querySelector('.my-element');
|
|
185
|
+
el.addEventListener('click', handler);
|
|
442
186
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
```js
|
|
446
|
-
const element = frozenPatty(html).merge(data).toDOM();
|
|
447
|
-
// => DOM Element
|
|
187
|
+
const newHtml = frozenPatty(el.outerHTML).merge({ text: '新' }).toHTML();
|
|
188
|
+
el.outerHTML = newHtml; // ← この時点で handler は失われる
|
|
448
189
|
```
|
|
449
190
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
`frozen-patty`はデフォルトでDOM-based XSS攻撃を防止するセキュリティ機能を備えています。
|
|
191
|
+
特に `node` 属性は要素のタグそのものを置換するため影響が最も大きい。回避策:
|
|
453
192
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
193
|
+
- 適用後に要素を再取得してイベントを張り直す
|
|
194
|
+
- 親要素でのイベント委譲を採用する
|
|
195
|
+
- DOM 操作とイベント管理レイヤーを分離した設計にする
|
|
457
196
|
|
|
458
|
-
|
|
459
|
-
const fpUnsafe = frozenPatty('<div data-field="content">コンテンツ</div>', {
|
|
460
|
-
xssSanitize: false,
|
|
461
|
-
});
|
|
197
|
+
## サブパス Export
|
|
462
198
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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 型定義 |
|
|
467
206
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
// XSS対策を無効化した場合(非推奨)
|
|
473
|
-
fpUnsafe.merge(data).toHTML();
|
|
474
|
-
// 結果: <div data-field="content"><script>alert("XSS");</script><p>安全なコンテンツ</p></div>
|
|
207
|
+
```ts
|
|
208
|
+
import { getValue } from '@burger-editor/frozen-patty/get-value';
|
|
209
|
+
import { setValue } from '@burger-editor/frozen-patty/set-value';
|
|
475
210
|
```
|
|
476
211
|
|
|
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操作を分離した設計にする
|
|
540
|
-
|
|
541
|
-
## API
|
|
542
|
-
|
|
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
|
|
555
|
-
|
|
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コードを自動的に除去します |
|
|
562
|
-
|
|
563
|
-
### `merge(data)`
|
|
564
|
-
|
|
565
|
-
JSONデータをHTMLにマージします。
|
|
566
|
-
|
|
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()`
|
|
584
|
-
|
|
585
|
-
DOM要素に変換します。
|
|
586
|
-
|
|
587
|
-
## ライセンス
|
|
212
|
+
## License
|
|
588
213
|
|
|
589
|
-
MIT
|
|
214
|
+
Dual Licensed under MIT OR Apache-2.0
|