css.bend
gralha-css-style@0.1.0.1 · 462 lines · 71 definitions
1
2import Base
3import gralha-text-scan@0.1.0.1/scan.bend as U
4import gralha-xml-doc@0.1.0.1/xml.bend as XML
5
6def css.lower(s: String) -> String:
7 match s:
8 case SNil{}: ""
9 case SCon{Chr{c}, t}:
10 +ch = c
11 SCon{Chr{U.choose(U32, U32.is_ge(ch, 65) && U32.is_le(ch, 90), (ch + 32 : U32), ch)}, css.lower(t)}
12
13def css.pair(s: U.Scan, attrs: List<&2, XML.Attr>) -> List<&2, XML.Attr>:
14 U.Scan{k, v} = s
15 Con{XML.Attr{U.trim.both(k), U.trim.both(v)}, attrs}
16
17def css.more(s: U.Scan, attrs: List<&2, XML.Attr>, next: String -> List<&2, XML.Attr> -> List<&2, XML.Attr>) -> List<&2, XML.Attr>:
18 U.Scan{h, t} = s
19 next(t)(css.pair(U.until(h, 58, ""), attrs))
20
21@unsafe
22def css(s: String, attrs: List<&2, XML.Attr>) -> List<&2, XML.Attr>:
23 match s:
24 case SNil{}: attrs
25 case SCon{c, t}: css.more(U.until(SCon{c, t}, 59, ""), attrs, rest => as => css(rest, as))
26
27def properties(+attrs: List<&2, XML.Attr>) -> List<&2, XML.Attr>:
28 U.branch(List<&2, XML.Attr>, U.string.equal(XML.attr.get(attrs, "#css-ready", ""), "1"), _ => attrs, _ => css(XML.attr.get(attrs, "style", ""), attrs))
29
30
31def css.stop(+ch: U32, +mode: U32) -> Bool:
32 U.choose(Bool, U32.is_eq(mode, 0), U.space(ch) || U32.is_eq(ch, 62) || U32.is_eq(ch, 43) || U32.is_eq(ch, 126), U.choose(Bool, U32.is_eq(mode, 1), U32.is_eq(ch, 123) || U32.is_eq(ch, 59), U32.is_eq(ch, mode)))
33
34
35@unsafe
36def css.cut(+s: String, +end: U32, +depth: U32, +quote: U32, +escaped: Bool, +acc: String) -> U.Scan:
37 match s:
38 case SNil{}: U.Scan{U.rev(acc, ""), ""}
39 case SCon{Chr{ch}, tail}:
40 +c = ch
41 +t = tail
42 +quoted = Bool.not(U32.is_eq(quote, 0))
43 +opening = U32.is_eq(c, 40) || U32.is_eq(c, 91) || U32.is_eq(c, 123)
44 +closing = U32.is_eq(c, 41) || U32.is_eq(c, 93) || U32.is_eq(c, 125)
45 U.branch(U.Scan, Bool.not(escaped) && Bool.not(quoted) && U32.is_eq(depth, 0) && css.stop(c, end),
46 _ => U.Scan{U.rev(acc, ""), s},
47 _ => U.branch(U.Scan, Bool.not(escaped) && Bool.not(quoted) && U.prefix(s, "/*"),
48 _ => css.cut(U.through(t, "*/"), end, depth, quote, False{}, acc),
49 _ => css.cut(t, end,
50 U.choose(U32, quoted || escaped, depth, U.choose(U32, opening, U32.inc(depth), U.choose(U32, closing && U32.is_gt(depth, 0), (depth - 1 : U32), depth))),
51 U.choose(U32, escaped, quote, U.choose(U32, quoted, U.choose(U32, U32.is_eq(c, quote), 0, quote), U.choose(U32, U32.is_eq(c, 34) || U32.is_eq(c, 39), c, 0))),
52 Bool.not(escaped) && U32.is_eq(c, 92), SCon{Chr{c}, acc})))
53
54def css.scan(s: String, end: U32) -> U.Scan:
55 css.cut(s, end, 0, 0, False{}, "")
56
57@unsafe
58def css.skip(+s: String) -> String:
59 U.branch(String, U.prefix(U.trim(s), "/*"), _ => css.skip(U.through(U.trim(s), "*/")), _ => U.trim(s))
60
61def css.ident.char(+c: U32) -> Bool:
62 U.digit(c) || (U32.is_ge(c, 65) && U32.is_le(c, 90)) || (U32.is_ge(c, 97) && U32.is_le(c, 122))
63 || U32.is_eq(c, 45) || U32.is_eq(c, 95) || U32.is_ge(c, 128)
64
65def css.ident(+s: String, +acc: String) -> U.Scan:
66 match s:
67 case SNil{}: U.Scan{U.rev(acc, ""), ""}
68 case SCon{Chr{c}, t}:
69 U.branch(U.Scan, css.ident.char(c), _ => css.ident(t, SCon{Chr{c}, acc}), _ => U.Scan{U.rev(acc, ""), s})
70
71def css.word(+s: String, +acc: String) -> U.Scan:
72 match s:
73 case SNil{}: U.Scan{U.rev(acc, ""), ""}
74 case SCon{Chr{c}, t}: U.branch(U.Scan, U.space(c), _ => U.Scan{U.rev(acc, ""), t}, _ => css.word(t, SCon{Chr{c}, acc}))
75
76def css.word.test(scan: U.Scan, +word: String, next: String -> Bool) -> Bool:
77 U.Scan{h, t} = scan
78 U.string.equal(h, word) || next(t)
79
80@unsafe
81def css.words(s: String, +word: String) -> Bool:
82 match s:
83 case SNil{}: False{}
84 case SCon{c, t}: css.word.test(css.word(U.trim(SCon{c, t}), ""), word, rest => css.words(rest, word))
85
86type CSSContext is Data:
87 CSSContext{node: XML.Element, parents: List<&2, CSSContext>, previous: List<&2, CSSContext>, index: U32, count: U32}
88
89type CSSStep is Data:
90 CSSStep{compound: String, relation: U32}
91
92def css.parts.scan(scan: U.Scan, rel: U32, acc: List<&2, CSSStep>, next: String -> List<&2, CSSStep> -> List<&2, CSSStep>) -> List<&2, CSSStep>:
93 U.Scan{h, t} = scan
94 next(t)(Con{CSSStep{h, rel}, acc})
95
96@unsafe
97def css.parts(+s: String, +rel: U32, +acc: List<&2, CSSStep>) -> List<&2, CSSStep>:
98 match s:
99 case SNil{}: acc
100 case SCon{Chr{c}, t}:
101 U.branch(List<&2, CSSStep>, U.space(c), _ => css.parts(U.trim(t), rel, acc),
102 _ => U.branch(List<&2, CSSStep>, U32.is_eq(c, 62) || U32.is_eq(c, 43) || U32.is_eq(c, 126),
103 _ => css.parts(U.trim(t), U.choose(U32, U32.is_eq(c, 62), 2, U.choose(U32, U32.is_eq(c, 43), 3, 4)), acc),
104 _ => css.parts.scan(css.scan(s, 0), rel, acc, rest => steps => css.parts(rest, 1, steps))))
105
106def css.attr.value(+actual: String, +expected: String, +op: String) -> Bool:
107 U.branch(Bool, U.string.equal(op, "="), _ => U.string.equal(actual, expected),
108 _ => U.branch(Bool, U.string.equal(expected, ""), _ => False{},
109 _ => U.choose(Bool, U.string.equal(op, "~="), css.words(U.trim(actual), expected),
110 U.choose(Bool, U.string.equal(op, "|="), U.string.equal(actual, expected) || U.prefix(actual, expected ++ "-"),
111 U.choose(Bool, U.string.equal(op, "^="), U.prefix(actual, expected),
112 U.choose(Bool, U.string.equal(op, "$="), U.prefix(U.rev(actual, ""), U.rev(expected, "")),
113 Bool.not(U.string.equal(U.through(actual, expected), "")) || U.prefix(U.rev(actual, ""), U.rev(expected, ""))))))))
114
115def css.attr.compare(scan: U.Scan, +actual: String, +op: String) -> Bool:
116 U.Scan{expected, tail} = scan
117 +ignoreCase = U.string.equal(css.lower(U.trim.both(tail)), "i")
118 +text = expected
119 css.attr.value(U.choose(String, ignoreCase, css.lower(actual), actual), U.choose(String, ignoreCase, css.lower(text), text), op)
120
121def css.attr.test(scan: U.Scan, +as: List<&2, XML.Attr>) -> Bool:
122 U.Scan{k, tail} = scan
123 +key = k
124 +rest = U.trim(tail)
125 +op = U.choose(String, U.prefix(rest, "="), "=", U.choose(String, U.prefix(rest, "~="), "~=", U.choose(String, U.prefix(rest, "|="), "|=", U.choose(String, U.prefix(rest, "^="), "^=", U.choose(String, U.prefix(rest, "$="), "$=", "*=")))))
126 +v = U.trim(U.drop(rest, op))
127 +actual = XML.attr.get(as, key, "#missing-css-attribute#")
128 U.branch(Bool, U.string.equal(rest, ""), _ => Bool.not(U.string.equal(actual, "#missing-css-attribute#")),
129 _ => Bool.not(U.string.equal(actual, "#missing-css-attribute#")) && css.attr.compare(XML.value(v), actual, op))
130
131def css.empty(xs: List<&2, XML.Element>) -> Bool:
132 match xs:
133 case Nil{}: True{}
134 case Con{XML.Element{n, as, kids, txt}, rest}:
135 U.string.equal(n, "#text") && U.string.equal(txt, "") && css.empty(rest)
136
137def css.compact(+s: String) -> String:
138 match s:
139 case SNil{}: ""
140 case SCon{Chr{c}, t}:
141 +tail = t
142 U.branch(String, U.space(c), _ => css.compact(tail), _ => SCon{Chr{c}, css.compact(tail)})
143
144def css.nth.formula(scan: U.Scan, +index: U32) -> Bool:
145 U.Scan{h, t} = scan
146 +head = h
147 +tail = t
148 +a = U.choose(F32, U.string.equal(head, "") || U.string.equal(head, "+"), 1.0, U.choose(F32, U.string.equal(head, "-"), F32.neg(1.0), U.scalar(head, 0.0)))
149 +b = U.scalar(U.drop(tail, "n"), 0.0)
150 +difference = (U32.to_f32(index) - b : F32)
151 +n = (difference / a : F32)
152 U.branch(Bool, U.string.equal(tail, ""), _ => F32.is_eq(U32.to_f32(index), U.scalar(head, F32.neg(1.0))),
153 _ => U.choose(Bool, F32.is_eq(a, 0.0), F32.is_eq(difference, 0.0), F32.is_ge(n, 0.0) && F32.is_eq(n, F32.floor(n))))
154
155def css.nth(+s: String, +index: U32) -> Bool:
156 +value = css.lower(css.compact(s))
157 U.choose(Bool, U.string.equal(value, "odd"), U32.is_eq(U32.mod(index, 2), 1),
158 U.choose(Bool, U.string.equal(value, "even"), U32.is_eq(U32.mod(index, 2), 0), css.nth.formula(css.scan(value, 110), index)))
159
160def css.is.root(parents: List<&2, CSSContext>) -> Bool:
161 match parents:
162 case Nil{}: True{}
163 case Con{h, t}: False{}
164
165def css.pseudo(+key: String, +arg: String, +context: CSSContext) -> Bool:
166 CSSContext{XML.Element{n, as, kids, txt}, parents, previous, index, count} = context
167 U.choose(Bool, U.string.equal(key, "root"), css.is.root(parents),
168 U.choose(Bool, U.string.equal(key, "first-child"), U32.is_eq(index, 1),
169 U.choose(Bool, U.string.equal(key, "last-child"), U32.is_eq(index, count),
170 U.choose(Bool, U.string.equal(key, "only-child"), U32.is_eq(count, 1),
171 U.choose(Bool, U.string.equal(key, "nth-child"), css.nth(arg, index),
172 U.choose(Bool, U.string.equal(key, "nth-last-child"), css.nth(arg, (count + 1 - index : U32)),
173 U.string.equal(key, "empty") && css.empty(kids)))))))
174
175
176def css.simple.scan(scan: U.Scan, +context: CSSContext, +kind: U32, next: String -> Bool) -> Bool:
177 U.Scan{h, t} = scan
178 CSSContext{XML.Element{n, as, kids, txt}, parents, previous, index, count} = context
179 +name = h
180 Bool.not(U.string.equal(name, "")) && U.choose(Bool, U32.is_eq(kind, 35), U.string.equal(XML.attr.get(as, "id", ""), name),
181 U.choose(Bool, U32.is_eq(kind, 46), css.words(U.trim(XML.attr.get(as, "class", "")), name), U.string.equal(n, name))) && next(t)
182
183def css.simple.attr(+scan: U.Scan, +context: CSSContext, next: String -> Bool) -> Bool:
184 U.Scan{h, t} = scan
185 CSSContext{XML.Element{n, as, kids, txt}, parents, previous, index, count} = context
186 U.prefix(t, "]") && css.attr.test(css.ident(U.trim(h), ""), as) && next(U.drop(t, "]"))
187
188def css.simple.pseudo.arg(+scan: U.Scan, +key: String, +context: CSSContext, next: String -> Bool,
189 nested: String -> Bool) -> Bool:
190 U.Scan{h, t} = scan
191 U.prefix(t, ")") && U.branch(Bool, U.string.equal(key, "not"), _ => Bool.not(nested(h)), _ => css.pseudo(key, h, context)) && next(U.drop(t, ")"))
192
193def css.simple.pseudo(scan: U.Scan, +context: CSSContext, next: String -> Bool, nested: String -> Bool) -> Bool:
194 U.Scan{h, t} = scan
195 +key = css.lower(h)
196 +tail = t
197 css.simple.pseudo.arg(U.choose(U.Scan, U.prefix(tail, "("), css.scan(U.drop(tail, "("), 41), U.Scan{"", ")" ++ tail}), key, context, next, nested)
198
199@unsafe
200def css.simple(+fuel: Nat, +s: String, +context: CSSContext) -> Bool:
201 match fuel s:
202 case 0n _: False{}
203 case 1n+d SNil{}: True{}
204 case 1n+d SCon{Chr{c}, t}:
205 U.branch(Bool, U32.is_eq(c, 42), _ => css.simple(d, t, context),
206 _ => U.branch(Bool, U32.is_eq(c, 35) || U32.is_eq(c, 46),
207 _ => css.simple.scan(css.ident(t, ""), context, c, rest => css.simple(d, rest, context)),
208 _ => U.branch(Bool, U32.is_eq(c, 91),
209 _ => css.simple.attr(css.scan(t, 93), context, rest => css.simple(d, rest, context)),
210 _ => U.branch(Bool, U32.is_eq(c, 58),
211 _ => css.simple.pseudo(css.ident(t, ""), context, rest => css.simple(d, rest, context), arg => css.simple(d, arg, context)),
212 _ => css.simple.scan(css.ident(s, ""), context, 0, rest => css.simple(d, rest, context))))))
213
214@unsafe
215def css.match(+steps: List<&2, CSSStep>, +contexts: List<&2, CSSContext>, +one: Bool) -> Bool:
216 match steps contexts:
217 case Nil{} _: True{}
218 case Con{step, rest} Nil{}: False{}
219 case Con{CSSStep{compound, relation}, rest} Con{CSSContext{node, parents, previous, index, count}, others}:
220 +rel = relation
221 (css.simple(128n, compound, CSSContext{node, parents, previous, index, count}) &&
222 U.branch(Bool, U32.is_eq(rel, 0), _ => True{},
223 _ => css.match(rest, U.choose(List<&2, CSSContext>, U32.is_le(rel, 2), parents, previous), U32.is_eq(rel, 2) || U32.is_eq(rel, 3))))
224 || U.branch(Bool, one, _ => False{}, _ => css.match(steps, others, False{}))
225
226def css.score.scan(scan: U.Scan, weight: U32, next: String -> U32) -> U32:
227 U.Scan{h, t} = scan
228 (weight + next(t) : U32)
229
230def css.score.arg(scan: U.Scan, negation: Bool, next: String -> U32, nested: String -> U32) -> U32:
231 U.Scan{h, t} = scan
232 (U.choose(U32, negation, nested(h), 1000) + next(U.drop(t, ")")) : U32)
233
234def css.score.pseudo(scan: U.Scan, next: String -> U32, nested: String -> U32) -> U32:
235 U.Scan{h, t} = scan
236 +tail = t
237 css.score.arg(U.choose(U.Scan, U.prefix(tail, "("), css.scan(U.drop(tail, "("), 41), U.Scan{"", ")" ++ tail}), U.string.equal(h, "not"), next, nested)
238
239@unsafe
240def css.score(+fuel: Nat, +s: String) -> U32:
241 match fuel s:
242 case 0n _: 0
243 case 1n+d SNil{}: 0
244 case 1n+d SCon{Chr{c}, t}:
245 U.branch(U32, U32.is_eq(c, 42), _ => css.score(d, t),
246 _ => U.branch(U32, U32.is_eq(c, 35) || U32.is_eq(c, 46),
247 _ => css.score.scan(css.ident(t, ""), U.choose(U32, U32.is_eq(c, 35), 1000000, 1000), rest => css.score(d, rest)),
248 _ => U.branch(U32, U32.is_eq(c, 91),
249 _ => css.score.scan(css.scan(t, 93), 1000, rest => css.score(d, U.drop(rest, "]"))),
250 _ => U.branch(U32, U32.is_eq(c, 58),
251 _ => css.score.pseudo(css.ident(t, ""), rest => css.score(d, rest), arg => css.score(d, arg)),
252 _ => css.score.scan(css.ident(s, ""), 1, rest => css.score(d, rest))))))
253
254def css.specificity(steps: List<&2, CSSStep>) -> U32:
255 match steps:
256 case Nil{}: 0
257 case Con{CSSStep{s, relation}, rest}: (css.score(128n, s) + css.specificity(rest) : U32)
258
259type CSSMatch is Data:
260 CSSMatch{matched: Bool, specificity: U32}
261
262def css.select.scan(scan: U.Scan, +context: CSSContext, +best: CSSMatch, next: String -> CSSMatch -> CSSMatch) -> CSSMatch:
263 U.Scan{h, t} = scan
264 CSSMatch{found, score} = best
265 +sel = U.trim.both(h)
266 +parts = css.parts(sel, 0, Nil{})
267 +hit = Bool.not(U.string.equal(sel, "")) && css.match(parts, Con{context, Nil{}}, True{})
268 +weight = css.specificity(parts)
269 next(U.drop(t, ","))(CSSMatch{found || hit, U.choose(U32, hit && U32.is_gt(weight, score), weight, score)})
270
271@unsafe
272def css.select(s: String, +context: CSSContext, best: CSSMatch) -> CSSMatch:
273 match s:
274 case SNil{}: best
275 case SCon{c, t}: css.select.scan(css.scan(SCon{c, t}, 44), context, best, rest => result => css.select(rest, context, result))
276
277type CSSRule is Data:
278 CSSRule{selector: String, declarations: List<&2, XML.Attr>}
279
280def css.declaration.pair(scan: U.Scan) -> XML.Attr:
281 U.Scan{k, t} = scan
282 XML.Attr{css.lower(U.trim.both(k)), U.trim.both(U.drop(t, ":"))}
283
284def css.declaration.expand(a: XML.Attr, rest: List<&2, XML.Attr>) -> List<&2, XML.Attr>:
285 XML.Attr{k, v} = a
286 +key = k
287 +val = v
288 +tail = rest
289 U.choose(List<&2, XML.Attr>, U.string.equal(key, "marker"), Con{XML.Attr{"marker-start", val}, Con{XML.Attr{"marker-mid", val}, Con{XML.Attr{"marker-end", val}, tail}}}, Con{XML.Attr{key, val}, tail})
290
291def css.declarations.scan(scan: U.Scan, next: String -> List<&2, XML.Attr>) -> List<&2, XML.Attr>:
292 U.Scan{h, t} = scan
293 css.declaration.expand(css.declaration.pair(css.scan(h, 58)), next(U.drop(t, ";")))
294
295@unsafe
296def css.declarations(s: String) -> List<&2, XML.Attr>:
297 match s:
298 case SNil{}: Nil{}
299 case SCon{c, t}: css.declarations.scan(css.scan(SCon{c, t}, 59), rest => css.declarations(rest))
300
301def css.media.feature(scan: U.Scan, +w: F32, +h: F32) -> Bool:
302 U.Scan{k, v} = scan
303 +key = U.trim.both(k)
304 +value = css.lower(U.trim.both(v))
305 +x = U.unit(U.number(value), w)
306 U.choose(Bool, U.string.equal(key, "min-width"), F32.is_ge(w, x),
307 U.choose(Bool, U.string.equal(key, "max-width"), F32.is_le(w, x),
308 U.choose(Bool, U.string.equal(key, "width"), F32.is_eq(w, x),
309 U.choose(Bool, U.string.equal(key, "min-height"), F32.is_ge(h, x),
310 U.choose(Bool, U.string.equal(key, "max-height"), F32.is_le(h, x),
311 U.choose(Bool, U.string.equal(key, "height"), F32.is_eq(h, x),
312 U.string.equal(key, "orientation") && U.choose(Bool, U.string.equal(value, "portrait"), F32.is_ge(h, w), U.string.equal(value, "landscape") && F32.is_gt(w, h))))))))
313
314def css.media.parens(scan: U.Scan, w: F32, h: F32, next: String -> Bool) -> Bool:
315 U.Scan{body, tail} = scan
316 css.media.feature(U.until(body, 58, ""), w, h) && next(U.drop(tail, ")"))
317
318def css.media.result(+word: String, +result: Bool) -> Bool:
319 U.choose(Bool, U.string.equal(word, "not"), Bool.not(result),
320 (U.string.equal(word, "screen") || U.string.equal(word, "all") || U.string.equal(word, "and") || U.string.equal(word, "only")) && result)
321
322def css.media.word(scan: U.Scan, next: String -> Bool) -> Bool:
323 U.Scan{head, tail} = scan
324 +word = head
325 U.branch(Bool, U.string.equal(word, ""), _ => False{}, _ => css.media.result(word, next(tail)))
326
327@unsafe
328def css.media.query(+s: String, +w: F32, +h: F32) -> Bool:
329 match s:
330 case SNil{}: True{}
331 case SCon{Chr{c}, t}:
332 U.branch(Bool, U.space(c), _ => css.media.query(t, w, h),
333 _ => U.branch(Bool, U32.is_eq(c, 40), _ => css.media.parens(css.scan(t, 41), w, h, rest => css.media.query(rest, w, h)),
334 _ => css.media.word(css.ident(s, ""), rest => css.media.query(rest, w, h))))
335
336def css.media.list(scan: U.Scan, w: F32, h: F32, next: String -> Bool) -> Bool:
337 U.Scan{head, tail} = scan
338 css.media.query(css.lower(U.trim.both(head)), w, h) || next(U.drop(tail, ","))
339
340@unsafe
341def css.media.more(s: String, +w: F32, +h: F32) -> Bool:
342 match s:
343 case SNil{}: False{}
344 case SCon{c, t}: css.media.list(css.scan(SCon{c, t}, 44), w, h, rest => css.media.more(rest, w, h))
345
346def css.media(+s: String, w: F32, h: F32) -> Bool:
347 U.string.equal(U.trim(s), "") || css.media.more(s, w, h)
348
349def css.rules.append(xs: List<&2, CSSRule>, ys: List<&2, CSSRule>) -> List<&2, CSSRule>:
350 match xs:
351 case Nil{}: ys
352 case Con{h, t}: Con{h, css.rules.append(t, ys)}
353
354def css.rules.body(selector: String, scan: U.Scan, w: F32, h: F32,
355 next: String -> List<&2, CSSRule>, nested: String -> List<&2, CSSRule>) -> List<&2, CSSRule>:
356 U.Scan{body, tail} = scan
357 +sel = U.trim.both(selector)
358 +content = body
359 +following = next(U.drop(tail, "}"))
360 U.branch(List<&2, CSSRule>, U.prefix(sel, "@"),
361 _ => U.branch(List<&2, CSSRule>, U.prefix(css.lower(sel), "@media") && css.media(U.drop(sel, "@media"), w, h),
362 _ => css.rules.append(nested(content), following), _ => following),
363 _ => Con{CSSRule{sel, css.declarations(content)}, following})
364
365def css.rules.head(scan: U.Scan, w: F32, h: F32,
366 next: String -> List<&2, CSSRule>, nested: String -> List<&2, CSSRule>) -> List<&2, CSSRule>:
367 U.Scan{head, tail} = scan
368 +rest = tail
369 css.rules.body(head, U.choose(U.Scan, U.prefix(rest, ";"), U.Scan{"", "}" ++ U.drop(rest, ";")}, css.scan(U.drop(rest, "{"), 125)), w, h, next, nested)
370
371@unsafe
372def css.rules(s: String, +w: F32, +h: F32) -> List<&2, CSSRule>:
373 match s:
374 case SNil{}: Nil{}
375 case SCon{c, t}: css.rules.head(css.scan(SCon{c, t}, 1), w, h, rest => css.rules(rest, w, h), body => css.rules(body, w, h))
376
377def css.text(xs: List<&2, XML.Element>) -> String:
378 match xs:
379 case Nil{}: ""
380 case Con{XML.Element{n, as, kids, txt}, rest}: txt ++ css.text(kids) ++ css.text(rest)
381
382def css.sheets(+xs: List<&2, XML.Element>, +w: F32, +h: F32) -> String:
383 match xs:
384 case Nil{}: ""
385 case Con{XML.Element{n, as, kids, txt}, rest}:
386 +nodes = kids
387 U.branch(String, U.string.equal(n, "style"),
388 _ => U.choose(String, U.string.equal(css.lower(XML.attr.get(as, "type", "text/css")), "text/css") && css.media(XML.attr.get(as, "media", ""), w, h), css.text(nodes), ""),
389 _ => css.sheets(nodes, w, h)) ++ "\n" ++ css.sheets(rest, w, h)
390
391type CSSChoice is Data:
392 CSSChoice{key: String, value: String, important: Bool, specificity: U32}
393
394def css.choice.scan(+k: String, value: U.Scan, score: U32) -> CSSChoice:
395 U.Scan{h, t} = value
396 +strong = U.string.equal(css.lower(U.trim.both(U.drop(t, "!"))), "important")
397 CSSChoice{k, U.trim.both(h), strong, score}
398
399def css.wins(a: CSSChoice, b: CSSChoice) -> Bool:
400 CSSChoice{ak, av, ai, aw} = a
401 CSSChoice{bk, bv, bi, bw} = b
402 +astrong = ai
403 +bstrong = bi
404 (astrong && Bool.not(bstrong)) || (((astrong && bstrong) || (Bool.not(astrong) && Bool.not(bstrong))) && U32.is_ge(aw, bw))
405
406def css.insert(+value: CSSChoice, +xs: List<&2, CSSChoice>) -> List<&2, CSSChoice>:
407 CSSChoice{nk, nv, ni, nw} = value
408 match xs:
409 case Nil{}: Con{value, Nil{}}
410 case Con{CSSChoice{k, v, imp, weight}, rest}:
411 +old = {CSSChoice{k, v, imp, weight} : CSSChoice}
412 U.branch(List<&2, CSSChoice>, U.string.equal(nk, k), _ => Con{U.choose(CSSChoice, css.wins(value, old), value, old), rest},
413 _ => Con{old, css.insert(value, rest)})
414
415def css.cascade(ds: List<&2, XML.Attr>, +score: U32, +choices: List<&2, CSSChoice>) -> List<&2, CSSChoice>:
416 match ds:
417 case Nil{}: choices
418 case Con{XML.Attr{k, v}, rest}:
419 +value = v
420 +key = k
421 css.cascade(rest, score, U.branch(List<&2, CSSChoice>, U.string.equal(key, "") || U.string.equal(value, ""), _ => choices,
422 _ => css.insert(css.choice.scan(key, css.scan(value, 33), score), choices)))
423
424def css.rule.matched(result: CSSMatch, ds: List<&2, XML.Attr>, +choices: List<&2, CSSChoice>) -> List<&2, CSSChoice>:
425 CSSMatch{matched, score} = result
426 U.branch(List<&2, CSSChoice>, matched, _ => css.cascade(ds, score, choices), _ => choices)
427
428def css.apply.rules(rs: List<&2, CSSRule>, +context: CSSContext, choices: List<&2, CSSChoice>) -> List<&2, CSSChoice>:
429 match rs:
430 case Nil{}: choices
431 case Con{CSSRule{sel, ds}, rest}:
432 css.apply.rules(rest, context, css.rule.matched(css.select(sel, context, CSSMatch{False{}, 0}), ds, choices))
433
434def css.chosen(xs: List<&2, CSSChoice>, attrs: List<&2, XML.Attr>) -> List<&2, XML.Attr>:
435 match xs:
436 case Nil{}: attrs
437 case Con{CSSChoice{k, v, important, weight}, rest}: Con{XML.Attr{k, v}, css.chosen(rest, attrs)}
438
439def css.styled(+context: CSSContext, rules: List<&2, CSSRule>) -> List<&2, XML.Attr>:
440 CSSContext{XML.Element{n, as, kids, txt}, parents, previous, index, count} = context
441 +attrs = as
442 Con{XML.Attr{"#css-ready", "1"}, Con{XML.Attr{"style", ""}, css.chosen(css.cascade(css.declarations(XML.attr.get(attrs, "style", "")), 1000000000,
443 css.apply.rules(rules, context, Nil{})), attrs)}}
444
445def css.element.count(xs: List<&2, XML.Element>) -> U32:
446 match xs:
447 case Nil{}: 0
448 case Con{XML.Element{n, as, kids, txt}, rest}: (U.choose(U32, U.string.equal(n, "#text"), 0, 1) + css.element.count(rest) : U32)
449
450@unsafe
451def css.tree(+xs: List<&2, XML.Element>, +rules: List<&2, CSSRule>, +parents: List<&2, CSSContext>, +previous: List<&2, CSSContext>, +index: U32, +count: U32) -> List<&2, XML.Element>:
452 match xs:
453 case Nil{}: Nil{}
454 case Con{XML.Element{n, as, kids, txt}, rest}:
455 +tagname = n
456 +children = kids
457 +node = {XML.Element{tagname, as, children, txt} : XML.Element}
458 +context = {CSSContext{node, parents, previous, index, count} : CSSContext}
459 U.branch(List<&2, XML.Element>, U.string.equal(tagname, "#text"),
460 _ => Con{node, css.tree(rest, rules, parents, previous, index, count)},
461 _ => Con{XML.Element{tagname, css.styled(context, rules), css.tree(children, rules, Con{context, parents}, Nil{}, 1, css.element.count(children)), txt},
462 css.tree(rest, rules, parents, Con{context, previous}, U32.inc(index), count)})