@epa-wg/custom-element 0.0.22 → 0.0.23
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 +3 -3
- package/bin/xslDtd2Ide.mjs +1 -1
- package/demo/form.html +91 -44
- package/demo/s.xml +9 -17
- package/ide/web-types-dce.json +1 -1
- package/ide/web-types-xsl.json +1 -1
- package/package.json +1 -1
- package/custom-element1-1.js +0 -763
- package/custom-element1.js +0 -763
package/custom-element1.js
DELETED
|
@@ -1,763 +0,0 @@
|
|
|
1
|
-
const XSL_NS_URL = 'http://www.w3.org/1999/XSL/Transform'
|
|
2
|
-
, HTML_NS_URL = 'http://www.w3.org/1999/xhtml'
|
|
3
|
-
, EXSL_NS_URL = 'http://exslt.org/common'
|
|
4
|
-
, DCE_NS_URL ="urn:schemas-epa-wg:dce";
|
|
5
|
-
|
|
6
|
-
// const log = x => console.debug( new XMLSerializer().serializeToString( x ) );
|
|
7
|
-
|
|
8
|
-
const attr = (el, attr)=> el.getAttribute?.(attr)
|
|
9
|
-
, isText = e => e.nodeType === 3
|
|
10
|
-
, isString = s => typeof s === 'string'
|
|
11
|
-
, isNode = e => e && typeof e.nodeType === 'number'
|
|
12
|
-
, create = ( tag, t = '', d=document ) => ( e => ((t && e.append(createText(d.ownerDocument||d, t))),e) )((d.ownerDocument || d ).createElement( tag ))
|
|
13
|
-
, createText = ( d, t) => (d.ownerDocument || d ).createTextNode( t )
|
|
14
|
-
, removeChildren = n => { while(n.firstChild) n.firstChild.remove(); return n; }
|
|
15
|
-
, emptyNode = n => { n.getAttributeNames().map( a => n.removeAttribute(a) ); return removeChildren(n); }
|
|
16
|
-
, xslNs = x => ( x?.setAttribute('xmlns:xsl', XSL_NS_URL ), x )
|
|
17
|
-
, xslHtmlNs = x => ( x?.setAttribute('xmlns:xhtml', HTML_NS_URL ), xslNs(x) )
|
|
18
|
-
, cloneAs = (p,tag) =>
|
|
19
|
-
{ const px = p.ownerDocument.createElementNS(p.namespaceURI,tag);
|
|
20
|
-
for( let a of p.attributes)
|
|
21
|
-
px.setAttribute(a.name, a.value);
|
|
22
|
-
while( p.firstChild )
|
|
23
|
-
px.append(p.firstChild);
|
|
24
|
-
return px;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
function
|
|
28
|
-
ASSERT(x)
|
|
29
|
-
{
|
|
30
|
-
// if(!x)
|
|
31
|
-
// debugger
|
|
32
|
-
}
|
|
33
|
-
export function
|
|
34
|
-
xml2dom( xmlString )
|
|
35
|
-
{
|
|
36
|
-
return new DOMParser().parseFromString( xmlString, "application/xml" )
|
|
37
|
-
}
|
|
38
|
-
export function
|
|
39
|
-
xmlString(doc){ return new XMLSerializer().serializeToString( doc ) }
|
|
40
|
-
|
|
41
|
-
function
|
|
42
|
-
injectData( root, sectionName, arr, cb )
|
|
43
|
-
{ const create = ( tag ) => root.ownerDocument.createElement( tag );
|
|
44
|
-
const inject = ( tag, parent, s ) =>
|
|
45
|
-
{ parent.append( s = create( tag,'',parent.ownerDocument ) );
|
|
46
|
-
return s;
|
|
47
|
-
};
|
|
48
|
-
const l = inject( sectionName, root );
|
|
49
|
-
[ ...arr ].forEach( e => l.append( cb( e, root.ownerDocument ) ) );
|
|
50
|
-
return l;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function
|
|
54
|
-
assureSlot( e, doc )
|
|
55
|
-
{
|
|
56
|
-
if( !e.slot )
|
|
57
|
-
{
|
|
58
|
-
if( !e.setAttribute )
|
|
59
|
-
e = create( 'span', e.textContent.replaceAll( '\n', '' ), doc );
|
|
60
|
-
e.setAttribute( 'slot', '' )
|
|
61
|
-
}
|
|
62
|
-
return e;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function
|
|
66
|
-
obj2node( o, tag, doc )
|
|
67
|
-
{ const t = typeof o;
|
|
68
|
-
if( t === 'string' )
|
|
69
|
-
return create(tag,o,doc);
|
|
70
|
-
if( t === 'number' )
|
|
71
|
-
return create(tag,''+o,doc);
|
|
72
|
-
|
|
73
|
-
if( o instanceof Array )
|
|
74
|
-
{ const ret = create('array','',doc);
|
|
75
|
-
o.map( ae => ret.append( obj2node(ae,tag,doc)) );
|
|
76
|
-
return ret
|
|
77
|
-
}
|
|
78
|
-
if( o instanceof FormData )
|
|
79
|
-
{ const ret = create('form-data','',doc);
|
|
80
|
-
for( const p of o )
|
|
81
|
-
ret.append( obj2node(p[1],p[0],doc) );
|
|
82
|
-
return ret
|
|
83
|
-
}
|
|
84
|
-
const ret = create(tag,'',doc);
|
|
85
|
-
for( let k in o )
|
|
86
|
-
if( isNode(o[k]) || typeof o[k] ==='function' || o[k] instanceof Window )
|
|
87
|
-
continue
|
|
88
|
-
else
|
|
89
|
-
if( typeof o[k] !== "object" )
|
|
90
|
-
ret.setAttribute(k, o[k] );
|
|
91
|
-
else
|
|
92
|
-
ret.append(obj2node(o[k], k, doc))
|
|
93
|
-
return ret;
|
|
94
|
-
}
|
|
95
|
-
export function
|
|
96
|
-
tagUid( node )
|
|
97
|
-
{ // {} to xsl:value-of
|
|
98
|
-
forEach$(node,'*',d => [...d.childNodes].filter( e=>e.nodeType === 3 ).forEach( e=>
|
|
99
|
-
{ if( e.parentNode.localName === 'style' )
|
|
100
|
-
return;
|
|
101
|
-
const m = e.data.matchAll( /{([^}]*)}/g );
|
|
102
|
-
if(m)
|
|
103
|
-
{ let l = 0
|
|
104
|
-
, txt = t => createText(e,t||'')
|
|
105
|
-
, tt = [];
|
|
106
|
-
[...m].forEach(t=>
|
|
107
|
-
{ if( t.index > l )
|
|
108
|
-
tt.push( txt( t.input.substring( l, t.index ) ))
|
|
109
|
-
const v = node.querySelector('value-of').cloneNode();
|
|
110
|
-
v.setAttribute('select', t[1] );
|
|
111
|
-
tt.push(v);
|
|
112
|
-
l = t.index+t[0].length;
|
|
113
|
-
})
|
|
114
|
-
if( l < e.data.length)
|
|
115
|
-
tt.push( txt( e.data.substring(l,e.data.length) ));
|
|
116
|
-
if( tt.length )
|
|
117
|
-
{ for( let t of tt )
|
|
118
|
-
d.insertBefore(t,e);
|
|
119
|
-
d.removeChild(e);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}));
|
|
123
|
-
|
|
124
|
-
if( 'all' in node ) {
|
|
125
|
-
let i= 1;
|
|
126
|
-
for( let e of node.all )
|
|
127
|
-
e.setAttribute && !e.tagName.startsWith('xsl:') && e.setAttribute('data-dce-id', '' + i++)
|
|
128
|
-
}
|
|
129
|
-
return node
|
|
130
|
-
}
|
|
131
|
-
export function
|
|
132
|
-
createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
133
|
-
{
|
|
134
|
-
if( templateNode.tagName === S || templateNode.documentElement?.tagName === S )
|
|
135
|
-
return tagUid(templateNode)
|
|
136
|
-
const sanitizeXsl = xml2dom(`<xsl:stylesheet version="1.0" xmlns:xsl="${ XSL_NS_URL }" xmlns:xhtml="${ HTML_NS_URL }" xmlns:exsl="${EXSL_NS_URL}" exclude-result-prefixes="exsl" >
|
|
137
|
-
<xsl:output method="xml" />
|
|
138
|
-
<xsl:template match="/"><dce-root xmlns="${ HTML_NS_URL }"><xsl:apply-templates select="*"/></dce-root></xsl:template>
|
|
139
|
-
<xsl:template match="*[name()='template']"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
|
|
140
|
-
<xsl:template match="*"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
|
|
141
|
-
<xsl:template match="*[name()='svg']|*[name()='math']"><xsl:apply-templates mode="sanitize" select="."/></xsl:template>
|
|
142
|
-
<xsl:template mode="sanitize" match="*[count(text())=1 and count(*)=0]"><xsl:copy><xsl:apply-templates mode="sanitize" select="@*"/><xsl:value-of select="text()"></xsl:value-of></xsl:copy></xsl:template>
|
|
143
|
-
<xsl:template mode="sanitize" match="xhtml:*[count(text())=1 and count(*)=0]"><xsl:element name="{local-name()}"><xsl:apply-templates mode="sanitize" select="@*"/><xsl:value-of select="text()"></xsl:value-of></xsl:element></xsl:template>
|
|
144
|
-
<xsl:template mode="sanitize" match="*|@*"><xsl:copy><xsl:apply-templates mode="sanitize" select="*|@*|text()"/></xsl:copy></xsl:template>
|
|
145
|
-
<xsl:template mode="sanitize" match="text()[normalize-space(.) = '']"/>
|
|
146
|
-
<xsl:template mode="sanitize" match="text()"><dce-text><xsl:copy/></dce-text></xsl:template>
|
|
147
|
-
<xsl:template mode="sanitize" match="xsl:value-of|*[name()='slot']"><dce-text><xsl:copy><xsl:apply-templates mode="sanitize" select="*|@*|text()"/></xsl:copy></dce-text></xsl:template>
|
|
148
|
-
<xsl:template mode="sanitize" match="xhtml:*"><xsl:element name="{local-name()}"><xsl:apply-templates mode="sanitize" select="*|@*|text()"/></xsl:element></xsl:template>
|
|
149
|
-
</xsl:stylesheet>`)
|
|
150
|
-
const sanitizeProcessor = new XSLTProcessor()
|
|
151
|
-
, tc = (n =>
|
|
152
|
-
{
|
|
153
|
-
forEach$(n,'script', s=> s.remove() );
|
|
154
|
-
const xslRoot = n.content ?? n.firstElementChild?.content ?? n.body ?? n;
|
|
155
|
-
xslTags.forEach( tag => forEach$( xslRoot, tag, el=>toXsl(el,xslRoot) ) );
|
|
156
|
-
const e = n.firstElementChild?.content || n.content
|
|
157
|
-
, asXmlNode = r => {
|
|
158
|
-
const d = xml2dom( '<xhtml/>' )
|
|
159
|
-
, n = d.importNode(r, true);
|
|
160
|
-
d.replaceChild(n,d.documentElement);
|
|
161
|
-
return xslHtmlNs(n);
|
|
162
|
-
};
|
|
163
|
-
if( e )
|
|
164
|
-
{ const t = create('div');
|
|
165
|
-
[ ...e.childNodes ].map( c => t.append(c.cloneNode(true)) )
|
|
166
|
-
return asXmlNode(t)
|
|
167
|
-
}
|
|
168
|
-
return asXmlNode(n.documentElement || n.body || n)
|
|
169
|
-
})(templateNode)
|
|
170
|
-
, xslDom = xml2dom(
|
|
171
|
-
`<xsl:stylesheet version="1.0"
|
|
172
|
-
xmlns:xsl="${ XSL_NS_URL }"
|
|
173
|
-
xmlns:xhtml="${ HTML_NS_URL }"
|
|
174
|
-
xmlns:dce="urn:schemas-epa-wg:dce"
|
|
175
|
-
xmlns:exsl="http://exslt.org/common"
|
|
176
|
-
exclude-result-prefixes="exsl"
|
|
177
|
-
>
|
|
178
|
-
<xsl:template match="ignore">
|
|
179
|
-
<xsl:choose>
|
|
180
|
-
<xsl:when test="//attr">{//attr}</xsl:when>
|
|
181
|
-
<xsl:otherwise>{def}</xsl:otherwise>
|
|
182
|
-
</xsl:choose><xsl:value-of select="."></xsl:value-of></xsl:template>
|
|
183
|
-
<xsl:template mode="payload" match="attributes"></xsl:template>
|
|
184
|
-
<xsl:template match="/">
|
|
185
|
-
<xsl:apply-templates mode="payload" select="/datadom/attributes"/>
|
|
186
|
-
</xsl:template>
|
|
187
|
-
|
|
188
|
-
<xsl:template match="@*|node()" mode="copy-html">
|
|
189
|
-
<xsl:copy><xsl:apply-templates select="@*|node()" mode="copy-html"/></xsl:copy>
|
|
190
|
-
</xsl:template>
|
|
191
|
-
<xsl:template match="node()[starts-with(name(),'xhtml:')]" mode="copy-html">
|
|
192
|
-
<xsl:element name="{local-name()}"><xsl:apply-templates select="@*|node()" mode="copy-html"/></xsl:element>
|
|
193
|
-
</xsl:template>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
<xsl:template name="slot" >
|
|
198
|
-
<xsl:param name="slotname" />
|
|
199
|
-
<xsl:param name="defaultvalue" />
|
|
200
|
-
<xsl:choose>
|
|
201
|
-
<xsl:when test="//payload/*[@slot=$slotname]">
|
|
202
|
-
<xsl:apply-templates mode="copy-html" select="//payload/*[@slot=$slotname]"/>
|
|
203
|
-
</xsl:when>
|
|
204
|
-
<xsl:otherwise>
|
|
205
|
-
<xsl:apply-templates mode="copy-html" select="$defaultvalue"/>
|
|
206
|
-
</xsl:otherwise>
|
|
207
|
-
</xsl:choose>
|
|
208
|
-
</xsl:template>
|
|
209
|
-
<xsl:variable name="js-injected-body">
|
|
210
|
-
<xsl:call-template name="slot" >
|
|
211
|
-
<xsl:with-param name="slotname" select="''"/>
|
|
212
|
-
<xsl:with-param name="defaultvalue"/>
|
|
213
|
-
</xsl:call-template>
|
|
214
|
-
</xsl:variable>
|
|
215
|
-
</xsl:stylesheet>`
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
sanitizeProcessor.importStylesheet( sanitizeXsl );
|
|
219
|
-
|
|
220
|
-
const fr = sanitizeProcessor.transformToFragment(tc, document)
|
|
221
|
-
, $ = (e,css) => e.querySelector(css)
|
|
222
|
-
, payload = $( xslDom, 'template[mode="payload"]');
|
|
223
|
-
if( !fr )
|
|
224
|
-
return console.error("transformation error",{ xml:tc.outerHTML, xsl: xmlString( sanitizeXsl ) });
|
|
225
|
-
const params = [];
|
|
226
|
-
[...fr.querySelectorAll('dce-root>attribute')].forEach( a=>
|
|
227
|
-
{
|
|
228
|
-
const p = cloneAs(a,'xsl:param')
|
|
229
|
-
, name = attr(a,'name');
|
|
230
|
-
payload.append(p);
|
|
231
|
-
let select = attr(p,'select')?.split('??')
|
|
232
|
-
if( !select)
|
|
233
|
-
{ select = ['//'+name, `'${p.textContent}'`];
|
|
234
|
-
emptyNode(p);
|
|
235
|
-
p.setAttribute('name',name);
|
|
236
|
-
}
|
|
237
|
-
let val;
|
|
238
|
-
if( select?.length>1 ){
|
|
239
|
-
p.removeAttribute('select');
|
|
240
|
-
const c = $( xslDom, 'template[match="ignore"]>choose').cloneNode(true);
|
|
241
|
-
emptyNode(c.firstElementChild).append( createText(c,'{'+select[0]+'}'));
|
|
242
|
-
emptyNode(c.lastElementChild ).append( createText(c,'{'+select[1]+'}'));
|
|
243
|
-
c.firstElementChild.setAttribute('test',select[0]);
|
|
244
|
-
p.append(c);
|
|
245
|
-
val = c.cloneNode(true);
|
|
246
|
-
}else
|
|
247
|
-
val=cloneAs(a,'xsl:value-of');
|
|
248
|
-
val.removeAttribute('name');
|
|
249
|
-
a.append(val);
|
|
250
|
-
a.removeAttribute('select');
|
|
251
|
-
params.push(p)
|
|
252
|
-
});
|
|
253
|
-
[...fr.querySelectorAll('[value]')].filter(el=>el.getAttribute('value').match( /\{(.*)\?\?(.*)\}/g )).forEach(el=>
|
|
254
|
-
{ const v = attr(el,'value');
|
|
255
|
-
if(v)
|
|
256
|
-
el.setAttribute('value', evalCurly(v));
|
|
257
|
-
});
|
|
258
|
-
for( const c of fr.childNodes )
|
|
259
|
-
payload.append(xslDom.importNode(c,true))
|
|
260
|
-
|
|
261
|
-
const embeddedTemplates = [...payload.querySelectorAll('template')];
|
|
262
|
-
embeddedTemplates.forEach(t=>payload.ownerDocument.documentElement.append(t));
|
|
263
|
-
|
|
264
|
-
const slotCall = $(xslDom,'call-template[name="slot"]')
|
|
265
|
-
, slot2xsl = s =>
|
|
266
|
-
{ const v = slotCall.cloneNode(true)
|
|
267
|
-
, name = attr(s,'name') || '';
|
|
268
|
-
name && v.firstElementChild.setAttribute('select',`'${ name }'`)
|
|
269
|
-
for( let c of s.childNodes)
|
|
270
|
-
v.lastElementChild.append(c)
|
|
271
|
-
return v
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
forEach$( payload,'slot', s => s.parentNode.replaceChild( slot2xsl(s), s ) )
|
|
275
|
-
|
|
276
|
-
const ret = tagUid(xslDom)
|
|
277
|
-
ret.params = params;
|
|
278
|
-
return ret;
|
|
279
|
-
}
|
|
280
|
-
export async function
|
|
281
|
-
xhrTemplate(src)
|
|
282
|
-
{
|
|
283
|
-
const dom = await new Promise((resolve,reject)=>
|
|
284
|
-
{ const xhr = new XMLHttpRequest();
|
|
285
|
-
xhr.open("GET", src);
|
|
286
|
-
xhr.responseType = "document";
|
|
287
|
-
// xhr.overrideMimeType("text/xml");
|
|
288
|
-
xhr.onload = () =>
|
|
289
|
-
{ if( xhr.readyState === xhr.DONE && xhr.status === 200 )
|
|
290
|
-
resolve( xhr.responseXML || create('div', xhr.responseText ) )
|
|
291
|
-
reject(xhr.statusText)
|
|
292
|
-
};
|
|
293
|
-
xhr.addEventListener("error", ev=>reject(ev) );
|
|
294
|
-
|
|
295
|
-
xhr.send();
|
|
296
|
-
})
|
|
297
|
-
return dom
|
|
298
|
-
}
|
|
299
|
-
export function
|
|
300
|
-
deepEqual(a, b, O=false)
|
|
301
|
-
{
|
|
302
|
-
if( a === b )
|
|
303
|
-
return true;
|
|
304
|
-
|
|
305
|
-
if( (typeof a !== "object" || a === null) || (typeof b !== "object" || b === null)
|
|
306
|
-
|| Object.keys(a).length !== Object.keys(b).length )
|
|
307
|
-
return O;
|
|
308
|
-
|
|
309
|
-
for( let k in a )
|
|
310
|
-
if( !(k in b) || !deepEqual( a[k], b[k] ) )
|
|
311
|
-
return O
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
export const
|
|
315
|
-
assureSlices = ( root, names) =>
|
|
316
|
-
names.split('|').filter(s=>s).map(n=>n.trim()).map( xp =>
|
|
317
|
-
{ const append = n=> (root.append(n),n);
|
|
318
|
-
if(xp.includes('/'))
|
|
319
|
-
{ const ret = [], r = root.ownerDocument.evaluate( xp, root );
|
|
320
|
-
for( let n; n = r.iterateNext(); )
|
|
321
|
-
ret.push( n )
|
|
322
|
-
return ret
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return [...root.childNodes].find(n=>n.localName === xp) || append( create(xp,'',root.ownerDocument) );
|
|
326
|
-
}).flat();
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
*
|
|
330
|
-
* @param x slice node
|
|
331
|
-
* @param sliceNames slice name, xPath in /datadom/slice/
|
|
332
|
-
* @param ev Event obj
|
|
333
|
-
* @param dce
|
|
334
|
-
*/
|
|
335
|
-
export function
|
|
336
|
-
event2slice( x, sliceNames, ev, dce )
|
|
337
|
-
{
|
|
338
|
-
if( ev.sliceProcessed )
|
|
339
|
-
return
|
|
340
|
-
ev.sliceProcessed = 1;
|
|
341
|
-
// evaluate slices[]
|
|
342
|
-
// inject @attributes
|
|
343
|
-
// inject event
|
|
344
|
-
// evaluate slice-value
|
|
345
|
-
// slice[i] = slice-value
|
|
346
|
-
return assureSlices( x, sliceNames ?? '' ).map( s =>
|
|
347
|
-
{
|
|
348
|
-
const d = x.ownerDocument
|
|
349
|
-
, el = ev.sliceEventSource
|
|
350
|
-
, sel = ev.sliceElement
|
|
351
|
-
, cleanSliceValue = ()=>[...s.childNodes].filter(n=>n.nodeType===3 || n.localName==='value' || n.localName==='form-data').map(n=>n.remove());
|
|
352
|
-
el.getAttributeNames().map( a => s.setAttribute( a, attr(el,a) ) );
|
|
353
|
-
[...s.childNodes].filter(n=>n.localName==='event').map(n=>n.remove());
|
|
354
|
-
if( 'validationMessage' in el )
|
|
355
|
-
s.setAttribute('validation-message', el.validationMessage);
|
|
356
|
-
ev.type==='init' && cleanSliceValue();
|
|
357
|
-
s.append( obj2node( ev, 'event', d ) );
|
|
358
|
-
if( sel.hasAttribute('slice-value') )
|
|
359
|
-
{ if( el.value === undefined)
|
|
360
|
-
s.removeAttribute('value')
|
|
361
|
-
else
|
|
362
|
-
s.setAttribute('value', el.value );
|
|
363
|
-
const v = xPath( attr( sel, 'slice-value'),s );
|
|
364
|
-
cleanSliceValue();
|
|
365
|
-
s.append( createText( d, v ) );
|
|
366
|
-
}else
|
|
367
|
-
{ if( 'elements' in el )
|
|
368
|
-
{ cleanSliceValue();
|
|
369
|
-
s.append( obj2node(new FormData(el),'value', s.ownerDocument) )
|
|
370
|
-
return s
|
|
371
|
-
}
|
|
372
|
-
const v = el.value ?? attr( sel, 'value' ) ;
|
|
373
|
-
cleanSliceValue();
|
|
374
|
-
if( v === null || v === undefined )
|
|
375
|
-
[...s.childNodes].filter(n=>n.localName!=='event').map(n=>n.remove());
|
|
376
|
-
else
|
|
377
|
-
if( isString(v) )
|
|
378
|
-
s.append( createText( d, v) );
|
|
379
|
-
else
|
|
380
|
-
s.append( obj2node(v,'value',s.ownerDocument) )
|
|
381
|
-
}
|
|
382
|
-
return s
|
|
383
|
-
})
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function forEach$( el, css, cb){
|
|
387
|
-
if( el.querySelectorAll )
|
|
388
|
-
[...el.querySelectorAll(css)].forEach(cb)
|
|
389
|
-
}
|
|
390
|
-
const loadTemplateRoots = async ( src, dce )=>
|
|
391
|
-
{
|
|
392
|
-
if( !src || !src.trim() )
|
|
393
|
-
return [dce]
|
|
394
|
-
if( src.startsWith('#') )
|
|
395
|
-
return ( n =>
|
|
396
|
-
{ if(!n) return []
|
|
397
|
-
const a = n.querySelectorAll(src)
|
|
398
|
-
if( a.length )
|
|
399
|
-
return [...a]
|
|
400
|
-
const r = n.getRootNode();
|
|
401
|
-
return r===n ? []: getByHashId(r)
|
|
402
|
-
})(dce.parentElement)
|
|
403
|
-
try
|
|
404
|
-
{ // todo cache
|
|
405
|
-
const dom = await xhrTemplate(src)
|
|
406
|
-
const hash = new URL(src, location).hash
|
|
407
|
-
if( hash )
|
|
408
|
-
{ const ret = dom.querySelectorAll(hash);
|
|
409
|
-
if( ret.length )
|
|
410
|
-
return [...ret]
|
|
411
|
-
return [dce]
|
|
412
|
-
}
|
|
413
|
-
return [dom]
|
|
414
|
-
}catch (error){ return [dce]}
|
|
415
|
-
}
|
|
416
|
-
export function mergeAttr( from, to )
|
|
417
|
-
{ if( isText(from) )
|
|
418
|
-
{
|
|
419
|
-
if( !isText(to) ){ debugger }
|
|
420
|
-
return
|
|
421
|
-
}
|
|
422
|
-
for( let a of from.attributes)
|
|
423
|
-
{ a.namespaceURI? to.setAttributeNS( a.namespaceURI, a.name, a.value ) : to.setAttribute( a.name, a.value )
|
|
424
|
-
if( a.name === 'value')
|
|
425
|
-
to.value = a.value
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
export function assureUnique(n, id=0)
|
|
429
|
-
{
|
|
430
|
-
const m = {}
|
|
431
|
-
for( const e of n.childNodes )
|
|
432
|
-
{
|
|
433
|
-
const a = attr(e,'data-dce-id') || e.dceId || 0;
|
|
434
|
-
if( !m[a] )
|
|
435
|
-
{ if( !a )
|
|
436
|
-
{ m[a] = e.dceId = ++id;
|
|
437
|
-
if( e.setAttribute )
|
|
438
|
-
e.setAttribute('data-dce-id', e.dceId )
|
|
439
|
-
}else
|
|
440
|
-
m[a] = 1;
|
|
441
|
-
}else
|
|
442
|
-
{ const v = e.dceId = a + '-' + m[a]++;
|
|
443
|
-
if( e.setAttribute )
|
|
444
|
-
e.setAttribute('data-dce-id', v )
|
|
445
|
-
}
|
|
446
|
-
e.childNodes.length && assureUnique(e)
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
export function merge( parent, fromArr )
|
|
450
|
-
{
|
|
451
|
-
if(!fromArr.length)
|
|
452
|
-
return removeChildren(parent);
|
|
453
|
-
const id2old = {};
|
|
454
|
-
for( let c of parent.childNodes)
|
|
455
|
-
{ ASSERT( !id2old[c.dceId] );
|
|
456
|
-
if( isText(c) )
|
|
457
|
-
{ ASSERT( c.data.trim() );
|
|
458
|
-
id2old[c.dceId || 0] = c;
|
|
459
|
-
} else
|
|
460
|
-
id2old[attr(c, 'data-dce-id') || 0] = c;
|
|
461
|
-
}
|
|
462
|
-
for( let e of [...fromArr] )
|
|
463
|
-
{ const k = attr(e, 'data-dce-id') || e.dceId;
|
|
464
|
-
const o = id2old[ k ];
|
|
465
|
-
if( o )
|
|
466
|
-
{ if( isText(e) )
|
|
467
|
-
{ if( o.nodeValue !== e.nodeValue )
|
|
468
|
-
o.nodeValue = e.nodeValue;
|
|
469
|
-
}else
|
|
470
|
-
{ mergeAttr(e,o)
|
|
471
|
-
if( o.childNodes.length || e.childNodes.length )
|
|
472
|
-
merge(o, e.childNodes)
|
|
473
|
-
}
|
|
474
|
-
delete id2old[ k ]
|
|
475
|
-
}else
|
|
476
|
-
parent.append( e )
|
|
477
|
-
}
|
|
478
|
-
for( let v of Object.values(id2old) )
|
|
479
|
-
v.remove();
|
|
480
|
-
}
|
|
481
|
-
export function assureUID(n,attr)
|
|
482
|
-
{ if( !n.hasAttribute(attr) )
|
|
483
|
-
n.setAttribute(attr, crypto.randomUUID());
|
|
484
|
-
return n.getAttribute(attr)
|
|
485
|
-
}
|
|
486
|
-
export const evalCurly = s =>
|
|
487
|
-
{ const exp = [...s?.matchAll( /([^{}]*)(\{)([^}]+)}([^{}]*)/g ) ].map(l=>`${l[1]}{${ xPathDefaults(l[3] )}}${l[4]}`);
|
|
488
|
-
return exp.join('');
|
|
489
|
-
}
|
|
490
|
-
export const xPathDefaults = x=>
|
|
491
|
-
{ if(!x.trim())
|
|
492
|
-
return x;
|
|
493
|
-
const xx = x.split('??')
|
|
494
|
-
, a = xx.shift()
|
|
495
|
-
, b = xPathDefaults(xx.join('??'));
|
|
496
|
-
|
|
497
|
-
return xx.length ? `concat( ${a} , substring( ${b} , (1+string-length( ${b} )) * string-length( ${a} ) ) )`: x
|
|
498
|
-
// return xx.length ? `${a}|(${xPathDefaults(xx.join('??'))})[not(${a})]`: a
|
|
499
|
-
}
|
|
500
|
-
export const xPath = (x,root)=>
|
|
501
|
-
{
|
|
502
|
-
const xx = x.split('??');
|
|
503
|
-
if( xx.length > 1 )
|
|
504
|
-
return xPath(xx[0], root) || xPath(xx[1], root);
|
|
505
|
-
|
|
506
|
-
x = xPathDefaults(x);
|
|
507
|
-
|
|
508
|
-
const it = root.ownerDocument.evaluate(x, root);
|
|
509
|
-
switch( it.resultType )
|
|
510
|
-
{ case XPathResult.NUMBER_TYPE: return it.numberValue;
|
|
511
|
-
case XPathResult.STRING_TYPE: return it.stringValue;
|
|
512
|
-
case XPathResult.BOOLEAN_TYPE: return it.booleanValue;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
let ret = '';
|
|
516
|
-
for( let n ;n=it.iterateNext(); )
|
|
517
|
-
ret += n.textContent;
|
|
518
|
-
return ret
|
|
519
|
-
}
|
|
520
|
-
export const xslTags = 'stylesheet,transform,import,include,strip-space,preserve-space,output,key,decimal-format,namespace-alias,template,value-of,copy-of,number,apply-templates,apply-imports,for-each,sort,if,choose,when,otherwise,attribute-set,call-template,with-param,variable,param,text,processing-instruction,element,attribute,comment,copy,message,fallback'.split(',');
|
|
521
|
-
export const toXsl = (el, defParent) => {
|
|
522
|
-
const x = create('xsl:'+el.localName);
|
|
523
|
-
for( let a of el.attributes )
|
|
524
|
-
x.setAttribute( a.name, a.value );
|
|
525
|
-
while(el.firstChild)
|
|
526
|
-
x.append(el.firstChild);
|
|
527
|
-
if( el.parentElement )
|
|
528
|
-
el.parentElement.replaceChild( x, el );
|
|
529
|
-
else
|
|
530
|
-
{ const p = (el.parentElement || defParent)
|
|
531
|
-
, arr = [...p.childNodes];
|
|
532
|
-
arr.forEach((n, i) => {
|
|
533
|
-
if (n === el)
|
|
534
|
-
arr[i] = x;
|
|
535
|
-
});
|
|
536
|
-
p.replaceChildren(...arr);
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
export class
|
|
541
|
-
CustomElement extends HTMLElement
|
|
542
|
-
{
|
|
543
|
-
static observedAttributes = ['src','tag','hidden'];
|
|
544
|
-
async connectedCallback()
|
|
545
|
-
{
|
|
546
|
-
const templateRoots = await loadTemplateRoots( attr( this, 'src' ), this )
|
|
547
|
-
, tag = attr( this, 'tag' )
|
|
548
|
-
, tagName = tag ? tag : 'dce-'+crypto.randomUUID();
|
|
549
|
-
|
|
550
|
-
for( const t of templateRoots )
|
|
551
|
-
forEach$(t.templateNode||t.content||t, 'style',s=>{
|
|
552
|
-
const slot = s.closest('slot');
|
|
553
|
-
const sName = slot ? `slot[name="${slot.name}"]`:'';
|
|
554
|
-
s.innerHTML = `${tagName} ${sName}{${s.innerHTML}}`;
|
|
555
|
-
this.append(s);
|
|
556
|
-
})
|
|
557
|
-
const templateDocs = templateRoots.map( n => createXsltFromDom( n ) )
|
|
558
|
-
, xp = templateDocs.map( (td, p) =>{ p = new XSLTProcessor(); p.importStylesheet( td ); return p })
|
|
559
|
-
|
|
560
|
-
Object.defineProperty( this, "xsltString", { get: ()=>templateDocs.map( td => xmlString(td) ).join('\n') });
|
|
561
|
-
|
|
562
|
-
const dce = this
|
|
563
|
-
, sliceNodes = [...this.templateNode.querySelectorAll('[slice]')]
|
|
564
|
-
, sliceNames = sliceNodes.map(e=>attr(e,'slice')).filter(n=>!n.includes('/')).filter((v, i, a)=>a.indexOf(v) === i)
|
|
565
|
-
, declaredAttributes = templateDocs.reduce( (ret,t) => { if( t.params ) ret.push( ...t.params ); return ret; }, [] );
|
|
566
|
-
|
|
567
|
-
class DceElement extends HTMLElement
|
|
568
|
-
{
|
|
569
|
-
static get observedAttributes(){ return declaredAttributes.map( a=>attr(a,'name')); }
|
|
570
|
-
#inTransform = 0;
|
|
571
|
-
connectedCallback()
|
|
572
|
-
{ let payload = [...this.childNodes];
|
|
573
|
-
if( this.firstElementChild?.tagName === 'TEMPLATE' )
|
|
574
|
-
{
|
|
575
|
-
if( this.firstElementChild !== this.lastElementChild )
|
|
576
|
-
{ console.error('payload should have TEMPLATE as only child', this.outerHTML ) }
|
|
577
|
-
const t = this.firstElementChild;
|
|
578
|
-
t.remove();
|
|
579
|
-
payload = t.content.childNodes;
|
|
580
|
-
|
|
581
|
-
for( const n of [...t.content.childNodes] )
|
|
582
|
-
if( n.localName === 'style' ){
|
|
583
|
-
const id = assureUID(this,'data-dce-style')
|
|
584
|
-
n.innerHTML= `${tagName}[data-dce-style="${id}"]{${n.innerHTML}}`;
|
|
585
|
-
this.insertAdjacentElement('afterbegin',n);
|
|
586
|
-
}else
|
|
587
|
-
if(n.nodeType===1)
|
|
588
|
-
t.insertAdjacentElement('beforebegin',n);
|
|
589
|
-
else if(n.nodeType===3)
|
|
590
|
-
t.insertAdjacentText('beforebegin',n.data);
|
|
591
|
-
}else
|
|
592
|
-
this.innerHTML='';
|
|
593
|
-
const x = xml2dom( `<datadom
|
|
594
|
-
xmlns:xsl="${ XSL_NS_URL }"
|
|
595
|
-
xmlns="${ HTML_NS_URL }"
|
|
596
|
-
xmlns:xhtml="${ HTML_NS_URL }"
|
|
597
|
-
xmlns:dce="urn:schemas-epa-wg:dce"
|
|
598
|
-
/>` ).documentElement;
|
|
599
|
-
const createXmlNode = ( tag, t = '' ) => ( e =>
|
|
600
|
-
{ if( t )
|
|
601
|
-
e.append( createText( x, t ))
|
|
602
|
-
return e;
|
|
603
|
-
})(x.ownerDocument.createElement( tag ))
|
|
604
|
-
|
|
605
|
-
injectData( x, 'payload' , payload , assureSlot );
|
|
606
|
-
const attrsRoot = injectData( x, 'attributes' , this.attributes, e => createXmlNode( e.nodeName, e.value ) );
|
|
607
|
-
injectData( x, 'dataset', Object.keys( this.dataset ), k => createXmlNode( k, this.dataset[ k ] ) );
|
|
608
|
-
const sliceRoot = injectData( x, 'slice', sliceNames, k => createXmlNode( k, '' ) )
|
|
609
|
-
, sliceXPath = x => xPath(x, sliceRoot);
|
|
610
|
-
this.xml = x;
|
|
611
|
-
|
|
612
|
-
const sliceEvents=[];
|
|
613
|
-
const applySlices = ()=>
|
|
614
|
-
{ const processed = {}
|
|
615
|
-
|
|
616
|
-
for(let ev; ev = sliceEvents.pop(); )
|
|
617
|
-
{ const s = attr( ev.sliceElement, 'slice');
|
|
618
|
-
if( processed[s] )
|
|
619
|
-
continue;
|
|
620
|
-
event2slice( sliceRoot, s, ev, this );
|
|
621
|
-
processed[s] = ev;
|
|
622
|
-
}
|
|
623
|
-
Object.keys(processed).length !== 0 && transform();
|
|
624
|
-
}
|
|
625
|
-
let timeoutID;
|
|
626
|
-
|
|
627
|
-
this.onSlice = ev=>
|
|
628
|
-
{ sliceEvents.push(ev);
|
|
629
|
-
if( !timeoutID )
|
|
630
|
-
timeoutID = setTimeout(()=>
|
|
631
|
-
{ applySlices();
|
|
632
|
-
timeoutID =0;
|
|
633
|
-
},1);
|
|
634
|
-
};
|
|
635
|
-
const transform = this.transform = ()=>
|
|
636
|
-
{ if(this.#inTransform){ debugger }
|
|
637
|
-
this.#inTransform = 1;
|
|
638
|
-
|
|
639
|
-
const ff = xp.map( (p,i) =>
|
|
640
|
-
{ const f = p.transformToFragment(x.ownerDocument, document)
|
|
641
|
-
if( !f )
|
|
642
|
-
console.error( "XSLT transformation error. xsl:\n", xmlString(templateDocs[i]), '\nxml:\n', xmlString(x) );
|
|
643
|
-
return f
|
|
644
|
-
});
|
|
645
|
-
ff.map( f =>
|
|
646
|
-
{ if( !f )
|
|
647
|
-
return;
|
|
648
|
-
assureUnique(f);
|
|
649
|
-
merge( this, f.childNodes )
|
|
650
|
-
})
|
|
651
|
-
|
|
652
|
-
DceElement.observedAttributes.map( a =>
|
|
653
|
-
{ let v = attr(this.firstElementChild,a);
|
|
654
|
-
if( v !== attr(this,a) )
|
|
655
|
-
{ this.setAttribute( a, v );
|
|
656
|
-
this.#applyAttribute( a, v );
|
|
657
|
-
}
|
|
658
|
-
})
|
|
659
|
-
|
|
660
|
-
forEach$( this,'[slice],[slice-event]', el =>
|
|
661
|
-
{ if( !el.dceInitialized )
|
|
662
|
-
{ el.dceInitialized = 1;
|
|
663
|
-
let evs = attr(el,'slice-event');
|
|
664
|
-
if( attr(el,'custom-validity') )
|
|
665
|
-
evs += ' change submit';
|
|
666
|
-
|
|
667
|
-
[...new Set((evs || 'change') .split(' '))]
|
|
668
|
-
.forEach( t=> (el.localName==='slice'? el.parentElement : el)
|
|
669
|
-
.addEventListener( t, ev=>
|
|
670
|
-
{ ev.sliceElement = el;
|
|
671
|
-
ev.sliceEventSource = ev.currentTarget || ev.target;
|
|
672
|
-
const slices = event2slice( sliceRoot, attr( ev.sliceElement, 'slice')||'', ev, this );
|
|
673
|
-
|
|
674
|
-
forEach$(this,'[custom-validity]',el =>
|
|
675
|
-
{ if( !el.setCustomValidity )
|
|
676
|
-
return;
|
|
677
|
-
const x = attr( el, 'custom-validity' );
|
|
678
|
-
try
|
|
679
|
-
{ const v = x && xPath( x, attrsRoot );
|
|
680
|
-
el.setCustomValidity( v === true? '': v === false ? 'invalid' : v );
|
|
681
|
-
}catch(err)
|
|
682
|
-
{ console.error(err, 'xPath', x) }
|
|
683
|
-
})
|
|
684
|
-
const x = attr(el,'custom-validity')
|
|
685
|
-
, v = x && xPath( x, attrsRoot )
|
|
686
|
-
, msg = v === true? '' : v;
|
|
687
|
-
if( x )
|
|
688
|
-
{ el.setCustomValidity ? el.setCustomValidity( msg ) : ( el.validationMessage = msg );
|
|
689
|
-
slices.map( s => s.setAttribute('validation-message', msg ) );
|
|
690
|
-
if( ev.type === 'submit' )
|
|
691
|
-
{ if( v === true )
|
|
692
|
-
return;
|
|
693
|
-
setTimeout(transform,1)
|
|
694
|
-
if( !!v === v )
|
|
695
|
-
{ v || ev.preventDefault();
|
|
696
|
-
return v;
|
|
697
|
-
}
|
|
698
|
-
if( v )
|
|
699
|
-
{ ev.preventDefault();
|
|
700
|
-
return !1
|
|
701
|
-
}
|
|
702
|
-
return ;
|
|
703
|
-
}else
|
|
704
|
-
setTimeout(transform,1)
|
|
705
|
-
}
|
|
706
|
-
this.onSlice(ev);
|
|
707
|
-
} ));
|
|
708
|
-
if( !evs || evs.includes('init') )
|
|
709
|
-
{ if( el.hasAttribute('slice-value') || el.hasAttribute('value') || el.value )
|
|
710
|
-
this.onSlice({type:'init', target: el, sliceElement:el, sliceEventSource:el })
|
|
711
|
-
else
|
|
712
|
-
el.value = sliceXPath( attr(el,'slice') )
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
});
|
|
716
|
-
this.#inTransform = 0;
|
|
717
|
-
};
|
|
718
|
-
transform();
|
|
719
|
-
applySlices();
|
|
720
|
-
}
|
|
721
|
-
#applyAttribute(name, newValue)
|
|
722
|
-
{ let a = this.xml.querySelector(`attributes>${name}`);
|
|
723
|
-
if( a )
|
|
724
|
-
emptyNode(a).append( createText(a,newValue) );
|
|
725
|
-
else
|
|
726
|
-
{ a = create( name, newValue, this.xml );
|
|
727
|
-
this.xml.querySelector('attributes').append( a );
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
attributeChangedCallback(name, oldValue, newValue)
|
|
731
|
-
{ if( !this.xml || this.#inTransform )
|
|
732
|
-
return;
|
|
733
|
-
this.#applyAttribute(name, newValue);
|
|
734
|
-
this.transform(); // needs throttling
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
get dce(){ return dce }
|
|
738
|
-
}
|
|
739
|
-
const registerTag = tag =>
|
|
740
|
-
{
|
|
741
|
-
if( window.customElements.get(tag) !== DceElement )
|
|
742
|
-
window.customElements.define( tag, DceElement);
|
|
743
|
-
};
|
|
744
|
-
if(tag)
|
|
745
|
-
registerTag(tag);
|
|
746
|
-
else
|
|
747
|
-
{ const t = tagName;
|
|
748
|
-
this.setAttribute('tag', t );
|
|
749
|
-
registerTag(t);
|
|
750
|
-
const el = document.createElement(t);
|
|
751
|
-
this.getAttributeNames().forEach(a=>el.setAttribute(a,this.getAttribute(a)));
|
|
752
|
-
el.append(...[...this.childNodes].filter( e => e.localName!=='style') );
|
|
753
|
-
this.append(el);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
get templateNode(){ return this.firstElementChild?.tagName === 'TEMPLATE'? this.firstElementChild.content : this }
|
|
757
|
-
get dce(){ return this }
|
|
758
|
-
|
|
759
|
-
get xslt(){ return xml2dom( this.xsltString ) }
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
window.customElements.define( 'custom-element', CustomElement );
|
|
763
|
-
export default CustomElement;
|