17
$\begingroup$

I would like a tool to convert any Mathematica notebook into a Jupyter labs notebook, preserving as much content as possible (.ipynb files are just special json). Obviously, this would need to rasterize output cells and possibly cull any other non-textual elements wherever needed.

$\endgroup$
11
+125
$\begingroup$

The main point is that Jupyter is a flat structure, but the Notebook is a nested structure. It took me a long time to fully solve this problem.

Thanks for @b3m2a1 's advice, text problems has reduced.

Now there still some problem:

  • Picture rendering has incomprehensible edges
  • More issues not found, put test files at GalAster/JupyterConvert if find any.

Test file can be downloaded from: Test/Notebook2Jupyter/code.nb

enter image description here

(* ::Package:: *)

Notebook2Jupyter::usage = "";

Begin["`Private`"];
JupyterInputCell::usage = "";
JupyterCodeCell::usage = "";
JupyterMarkdownCell::usage = "";
JupyterRawCell::usage = "";


Options[Notebook2Jupyter] = {};
Notebook2Jupyter[nb_NotebookObject, o : OptionsPattern[]] := Block[
    {jp = $JupyterTemplate, parsed, cells},
    parsed = Flatten[parseCell /@ Cells[nb]];
    cells = SequenceSplit[parsed, {
        {text__JupyterMarkdownCell} :> JupyterMarkdownBuild[First /@ {text}],
        {in_JupyterInputCell, other___JupyterCodeCell} :> JupyterCodeBuild[First /@ {in, other}]
    }];
    jp["cells"] = cells;
    Return@jp;
];
Notebook2Jupyter[nb_NotebookObject, path_String, o : OptionsPattern[]] := Block[
    {jp = Notebook2Jupyter[nb, o]},
    File@Export[path, jp, "JSON"]
];


JupyterMarkdownBuild[text_List] := <|
    "cell_type" -> "markdown",
    "source" -> StringRiffle[text, "\n\n"]
|>;
JupyterCodeBuild[{code_}] := <|
    "cell_type" -> "code",
    "source" -> code
|>;
JupyterCodeBuild[{code_, out_}] := <|
    "cell_type" -> "code",
    "source" -> code,
    "outputs" -> {
        <|
            "output_type" -> "execute_result",
            "data" -> out
        |>
    }
|>;
JupyterCodeBuild[{code_, print__, out_}] := <|
    "cell_type" -> "code",
    "source" -> code,
    "outputs" -> Flatten@{
        <|"name" -> "stdout", "output_type" -> "stream", "text" -> #|>& /@ {print},
        <|
            "output_type" -> "execute_result",
            "data" -> out
        |>
    }
|>;


(* ::Chapter:: *)
(*Cell*)


(* ::Section:: *)
(*Template*)


$JupyterTemplate = <|
    "metadata" -> <||>
|>;


(* ::Section:: *)
(*Default*)


parseCell[co_CellObject] := parseCell[NotebookRead[co], co];
parseCell[c_Cell, co_CellObject] := parseCell[#2, #, co]& @@ c;
parseCell[s_, o___] := (
    Echo[Inactive[parseCell][s, o], "Todo: "];
    JupyterMarkdownCell@TemplateApply["[//]: # (No rules defined for ``)\n\n", {s}]
);


(* ::Section:: *)
(*Normal*)


parseCell["Title", data_, co_CellObject] := JupyterMarkdownCell["# " <> parseData@data];
parseCell["Subtitle", data_, co_CellObject] := JupyterMarkdownCell["## " <> parseData@data];
parseCell["Chapter", data_, co_CellObject] := JupyterMarkdownCell["### " <> parseData@data];
parseCell["Section", data_, co_CellObject] := JupyterMarkdownCell["#### " <> parseData@data];
parseCell["Subsection", data_, co_CellObject] := JupyterMarkdownCell["##### " <> parseData@data];
parseCell["Subsubsection", data_, co_CellObject] := JupyterMarkdownCell["###### " <> parseData@data];


parseCell["Text", data_, co_CellObject] := JupyterMarkdownCell[parseData@data];
parseCell["WolframAlphaShort", data_String, co_CellObject] := JupyterMarkdownCell[data];


(* ::Section:: *)
(*Code*)


toASCII[a_] := StringTake[ToString[a, InputForm, CharacterEncoding -> "ASCII"], {10, -2}];
parseCell["Input", boxes_, co_CellObject] := Block[
    {expr = MakeExpression[Cell@boxes, StandardForm], out},
    out = expr //. {
        HoldComplete[ExpressionCell[{a___, Null, b___}]] :> StringJoin[toASCII[HoldForm@a], ";\n", toASCII[HoldForm@b]],
        HoldComplete[ExpressionCell[a_]] :> toASCII[HoldForm@a]
    };
    JupyterInputCell[out]
];
parseCell["Print", boxes_, o___] := JupyterCodeCell[First@MathLink`CallFrontEnd[ExportPacket[Cell@boxes, "PlainText"]]];
parseCell["Echo", data___] := parseCell["Print", data];
parseCell["Message", data___] := parseCell["Print", data];
parseCell["Output", boxes_, co_CellObject] := Block[
    {dump = First@MathLink`CallFrontEnd[ExportPacket[Cell@boxes, "PlainText"]]},
    JupyterCodeCell@If[
        dump == "",
        <|"image/png" -> ExportString[Rasterize[co, Background -> None], {"Base64", "PNG"}, Background -> None]|>,
        <|"text/plain" -> dump|>
    ]
];


