lxml-css.lua /size: 33 Kb    last modification: 2021-10-28 13:50
1
if
not
modules
then
modules
=
{
}
end
modules
[
'
lxml-css
'
]
=
{
2
version
=
1
.
001
,
3
comment
=
"
companion to lxml-css.mkiv
"
,
4
author
=
"
Hans Hagen, PRAGMA-ADE, Hasselt NL
"
,
5
copyright
=
"
PRAGMA ADE / ConTeXt Development Team
"
,
6
license
=
"
see context related readme files
"
7
}
8 9
local
tonumber
,
rawset
,
type
,
select
=
tonumber
,
rawset
,
type
,
select
10
local
lower
,
format
,
find
,
gmatch
=
string
.
lower
,
string
.
format
,
string
.
find
,
string
.
gmatch
11
local
topattern
,
is_empty
=
string
.
topattern
,
string
.
is_empty
12
local
P
,
S
,
C
,
R
,
Cb
,
Cg
,
Carg
,
Ct
,
Cc
,
Cf
,
Cs
=
lpeg
.
P
,
lpeg
.
S
,
lpeg
.
C
,
lpeg
.
R
,
lpeg
.
Cb
,
lpeg
.
Cg
,
lpeg
.
Carg
,
lpeg
.
Ct
,
lpeg
.
Cc
,
lpeg
.
Cf
,
lpeg
.
Cs
13
local
lpegmatch
,
lpegpatterns
=
lpeg
.
match
,
lpeg
.
patterns
14
local
sort
=
table
.
sort
15
local
setmetatableindex
=
table
.
setmetatableindex
16 17
xml
.
css
=
xml
.
css
or
{
}
18
local
css
=
xml
.
css
19 20
local
report_css
=
logs
and
logs
.
reporter
(
"
xml
"
,
"
css
"
)
or
function
(
...
)
print
(
string
.
format
(
...
)
)
end
21 22
local
getid
=
lxml
.
getid
23 24
if
not
number
.
dimenfactors
then
25
require
(
"
util-dim.lua
"
)
26
end
27 28
local
dimenfactors
=
number
.
dimenfactors
29
local
bpf
=
1
/
dimenfactors
.
bp
30
local
cmf
=
1
/
dimenfactors
.
cm
31
local
mmf
=
1
/
dimenfactors
.
mm
32
local
inf
=
1
/
dimenfactors
[
"
in
"
]
33 34
local
whitespace
=
lpegpatterns
.
whitespace
35
local
skipspace
=
whitespace
^
0
36 37
local
percentage
,
exheight
,
emwidth
,
pixels
38 39
if
tex
then
40 41
local
exheights
=
fonts
.
hashes
.
exheights
42
local
emwidths
=
fonts
.
hashes
.
emwidths
43
local
texget
=
tex
.
get
44 45
percentage
=
function
(
s
,
pcf
)
return
tonumber
(
s
)
*
(
pcf
or
texget
(
"
hsize
"
)
)
end
46
exheight
=
function
(
s
,
exf
)
return
tonumber
(
s
)
*
(
exf
or
exheights
[
true
]
)
end
47
emwidth
=
function
(
s
,
emf
)
return
tonumber
(
s
)
*
(
emf
or
emwidths
[
true
]
)
end
48
pixels
=
function
(
s
,
pxf
)
return
tonumber
(
s
)
*
(
pxf
or
emwidths
[
true
]
/
300
)
end
49 50
else
51 52
local
function
generic
(
s
,
unit
)
return
tonumber
(
s
)
*
unit
end
53 54
percentage
=
generic
55
exheight
=
generic
56
emwidth
=
generic
57
pixels
=
generic
58 59
end
60 61
local
validdimen
=
Cg
(
lpegpatterns
.
number
,
'
a
'
)
*
(
62
Cb
(
'
a
'
)
*
P
(
"
pt
"
)
/
function
(
s
)
return
tonumber
(
s
)
*
bpf
end
63
+
Cb
(
'
a
'
)
*
P
(
"
cm
"
)
/
function
(
s
)
return
tonumber
(
s
)
*
cmf
end
64
+
Cb
(
'
a
'
)
*
P
(
"
mm
"
)
/
function
(
s
)
return
tonumber
(
s
)
*
mmf
end
65
+
Cb
(
'
a
'
)
*
P
(
"
in
"
)
/
function
(
s
)
return
tonumber
(
s
)
*
inf
end
66
+
Cb
(
'
a
'
)
*
P
(
"
px
"
)
*
Carg
(
1
)
/
pixels
67
+
Cb
(
'
a
'
)
*
P
(
"
%
"
)
*
Carg
(
2
)
/
percentage
68
+
Cb
(
'
a
'
)
*
P
(
"
ex
"
)
*
Carg
(
3
)
/
exheight
69
+
Cb
(
'
a
'
)
*
P
(
"
em
"
)
*
Carg
(
4
)
/
emwidth
70
+
Cb
(
'
a
'
)
*
Carg
(
1
)
/
pixels
71
)
72 73
local
pattern
=
(
validdimen
*
skipspace
)
^
1
74 75
-- todo: default if ""
76 77
local
function
dimension
(
str
,
pixel
,
percent
,
exheight
,
emwidth
)
78
return
(
lpegmatch
(
pattern
,
str
,
1
,
pixel
,
percent
,
exheight
,
emwidth
)
)
79
end
80 81
local
function
padding
(
str
,
pixel
,
percent
,
exheight
,
emwidth
)
82
local
top
,
bottom
,
left
,
right
=
lpegmatch
(
pattern
,
str
,
1
,
pixel
,
percent
,
exheight
,
emwidth
)
83
if
not
bottom
then
84
bottom
,
left
,
right
=
top
,
top
,
top
85
elseif
not
left
then
86
bottom
,
left
,
right
=
top
,
bottom
,
bottom
87
elseif
not
right
then
88
bottom
,
left
,
right
=
left
,
bottom
,
bottom
89
end
90
return
top
,
bottom
,
left
,
right
91
end
92 93
css
.
dimension
=
dimension
94
css
.
padding
=
padding
95 96
-- local hsize = 655360*100
97
-- local exheight = 65536*4
98
-- local emwidth = 65536*10
99
-- local pixel = emwidth/100
100
--
101
-- print(padding("10px",pixel,hsize,exheight,emwidth))
102
-- print(padding("10px 20px",pixel,hsize,exheight,emwidth))
103
-- print(padding("10px 20px 30px",pixel,hsize,exheight,emwidth))
104
-- print(padding("10px 20px 30px 40px",pixel,hsize,exheight,emwidth))
105
--
106
-- print(padding("10%",pixel,hsize,exheight,emwidth))
107
-- print(padding("10% 20%",pixel,hsize,exheight,emwidth))
108
-- print(padding("10% 20% 30%",pixel,hsize,exheight,emwidth))
109
-- print(padding("10% 20% 30% 40%",pixel,hsize,exheight,emwidth))
110
--
111
-- print(padding("10",pixel,hsize,exheight,emwidth))
112
-- print(padding("10 20",pixel,hsize,exheight,emwidth))
113
-- print(padding("10 20 30",pixel,hsize,exheight,emwidth))
114
-- print(padding("10 20 30 40",pixel,hsize,exheight,emwidth))
115
--
116
-- print(padding("10pt",pixel,hsize,exheight,emwidth))
117
-- print(padding("10pt 20pt",pixel,hsize,exheight,emwidth))
118
-- print(padding("10pt 20pt 30pt",pixel,hsize,exheight,emwidth))
119
-- print(padding("10pt 20pt 30pt 40pt",pixel,hsize,exheight,emwidth))
120 121
-- print(padding("0",pixel,hsize,exheight,emwidth))
122 123
local
context
=
context
124 125
if
context
then
126 127
local
currentfont
=
font
.
current
128
local
texget
=
tex
.
get
129
local
hashes
=
fonts
.
hashes
130
local
quads
=
hashes
.
quads
131
local
xheights
=
hashes
.
xheights
132 133
local
function
todimension
(
str
)
134
local
font
=
currentfont
(
)
135
local
exheight
=
xheights
[
font
]
136
local
emwidth
=
quads
[
font
]
137
local
hsize
=
texget
(
"
hsize
"
)
/
100
138
local
pixel
=
emwidth
/
100
139
return
dimension
(
str
,
pixel
,
hsize
,
exheight
,
emwidth
)
140
end
141 142
css
.
todimension
=
todimension
143 144
function
context
.
cssdimension
(
str
)
145
-- context("%ssp",todimension(str))
146
context
(
todimension
(
str
)
.
.
"
sp
"
)
147
end
148 149
end
150 151 152
do
153 154
local
p_digit
=
lpegpatterns
.
digit
155
local
p_unquoted
=
Cs
(
lpegpatterns
.
unquoted
)
156
local
p_size
=
(
S
(
"
+-
"
)
^
0
*
(
p_digit
^
0
*
P
(
"
.
"
)
*
p_digit
^
1
+
p_digit
^
1
*
P
(
"
.
"
)
+
p_digit
^
1
)
)
/
tonumber
157
*
C
(
P
(
"
p
"
)
*
S
(
"
txc
"
)
+
P
(
"
e
"
)
*
S
(
"
xm
"
)
+
S
(
"
mc
"
)
*
P
(
"
m
"
)
+
P
(
"
in
"
)
+
P
(
"
%
"
)
)
158 159
local
pattern
=
Cf
(
Ct
(
"
"
)
*
(
160
Cg
(
161
Cc
(
"
style
"
)
*
(
162
C
(
"
italic
"
)
163
+
C
(
"
oblique
"
)
164
+
C
(
"
slanted
"
)
/
"
oblique
"
165
)
166
+
Cc
(
"
variant
"
)
*
(
167
(
C
(
"
smallcaps
"
)
+
C
(
"
caps
"
)
)
/
"
small-caps
"
168
)
169
+
Cc
(
"
weight
"
)
*
(
170
C
(
"
bold
"
)
171
)
172
+
Cc
(
"
family
"
)
*
(
173
(
C
(
"
mono
"
)
+
C
(
"
type
"
)
)
/
"
monospace
"
-- just ignore the "space(d)"
174
+
(
C
(
"
sansserif
"
)
+
C
(
"
sans
"
)
)
/
"
sans-serif
"
-- match before serif
175
+
C
(
"
serif
"
)
176
)
177
+
Cc
(
"
size
"
)
*
Ct
(
p_size
)
178
)
179
+
P
(
1
)
180
)
^
0
,
rawset
)
181 182
function
css
.
fontspecification
(
str
)
183
return
str
and
lpegmatch
(
pattern
,
lower
(
str
)
)
184
end
185 186
-- These map onto context!
187 188
function
css
.
style
(
str
)
189
if
str
and
str
~
=
"
"
then
190
str
=
lower
(
str
)
191
if
str
=
=
"
italic
"
then
192
return
"
italic
"
193
elseif
str
=
=
"
slanted
"
or
str
=
=
"
oblique
"
then
194
return
"
slanted
"
195
end
196
end
197
return
"
normal
"
198
end
199 200
function
css
.
variant
(
str
)
-- will change to a feature
201
if
str
and
str
~
=
"
"
then
202
str
=
lower
(
str
)
203
if
str
=
=
"
small-caps
"
or
str
=
=
"
caps
"
or
str
=
=
"
smallcaps
"
then
204
return
"
caps
"
205
end
206
end
207
return
"
normal
"
208
end
209 210
function
css
.
weight
(
str
)
211
if
str
and
str
~
=
"
"
then
212
str
=
lower
(
str
)
213
if
str
=
=
"
bold
"
then
214
return
"
bold
"
215
end
216
end
217
return
"
normal
"
218
end
219 220
function
css
.
family
(
str
)
221
if
str
and
str
~
=
"
"
then
222
str
=
lower
(
str
)
223
if
str
=
=
"
mono
"
or
str
=
=
"
type
"
or
str
=
=
"
monospace
"
then
224
return
"
mono
"
225
elseif
str
=
=
"
sansserif
"
or
str
=
=
"
sans
"
then
226
return
"
sans
"
227
elseif
str
=
=
"
serif
"
then
228
return
"
serif
"
229
else
230
-- what if multiple ...
231
return
lpegmatch
(
p_unquoted
,
str
)
or
str
232
end
233
end
234
end
235 236
function
css
.
size
(
str
,
factors
,
pct
)
237
local
size
,
unit
238
if
type
(
str
)
=
=
"
table
"
then
239
size
,
unit
=
str
[
1
]
,
str
[
2
]
240
elseif
str
and
str
~
=
"
"
then
241
size
,
unit
=
lpegmatch
(
p_size
,
lower
(
str
)
)
242
end
243
if
size
and
unit
then
244
if
unit
=
=
"
%
"
and
pct
then
245
return
size
*
pct
246
elseif
factors
then
247
return
(
factors
[
unit
]
or
1
)
*
size
248
else
249
return
size
,
unit
250
end
251
end
252
end
253 254
function
css
.
colorspecification
(
str
)
255
if
str
and
str
~
=
"
"
then
256
local
c
=
attributes
.
colors
.
values
[
tonumber
(
str
)
]
257
if
c
then
258
return
format
(
"
rgb(%s%%,%s%%,%s%%)
"
,
c
[
3
]
*
100
,
c
[
4
]
*
100
,
c
[
5
]
*
100
)
259
end
260
end
261
end
262 263
end
264 265
-- The following might be handy. It hooks into the normal parser as <selector>
266
-- and should work ok with the rest. It's sometimes even a bit faster but that might
267
-- change. It's somewhat optimized but not too aggressively.
268 269
-- element-1 > element-2 : element-2 with parent element-1
270 271
local
function
s_element_a
(
list
,
collected
,
c
,
negate
,
str
,
dummy
,
dummy
,
n
)
272
local
all
=
str
=
=
"
*
"
273
for
l
=
1
,
#
list
do
274
local
ll
=
list
[
l
]
275
local
dt
=
ll
.
dt
276
if
dt
then
277
local
ok
=
all
or
ll
.
tg
=
=
str
278
if
negate
then
279
ok
=
not
ok
280
end
281
if
ok
then
282
c
=
c
+
1
283
collected
[
c
]
=
ll
284
end
285
if
(
not
n
or
n
>
1
)
and
dt
then
286
c
=
s_element_a
(
dt
,
collected
,
c
,
negate
,
str
,
dummy
,
dummy
,
n
and
n
+
1
or
1
)
287
end
288
end
289
end
290
return
c
291
end
292 293
-- element-1 + element-2 : element-2 preceded by element-1
294 295
local
function
s_element_b
(
list
,
collected
,
c
,
negate
,
str
)
296
local
all
=
str
=
=
"
*
"
297
for
l
=
1
,
#
list
do
298
local
ll
=
list
[
l
]
299
local
pp
=
ll
.
__p__
300
if
pp
then
301
local
dd
=
pp
.
dt
302
if
dd
then
303
local
ni
=
ll
.
ni
304
local
d
=
dd
[
ni
+
1
]
305
local
dt
=
d
and
d
.
dt
306
if
not
dt
then
307
d
=
dd
[
ni
+
2
]
308
dt
=
d
and
d
.
dt
309
end
310
if
dt
then
311
local
ok
=
all
or
d
.
tg
=
=
str
312
if
negate
then
313
ok
=
not
ok
314
end
315
if
ok
then
316
c
=
c
+
1
317
collected
[
c
]
=
d
318
end
319
end
320
end
321
end
322
end
323
return
c
324
end
325 326
-- element-1 ~ element-2 : element-2 preceded by element-1 -- ?
327 328
local
function
s_element_c
(
list
,
collected
,
c
,
negate
,
str
)
329
local
all
=
str
=
=
"
*
"
330
for
l
=
1
,
#
list
do
331
local
ll
=
list
[
l
]
332
local
pp
=
ll
.
__p__
333
if
pp
then
334
local
dt
=
pp
.
dt
335
if
dt
then
336
local
ni
=
ll
.
ni
337
for
i
=
ni
+
1
,
#
dt
do
338
local
d
=
dt
[
i
]
339
local
dt
=
d
.
dt
340
if
dt
then
341
local
ok
=
all
or
d
.
tg
=
=
str
342
if
negate
then
343
ok
=
not
ok
344
end
345
if
ok
then
346
c
=
c
+
1
347
collected
[
c
]
=
d
348
end
349
end
350
end
351
end
352
end
353
end
354
return
c
355
end
356 357
-- element
358
-- element-1 element-2 : element-2 inside element-1
359 360
local
function
s_element_d
(
list
,
collected
,
c
,
negate
,
str
)
361
if
str
=
=
"
*
"
then
362
if
not
negate
then
363
for
l
=
1
,
#
list
do
364
local
ll
=
list
[
l
]
365
local
dt
=
ll
.
dt
366
if
dt
then
367
if
not
ll
.
special
then
368
c
=
c
+
1
369
collected
[
c
]
=
ll
370
end
371
c
=
s_element_d
(
dt
,
collected
,
c
,
negate
,
str
)
372
end
373
end
374
end
375
else
376
for
l
=
1
,
#
list
do
377
local
ll
=
list
[
l
]
378
local
dt
=
ll
.
dt
379
if
dt
then
380
if
not
ll
.
special
then
381
local
ok
=
ll
.
tg
=
=
str
382
if
negate
then
383
ok
=
not
ok
384
end
385
if
ok
then
386
c
=
c
+
1
387
collected
[
c
]
=
ll
388
end
389
end
390
c
=
s_element_d
(
dt
,
collected
,
c
,
negate
,
str
)
391
end
392
end
393
end
394
return
c
395
end
396 397
-- [attribute]
398
-- [attribute=value] equals
399
-- [attribute~=value] contains word
400
-- [attribute^="value"] starts with
401
-- [attribute$="value"] ends with
402
-- [attribute*="value"] contains
403 404
-- .class (no need to optimize)
405
-- #id (no need to optimize)
406 407
local
function
s_attribute
(
list
,
collected
,
c
,
negate
,
str
,
what
,
value
)
408
for
l
=
1
,
#
list
do
409
local
ll
=
list
[
l
]
410
local
dt
=
ll
.
dt
411
if
dt
then
412
local
at
=
ll
.
at
413
if
at
then
414
local
v
=
at
[
str
]
415
local
ok
=
negate
416
if
v
then
417
if
not
what
then
418
ok
=
not
negate
419
elseif
what
=
=
1
then
420
if
v
=
=
value
then
421
ok
=
not
negate
422
end
423
elseif
what
=
=
2
then
424
-- todo: lpeg
425
if
find
(
v
,
value
)
then
-- value can be a pattern
426
ok
=
not
negate
427
end
428
elseif
what
=
=
3
then
429
-- todo: lpeg
430
if
find
(
v
,
"
"
,
1
,
true
)
then
431
for
s
in
gmatch
(
v
,
"
[^ ]+
"
)
do
432
if
s
=
=
value
then
433
ok
=
not
negate
434
break
435
end
436
end
437
elseif
v
=
=
value
then
438
ok
=
not
negate
439
end
440
end
441
end
442
if
ok
then
443
c
=
c
+
1
444
collected
[
c
]
=
ll
445
end
446
end
447
c
=
s_attribute
(
dt
,
collected
,
c
,
negate
,
str
,
what
,
value
)
448
end
449
end
450
return
c
451
end
452 453
-- :nth-child(n)
454
-- :nth-last-child(n)
455
-- :first-child
456
-- :last-child
457 458
local
function
filter_down
(
collected
,
c
,
negate
,
dt
,
a
,
b
)
459
local
t
=
{
}
460
local
n
=
0
461
for
i
=
1
,
#
dt
do
462
local
d
=
dt
[
i
]
463
if
type
(
d
)
=
=
"
table
"
then
464
n
=
n
+
1
465
t
[
n
]
=
i
466
end
467
end
468
if
n
=
=
0
then
469
return
0
470
end
471
local
m
=
a
472
while
true
do
473
if
m
>
n
then
474
break
475
end
476
if
m
>
0
then
477
t
[
m
]
=
-
t
[
m
]
-- sign signals match
478
end
479
m
=
m
+
b
480
end
481
if
negate
then
482
for
i
=
n
,
1
-1
do
483
local
ti
=
t
[
i
]
484
if
ti
>
0
then
485
local
di
=
dt
[
ti
]
486
c
=
c
+
1
487
collected
[
c
]
=
di
488
end
489
end
490
else
491
for
i
=
n
,
1
,
-1
do
492
local
ti
=
t
[
i
]
493
if
ti
<
0
then
494
ti
=
-
ti
495
local
di
=
dt
[
ti
]
496
c
=
c
+
1
497
collected
[
c
]
=
di
498
end
499
end
500
end
501
return
c
502
end
503 504
local
function
filter_up
(
collected
,
c
,
negate
,
dt
,
a
,
b
)
505
local
t
=
{
}
506
local
n
=
0
507
for
i
=
1
,
#
dt
do
508
local
d
=
dt
[
i
]
509
if
type
(
d
)
=
=
"
table
"
then
510
n
=
n
+
1
511
t
[
n
]
=
i
512
end
513
end
514
if
n
=
=
0
then
515
return
0
516
end
517
if
not
b
then
518
b
=
0
519
end
520
local
m
=
n
-
a
521
while
true
do
522
if
m
<
1
then
523
break
524
end
525
if
m
<
n
then
526
t
[
m
]
=
-
t
[
m
]
-- sign signals match
527
end
528
m
=
m
-
b
529
end
530
if
negate
then
531
for
i
=
1
,
n
do
532
local
ti
=
t
[
i
]
533
if
ti
>
0
then
534
local
di
=
dt
[
ti
]
535
c
=
c
+
1
536
collected
[
c
]
=
di
537
end
538
end
539
else
540
for
i
=
1
,
n
do
541
local
ti
=
t
[
i
]
542
if
ti
<
0
then
543
ti
=
-
ti
544
local
di
=
dt
[
ti
]
545
c
=
c
+
1
546
collected
[
c
]
=
di
547
end
548
end
549
end
550
return
c
551
end
552 553
local
function
just
(
collected
,
c
,
negate
,
dt
,
a
,
start
,
stop
,
step
)
554
local
m
=
0
555
for
i
=
start
,
stop
,
step
do
556
local
d
=
dt
[
i
]
557
if
type
(
d
)
=
=
"
table
"
then
558
m
=
m
+
1
559
if
negate
then
560
if
a
~
=
m
then
561
c
=
c
+
1
562
collected
[
c
]
=
d
563
end
564
else
565
if
a
=
=
m
then
566
c
=
c
+
1
567
collected
[
c
]
=
d
568
break
569
end
570
end
571
end
572
end
573
return
c
574
end
575 576
local
function
s_nth_child
(
list
,
collected
,
c
,
negate
,
a
,
n
,
b
)
577
if
n
=
=
"
n
"
then
578
for
l
=
1
,
#
list
do
579
local
ll
=
list
[
l
]
580
local
dt
=
ll
.
dt
581
if
dt
then
582
c
=
filter_up
(
collected
,
c
,
negate
,
dt
,
a
,
b
)
583
end
584
end
585
else
586
for
l
=
1
,
#
list
do
587
local
ll
=
list
[
l
]
588
local
dt
=
ll
.
dt
589
if
dt
then
590
c
=
just
(
collected
,
c
,
negate
,
dt
,
a
,
1
,
#
dt
,
1
)
591
end
592
end
593
end
594
return
c
595
end
596 597
local
function
s_nth_last_child
(
list
,
collected
,
c
,
negate
,
a
,
n
,
b
)
598
if
n
=
=
"
n
"
then
599
for
l
=
1
,
#
list
do
600
local
ll
=
list
[
l
]
601
local
dt
=
ll
.
dt
602
if
dt
then
603
c
=
filter_down
(
collected
,
c
,
negate
,
dt
,
a
,
b
)
604
end
605
end
606
else
607
for
l
=
1
,
#
list
do
608
local
ll
=
list
[
l
]
609
local
dt
=
ll
.
dt
610
if
dt
then
611
c
=
just
(
collected
,
c
,
negate
,
dt
,
a
,
#
dt
,
1
,
-1
)
612
end
613
end
614
end
615
return
c
616
end
617 618
-- :nth-of-type(n)
619
-- :nth-last-of-type(n)
620
-- :first-of-type
621
-- :last-of-type
622 623
local
function
s_nth_of_type
(
list
,
collected
,
c
,
negate
,
a
,
n
,
b
)
624
if
n
=
=
"
n
"
then
625
return
filter_up
(
collected
,
c
,
negate
,
list
,
a
,
b
)
626
else
627
return
just
(
collected
,
c
,
negate
,
list
,
a
,
1
,
#
list
,
1
)
628
end
629
end
630 631
local
function
s_nth_last_of_type
(
list
,
collected
,
c
,
negate
,
a
,
n
,
b
)
632
if
n
=
=
"
n
"
then
633
return
filter_down
(
collected
,
c
,
negate
,
list
,
a
,
b
)
634
else
635
return
just
(
collected
,
c
,
negate
,
list
,
a
,
#
list
,
1
,
-1
)
636
end
637
end
638 639
-- :only-of-type
640 641
local
function
s_only_of_type
(
list
,
collected
,
c
,
negate
)
642
if
negate
then
643
for
i
=
1
,
#
list
do
644
c
=
c
+
1
645
collected
[
c
]
=
list
[
i
]
646
end
647
else
648
if
#
list
=
=
1
then
649
c
=
c
+
1
650
collected
[
c
]
=
list
[
1
]
651
end
652
end
653
return
c
654
end
655 656
-- :only-child
657 658
local
function
s_only_child
(
list
,
collected
,
c
,
negate
)
659
if
negate
then
660
for
l
=
1
,
#
list
do
661
local
ll
=
list
[
l
]
662
local
dt
=
ll
.
dt
663
if
dt
then
664
for
i
=
1
,
#
dt
do
665
local
di
=
dt
[
i
]
666
if
type
(
di
)
=
=
"
table
"
then
667
c
=
c
+
1
668
collected
[
c
]
=
di
669
end
670
end
671
end
672
end
673
else
674
for
l
=
1
,
#
list
do
675
local
ll
=
list
[
l
]
676
local
dt
=
ll
.
dt
677
if
dt
and
#
dt
=
=
1
then
678
local
di
=
dt
[
1
]
679
if
type
(
di
)
=
=
"
table
"
then
680
c
=
c
+
1
681
collected
[
c
]
=
di
682
end
683
end
684
end
685
end
686
return
c
687
end
688 689
-- :empty
690 691
local
function
s_empty
(
list
,
collected
,
c
,
negate
)
692
for
l
=
1
,
#
list
do
693
local
ll
=
list
[
l
]
694
local
dt
=
ll
.
dt
695
if
dt
then
696
local
dn
=
#
dt
697
local
ok
=
dn
=
=
0
698
if
not
ok
and
dn
=
=
1
then
699
local
d
=
dt
[
1
]
700
if
type
(
d
)
=
=
"
string
"
and
is_empty
(
d
)
then
701
ok
=
true
702
end
703
end
704
if
negate
then
705
ok
=
not
ok
706
end
707
if
ok
then
708
c
=
c
+
1
709
collected
[
c
]
=
ll
710
end
711
end
712
end
713
return
c
714
end
715 716
-- :root
717 718
local
function
s_root
(
list
,
collected
,
c
,
negate
)
719
for
l
=
1
,
#
list
do
720
local
ll
=
list
[
l
]
721
if
type
(
ll
)
=
=
"
table
"
then
722
local
r
=
xml
.
root
(
ll
)
723
if
r
then
724
if
r
.
special
and
r
.
tg
=
=
"
@rt@
"
then
725
r
=
r
.
dt
[
r
.
ri
]
726
end
727
c
=
c
+
1
728
collected
[
c
]
=
r
729
break
730
end
731
end
732
end
733
return
c
734
end
735 736
local
P
,
R
,
S
,
C
,
Cs
,
Ct
,
Cc
,
Carg
,
lpegmatch
=
lpeg
.
P
,
lpeg
.
R
,
lpeg
.
S
,
lpeg
.
C
,
lpeg
.
Cs
,
lpeg
.
Ct
,
lpeg
.
Cc
,
lpeg
.
Carg
,
lpeg
.
match
737 738
local
p_number
=
lpegpatterns
.
integer
/
tonumber
739 740
local
p_key
=
C
(
(
R
(
"
az
"
,
"
AZ
"
,
"
09
"
)
+
S
(
"
_-
"
)
)
^
1
)
741
local
p_left
=
S
(
"
#.[],:()
"
)
742
local
p_right
=
S
(
"
#.[],:()
"
)
743
local
p_tag
=
C
(
(
1
-
p_left
)
*
(
1
-
p_right
)
^
0
)
744
local
p_value
=
C
(
(
1
-
P
(
"
]
"
)
)
^
0
)
745
local
p_unquoted
=
(
P
(
'
"
'
)
/
"
"
)
*
C
(
(
1
-
P
(
'
"
'
)
)
^
0
)
*
(
P
(
'
"
'
)
/
"
"
)
746
+
(
1
-
P
(
"
]
"
)
)
^
1
747
local
p_element
=
Ct
(
(
748
P
(
"
>
"
)
*
skipspace
*
Cc
(
s_element_a
)
+
749
P
(
"
+
"
)
*
skipspace
*
Cc
(
s_element_b
)
+
750
P
(
"
~
"
)
*
skipspace
*
Cc
(
s_element_c
)
+
751
Cc
(
s_element_d
)
752
)
*
p_tag
)
753
local
p_attribute
=
P
(
"
[
"
)
*
Ct
(
Cc
(
s_attribute
)
*
p_key
*
(
754
P
(
"
=
"
)
*
Cc
(
1
)
*
Cs
(
p_unquoted
)
755
+
P
(
"
^=
"
)
*
Cc
(
2
)
*
Cs
(
Cc
(
"
^
"
)
*
(
p_unquoted
/
topattern
)
)
756
+
P
(
"
$=
"
)
*
Cc
(
2
)
*
Cs
(
p_unquoted
/
topattern
*
Cc
(
"
$
"
)
)
757
+
P
(
"
*=
"
)
*
Cc
(
2
)
*
Cs
(
p_unquoted
/
topattern
)
758
+
P
(
"
~=
"
)
*
Cc
(
3
)
*
Cs
(
p_unquoted
)
759
)
^
0
*
P
(
"
]
"
)
)
760 761
local
p_separator
=
skipspace
*
P
(
"
,
"
)
*
skipspace
762 763
local
p_formula
=
skipspace
*
P
(
"
(
"
)
764
*
skipspace
765
*
(
766
p_number
*
skipspace
*
(
C
(
"
n
"
)
*
skipspace
*
(
p_number
+
Cc
(
0
)
)
)
^
-1
767
+
P
(
"
even
"
)
*
Cc
(
0
)
*
Cc
(
"
n
"
)
*
Cc
(
2
)
768
+
P
(
"
odd
"
)
*
Cc
(
-1
)
*
Cc
(
"
n
"
)
*
Cc
(
2
)
769
)
770
*
skipspace
771
*
P
(
"
)
"
)
772 773
local
p_step
=
P
(
"
.
"
)
*
Ct
(
Cc
(
s_attribute
)
*
Cc
(
"
class
"
)
*
Cc
(
3
)
*
p_tag
)
774
+
P
(
"
#
"
)
*
Ct
(
Cc
(
s_attribute
)
*
Cc
(
"
id
"
)
*
Cc
(
1
)
*
p_tag
)
775
+
p_attribute
776
+
p_element
777
+
P
(
"
:nth-child
"
)
*
Ct
(
Cc
(
s_nth_child
)
*
p_formula
)
778
+
P
(
"
:nth-last-child
"
)
*
Ct
(
Cc
(
s_nth_last_child
)
*
p_formula
)
779
+
P
(
"
:first-child
"
)
*
Ct
(
Cc
(
s_nth_child
)
*
Cc
(
1
)
)
780
+
P
(
"
:last-child
"
)
*
Ct
(
Cc
(
s_nth_last_child
)
*
Cc
(
1
)
)
781
+
P
(
"
:only-child
"
)
*
Ct
(
Cc
(
s_only_child
)
)
782
+
P
(
"
:nth-of-type
"
)
*
Ct
(
Cc
(
s_nth_of_type
)
*
p_formula
)
783
+
P
(
"
:nth-last-of-type
"
)
*
Ct
(
Cc
(
s_nth_last_of_type
)
*
p_formula
)
784
+
P
(
"
:first-of-type
"
)
*
Ct
(
Cc
(
s_nth_of_type
)
*
Cc
(
1
)
)
785
+
P
(
"
:last-of-type
"
)
*
Ct
(
Cc
(
s_nth_last_of_type
)
*
Cc
(
1
)
)
786
+
P
(
"
:only-of-type
"
)
*
Ct
(
Cc
(
s_only_of_type
)
)
787
+
P
(
"
:empty
"
)
*
Ct
(
Cc
(
s_empty
)
)
788
+
P
(
"
:root
"
)
*
Ct
(
Cc
(
s_root
)
)
789 790
local
p_not
=
P
(
"
:not
"
)
*
Cc
(
true
)
*
skipspace
*
P
(
"
(
"
)
*
skipspace
*
p_step
*
skipspace
*
P
(
"
)
"
)
791
local
p_yes
=
Cc
(
false
)
*
skipspace
*
p_step
792 793
local
p_stepper
=
Ct
(
(
skipspace
*
(
p_not
+
p_yes
)
)
^
1
)
794
local
p_steps
=
Ct
(
(
p_stepper
*
p_separator
^
0
)
^
1
)
*
skipspace
*
(
P
(
-1
)
+
function
(
)
report_css
(
"
recovering from error
"
)
end
)
795 796
local
cache
=
setmetatableindex
(
function
(
t
,
k
)
797
local
v
=
lpegmatch
(
p_steps
,
k
)
or
false
798
t
[
k
]
=
v
799
return
v
800
end
)
801 802
local
function
selector
(
root
,
s
)
803
-- local steps = lpegmatch(p_steps,s)
804
local
steps
=
cache
[
s
]
805
if
steps
then
806
local
done
=
{
}
807
local
collected
=
{
}
808
local
nofcollected
=
0
809
local
nofsteps
=
#
steps
810
for
i
=
1
,
nofsteps
do
811
local
step
=
steps
[
i
]
812
local
n
=
#
step
813
if
n
>
0
then
814
local
r
=
root
815
local
m
=
0
816
local
c
=
{
}
817
for
i
=
1
,
n
,
2
do
818
local
s
=
step
[
i
+
1
]
-- function + data
819
m
=
s
[
1
]
(
r
,
c
,
0
,
step
[
i
]
,
s
[
2
]
,
s
[
3
]
,
s
[
4
]
)
820
if
m
=
=
0
then
821
break
822
else
823
r
=
c
824
c
=
{
}
825
end
826
end
827
if
m
>
0
then
828
if
nofsteps
>
1
then
829
for
i
=
1
,
m
do
830
local
ri
=
r
[
i
]
831
if
done
[
ri
]
then
832
-- print("duplicate",i)
833
-- elseif ri.special then
834
-- done[ri] = true
835
else
836
nofcollected
=
nofcollected
+
1
837
collected
[
nofcollected
]
=
ri
838
done
[
ri
]
=
true
839
end
840
end
841
else
842
return
r
843
end
844
end
845
end
846
end
847
if
nofcollected
>
1
then
848
-- local n = 0
849
-- local function traverse(e)
850
-- if done[e] then
851
-- n = n + 1
852
-- done[e] = n
853
-- end
854
-- local dt = e.dt
855
-- if dt then
856
-- for i=1,#dt do
857
-- local e = dt[i]
858
-- if type(e) == "table" then
859
-- traverse(e)
860
-- end
861
-- end
862
-- end
863
-- end
864
-- traverse(root[1])
865
--
866
local
n
=
0
867
local
function
traverse
(
dt
)
868
for
i
=
1
,
#
dt
do
869
local
e
=
dt
[
i
]
870
if
done
[
e
]
then
871
n
=
n
+
1
872
done
[
e
]
=
n
873
if
n
=
=
nofcollected
then
874
return
875
end
876
end
877
local
d
=
e
.
dt
878
if
d
then
879
traverse
(
d
)
880
if
n
=
=
nofcollected
then
881
return
882
end
883
end
884
end
885
end
886
local
r
=
root
[
1
]
887
if
done
[
r
]
then
888
n
=
n
+
1
889
done
[
r
]
=
n
890
end
891
traverse
(
r
.
dt
)
892
--
893
sort
(
collected
,
function
(
a
,
b
)
return
done
[
a
]
<
done
[
b
]
end
)
894
end
895
return
collected
896
else
897
return
{
}
898
end
899
end
900 901
xml
.
applyselector
=
selector
902 903
-- local t = [[
904
-- <?xml version="1.0" ?>
905
--
906
-- <a>
907
-- <b class="one"> </b>
908
-- <b class="two"> </b>
909
-- <b class="one"> </b>
910
-- <b class="three"> </b>
911
-- <b id="first"> </b>
912
-- <c> </c>
913
-- <d> d e </d>
914
-- <e> d e </e>
915
-- <e> d e e </e>
916
-- <d> d f </d>
917
-- <f foo="bar"> </f>
918
-- <f bar="foo"> </f>
919
-- <f bar="foo1"> </f>
920
-- <f bar="foo2"> </f>
921
-- <f bar="foo3"> </f>
922
-- <f bar="foo+4"> </f>
923
-- <g> </g>
924
-- <?crap ?>
925
-- <!-- crap -->
926
-- <g> <gg> <d> </d> </gg> </g>
927
-- <g> <gg> <f> </f> </gg> </g>
928
-- <g> <gg> <f class="one"> g gg f </f> </gg> </g>
929
-- <g> </g>
930
-- <g> <gg> <f class="two"> g gg f </f> </gg> </g>
931
-- <g> <gg> <f class="three"> g gg f </f> </gg> </g>
932
-- <g> <f class="one"> g f </f> </g>
933
-- <g> <f class="three"> g f </f> </g>
934
-- <h whatever="four five six"> </h>
935
-- </a>
936
-- ]]
937
--
938
-- local s = [[ .one ]]
939
-- local s = [[ .one, .two ]]
940
-- local s = [[ .one, .two, #first ]]
941
-- local s = [[ .one, .two, #first, c, e, [foo], [bar=foo] ]]
942
-- local s = [[ .one, .two, #first, c, e, [foo], [bar=foo], [bar~=foo] [bar^="foo"] ]]
943
-- local s = [[ [bar^="foo"] ]]
944
-- local s = [[ g f .one, g f .three ]]
945
-- local s = [[ g > f .one, g > f .three ]]
946
-- local s = [[ * ]]
947
-- local s = [[ d + e ]]
948
-- local s = [[ d ~ e ]]
949
-- local s = [[ d ~ e, g f .one, g f .three ]]
950
-- local s = [[ :not(d) ]]
951
-- local s = [[ [whatever~="five"] ]]
952
-- local s = [[ :not([whatever~="five"]) ]]
953
-- local s = [[ e ]]
954
-- local s = [[ :not ( e ) ]]
955
-- local s = [[ a:nth-child(3) ]]
956
-- local s = [[ a:nth-child(3n+1) ]]
957
-- local s = [[ a:nth-child(2n+8) ]]
958
-- local s = [[ g:nth-of-type(3) ]]
959
-- local s = [[ a:first-child ]]
960
-- local s = [[ a:last-child ]]
961
-- local s = [[ e:first-of-type ]]
962
-- local s = [[gg d:only-of-type ]]
963
-- local s = [[ a:nth-child(even) ]]
964
-- local s = [[ a:nth-child(odd) ]]
965
-- local s = [[ g:empty ]]
966
-- local s = [[ g:root ]]
967 968
-- local c = css.applyselector(xml.convert(t),s) for i=1,#c do print(xml.tostring(c[i])) end
969 970
function
css
.
applyselector
(
x
,
str
)
971
-- the wrapping needs checking so this is a placeholder
972
return
applyselector
(
{
x
}
,
str
)
973
end
974 975
-- -- Some helpers to map e.g. style attributes:
976
--
977
-- -- string based (2.52):
978
--
979
-- local match = string.match
980
-- local topattern = string.topattern
981
--
982
-- function css.stylevalue(root,name)
983
-- local list = getid(root).at.style
984
-- if list then
985
-- local pattern = topattern(name) .. ":%s*([^;]+)"
986
-- local value = match(list,pattern)
987
-- if value then
988
-- context(value)
989
-- end
990
-- end
991
-- end
992
--
993
-- -- string based, cached (2.28 / 2.17 interfaced):
994
--
995
-- local match = string.match
996
-- local topattern = string.topattern
997
--
998
-- local patterns = table.setmetatableindex(function(t,k)
999
-- local v = topattern(k) .. ":%s*([^;]+)"
1000
-- t[k] = v
1001
-- return v
1002
-- end)
1003
--
1004
-- function css.stylevalue(root,name)
1005
-- local list = getid(root).at.style
1006
-- if list then
1007
-- local value = match(list,patterns[name])
1008
-- if value then
1009
-- context(value)
1010
-- end
1011
-- end
1012
-- end
1013
--
1014
-- -- lpeg based (4.26):
1015
--
1016
-- the lpeg variant also removes trailing spaces and accepts spaces before a colon
1017 1018
local
ctx_sprint
=
context
.
sprint
1019
local
ctx_xmlvalue
=
context
.
xmlvalue
1020 1021
local
colon
=
P
(
"
:
"
)
1022
local
semicolon
=
P
(
"
;
"
)
1023
local
eos
=
P
(
-1
)
1024
local
somevalue
=
(
1
-
(
skipspace
*
(
semicolon
+
eos
)
)
)
^
1
1025
local
someaction
=
skipspace
*
colon
*
skipspace
*
(
somevalue
/
ctx_sprint
)
1026 1027
-- function css.stylevalue(root,name)
1028
-- local list = getid(root).at.style
1029
-- if list then
1030
-- lpegmatch(P(name * someaction + 1)^0,list)
1031
-- end
1032
-- end
1033 1034
-- -- cache patterns (2.13):
1035 1036
local
patterns
=
setmetatableindex
(
function
(
t
,
k
)
1037
local
v
=
P
(
k
*
someaction
+
1
)
^
0
1038
t
[
k
]
=
v
1039
return
v
1040
end
)
1041 1042
function
css
.
stylevalue
(
root
,
name
)
1043
local
list
=
getid
(
root
)
.
at
.
style
-- hard coded style
1044
if
list
then
1045
lpegmatch
(
patterns
[
name
]
,
list
)
1046
end
1047
end
1048 1049
local
somevalue
=
(
1
-
whitespace
-
semicolon
-
eos
)
^
1
1050
local
someaction
=
skipspace
*
colon
*
(
skipspace
*
Carg
(
1
)
*
C
(
somevalue
)
/
function
(
m
,
s
)
1051
ctx_xmlvalue
(
m
,
s
,
"
"
)
-- use one with two args
1052
end
)
^
1
1053 1054
local
patterns
=
setmetatableindex
(
function
(
t
,
k
)
1055
local
v
=
P
(
k
*
someaction
+
1
)
^
0
1056
t
[
k
]
=
v
1057
return
v
1058
end
)
1059 1060
function
css
.
mappedstylevalue
(
root
,
map
,
name
)
1061
local
list
=
getid
(
root
)
.
at
.
style
-- hard coded style
1062
if
list
then
1063
lpegmatch
(
patterns
[
name
]
,
list
,
1
,
map
)
1064
end
1065
end
1066 1067
-- -- faster interface (1.02):
1068 1069
interfaces
.
implement
{
1070
name
=
"
xmlcssstylevalue
"
,
1071
public
=
true
,
1072
actions
=
css
.
stylevalue
,
1073
arguments
=
"
2 strings
"
,
1074
}
1075 1076
interfaces
.
implement
{
1077
name
=
"
xmlcssmappedstylevalue
"
,
1078
public
=
true
,
1079
actions
=
css
.
mappedstylevalue
,
1080
arguments
=
"
3 strings
"
,
1081
}
1082 1083
-- more (for mm)
1084 1085
local
containsws
=
string
.
containsws
1086
local
classsplitter
=
lpeg
.
tsplitat
(
whitespace
^
1
)
1087 1088
function
xml
.
functions
.
classes
(
e
,
class
)
-- cache
1089
if
class
then
1090
local
at
=
e
.
at
1091
local
data
=
at
[
class
]
or
at
.
class
1092
if
data
then
1093
return
lpegmatch
(
classsplitter
,
data
)
or
{
}
1094
end
1095
end
1096
return
{
}
1097
end
1098 1099
-- function xml.functions.hasclass(e,class,name)
1100
-- if class then
1101
-- local at = e.at
1102
-- local data = at[class] or at.class
1103
-- if data then
1104
-- return data == name or containsws(data,name)
1105
-- end
1106
-- end
1107
-- return false
1108
-- end
1109
--
1110
-- function xml.expressions.hasclass(attribute,name)
1111
-- if attribute then
1112
-- return attribute == name or containsws(attribute,name)
1113
-- end
1114
-- return false
1115
-- end
1116 1117
function
xml
.
functions
.
hasclass
(
e
,
class
,
name
,
more
,
...
)
1118
if
class
and
name
then
1119
local
at
=
e
.
at
1120
local
data
=
at
[
class
]
or
at
.
class
1121
if
not
data
or
data
=
=
"
"
then
1122
return
false
1123
end
1124
if
data
=
=
name
or
data
=
=
more
then
1125
return
true
1126
end
1127
if
containsws
(
data
,
name
)
then
1128
return
true
1129
end
1130
if
not
more
then
1131
return
false
1132
end
1133
if
containsws
(
data
,
more
)
then
1134
return
true
1135
end
1136
for
i
=
1
,
select
(
"
#
"
,
...
)
do
1137
if
containsws
(
data
,
select
(
i
,
...
)
)
then
1138
return
true
1139
end
1140
end
1141
end
1142
return
false
1143
end
1144 1145
function
xml
.
expressions
.
hasclass
(
data
,
name
,
more
,
...
)
1146
if
data
then
1147
if
not
data
or
data
=
=
"
"
then
1148
return
false
1149
end
1150
if
data
=
=
name
or
data
=
=
more
then
1151
return
true
1152
end
1153
if
containsws
(
data
,
name
)
then
1154
return
true
1155
end
1156
if
not
more
then
1157
return
false
1158
end
1159
if
containsws
(
data
,
more
)
then
1160
return
true
1161
end
1162
for
i
=
1
,
select
(
"
#
"
,
...
)
do
1163
if
containsws
(
data
,
select
(
i
,
...
)
)
then
1164
return
true
1165
end
1166
end
1167
end
1168
return
false
1169
end
1170