@xrplkit/xls26 2.4.0 → 2.5.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/package.json +1 -1
- package/readme.md +44 -54
- package/xls26.js +160 -101
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -22,29 +22,27 @@ console.log(xls26Data)
|
|
|
22
22
|
|
|
23
23
|
```toml
|
|
24
24
|
[[ISSUERS]]
|
|
25
|
-
address = "
|
|
26
|
-
name = "
|
|
27
|
-
|
|
28
|
-
[[ISSUERS.WEBLINKS]]
|
|
29
|
-
url = "https://aesthetes.art"
|
|
30
|
-
type = "info"
|
|
31
|
-
title = "Official Website"
|
|
32
|
-
|
|
33
|
-
[[ISSUERS.WEBLINKS]]
|
|
34
|
-
url = "https://twitter.com/aesthetes_art"
|
|
35
|
-
type = "socialmedia"
|
|
25
|
+
address = "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De"
|
|
26
|
+
name = "Ripple"
|
|
27
|
+
desc = "We're building the Internet of Value."
|
|
36
28
|
|
|
37
29
|
[[TOKENS]]
|
|
38
|
-
issuer = "
|
|
39
|
-
currency = "
|
|
40
|
-
name = "
|
|
41
|
-
desc = "
|
|
42
|
-
icon = "https://
|
|
43
|
-
asset_class = "
|
|
30
|
+
issuer = "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De"
|
|
31
|
+
currency = "RLUSD"
|
|
32
|
+
name = "Ripple USD"
|
|
33
|
+
desc = "Ripple USD (RLUSD) is natively issued on the XRP Ledger and Ethereum blockchains and is enabled with a number of features to ensure strict adherence to compliance standards, flexibility for developers, and security for holders."
|
|
34
|
+
icon = "https://ripple.com/assets/rlusd-logo.png"
|
|
35
|
+
asset_class = "rwa"
|
|
36
|
+
asset_subclass = "stablecoin"
|
|
37
|
+
|
|
38
|
+
[[TOKENS.URLS]]
|
|
39
|
+
url = "https://ripple.com"
|
|
40
|
+
type = "website"
|
|
41
|
+
title = "Official Website"
|
|
44
42
|
|
|
45
|
-
[[TOKENS.
|
|
46
|
-
url = "https://
|
|
47
|
-
type = "
|
|
43
|
+
[[TOKENS.URLS]]
|
|
44
|
+
url = "https://x.com/ripple"
|
|
45
|
+
type = "social"
|
|
48
46
|
```
|
|
49
47
|
|
|
50
48
|
|
|
@@ -52,40 +50,32 @@ type = "community"
|
|
|
52
50
|
|
|
53
51
|
```javascript
|
|
54
52
|
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
tokens: [
|
|
53
|
+
issuers: [
|
|
54
|
+
{
|
|
55
|
+
address: 'rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De',
|
|
56
|
+
name: 'Ripple',
|
|
57
|
+
desc: "We're building the Internet of Value."
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
tokens: [
|
|
61
|
+
{
|
|
62
|
+
currency: 'RLUSD',
|
|
63
|
+
issuer: 'rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De',
|
|
64
|
+
name: 'Ripple USD',
|
|
65
|
+
desc: 'Ripple USD (RLUSD) is natively issued on the XRP Ledger and Ethereum blockchains and is enabled with a number of features to ensure strict adherence to compliance standards, flexibility for developers, and security for holders.',
|
|
66
|
+
icon: 'https://ripple.com/assets/rlusd-logo.png',
|
|
67
|
+
asset_class: 'rwa',
|
|
68
|
+
asset_subclass: 'stablecoin',
|
|
69
|
+
urls: [
|
|
73
70
|
{
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
type: 'community'
|
|
84
|
-
}
|
|
85
|
-
]
|
|
86
|
-
}
|
|
87
|
-
],
|
|
88
|
-
issues: []
|
|
71
|
+
url: 'https://ripple.com',
|
|
72
|
+
type: 'website',
|
|
73
|
+
title: 'Official Website'
|
|
74
|
+
},
|
|
75
|
+
{ url: 'https://x.com/ripple', type: 'social' }
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
issues: []
|
|
89
80
|
}
|
|
90
|
-
|
|
91
81
|
```
|
package/xls26.js
CHANGED
|
@@ -1,19 +1,50 @@
|
|
|
1
1
|
// The XLS-26 standard adds additional asset metadata fields to the existing xrp-ledger.toml standard,
|
|
2
2
|
// https://github.com/XRPLF/XRPL-Standards/discussions/71
|
|
3
3
|
// This package provides an implementation for a parser according to this standard.
|
|
4
|
+
// Version 5 from 2025-06-06.
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
import { parse as parseToml } from '@xrplkit/toml'
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
9
|
+
const validUrlRegex = /^(https?)|(ipfs):\/\/.*$/
|
|
10
|
+
const validUrlTypes = {
|
|
11
|
+
website: 'website',
|
|
12
|
+
social: 'social',
|
|
13
|
+
docs: 'docs',
|
|
14
|
+
other: 'other',
|
|
15
|
+
info: 'website',
|
|
16
|
+
socialmedia: 'social',
|
|
17
|
+
community: 'social',
|
|
18
|
+
support: 'website',
|
|
19
|
+
whitepaper: 'docs',
|
|
20
|
+
certificate: 'docs',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const validAssetClasses = [
|
|
24
|
+
'rwa',
|
|
25
|
+
'memes',
|
|
26
|
+
'wrapped',
|
|
27
|
+
'gaming',
|
|
28
|
+
'defi',
|
|
29
|
+
'other'
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
const validAssetSubClasses = [
|
|
33
|
+
'stablecoin',
|
|
34
|
+
'commodity',
|
|
35
|
+
'real_estate',
|
|
36
|
+
'private_credit',
|
|
37
|
+
'equity',
|
|
38
|
+
'treasury',
|
|
39
|
+
'other'
|
|
15
40
|
]
|
|
16
41
|
|
|
42
|
+
const legacyAssetClasses = {
|
|
43
|
+
fiat: { asset_class: 'rwa', asset_subclass: 'stablecoin' },
|
|
44
|
+
commodity: { asset_class: 'rwa', asset_subclass: 'commodity' },
|
|
45
|
+
equity: { asset_class: 'rwa', asset_subclass: 'equity' }
|
|
46
|
+
}
|
|
47
|
+
|
|
17
48
|
const validAdvisoryTypes = [
|
|
18
49
|
'scam',
|
|
19
50
|
'spam',
|
|
@@ -22,17 +53,10 @@ const validAdvisoryTypes = [
|
|
|
22
53
|
'hijacked'
|
|
23
54
|
]
|
|
24
55
|
|
|
25
|
-
const validAssetClasses = [
|
|
26
|
-
'fiat',
|
|
27
|
-
'commodity',
|
|
28
|
-
'equity',
|
|
29
|
-
'cryptocurrency'
|
|
30
|
-
]
|
|
31
|
-
|
|
32
56
|
const issuerFields = [
|
|
33
57
|
{
|
|
34
58
|
key: 'address',
|
|
35
|
-
|
|
59
|
+
required: true,
|
|
36
60
|
validate: v => {
|
|
37
61
|
if(!/^[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{25,35}$/.test(v))
|
|
38
62
|
throw 'is not a valid XRPL address'
|
|
@@ -42,40 +66,40 @@ const issuerFields = [
|
|
|
42
66
|
key: 'name',
|
|
43
67
|
validate: v => {
|
|
44
68
|
if(typeof v !== 'string' || v.length === 0)
|
|
45
|
-
throw '
|
|
69
|
+
throw 'must be a non empty string'
|
|
46
70
|
}
|
|
47
71
|
},
|
|
48
72
|
{
|
|
49
|
-
key: '
|
|
50
|
-
alternativeKeys: ['
|
|
73
|
+
key: 'desc',
|
|
74
|
+
alternativeKeys: ['description'],
|
|
51
75
|
validate: v => {
|
|
52
76
|
if(typeof v !== 'string' || v.length === 0)
|
|
53
|
-
throw '
|
|
77
|
+
throw 'must be a non empty string'
|
|
54
78
|
}
|
|
55
79
|
},
|
|
56
80
|
{
|
|
57
81
|
key: 'domain',
|
|
58
82
|
validate: v => {
|
|
59
83
|
if(typeof v !== 'string' || v.length === 0)
|
|
60
|
-
throw '
|
|
84
|
+
throw 'must be a non empty string'
|
|
61
85
|
}
|
|
62
86
|
},
|
|
63
87
|
{
|
|
64
88
|
key: 'icon',
|
|
65
89
|
alternativeKeys: ['avatar'],
|
|
66
90
|
validate: v => {
|
|
67
|
-
if(
|
|
68
|
-
throw '
|
|
91
|
+
if(!validUrlRegex.test(v))
|
|
92
|
+
throw 'must be a valid URL that starts with "http" or "ipfs"'
|
|
69
93
|
}
|
|
70
94
|
},
|
|
71
95
|
{
|
|
72
96
|
key: 'trust_level',
|
|
73
97
|
validate: v => {
|
|
74
98
|
if(v !== parseInt(v))
|
|
75
|
-
throw '
|
|
99
|
+
throw 'must be a integer'
|
|
76
100
|
|
|
77
101
|
if(v < 0 || v > 3)
|
|
78
|
-
throw '
|
|
102
|
+
throw 'must be between 0 and 3'
|
|
79
103
|
}
|
|
80
104
|
}
|
|
81
105
|
]
|
|
@@ -84,7 +108,7 @@ const tokenFields = [
|
|
|
84
108
|
{
|
|
85
109
|
key: 'currency',
|
|
86
110
|
alternativeKeys: ['code'],
|
|
87
|
-
|
|
111
|
+
required: true,
|
|
88
112
|
validate: v => {
|
|
89
113
|
if(typeof v !== 'string' && v.length < 3)
|
|
90
114
|
throw 'is not a valid XRPL currency code'
|
|
@@ -92,7 +116,7 @@ const tokenFields = [
|
|
|
92
116
|
},
|
|
93
117
|
{
|
|
94
118
|
key: 'issuer',
|
|
95
|
-
|
|
119
|
+
required: true,
|
|
96
120
|
validate: v => {
|
|
97
121
|
if(!/^[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{25,35}$/.test(v))
|
|
98
122
|
throw 'is not a valid XRPL address'
|
|
@@ -102,65 +126,75 @@ const tokenFields = [
|
|
|
102
126
|
key: 'name',
|
|
103
127
|
validate: v => {
|
|
104
128
|
if(typeof v !== 'string' || v.length === 0)
|
|
105
|
-
throw '
|
|
129
|
+
throw 'must be a non empty string'
|
|
106
130
|
}
|
|
107
131
|
},
|
|
108
132
|
{
|
|
109
|
-
key: '
|
|
110
|
-
alternativeKeys: ['
|
|
133
|
+
key: 'desc',
|
|
134
|
+
alternativeKeys: ['description'],
|
|
111
135
|
validate: v => {
|
|
112
136
|
if(typeof v !== 'string' || v.length === 0)
|
|
113
|
-
throw '
|
|
137
|
+
throw 'must be a non empty string'
|
|
114
138
|
}
|
|
115
139
|
},
|
|
116
140
|
{
|
|
117
141
|
key: 'icon',
|
|
118
142
|
alternativeKeys: ['avatar'],
|
|
119
143
|
validate: v => {
|
|
120
|
-
if(
|
|
121
|
-
throw '
|
|
144
|
+
if(!validUrlRegex.test(v))
|
|
145
|
+
throw 'must be a valid URL starting with "http" or "ipfs"'
|
|
122
146
|
}
|
|
123
147
|
},
|
|
124
148
|
{
|
|
125
149
|
key: 'trust_level',
|
|
126
150
|
validate: v => {
|
|
127
151
|
if(v !== parseInt(v))
|
|
128
|
-
throw '
|
|
152
|
+
throw 'must be a integer'
|
|
129
153
|
|
|
130
154
|
if(v < 0 || v > 3)
|
|
131
|
-
throw '
|
|
155
|
+
throw 'must be between 0 and 3'
|
|
132
156
|
}
|
|
133
157
|
},
|
|
134
158
|
{
|
|
135
159
|
key: 'asset_class',
|
|
136
160
|
validate: v => {
|
|
137
|
-
if(!validAssetClasses.includes(v))
|
|
138
|
-
throw `
|
|
161
|
+
if(!legacyAssetClasses[v] && !validAssetClasses.includes(v))
|
|
162
|
+
throw `must be one of: ${validAssetClasses.join(', ')}`
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
key: 'asset_subclass',
|
|
167
|
+
validate: v => {
|
|
168
|
+
if(!validAssetSubClasses.includes(v))
|
|
169
|
+
throw `must be one of: ${validAssetSubClasses.join(', ')}`
|
|
139
170
|
}
|
|
140
171
|
}
|
|
141
172
|
]
|
|
142
173
|
|
|
143
|
-
const
|
|
174
|
+
const urlFields = [
|
|
144
175
|
{
|
|
145
176
|
key: 'url',
|
|
146
|
-
|
|
177
|
+
required: true,
|
|
147
178
|
validate: v => {
|
|
148
|
-
if(
|
|
149
|
-
throw '
|
|
179
|
+
if(!validUrlRegex.test(v))
|
|
180
|
+
throw 'must be a valid URL starting with "http" or "ipfs"'
|
|
150
181
|
}
|
|
151
182
|
},
|
|
152
183
|
{
|
|
153
184
|
key: 'type',
|
|
154
185
|
validate: v => {
|
|
155
|
-
if(!
|
|
156
|
-
throw `
|
|
186
|
+
if(!validUrlTypes[v])
|
|
187
|
+
throw `must be one of: ${Array.from(new Set(Object.values(validUrlTypes))).join(', ')}`
|
|
188
|
+
},
|
|
189
|
+
transform: v => {
|
|
190
|
+
return validUrlTypes[v]
|
|
157
191
|
}
|
|
158
192
|
},
|
|
159
193
|
{
|
|
160
194
|
key: 'title',
|
|
161
195
|
validate: v => {
|
|
162
196
|
if(typeof v !== 'string' || v.length === 0)
|
|
163
|
-
throw '
|
|
197
|
+
throw 'must be a non empty string'
|
|
164
198
|
}
|
|
165
199
|
},
|
|
166
200
|
]
|
|
@@ -168,7 +202,7 @@ const weblinkFields = [
|
|
|
168
202
|
const advisoryFields = [
|
|
169
203
|
{
|
|
170
204
|
key: 'address',
|
|
171
|
-
|
|
205
|
+
required: true,
|
|
172
206
|
validate: v => {
|
|
173
207
|
if(!/^[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{25,35}$/.test(v))
|
|
174
208
|
throw 'is not a valid XRPL address'
|
|
@@ -178,7 +212,7 @@ const advisoryFields = [
|
|
|
178
212
|
key: 'type',
|
|
179
213
|
validate: v => {
|
|
180
214
|
if(!validAdvisoryTypes.includes(v))
|
|
181
|
-
throw `
|
|
215
|
+
throw `must be one of: ${validAdvisoryTypes.join(', ')}`
|
|
182
216
|
}
|
|
183
217
|
},
|
|
184
218
|
{
|
|
@@ -186,7 +220,7 @@ const advisoryFields = [
|
|
|
186
220
|
alternativeKeys: ['desc'],
|
|
187
221
|
validate: v => {
|
|
188
222
|
if(typeof v !== 'string' || v.length === 0)
|
|
189
|
-
throw '
|
|
223
|
+
throw 'must be a non empty string'
|
|
190
224
|
}
|
|
191
225
|
}
|
|
192
226
|
]
|
|
@@ -204,71 +238,67 @@ export function parse(str){
|
|
|
204
238
|
let advisories = []
|
|
205
239
|
|
|
206
240
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
let { valid, parsed: issuer, issues: issuerIssues } = parseStanza(stanza, issuerFields)
|
|
241
|
+
for(let stanza of (toml.ISSUERS || toml.ACCOUNTS || [])){
|
|
242
|
+
let { valid, parsed: issuer, issues: issuerIssues } = parseStanza(stanza, issuerFields)
|
|
210
243
|
|
|
211
|
-
|
|
212
|
-
|
|
244
|
+
issues.push(
|
|
245
|
+
...issuerIssues.map(
|
|
246
|
+
issue => `[[ISSUERS]] ${issue}`
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if(valid)
|
|
251
|
+
issuers.push(issuer)
|
|
252
|
+
else
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
for(let substanza of (stanza.URLS || stanza.WEBLINKS || [])){
|
|
256
|
+
let { valid, parsed: url, issues: urlIssues } = parseStanza(substanza, urlFields)
|
|
257
|
+
|
|
258
|
+
if(valid){
|
|
259
|
+
issuer.urls = [
|
|
260
|
+
...(issuer.urls || []),
|
|
261
|
+
url
|
|
262
|
+
]
|
|
263
|
+
}
|
|
213
264
|
|
|
214
265
|
issues.push(
|
|
215
|
-
...
|
|
216
|
-
issue => `[[ISSUERS]] ${issue}`
|
|
266
|
+
...urlIssues.map(
|
|
267
|
+
issue => `[[ISSUERS.URLS]] ${issue}`
|
|
217
268
|
)
|
|
218
269
|
)
|
|
219
|
-
|
|
220
|
-
if(valid && stanza.WEBLINKS){
|
|
221
|
-
for(let substanza of stanza.WEBLINKS){
|
|
222
|
-
let { valid, parsed: weblink, issues: weblinkIssues } = parseStanza(substanza, weblinkFields)
|
|
223
|
-
|
|
224
|
-
if(valid){
|
|
225
|
-
issuer.weblinks = [
|
|
226
|
-
...(issuer.weblinks || []),
|
|
227
|
-
weblink
|
|
228
|
-
]
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
issues.push(
|
|
232
|
-
...weblinkIssues.map(
|
|
233
|
-
issue => `[[WEBLINK]] ${issue}`
|
|
234
|
-
)
|
|
235
|
-
)
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
270
|
}
|
|
239
271
|
}
|
|
240
272
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
let { valid, parsed: token, issues: tokenIssues } = parseStanza(stanza, tokenFields)
|
|
273
|
+
for(let stanza of (toml.TOKENS || toml.CURRENCIES || [])){
|
|
274
|
+
let { valid, parsed: token, issues: tokenIssues } = parseStanza(stanza, tokenFields)
|
|
244
275
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
issues.push(
|
|
249
|
-
...tokenIssues.map(
|
|
250
|
-
issue => `[[TOKENS]] ${issue}`
|
|
251
|
-
)
|
|
276
|
+
issues.push(
|
|
277
|
+
...tokenIssues.map(
|
|
278
|
+
issue => `[[TOKENS]] ${issue}`
|
|
252
279
|
)
|
|
280
|
+
)
|
|
253
281
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
issue => `[[WEBLINK]] ${issue}`
|
|
268
|
-
)
|
|
269
|
-
)
|
|
270
|
-
}
|
|
282
|
+
if(valid)
|
|
283
|
+
tokens.push(token)
|
|
284
|
+
else
|
|
285
|
+
continue
|
|
286
|
+
|
|
287
|
+
for(let substanza of (stanza.URLS || stanza.WEBLINKS || [])){
|
|
288
|
+
let { valid, parsed: url, issues: urlIssues } = parseStanza(substanza, urlFields)
|
|
289
|
+
|
|
290
|
+
if(valid){
|
|
291
|
+
token.urls = [
|
|
292
|
+
...(token.urls || []),
|
|
293
|
+
url
|
|
294
|
+
]
|
|
271
295
|
}
|
|
296
|
+
|
|
297
|
+
issues.push(
|
|
298
|
+
...urlIssues.map(
|
|
299
|
+
issue => `[[TOKENS.URLS]] ${issue}`
|
|
300
|
+
)
|
|
301
|
+
)
|
|
272
302
|
}
|
|
273
303
|
}
|
|
274
304
|
|
|
@@ -287,6 +317,32 @@ export function parse(str){
|
|
|
287
317
|
}
|
|
288
318
|
}
|
|
289
319
|
|
|
320
|
+
// Issuer URLs have been dropped since Version 5
|
|
321
|
+
// Issuer URLs now get mapped to respective tokens
|
|
322
|
+
|
|
323
|
+
for(let issuer of issuers){
|
|
324
|
+
if(!issuer.urls)
|
|
325
|
+
continue
|
|
326
|
+
|
|
327
|
+
for(let token of tokens){
|
|
328
|
+
if(token.issuer !== issuer.address)
|
|
329
|
+
continue
|
|
330
|
+
|
|
331
|
+
token.urls = [
|
|
332
|
+
...issuer.urls,
|
|
333
|
+
...(token.urls || [])
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
delete issuer.urls
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
for(let token of tokens){
|
|
341
|
+
if(!legacyAssetClasses[token.asset_class])
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
Object.assign(token, legacyAssetClasses[token.asset_class])
|
|
345
|
+
}
|
|
290
346
|
|
|
291
347
|
return {
|
|
292
348
|
issuers,
|
|
@@ -301,7 +357,7 @@ function parseStanza(stanza, schemas){
|
|
|
301
357
|
let issues = []
|
|
302
358
|
let valid = true
|
|
303
359
|
|
|
304
|
-
for(let { key, alternativeKeys,
|
|
360
|
+
for(let { key, alternativeKeys, required, validate, transform } of schemas){
|
|
305
361
|
let keys = [key]
|
|
306
362
|
|
|
307
363
|
if(alternativeKeys)
|
|
@@ -322,11 +378,14 @@ function parseStanza(stanza, schemas){
|
|
|
322
378
|
}
|
|
323
379
|
}
|
|
324
380
|
|
|
381
|
+
if(transform)
|
|
382
|
+
value = transform(value)
|
|
383
|
+
|
|
325
384
|
parsed[key] = value
|
|
326
385
|
break
|
|
327
386
|
}
|
|
328
387
|
|
|
329
|
-
if(
|
|
388
|
+
if(required && parsed[key] === undefined){
|
|
330
389
|
issues.push(`${key} field missing: skipping stanza`)
|
|
331
390
|
valid = false
|
|
332
391
|
}
|