Skip to content

Commit f1e56ae

Browse files
committed
Add XSLT stylesheet support (closes #6)
1 parent 3c1d739 commit f1e56ae

File tree

4 files changed

+95
-19
lines changed

4 files changed

+95
-19
lines changed

splitflap-doc/mod-splitflap.scrbl

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
"misc.rkt"
55
scribble/examples
66
(for-label splitflap
7-
(except-in gregor date? date)
7+
(except-in gregor date? date)
8+
(only-in net/url url?)
89
racket/base
910
racket/promise
11+
(only-in racket/string non-empty-string?)
1012
txexpr
1113
xml)
1214
(for-syntax racket/base racket/syntax))
@@ -103,10 +105,20 @@ The @racket[_feed-url] argument must be supplied as a valid URL string when @rac
103105
It is not a required argument when @racket[_data] is any other type, and in those cases it will be
104106
ignored if supplied.
105107

106-
If the parameter @racket[include-generator?] is @racket[#t], a @tt{generator} element will be
107-
included in complete feeds. If the parameter @racket[feed-language] is not set to @racket[#f], it
108-
will be used as the language code for the feed; otherwise the result of @racket[(force
109-
system-language)] is used.
108+
For complete feeds (e.g., when @racket[_data] is a @racket[feed] or @racket[podcast]) the output can
109+
be further affected by other parameters. View their documentation for more information.
110+
111+
@itemlist[#:style 'compact
112+
113+
@item{If @racket[include-generator?] is @racket[#t], a @tt{generator} element will be included.}
114+
115+
@item{If @racket[feed-language] is not @racket[#f], its value will be used as the language code for
116+
the feed; otherwise the result of @racket[(force system-language)] is used.}
117+
118+
@item{If @racket[feed-xslt-stylesheet] is not @racket[#f], its value will be used as the path to a
119+
stylesheet for the feed.}
120+
121+
]
110122

111123
@examples[#:eval mod-feed
112124
(define item
@@ -139,6 +151,23 @@ A parameter that, when not set to @racket[#f], is used by @racket[express-xml] a
139151

140152
}
141153

154+
@defparam[feed-xslt-stylesheet path (or/c url? non-empty-string? #f) #:value #f]{
155+
156+
If this parameter is not @racket[#f], @racket[express-xml] will use its value as the path to an
157+
@link["https://en.wikipedia.org/wiki/XSLT"]{XSLT stylesheet} in the prolog of the XML document
158+
produced for a @racket[feed] or @racket[podcast].
159+
160+
Generally, the XSLT file must be served from the same domain as the feed itself, otherwise the
161+
styling will not be applied.
162+
163+
@margin-note{Stylesheets can solve the problem of feeds looking and behaving in unfriendly ways when
164+
accessed directly in a web browser. See
165+
@link["https://github.com/genmon/aboutfeeds/issues/8#issuecomment-673293655"]{this Github issue} for
166+
examples of people who have used XSLT stylesheets to add informative links and a welcoming layout to
167+
their feeds.}
168+
169+
}
170+
142171
@section{Podcasts}
143172

144173
Splitflap provides some special data types for podcast feeds: @racket[episode] and

splitflap-lib/private/dust.rkt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#lang racket/base
22

33
(require (for-syntax racket/base)
4+
(only-in net/url url? url->string)
45
racket/file
56
racket/list
67
racket/match
@@ -37,10 +38,23 @@
3738
(check-equal? (as-cdata '(div (p "Hi ]]> whoops how did that get there")))
3839
(cdata 'racket 'racket "<![CDATA[<div><p>Hi ]]&gt; whoops how did that get there</p></div>]]>")))
3940

41+
;; Optional path to XSLT stylesheet
42+
(define feed-xslt-stylesheet (make-parameter #f))
43+
4044
;; Convert an x-expression to a complete XML document
4145
(define (xml-document xpr)
46+
(define xslt-path
47+
(match (feed-xslt-stylesheet)
48+
[(? url? u) (url->string u)]
49+
[v v]))
50+
(define xml-processing
51+
(cons (p-i 'racket 'racket 'xml "version=\"1.0\" encoding=\"UTF-8\"")
52+
(if xslt-path
53+
(list (p-i 'racket 'racket 'xml-stylesheet
54+
(format "href=\"~a\" type=\"text/xsl\"" xslt-path)))
55+
'())))
4256
(document
43-
(prolog (list (p-i 'racket 'racket 'xml "version=\"1.0\" encoding=\"UTF-8\"")) #f '())
57+
(prolog xml-processing #f '())
4458
(xexpr->xml xpr)
4559
'()))
4660

@@ -135,9 +149,9 @@
135149
`(,element
136150
,@(if/sp (and (txexpr? content) (eq? dialect 'atom)) `[[type "html"]])
137151
,(cond
138-
[(string? content)
139-
(let ([result (as-cdata content)]) (if preserve-cdata-struct? result (cdata-string result)))]
140-
[else (txexpr->safe-content-str content)])))
152+
[(string? content)
153+
(let ([result (as-cdata content)]) (if preserve-cdata-struct? result (cdata-string result)))]
154+
[else (txexpr->safe-content-str content)])))
141155

142156
(module+ test
143157
(define content2 "<div>Hi ]]> whoops how did that get there</div>")

splitflap-lib/private/feed.rkt

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,22 @@
66
"xml-generic.rkt"
77
"validation.rkt"
88
gregor
9+
(only-in net/url url?)
910
txexpr
1011
racket/contract
1112
racket/generic
1213
racket/list
1314
racket/match
1415
racket/promise
1516
racket/runtime-path
17+
(only-in racket/string non-empty-string?)
1618
setup/getinfo
1719
xml)
1820

19-
(provide feed-language
20-
food?
21+
(provide food?
2122
(contract-out
23+
[feed-xslt-stylesheet (parameter/c (or/c non-empty-string? url? #f))]
24+
[feed-language (parameter/c (or/c iso-639-language-code? #f))]
2225
[rename make-feed-item feed-item
2326
(->* (tag-uri? valid-url-string? string? person? moment? moment? xexpr?)
2427
((or/c enclosure? #f))
@@ -55,16 +58,9 @@
5558
include-generator?
5659
express-xml)
5760

58-
5961
;; ~~ Ancillary elements ~~~~~~~~~~~~~~~~~~~~~~~~~
6062

61-
(define feed-language
62-
(make-parameter
63-
#f
64-
(λ (v)
65-
(unless (or (iso-639-language-code? v) (not v))
66-
(raise-argument-error 'feed-locale "either iso-639-language-code? or #false" v))
67-
v)))
63+
(define feed-language (make-parameter #f))
6864

6965
;; Build the <generator> tag (caches for each dialect)
7066
(define generator

splitflap-lib/tests.rkt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
(require "main.rkt"
66
gregor
7+
net/url
78
rackunit)
89

910
(define site-id (mint-tag-uri "example.com" "2007" "blog"))
@@ -91,6 +92,42 @@ END
9192
(check-equal? (express-xml f1 'atom "https://example.com/feed.atom") expect-feed-atom)
9293
(check-equal? (express-xml f1 'rss "https://example.com/feed.rss") expect-feed-rss))
9394

95+
;; Setup: XSLT Stylesheet inclusion
96+
(define expect-feed-atom-xslt #<<END
97+
<?xml version="1.0" encoding="UTF-8"?>
98+
<?xml-stylesheet href="/feed.xsl" type="text/xsl"?>
99+
100+
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
101+
<title type="text">Kate Poster Posts</title>
102+
<link rel="self" href="https://example.com/feed.atom" />
103+
<link rel="alternate" href="https://example.com/" />
104+
<updated>2007-03-17T00:00:00Z</updated>
105+
<id>tag:example.com,2007:blog</id>
106+
<entry>
107+
<title type="text">Kate's First Post</title>
108+
<link rel="alternate" href="https://example.com/blog/one.html" />
109+
<updated>2007-03-17T00:00:00Z</updated>
110+
<published>2007-03-17T00:00:00Z</published>
111+
<link rel="enclosure" type="audio/x-m4a" length="1234" href="gopher://umn.edu/greeting.m4a" />
112+
<author>
113+
<name>Kate Poster</name>
114+
<email>kate@example.com</email>
115+
</author>
116+
<id>tag:example.com,2007:blog.one</id>
117+
<content type="html">&lt;div&gt;&lt;p&gt;Welcome to my blog.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&amp;amp; ' &amp;lt; &amp;gt; © ℗ ™&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;</content>
118+
</entry>
119+
</feed>
120+
END
121+
)
122+
123+
;; Check expected results of setting feed-xslt-stylesheet to a string and a url struct
124+
(parameterize ([include-generator? #f]
125+
[feed-language 'en])
126+
(parameterize ([feed-xslt-stylesheet "/feed.xsl"])
127+
(check-equal? (express-xml f1 'atom "https://example.com/feed.atom") expect-feed-atom-xslt))
128+
(parameterize ([feed-xslt-stylesheet (string->url "/feed.xsl")])
129+
(check-equal? (express-xml f1 'atom "https://example.com/feed.atom") expect-feed-atom-xslt)))
130+
94131
;; Setup: Duplicate tag URIs cause exception at time of feed construction
95132
(define entry2-suffix "conflict")
96133
(define entry2

0 commit comments

Comments
 (0)