git.iko.soy/typeable/pandoc
on master help sign in
Support ipynb (Jupyter notebook) as input and output format.

[API change] * Depend on ipynb library. * Add `ipynb` as input and output format. * Added Text.Pandoc.Readers.Ipynb (supports both nbformat v3 and v4). * Added Text.Pandoc.Writers.Ipynb (supports nbformat v4). * Added ipynb readers and writers to T.P.Readers, T.P.Writers, and T.P.Extensions. Register the file extension .ipynb for this format. * Add `PandocIpynbDecodingError` constructor to Text.Pandoc.Error.Error. * Note: there is no template for ipynb.

commit395ea03069167568f8fccb018c794bd43a787b94 authorJohn MacFarlane <jgm@berkeley.edu> dateSun 13 Jan 2019, 07:56 (8 years ago) parent5ddd7b1
13 files changed+638−9
MANUAL.txt+125−1 README.md+5 cabal.project+1 pandoc.cabal+11−8 src/Text/Pandoc/App/FormatHeuristics.hs+1 src/Text/Pandoc/Error.hs+3 src/Text/Pandoc/Extensions.hs+2 src/Text/Pandoc/Readers.hs+3 src/Text/Pandoc/Readers/Ipynb.hs+249 src/Text/Pandoc/Templates.hs+1 src/Text/Pandoc/Writers.hs+3 src/Text/Pandoc/Writers/Ipynb.hs+233 stack.yaml+1
MANUAL.txt+125−1
@@ -248,6 +248,7 @@ General options {.options}
248248 if you need extensions not supported in [`gfm`](#markdown-variants).
249249 - `haddock` ([Haddock markup])
250250 - `html` ([HTML])
251+ - `ipynb` ([Jupyter notebook])
251252 - `jats` ([JATS] XML)
252253 - `json` (JSON version of native AST)
253254 - `latex` ([LaTeX])
@@ -300,6 +301,7 @@ General options {.options}
300301 - `html` or `html5` ([HTML], i.e. [HTML5]/XHTML [polyglot markup])
301302 - `html4` ([XHTML] 1.0 Transitional)
302303 - `icml` ([InDesign ICML])
304+ - `ipynb` ([Jupyter notebook])
303305 - `jats` ([JATS] XML)
304306 - `json` (JSON version of native AST)
305307 - `latex` ([LaTeX])
@@ -471,6 +473,7 @@ General options {.options}
471473[PDF]: https://www.adobe.com/pdf/
472474[reveal.js]: http://lab.hakim.se/reveal-js/
473475[FictionBook2]: http://www.fictionbook.org/index.php/Eng:XML_Schema_Fictionbook_2.1
476+[Jupyter notebook]: https://nbformat.readthedocs.io/en/latest/
474477[InDesign ICML]: http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/indesign/sdk/cs6/idml/idml-cookbook.pdf
475478[TEI Simple]: https://github.com/TEIC/TEI-Simple
476479[Muse]: https://amusewiki.org/library/manual
@@ -730,6 +733,8 @@ General writer options {.options}
730733 where there are nonsemantic newlines in the source, there
731734 will be nonsemantic newlines in the output as well).
732735 Automatic wrapping does not currently work in HTML output.
736+ In `ipynb` output, this option affects wrapping of the
737+ contents of markdown cells.
733738
734739`--columns=`*NUMBER*
735740
@@ -902,6 +907,7 @@ Options affecting specific writers {.options}
902907: Use ATX-style headers in Markdown output. The default is
903908 to use setext-style headers for levels 1-2, and then ATX headers.
904909 (Note: for `gfm` output, ATX headers are always used.)
910+ This option also affects markdown cells in `ipynb` output.
905911
906912`--top-level-division=[default|section|chapter|part]`
907913
@@ -1806,6 +1812,10 @@ section [Pandoc's Markdown] below (See [Markdown variants] for
18061812`commonmark` and `gfm`.) In the following, extensions that also work
18071813for other formats are covered.
18081814
1815+Note that markdown extensions added to the `ipynb` format
1816+affect Markdown cells in Jupyter notebooks (as do command-line
1817+options like `--atx-headers`).
1818+
18091819Typography
18101820----------
18111821
@@ -1955,11 +1965,19 @@ This extension can be enabled/disabled for the following formats
19551965
19561966input formats
19571967: `latex`, `org`, `textile`, `html` (environments, `\ref`, and
1958− `\eqref` only)
1968+ `\eqref` only), `ipynb`
19591969
19601970output formats
19611971: `textile`, `commonmark`
19621972
1973+Note: as applied to `ipynb`, `raw_html` and `raw_tex` affect not
1974+only raw TeX in markdown cells, but data with mime type
1975+`text/html` in output cells. Since the `ipynb` reader attempts
1976+to preserve the richest possible outputs when several options
1977+are given, you will get best results if you disable `raw_html`
1978+and `raw_tex` when converting to formats like `docx` which don't
1979+allow raw `html` or `tex`.
1980+
19631981#### Extension: `native_divs` {#native_divs}
19641982
19651983This extension is enabled by default for HTML input. This means that
@@ -4747,6 +4765,112 @@ with the `src` attribute. For example:
47474765 </source>
47484766 </audio>
47494767
4768+Creating Jupyter notebooks with pandoc
4769+======================================
4770+
4771+When creating a [Jupyter notebook], pandoc will try to infer the
4772+notebook structure. Code blocks with the class `code` will be
4773+taken as code cells, and intervening content will be taken as
4774+Markdown cells. Attachments will automatically be created for
4775+images in Markdown cells. For example:
4776+
4777+````
4778+---
4779+title: My notebook
4780+nbformat: 4
4781+nbformat_minor: 5
4782+kernelspec:
4783+ display_name: Python 2
4784+ language: python
4785+ name: python2
4786+language_info:
4787+ codemirror_mode:
4788+ name: ipython
4789+ version: 2
4790+ file_extension: ".py"
4791+ mimetype: "text/x-python"
4792+ name: "python"
4793+ nbconvert_exporter: "python"
4794+ pygments_lexer: "ipython2"
4795+ version: "2.7.15"
4796+---
4797+
4798+# Lorem ipsum
4799+
4800+**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus
4801+bibendum felis dictum sodales.
4802+
4803+``` code
4804+print("hello")
4805+```
4806+
4807+## Pyout
4808+
4809+``` code
4810+from IPython.display import HTML
4811+HTML("""
4812+<script>
4813+console.log("hello");
4814+</script>
4815+<b>HTML</b>
4816+""")
4817+```
4818+
4819+## Image
4820+
4821+This image ![image](myimage.png) will be
4822+included as a cell attachment.
4823+````
4824+
4825+If you want to add cell attributes, group cells differently, or
4826+add output to code cells, then you need to include divs to
4827+indicate the structure. You can use either [fenced
4828+divs][Extension: `fenced_divs`] or [native divs][Extension:
4829+`native_divs`] for this. Here is an example:
4830+
4831+````
4832+:::::: {.cell .markdown}
4833+# Lorem
4834+
4835+**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus
4836+bibendum felis dictum sodales.
4837+::::::
4838+
4839+:::::: {.cell .code execution_count=1}
4840+``` {.python}
4841+print("hello")
4842+```
4843+
4844+::: {.output .stream .stdout}
4845+```
4846+hello
4847+```
4848+:::
4849+::::::
4850+
4851+:::::: {.cell .code execution_count=2}
4852+``` {.python}
4853+from IPython.display import HTML
4854+HTML("""
4855+<script>
4856+console.log("hello");
4857+</script>
4858+<b>HTML</b>
4859+""")
4860+```
4861+
4862+::: {.output .execute_result execution_count=2}
4863+```{=html}
4864+<script>
4865+console.log("hello");
4866+</script>
4867+<b>HTML</b>
4868+hello
4869+```
4870+:::
4871+::::::
4872+````
4873+
47504874Syntax highlighting
47514875===================
47524876
README.md+5
@@ -32,6 +32,7 @@ library. It can convert *from*
3232 - `creole` ([Creole 1.0](http://www.wikicreole.org/wiki/Creole1.0))
3333 - `docbook` ([DocBook](http://docbook.org))
3434 - `docx` ([Word docx](https://en.wikipedia.org/wiki/Office_Open_XML))
35+ - `dokuwiki` ([DokuWiki markup](https://www.dokuwiki.org/dokuwiki))
3536 - `epub` ([EPUB](http://idpf.org/epub))
3637 - `fb2`
3738 ([FictionBook2](http://www.fictionbook.org/index.php/Eng:XML_Schema_Fictionbook_2.1)
@@ -44,6 +45,8 @@ library. It can convert *from*
4445 - `haddock` ([Haddock
4546 markup](https://www.haskell.org/haddock/doc/html/ch03s08.html))
4647 - `html` ([HTML](http://www.w3.org/html/))
48+ - `ipynb` ([Jupyter
49+ notebook](https://nbformat.readthedocs.io/en/latest/))
4750 - `jats` ([JATS](https://jats.nlm.nih.gov) XML)
4851 - `json` (JSON version of native AST)
4952 - `latex` ([LaTeX](http://latex-project.org))
@@ -105,6 +108,8 @@ It can convert *to*
105108 - `html4` ([XHTML](http://www.w3.org/TR/xhtml1/) 1.0 Transitional)
106109 - `icml` ([InDesign
107110 ICML](http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/indesign/sdk/cs6/idml/idml-cookbook.pdf))
111+ - `ipynb` ([Jupyter
112+ notebook](https://nbformat.readthedocs.io/en/latest/))
108113 - `jats` ([JATS](https://jats.nlm.nih.gov) XML)
109114 - `json` (JSON version of native AST)
110115 - `latex` ([LaTeX](http://latex-project.org))
cabal.project+1
@@ -4,3 +4,4 @@ source-repository-package
44 type: git
55 location: https://github.com/jgm/pandoc-citeproc
66 tag: 4b467c62af17ddfc739933891c5ea2291a6b9b76
7+
pandoc.cabal+11−8
@@ -20,14 +20,14 @@ description: Pandoc is a Haskell library for converting from one markup
2020 (subsets of) HTML, reStructuredText, LaTeX, DocBook, JATS,
2121 MediaWiki markup, DokuWiki markup, TWiki markup,
2222 TikiWiki markup, Creole 1.0, Haddock markup, OPML,
23− Emacs Org-Mode, Emacs Muse, txt2tags,
24− Vimwiki, Word Docx, ODT, EPUB, FictionBook2, roff man,
25− and Textile, and it can write Markdown, reStructuredText,
26− XHTML, HTML 5, LaTeX, ConTeXt, DocBook, JATS, OPML, TEI,
27− OpenDocument, ODT, Word docx, PowerPoint pptx,
28− RTF, MediaWiki, DokuWiki, ZimWiki, Textile,
23+ Emacs Org-Mode, Emacs Muse, txt2tags, ipynb (Jupyter
24+ notebooks), Vimwiki, Word Docx, ODT, EPUB, FictionBook2,
25+ roff man, and Textile, and it can write Markdown,
26+ reStructuredText, XHTML, HTML 5, LaTeX, ConTeXt, DocBook,
27+ JATS, OPML, TEI, OpenDocument, ODT, Word docx,
28+ PowerPoint pptx, RTF, MediaWiki, DokuWiki, ZimWiki, Textile,
2929 roff man, roff ms, plain text, Emacs Org-Mode,
30− AsciiDoc, Haddock markup, EPUB (v2 and v3),
30+ AsciiDoc, Haddock markup, EPUB (v2 and v3), ipynb,
3131 FictionBook2, InDesign ICML, Muse, LaTeX beamer slides,
3232 and several kinds of HTML/JavaScript slide shows
3333 (S5, Slidy, Slideous, DZSlides, reveal.js).
@@ -398,7 +398,8 @@ library
398398 http-types >= 0.8 && < 0.13,
399399 case-insensitive >= 1.2 && < 1.3,
400400 unicode-transforms >= 0.3 && < 0.4,
401− HsYAML >= 0.1.1.1 && < 0.2
401+ HsYAML >= 0.1.1.1 && < 0.2,
402+ ipynb >= 0.1 && < 0.2
402403 if impl(ghc < 8.0)
403404 build-depends: semigroups == 0.18.*,
404405 -- basement 0.0.8 and foundation 0.0.21, transitive
@@ -470,12 +471,14 @@ library
470471 Text.Pandoc.Readers.Man,
471472 Text.Pandoc.Readers.FB2,
472473 Text.Pandoc.Readers.DokuWiki,
474+ Text.Pandoc.Readers.Ipynb,
473475 Text.Pandoc.Writers,
474476 Text.Pandoc.Writers.Native,
475477 Text.Pandoc.Writers.Docbook,
476478 Text.Pandoc.Writers.JATS,
477479 Text.Pandoc.Writers.OPML,
478480 Text.Pandoc.Writers.HTML,
481+ Text.Pandoc.Writers.Ipynb,
479482 Text.Pandoc.Writers.ICML,
480483 Text.Pandoc.Writers.LaTeX,
481484 Text.Pandoc.Writers.ConTeXt,
src/Text/Pandoc/App/FormatHeuristics.hs+1
@@ -90,5 +90,6 @@ formatFromFilePath x =
9090 ".txt" -> Just "markdown"
9191 ".wiki" -> Just "mediawiki"
9292 ".xhtml" -> Just "html"
93+ ".ipynb" -> Just "ipynb"
9394 ['.',y] | y `elem` ['1'..'9'] -> Just "man"
9495 _ -> Nothing
src/Text/Pandoc/Error.hs+3
@@ -70,6 +70,7 @@ data PandocError = PandocIOError String IOError
7070 | PandocEpubSubdirectoryError String
7171 | PandocMacroLoop String
7272 | PandocUTF8DecodingError String Int Word8
73+ | PandocIpynbDecodingError String
7374 deriving (Show, Typeable, Generic)
7475
7576instance Exception PandocError
@@ -124,6 +125,8 @@ handleError (Left e) =
124125 "UTF-8 decoding error in " ++ f ++ " at byte offset " ++ show offset ++
125126 " (" ++ printf "%2x" w ++ ").\n" ++
126127 "The input must be a UTF-8 encoded text."
128+ PandocIpynbDecodingError w -> err 93 $
129+ "ipynb decoding error: " ++ w
127130
128131err :: Int -> String -> IO a
129132err exitCode msg = do
src/Text/Pandoc/Extensions.hs+2
@@ -332,6 +332,8 @@ getDefaultExtensions "markdown_phpextra" = phpMarkdownExtraExtensions
332332getDefaultExtensions "markdown_mmd" = multimarkdownExtensions
333333getDefaultExtensions "markdown_github" = githubMarkdownExtensions
334334getDefaultExtensions "markdown" = pandocExtensions
335+getDefaultExtensions "ipynb" = enableExtension Ext_tex_math_dollars
336+ githubMarkdownExtensions
335337getDefaultExtensions "muse" = extensionsFromList
336338 [Ext_amuse,
337339 Ext_auto_identifiers]
src/Text/Pandoc/Readers.hs+3
@@ -67,6 +67,7 @@ module Text.Pandoc.Readers
6767 , readEPUB
6868 , readMuse
6969 , readFB2
70+ , readIpynb
7071 -- * Miscellaneous
7172 , getReader
7273 , getDefaultExtensions
@@ -90,6 +91,7 @@ import Text.Pandoc.Readers.Docx
9091import Text.Pandoc.Readers.DokuWiki
9192import Text.Pandoc.Readers.EPUB
9293import Text.Pandoc.Readers.FB2
94+import Text.Pandoc.Readers.Ipynb
9395import Text.Pandoc.Readers.Haddock
9496import Text.Pandoc.Readers.HTML (readHtml)
9597import Text.Pandoc.Readers.JATS (readJATS)
@@ -147,6 +149,7 @@ readers = [ ("native" , TextReader readNative)
147149 ,("muse" , TextReader readMuse)
148150 ,("man" , TextReader readMan)
149151 ,("fb2" , TextReader readFB2)
152+ ,("ipynb" , TextReader readIpynb)
150153 ]
151154
152155-- | Retrieve reader, extensions based on formatSpec (format+extensions).
src/Text/Pandoc/Readers/Ipynb.hsnew file+249
@@ -0,0 +1,249 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE LambdaCase #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE FlexibleContexts #-}
6+{-# LANGUAGE ScopedTypeVariables #-}
7+{-
8+Copyright (C) 2019 John MacFarlane <jgm@berkeley.edu>
9+
10+This program is free software; you can redistribute it and/or modify
11+it under the terms of the GNU General Public License as published by
12+the Free Software Foundation; either version 2 of the License, or
13+(at your option) any later version.
14+
15+This program is distributed in the hope that it will be useful,
16+but WITHOUT ANY WARRANTY; without even the implied warranty of
17+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+GNU General Public License for more details.
19+
20+You should have received a copy of the GNU General Public License
21+along with this program; if not, write to the Free Software
22+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23+-}
24+
25+{- |
26+ Module : Text.Pandoc.Readers.Ipynb
27+ Copyright : Copyright (C) 2019 John MacFarlane
28+ License : GNU GPL, version 2 or above
29+
30+ Maintainer : John MacFarlane <jgm@berkeley.edu>
31+ Stability : alpha
32+ Portability : portable
33+
34+Ipynb (Jupyter notebook JSON format) reader for pandoc.
35+-}
36+module Text.Pandoc.Readers.Ipynb ( readIpynb )
37+where
38+import Prelude
39+import Data.Maybe (fromMaybe)
40+import Data.Digest.Pure.SHA (sha1, showDigest)
41+import Text.Pandoc.Options
42+import qualified Text.Pandoc.Builder as B
43+import Text.Pandoc.Logging
44+import Text.Pandoc.Definition
45+import Data.Ipynb as Ipynb
46+import Text.Pandoc.Class
47+import Text.Pandoc.MIME (extensionFromMimeType)
48+import Text.Pandoc.UTF8
49+import Text.Pandoc.Error
50+import Data.Text (Text)
51+import qualified Data.Map as M
52+import qualified Data.Text as T
53+import qualified Data.Text.Encoding as TE
54+import qualified Data.ByteString.Lazy as BL
55+import Data.Aeson as Aeson
56+import Control.Monad.Except (throwError)
57+import Text.Pandoc.Readers.Markdown (readMarkdown)
58+import Text.Pandoc.Readers.HTML (readHtml)
59+
60+readIpynb :: PandocMonad m => ReaderOptions -> Text -> m Pandoc
61+readIpynb opts t = do
62+ let src = BL.fromStrict (TE.encodeUtf8 t)
63+ case eitherDecode src of
64+ Right (notebook4 :: Notebook NbV4) -> notebookToPandoc opts notebook4
65+ Left _ ->
66+ case eitherDecode src of
67+ Right (notebook3 :: Notebook NbV3) -> notebookToPandoc opts notebook3
68+ Left err -> throwError $ PandocIpynbDecodingError err
69+
70+notebookToPandoc :: (PandocMonad m, FromJSON (Notebook a))
71+ => ReaderOptions -> Notebook a -> m Pandoc
72+notebookToPandoc opts notebook = do
73+ let cells = notebookCells notebook
74+ let (fmt,fmtminor) = notebookFormat notebook
75+ let m = M.insert "nbformat" (MetaString $ show fmt) $
76+ M.insert "nbformat_minor" (MetaString $ show fmtminor) $
77+ jsonMetaToMeta (notebookMetadata notebook)
78+ let lang = case M.lookup "kernelspec" m of
79+ Just (MetaMap ks) ->
80+ case M.lookup "language" ks of
81+ Just (MetaString l) -> l
82+ _ -> "python"
83+ _ -> "python"
84+ bs <- mconcat <$> mapM (cellToBlocks opts lang) cells
85+ let Pandoc _ blocks = B.doc bs
86+ return $ Pandoc (Meta m) blocks
87+
88+cellToBlocks :: PandocMonad m
89+ => ReaderOptions -> String -> Cell a -> m B.Blocks
90+cellToBlocks opts lang c = do
91+ let Source ts = cellSource c
92+ let source = mconcat ts
93+ let kvs = jsonMetaToPairs (cellMetadata c)
94+ let attachments = maybe mempty M.toList $ cellAttachments c
95+ mapM_ addAttachment attachments
96+ case cellType c of
97+ Ipynb.Markdown -> do
98+ Pandoc _ bs <- readMarkdown opts source
99+ return $ B.divWith ("",["cell","markdown"],kvs)
100+ $ B.fromList bs
101+ Ipynb.Heading lev -> do
102+ Pandoc _ bs <- readMarkdown opts
103+ (T.replicate lev "#" <> " " <> source)
104+ return $ B.divWith ("",["cell","markdown"],kvs)
105+ $ B.fromList bs
106+ Ipynb.Raw -> do
107+ let format = fromMaybe "" $ lookup "format" kvs
108+ let format' =
109+ case format of
110+ "text/html" -> "html"
111+ "text/latex" -> "latex"
112+ "application/pdf" -> "latex"
113+ "text/markdown" -> "markdown"
114+ "text/x-rsrt" -> "rst"
115+ _ -> format
116+ return $ B.divWith ("",["cell","raw"],kvs) $ B.rawBlock format'
117+ $ T.unpack source
118+ Ipynb.Code{ codeOutputs = outputs, codeExecutionCount = ec } -> do
119+ outputBlocks <- mconcat <$> mapM (outputToBlock opts) outputs
120+ let kvs' = maybe kvs (\x -> ("execution_count", show x):kvs) ec
121+ return $ B.divWith ("",["cell","code"],kvs') $
122+ B.codeBlockWith ("",[lang],[]) (T.unpack source)
123+ <> outputBlocks
124+
125+addAttachment :: PandocMonad m => (Text, MimeBundle) -> m ()
126+addAttachment (fname, mimeBundle) = do
127+ let fp = T.unpack fname
128+ case M.toList (unMimeBundle mimeBundle) of
129+ (mimeType, BinaryData bs):_ ->
130+ insertMedia fp (Just $ T.unpack mimeType) (BL.fromStrict bs)
131+ (mimeType, TextualData t):_ ->
132+ insertMedia fp (Just $ T.unpack mimeType)
133+ (BL.fromStrict $ TE.encodeUtf8 t)
134+ (mimeType, JsonData v):_ ->
135+ insertMedia fp (Just $ T.unpack mimeType) (encode v)
136+ [] -> report $ CouldNotFetchResource fp "no attachment"
137+
138+outputToBlock :: PandocMonad m => ReaderOptions -> Output a -> m B.Blocks
139+outputToBlock _ Stream{ streamName = sName,
140+ streamText = Source text } = do
141+ return $ B.divWith ("",["output","stream",T.unpack sName],[])
142+ $ B.codeBlock $ T.unpack . mconcat $ text
143+outputToBlock opts DisplayData{ displayData = data',
144+ displayMetadata = metadata' } =
145+ B.divWith ("",["output", "display_data"],[]) <$>
146+ handleData opts metadata' data'
147+outputToBlock opts ExecuteResult{ executeCount = ec,
148+ executeData = data',
149+ executeMetadata = metadata' } =
150+ B.divWith ("",["output", "execute_result"],[("execution_count",show ec)])
151+ <$> handleData opts metadata' data'
152+outputToBlock _ Err{ errName = ename,
153+ errValue = evalue,
154+ errTraceback = traceback } = do
155+ return $ B.divWith ("",["output","error"],
156+ [("ename",T.unpack ename),
157+ ("evalue",T.unpack evalue)])
158+ $ B.codeBlock $ T.unpack . T.unlines $ traceback
159+
160+-- We want to display the richest output possible given
161+-- the output format.
162+handleData :: PandocMonad m
163+ => ReaderOptions -> JSONMeta -> MimeBundle -> m B.Blocks
164+handleData opts metadata (MimeBundle mb) = do
165+ let mimePairs = M.toList mb
166+
167+ results <- mapM dataBlock mimePairs
168+
169+ -- return the result with highest priority:
170+
171+ let highest = maximum (0 : map fst results)
172+ return $ case [r | (pr, r) <- results, pr == highest] of
173+ x:_ -> x
174+ [] -> mempty
175+
176+ where
177+
178+ exts = readerExtensions opts
179+
180+ dataBlock :: PandocMonad m => (MimeType, MimeData) -> m (Int, B.Blocks)
181+ dataBlock (mt, BinaryData bs)
182+ | "image/" `T.isPrefixOf` mt
183+ = do
184+ -- normally metadata maps from mime types to key-value map;
185+ -- but not always...
186+ let meta = case M.lookup mt metadata of
187+ Just v@(Object{}) ->
188+ case fromJSON v of
189+ Success m' -> m'
190+ Error _ -> mempty
191+ _ -> mempty
192+ let metaPairs = jsonMetaToPairs meta
193+ let bl = BL.fromStrict bs
194+ -- SHA1 hash for filename
195+ let mt' = T.unpack mt
196+ let fname = showDigest (sha1 bl) ++
197+ case extensionFromMimeType mt' of
198+ Nothing -> ""
199+ Just ext -> '.':ext
200+ insertMedia fname (Just mt') bl
201+ return (3, B.para $ B.imageWith ("",[],metaPairs) fname "" mempty)
202+
203+ dataBlock (_, BinaryData _) = return (0, mempty)
204+
205+ dataBlock ("text/html", TextualData t)
206+ | extensionEnabled Ext_raw_html exts
207+ = return (2, B.rawBlock "html" $ T.unpack t)
208+ | otherwise = do -- try parsing the HTML
209+ Pandoc _ bls <- readHtml opts t
210+ return (1, B.fromList bls)
211+
212+ dataBlock ("text/latex", TextualData t) =
213+ return $ if extensionEnabled Ext_raw_tex exts
214+ then (2, B.rawBlock "latex" $ T.unpack t)
215+ else (0, mempty)
216+
217+ dataBlock ("text/plain", TextualData t) =
218+ return (0, B.codeBlock $ T.unpack t)
219+
220+ dataBlock (_, JsonData v) =
221+ return (2, B.codeBlockWith ("",["json"],[]) $ toStringLazy $ encode v)
222+
223+ dataBlock _ = return (0, mempty)
224+
225+jsonMetaToMeta :: JSONMeta -> M.Map String MetaValue
226+jsonMetaToMeta = M.mapKeys T.unpack . M.map valueToMetaValue
227+ where
228+ valueToMetaValue :: Value -> MetaValue
229+ valueToMetaValue x@(Object{}) =
230+ case fromJSON x of
231+ Error s -> MetaString s
232+ Success jm' -> MetaMap $ jsonMetaToMeta jm'
233+ valueToMetaValue x@(Array{}) =
234+ case fromJSON x of
235+ Error s -> MetaString s
236+ Success xs -> MetaList $ map valueToMetaValue xs
237+ valueToMetaValue (Bool b) = MetaBool b
238+ valueToMetaValue (String t) = MetaString (T.unpack t)
239+ valueToMetaValue (Number n) = MetaString (show n)
240+ valueToMetaValue Aeson.Null = MetaString ""
241+
242+jsonMetaToPairs :: JSONMeta -> [(String, String)]
243+jsonMetaToPairs = M.toList . M.mapMaybe
244+ (\case
245+ MetaString s -> Just s
246+ MetaBool True -> Just "true"
247+ MetaBool False -> Just "false"
248+ -- for now we skip complex cell metadata:
249+ _ -> Nothing) . jsonMetaToMeta
src/Text/Pandoc/Templates.hs+1
@@ -58,6 +58,7 @@ getDefaultTemplate writer = do
5858 "docx" -> return ""
5959 "fb2" -> return ""
6060 "pptx" -> return ""
61+ "ipynb" -> return ""
6162 "odt" -> getDefaultTemplate "opendocument"
6263 "html" -> getDefaultTemplate "html5"
6364 "docbook" -> getDefaultTemplate "docbook5"
src/Text/Pandoc/Writers.hs+3
@@ -49,6 +49,7 @@ module Text.Pandoc.Writers
4949 , writeEPUB2
5050 , writeEPUB3
5151 , writeFB2
52+ , writeIpynb
5253 , writeHaddock
5354 , writeHtml4
5455 , writeHtml4String
@@ -101,6 +102,7 @@ import Text.Pandoc.Writers.Docx
101102import Text.Pandoc.Writers.DokuWiki
102103import Text.Pandoc.Writers.EPUB
103104import Text.Pandoc.Writers.FB2
105+import Text.Pandoc.Writers.Ipynb
104106import Text.Pandoc.Writers.Haddock
105107import Text.Pandoc.Writers.HTML
106108import Text.Pandoc.Writers.ICML
@@ -140,6 +142,7 @@ writers = [
140142 ,("epub2" , ByteStringWriter writeEPUB2)
141143 ,("epub3" , ByteStringWriter writeEPUB3)
142144 ,("fb2" , TextWriter writeFB2)
145+ ,("ipynb" , TextWriter writeIpynb)
143146 ,("html" , TextWriter writeHtml5String)
144147 ,("html4" , TextWriter writeHtml4String)
145148 ,("html5" , TextWriter writeHtml5String)
src/Text/Pandoc/Writers/Ipynb.hsnew file+233
@@ -0,0 +1,233 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-
6+Copyright (C) 2019 John MacFarlane <jgm@berkeley.edu>
7+
8+This program is free software; you can redistribute it and/or modify
9+it under the terms of the GNU General Public License as published by
10+the Free Software Foundation; either version 2 of the License, or
11+(at your option) any later version.
12+
13+This program is distributed in the hope that it will be useful,
14+but WITHOUT ANY WARRANTY; without even the implied warranty of
15+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+GNU General Public License for more details.
17+
18+You should have received a copy of the GNU General Public License
19+along with this program; if not, write to the Free Software
20+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21+-}
22+
23+{- |
24+ Module : Text.Pandoc.Writers.Ipynb
25+ Copyright : Copyright (C) 2019 John MacFarlane
26+ License : GNU GPL, version 2 or above
27+
28+ Maintainer : John MacFarlane <jgm@berkeley.edu>
29+ Stability : alpha
30+ Portability : portable
31+
32+Ipynb (Jupyter notebook JSON format) writer for pandoc.
33+
34+-}
35+module Text.Pandoc.Writers.Ipynb ( writeIpynb )
36+where
37+import Prelude
38+import Control.Monad (foldM)
39+import Control.Monad.State
40+import qualified Data.Map as M
41+import Data.Maybe (catMaybes, fromMaybe)
42+import Text.Pandoc.Options
43+import Text.Pandoc.Definition
44+import Data.Ipynb as Ipynb
45+import Text.Pandoc.Walk (walkM)
46+import qualified Text.Pandoc.Builder as B
47+import Text.Pandoc.Class
48+import Text.Pandoc.Logging
49+import Data.Text (Text)
50+import qualified Data.Text as T
51+import Data.Aeson as Aeson
52+import qualified Text.Pandoc.UTF8 as UTF8
53+import Text.Pandoc.Shared (safeRead)
54+import Text.Pandoc.Writers.Shared (metaToJSON')
55+import Text.Pandoc.Writers.Markdown (writeMarkdown)
56+import qualified Data.Text.Encoding as TE
57+import qualified Data.ByteString.Lazy as BL
58+import Data.Aeson.Encode.Pretty (Config(..), defConfig,
59+ encodePretty', keyOrder, Indent(Spaces))
60+
61+writeIpynb :: PandocMonad m => WriterOptions -> Pandoc -> m Text
62+writeIpynb opts d = do
63+ notebook <- pandocToNotebook opts d
64+ return $ TE.decodeUtf8 . BL.toStrict . encodePretty' defConfig{
65+ confIndent = Spaces 1,
66+ confCompare = keyOrder
67+ [ "cells", "nbformat", "nbformat_minor",
68+ "cell_type", "output_type",
69+ "execution_count", "metadata",
70+ "outputs", "source",
71+ "data", "name", "text" ] }
72+ $ notebook
73+
74+pandocToNotebook :: PandocMonad m
75+ => WriterOptions -> Pandoc -> m (Notebook NbV4)
76+pandocToNotebook opts (Pandoc meta blocks) = do
77+ let blockWriter bs = writeMarkdown
78+ opts{ writerTemplate = Nothing } (Pandoc nullMeta bs)
79+ let inlineWriter ils = T.stripEnd <$> writeMarkdown
80+ opts{ writerTemplate = Nothing } (Pandoc nullMeta [Plain ils])
81+ metadata' <- metaToJSON' blockWriter inlineWriter $
82+ B.deleteMeta "nbformat" $
83+ B.deleteMeta "nbformat_minor" $ meta
84+ let metadata = case fromJSON metadata' of
85+ Error _ -> mempty -- TODO warning here? shouldn't happen
86+ Success x -> x
87+ cells <- extractCells opts blocks
88+ return $ Notebook{
89+ notebookMetadata = metadata
90+ , notebookFormat = (4, 5)
91+ , notebookCells = cells }
92+
93+addAttachment :: PandocMonad m
94+ => Inline
95+ -> StateT (M.Map Text MimeBundle) m Inline
96+addAttachment (Image attr lab (src,tit)) = do
97+ (img, mbmt) <- fetchItem src
98+ let mt = maybe "application/octet-stream" (T.pack) mbmt
99+ modify $ M.insert (T.pack src)
100+ (MimeBundle (M.insert mt (BinaryData img) mempty))
101+ return $ Image attr lab ("attachment:" <> src, tit)
102+addAttachment x = return x
103+
104+extractCells :: PandocMonad m => WriterOptions -> [Block] -> m [Cell a]
105+extractCells _ [] = return []
106+extractCells opts (Div (_id,classes,kvs) xs : bs)
107+ | "cell" `elem` classes
108+ , "markdown" `elem` classes = do
109+ let meta = pairsToJSONMeta kvs
110+ (newdoc, attachments) <-
111+ runStateT (walkM addAttachment (Pandoc nullMeta xs)) mempty
112+ source <- writeMarkdown opts{ writerTemplate = Nothing } newdoc
113+ (Cell{
114+ cellType = Markdown
115+ , cellSource = Source $ breakLines source
116+ , cellMetadata = meta
117+ , cellAttachments = if M.null attachments
118+ then Nothing
119+ else Just attachments } :)
120+ <$> extractCells opts bs
121+ | "cell" `elem` classes
122+ , "code" `elem` classes = do
123+ let (codeContent, rest) =
124+ case xs of
125+ (CodeBlock _ t : ys) -> (T.pack t, ys)
126+ ys -> (mempty, ys)
127+ let meta = pairsToJSONMeta kvs
128+ outputs <- catMaybes <$> mapM blockToOutput rest
129+ let exeCount = lookup "execution_count" kvs >>= safeRead
130+ (Cell{
131+ cellType = Ipynb.Code {
132+ codeExecutionCount = exeCount
133+ , codeOutputs = outputs
134+ }
135+ , cellSource = Source $ breakLines codeContent
136+ , cellMetadata = meta
137+ , cellAttachments = Nothing } :) <$> extractCells opts bs
138+ | "cell" `elem` classes
139+ , "raw" `elem` classes =
140+ case xs of
141+ [RawBlock (Format f) raw] -> do
142+ let format' =
143+ case f of
144+ "html" -> "text/html"
145+ "revealjs" -> "text/html"
146+ "latex" -> "text/latex"
147+ "markdown" -> "text/markdown"
148+ "rst" -> "text/x-rst"
149+ _ -> f
150+ (Cell{
151+ cellType = Raw
152+ , cellSource = Source $ breakLines $ T.pack raw
153+ , cellMetadata = M.insert "format"
154+ (Aeson.String $ T.pack format') mempty
155+ , cellAttachments = Nothing } :) <$> extractCells opts bs
156+ _ -> extractCells opts bs
157+extractCells opts (CodeBlock (_id,classes,kvs) raw : bs)
158+ | "code" `elem` classes = do
159+ let meta = pairsToJSONMeta kvs
160+ let exeCount = lookup "execution_count" kvs >>= safeRead
161+ (Cell{
162+ cellType = Ipynb.Code {
163+ codeExecutionCount = exeCount
164+ , codeOutputs = []
165+ }
166+ , cellSource = Source $ breakLines $ T.pack raw
167+ , cellMetadata = meta
168+ , cellAttachments = Nothing } :) <$> extractCells opts bs
169+extractCells opts (b:bs) = do
170+ let isCodeOrDiv (CodeBlock (_,cl,_) _) = "code" `elem` cl
171+ isCodeOrDiv (Div (_,cl,_) _) = "cell" `elem` cl
172+ isCodeOrDiv _ = False
173+ let (mds, rest) = break (isCodeOrDiv) bs
174+ extractCells opts (Div ("",["cell","markdown"],[]) (b:mds) : rest)
175+
176+blockToOutput :: PandocMonad m => Block -> m (Maybe (Output a))
177+blockToOutput (Div (_,["output","stream",sname],_) (CodeBlock _ t:_)) =
178+ return $ Just
179+ $ Stream{ streamName = T.pack sname
180+ , streamText = Source (breakLines $ T.pack t) }
181+blockToOutput (Div (_,["output","error"],kvs) (CodeBlock _ t:_)) =
182+ return $ Just
183+ $ Err{ errName = maybe mempty T.pack (lookup "ename" kvs)
184+ , errValue = maybe mempty T.pack (lookup "evalue" kvs)
185+ , errTraceback = breakLines $ T.pack t }
186+blockToOutput (Div (_,["output","execute_result"],kvs) bs) = do
187+ (data', metadata') <- extractData bs
188+ return $ Just
189+ $ ExecuteResult{ executeCount = fromMaybe 0 $
190+ lookup "execution_count" kvs >>= safeRead
191+ , executeData = data'
192+ , executeMetadata = pairsToJSONMeta kvs <> metadata'}
193+blockToOutput (Div (_,["output","display_data"],kvs) bs) = do
194+ (data', metadata') <- extractData bs
195+ return $ Just
196+ $ DisplayData { displayData = data'
197+ , displayMetadata = pairsToJSONMeta kvs <> metadata'}
198+blockToOutput _ = return Nothing
199+
200+extractData :: PandocMonad m => [Block] -> m (MimeBundle, JSONMeta)
201+extractData bs = do
202+ (mmap, meta) <- foldM go mempty bs
203+ return (MimeBundle mmap, meta)
204+ where
205+ go (mmap, meta) b@(Para [Image (_,_,kvs) _ (src,_)]) = do
206+ (img, mbmt) <- fetchItem src
207+ case mbmt of
208+ Just mt -> return
209+ (M.insert (T.pack mt) (BinaryData img) mmap,
210+ meta <> pairsToJSONMeta kvs)
211+ Nothing -> (mmap, meta) <$ report (BlockNotRendered b)
212+ go (mmap, meta) b@(CodeBlock (_,["json"],_) code) =
213+ case decode (UTF8.fromStringLazy code) of
214+ Just v -> return
215+ (M.insert "application/json" (JsonData v) mmap, meta)
216+ Nothing -> (mmap, meta) <$ report (BlockNotRendered b)
217+ go (mmap, meta) (CodeBlock ("",[],[]) code) =
218+ return (M.insert "text/plain" (TextualData (T.pack code)) mmap, meta)
219+ go (mmap, meta) (RawBlock (Format "html") raw) =
220+ return (M.insert "text/html" (TextualData (T.pack raw)) mmap, meta)
221+ go (mmap, meta) (RawBlock (Format "latex") raw) =
222+ return (M.insert "text/latex" (TextualData (T.pack raw)) mmap, meta)
223+ go (mmap, meta) b = (mmap, meta) <$ report (BlockNotRendered b)
224+
225+pairsToJSONMeta :: [(String, String)] -> JSONMeta
226+pairsToJSONMeta kvs =
227+ M.fromList [(T.pack k, case v of
228+ "true" -> Bool True
229+ "false" -> Bool False
230+ _ -> case safeRead v of
231+ Just n -> Number n
232+ _ -> String (T.pack v))
233+ | (k,v) <- kvs , k /= "execution_count" ]
stack.yaml+1
@@ -21,6 +21,7 @@ packages:
2121extra-deps:
2222- github: jgm/pandoc-citeproc
2323 commit: 4b467c62af17ddfc739933891c5ea2291a6b9b76
24+- ipynb-0.1
2425- haddock-library-1.7.0
2526- HsYAML-0.1.1.2
2627- yaml-0.11.0.0