|
9 | 9 | racket/match
|
10 | 10 | racket/path
|
11 | 11 | racket/serialize
|
| 12 | + racket/string |
12 | 13 |
|
13 | 14 | scribble/base-render
|
14 | 15 | scribble/core
|
15 |
| - scribble/html-properties |
| 16 | + (except-in scribble/html-properties attributes attributes-assoc) |
| 17 | + (prefix-in scribble: scribble/html-properties) |
16 | 18 | (prefix-in scribble: scribble/html-render)
|
17 | 19 | (only-in scribble/private/render-utils part-style?)
|
18 | 20 |
|
|
23 | 25 | threading
|
24 | 26 | (only-in xml write-xexpr xexpr/c)
|
25 | 27 |
|
| 28 | + "../../lang/base.rkt" |
26 | 29 | "../../lang/metadata.rkt"
|
27 |
| - "../../lang/post-language.rkt" |
28 | 30 | "../../paths.rkt"
|
29 | 31 | "../metadata.rkt"
|
30 | 32 | "highlight/pygments.rkt"
|
|
42 | 44 |
|
43 | 45 | ;; -----------------------------------------------------------------------------
|
44 | 46 |
|
45 |
| -(define (attributes-union attrs1 attrs2) |
46 |
| - (hash-union attrs1 attrs2 #:combine/key |
47 |
| - (λ (k a b) |
48 |
| - (match k |
49 |
| - ['class (string-append a " " b)] |
50 |
| - [_ (error 'merge-attributes |
51 |
| - (string-append "duplicate values for attribute\n" |
52 |
| - " attribute: ~e\n" |
53 |
| - " values:\n" |
54 |
| - " ~e\n" |
55 |
| - " ~e") |
56 |
| - k a b)])))) |
| 47 | +(struct attributes (assoc styles) #:transparent |
| 48 | + #:name make-attributes |
| 49 | + #:constructor-name make-attributes) |
| 50 | + |
| 51 | +(define empty-attributes (make-attributes (hasheq) (hasheq))) |
| 52 | + |
| 53 | +(define (attributes-empty? attrs) |
| 54 | + (and (hash-empty? (attributes-assoc attrs)) |
| 55 | + (hash-empty? (attributes-styles attrs)))) |
| 56 | + |
| 57 | +(define (attributes #:styles [styles (hasheq)] . pairs) |
| 58 | + (if (and (empty? pairs) (hash-empty? styles)) |
| 59 | + empty-attributes |
| 60 | + (make-attributes (apply hasheq pairs) styles))) |
| 61 | + |
| 62 | +(define (list->attributes [pairs '()] #:styles [styles '()]) |
| 63 | + (if (and (empty? pairs) (empty? styles)) |
| 64 | + empty-attributes |
| 65 | + (make-attributes (make-immutable-hasheq pairs) (make-immutable-hasheq styles)))) |
| 66 | + |
| 67 | +(define (attributes-union #:allow-override? [allow-override? #f] attrs . attrss) |
| 68 | + (define (multiple-values-error what k a b) |
| 69 | + (error 'attributes-union |
| 70 | + (~a "multiple values for " what "\n" |
| 71 | + " " what ": ~e\n" |
| 72 | + " values:\n" |
| 73 | + " ~e\n" |
| 74 | + " ~e") |
| 75 | + k a b)) |
| 76 | + |
| 77 | + (for/fold ([attrs attrs]) |
| 78 | + ([new-attrs (in-list attrss)]) |
| 79 | + (make-attributes |
| 80 | + (hash-union (attributes-assoc attrs) |
| 81 | + (attributes-assoc new-attrs) |
| 82 | + #:combine/key |
| 83 | + (λ (k a b) |
| 84 | + (match k |
| 85 | + ['class (string-append a " " b)] |
| 86 | + [_ (if allow-override? |
| 87 | + b |
| 88 | + (multiple-values-error "attribute" k a b))]))) |
| 89 | + (hash-union (attributes-styles attrs) |
| 90 | + (attributes-styles new-attrs) |
| 91 | + #:combine/key |
| 92 | + (λ (k a b) (multiple-values-error "CSS property" k a b)))))) |
57 | 93 |
|
58 | 94 | (define (attributes->list attrs)
|
59 |
| - (for/list ([(k v) (in-hash attrs)]) |
| 95 | + (define all-attrs |
| 96 | + (cond |
| 97 | + [(hash-empty? (attributes-styles attrs)) |
| 98 | + (attributes-assoc attrs)] |
| 99 | + [(hash-ref (attributes-assoc attrs) 'style #f) |
| 100 | + => (λ (existing-styles) |
| 101 | + (raise-arguments-error 'render (~a "conflicting value for ‘style’ attribute;\n" |
| 102 | + " values were given via both ‘attributes’ and ‘css-styles’ properties") |
| 103 | + "attribute value" existing-styles |
| 104 | + "css-styles properties" (attributes-styles attrs)))] |
| 105 | + [else |
| 106 | + (define style-strs (for/list ([(k v) (in-hash (attributes-styles attrs))]) |
| 107 | + (~a k ":" v))) |
| 108 | + (hash-set (attributes-assoc attrs) 'style (string-join style-strs ";"))])) |
| 109 | + (for/list ([(k v) (in-immutable-hash all-attrs)]) |
60 | 110 | (list k v)))
|
61 | 111 |
|
62 | 112 | (define (style->tag-name+attributes s ri)
|
63 | 113 | (for/fold ([tag-name #f]
|
64 | 114 | [attrs (if (string? (style-name s))
|
65 |
| - (hasheq 'class (style-name s)) |
66 |
| - (hasheq))]) |
| 115 | + (attributes 'class (style-name s)) |
| 116 | + (attributes))]) |
67 | 117 | ([prop (in-list (style-properties s))])
|
68 | 118 | (match prop
|
69 | 119 | ['div
|
70 | 120 | (values 'div attrs)]
|
71 | 121 | [(alt-tag name)
|
72 | 122 | (values (string->symbol name) attrs)]
|
73 |
| - [(attributes attrs*) |
74 |
| - (values tag-name (attributes-union attrs (make-immutable-hasheq attrs*)))] |
| 123 | + [(scribble:attributes assoc) |
| 124 | + (values tag-name (attributes-union attrs (list->attributes assoc)))] |
| 125 | + [(css-styles assoc) |
| 126 | + (values tag-name (attributes-union attrs (list->attributes #:styles assoc)))] |
75 | 127 | [(link-target tag)
|
76 | 128 | (values tag-name (attributes-union attrs
|
77 |
| - (hasheq 'id (tag->anchor-name (resolve-tag tag ri)))))] |
| 129 | + (attributes 'id (tag->anchor-name (resolve-tag tag ri)))))] |
78 | 130 | [_
|
79 | 131 | (values tag-name attrs)])))
|
80 | 132 |
|
|
267 | 319 |
|
268 | 320 | (inherit get-dest-directory
|
269 | 321 |
|
| 322 | + render-block |
270 | 323 | render-flow
|
271 | 324 | render-part
|
272 | 325 |
|
|
318 | 371 | ,@(for/list ([blocks (in-list (itemization-blockss e))])
|
319 | 372 | `(li ,@(render-flow blocks part ri #t))))])
|
320 | 373 |
|
| 374 | + (define/override (render-table e part ri starting-item?) |
| 375 | + (define cellss (table-blockss e)) |
| 376 | + (define num-columns |
| 377 | + (cond |
| 378 | + [(empty? cellss) 0] |
| 379 | + [else |
| 380 | + (define num-columns (length (first cellss))) |
| 381 | + (for ([cells (in-list (rest cellss))]) |
| 382 | + (unless (= (length cells) num-columns) |
| 383 | + (raise-arguments-error 'render-table "table rows have inconsistent lengths" |
| 384 | + "first row length" num-columns |
| 385 | + "other row length" (length cells) |
| 386 | + "first row..." (first cellss) |
| 387 | + "other row..." cells))) |
| 388 | + num-columns])) |
| 389 | + |
| 390 | + (define t-style (table-style e)) |
| 391 | + (define col-styles (and~> (findf table-columns? (style-properties t-style)) |
| 392 | + table-columns-styles)) |
| 393 | + (define row-styles (and~> (findf table-rows? (style-properties t-style)) |
| 394 | + table-rows-styles)) |
| 395 | + (define cell-styless (and~> (findf table-cells? (style-properties t-style)) |
| 396 | + table-cells-styless)) |
| 397 | + |
| 398 | + (define (check-count what in-what in-v given expected) |
| 399 | + (unless (= given expected) |
| 400 | + (raise-argument-error 'render-table (~a (if (< given expected) "not enough" "too many") |
| 401 | + " " what " styles for " in-what) |
| 402 | + (~a what " count") expected |
| 403 | + "style count" given |
| 404 | + (~a in-what "...") in-v))) |
| 405 | + (when col-styles |
| 406 | + (check-count "column" "table" e (length col-styles) num-columns)) |
| 407 | + (when row-styles |
| 408 | + (check-count "row" "table" e (length row-styles) (length cellss))) |
| 409 | + (when cell-styless |
| 410 | + (check-count "row" "table" e (length cell-styless) (length cellss)) |
| 411 | + (for ([cells (in-list cellss)] |
| 412 | + [cell-styles (in-list cell-styless)]) |
| 413 | + (check-count "cell" "row" cells (length cell-styles) num-columns))) |
| 414 | + |
| 415 | + (define-values [tag-name attrs] (style->tag-name+attributes t-style ri)) |
| 416 | + `[(,(or tag-name 'table) |
| 417 | + ,(attributes->list attrs) |
| 418 | + ,@(for/list ([cells (in-list cellss)] |
| 419 | + [row-style (if row-styles |
| 420 | + (in-list row-styles) |
| 421 | + (in-cycle (in-value plain)))] |
| 422 | + [cell-styles (if cell-styless |
| 423 | + (in-list cell-styless) |
| 424 | + (in-cycle (in-value #f)))]) |
| 425 | + (define-values [tag-name attrs] (style->tag-name+attributes row-style ri)) |
| 426 | + (list* (or tag-name 'tr) |
| 427 | + (attributes->list attrs) |
| 428 | + (let loop ([cells cells] |
| 429 | + [col-styles col-styles] |
| 430 | + [cell-styles cell-styles]) |
| 431 | + (match cells |
| 432 | + ['() '()] |
| 433 | + [(list* cell (and 'cont cont) ... cells) |
| 434 | + (define colspan (add1 (length cont))) |
| 435 | + |
| 436 | + (define (split-styles ss) |
| 437 | + (if ss |
| 438 | + (values (first ss) (drop ss colspan)) |
| 439 | + (values plain #f))) |
| 440 | + (define-values [col-style col-styles*] (split-styles col-styles)) |
| 441 | + (define-values [cell-style cell-styles*] (split-styles cell-styles)) |
| 442 | + |
| 443 | + (define-values [col-tag-name col-attrs] (style->tag-name+attributes col-style ri)) |
| 444 | + (define-values [cell-tag-name cell-attrs] (style->tag-name+attributes cell-style ri)) |
| 445 | + (define attrs (~> (attributes-union col-attrs cell-attrs #:allow-override? #t) |
| 446 | + (attributes-union (if (= colspan 1) |
| 447 | + (attributes) |
| 448 | + (attributes 'colspan (~a colspan))) |
| 449 | + (table-cell-style->attributes col-style cell-style)))) |
| 450 | + (cons (list* (or cell-tag-name col-tag-name 'td) |
| 451 | + (attributes->list attrs) |
| 452 | + (render-table-cell cell part ri)) |
| 453 | + (loop cells col-styles* cell-styles*))])))))]) |
| 454 | + |
| 455 | + (define/private (table-cell-style->attributes col-s cell-s) |
| 456 | + (define col-props (style-properties col-s)) |
| 457 | + (define cell-props (style-properties cell-s)) |
| 458 | + |
| 459 | + (define (alignment what which) |
| 460 | + (define (go for-what props) |
| 461 | + (define aligns (filter (λ~> (memq which)) props)) |
| 462 | + (match aligns |
| 463 | + ['() #f] |
| 464 | + [(list align) (~a "cell-align-" align)] |
| 465 | + [_ (raise-arguments-error 'render-table (~a for-what " has multiple " what " alignments") |
| 466 | + "alignment properties" aligns)])) |
| 467 | + (or (go "cell" cell-props) |
| 468 | + (go "column" col-props))) |
| 469 | + |
| 470 | + (define h-align (alignment "horizontal" '(left right center))) |
| 471 | + (define v-align (alignment "vertical" '(top baseline bottom vcenter))) |
| 472 | + (attributes 'class (string-join (filter values (list h-align v-align))))) |
| 473 | + |
| 474 | + (define/public (render-table-cell e part ri) |
| 475 | + (cond |
| 476 | + [(and (paragraph? e) |
| 477 | + (memq 'omitable (style-properties (paragraph-style e)))) |
| 478 | + (render-content (paragraph-content e) part ri)] |
| 479 | + [else |
| 480 | + (render-block e part ri #f)])) |
| 481 | + |
321 | 482 | (define/public (render-title-link tag part ri)
|
322 | 483 | (define title-content
|
323 | 484 | (match (resolve-get part ri tag)
|
|
338 | 499 | ; First, we determine what element wrappers are needed by the style.
|
339 | 500 |
|
340 | 501 | ; alt-tag needs a custom wrapper
|
341 |
| - (define alt-tag-wrap (if tag-name (cons tag-name (hasheq)) #f)) |
| 502 | + (define alt-tag-wrap (if tag-name (cons tag-name (attributes)) #f)) |
342 | 503 |
|
343 | 504 | ; target-url needs an 'a wrapper
|
344 | 505 | (define link-wrap
|
345 | 506 | (match (findf target-url? (style-properties style))
|
346 | 507 | [(target-url target)
|
347 |
| - (cons 'a (hasheq 'href target))] |
| 508 | + (cons 'a (attributes 'href target))] |
348 | 509 | [#f #f]))
|
349 | 510 |
|
350 | 511 | ; certain symbolic styles need wrappers
|
351 | 512 | (define style-name-wrap
|
352 | 513 | (match (style-name style)
|
353 |
| - ['bold (cons 'strong (hasheq))] |
354 |
| - [(or 'emph 'italic) (cons 'em (hasheq))] |
355 |
| - ['tt (cons 'code (hasheq))] |
356 |
| - ['superscript (cons 'sup (hasheq))] |
357 |
| - ['subscript (cons 'sub (hasheq))] |
| 514 | + ['bold (cons 'strong (attributes))] |
| 515 | + [(or 'emph 'italic) (cons 'em (attributes))] |
| 516 | + ['tt (cons 'code (attributes))] |
| 517 | + ['superscript (cons 'sup (attributes))] |
| 518 | + ['subscript (cons 'sub (attributes))] |
358 | 519 | [_ #f]))
|
359 | 520 |
|
360 | 521 | ; Now we combine the wrappers and mix in extra attributes.
|
|
363 | 524 | ; If there are no wrappers, but we need to add attributes, add a
|
364 | 525 | ; 'span wrapper to hold them.
|
365 | 526 | ['()
|
366 |
| - (if (hash-empty? attrs) |
| 527 | + (if (attributes-empty? attrs) |
367 | 528 | '()
|
368 | 529 | (list (cons 'span attrs)))]
|
369 | 530 | ; Otherwise, add the attributes to the outermost wrapper.
|
|
381 | 542 | (super render-content elem part ri)))
|
382 | 543 | (match elem
|
383 | 544 | [(target-element _ _ tag)
|
384 |
| - (define anchor-name (tag->anchor-name (resolve-tag tag ri))) |
385 |
| - (define attrs* (attributes-union (hasheq 'name anchor-name) attrs)) |
386 |
| - (wrap-for-style #:attrs (hasheq) `[(a ,(attributes->list attrs*) ,@rendered)])] |
| 545 | + (tag->anchor-name (resolve-tag tag ri)) |
| 546 | + (cons `(a ([name ,(tag->anchor-name (resolve-tag tag ri))])) |
| 547 | + (wrap-for-style rendered))] |
387 | 548 |
|
388 | 549 | [(link-element _ _ tag)
|
389 | 550 | (define indirect? (memq 'indirect-link (style-properties style)))
|
|
405 | 566 | (string-append "#" (uri-encode anchor))
|
406 | 567 | (site-path->url-string path #:fragment anchor))]))
|
407 | 568 |
|
408 |
| - (define attrs* (attributes-union (hasheq 'href href) attrs)) |
409 |
| - (wrap-for-style #:attrs (hasheq) `[(a ,(attributes->list attrs*) ,@rendered)])] |
| 569 | + (define attrs* (attributes-union (attributes 'href href) attrs)) |
| 570 | + (wrap-for-style #:attrs (attributes) `[(a ,(attributes->list attrs*) ,@rendered)])] |
410 | 571 |
|
411 | 572 | [_ (wrap-for-style rendered)])]))
|
412 | 573 |
|
|
450 | 611 | #:body (render-part part ri)))
|
451 | 612 | (write-xexpr xexpr)
|
452 | 613 | xexpr)
|
453 |
| - |
| 614 | + |
454 | 615 | (super-new)))
|
455 | 616 |
|
456 | 617 | ;; The renderer used for actual blog posts, with all the bells and whistles.
|
|
0 commit comments