1 module Rakka.Wiki.Parser
9 import Network.URI hiding (fragment)
11 import Text.ParserCombinators.Parsec hiding (label)
14 type CommandTypeOf = String -> Maybe CommandType
17 wikiPage :: CommandTypeOf -> Parser WikiPage
19 = do xs <- many (blockElement cmdTypeOf)
22 (newline >> return ())
28 blockElement :: CommandTypeOf -> Parser BlockElement
29 blockElement cmdTypeOf
30 = try $ do skipMany ( comment
32 (newline >> return ())
34 foldr (<|>) pzero [ heading
36 , listElement cmdTypeOf
37 , definitionList cmdTypeOf
39 , leadingSpaced cmdTypeOf
45 heading :: Parser BlockElement
46 heading = foldr (<|>) pzero (map heading' [1..5])
50 heading' :: Int -> Parser BlockElement
51 heading' n = do try $ do _ <- count n (char '=')
52 notFollowedBy (char '=')
54 x <- notFollowedBy (char '=') >> anyChar
55 xs <- manyTill anyChar (try $ ws >> ( count n (char '=')
57 ("trailing " ++ replicate n '=')
62 return (Heading n (x:xs))
65 horizontalLine :: Parser BlockElement
66 horizontalLine = try ( do _ <- count 4 (char '-')
76 listElement :: CommandTypeOf -> Parser BlockElement
77 listElement cmdTypeOf = listElement' []
79 listElement' :: [Char] -> Parser BlockElement
83 xs <- items (stack ++ [t])
84 return (List (toType t) xs)
86 items :: [Char] -> Parser [ListItem]
87 items stack = do xs <- many1 $ inlineElement cmdTypeOf
88 nested <- option Nothing
89 $ try $ do skipMany comment
92 liftM Just (listElement' stack)
94 return $ (map Inline xs ++ map Block (catMaybes [nested])) : rest
96 (try $ do skipMany comment
105 toType :: Char -> ListType
107 toType '#' = Numbered
111 definitionList :: CommandTypeOf -> Parser BlockElement
112 definitionList cmdTypeOf = liftM DefinitionList (many1 definition)
114 definition :: Parser Definition
115 definition = do _ <- char ';'
117 tHead <- inlineElement cmdTypeOf
120 return (Definition (tHead:tRest) d)
124 term :: Parser [InlineElement]
125 term = (char ':' >> ws >> return [])
127 (newline >> char ':' >> ws >> return [])
129 do x <- inlineElement cmdTypeOf
135 description :: Parser [InlineElement]
136 description = do x <- inlineElement cmdTypeOf
140 try ( do _ <- newline
144 return (Text "\n" : xs)
147 (newline >> return [])
151 "description of term"
154 verbatim :: Parser BlockElement
155 verbatim = do _ <- try (string "<!verbatim[")
156 _ <- many (oneOf " \t\n")
158 return (Preformatted [Text x])
160 verbatim' :: Parser String
161 verbatim' = do _ <- try (many (oneOf " \t\n") >> string "]>")
169 leadingSpaced :: CommandTypeOf -> Parser BlockElement
170 leadingSpaced cmdTypeOf = liftM Preformatted (char ' ' >> leadingSpaced')
174 leadingSpaced' :: Parser [InlineElement]
175 leadingSpaced' = do x <- inlineElement cmdTypeOf
179 try ( liftM (Text "\n" :) ( newline
190 paragraph :: CommandTypeOf -> Parser BlockElement
191 paragraph cmdTypeOf = liftM Paragraph paragraph'
193 paragraph' :: Parser [InlineElement]
194 paragraph' = do x <- inlineElement cmdTypeOf
195 xs <- try ( do _ <- newline
198 -- \n で文字列が終はってゐたら、ここ
202 try ( do _ <- newline
203 _ <- ((oneOf ('\n':blockSymbols) >> pzero) <|> return ())
204 ys <- (paragraph' <|> return [])
205 return (Text "\n" : ys)
206 -- \n があり、その次に \n または
207 -- blockSymbols があれば、fail して最
208 -- 初の newline を讀んだ所まで卷き戻
209 -- す。oneOf が一文字消費しているので、
210 -- <|> は右辺を適用せずに try まで戻
215 -- それ以外の場合は次の inlineElement から
216 -- を讀んで見る。但し一つも無くても良い。
218 return [] -- 全部失敗したらここで終了。
222 blockCmd :: CommandTypeOf -> Parser BlockElement
224 = (try $ do (tagName, tagAttrs) <- openTag
225 case cmdTypeOf tagName of
226 Just BlockCommandType
229 return $ BlockCmd BlockCommand {
231 , bCmdAttributes = tagAttrs
235 Just InlineCommandType
238 _ -> return $ undefinedCmdErr tagName
241 (try $ do (tagName, tagAttrs) <- emptyTag
242 case cmdTypeOf tagName of
243 Just BlockCommandType
244 -> return $ BlockCmd BlockCommand {
246 , bCmdAttributes = tagAttrs
250 Just InlineCommandType
253 _ -> return $ undefinedCmdErr tagName
258 contents :: Parser [BlockElement]
259 contents = do x <- blockElement cmdTypeOf
263 (newline >> contents)
265 (comment >> contents)
269 undefinedCmdErr :: String -> BlockElement
271 = Div [("class", "error")]
272 [ Block (Paragraph [Text ("The command `" ++ name ++ "' is not defined. " ++
273 "Make sure you haven't mistyped.")
278 inlineElement :: CommandTypeOf -> Parser InlineElement
279 inlineElement cmdTypeOf
280 = try $ do skipMany comment
281 foldr (<|>) pzero [ nowiki
282 , apostrophes cmdTypeOf
287 , inlineCmd cmdTypeOf
291 nowiki :: Parser InlineElement
292 nowiki = liftM Text (try (string "<!nowiki[") >> nowiki')
294 nowiki' :: Parser String
295 nowiki' = do _ <- try (string "]>")
303 text :: Parser InlineElement
304 text = liftM (Text . (':' :)) ( char ':'
306 many (noneOf ('\n':inlineSymbols))
308 -- 定義リストとの關係上、コロンは先頭にしか來られない。
310 liftM Text (many1 (noneOf ('\n':inlineSymbols)))
315 apostrophes :: CommandTypeOf -> Parser InlineElement
316 apostrophes cmdTypeOf = foldr (<|>) pzero (map try [apos1, apos2, apos3, apos4, apos5])
318 apos1 = apos 1 >> return (Text "'")
321 xs <- many1 $ inlineElement cmdTypeOf
326 xs <- many1 $ inlineElement cmdTypeOf
330 apos4 = apos 4 >> return (Text "'")
333 xs <- many1 $ inlineElement cmdTypeOf
335 return (Italic [Bold xs])
337 apos :: Int -> Parser ()
338 apos n = count n (char '\'') >> notFollowedBy (char '\'')
341 objLink :: Parser InlineElement
342 objLink = do _ <- try (string "[[[")
343 page <- many1 (noneOf "|]")
344 label <- option Nothing
345 (liftM Just (char '|' >> many1 (satisfy (/= ']'))))
347 return $ ObjectLink page label
352 pageLink :: Parser InlineElement
353 pageLink = do _ <- try (string "[[")
354 page <- option Nothing
355 (liftM Just (many1 (noneOf "#|]")))
356 fragment <- option Nothing
357 (liftM Just (char '#' >> many1 (noneOf "|]")))
358 label <- option Nothing
359 (liftM Just (char '|' >> many1 (satisfy (/= ']'))))
361 case (page, fragment) of
362 (Nothing, Nothing) -> pzero
366 return $ PageLink page fragment label
371 extLink :: Parser InlineElement
372 extLink = do _ <- char '['
373 uriStr <- many1 (noneOf " \t]")
374 _ <- skipMany (oneOf " \t")
375 label <- option Nothing
376 (liftM Just (many1 (noneOf "]")))
378 case parseURI uriStr of
379 Just uri -> char ']' >> return (ExternalLink uri label)
380 Nothing -> pzero <?> "absolute URI"
385 inlineCmd :: CommandTypeOf -> Parser InlineElement
387 = (try $ do (tagName, tagAttrs) <- openTag
388 case cmdTypeOf tagName of
389 Just InlineCommandType
392 return $ InlineCmd InlineCommand {
394 , iCmdAttributes = tagAttrs
400 (try $ do (tagName, tagAttrs) <- emptyTag
401 case cmdTypeOf tagName of
402 Just InlineCommandType
403 -> return $ InlineCmd InlineCommand {
405 , iCmdAttributes = tagAttrs
413 contents :: Parser [InlineElement]
414 contents = do x <- inlineElement cmdTypeOf
418 (comment >> contents)
420 liftM (Text "\n" :) (newline >> contents)
425 openTag :: Parser (String, [Attribute])
426 openTag = try $ do _ <- char '<'
430 attrs <- many $ do attr <- tagAttr
437 emptyTag :: Parser (String, [Attribute])
438 emptyTag = try $ do _ <- char '<'
442 attrs <- many $ do attr <- tagAttr
451 closeTag :: String -> Parser ()
452 closeTag name = try $ do _ <- char '<'
462 tagAttr :: Parser (String, String)
463 tagAttr = do name <- many1 letter
466 value <- many (satisfy (/= '"'))
472 comment = (try (string "<!--") >> skipTillEnd 1)
476 skipTillEnd :: Int -> Parser ()
477 skipTillEnd level = (try (string "<!--") >> skipTillEnd (level + 1))
479 (try (string "-->") >> case level of
481 n -> skipTillEnd (n - 1))
483 (anyChar >> skipTillEnd level)
486 blockSymbols :: [Char]
487 blockSymbols = " =-*#;<"
490 inlineSymbols :: [Char]
491 inlineSymbols = "<[:'"
495 ws = skipMany ( (oneOf " \t" >> return ())
502 eol = (newline >> return ())