-
Notifications
You must be signed in to change notification settings - Fork 10
/
jdom.js.html
711 lines (708 loc) · 88.8 KB
/
jdom.js.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>jdom.js annotated source</title>
<link rel="stylesheet" href="main.css">
</head>
<body>
<main>
<div class="line">
<div class="doc">
<h1>jdom.js <span class="fade">annotated source</span></h1>
<em><a class="back" href="./">Back to index</a></em>
</div>
<pre></pre>
</div>
<div class="line"><div class="doc"><p>Shortcut utility function to check if a given name is
bound to something that's an actual object (not just null)
We perform the <code>null</code> check first because it's faster.</p>
</div><pre class="source javascript"><strong class="lineNumber">4</strong>const isObject = obj => obj !== null && typeof obj === 'object';</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">5</strong></pre></div>
<div class="line"><div class="doc"><p>Clip the end of a given string by the length of a substring</p>
</div><pre class="source javascript"><strong class="lineNumber">7</strong>const clipStringEnd = (base, substr) => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">8</strong> return base.substr(0, base.length - substr.length);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">9</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">10</strong></pre></div>
<div class="line"><div class="doc"><p>This allows us to write HTML entities like '<' and '>' without breaking
the HTML parser.</p>
</div><pre class="source javascript"><strong class="lineNumber">13</strong>const decodeEntity = entity => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">14</strong> return String.fromCodePoint((+(/&#(\w+);/).exec(entity)[1]));</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">15</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">16</strong></pre></div>
<div class="line"><div class="doc"><p>Interpolate between lists of strings into a single string. Used to
merge the two parts of a template tag's arguments.</p>
</div><pre class="source javascript"><strong class="lineNumber">19</strong>const interpolate = (tplParts, dynamicParts) => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">20</strong> let str = tplParts[0];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">21</strong> for (let i = 1, len = dynamicParts.length; i <= len; i ++) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">22</strong> str += dynamicParts[i - 1] + tplParts[i];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">23</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">24</strong> return str;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">25</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">26</strong></pre></div>
<div class="line"><div class="doc"><p>The <code>Reader</code> class represents a sequence of characters we can read from a template.</p>
</div><pre class="source javascript"><strong class="lineNumber">28</strong>class Reader {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">29</strong></pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">30</strong> constructor(content) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">31</strong> this.idx = 0;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">32</strong> this.content = content;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">33</strong> this.len = content.length;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">34</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">35</strong></pre></div>
<div class="line"><div class="doc"><p>Returns the current character and moves the pointer one place farther.</p>
</div><pre class="source javascript"><strong class="lineNumber">37</strong> next() {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">38</strong> const char = this.content[this.idx ++];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">39</strong> if (char === undefined) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">40</strong> this.idx = this.len;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">41</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">42</strong> return char;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">43</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">44</strong></pre></div>
<div class="line"><div class="doc"><p>Move the pointer back one place, undoing the last character read.
In practice, we never backtrack from index 0 -- we only use back()
to "un-read" a character we've read. So we don't check for negative cases here.</p>
</div><pre class="source javascript"><strong class="lineNumber">48</strong> back() {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">49</strong> this.idx --;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">50</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">51</strong></pre></div>
<div class="line"><div class="doc"><p>Read up to a specified <em>contiguous</em> substring,
but not including the substring.</p>
</div><pre class="source javascript"><strong class="lineNumber">54</strong> readUpto(substr) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">55</strong> const nextIdx = this.content.substr(this.idx).indexOf(substr);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">56</strong> return this.toNext(nextIdx);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">57</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">58</strong></pre></div>
<div class="line"><div class="doc"><p>Read up to and including a <em>contiguous</em> substring, or read until
the end of the template.</p>
</div><pre class="source javascript"><strong class="lineNumber">61</strong> readUntil(substr) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">62</strong> const nextIdx = this.content.substr(this.idx).indexOf(substr) + substr.length;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">63</strong> return this.toNext(nextIdx);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">64</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">65</strong></pre></div>
<div class="line"><div class="doc"><p>Abstraction used for both <code>readUpto</code> and <code>readUntil</code> above.</p>
</div><pre class="source javascript"><strong class="lineNumber">67</strong> toNext(nextIdx) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">68</strong> const rest = this.content.substr(this.idx);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">69</strong> if (nextIdx === -1) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">70</strong> this.idx = this.len;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">71</strong> return rest;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">72</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">73</strong> const part = rest.substr(0, nextIdx);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">74</strong> this.idx += nextIdx;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">75</strong> return part;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">76</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">77</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">78</strong></pre></div>
<div class="line"><div class="doc"><p>Remove some substring from the end of the template, if it ends in the substring.
This also returns whether the given substring was a valid ending substring.</p>
</div><pre class="source javascript"><strong class="lineNumber">81</strong> clipEnd(substr) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">82</strong> if (this.content.endsWith(substr)) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">83</strong> this.content = clipStringEnd(this.content, substr);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">84</strong> return true;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">85</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">86</strong> return false;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">87</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">88</strong></pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">89</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">90</strong></pre></div>
<div class="line"><div class="doc"><p>For converting CSS property names to their JavaScript counterparts</p>
</div><pre class="source javascript"><strong class="lineNumber">92</strong>const kebabToCamel = kebabStr => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">93</strong> let result = '';</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">94</strong> for (let i = 0, len = kebabStr.length; i < len; i ++) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">95</strong> result += kebabStr[i] === '-' ? kebabStr[++ i].toUpperCase() : kebabStr[i];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">96</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">97</strong> return result;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">98</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">99</strong></pre></div>
<div class="line"><div class="doc"><p>Pure function to parse the contents of an HTML opening tag to a JDOM stub</p>
</div><pre class="source javascript"><strong class="lineNumber">101</strong>const parseOpeningTagContents = content => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">102</strong></pre></div>
<div class="line"><div class="doc"><p>If the opening tag is just the tag name (the most common case), take
a shortcut and run a simpler algorithm.</p>
</div><pre class="source javascript"><strong class="lineNumber">105</strong> content = content.trim();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">106</strong> if (content[0] === '!') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">107</strong> // comment</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">108</strong> return {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">109</strong> jdom: null,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">110</strong> selfClosing: true,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">111</strong> };</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">112</strong> } else if (!content.includes(' ')) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">113</strong> const selfClosing = content.endsWith('/');</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">114</strong> return {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">115</strong> jdom: {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">116</strong> tag: selfClosing ? clipStringEnd(content, '/') : content,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">117</strong> attrs: {},</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">118</strong> events: {},</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">119</strong> },</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">120</strong> selfClosing: selfClosing,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">121</strong> };</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">122</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">123</strong></pre></div>
<div class="line"><div class="doc"><p>Make another reader to read the tag contents</p>
</div><pre class="source javascript"><strong class="lineNumber">125</strong> const reader = new Reader(content);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">126</strong> const selfClosing = reader.clipEnd('/');</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">127</strong></pre></div>
<div class="line"><div class="doc"><p>Read the individual characters into a list of tokens:
things that may be attribute names, and values.</p>
</div><pre class="source javascript"><strong class="lineNumber">130</strong> let head = '';</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Are we waiting to read an attribute value?</p>
</div><pre class="source javascript"><strong class="lineNumber">132</strong> let waitingForAttr = false;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Are we in a quoted attribute value?</p>
</div><pre class="source javascript"><strong class="lineNumber">134</strong> let inQuotes = false;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Array of parsed tokens</p>
</div><pre class="source javascript"><strong class="lineNumber">136</strong> const tokens = [];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">137</strong> const TYPE_KEY = 0;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">138</strong> const TYPE_VALUE = 1;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">139</strong></pre></div>
<div class="line"><div class="doc"><p>Is the next token a key or a value? This is determined by the presence
of equals signs <code>=</code>, quotations, and whitespace.</p>
</div><pre class="source javascript"><strong class="lineNumber">142</strong> let nextType = TYPE_KEY;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">143</strong></pre></div>
<div class="line"><div class="doc"><p>Commit what's currently read as a new token.</p>
</div><pre class="source javascript"><strong class="lineNumber">145</strong> const commitToken = force => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">146</strong> head = head.trim();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">147</strong> if (head !== '' || force) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">148</strong> tokens.push({</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">149</strong> type: nextType,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">150</strong> value: head,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">151</strong> });</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">152</strong> waitingForAttr = false;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">153</strong> head = '';</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">154</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">155</strong> }</pre></div>
<div class="line"><div class="doc"><p>Iterate through each read character from the reader and parse the character
stream into tokens.</p>
</div><pre class="source javascript"><strong class="lineNumber">158</strong> for (let next = reader.next(); next !== undefined; next = reader.next()) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">159</strong> switch (next) {</pre></div>
<div class="line"><div class="doc"><p>Equals sign denotes the start of an attribute value unless in quotes</p>
</div><pre class="source javascript"><strong class="lineNumber">161</strong> case '=':</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">162</strong> if (inQuotes) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">163</strong> head += next;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">164</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">165</strong> commitToken();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">166</strong> waitingForAttr = true;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">167</strong> nextType = TYPE_VALUE;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">168</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">169</strong> break;</pre></div>
<div class="line"><div class="doc"><p>Because we replaced all whitespace with spaces earlier, this catches
all whitespaces. Whitespaces are only meaningful separates of values
if we're not in quotes.</p>
</div><pre class="source javascript"><strong class="lineNumber">173</strong> case ' ':</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">174</strong> if (inQuotes) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">175</strong> head += next;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">176</strong> } else if (!waitingForAttr) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">177</strong> commitToken();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">178</strong> nextType = TYPE_KEY;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">179</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">180</strong> break;</pre></div>
<div class="line"><div class="doc"><p>Allow backslash to escape characters if we're in quotes.</p>
</div><pre class="source javascript"><strong class="lineNumber">182</strong> case '\\':</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">183</strong> if (inQuotes) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">184</strong> next = reader.next();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">185</strong> head += next;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">186</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">187</strong> break;</pre></div>
<div class="line"><div class="doc"><p>If we're in quotes, '"' escapes quotes. Otherwise, it opens
a quoted section.</p>
</div><pre class="source javascript"><strong class="lineNumber">190</strong> case '"':</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">191</strong> if (inQuotes) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">192</strong> inQuotes = false;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">193</strong> commitToken(true);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">194</strong> nextType = TYPE_KEY;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">195</strong> } else if (nextType === TYPE_VALUE) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">196</strong> inQuotes = true;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">197</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">198</strong> break;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">199</strong> default:</pre></div>
<div class="line"><div class="doc"><p>Append all other characters to the head</p>
</div><pre class="source javascript"><strong class="lineNumber">201</strong> head += next;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">202</strong> waitingForAttr = false;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">203</strong> break;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">204</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">205</strong> }</pre></div>
<div class="line"><div class="doc"><p>If we haven't committed any last-read tokens, commit it now.</p>
</div><pre class="source javascript"><strong class="lineNumber">207</strong> commitToken();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">208</strong></pre></div>
<div class="line"><div class="doc"><p>Now, we parse the tokens into tag, attribute, and events values in the JDOM.</p>
</div><pre class="source javascript"><strong class="lineNumber">210</strong> let tag = '';</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">211</strong> const attrs = {};</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">212</strong> const events = {};</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">213</strong></pre></div>
<div class="line"><div class="doc"><p>The tag name is always the first token</p>
</div><pre class="source javascript"><strong class="lineNumber">215</strong> tag = tokens.shift().value;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">216</strong> let last = null;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">217</strong> let curr = tokens.shift();</pre></div>
<div class="line"><div class="doc"><p>Function to step through to the next token</p>
</div><pre class="source javascript"><strong class="lineNumber">219</strong> const step = () => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">220</strong> last = curr;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">221</strong> curr = tokens.shift();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">222</strong> }</pre></div>
<div class="line"><div class="doc"><p>Walk through the token list. If the token is a value token,
the previous token is its key. If the current token is a key,
the previous token is an attribute without value (like <code>disabled</code>).</p>
</div><pre class="source javascript"><strong class="lineNumber">226</strong> while (curr !== undefined) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">227</strong> if (curr.type === TYPE_VALUE) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">228</strong> const key = last.value;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">229</strong> let val = curr.value.trim();</pre></div>
<div class="line"><div class="doc"><p>Commit a key-value pair of string attributes to the JDOM stub. This section
treats class lists and style dictionaries separately, and adds function
values as event handlers.</p>
</div><pre class="source javascript"><strong class="lineNumber">233</strong> if (key.startsWith('on')) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">234</strong> events[key.substr(2)] = [val];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">235</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">236</strong> if (key === 'class') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">237</strong> if (val !== '') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">238</strong> attrs[key] = val.split(' ');</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">239</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">240</strong> } else if (key === 'style') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">241</strong> if (val.endsWith(';')) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">242</strong> val = val.substr(0, val.length - 1);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">243</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">244</strong> const rule = {};</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">245</strong> for (const pair of val.split(';')) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">246</strong> const idx = pair.indexOf(':');</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">247</strong> const first = pair.substr(0, idx);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">248</strong> const rest = pair.substr(idx + 1);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">249</strong> rule[kebabToCamel(first.trim())] = rest.trim();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">250</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">251</strong> attrs[key] = rule;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">252</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">253</strong> attrs[key] = val;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">254</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">255</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">256</strong> step();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">257</strong> } else if (last) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">258</strong> attrs[last.value] = true;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">259</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">260</strong> step();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">261</strong> }</pre></div>
<div class="line"><div class="doc"><p>If the last value is a value-less attribute (like <code>disabled</code>), commit it.</p>
</div><pre class="source javascript"><strong class="lineNumber">263</strong> if (last && last.type === TYPE_KEY) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">264</strong> attrs[last.value] = true;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">265</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">266</strong></pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">267</strong> return {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">268</strong> jdom: {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">269</strong> tag: tag,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">270</strong> attrs: attrs,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">271</strong> events: events,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">272</strong> },</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">273</strong> selfClosing: selfClosing,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">274</strong> };</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">275</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">276</strong></pre></div>
<div class="line"><div class="doc"><p>Function to parse an entire JDOM template tree (which we vaguely call JSX here).
This recursively calls itself on children elements.</p>
</div><pre class="source javascript"><strong class="lineNumber">279</strong>const parseTemplate = reader => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">280</strong> const result = [];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">281</strong></pre></div>
<div class="line"><div class="doc"><p>The current JDOM object being worked on. Sort of an "element register"</p>
</div><pre class="source javascript"><strong class="lineNumber">283</strong> let currentElement = null;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Are we reading a text node (and should ignore special characters)?</p>
</div><pre class="source javascript"><strong class="lineNumber">285</strong> let inTextNode = false;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Commit currently reading element to the result list, and reset the current element</p>
</div><pre class="source javascript"><strong class="lineNumber">287</strong> const commit = () => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>If the text node we're about to commit is just whitespace, don't bother</p>
</div><pre class="source javascript"><strong class="lineNumber">289</strong> if (inTextNode && currentElement.trim() === '') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">290</strong> // pass</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">291</strong> } else if (currentElement) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">292</strong> result.push(currentElement);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">293</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">294</strong> currentElement = null;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">295</strong> inTextNode = false;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">296</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">297</strong></pre></div>
<div class="line"><div class="doc"><p>Shortcut to handle/commit string tokens properly, which we do more than once below.</p>
</div><pre class="source javascript"><strong class="lineNumber">299</strong> const handleString = next => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">300</strong> if (inTextNode === false) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">301</strong> commit();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">302</strong> inTextNode = true;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">303</strong> currentElement = '';</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">304</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">305</strong> currentElement += next;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">306</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">307</strong></pre></div>
<div class="line"><div class="doc"><p>Main parsing logic. This might be confusingly recursive. In essence, the parser
recursively calls itself with its <code>reader</code> if it has children to parse,
and trusts that the parser will return when it encounters the closing tag
that marks the end of the list of children. So, the parser breaks the loop
and returns if it encounters a closing tag. This cooperation between the function
and the parent function that called it recursively makes this parser work.</p>
</div><pre class="source javascript"><strong class="lineNumber">314</strong> for (let next = reader.next(); next !== undefined; next = reader.next()) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>if we see the start of a tag ...</p>
</div><pre class="source javascript"><strong class="lineNumber">316</strong> if (next === '<') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>... first commit any previous reads, since we're starting a new node ...</p>
</div><pre class="source javascript"><strong class="lineNumber">318</strong> commit();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>... it's an opening tag if the next character isn't <code>'/'</code>.</p>
</div><pre class="source javascript"><strong class="lineNumber">320</strong> if (reader.next() !== '/') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">321</strong> reader.back();</pre></div>
<div class="line"><div class="doc"><p>Read and parse the contents of the tag up to the end of
the opening tag.</p>
</div><pre class="source javascript"><strong class="lineNumber">324</strong> const result = parseOpeningTagContents(reader.readUpto('>'));</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">325</strong> reader.next(); // read the '>'</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">326</strong> currentElement = result && result.jdom;</pre></div>
<div class="line"><div class="doc"><p>If the current element is a full-fledged element (and not a comment
or text node), let's try to parse the children by handing the reader
to a recursively call of this function.</p>
</div><pre class="source javascript"><strong class="lineNumber">330</strong> if (!result.selfClosing && currentElement !== null) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">331</strong> currentElement.children = parseTemplate(reader);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">332</strong> }</pre></div>
<div class="line"><div class="doc"><p>... it's a closing tag otherwise ...</p>
</div><pre class="source javascript"><strong class="lineNumber">334</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>... so finish out reading the closing tag.
A top-level closing tag means it's actually closing the parent tag, so
we need to stop parsing and hand the parsing flow back to the parent
call in this recursive function.</p>
</div><pre class="source javascript"><strong class="lineNumber">339</strong> reader.readUntil('>');</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">340</strong> break;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">341</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">342</strong> } else {</pre></div>
<div class="line"><div class="doc"><p>If an HTML entity is encoded (e.g. < is '<'), decode it and handle it.</p>
</div><pre class="source javascript"><strong class="lineNumber">344</strong> if (next === '&') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">345</strong> handleString(decodeEntity(next + reader.readUntil(';')));</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">346</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">347</strong> handleString(next);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">348</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">349</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">350</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">351</strong></pre></div>
<div class="line"><div class="doc"><p>Commit any last remaining tokens as-is</p>
</div><pre class="source javascript"><strong class="lineNumber">353</strong> commit();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">354</strong></pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">355</strong> return result;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">356</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">357</strong></pre></div>
<div class="line"><div class="doc"><p>Cache for <code>jdom</code>, keyed by the string parts, value is a function that takes the dynamic
parts of the template as input and returns the result of parseTemplate. We make an assumption
here that the user of the template won't swap between having an element attribute being
a function once and something that isn't a function the next time. In practice this is fine.</p>
</div><pre class="source javascript"><strong class="lineNumber">362</strong>const JDOM_CACHE = new Map();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>This HTML parsing algorithm works by replacing all the dynamic parts with a unique string,
parsing the string markup into a JSON tree, and caching that tree. On renders, we walk the tree
and replace any matching strings with their correct dynamic parts. This makes the algorithm
cache-friendly and relatively fast, despite doing a lot at runtime. <code>JDOM_PLACEHOLDER_RE</code> is
the regex we use to correlate string keys to their correct dynamic parts.</p>
</div><pre class="source javascript"><strong class="lineNumber">368</strong>const JDOM_PLACEHOLDER_RE = /jdom_tpl_obj_\[(\d+)\]/;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>This is for a performance optimization, that when we're filling out template
strings, if a string in which we're searching for a placeholder is shorter than
placeholder strings, we just stop searching.</p>
</div><pre class="source javascript"><strong class="lineNumber">372</strong>const JDOM_PLACEHOLDER_MIN_LENGTH = 14;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">373</strong></pre></div>
<div class="line"><div class="doc"><p>Does a given string have a placeholder for the template values?</p>
</div><pre class="source javascript"><strong class="lineNumber">375</strong>const hasPlaceholder = str => typeof str === 'string' && str.includes('jdom_tpl_');</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">376</strong></pre></div>
<div class="line"><div class="doc"><p><strong>Utility functions for walking a JSON tree and filling in placeholders</strong>
The functions here that take mutable values (arrays, objects) will mutate the
given value to be faster than creating new objects.</p>
</div><pre class="source javascript"><strong class="lineNumber">380</strong></pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Given a string, replace placeholders with their correct dynamic parts and return
the result as an array, so if any dynamic values are objects or HTML nodes, they
are not cast to strings. Used to parse HTML children.</p>
</div><pre class="source javascript"><strong class="lineNumber">384</strong>const splitByPlaceholder = (str, dynamicParts) => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">385</strong> if (hasPlaceholder(str)) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">386</strong> const match = JDOM_PLACEHOLDER_RE.exec(str);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">387</strong> const parts = str.split(match[0]);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">388</strong> const number = match[1];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">389</strong> const processedBack = splitByPlaceholder(parts[1], dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">390</strong></pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">391</strong> let result = [];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">392</strong> if (parts[0] !== '') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">393</strong> result.push(parts[0]);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">394</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">395</strong> if (Array.isArray(dynamicParts[number])) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">396</strong> result = result.concat(dynamicParts[number]);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">397</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">398</strong> result.push(dynamicParts[number]);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">399</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">400</strong> if (processedBack.length !== 0) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">401</strong> result = result.concat(processedBack);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">402</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">403</strong> return result;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">404</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">405</strong> return str !== '' ? [str] : [];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">406</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">407</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">408</strong></pre></div>
<div class="line"><div class="doc"><p>Given an array of child JDOM elements, flatten that list of children
into a flat array and parse any placeholders in it.</p>
</div><pre class="source javascript"><strong class="lineNumber">411</strong>const replaceChildrenToFlatArray = (children, dynamicParts) => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">412</strong> const newChildren = [];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">413</strong> for (const childString of children) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">414</strong> for (const child of splitByPlaceholder(childString, dynamicParts)) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">415</strong> if (isObject(child)) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">416</strong> replaceInObjectLiteral(child, dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">417</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">418</strong> newChildren.push(child);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">419</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">420</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">421</strong> const first = newChildren[0];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">422</strong> const last = newChildren[newChildren.length - 1];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">423</strong> if (typeof first === 'string' && first.trim() === '') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">424</strong> newChildren.shift();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">425</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">426</strong> if (typeof last === 'string' && last.trim() === '') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">427</strong> newChildren.pop();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">428</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">429</strong> return newChildren;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">430</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">431</strong></pre></div>
<div class="line"><div class="doc"><p>Given a string, replace any placeholder values and return a new string.</p>
</div><pre class="source javascript"><strong class="lineNumber">433</strong>const replaceInString = (str, dynamicParts) => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>As an optimization, if the string is too short to contain placeholders,
just return early.</p>
</div><pre class="source javascript"><strong class="lineNumber">436</strong> if (str.length < JDOM_PLACEHOLDER_MIN_LENGTH) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">437</strong> return str;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">438</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">439</strong> const match = JDOM_PLACEHOLDER_RE.exec(str);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">440</strong> if (match === null) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">441</strong> return str;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">442</strong> } else if (str.trim() === match[0]) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">443</strong> return dynamicParts[match[1]];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">444</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">445</strong> const parts = str.split(match[0]);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">446</strong> return (parts[0] + dynamicParts[match[1]]</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">447</strong> + replaceInString(parts[1], dynamicParts));</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">448</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">449</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">450</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">451</strong></pre></div>
<div class="line"><div class="doc"><p>Given an array literal, replace placeholders in it and its children, recursively.</p>
</div><pre class="source javascript"><strong class="lineNumber">453</strong>const replaceInArrayLiteral = (arr, dynamicParts) => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">454</strong> for (let i = 0, len = arr.length; i < len; i ++) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">455</strong> const val = arr[i];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">456</strong> if (typeof val === 'string') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">457</strong> arr[i] = replaceInString(val, dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">458</strong> } else if (Array.isArray(val)) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">459</strong> replaceInArrayLiteral(val, dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">460</strong> } else { // it's an object otherwise</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">461</strong> replaceInObjectLiteral(val, dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">462</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">463</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">464</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">465</strong></pre></div>
<div class="line"><div class="doc"><p>Given an object, replace placeholders in it and its values.</p>
</div><pre class="source javascript"><strong class="lineNumber">467</strong>const replaceInObjectLiteral = (obj, dynamicParts) => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">468</strong> if (obj instanceof Node) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">469</strong> return;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">470</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">471</strong></pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">472</strong> for (const prop of Object.keys(obj)) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">473</strong> const val = obj[prop];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">474</strong> if (typeof val === 'string') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">475</strong> obj[prop] = replaceInString(val, dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">476</strong> } else if (Array.isArray(val)) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">477</strong> if (prop === 'children') {</pre></div>
<div class="line"><div class="doc"><p>We need to treat children of JDOM objects differently because
they need to all be flat arrays, and sometimes for API convenience
they're passed in as nested arrays.</p>
</div><pre class="source javascript"><strong class="lineNumber">481</strong> obj.children = replaceChildrenToFlatArray(val, dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">482</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">483</strong> replaceInArrayLiteral(val, dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">484</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">485</strong> } else if (isObject(val)) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">486</strong> replaceInObjectLiteral(val, dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">487</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">488</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">489</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">490</strong></pre></div>
<div class="line"><div class="doc"><p><code>jdom</code> template tag, using the JDOM parsed templates cache from above.</p>
</div><pre class="source javascript"><strong class="lineNumber">492</strong>const jdom = (tplParts, ...dynamicParts) => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>The key for our cache is just the string parts, joined together with a unique joiner string.</p>
</div><pre class="source javascript"><strong class="lineNumber">494</strong> const cacheKey = tplParts.join('jdom_tpl_joiner');</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">495</strong> try {</pre></div>
<div class="line"><div class="doc"><p>If we don't have the template in cache, we need to put a translator function
in the cache now.</p>
</div><pre class="source javascript"><strong class="lineNumber">498</strong> if (!JDOM_CACHE.has(cacheKey)) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Generate placeholder string values for each dynamic value in the template</p>
</div><pre class="source javascript"><strong class="lineNumber">500</strong> const dpPlaceholders = dynamicParts.map((_obj, i) => `jdom_tpl_obj_[${i}]`);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">501</strong></pre></div>
<div class="line"><div class="doc"><p>Make a new reader, interpolating the template's static and dynamic parts together.</p>
</div><pre class="source javascript"><strong class="lineNumber">503</strong> const reader = new Reader(interpolate(tplParts.map(part => part.replace(/\s+/g, ' ')), dpPlaceholders));</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Parse the template and take the first child, if there are more, as the element we care about.</p>
</div><pre class="source javascript"><strong class="lineNumber">505</strong> const result = parseTemplate(reader)[0];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">506</strong> const resultType = typeof result;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">507</strong> const resultString = JSON.stringify(result);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">508</strong></pre></div>
<div class="line"><div class="doc"><p>Put a function into the cache that translates an array of the dynamic parts of a template
into the full JDOM for the template.</p>
</div><pre class="source javascript"><strong class="lineNumber">511</strong> JDOM_CACHE.set(cacheKey, dynamicParts => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">512</strong> if (resultType === 'string') {</pre></div>
<div class="line"><div class="doc"><p>If the result of the template is just a string, replace stuff in the string</p>
</div><pre class="source javascript"><strong class="lineNumber">514</strong> return replaceInString(result, dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">515</strong> } else if (resultType === 'object') {</pre></div>
<div class="line"><div class="doc"><p>Recall that the template translating functions above mutate the object passed
in wherever possible. so we make a brand-new object to represent a new result.</p>
</div><pre class="source javascript"><strong class="lineNumber">518</strong> const target = {};</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Since the non-dynamic parts of JDOM objects are by definition completely JSON
serializable, this is a good enough way to deep-copy the cached result of <code>parseTemplate()</code>.</p>
</div><pre class="source javascript"><strong class="lineNumber">521</strong> const template = JSON.parse(resultString);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">522</strong> replaceInObjectLiteral(Object.assign(target, template), dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">523</strong> return target;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">524</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">525</strong> return null;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">526</strong> });</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">527</strong> }</pre></div>
<div class="line"><div class="doc"><p>Now that we have a translator function in the cache, call that to get a new template result.</p>
</div><pre class="source javascript"><strong class="lineNumber">529</strong> return JDOM_CACHE.get(cacheKey)(dynamicParts);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">530</strong> } catch (e) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">531</strong> /* istanbul ignore next: haven't found error cases that trigger this, but exists just in case */</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">532</strong> console.error(`jdom parse error.\ncheck for mismatched brackets, tags, quotes.\n${</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">533</strong> interpolate(tplParts, dynamicParts)}\n${e.stack || e}`);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">534</strong> /* istanbul ignore next: see above */</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">535</strong> return '';</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">536</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">537</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">538</strong></pre></div>
<div class="line"><div class="doc"><p>Helper to convert a string representation of a dict into a JavaScript object, CSS-style.
<code>stringToDict</code> is recursive to parse nested dictionaries.</p>
</div><pre class="source javascript"><strong class="lineNumber">541</strong>const stringToDict = reader => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">542</strong></pre></div>
<div class="line"><div class="doc"><p>Dictionary to be constructed from this step</p>
</div><pre class="source javascript"><strong class="lineNumber">544</strong> const dict = {};</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">545</strong></pre></div>
<div class="line"><div class="doc"><p>Enums for marking whether we are in a key or value section of a dictionary</p>
</div><pre class="source javascript"><strong class="lineNumber">547</strong> const PROP = 0;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">548</strong> const VAL = 1;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">549</strong> let part = PROP;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">550</strong></pre></div>
<div class="line"><div class="doc"><p>Current key, value pair being parsed</p>
</div><pre class="source javascript"><strong class="lineNumber">552</strong> let current = ['', ''];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Utility function to commit the tokens in the current read buffer (<code>current</code>) to the
result dictionary and move on to the next key-value pair.</p>
</div><pre class="source javascript"><strong class="lineNumber">555</strong> const commit = () => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">556</strong> if (typeof current[VAL] === 'string') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">557</strong> dict[current[PROP].trim()] = current[VAL].trim();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">558</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">559</strong> dict[current[PROP].trim()] = current[VAL];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">560</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">561</strong> current = ['', ''];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">562</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">563</strong></pre></div>
<div class="line"><div class="doc"><p>Begin reading the dictionary by stripping off any whitespace before the starting curlybrace.</p>
</div><pre class="source javascript"><strong class="lineNumber">565</strong> reader.readUntil('{');</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Loop through each character in the string being read...</p>
</div><pre class="source javascript"><strong class="lineNumber">567</strong> for (let next = reader.next(); next !== undefined; next = reader.next()) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>If we encounter a closing brace, we assume it's the end of the dictionary at the current
level of nesting, so we halt parsing at this step and return.</p>
</div><pre class="source javascript"><strong class="lineNumber">570</strong> if (next === '}') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">571</strong> break;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">572</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">573</strong> const p = current[PROP];</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">574</strong> switch (next) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">575</strong> case '"':</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">576</strong> case '\'':</pre></div>
<div class="line"><div class="doc"><p>If we encounter quotes, we read blindly until the end of the quoted section,
ignoring escaped quotes. This is a slightly strange but simple way to achieve that.</p>
</div><pre class="source javascript"><strong class="lineNumber">579</strong> current[part] += next + reader.readUntil(next);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">580</strong> while (current[part].endsWith('\\' + next)) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">581</strong> current[part] += reader.readUntil(next);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">582</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">583</strong> break;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">584</strong> case ':':</pre></div>
<div class="line"><div class="doc"><p>The colon character is ambiguous in SCSS syntax, because it is used
for pseudoselectors and pseudoelements, as well as in the dict syntax.
We disambiguate by looking at the preceding part of the token.</p>
</div><pre class="source javascript"><strong class="lineNumber">588</strong> if (</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">589</strong> p.trim() === '' // empty key is not a thing; probably pseudoselector</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">590</strong> || p.includes('&') // probably part of nested SCSS selector</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">591</strong> || p.includes('@') // probably part of media query</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">592</strong> || p.includes(':') // probably pseudoselector/ pseudoelement selector</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">593</strong> ) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">594</strong> current[part] += next;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">595</strong> } else {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">596</strong> part = VAL;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">597</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">598</strong> break;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">599</strong> case ';':</pre></div>
<div class="line"><div class="doc"><p>Commit read tokens if we've reached the end of the rule</p>
</div><pre class="source javascript"><strong class="lineNumber">601</strong> part = PROP;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">602</strong> commit();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">603</strong> break;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">604</strong> case '{':</pre></div>
<div class="line"><div class="doc"><p>If we come across <code>{</code>, this means we found a nested structure.
We backtrack the reader and recursively call <code>stringToDict</code> to parse the nested dict first
before moving on.</p>
</div><pre class="source javascript"><strong class="lineNumber">608</strong> reader.back();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">609</strong> current[VAL] = stringToDict(reader);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">610</strong> commit();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">611</strong> break;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">612</strong> default:</pre></div>
<div class="line"><div class="doc"><p>For all other characters, just append it to the currently read buffer.</p>
</div><pre class="source javascript"><strong class="lineNumber">614</strong> current[part] += next;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">615</strong> break;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">616</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">617</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">618</strong></pre></div>
<div class="line"><div class="doc"><p>Take care of any dangling CSS rules without a semicolon.</p>
</div><pre class="source javascript"><strong class="lineNumber">620</strong> if (current[PROP].trim() !== '') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">621</strong> commit();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">622</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">623</strong></pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">624</strong> return dict;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">625</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">626</strong></pre></div>
<div class="line"><div class="doc"><p>Cache for CSS parser outputs</p>
</div><pre class="source javascript"><strong class="lineNumber">628</strong>const CSS_CACHE = new Map();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">629</strong></pre></div>
<div class="line"><div class="doc"><p><code>css</code> is a CSS parser that takes a string and returns CSS style objects for JDOM.</p>
</div><pre class="source javascript"><strong class="lineNumber">631</strong>const css = (tplParts, ...dynamicParts) => {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>Parse template as a string first</p>
</div><pre class="source javascript"><strong class="lineNumber">633</strong> const result = interpolate(tplParts, dynamicParts).trim();</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber"></strong></pre></div>
<div class="line"><div class="doc"><p>If the CSS rule had not been parsed before (is not in the cache),
parse and cache it before returning it.</p>
</div><pre class="source javascript"><strong class="lineNumber">636</strong> if (!CSS_CACHE.has(result)) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">637</strong> CSS_CACHE.set(result, stringToDict(new Reader('{' + result + '}')));</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">638</strong> }</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">639</strong> return CSS_CACHE.get(result);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">640</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">641</strong></pre></div>
<div class="line"><div class="doc"><p>Expose both template tags as globals</p>
</div><pre class="source javascript"><strong class="lineNumber">643</strong>const exposedNames = {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">644</strong> jdom,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">645</strong> css,</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">646</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">647</strong></pre></div>
<div class="line"><div class="doc"><p>If there is a global <code>window</code> object, bind API names to it.</p>
</div><pre class="source javascript"><strong class="lineNumber">649</strong>/* istanbul ignore else */</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">650</strong>if (typeof window === 'object') {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">651</strong> Object.assign(window, exposedNames);</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">652</strong>}</pre></div>
<div class="line"><div class="doc"><p>Export public APIs CommonJS-style</p>
</div><pre class="source javascript"><strong class="lineNumber">654</strong>/* istanbul ignore next */</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">655</strong>if (typeof module === 'object' && module.exports) {</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">656</strong> module.exports = exposedNames;</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">657</strong>}</pre></div>
<div class="line"><div class="doc"></div><pre class="source javascript"><strong class="lineNumber">658</strong></pre></div>
</main>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/github-gist.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"></script>
<script>
for (const el of document.querySelectorAll('.line pre')) {
hljs.highlightBlock(el);
}
</script>
</body>
</html>