@@ -0,0 +1,239 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-
3+Copyright (C) 2011 John MacFarlane <jgm@berkeley.edu>
4+
5+This program is free software; you can redistribute it and/or modify
6+it under the terms of the GNU General Public License as published by
7+the Free Software Foundation; either version 2 of the License, or
8+(at your option) any later version.
9+
10+This program is distributed in the hope that it will be useful,
11+but WITHOUT ANY WARRANTY; without even the implied warranty of
12+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+GNU General Public License for more details.
14+
15+You should have received a copy of the GNU General Public License
16+along with this program; if not, write to the Free Software
17+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18+-}
19+
20+{- |
21+ Module : Text.Pandoc.Offline
22+ Copyright : Copyright (C) 2011 John MacFarlane
23+ License : GNU GPL, version 2 or above
24+
25+ Maintainer : John MacFarlane <jgm@berkeley.edu>
26+ Stability : alpha
27+ Portability : portable
28+
29+Functions for converting an HTML file into one that can be viewed
30+offline, by incorporating linked images, CSS, and scripts into
31+the HTML using data URIs.
32+-}
33+module Text.Pandoc.Offline ( offline ) where
34+import Text.HTML.TagSoup
35+import Network.URI (isAbsoluteURI, parseURI, escapeURIString)
36+import Network.Browser
37+import Network.HTTP
38+import Data.ByteString.Base64
39+import System.IO
40+import qualified Data.ByteString.Char8 as B
41+import Data.ByteString (ByteString)
42+import Data.ByteString.UTF8 (toString, fromString)
43+import System.FilePath (takeExtension, dropExtension)
44+import Data.Char (toLower, isAscii, isAlphaNum)
45+import Codec.Compression.GZip as Gzip
46+import qualified Data.ByteString.Lazy as L
47+
48+getItem :: String -> IO ByteString
49+getItem f =
50+ if isAbsoluteURI f
51+ then openURL f
52+ else B.readFile f
53+
54+openURL :: String -> IO ByteString
55+openURL u = getResponseBody =<< simpleHTTP (getReq u)
56+ where getReq u = case parseURI u of
57+ Nothing -> error $ "Could not parse URI: " ++ u
58+ Just u' -> mkRequest GET u'
59+
60+mimeTypeFor :: String -> ByteString
61+mimeTypeFor s = case lookup s mimetypes of
62+ Nothing -> error $ "Could not find mime type for " ++ s
63+ Just x -> x
64+ where mimetypes = [ -- taken from MissingH
65+ (".a", "application/octet-stream"),
66+ (".ai", "application/postscript"),
67+ (".aif", "audio/x-aiff"),
68+ (".aifc", "audio/x-aiff"),
69+ (".aiff", "audio/x-aiff"),
70+ (".au", "audio/basic"),
71+ (".avi", "video/x-msvideo"),
72+ (".bat", "text/plain"),
73+ (".bcpio", "application/x-bcpio"),
74+ (".bin", "application/octet-stream"),
75+ (".bmp", "image/x-ms-bmp"),
76+ (".c", "text/plain"),
77+ (".cdf", "application/x-netcdf"),
78+ (".cpio", "application/x-cpio"),
79+ (".csh", "application/x-csh"),
80+ (".css", "text/css"),
81+ (".dll", "application/octet-stream"),
82+ (".doc", "application/msword"),
83+ (".dot", "application/msword"),
84+ (".dvi", "application/x-dvi"),
85+ (".eml", "message/rfc822"),
86+ (".eps", "application/postscript"),
87+ (".etx", "text/x-setext"),
88+ (".exe", "application/octet-stream"),
89+ (".gif", "image/gif"),
90+ (".gtar", "application/x-gtar"),
91+ (".h", "text/plain"),
92+ (".hdf", "application/x-hdf"),
93+ (".htm", "text/html"),
94+ (".html", "text/html"),
95+ (".ief", "image/ief"),
96+ (".jpe", "image/jpeg"),
97+ (".jpeg", "image/jpeg"),
98+ (".jpg", "image/jpeg"),
99+ (".js", "application/x-javascript"),
100+ (".ksh", "text/plain"),
101+ (".latex", "application/x-latex"),
102+ (".m1v", "video/mpeg"),
103+ (".man", "application/x-troff-man"),
104+ (".me", "application/x-troff-me"),
105+ (".mht", "message/rfc822"),
106+ (".mhtml", "message/rfc822"),
107+ (".mif", "application/x-mif"),
108+ (".mov", "video/quicktime"),
109+ (".movie", "video/x-sgi-movie"),
110+ (".mp2", "audio/mpeg"),
111+ (".mp3", "audio/mpeg"),
112+ (".mpa", "video/mpeg"),
113+ (".mpe", "video/mpeg"),
114+ (".mpeg", "video/mpeg"),
115+ (".mpg", "video/mpeg"),
116+ (".ms", "application/x-troff-ms"),
117+ (".nc", "application/x-netcdf"),
118+ (".nws", "message/rfc822"),
119+ (".o", "application/octet-stream"),
120+ (".obj", "application/octet-stream"),
121+ (".oda", "application/oda"),
122+ (".p12", "application/x-pkcs12"),
123+ (".p7c", "application/pkcs7-mime"),
124+ (".pbm", "image/x-portable-bitmap"),
125+ (".pdf", "application/pdf"),
126+ (".pfx", "application/x-pkcs12"),
127+ (".pgm", "image/x-portable-graymap"),
128+ (".pl", "text/plain"),
129+ (".png", "image/png"),
130+ (".pnm", "image/x-portable-anymap"),
131+ (".pot", "application/vnd.ms-powerpoint"),
132+ (".ppa", "application/vnd.ms-powerpoint"),
133+ (".ppm", "image/x-portable-pixmap"),
134+ (".pps", "application/vnd.ms-powerpoint"),
135+ (".ppt", "application/vnd.ms-powerpoint"),
136+ (".ps", "application/postscript"),
137+ (".pwz", "application/vnd.ms-powerpoint"),
138+ (".py", "text/x-python"),
139+ (".pyc", "application/x-python-code"),
140+ (".pyo", "application/x-python-code"),
141+ (".qt", "video/quicktime"),
142+ (".ra", "audio/x-pn-realaudio"),
143+ (".ram", "application/x-pn-realaudio"),
144+ (".ras", "image/x-cmu-raster"),
145+ (".rdf", "application/xml"),
146+ (".rgb", "image/x-rgb"),
147+ (".roff", "application/x-troff"),
148+ (".rtx", "text/richtext"),
149+ (".sgm", "text/x-sgml"),
150+ (".sgml", "text/x-sgml"),
151+ (".sh", "application/x-sh"),
152+ (".shar", "application/x-shar"),
153+ (".snd", "audio/basic"),
154+ (".so", "application/octet-stream"),
155+ (".src", "application/x-wais-source"),
156+ (".sv4cpio", "application/x-sv4cpio"),
157+ (".sv4crc", "application/x-sv4crc"),
158+ (".swf", "application/x-shockwave-flash"),
159+ (".t", "application/x-troff"),
160+ (".tar", "application/x-tar"),
161+ (".tcl", "application/x-tcl"),
162+ (".tex", "application/x-tex"),
163+ (".texi", "application/x-texinfo"),
164+ (".texinfo", "application/x-texinfo"),
165+ (".tif", "image/tiff"),
166+ (".tiff", "image/tiff"),
167+ (".tr", "application/x-troff"),
168+ (".tsv", "text/tab-separated-values"),
169+ (".txt", "text/plain"),
170+ (".ustar", "application/x-ustar"),
171+ (".vcf", "text/x-vcard"),
172+ (".wav", "audio/x-wav"),
173+ (".wiz", "application/msword"),
174+ (".xbm", "image/x-xbitmap"),
175+ (".xlb", "application/vnd.ms-excel"),
176+ (".xls", "application/vnd.ms-excel"),
177+ (".xml", "text/xml"),
178+ (".xpm", "image/x-xpixmap"),
179+ (".xsl", "application/xml"),
180+ (".xwd", "image/x-xwindowdump"),
181+ (".zip", "application/zip"),
182+ (".jpg", "image/jpg"),
183+ (".mid", "audio/midi"),
184+ (".midi", "audio/midi"),
185+ (".pct", "image/pict"),
186+ (".pic", "image/pict"),
187+ (".pict", "image/pict"),
188+ (".rtf", "application/rtf"),
189+ (".xul", "text/xul")
190+ ]
191+
192+isOk c = isAscii c && isAlphaNum c
193+
194+convertTag :: Tag ByteString -> IO (Tag ByteString)
195+convertTag t@(TagOpen "img" as) =
196+ case fromAttrib "src" t of
197+ src | not (B.null src) -> do
198+ (raw, mime) <- getRaw t src
199+ let enc = "data:" `B.append` mime `B.append` ";base64," `B.append` encode raw
200+ return $ TagOpen "img" (("src",enc) : [(x,y) | (x,y) <- as, x /= "src"])
201+ _ -> return t
202+convertTag t@(TagOpen "script" as) =
203+ case fromAttrib "src" t of
204+ src | not (B.null src) -> do
205+ (raw, mime) <- getRaw t src
206+ let enc = "data:" `B.append` mime `B.append` "," `B.append`
207+ (fromString $ escapeURIString isOk $ toString raw)
208+ return $ TagOpen "script" (("src",enc) : [(x,y) | (x,y) <- as, x /= "src"])
209+ _ -> return t
210+convertTag t@(TagOpen "link" as) =
211+ case fromAttrib "href" t of
212+ src | not (B.null src) -> do
213+ (raw, mime) <- getRaw t src
214+ let enc = "data:" `B.append` mime `B.append` "," `B.append`
215+ (fromString $ escapeURIString isOk $ toString raw)
216+ return $ TagOpen "link" (("href",enc) : [(x,y) | (x,y) <- as, x /= "href"])
217+ _ -> return t
218+convertTag t = return t
219+
220+getRaw :: Tag ByteString -> ByteString -> IO (ByteString, ByteString)
221+getRaw t src = do
222+ let src' = toString src
223+ let ext = map toLower $ takeExtension src'
224+ let (ext',decompress) = if ext == ".gz"
225+ then (takeExtension $ dropExtension src', B.concat . L.toChunks . Gzip.decompress . L.fromChunks . (:[]))
226+ else (ext, id)
227+ let mime = case fromAttrib "type" t of
228+ x | not (B.null x) -> x
229+ _ -> mimeTypeFor ext'
230+ raw <- getItem src'
231+ return (decompress raw, mime)
232+
233+offline :: ByteString -> IO ByteString
234+offline inp = do
235+ let tags = parseTags inp
236+ out <- mapM convertTag tags
237+ return $ renderTagsOptions renderOptions{ optMinimize = (\t -> t == "br"
238+ || t == "img" || t == "meta" || t == "link" ) } out
239+