49

Is there a way to export a Mathematica notebook into Markdown?

I'm sure it's possible to implement rules for converting each Mathematica cell subexpression into Markdown, but I'm also sure that I don't know enough about BoxForms to get this right!

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
M.R.
  • 31,425
  • 8
  • 90
  • 281
  • 1
    Related meta posts: http://meta.mathematica.stackexchange.com/questions/990/is-there-a-way-to-convert-a-mathematica-notebook-to-stack-exchange-format, http://meta.mathematica.stackexchange.com/questions/1286/has-someone-created-markdown-notebook-converter – Michael E2 May 27 '15 at 16:55
  • Can pandoc be called from within Mathematica? You can do something like URLFetch["heckyesmarkdown.com/go/", "Parameters" -> {"html" -> ExportString[nb, "HTML"]}] where nb is the NotebookObject. – bobthechemist May 27 '15 at 17:31
  • 7
    Mathematic's nb conversion to html is terrible, cell and graphics styles and formatting never export correctly (mostly unsupported) to html and printing notebooks is a fool's errand. Am I off base here? – M.R. May 27 '15 at 21:15
  • 8
    I'd use the more politically correct 'suboptimal' ;-) – Sjoerd C. de Vries May 27 '15 at 21:20
  • 2
    In R it's very elegant and straightforward. See RMarkdown – Murta May 29 '15 at 12:49
  • 1
    It would suffice to export the cell group structure to json, anyone know how to do that? – M.R. Oct 06 '15 at 16:12
  • 1
    One way might be with html + pandoc, but html conversion is buggy. – M.R. Nov 17 '15 at 20:50
  • And what about outputs and 2D structures in notebook? – Kuba Nov 17 '15 at 22:07
  • @Kuba I would say anything textual -> text, math expressions -> latex, graphics/images -> images, sections -> sections, grids -> tables, item cells -> outlines, and anything else 2d, like graphs -> images. – M.R. Nov 18 '15 at 02:33
  • You might start with the SENotebook package. It doesn't seem to work at present (well, for me, with zero effort to troubleshoot). What's being asked, while probably of interest to regular users here, seems "too broad" or in need of the "services of a professional consultant." – Michael E2 Nov 18 '15 at 03:31
  • 1
    @MichaelE2 Some subsets of possible cells can easily be translated, I wanted to see what others could do and I will accept the code that covers the most cases, doesn't have to be exhaustive. – M.R. Nov 18 '15 at 04:11
  • OK, but "converting each Mathematica cell subexpression into Markdown" sounds exhaustive. – Michael E2 Nov 18 '15 at 10:49
  • @MichaelE2 Right, but I was just trying to suggest that there could be a nice recursive solution. – M.R. Nov 18 '15 at 15:40
  • @M.R. it is not in GitHub so if you are still interested in new features, let me know so I'm aware of the demand and what to focus on. – Kuba May 26 '18 at 09:54

1 Answers1

47

In this post only the original code is kept. For most recent versions visit GH.

In order to install:

ResourceFunction["GitHubInstall"]["kubapod", "m2md"] 

Or visit GH readme.


Here are related solutions in case this does not fit your needs:


Original post

Features

