git.iko.soy/typeable/pandoc
on master help sign in
Added basic mediawiki reader.

Text.Pandoc.Readers.MediaWiki module, tests/mediawiki-reader.{txt,native}.

commit3d361b2101c097ecde343625b15da8c197d733eb authorJohn MacFarlane <fiddlosopher@gmail.com> dateMon 10 Sep 2012, 10:02 (14 years ago) parent167012d
8 files changed+435−7
pandoc.cabal+8−4 src/Text/Pandoc.hs+3 src/Text/Pandoc/Readers/Markdown.hs−2 src/Text/Pandoc/Readers/MediaWiki.hs+311 src/pandoc.hs+1 tests/Tests/Old.hs+6−1 tests/mediawiki-reader.native+35 tests/mediawiki-reader.wiki+71
pandoc.cabal+8−4
@@ -16,10 +16,11 @@ Synopsis: Conversion between markup formats
1616Description: Pandoc is a Haskell library for converting from one markup
1717 format to another, and a command-line tool that uses
1818 this library. It can read markdown and (subsets of) HTML,
19− reStructuredText, LaTeX, DocBook, and Textile, and it can write
20− markdown, reStructuredText, HTML, LaTeX, ConTeXt, Docbook,
21− OpenDocument, ODT, Word docx, RTF, MediaWiki, Textile,
22− groff man pages, plain text, Emacs Org-Mode, AsciiDoc, EPUB,
19+ reStructuredText, LaTeX, DocBook, MediaWiki markup,
20+ and Textile, and it can write markdown, reStructuredText,
21+ HTML, LaTeX, ConTeXt, Docbook, OpenDocument, ODT,
22+ Word docx, RTF, MediaWiki, Textile, groff man pages,
23+ plain text, Emacs Org-Mode, AsciiDoc, EPUB,
2324 FictionBook2, and S5, Slidy and Slideous HTML slide shows.
2425 .
2526 Pandoc extends standard markdown syntax with footnotes,
@@ -120,6 +121,8 @@ Extra-Source-Files:
120121 tests/markdown-citations.mhra.txt,
121122 tests/markdown-citations.ieee.txt,
122123 tests/textile-reader.textile,
124+ tests/mediawiki-reader.wiki,
125+ tests/mediawiki-reader.native,
123126 tests/rst-reader.native,
124127 tests/rst-reader.rst,
125128 tests/s5.basic.html,
@@ -262,6 +265,7 @@ Library
262265 Text.Pandoc.Readers.HTML,
263266 Text.Pandoc.Readers.LaTeX,
264267 Text.Pandoc.Readers.Markdown,
268+ Text.Pandoc.Readers.MediaWiki,
265269 Text.Pandoc.Readers.RST,
266270 Text.Pandoc.Readers.DocBook,
267271 Text.Pandoc.Readers.TeXMath,
src/Text/Pandoc.hs+3
@@ -66,6 +66,7 @@ module Text.Pandoc
6666 , writers
6767 -- * Readers: converting /to/ Pandoc format
6868 , readMarkdown
69+ , readMediaWiki
6970 , readRST
7071 , readLaTeX
7172 , readHtml
@@ -110,6 +111,7 @@ module Text.Pandoc
110111import Text.Pandoc.Definition
111112import Text.Pandoc.Generic
112113import Text.Pandoc.Readers.Markdown
114+import Text.Pandoc.Readers.MediaWiki
113115import Text.Pandoc.Readers.RST
114116import Text.Pandoc.Readers.DocBook
115117import Text.Pandoc.Readers.LaTeX
@@ -179,6 +181,7 @@ readers = [("native" , \_ -> readNative)
179181 ,("markdown_strict" , readMarkdown)
180182 ,("markdown" , readMarkdown)
181183 ,("rst" , readRST)
184+ ,("mediawiki" , readMediaWiki)
182185 ,("docbook" , readDocBook)
183186 ,("textile" , readTextile) -- TODO : textile+lhs
184187 ,("html" , readHtml)
src/Text/Pandoc/Readers/Markdown.hs−2
@@ -1,6 +1,4 @@
11{-# LANGUAGE RelaxedPolyRec #-} -- needed for inlinesBetween on GHC < 7
2−{-# LANGUAGE FlexibleInstances, TypeSynonymInstances,
3− GeneralizedNewtypeDeriving #-}
42{-
53Copyright (C) 2006-2010 John MacFarlane <jgm@berkeley.edu>
64
src/Text/Pandoc/Readers/MediaWiki.hsnew file+311
@@ -0,0 +1,311 @@
1+{-# LANGUAGE RelaxedPolyRec #-} -- needed for inlinesBetween on GHC < 7
2+{-
3+Copyright (C) 2012 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.Readers.MediaWiki
22+ Copyright : Copyright (C) 2012 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+Conversion of mediawiki text to 'Pandoc' document.
30+-}
31+{-
32+TODO:
33+_ fix pre parser -- it should use html tagsoup parsers,
34+ then just strip out the text from text tags.
35+_ correctly handle skipped level in list, e.g. # to ###
36+_ tests for lists
37+_ support HTML lists
38+_ support list style attributes and start values in ol lists, also
39+ value attribute on li
40+_ support <p> tags in lists (and out?)
41+_ support :, ::, etc. for indent (treat as list continuation paras?)
42+_ support preformatted text (lines starting with space)
43+_ support preformatted text blocks
44+_ code highlighting: http://www.mediawiki.org/wiki/Extension:SyntaxHighlight_GeSHi <syntaxhighlight lang="php"> (alternativel, <source...>)
45+ if 'line' attribute present, number lines
46+ if 'start' present, set starting line number
47+_ support internal links http://www.mediawiki.org/wiki/Help:Links
48+_ support external links
49+_ support automatic linkification of URLs
50+_ support images http://www.mediawiki.org/wiki/Help:Images
51+_ ignore gallery tag?
52+_ support tables http://www.mediawiki.org/wiki/Help:Tables
53+_ support <math> tag for latex math
54+_ templates or anything in {{}} -> handle as raw wikimedia, can be dealt with in
55+ postprocessing
56+_ category links
57+_ tests for raw html inline
58+_ tests for sup, sub, del
59+_ tests for pre, haskell
60+_ tests for code, tt, hask
61+_ test for blockquote
62+-}
63+module Text.Pandoc.Readers.MediaWiki ( readMediaWiki ) where
64+
65+import Text.Pandoc.Definition
66+import qualified Text.Pandoc.Builder as B
67+import Text.Pandoc.Builder (Inlines, Blocks, trimInlines, (<>))
68+import Text.Pandoc.Options
69+import Text.Pandoc.Readers.HTML ( htmlTag, htmlInBalanced,
70+ isInlineTag, isBlockTag, isTextTag, isCommentTag )
71+import Text.Pandoc.XML ( fromEntities )
72+import Text.Pandoc.Parsing
73+import Text.Pandoc.Shared ( stripTrailingNewlines )
74+import Data.Monoid (mconcat, mempty)
75+import Control.Applicative ((<$>), (<*), (*>), (<$))
76+import Control.Monad
77+import Data.List (intersperse)
78+import Text.HTML.TagSoup
79+
80+-- | Read mediawiki from an input string and return a Pandoc document.
81+readMediaWiki :: ReaderOptions -- ^ Reader options
82+ -> String -- ^ String to parse (assuming @'\n'@ line endings)
83+ -> Pandoc
84+readMediaWiki opts s =
85+ (readWith parseMediaWiki) def{ stateOptions = opts } (s ++ "\n\n")
86+
87+type MWParser = Parser [Char] ParserState
88+
89+--
90+-- auxiliary functions
91+--
92+
93+specialChars :: [Char]
94+specialChars = "'[]<=&*"
95+
96+spaceChars :: [Char]
97+spaceChars = " \n\t"
98+
99+sym :: String -> MWParser ()
100+sym s = () <$ try (string s)
101+
102+htmlComment :: MWParser ()
103+htmlComment = () <$ htmlTag isCommentTag
104+
105+inlinesInTags :: String -> MWParser Inlines
106+inlinesInTags tag = trimInlines . mconcat <$> try
107+ (htmlTag (~== TagOpen tag []) *>
108+ manyTill inline (htmlTag (~== TagClose tag)))
109+
110+blocksInTags :: String -> MWParser Blocks
111+blocksInTags tag = mconcat <$> try
112+ (htmlTag (~== TagOpen tag []) *>
113+ manyTill block (htmlTag (~== TagClose tag)))
114+
115+charsInTags :: String -> MWParser [Char]
116+charsInTags tag = fromEntities <$> try
117+ (htmlTag (~== TagOpen tag []) *>
118+ manyTill anyChar (htmlTag (~== TagClose tag)))
119+
120+--
121+-- main parser
122+--
123+
124+parseMediaWiki :: MWParser Pandoc
125+parseMediaWiki = do
126+ bs <- mconcat <$> many block
127+ spaces
128+ eof
129+ return $ B.doc bs
130+
131+--
132+-- block parsers
133+--
134+
135+block :: MWParser Blocks
136+block = header
137+ <|> hrule
138+ <|> bulletList
139+ <|> orderedList
140+ <|> definitionList
141+ <|> blockquote
142+ <|> codeblock
143+ <|> haskell
144+ <|> mempty <$ skipMany1 blankline
145+ <|> mempty <$ try (spaces *> htmlComment)
146+ <|> para
147+
148+para :: MWParser Blocks
149+para = B.para . trimInlines . mconcat <$> many1 inline
150+
151+hrule :: MWParser Blocks
152+hrule = B.horizontalRule <$ try (string "----" *> many (char '-') *> newline)
153+
154+blockquote :: MWParser Blocks
155+blockquote = B.blockQuote <$> blocksInTags "blockquote"
156+
157+codeblock :: MWParser Blocks
158+codeblock = B.codeBlock . trimCode <$> charsInTags "pre"
159+
160+trimCode :: String -> String
161+trimCode ('\n':xs) = stripTrailingNewlines xs
162+trimCode xs = stripTrailingNewlines xs
163+
164+haskell :: MWParser Blocks
165+haskell = B.codeBlockWith ("",["haskell"],[]) . trimCode <$>
166+ charsInTags "haskell"
167+
168+header :: MWParser Blocks
169+header = try $ do
170+ col <- sourceColumn <$> getPosition
171+ guard $ col == 1 -- header must be at beginning of line
172+ eqs <- many1 (char '=')
173+ let lev = length eqs
174+ guard $ lev <= 6
175+ contents <- trimInlines . mconcat <$> manyTill inline (count lev $ char '=')
176+ return $ B.header lev contents
177+
178+bulletList :: MWParser Blocks
179+bulletList = B.bulletList <$> many1 (listItem '*')
180+
181+orderedList :: MWParser Blocks
182+orderedList = B.orderedList <$> many1 (listItem '#')
183+
184+definitionList :: MWParser Blocks
185+definitionList = B.definitionList <$> many1 defListItem
186+
187+defListItem :: MWParser (Inlines, [Blocks])
188+defListItem = try $ do
189+ terms <- mconcat . intersperse B.linebreak <$> many1 defListTerm
190+ defs <- many1 $ listItem ':'
191+ return (terms, defs)
192+
193+defListTerm :: MWParser Inlines
194+defListTerm = char ';' >> skipMany spaceChar >> manyTill anyChar newline >>=
195+ parseFromString (trimInlines . mconcat <$> many inline)
196+
197+listStart :: Char -> MWParser ()
198+listStart c = char c *> notFollowedBy listStartChar
199+
200+listStartChar :: MWParser Char
201+listStartChar = oneOf "*#;:"
202+
203+anyListStart :: MWParser ()
204+anyListStart = listStart '*' <|> listStart '#' <|> listStart ';'
205+
206+listItem :: Char -> MWParser Blocks
207+listItem c = try $ do
208+ listStart c
209+ first <- manyTill anyChar newline
210+ rest <- many (try $ char c *> lookAhead listStartChar *>
211+ manyTill anyChar newline)
212+ parseFromString (mconcat <$> many1 block) $ unlines $ first : rest
213+
214+--
215+-- inline parsers
216+--
217+
218+inline :: MWParser Inlines
219+inline = whitespace
220+ <|> url
221+ <|> str
222+ <|> strong
223+ <|> emph
224+ <|> nowiki
225+ <|> linebreak
226+ <|> externalLink
227+ <|> strikeout
228+ <|> subscript
229+ <|> superscript
230+ <|> code
231+ <|> hask
232+ <|> B.singleton <$> charRef
233+ <|> inlineHtml
234+ <|> special
235+
236+str :: MWParser Inlines
237+str = B.str <$> many1 (noneOf $ specialChars ++ spaceChars)
238+
239+special :: MWParser Inlines
240+special = B.str <$> count 1 (notFollowedBy' (htmlTag isBlockTag) *>
241+ oneOf specialChars)
242+
243+inlineHtml :: MWParser Inlines
244+inlineHtml = B.rawInline "html" . snd <$> htmlTag isInlineTag
245+
246+whitespace :: MWParser Inlines
247+whitespace = B.space <$ (skipMany1 spaceChar <|> endline <|> htmlComment)
248+
249+endline :: MWParser ()
250+endline = () <$ try (newline <*
251+ notFollowedBy blankline <*
252+ notFollowedBy' hrule <*
253+ notFollowedBy anyListStart)
254+
255+linebreak :: MWParser Inlines
256+linebreak = B.linebreak <$
257+ (htmlTag (~== TagOpen "br" []) *>
258+ optional (htmlTag (~== TagClose "br")) *>
259+ optional blankline)
260+
261+externalLink :: MWParser Inlines
262+externalLink = try $ do
263+ char '['
264+ (orig, src) <- uri
265+ skipMany1 spaceChar
266+ lab <- manyTill inline (char ']')
267+ let lab' = if null lab
268+ then [B.str "1"] -- TODO generate sequentially from state
269+ else lab
270+ return $ B.link src "" $ trimInlines $ mconcat lab'
271+
272+url :: MWParser Inlines
273+url = do
274+ (_, src) <- uri
275+ return $ B.link src "" (B.str orig)
276+
277+nowiki :: MWParser Inlines
278+nowiki = B.text <$> charsInTags "nowiki"
279+
280+strikeout :: MWParser Inlines
281+strikeout = B.strikeout <$> (inlinesInTags "strike" <|> inlinesInTags "del")
282+
283+superscript :: MWParser Inlines
284+superscript = B.superscript <$> inlinesInTags "sup"
285+
286+subscript :: MWParser Inlines
287+subscript = B.subscript <$> inlinesInTags "sub"
288+
289+code :: MWParser Inlines
290+code = B.code <$> (charsInTags "code" <|> charsInTags "tt")
291+
292+hask :: MWParser Inlines
293+hask = B.codeWith ("",["haskell"],[]) <$> charsInTags "hask"
294+
295+-- | Parses a list of inlines between start and end delimiters.
296+inlinesBetween :: (Show b) => MWParser a -> MWParser b -> MWParser Inlines
297+inlinesBetween start end =
298+ (trimInlines . mconcat) <$> try (start >> many1Till inner end)
299+ where inner = innerSpace <|> (notFollowedBy' (() <$ whitespace) >> inline)
300+ innerSpace = try $ whitespace >>~ notFollowedBy' end
301+
302+emph :: MWParser Inlines
303+emph = B.emph <$> nested (inlinesBetween start end)
304+ where start = sym "''" >> lookAhead nonspaceChar
305+ end = try $ notFollowedBy' (() <$ strong) >> sym "''"
306+
307+strong :: MWParser Inlines
308+strong = B.strong <$> nested (inlinesBetween start end)
309+ where start = sym "'''" >> lookAhead nonspaceChar
310+ end = try $ sym "'''"
311+
src/pandoc.hs+1
@@ -721,6 +721,7 @@ defaultReaderName fallback (x:xs) =
721721 ".rst" -> "rst"
722722 ".lhs" -> "markdown+lhs"
723723 ".db" -> "docbook"
724+ ".wiki" -> "mediawiki"
724725 ".textile" -> "textile"
725726 ".native" -> "native"
726727 ".json" -> "json"
tests/Tests/Old.hs+6−1
@@ -119,9 +119,14 @@ tests = [ testGroup "markdown"
119119 , fb2WriterTest "math" [] "fb2.math.markdown" "fb2.math.fb2"
120120 , fb2WriterTest "testsuite" [] "testsuite.native" "writer.fb2"
121121 ]
122+ , testGroup "mediawiki"
123+ [ testGroup "writer" $ writerTests "mediawiki"
124+ , test "reader" ["-r", "mediawiki", "-w", "native", "-s"]
125+ "mediawiki-reader.wiki" "mediawiki-reader.native"
126+ ]
122127 , testGroup "other writers" $ map (\f -> testGroup f $ writerTests f)
123128 [ "opendocument" , "context" , "texinfo"
124− , "man" , "plain" , "mediawiki", "rtf", "org", "asciidoc"
129+ , "man" , "plain" , "rtf", "org", "asciidoc"
125130 ]
126131 ]
127132
tests/mediawiki-reader.nativenew file+35
@@ -0,0 +1,35 @@
1+Pandoc (Meta {docTitle = [], docAuthors = [], docDate = []})
2+[Header 1 [Str "header"]
3+,Header 2 [Str "header",Space,Str "level",Space,Str "two"]
4+,Header 3 [Str "header",Space,Str "level",Space,Str "3"]
5+,Header 4 [Str "header",Space,Emph [Str "level"],Space,Str "four"]
6+,Header 5 [Str "header",Space,Str "level",Space,Str "5"]
7+,Header 6 [Str "header",Space,Str "level",Space,Str "6"]
8+,Para [Str "=======",Space,Str "not",Space,Str "a",Space,Str "header",Space,Str "========"]
9+,Para [Str "==",Space,Str "not",Space,Str "a",Space,Str "header",Space,Str "=="]
10+,Header 2 [Str "emph",Space,Str "and",Space,Str "strong"]
11+,Para [Emph [Str "emph"],Space,Strong [Str "strong"]]
12+,Para [Strong [Emph [Str "strong",Space,Str "and",Space,Str "emph"]]]
13+,Para [Strong [Emph [Str "emph",Space,Str "inside"],Space,Str "strong"]]
14+,Para [Strong [Str "strong",Space,Str "with",Space,Emph [Str "emph"]]]
15+,Para [Emph [Strong [Str "strong",Space,Str "inside"],Space,Str "emph"]]
16+,Header 2 [Str "horizontal",Space,Str "rule"]
17+,Para [Str "top"]
18+,HorizontalRule
19+,Para [Str "bottom"]
20+,HorizontalRule
21+,Header 2 [Str "nowiki"]
22+,Para [Str "''not",Space,Str "emph''"]
23+,Header 2 [Str "strikeout"]
24+,Para [Strikeout [Str "This",Space,Str "is",Space,Emph [Str "struck",Space,Str "out"]]]
25+,Header 2 [Str "entities"]
26+,Para [Str "hi",Space,Str "&",Space,Str "low"]
27+,Para [Str "hi",Space,Str "&",Space,Str "low"]
28+,Para [Str "G\246del"]
29+,Para [Str "\777\2730"]
30+,Header 2 [Str "comments"]
31+,Para [Str "inline",Space,Str "comment"]
32+,Para [Str "between",Space,Str "blocks"]
33+,Header 2 [Str "linebreaks"]
34+,Para [Str "hi",LineBreak,Str "there"]
35+,Para [Str "hi",LineBreak,Str "there"]]
tests/mediawiki-reader.wikinew file+71
@@ -0,0 +1,71 @@
1+= header =
2+
3+== header level two ==
4+
5+===header level 3===
6+
7+====header ''level'' four====
8+
9+===== header level 5 =====
10+
11+====== header level 6 ======
12+
13+======= not a header ========
14+
15+ == not a header ==
16+
17+== emph and strong ==
18+
19+''emph'' '''strong'''
20+
21+'''''strong and emph'''''
22+
23+'''''emph inside'' strong'''
24+
25+'''strong with ''emph'''''
26+
27+'''''strong inside''' emph''
28+
29+== horizontal rule ==
30+
31+top
32+----
33+bottom
34+
35+----
36+
37+== nowiki ==
38+
39+<nowiki>''not emph''</nowiki>
40+
41+== strikeout ==
42+
43+<strike> This is ''struck out''</strike>
44+
45+== entities ==
46+
47+hi & low
48+
49+hi &amp; low
50+
51+G&ouml;del
52+
53+&#777;&#xAAA;
54+
55+== comments ==
56+
57+inline<!-- secret --> comment
58+
59+<!-- secret -->
60+
61+between blocks
62+
63+ <!-- secret -->
64+
65+== linebreaks ==
66+
67+hi<br/>there
68+
69+hi<br>
70+there
71+