1 module Rakka.Wiki.Engine
10 import qualified Codec.Binary.Base64 as B64
12 import Control.Arrow.ArrowIO
13 import Control.Arrow.ArrowList
14 import qualified Data.ByteString.Lazy as L
16 import Data.Encoding.UTF8
19 import qualified Data.Map as M
21 import Network.HTTP.Lucu
25 import Rakka.SystemConfig
28 import Rakka.Wiki.Parser
29 import Rakka.Wiki.Formatter
30 import Rakka.Wiki.Interpreter
31 import Text.HyperEstraier hiding (getText)
32 import Text.ParserCombinators.Parsec
33 import Text.XML.HXT.Arrow.XmlArrow
34 import Text.XML.HXT.Arrow.XmlNodeSet
35 import Text.XML.HXT.DOM.TypeDefs
38 type InterpTable = Map String Interpreter
44 lang="ja" -- 存在しない場合もある
45 fileName="bar.rakka" -- 存在しない場合もある
46 isTheme="no" -- text/css の場合のみ存在
47 isFeed="no" -- text/x-rakka の場合のみ存在
50 revision="112"> -- デフォルトでない場合のみ存在
51 lastModified="2000-01-01T00:00:00">
55 </summary> -- 存在しない場合もある
57 <otherLang> -- 存在しない場合もある
58 <link lang="ja" page="Bar/Baz" />
66 SKJaHKS8JK/DH8KS43JDK2aKKaSFLLS...
70 xmlizePage :: (ArrowXml a, ArrowChoice a) => a Page XmlTree
75 += sattr "name" (pageName page)
76 += sattr "type" (show $ pageType page)
77 += ( case pageLanguage page of
78 Just x -> sattr "lang" x
81 += ( case pageFileName page of
82 Just x -> sattr "fileName" x
85 += ( case pageType page of
86 MIMEType "text" "css" _
87 -> sattr "isTheme" (yesOrNo $ pageIsTheme page)
88 MIMEType "text" "x-rakka" _
89 -> sattr "isFeed" (yesOrNo $ pageIsFeed page)
93 += sattr "isLocked" (yesOrNo $ pageIsLocked page)
94 += sattr "isBoring" (yesOrNo $ pageIsBoring page)
95 += sattr "isBinary" (yesOrNo $ pageIsBinary page)
96 += sattr "revision" (show $ pageRevision page)
97 += sattr "lastModified" (formatW3CDateTime $ pageLastMod page)
98 += ( case pageSummary page of
99 Just s -> eelem "summary" += txt s
102 += ( if M.null (pageOtherLang page) then
109 | (lang, page) <- M.toList (pageOtherLang page) ]
111 += ( if pageIsBinary page then
113 += txt (B64.encode $ L.unpack $ pageContent page)
117 += txt (decodeLazy UTF8 $ pageContent page)
124 wikifyPage :: (ArrowXml a, ArrowChoice a) => InterpTable -> a XmlTree WikiPage
125 wikifyPage interpTable
127 -> do pName <- getXPathTreesInDoc "/page/@name/text()" >>> getText -< tree
128 pType <- getXPathTreesInDoc "/page/@type/text()" >>> getText >>> arr read -< tree
129 pFileName <- maybeA (getXPathTreesInDoc "/page/fileName/text()" >>> getText) -< tree
130 textData <- maybeA (getXPathTreesInDoc "/page/textData/text()" >>> getText) -< tree
131 base64Data <- maybeA (getXPathTreesInDoc "/page/binaryData/text()" >>> getText) -< tree
133 let dataURI = fmap (binToURI pType) base64Data
136 MIMEType "text" "x-rakka" _
137 -> case parse (wikiPage cmdTypeOf) "" (fromJust textData) of
138 Left err -> wikifyParseError -< err
139 Right xs -> returnA -< xs
142 -- <img src="data:image/png;base64,..." />
143 -> returnA -< [ Paragraph [Image (Left $ fromJust dataURI) Nothing] ]
145 _ -> if isJust dataURI then
146 -- <a href="data:application/zip;base64,...">foo.zip</a>
147 returnA -< [ Paragraph [ Anchor
148 [("href", show dataURI)]
149 [Text (fromMaybe (defaultFileName pType pName) pFileName)]
154 returnA -< [ Preformatted [Text $ fromJust textData] ]
156 cmdTypeOf :: String -> Maybe CommandType
158 = fmap commandType (M.lookup name interpTable)
160 binToURI :: MIMEType -> String -> URI
161 binToURI pType base64Data
164 , uriPath = show pType ++ ";base64," ++ (stripWhiteSpace base64Data)
167 stripWhiteSpace :: String -> String
168 stripWhiteSpace [] = []
169 stripWhiteSpace (x:xs)
170 | x `elem` " \t\n" = stripWhiteSpace xs
171 | otherwise = x : stripWhiteSpace xs
174 makeMainXHTML :: (ArrowXml a, ArrowChoice a, ArrowIO a) =>
179 makeMainXHTML sto sysConf interpTable
181 -> do BaseURI baseURI <- getSysConfA sysConf -< ()
182 wiki <- wikifyPage interpTable -< tree
183 pName <- getXPathTreesInDoc "/page/@name/text()" >>> getText -< tree
184 interpreted <- interpretCommands sto sysConf interpTable
185 -< (pName, Just (tree, wiki), wiki)
186 formatWikiBlocks -< (baseURI, interpreted)
189 makeSubXHTML :: (ArrowXml a, ArrowChoice a, ArrowIO a) =>
193 -> a (PageName, Maybe XmlTree, XmlTree) XmlTree
194 makeSubXHTML sto sysConf interpTable
195 = proc (mainPageName, mainPage, subPage)
196 -> do BaseURI baseURI <- getSysConfA sysConf -< ()
197 mainWiki <- case mainPage of
199 -> do wiki <- wikifyPage interpTable -< page
200 returnA -< Just (page, wiki)
202 -> returnA -< Nothing
203 subWiki <- wikifyPage interpTable -< subPage
204 interpreted <- interpretCommands sto sysConf interpTable
205 -< (mainPageName, mainWiki, subWiki)
206 formatWikiBlocks -< (baseURI, interpreted)
209 interpretCommands :: (ArrowXml a, ArrowChoice a, ArrowIO a) =>
213 -> a (PageName, Maybe (XmlTree, WikiPage), WikiPage) WikiPage
214 interpretCommands sto sysConf interpTable
215 = proc (name, mainPageAndWiki, targetWiki)
216 -> let ctx = InterpreterContext {
218 , ctxMainPage = fmap fst mainPageAndWiki
219 , ctxMainWiki = fmap snd mainPageAndWiki
220 , ctxTargetWiki = targetWiki
222 , ctxSysConf = sysConf
225 ( arrIO (everywhereM' (mkM $ interpBlockCmd ctx))
227 arrIO (everywhereM' (mkM $ interpInlineCmd ctx))
230 interpBlockCmd :: InterpreterContext -> BlockElement -> IO BlockElement
231 interpBlockCmd ctx (BlockCmd cmd) = interpBlockCmd' ctx cmd
232 interpBlockCmd _ others = return others
234 interpBlockCmd' :: InterpreterContext -> BlockCommand -> IO BlockElement
235 interpBlockCmd' ctx cmd
236 = case M.lookup (bCmdName cmd) interpTable of
238 -> fail ("no such interpreter: " ++ bCmdName cmd)
241 -> bciInterpret interp ctx cmd
244 interpInlineCmd :: InterpreterContext -> InlineElement -> IO InlineElement
245 interpInlineCmd ctx (InlineCmd cmd) = interpInlineCmd' ctx cmd
246 interpInlineCmd _ others = return others
248 interpInlineCmd' :: InterpreterContext -> InlineCommand -> IO InlineElement
249 interpInlineCmd' ctx cmd
250 = case M.lookup (iCmdName cmd) interpTable of
252 -> fail ("no such interpreter: " ++ iCmdName cmd)
255 -> iciInterpret interp ctx cmd
258 makeDraft :: (ArrowXml a, ArrowChoice a, ArrowIO a) => InterpTable -> a XmlTree Document
259 makeDraft interpTable
261 do doc <- arrIO0 newDocument -< ()
263 pName <- getXPathTreesInDoc "/page/@name/text()" >>> getText -< tree
264 pType <- getXPathTreesInDoc "/page/@type/text()" >>> getText -< tree
265 pLastMod <- getXPathTreesInDoc "/page/@lastModified/text()" >>> getText -< tree
266 pIsLocked <- getXPathTreesInDoc "/page/@isLocked/text()" >>> getText -< tree
267 pIsBoring <- getXPathTreesInDoc "/page/@isBoring/text()" >>> getText -< tree
268 pIsBinary <- getXPathTreesInDoc "/page/@isBinary/text()" >>> getText -< tree
269 pRevision <- getXPathTreesInDoc "/page/@revision/text()" >>> getText -< tree
270 pLang <- maybeA (getXPathTreesInDoc "/page/@lang/text()" >>> getText) -< tree
271 pFileName <- maybeA (getXPathTreesInDoc "/page/@fileName/text()" >>> getText) -< tree
272 pIsTheme <- maybeA (getXPathTreesInDoc "/page/@isTheme/text()" >>> getText) -< tree
273 pIsFeed <- maybeA (getXPathTreesInDoc "/page/@isFeed/text()" >>> getText) -< tree
274 pSummary <- maybeA (getXPathTreesInDoc "/page/summary/text()" >>> getText) -< tree
276 arrIO2 setURI -< (doc, Just $ mkRakkaURI pName)
277 arrIO2 (flip setAttribute "@title" ) -< (doc, Just pName)
278 arrIO2 (flip setAttribute "@type" ) -< (doc, Just pType)
279 arrIO2 (flip setAttribute "@mdate" ) -< (doc, Just pLastMod)
280 arrIO2 (flip setAttribute "@lang" ) -< (doc, pLang)
281 arrIO2 (flip setAttribute "rakka:fileName") -< (doc, pFileName)
282 arrIO2 (flip setAttribute "rakka:isLocked") -< (doc, Just pIsLocked)
283 arrIO2 (flip setAttribute "rakka:isBoring") -< (doc, Just pIsBoring)
284 arrIO2 (flip setAttribute "rakka:isBinary") -< (doc, Just pIsBinary)
285 arrIO2 (flip setAttribute "rakka:revision") -< (doc, Just pRevision)
286 arrIO2 (flip setAttribute "rakka:summary" ) -< (doc, pSummary)
288 arrIO2 addHiddenText -< (doc, pName)
291 Just s -> arrIO2 addHiddenText -< (doc, s)
292 Nothing -> returnA -< ()
294 -- otherLang はリンク先ページ名を hidden text で入れる。
295 otherLangs <- listA (getXPathTreesInDoc "/page/otherLang/link/@page/text()" >>> getText) -< tree
296 listA ( (arr fst &&& arrL snd)
301 ) -< (doc, otherLangs)
304 MIMEType "text" "css" _
305 -> arrIO2 (flip setAttribute "rakka:isTheme") -< (doc, pIsTheme)
307 MIMEType "text" "x-rakka" _
308 -- wikify して興味のある部分を addText する。
309 -> do arrIO2 (flip setAttribute "rakka:isFeed") -< (doc, pIsFeed)
310 wikiPage <- wikifyPage interpTable -< tree
311 arrIO0 (everywhereM' (mkM (addBlockText doc)) wikiPage) -<< ()
312 arrIO0 (everywhereM' (mkM (addInlineText doc)) wikiPage) -<< ()
320 addBlockText :: Document -> BlockElement -> IO BlockElement
328 addInlineText :: Document -> InlineElement -> IO InlineElement
333 ObjectLink page Nothing
335 ObjectLink page (Just text)
336 -> do addHiddenText doc page
338 PageLink page fragment Nothing
339 -> addText doc (fromMaybe "" page ++
340 fromMaybe "" fragment)
341 PageLink page fragment (Just text)
342 -> do addHiddenText doc (fromMaybe "" page ++
343 fromMaybe "" fragment)
345 ExternalLink uri Nothing
346 -> addText doc (uriToString id uri "")
347 ExternalLink uri (Just text)
348 -> do addHiddenText doc (uriToString id uri "")
353 -- Perform monadic transformation in top-down order.
354 everywhereM' :: Monad m => GenericM m -> GenericM m
355 everywhereM' f x = f x >>= gmapM (everywhereM' f)
358 wikifyParseError :: Arrow a => a ParseError WikiPage
359 wikifyParseError = proc err
360 -> returnA -< [Div [("class", "error")]
361 [ Block (Preformatted [Text (show err)]) ]]