Markdown capabilities differ from environment to environment so I expect this will be only a base that one can modify so it fits his/her needs best.

  • StackExchange MD supports only H1-H3 headers and Sub/sub/Titles are cell styles which I've associated with them. Sub/sub/Sections are associated with H4-H6 so you work more with those you may change it since the latter group will look here as ordinary text.

  • An Output cells generated by whatever // TraditionalForm are automatically $\LaTeX$ blocks. An Inline TraditionalForm is inline $\LaTeX$.

  • At the moment only this one type of Output cell is parsed.

  • Unknown cell styles leave MD comment: [//]: # (No rules defined for *CellStyleName*), which is not displayed ofc.

  • I tried to convert Hyperlinks and it seems to work.

  • Sub and Subsub ItemsNumbered only get prefix in form: 1., 2. etc. I could put there what we see in MMA, like 1.2.1. but it doesn't seem to be supported in MD. Or rather, it is sometimes but up to SubitemNumbered and no deeper.


Tests

I've put it as a procedure in palette which can convert things quite nicely.

This is a test notebook I'm using and the result of tests pasted here:

CreateDocument@ Uncompress@ "1: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"

(^ double click to select. Don't evaluate this if the last edit was made by someone else than me)

Result


Title bold

Subtitle italic

Subsubtitle

Enter text here. Enter TraditionalForm input for evaluation in a separate cell below:


Integrate[x, x] + Sqrt[x] // TraditionalForm

$$\frac{x^2}{2}+\sqrt{x}$$

  • Item asdasd

    ItemParagraph

    • Subitem

Text cell

Title with Hyperlink: Wolfram Research, Inc. and

a TraditionalForm expression: $\frac{x^2}{2}+\sqrt{x}$

(both are in InlineCells)

Text with inline formula: $4$.

  1. ItemNumbered

    ItemParagraph

    1. SubitemNumbered

      SubitemParagraph

      1. SubsubitemNumbered

        SubsubitemParagraph


fun[x_] := 1 

/Result

Code for palette

CreatePalette[#, CellContext -> Notebook] &@DynamicModule[{},

Button["Export to Markdown",

CreateDocument@exportMD@InputNotebook[]],

Initialization :> (

itemIndent = "   ";

codeIndent = "    ";

itemMark = "+ ";



itemPrefix = Function[{cellObj, style}, Module[{

    ind, depth, numberedQ, paragraphQ},

   ind = ToString@CurrentValue[cellObj, {"CounterValue", style}];

   depth = StringCount[style, "sub", IgnoreCase -> True];

   numberedQ = 

    StringCount[style, "numbered", IgnoreCase -> True] > 0;

   paragraphQ = 

    StringCount[style, "paragraph", IgnoreCase -> True] > 0;

   StringJoin@Flatten@{

      ConstantArray[itemIndent, depth + If[paragraphQ, 2, 1]],

      Which[

       numberedQ, {ind, ". "},

       paragraphQ, "",

       True, itemMark]

      }

   ]];



prefix[styleName_] := Switch[styleName,

  "Title", "# ",

  "Subtitle", "## ",

  "Subsubtitle", "### ",

  "Section", "#### ",

  "Subsection", "##### ",

  "Subsubsection", "###### ",

  "Text", "",

  "items", itemPrefix,

  "code", codeIndent

  ];







styleWrapper[opts___] := Module[{italic, bold, wrapper

   },

  italic = MemberQ[{opts}, Verbatim[Rule][FontSlant, "Italic"]];

  bold = MemberQ[{opts}, Verbatim[Rule][FontWeight, "Bold"]];

  wrapper = Which[

    bold, "**",

    italic, "*",

    True, ""

    ];

  wrapper <> # <> wrapper &



  ];



parseCodeData[data_] := StringReplace[

  First[FrontEndExecute[FrontEnd`ExportPacket[data, "InputText"]]],

  "\n" -> "\n" <> codeIndent

  ];



textStyleQ = (StringCount[#, "title" | "section" | "text", 

     IgnoreCase -> True] > 0) &;

itemStyleQ = (StringCount[#, "item", IgnoreCase -> True] > 

    0) &;

codeStyleQ = MemberQ[{"Code", "Input"}, #] &;



exportMD[nb_NotebookObject] := 

 StringJoin@Flatten[exportMD /@ Cells[nb]];



exportMD[cellObj_CellObject] := 

 exportMD[NotebookRead[cellObj], cellObj];



exportMD[cell_Cell, cellObj_CellObject] := 

 exportMD[#2, #, cellObj] & @@ cell;



exportMD[style_?textStyleQ, data_, cellObj_CellObject] := {

  prefix[style], addPrefix[style] /@ Flatten@{parseData[data]}, 

  "\n\n"

  };



addPrefix[style_][expr : Except[_String]] := expr;

addPrefix[style_][s_String] := 

 StringReplace[s, "\n" -> "\n" <> prefix[style]];



exportMD[style_?itemStyleQ, data_, cellObj_CellObject] := {

  prefix["items"][cellObj, style], parseData@data, "\n\n"};



exportMD[style_?codeStyleQ, data_, cellObj_CellObject] := {

  "\n\n----------\n\n",

  codeIndent, parseCodeData@data,

  "\n\n"};



exportMD["Output", BoxData[FormBox[boxes_, TraditionalForm]], 

  cellObj_CellObject] := TemplateApply[

  &quot;<span class="math-container">$$``$$</span>\n\n&quot;, {boxesToTeX@boxes}

  ];





parseData[list_List] := parseData /@ list;

parseData[string_String] := string;



parseData[data_ (BoxData | TextData)] := 

 List @@ (parseData /@ data);

parseData[cell_Cell] := 

 parseData@First@cell; (*inlince cells style skipped*)



parseData[StyleBox[expr_, opts___]] := 

 styleWrapper[opts]@parseData[expr];



parseData[

  FormBox[boxes : Except[_TagBox], TraditionalForm, ___]] := 

 Module[{teXForm},

  teXForm = boxesToTeX@boxes;

  &quot;<span class="math-container">$" &lt;&gt; teXForm &lt;&gt; "$</span>&quot;

  ];



parseData[

  box : ButtonBox[_, ___, BaseStyle -&gt; &quot;Hyperlink&quot;, ___]] := 

 Module[{label, url},

  {label, url} = {#, #2} &amp; @@ ToExpression[box];



  TemplateApply[

   &quot;[``](``)&quot;, {StringJoin@Flatten@{parseData@label}, url}]



  ];



(*default behaviour for boxes*)

parseData[boxes_] := parseData@First@boxes;



(*default behaviour for cell styles*)

exportMD[s_, ___] := 

 TemplateApply[&quot;[//]: # (No rules defined for ``)\n\n&quot;, {s}];



boxesToTeX = ToString[ToExpression@#, TeXForm] &amp;;

)

]

Kuba
  • 136,707
  • 13
  • 279
  • 740
  • Idle thought: what if TraditionalForm[] objects are the ones converted to $\LaTeX$ (that is, TraditionalForm[Sqrt[z]] becomes $\sqrt z$, perhaps through TeXForm[])? – J. M.'s missing motivation Nov 22 '15 at 03:28
  • Nice! What about image and graphics? You could save them as files and hyperlink them in. I'll try running this as soon as I get back from the Dominican Republic. – M.R. Nov 22 '15 at 06:47
  • And maybe comment the stuff it can't parse. – M.R. Nov 22 '15 at 09:24
  • Have you seen this (by R.M.) already? – J. M.'s missing motivation Nov 23 '15 at 09:00
  • I saw it while looking at the chat archives; then I remembered this. – J. M.'s missing motivation Nov 23 '15 at 09:19
  • R.M. might want to chime in himself if he sees this; in any case, there might be pieces in there you could use for this answer. – J. M.'s missing motivation Nov 23 '15 at 09:25
  • @J.M. I see that link to R.M. notebook is getting passed over various comments. I wonder why such an obscurity? It would be nice if R.M. or a friend would post that work here as an answer. What do you guys think? – Vitaliy Kaurov Nov 24 '15 at 08:36
  • Kuba this is a very nice work - i'd give +n if I could. I just wish all good work would be gathered in a single place for reference - *wink ro @R.M. - ( do not know why @ names do not become links in the comments ) – Vitaliy Kaurov Nov 24 '15 at 08:40
  • @VitaliyKaurov Thanks :) I will try to take a look later what's inside this repository. p.s. feel free to share it on Community, I'm looking forward to hearing what features are needed. I will try to add a user interface for more handy customization of the output later. – Kuba Nov 24 '15 at 08:42
  • @Kuba it did indeed. and of course i could link but i thought that auto-linking is also a sign of sending a auto-notification to a user that his/her name was mentioned. that's the main point. – Vitaliy Kaurov Nov 24 '15 at 08:50
  • 2
    Just roadtested it and already seems to go a fair way. Great! Yes images, code-formatting, inline cells etc would all be handy improvements - agree with @VitaliyKaurov, be nice to bring it all together into an evolving package with R.M.'s work and perhaps is to be part of Leonid's Front-End IDE project? Being able to build large projects with an inset window to seamlessly put/get SE answers would be as valuable as the documentation IMO. Also imagine that once set up/refined, it could be included in welcoming message to all new SE users. – Ronald Monson Jan 01 '16 at 08:49
  • @Kuba I did use this code to create the Markdown file for this blog post: "Making Chernoff faces for data visualization". I had to change the definition of parseData[box:ButtonBox...] in order to get proper exports of the links in the "References" section. – Anton Antonov Jun 03 '16 at 02:55
  • @AntonAntonov yep, I've only taken care of basic hyperlinks. I'm glad it works and is useful! I should probably polish that, make a proper package and add it to github or something. – Kuba Jun 03 '16 at 07:18
  • @RonaldMonson code formatting is already done in Leonids package somewhere and image exports are done by Halirutan with JLink in his SE palette. So one have to put pieces together, add handy interface and it could be really nice. The time is a problem :) – Kuba Jun 03 '16 at 07:20
  • I'm road testing this now on some documentation for the tests I've been writing. There are a couple of hiccups: I use fairly deep sub-sections, Text immediately under Subsubsection showed up as code, and doesn't pick up inline cells (done as StyleBox[content, "namedStyle"]). The first was easy to adjust to suite, the third likely will be, too, but the second will take some thought. Definitely, +1. – rcollyer Apr 24 '19 at 20:57
  • @rcollyer I am encouraging you to create issues on GitHub. If not I will do this next week or so. I will try to sit at that code soon as an upgrade is an oldtimer on my todo list. – Kuba Apr 24 '19 at 21:18
  • @Kuba will do. But, I thought I'd give you the +1, too. :) – rcollyer Apr 24 '19 at 21:38
  • @rcollyer Thanks a lot :) Nice to hear it can be useful. – Kuba Apr 24 '19 at 21:40