(* ::Section:: *)
(*TeX*)


boxesToTeX = ToString[ToExpression@#, TeXForm] &;
parseCell["Output", BoxData[FormBox[boxes_, TraditionalForm]], cellObj_CellObject] := TemplateApply["$$``$$\n\n", {boxesToTeX@boxes}];


(* ::Section:: *)
(*Pass*)



parseCell["Code", data___] := {};
parseCell[$Failed, data___] := {};


(* ::Chapter:: *)
(*Data*)


parseData[list_List] := parseData /@ list;
parseData[string_String] := string;
parseData[cell_Cell] := parseData@First@cell;
parseData[boxes_] := (
    Echo[Inactive[parseData][boxes], "Todo: "];
    parseData@First@boxes
);


parseData[data_BoxData] := List @@ (parseData /@ data);
parseData[data_TextData] := List @@ (parseData /@ data);


parseData[TemplateBox[{text_String, link_String}, "HyperlinkURL"]] := TemplateApply["[``](``)", {text, link}];




End[]
|improve this answer|||||
$\endgroup$
  • 1
    $\begingroup$ I think rather than ToString[ToExpression[...]] you'll have better luck with MathLink`CallFrontEnd[ExportPacket[boxes, "PlainText"] although "InputText" might also be a good approach. I think that can automatically go to "TeX" as well... I do a lot of this in my NotebookToMarkdown package. $\endgroup$ – b3m2a1 Dec 8 '19 at 19:15
8
$\begingroup$

This nb2ipynb function takes the notebook filename as input and it returns a jupyter notebook compatible with JWLS.

nb2ipynb = Module[{

  cellF = {
   "cell_type" -> "code",
   "execution_count" -> 1,
   "metadata" -> <||>,
   "outputs" -> {<|"name" -> "stdout",
                   "output_type" -> "stream",
                   "text" -> StringRiffle[ToString /@ {##2}, "\n"]|>},
   "source" -> ToString /@ List @@ HoldForm /@ (#1/.Null -> "\n\n")
  }&,

 ipynbF = {
   "cells" -> cellF @@@ #,
   "metadata" -> {"kernelspec" -> {"display_name" -> "JWLS_2",
                                   "language" -> "text",
                                   "name" -> "jwls_2"},
                  "language_info" -> {"codemirror_mode" -> "mathematica",
                                      "file_extension" -> ".wl",
                                      "mimetype" -> "text/x-mathematica",
                                      "name" -> "WolframScript"}},
   "nbformat" -> 4,
   "nbformat_minor" -> 2
  }&,

 nb = NotebookImport[# _,"FlattenCellGroups" -> False]~
      DeleteCases~ HoldComplete[Null]},

Export[FileBaseName@#<>".ipynb", ipynbF@nb, "JSON"]
]&

I haven't try it with the official WRI kernel but I guess you simply need to modify the metadata field.

|improve this answer|||||
$\endgroup$

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.