36
$\begingroup$

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!

$\endgroup$
  • 1
  • 6
    $\begingroup$ 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? $\endgroup$ – M.R. May 27 '15 at 21:15
  • 8
    $\begingroup$ I'd use the more politically correct 'suboptimal' ;-) $\endgroup$ – Sjoerd C. de Vries May 27 '15 at 21:20
  • 2
    $\begingroup$ In R it's very elegant and straightforward. See RMarkdown $\endgroup$ – Murta May 29 '15 at 12:49
  • 1
    $\begingroup$ It would suffice to export the cell group structure to json, anyone know how to do that? $\endgroup$ – M.R. Oct 6 '15 at 16:12
36
+200
$\begingroup$

Edit 25.05.2018

I refactored the code to a paclet and put it on GitHub: https://github.com/kubaPod/M2MD

So if it gets updated it will be there and here only the original code is kept.

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[

      "$$``$$\n\n", {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;

      "$" <> teXForm <> "$"

      ];



    parseData[

      box : ButtonBox[_, ___, BaseStyle -> "Hyperlink", ___]] := 

     Module[{label, url},

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



      TemplateApply[

       "[``](``)", {StringJoin@Flatten@{parseData@label}, url}]



      ];



    (*default behaviour for boxes*)

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



    (*default behaviour for cell styles*)

    exportMD[s_, ___] := 

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



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

    )

  ]
$\endgroup$
  • $\begingroup$ Idle thought: what if TraditionalForm[] objects are the ones converted to $\LaTeX$ (that is, TraditionalForm[Sqrt[z]] becomes $\sqrt z$, perhaps through TeXForm[])? $\endgroup$ – J. M. is away Nov 22 '15 at 3:28
  • $\begingroup$ 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. $\endgroup$ – M.R. Nov 22 '15 at 6:47
  • $\begingroup$ And maybe comment the stuff it can't parse. $\endgroup$ – M.R. Nov 22 '15 at 9:24
  • $\begingroup$ Have you seen this (by R.M.) already? $\endgroup$ – J. M. is away Nov 23 '15 at 9:00
  • 2
    $\begingroup$ 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. $\endgroup$ – Ronald Monson Jan 1 '16 at 8:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.