git.iko.soy/typeable/pandoc
on master help sign in
Added a new FictionBook2 (FB2) writer.
b39597aSergey Astanin
README+24−15
@@ -16,7 +16,7 @@ another, and a command-line tool that uses this library. It can read
16[LaTeX], and [DocBook XML]; and it can write plain text, [markdown],
16[LaTeX], and [DocBook XML]; and it can write plain text, [markdown],
17[reStructuredText], [XHTML], [HTML 5], [LaTeX] (including [beamer]
17[reStructuredText], [XHTML], [HTML 5], [LaTeX] (including [beamer]
18slide shows), [ConTeXt], [RTF], [DocBook XML], [OpenDocument XML],
18slide shows), [ConTeXt], [RTF], [DocBook XML], [OpenDocument XML],
19−[ODT], [Word docx], [GNU Texinfo], [MediaWiki markup], [EPUB],
19+[ODT], [Word docx], [GNU Texinfo], [MediaWiki markup], [EPUB], [FictionBook2],
20[Textile], [groff man] pages, [Emacs Org-Mode], [AsciiDoc], and [Slidy],
20[Textile], [groff man] pages, [Emacs Org-Mode], [AsciiDoc], and [Slidy],
21[Slideous], [DZSlides], or [S5] HTML slide shows. It can also produce
21[Slideous], [DZSlides], or [S5] HTML slide shows. It can also produce
22[PDF] output on systems where LaTeX is installed.
22[PDF] output on systems where LaTeX is installed.
@@ -122,7 +122,7 @@ invoked under the name `hsmarkdown`, `pandoc` will behave as if the
122recognized. However, this approach does not work under Cygwin, due to
122recognized. However, this approach does not work under Cygwin, due to
123problems with its simulation of symbolic links.
123problems with its simulation of symbolic links.
124
124
125−[Cygwin]: http://www.cygwin.com/
125+[Cygwin]: http://www.cygwin.com/
126[`iconv`]: http://www.gnu.org/software/libiconv/
126[`iconv`]: http://www.gnu.org/software/libiconv/
127[CTAN]: http://www.ctan.org "Comprehensive TeX Archive Network"
127[CTAN]: http://www.ctan.org "Comprehensive TeX Archive Network"
128[TeX Live]: http://www.tug.org/texlive/
128[TeX Live]: http://www.tug.org/texlive/
@@ -152,7 +152,8 @@ General options
152 `textile` (Textile), `org` (Emacs Org-Mode), `texinfo` (GNU Texinfo),
152 `textile` (Textile), `org` (Emacs Org-Mode), `texinfo` (GNU Texinfo),
153 `docbook` (DocBook XML), `opendocument` (OpenDocument XML), `odt`
153 `docbook` (DocBook XML), `opendocument` (OpenDocument XML), `odt`
154 (OpenOffice text document), `docx` (Word docx), `epub` (EPUB book),
154 (OpenOffice text document), `docx` (Word docx), `epub` (EPUB book),
155+ `fb2` (FictionBook2 e-book), `asciidoc` (AsciiDoc),
155− `asciidoc` (AsciiDoc), `slidy` (Slidy HTML and javascript slide show),
156+ `slidy` (Slidy HTML and javascript slide show),
156 `slideous` (Slideous HTML and javascript slide show),
157 `slideous` (Slideous HTML and javascript slide show),
157 `dzslides` (HTML5 + javascript slide show), `s5` (S5 HTML and javascript
158 `dzslides` (HTML5 + javascript slide show), `s5` (S5 HTML and javascript
158 slide show), or `rtf` (rich text format). Note that `odt` and `epub`
159 slide show), or `rtf` (rich text format). Note that `odt` and `epub`
@@ -248,7 +249,8 @@ General writer options
248`-s`, `--standalone`
249`-s`, `--standalone`
249: Produce output with an appropriate header and footer (e.g. a
250: Produce output with an appropriate header and footer (e.g. a
250 standalone HTML, LaTeX, or RTF file, not a fragment). This option
251 standalone HTML, LaTeX, or RTF file, not a fragment). This option
251− is set automatically for `pdf`, `epub`, `docx`, and `odt` output.
252+ is set automatically for `pdf`, `epub`, `fb2`, `docx`, and `odt`
253+ output.
252
254
253`--template=`*FILE*
255`--template=`*FILE*
254: Use *FILE* as a custom template for the generated document. Implies
256: Use *FILE* as a custom template for the generated document. Implies
@@ -590,7 +592,7 @@ Math rendering in HTML
590`--gladtex`
592`--gladtex`
591: Enclose TeX math in `<eq>` tags in HTML output. These can then
593: Enclose TeX math in `<eq>` tags in HTML output. These can then
592 be processed by [gladTeX] to produce links to images of the typeset
594 be processed by [gladTeX] to produce links to images of the typeset
593− formulas.
595+ formulas.
594
596
595`--mimetex`[=*URL*]
597`--mimetex`[=*URL*]
596: Render TeX math using the [mimeTeX] CGI script. If *URL* is not
598: Render TeX math using the [mimeTeX] CGI script. If *URL* is not
@@ -628,7 +630,7 @@ Options for wrapper scripts
628[jsMath]: http://www.math.union.edu/~dpvc/jsmath/
630[jsMath]: http://www.math.union.edu/~dpvc/jsmath/
629[MathJax]: http://www.mathjax.org/
631[MathJax]: http://www.mathjax.org/
630[gladTeX]: http://ans.hsh.no/home/mgg/gladtex/
632[gladTeX]: http://ans.hsh.no/home/mgg/gladtex/
631−[mimeTeX]: http://www.forkosh.com/mimetex.html
633+[mimeTeX]: http://www.forkosh.com/mimetex.html
632[CSL]: http://CitationStyles.org
634[CSL]: http://CitationStyles.org
633
635
634Templates
636Templates
@@ -1258,7 +1260,7 @@ or hyphens.
1258### Compact and loose lists ###
1260### Compact and loose lists ###
1259
1261
1260Pandoc behaves differently from `Markdown.pl` on some "edge
1262Pandoc behaves differently from `Markdown.pl` on some "edge
1261−cases" involving lists. Consider this source:
1263+cases" involving lists. Consider this source:
1262
1264
1263 + First
1265 + First
1264 + Second:
1266 + Second:
@@ -1352,7 +1354,7 @@ to the dashed line below it:[^4]
1352
1354
1353 - If the dashed line is flush with the header text on the right side
1355 - If the dashed line is flush with the header text on the right side
1354 but extends beyond it on the left, the column is right-aligned.
1356 but extends beyond it on the left, the column is right-aligned.
1355− - If the dashed line is flush with the header text on the left side
1357+ - If the dashed line is flush with the header text on the left side
1356 but extends beyond it on the right, the column is left-aligned.
1358 but extends beyond it on the right, the column is left-aligned.
1357 - If the dashed line extends beyond the header text on both sides,
1359 - If the dashed line extends beyond the header text on both sides,
1358 the column is centered.
1360 the column is centered.
@@ -1713,7 +1715,12 @@ Docbook
1713Docx
1715Docx
1714 ~ It will be rendered using OMML math markup.
1716 ~ It will be rendered using OMML math markup.
1715
1717
1718+FictionBook2
1719+ ~ If the `--webtex` option is used, formulas are rendered as images
1720+ using Google Charts or other compatible web service, downloaded
1721+ and embedded in the e-book. Otherwise, they will appear verbatim.
1722+
1716−HTML, Slidy, Slideous, DZSlides, S5, EPUB
1723+HTML, Slidy, DZSlides, S5, EPUB
1717 ~ The way math is rendered in HTML will depend on the
1724 ~ The way math is rendered in HTML will depend on the
1718 command-line options selected:
1725 command-line options selected:
1719
1726
@@ -1827,7 +1834,7 @@ Note that in LaTeX environments, like
1827 \begin{tabular}{|l|l|}\hline
1834 \begin{tabular}{|l|l|}\hline
1828 Age & Frequency \\ \hline
1835 Age & Frequency \\ \hline
1829 18--25 & 15 \\
1836 18--25 & 15 \\
1830− 26--35 & 33 \\
1837+ 26--35 & 33 \\
1831 36--45 & 22 \\ \hline
1838 36--45 & 22 \\ \hline
1832 \end{tabular}
1839 \end{tabular}
1833
1840
@@ -1971,7 +1978,7 @@ If you just want a regular inline image, just make sure it is not
1971the only thing in the paragraph. One way to do this is to insert a
1978the only thing in the paragraph. One way to do this is to insert a
1972nonbreaking space after the image:
1979nonbreaking space after the image:
1973
1980
1974− ![This image won't be a figure](/url/of/image.png)\
1981+ ![This image won't be a figure](/url/of/image.png)\
1975
1982
1976
1983
1977Footnotes
1984Footnotes
@@ -1987,7 +1994,7 @@ Pandoc's markdown allows footnotes, using the following syntax:
1987
1994
1988 [^longnote]: Here's one with multiple blocks.
1995 [^longnote]: Here's one with multiple blocks.
1989
1996
1990− Subsequent paragraphs are indented to show that they
1997+ Subsequent paragraphs are indented to show that they
1991 belong to the previous footnote.
1998 belong to the previous footnote.
1992
1999
1993 { some.code }
2000 { some.code }
@@ -2289,7 +2296,8 @@ Andrea Rossato, Eric Kow, infinity0x, Luke Plant, shreevatsa.public,
2289Puneeth Chaganti, Paul Rivier, rodja.trappe, Bradley Kuhn, thsutton,
2296Puneeth Chaganti, Paul Rivier, rodja.trappe, Bradley Kuhn, thsutton,
2290Nathan Gass, Jonathan Daugherty, Jérémy Bobbio, Justin Bogner, qerub,
2297Nathan Gass, Jonathan Daugherty, Jérémy Bobbio, Justin Bogner, qerub,
2291Christopher Sawicki, Kelsey Hightower, Masayoshi Takahashi, Antoine
2298Christopher Sawicki, Kelsey Hightower, Masayoshi Takahashi, Antoine
2292−Latter, Ralf Stephan, Eric Seidel, B. Scott Michel, Gavin Beatty.
2299+Latter, Ralf Stephan, Eric Seidel, B. Scott Michel, Gavin Beatty,
2300+Sergey Astanin.
2293
2301
2294[markdown]: http://daringfireball.net/projects/markdown/
2302[markdown]: http://daringfireball.net/projects/markdown/
2295[reStructuredText]: http://docutils.sourceforge.net/docs/ref/rst/introduction.html
2303[reStructuredText]: http://docutils.sourceforge.net/docs/ref/rst/introduction.html
@@ -2301,10 +2309,10 @@ Latter, Ralf Stephan, Eric Seidel, B. Scott Michel, Gavin Beatty.
2301[XHTML]: http://www.w3.org/TR/xhtml1/
2309[XHTML]: http://www.w3.org/TR/xhtml1/
2302[LaTeX]: http://www.latex-project.org/
2310[LaTeX]: http://www.latex-project.org/
2303[beamer]: http://www.tex.ac.uk/CTAN/macros/latex/contrib/beamer
2311[beamer]: http://www.tex.ac.uk/CTAN/macros/latex/contrib/beamer
2304−[ConTeXt]: http://www.pragma-ade.nl/
2312+[ConTeXt]: http://www.pragma-ade.nl/
2305[RTF]: http://en.wikipedia.org/wiki/Rich_Text_Format
2313[RTF]: http://en.wikipedia.org/wiki/Rich_Text_Format
2306[DocBook XML]: http://www.docbook.org/
2314[DocBook XML]: http://www.docbook.org/
2307−[OpenDocument XML]: http://opendocument.xml.org/
2315+[OpenDocument XML]: http://opendocument.xml.org/
2308[ODT]: http://en.wikipedia.org/wiki/OpenDocument
2316[ODT]: http://en.wikipedia.org/wiki/OpenDocument
2309[Textile]: http://redcloth.org/textile
2317[Textile]: http://redcloth.org/textile
2310[MediaWiki markup]: http://www.mediawiki.org/wiki/Help:Formatting
2318[MediaWiki markup]: http://www.mediawiki.org/wiki/Help:Formatting
@@ -2319,3 +2327,4 @@ Latter, Ralf Stephan, Eric Seidel, B. Scott Michel, Gavin Beatty.
2319[ISO 8601 format]: http://www.w3.org/TR/NOTE-datetime
2327[ISO 8601 format]: http://www.w3.org/TR/NOTE-datetime
2320[Word docx]: http://www.microsoft.com/interop/openup/openxml/default.aspx
2328[Word docx]: http://www.microsoft.com/interop/openup/openxml/default.aspx
2321[PDF]: http://www.adobe.com/pdf/
2329[PDF]: http://www.adobe.com/pdf/
2330+[FictionBook2]: http://www.fictionbook.org/index.php/Eng:XML_Schema_Fictionbook_2.1
src/Text/Pandoc.hs+17−8
@@ -20,10 +20,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20{- |
20{- |
21 Module : Text.Pandoc
21 Module : Text.Pandoc
22 Copyright : Copyright (C) 2006-2010 John MacFarlane
22 Copyright : Copyright (C) 2006-2010 John MacFarlane
23− License : GNU GPL, version 2 or above
23+ License : GNU GPL, version 2 or above
24
24
25 Maintainer : John MacFarlane <jgm@berkeley.edu>
25 Maintainer : John MacFarlane <jgm@berkeley.edu>
26− Stability : alpha
26+ Stability : alpha
27 Portability : portable
27 Portability : portable
28
28
29This helper module exports the main writers, readers, and data
29This helper module exports the main writers, readers, and data
@@ -45,7 +45,7 @@ inline links:
45> markdownToRST =
45> markdownToRST =
46> (writeRST defaultWriterOptions {writerReferenceLinks = True}) .
46> (writeRST defaultWriterOptions {writerReferenceLinks = True}) .
47> readMarkdown defaultParserState
47> readMarkdown defaultParserState
48−>
48+>
49> main = getContents >>= putStrLn . markdownToRST
49> main = getContents >>= putStrLn . markdownToRST
50
50
51Note: all of the readers assume that the input text has @'\n'@
51Note: all of the readers assume that the input text has @'\n'@
@@ -55,7 +55,7 @@ you should remove @'\r'@ characters using @filter (/='\r')@.
55-}
55-}
56
56
57module Text.Pandoc
57module Text.Pandoc
58− (
58+ (
59 -- * Definitions
59 -- * Definitions
60 module Text.Pandoc.Definition
60 module Text.Pandoc.Definition
61 -- * Generics
61 -- * Generics
@@ -63,6 +63,7 @@ module Text.Pandoc
63 -- * Lists of readers and writers
63 -- * Lists of readers and writers
64 , readers
64 , readers
65 , writers
65 , writers
66+ , iowriters
66 -- * Readers: converting /to/ Pandoc format
67 -- * Readers: converting /to/ Pandoc format
67 , readMarkdown
68 , readMarkdown
68 , readRST
69 , readRST
@@ -98,9 +99,10 @@ module Text.Pandoc
98 , writeODT
99 , writeODT
99 , writeDocx
100 , writeDocx
100 , writeEPUB
101 , writeEPUB
102+ , writeFB2
101 , writeOrg
103 , writeOrg
102 , writeAsciiDoc
104 , writeAsciiDoc
103− -- * Writer options used in writers
105+ -- * Writer options used in writers
104 , WriterOptions (..)
106 , WriterOptions (..)
105 , HTMLSlideVariant (..)
107 , HTMLSlideVariant (..)
106 , HTMLMathMethod (..)
108 , HTMLMathMethod (..)
@@ -129,7 +131,7 @@ import Text.Pandoc.Readers.Textile
129import Text.Pandoc.Readers.Native
131import Text.Pandoc.Readers.Native
130import Text.Pandoc.Writers.Native
132import Text.Pandoc.Writers.Native
131import Text.Pandoc.Writers.Markdown
133import Text.Pandoc.Writers.Markdown
132−import Text.Pandoc.Writers.RST
134+import Text.Pandoc.Writers.RST
133import Text.Pandoc.Writers.LaTeX
135import Text.Pandoc.Writers.LaTeX
134import Text.Pandoc.Writers.ConTeXt
136import Text.Pandoc.Writers.ConTeXt
135import Text.Pandoc.Writers.Texinfo
137import Text.Pandoc.Writers.Texinfo
@@ -137,10 +139,11 @@ import Text.Pandoc.Writers.HTML
137import Text.Pandoc.Writers.ODT
139import Text.Pandoc.Writers.ODT
138import Text.Pandoc.Writers.Docx
140import Text.Pandoc.Writers.Docx
139import Text.Pandoc.Writers.EPUB
141import Text.Pandoc.Writers.EPUB
142+import Text.Pandoc.Writers.FB2
140import Text.Pandoc.Writers.Docbook
143import Text.Pandoc.Writers.Docbook
141import Text.Pandoc.Writers.OpenDocument
144import Text.Pandoc.Writers.OpenDocument
142import Text.Pandoc.Writers.Man
145import Text.Pandoc.Writers.Man
143−import Text.Pandoc.Writers.RTF
146+import Text.Pandoc.Writers.RTF
144import Text.Pandoc.Writers.MediaWiki
147import Text.Pandoc.Writers.MediaWiki
145import Text.Pandoc.Writers.Textile
148import Text.Pandoc.Writers.Textile
146import Text.Pandoc.Writers.Org
149import Text.Pandoc.Writers.Org
@@ -168,7 +171,7 @@ readers = [("native" , \_ -> readNative)
168 ,("rst+lhs" , \st ->
171 ,("rst+lhs" , \st ->
169 readRST st{ stateLiterateHaskell = True})
172 readRST st{ stateLiterateHaskell = True})
170 ,("docbook" , readDocBook)
173 ,("docbook" , readDocBook)
171− ,("textile" , readTextile) -- TODO : textile+lhs
174+ ,("textile" , readTextile) -- TODO : textile+lhs
172 ,("html" , readHtml)
175 ,("html" , readHtml)
173 ,("latex" , readLaTeX)
176 ,("latex" , readLaTeX)
174 ,("latex+lhs" , \st ->
177 ,("latex+lhs" , \st ->
@@ -218,6 +221,12 @@ writers = [("native" , writeNative)
218 ,("asciidoc" , writeAsciiDoc)
221 ,("asciidoc" , writeAsciiDoc)
219 ]
222 ]
220
223
224+-- | Association list of formats and writers which require IO to work.
225+-- These writers produce text output as well as thoses in 'writers'.
226+iowriters :: [ (String, WriterOptions -> Pandoc -> IO String) ]
227+iowriters = [ ("fb2" , writeFB2)
228+ ]
229+
221{-# DEPRECATED jsonFilter "Use toJsonFilter instead" #-}
230{-# DEPRECATED jsonFilter "Use toJsonFilter instead" #-}
222-- | Converts a transformation on the Pandoc AST into a function
231-- | Converts a transformation on the Pandoc AST into a function
223-- that reads and writes a JSON-encoded string. This is useful
232-- that reads and writes a JSON-encoded string. This is useful
src/Text/Pandoc/Writers/FB2.hsnew file+570
@@ -0,0 +1,570 @@
1+{-
2+Copyright (c) 2011-2012, Sergey Astanin
3+All rights reserved.
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+{- | Conversion of 'Pandoc' documents to FB2 (FictionBook2) format.
21+
22+FictionBook is an XML-based e-book format. For more information see:
23+<http://www.fictionbook.org/index.php/Eng:XML_Schema_Fictionbook_2.1>
24+
25+-}
26+module Text.Pandoc.Writers.FB2 (writeFB2) where
27+
28+import Control.Monad.State (StateT, evalStateT, get, modify)
29+import Control.Monad.State (liftM, liftM2, liftIO)
30+import Data.ByteString.Base64 (encode)
31+import Data.Char (toUpper, toLower, isSpace)
32+import Data.List (intersperse, intercalate)
33+import Data.Either (lefts, rights)
34+import Network.Browser (browse, request, setAllowRedirects, setOutHandler)
35+import Network.HTTP (catchIO_, getRequest, getHeaders, getResponseBody)
36+import Network.HTTP (lookupHeader, HeaderName(..), urlEncode)
37+import Network.URI (isURI, unEscapeString)
38+import System.FilePath (takeExtension)
39+import Text.XML.Light
40+import qualified Control.Exception as E
41+import qualified Data.ByteString as B
42+import qualified Text.XML.Light as X
43+import qualified Text.XML.Light.Cursor as XC
44+
45+import Text.Pandoc.Definition
46+import Text.Pandoc.Shared (WriterOptions(..), HTMLMathMethod(..))
47+import Text.Pandoc.Shared (orderedListMarkers, defaultWriterOptions)
48+import Text.Pandoc.Generic (bottomUp)
49+
50+-- | Data to be written at the end of the document:
51+-- (foot)notes, URLs, references, images.
52+data FbRenderState = FbRenderState
53+ { footnotes :: [ (Int, String, [Content]) ] -- ^ #, ID, text
54+ , imagesToFetch :: [ (String, String) ] -- ^ filename, URL or path
55+ , parentListMarker :: String -- ^ list marker of the parent ordered list
56+ , parentBulletLevel :: Int -- ^ nesting level of the unordered list
57+ , writerOptions :: WriterOptions
58+ } deriving (Show)
59+
60+-- | FictionBook building monad.
61+type FBM = StateT FbRenderState IO
62+
63+newFB :: FbRenderState
64+newFB = FbRenderState { footnotes = [], imagesToFetch = []
65+ , parentListMarker = "", parentBulletLevel = 0
66+ , writerOptions = defaultWriterOptions }
67+
68+data ImageMode = NormalImage | InlineImage deriving (Eq)
69+instance Show ImageMode where
70+ show NormalImage = "imageType"
71+ show InlineImage = "inlineImageType"
72+
73+-- | Produce an FB2 document from a 'Pandoc' document.
74+writeFB2 :: WriterOptions -- ^ conversion options
75+ -> Pandoc -- ^ document to convert
76+ -> IO String -- ^ FictionBook2 document (not encoded yet)
77+writeFB2 opts (Pandoc meta blocks) = flip evalStateT newFB $ do
78+ modify (\s -> s { writerOptions = opts { writerStandalone = True } })
79+ desc <- description meta
80+ fp <- frontpage meta
81+ secs <- renderSections 1 blocks
82+ let body = el "body" $ fp ++ secs
83+ notes <- renderFootnotes
84+ (imgs,missing) <- liftM imagesToFetch get >>= \s -> liftIO (fetchImages s)
85+ let body' = replaceImagesWithAlt missing body
86+ let fb2_xml = el "FictionBook" (fb2_attrs, [desc, body'] ++ notes ++ imgs)
87+ return $ xml_head ++ (showContent fb2_xml)
88+ where
89+ xml_head = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
90+ fb2_attrs =
91+ let xmlns = "http://www.gribuser.ru/xml/fictionbook/2.0"
92+ xlink = "http://www.w3.org/1999/xlink"
93+ in [ uattr "xmlns" xmlns
94+ , attr ("xmlns", "l") xlink ]
95+ --
96+ frontpage :: Meta -> FBM [Content]
97+ frontpage meta' = do
98+ t <- cMapM toXml . docTitle $ meta'
99+ return $
100+ [ el "title" (el "p" t)
101+ , el "annotation" (map (el "p" . cMap plain)
102+ (docAuthors meta' ++ [docDate meta']))
103+ ]
104+ description :: Meta -> FBM Content
105+ description meta' = do
106+ bt <- booktitle meta'
107+ let as = authors meta'
108+ dd <- docdate meta'
109+ return $ el "description"
110+ [ el "title-info" (bt ++ as ++ dd)
111+ , el "document-info" [ el "program-used" "pandoc" ] -- FIXME: +version
112+ ]
113+ booktitle :: Meta -> FBM [Content]
114+ booktitle meta' = do
115+ t <- cMapM toXml . docTitle $ meta'
116+ return $ if null t
117+ then []
118+ else [ el "book-title" t ]
119+ authors :: Meta -> [Content]
120+ authors meta' = cMap author (docAuthors meta')
121+ author :: [Inline] -> [Content]
122+ author ss =
123+ let ws = words . cMap plain $ ss
124+ email = (el "email") `fmap` (take 1 $ filter ('@' `elem`) ws)
125+ ws' = filter ('@' `notElem`) ws
126+ names = case ws' of
127+ (nickname:[]) -> [ el "nickname" nickname ]
128+ (fname:lname:[]) -> [ el "first-name" fname
129+ , el "last-name" lname ]
130+ (fname:rest) -> [ el "first-name" fname
131+ , el "middle-name" (concat . init $ rest)
132+ , el "last-name" (last rest) ]
133+ ([]) -> []
134+ in list $ el "author" (names ++ email)
135+ docdate :: Meta -> FBM [Content]
136+ docdate meta' = do
137+ let ss = docDate meta'
138+ d <- cMapM toXml ss
139+ return $ if null d
140+ then []
141+ else [el "date" d]
142+
143+-- | Divide the stream of blocks into sections and convert to XML
144+-- representation.
145+renderSections :: Int -> [Block] -> FBM [Content]
146+renderSections level blocks = do
147+ let secs = splitSections level blocks
148+ mapM (renderSection level) secs
149+
150+renderSection :: Int -> ([Inline], [Block]) -> FBM Content
151+renderSection level (ttl, body) = do
152+ title <- if null ttl
153+ then return []
154+ else return . list . el "title" . formatTitle $ ttl
155+ content <- if (hasSubsections body)
156+ then renderSections (level + 1) body
157+ else cMapM blockToXml body
158+ return $ el "section" (title ++ content)
159+ where
160+ hasSubsections = any isHeader
161+ isHeader (Header _ _) = True
162+ isHeader _ = False
163+
164+-- | Only <p> and <empty-line> are allowed within <title> in FB2.
165+formatTitle :: [Inline] -> [Content]
166+formatTitle inlines =
167+ let lns = split isLineBreak inlines
168+ lns' = map (el "p" . cMap plain) lns
169+ in intersperse (el "empty-line" ()) lns'
170+
171+split :: (a -> Bool) -> [a] -> [[a]]
172+split _ [] = []
173+split cond xs = let (b,a) = break cond xs
174+ in (b:split cond (drop 1 a))
175+
176+isLineBreak :: Inline -> Bool
177+isLineBreak LineBreak = True
178+isLineBreak _ = False
179+
180+-- | Divide the stream of block elements into sections: [(title, blocks)].
181+splitSections :: Int -> [Block] -> [([Inline], [Block])]
182+splitSections level blocks = reverse $ revSplit (reverse blocks)
183+ where
184+ revSplit [] = []
185+ revSplit rblocks =
186+ let (lastsec, before) = break sameLevel rblocks
187+ (header, prevblocks) =
188+ case before of
189+ ((Header n title):prevblocks') ->
190+ if n == level
191+ then (title, prevblocks')
192+ else ([], before)
193+ _ -> ([], before)
194+ in (header, reverse lastsec) : revSplit prevblocks
195+ sameLevel (Header n _) = n == level
196+ sameLevel _ = False
197+
198+-- | Make another FictionBook body with footnotes.
199+renderFootnotes :: FBM [Content]
200+renderFootnotes = do
201+ fns <- footnotes `liftM` get
202+ if null fns
203+ then return [] -- no footnotes
204+ else return . list $
205+ el "body" ([uattr "name" "notes"], map renderFN (reverse fns))
206+ where
207+ renderFN (n, idstr, cs) =
208+ let fn_texts = (el "title" (el "p" (show n))) : cs
209+ in el "section" ([uattr "id" idstr], fn_texts)
210+
211+-- | Fetch images and encode them for the FictionBook XML.
212+-- Return image data and a list of hrefs of the missing images.
213+fetchImages :: [(String,String)] -> IO ([Content],[String])
214+fetchImages links = do
215+ imgs <- mapM (uncurry fetchImage) links
216+ return $ (rights imgs, lefts imgs)
217+
218+-- | Fetch image data from disk or from network and make a <binary> XML section.
219+-- Return either (Left hrefOfMissingImage) or (Right xmlContent).
220+fetchImage :: String -> String -> IO (Either String Content)
221+fetchImage href link = do
222+ mbimg <-
223+ if isURI link
224+ then fetchURL link
225+ else do
226+ d <- nothingOnError $ B.readFile (unEscapeString link)
227+ let t = case map toLower (takeExtension link) of
228+ ".png" -> Just "image/png"
229+ ".jpg" -> Just "image/jpeg"
230+ ".jpeg" -> Just "image/jpeg"
231+ ".jpe" -> Just "image/jpeg"
232+ _ -> Nothing -- only PNG and JPEG are supported in FB2
233+ return $ liftM2 (,) t d
234+ case mbimg of
235+ Just (imgtype, imgdata) -> do
236+ let encdata = encode imgdata
237+ let encstr = map (toEnum . fromEnum) . B.unpack $ encdata
238+ return . Right $ el "binary"
239+ ( [uattr "id" href
240+ , uattr "content-type" imgtype]
241+ , txt encstr )
242+ _ -> return (Left ('#':href))
243+ where
244+ nothingOnError :: (IO B.ByteString) -> (IO (Maybe B.ByteString))
245+ nothingOnError action = liftM Just action `E.catch` omnihandler
246+ omnihandler :: E.SomeException -> IO (Maybe B.ByteString)
247+ omnihandler _ = return Nothing
248+
249+-- | Fetch URL, return its Content-Type and binary data on success.
250+fetchURL :: String -> IO (Maybe (String, B.ByteString))
251+fetchURL url = do
252+ flip catchIO_ (return Nothing) $ do
253+ r <- browse $ do
254+ setOutHandler (const (return ()))
255+ setAllowRedirects True
256+ liftM snd . request . getRequest $ url
257+ let content_type = lookupHeader HdrContentType (getHeaders r)
258+ content <- liftM (Just . toBS) . getResponseBody $ Right r
259+ return $ liftM2 (,) content_type content
260+ where
261+ toBS = B.pack . map (toEnum . fromEnum)
262+
263+footnoteID :: Int -> String
264+footnoteID i = "n" ++ (show i)
265+
266+linkID :: Int -> String
267+linkID i = "l" ++ (show i)
268+
269+-- | Convert a block-level Pandoc's element to FictionBook XML representation.
270+blockToXml :: Block -> FBM [Content]
271+blockToXml (Plain ss) = cMapM toXml ss -- FIXME: can lead to malformed FB2
272+blockToXml (Para [Math DisplayMath formula]) = insertMath NormalImage formula
273+blockToXml (Para [img@(Image _ _)]) = insertImage NormalImage img
274+blockToXml (Para ss) = liftM (list . el "p") $ cMapM toXml ss
275+blockToXml (CodeBlock _ s) = return . spaceBeforeAfter .
276+ map (el "p" . el "code") . lines $ s
277+blockToXml (RawBlock _ s) = return . spaceBeforeAfter .
278+ map (el "p" . el "code") . lines $ s
279+blockToXml (BlockQuote bs) = liftM (list . el "cite") $ cMapM blockToXml bs
280+blockToXml (OrderedList a bss) = do
281+ state <- get
282+ let pmrk = parentListMarker state
283+ let markers = map ((pmrk ++ " ") ++) $ orderedListMarkers a
284+ let mkitem mrk bs = do
285+ modify (\s -> s { parentListMarker = mrk })
286+ itemtext <- cMapM blockToXml . paraToPlain $ bs
287+ modify (\s -> s { parentListMarker = pmrk }) -- old parent marker
288+ return . el "p" $ [ txt mrk, txt " " ] ++ itemtext
289+ mapM (uncurry mkitem) (zip markers bss)
290+blockToXml (BulletList bss) = do
291+ state <- get
292+ let level = parentBulletLevel state
293+ let pmrk = parentListMarker state
294+ let prefix = replicate (length pmrk) ' '
295+ let bullets = ["\x2022", "\x25e6", "*", "\x2043", "\x2023"]
296+ let mrk = prefix ++ bullets !! (level `mod` (length bullets))
297+ let mkitem bs = do
298+ modify (\s -> s { parentBulletLevel = (level+1) })
299+ itemtext <- cMapM blockToXml . paraToPlain $ bs
300+ modify (\s -> s { parentBulletLevel = level }) -- restore bullet level
301+ return $ el "p" $ [ txt (mrk ++ " ") ] ++ itemtext
302+ mapM mkitem bss
303+blockToXml (DefinitionList defs) =
304+ cMapM mkdef defs
305+ where
306+ mkdef (term, bss) = do
307+ def <- cMapM (cMapM blockToXml . sep . paraToPlain . map indent) bss
308+ t <- wrap "strong" term
309+ return [ el "p" t, el "p" def ]
310+ sep blocks =
311+ if all needsBreak blocks then
312+ blocks ++ [Plain [LineBreak]]
313+ else
314+ blocks
315+ needsBreak (Para _) = False
316+ needsBreak (Plain ins) = LineBreak `notElem` ins
317+ needsBreak _ = True
318+blockToXml (Header _ _) = -- should never happen, see renderSections
319+ error "unexpected header in section text"
320+blockToXml HorizontalRule = return
321+ [ el "empty-line" ()
322+ , el "p" (txt (replicate 10 '—'))
323+ , el "empty-line" () ]
324+blockToXml (Table caption aligns _ headers rows) = do
325+ hd <- mkrow "th" headers aligns
326+ bd <- mapM (\r -> mkrow "td" r aligns) rows
327+ c <- return . el "emphasis" =<< cMapM toXml caption
328+ return [el "table" (hd : bd), el "p" c]
329+ where
330+ mkrow :: String -> [TableCell] -> [Alignment] -> FBM Content
331+ mkrow tag cells aligns' =
332+ (el "tr") `liftM` (mapM (mkcell tag) (zip cells aligns'))
333+ --
334+ mkcell :: String -> (TableCell, Alignment) -> FBM Content
335+ mkcell tag (cell, align) = do
336+ cblocks <- cMapM blockToXml cell
337+ return $ el tag ([align_attr align], cblocks)
338+ --
339+ align_attr a = Attr (QName "align" Nothing Nothing) (align_str a)
340+ align_str AlignLeft = "left"
341+ align_str AlignCenter = "center"
342+ align_str AlignRight = "right"
343+ align_str AlignDefault = "left"
344+blockToXml Null = return []
345+
346+-- Replace paragraphs with plain text and line break.
347+-- Necessary to simulate multi-paragraph lists in FB2.
348+paraToPlain :: [Block] -> [Block]
349+paraToPlain [] = []
350+paraToPlain (Para inlines : rest) =
351+ let p = (Plain (inlines ++ [LineBreak]))
352+ in p : paraToPlain rest
353+paraToPlain (p:rest) = p : paraToPlain rest
354+
355+-- Simulate increased indentation level. Will not really work
356+-- for multi-line paragraphs.
357+indent :: Block -> Block
358+indent = indentBlock
359+ where
360+ -- indentation space
361+ spacer :: String
362+ spacer = replicate 4 ' '
363+ --
364+ indentBlock (Plain ins) = Plain ((Str spacer):ins)
365+ indentBlock (Para ins) = Para ((Str spacer):ins)
366+ indentBlock (CodeBlock a s) =
367+ let s' = unlines . map (spacer++) . lines $ s
368+ in CodeBlock a s'
369+ indentBlock (BlockQuote bs) = BlockQuote (map indent bs)
370+ indentBlock (Header l ins) = Header l (indentLines ins)
371+ indentBlock everythingElse = everythingElse
372+ -- indent every (explicit) line
373+ indentLines :: [Inline] -> [Inline]
374+ indentLines ins = let lns = split isLineBreak ins :: [[Inline]]
375+ in intercalate [LineBreak] $ map ((Str spacer):) lns
376+
377+-- | Convert a Pandoc's Inline element to FictionBook XML representation.
378+toXml :: Inline -> FBM [Content]
379+toXml (Str s) = return [txt s]
380+toXml (Emph ss) = list `liftM` wrap "emphasis" ss
381+toXml (Strong ss) = list `liftM` wrap "strong" ss
382+toXml (Strikeout ss) = list `liftM` wrap "strikethrough" ss
383+toXml (Superscript ss) = list `liftM` wrap "sup" ss
384+toXml (Subscript ss) = list `liftM` wrap "sub" ss
385+toXml (SmallCaps ss) = cMapM toXml $ bottomUp (map toUpper) ss
386+toXml (Quoted SingleQuote ss) = do -- FIXME: should be language-specific
387+ inner <- cMapM toXml ss
388+ return $ [txt "‘"] ++ inner ++ [txt "’"]
389+toXml (Quoted DoubleQuote ss) = do
390+ inner <- cMapM toXml ss
391+ return $ [txt "“"] ++ inner ++ [txt "”"]
392+toXml (Cite _ ss) = cMapM toXml ss -- FIXME: support citation styles
393+toXml (Code _ s) = return [el "code" s]
394+toXml Space = return [txt " "]
395+toXml LineBreak = return [el "empty-line" ()]
396+toXml (Math _ formula) = insertMath InlineImage formula
397+toXml (RawInline _ _) = return [] -- raw TeX and raw HTML are suppressed
398+toXml (Link text (url,ttl)) = do
399+ fns <- footnotes `liftM` get
400+ let n = 1 + length fns
401+ let ln_id = linkID n
402+ let ln_ref = list . el "sup" . txt $ "[" ++ show n ++ "]"
403+ ln_text <- cMapM toXml text
404+ let ln_desc =
405+ let ttl' = dropWhile isSpace ttl
406+ in if null ttl'
407+ then list . el "p" $ el "code" url
408+ else list . el "p" $ [ txt (ttl' ++ ": "), el "code" url ]
409+ modify (\s -> s { footnotes = (n, ln_id, ln_desc) : fns })
410+ return $ ln_text ++
411+ [ el "a"
412+ ( [ attr ("l","href") ('#':ln_id)
413+ , uattr "type" "note" ]
414+ , ln_ref) ]
415+toXml img@(Image _ _) = insertImage InlineImage img
416+toXml (Note bs) = do
417+ fns <- footnotes `liftM` get
418+ let n = 1 + length fns
419+ let fn_id = footnoteID n
420+ fn_desc <- cMapM blockToXml bs
421+ modify (\s -> s { footnotes = (n, fn_id, fn_desc) : fns })
422+ let fn_ref = el "sup" . txt $ "[" ++ show n ++ "]"
423+ return . list $ el "a" ( [ attr ("l","href") ('#':fn_id)
424+ , uattr "type" "note" ]
425+ , fn_ref )
426+
427+insertMath :: ImageMode -> String -> FBM [Content]
428+insertMath immode formula = do
429+ htmlMath <- return . writerHTMLMathMethod . writerOptions =<< get
430+ case htmlMath of
431+ WebTeX url -> do
432+ let alt = [Code nullAttr formula]
433+ let imgurl = url ++ urlEncode formula
434+ let img = Image alt (imgurl, "")
435+ insertImage immode img
436+ _ -> return [el "code" formula]
437+
438+insertImage :: ImageMode -> Inline -> FBM [Content]
439+insertImage immode (Image alt (url,ttl)) = do
440+ images <- imagesToFetch `liftM` get
441+ let n = 1 + length images
442+ let fname = "image" ++ show n
443+ modify (\s -> s { imagesToFetch = (fname, url) : images })
444+ let ttlattr = case (immode, null ttl) of
445+ (NormalImage, False) -> [ uattr "title" ttl ]
446+ _ -> []
447+ return . list $
448+ el "image" $
449+ [ attr ("l","href") ('#':fname)
450+ , attr ("l","type") (show immode)
451+ , uattr "alt" (cMap plain alt) ]
452+ ++ ttlattr
453+insertImage _ _ = error "unexpected inline instead of image"
454+
455+replaceImagesWithAlt :: [String] -> Content -> Content
456+replaceImagesWithAlt missingHrefs body =
457+ let cur = XC.fromContent body
458+ cur' = replaceAll cur
459+ in XC.toTree . XC.root $ cur'
460+ where
461+ --
462+ replaceAll :: XC.Cursor -> XC.Cursor
463+ replaceAll c =
464+ let n = XC.current c
465+ c' = if isImage n && isMissing n
466+ then XC.modifyContent replaceNode c
467+ else c
468+ in case XC.nextDF c' of
469+ (Just cnext) -> replaceAll cnext
470+ Nothing -> c' -- end of document
471+ --
472+ isImage :: Content -> Bool
473+ isImage (Elem e) = (elName e) == (uname "image")
474+ isImage _ = False
475+ --
476+ isMissing (Elem img@(Element _ _ _ _)) =
477+ let imgAttrs = elAttribs img
478+ badAttrs = map (attr ("l","href")) missingHrefs
479+ in any (`elem` imgAttrs) badAttrs
480+ isMissing _ = False
481+ --
482+ replaceNode :: Content -> Content
483+ replaceNode n@(Elem img@(Element _ _ _ _)) =
484+ let attrs = elAttribs img
485+ alt = getAttrVal attrs (uname "alt")
486+ imtype = getAttrVal attrs (qname "l" "type")
487+ in case (alt, imtype) of
488+ (Just alt', Just imtype') ->
489+ if imtype' == show NormalImage
490+ then el "p" alt'
491+ else txt alt'
492+ (Just alt', Nothing) -> txt alt' -- no type attribute
493+ _ -> n -- don't replace if alt text is not found
494+ replaceNode n = n
495+ --
496+ getAttrVal :: [X.Attr] -> QName -> Maybe String
497+ getAttrVal attrs name =
498+ case filter ((name ==) . attrKey) attrs of
499+ (a:_) -> Just (attrVal a)
500+ _ -> Nothing
501+
502+
503+-- | Wrap all inlines with an XML tag (given its unqualified name).
504+wrap :: String -> [Inline] -> FBM Content
505+wrap tagname inlines = el tagname `liftM` cMapM toXml inlines
506+
507+-- " Create a singleton list.
508+list :: a -> [a]
509+list = (:[])
510+
511+-- | Convert an 'Inline' to plaintext.
512+plain :: Inline -> String
513+plain (Str s) = s
514+plain (Emph ss) = concat (map plain ss)
515+plain (Strong ss) = concat (map plain ss)
516+plain (Strikeout ss) = concat (map plain ss)
517+plain (Superscript ss) = concat (map plain ss)
518+plain (Subscript ss) = concat (map plain ss)
519+plain (SmallCaps ss) = concat (map plain ss)
520+plain (Quoted _ ss) = concat (map plain ss)
521+plain (Cite _ ss) = concat (map plain ss) -- FIXME
522+plain (Code _ s) = s
523+plain Space = " "
524+plain LineBreak = "\n"
525+plain (Math _ s) = s
526+plain (RawInline _ s) = s
527+plain (Link text (url,_)) = concat (map plain text ++ [" <", url, ">"])
528+plain (Image alt _) = concat (map plain alt)
529+plain (Note _) = "" -- FIXME
530+
531+-- | Create an XML element.
532+el :: (Node t)
533+ => String -- ^ unqualified element name
534+ -> t -- ^ node contents
535+ -> Content -- ^ XML content
536+el name cs = Elem $ unode name cs
537+
538+-- | Put empty lines around content
539+spaceBeforeAfter :: [Content] -> [Content]
540+spaceBeforeAfter cs =
541+ let emptyline = el "empty-line" ()
542+ in [emptyline] ++ cs ++ [emptyline]
543+
544+-- | Create a plain-text XML content.
545+txt :: String -> Content
546+txt s = Text $ CData CDataText s Nothing
547+
548+-- | Create an XML attribute with an unqualified name.
549+uattr :: String -> String -> Text.XML.Light.Attr
550+uattr name val = Attr (uname name) val
551+
552+-- | Create an XML attribute with a qualified name from given namespace.
553+attr :: (String, String) -> String -> Text.XML.Light.Attr
554+attr (ns, name) val = Attr (qname ns name) val
555+
556+-- | Unqualified name
557+uname :: String -> QName
558+uname name = QName name Nothing Nothing
559+
560+-- | Qualified name
561+qname :: String -> String -> QName
562+qname ns name = QName name Nothing (Just ns)
563+
564+-- | Abbreviation for 'concatMap'.
565+cMap :: (a -> [b]) -> [a] -> [b]
566+cMap = concatMap
567+
568+-- | Monadic equivalent of 'concatMap'.
569+cMapM :: (Monad m) => (a -> m [b]) -> [a] -> m [b]
570+cMapM f xs = concat `liftM` mapM f xs
src/pandoc.hs+17−7
@@ -56,7 +56,8 @@ import Control.Monad (when, unless, liftM)
56import Network.HTTP (simpleHTTP, mkRequest, getResponseBody, RequestMethod(..))
56import Network.HTTP (simpleHTTP, mkRequest, getResponseBody, RequestMethod(..))
57import Network.URI (parseURI, isURI, URI(..))
57import Network.URI (parseURI, isURI, URI(..))
58import qualified Data.ByteString.Lazy as B
58import qualified Data.ByteString.Lazy as B
59−import Data.ByteString.Lazy.UTF8 (toString )
59+import Data.ByteString.Lazy.UTF8 (toString)
60+import Codec.Binary.UTF8.String (decodeString, encodeString)
60import Text.CSL.Reference (Reference(..))
61import Text.CSL.Reference (Reference(..))
61#if MIN_VERSION_base(4,4,0)
62#if MIN_VERSION_base(4,4,0)
62#else
63#else
@@ -694,8 +695,11 @@ options =
694usageMessage :: String -> [OptDescr (Opt -> IO Opt)] -> String
695usageMessage :: String -> [OptDescr (Opt -> IO Opt)] -> String
695usageMessage programName = usageInfo
696usageMessage programName = usageInfo
696 (programName ++ " [OPTIONS] [FILES]" ++ "\nInput formats: " ++
697 (programName ++ " [OPTIONS] [FILES]" ++ "\nInput formats: " ++
697− (wrapWords 16 78 $ map fst readers) ++ "\nOutput formats: " ++
698+ (wrapWords 16 78 $ readers'names) ++ "\nOutput formats: " ++
698− (wrapWords 16 78 $ map fst writers ++ nonTextFormats) ++ "\nOptions:")
699+ (wrapWords 16 78 $ writers'names ++ nonTextFormats) ++ "\nOptions:")
700+ where
701+ writers'names = map fst writers ++ map fst iowriters
702+ readers'names = map fst readers
699
703
700-- Determine default reader based on source file extensions
704-- Determine default reader based on source file extensions
701defaultReaderName :: String -> [FilePath] -> String
705defaultReaderName :: String -> [FilePath] -> String
@@ -752,6 +756,7 @@ defaultWriterName x =
752 ".org" -> "org"
756 ".org" -> "org"
753 ".asciidoc" -> "asciidoc"
757 ".asciidoc" -> "asciidoc"
754 ".pdf" -> "latex"
758 ".pdf" -> "latex"
759+ ".fb2" -> "fb2"
755 ['.',y] | y `elem` ['1'..'9'] -> "man"
760 ['.',y] | y `elem` ['1'..'9'] -> "man"
756 _ -> "html"
761 _ -> "html"
757
762
@@ -1042,8 +1047,13 @@ main = do
1042 writerFn "-" = UTF8.putStr
1047 writerFn "-" = UTF8.putStr
1043 writerFn f = UTF8.writeFile f
1048 writerFn f = UTF8.writeFile f
1044
1049
1045− case lookup writerName' writers of
1050+ let purewriter = lookup writerName' writers
1051+ let iowriter = lookup writerName' iowriters
1052+ case (purewriter, iowriter) of
1046− Nothing
1053+ (Nothing, Just iow) -> do
1054+ d <- iow writerOptions doc2
1055+ writerFn outputFile d
1056+ (Nothing, Nothing)
1047 | writerName' == "epub" ->
1057 | writerName' == "epub" ->
1048 writeEPUB epubStylesheet epubFonts writerOptions doc2
1058 writeEPUB epubStylesheet epubFonts writerOptions doc2
1049 >>= writeBinary
1059 >>= writeBinary
@@ -1052,13 +1062,13 @@ main = do
1052 | writerName' == "docx" ->
1062 | writerName' == "docx" ->
1053 writeDocx referenceDocx writerOptions doc2 >>= writeBinary
1063 writeDocx referenceDocx writerOptions doc2 >>= writeBinary
1054 | otherwise -> err 9 ("Unknown writer: " ++ writerName')
1064 | otherwise -> err 9 ("Unknown writer: " ++ writerName')
1055− Just w
1065+ (Just w, _)
1056 | pdfOutput -> do
1066 | pdfOutput -> do
1057 res <- tex2pdf latexEngine $ w writerOptions doc2
1067 res <- tex2pdf latexEngine $ w writerOptions doc2
1058 case res of
1068 case res of
1059 Right pdf -> writeBinary pdf
1069 Right pdf -> writeBinary pdf
1060 Left err' -> err 43 $ toString err'
1070 Left err' -> err 43 $ toString err'
1061− Just w
1071+ (Just w, _)
1062 | htmlFormat && ascii ->
1072 | htmlFormat && ascii ->
1063 writerFn outputFile . toEntities =<< selfcontain result
1073 writerFn outputFile . toEntities =<< selfcontain result
1064 | otherwise ->
1074 | otherwise ->