This code is far from being complete, but since I promised to show something
this weekend, I decided to publish it anyway.
It provides functions for converting *Mathematica* cells to TeX code
compatible with [mmacells](https://github.com/jkuczm/mmacells) package.
Import["https://raw.githubusercontent.com/jkuczm/MathematicaOptionsUtilities/master/OptionsUtilities/OptionsUtilities.m"]
ClearAll[ruleToMakeStringDef]
ruleToMakeStringDef::usage = "\
ruleToMakeStringDef[lhs -> rhs] or ruleToMakeStringDef[lhs :> rhs] \
assigns definitions to makeString function using given rule."
ruleToMakeStringDef[lhs_ -> rhs_] := (makeString[lhs] = rhs)
ruleToMakeStringDef[lhs_ :> rhs_] := (makeString[lhs] := rhs)
ClearAll[makeStringDefault]
makeStringDefault::usage = "\
makeStringDefault[boxes] \
returns String representation of given boxes. \
This function is called by makeString function, when no user provided BoxRule \
applies."
makeStringDefault[boxes_List] := StringJoin[makeString /@ boxes]
makeStringDefault[BoxData[boxes_]] := makeString[boxes]
ClearAll[makeString]
makeString::usage = "\
makeString[boxes] \
returns String representation of given boxes. \
Definitions for this function are locally created from user provided BoxRules \
on each evaluation of boxesToString function."
makeString[arg_] := makeStringDefault[arg]
ClearAll[boxesToString]
boxesToString::usage = "\
boxesToString[boxes] \
returns String representation of given boxes."
boxesToString::unspt = "FormatType: `1` is not supported."
Options[boxesToString] = Append[Options[ToString], "BoxRules" -> {}]
boxesToString[boxes_, opts : OptionsPattern[]] :=
Internal`InheritedBlock[{makeStringDefault, makeString, ToString},
Switch[OptionValue[FormatType],
InputForm,
makeStringDefault[RowBox[l_List]] := makeString[l];
makeStringDefault[str_String] := str;
makeStringDefault[arg_] := ToString[arg],
OutputForm,
makeStringDefault[RowBox[l_List]] :=
ToString[DisplayForm[RowBox[makeString /@ l]]];
makeStringDefault[str_String] :=
ToString[DisplayForm[RowBox[{str}]]];
makeStringDefault[arg_] := ToString[DisplayForm[arg]],
_,
Message[boxesToString::unspt, OptionValue[FormatType]];
Return[$Failed]
];
ruleToMakeStringDef /@ OptionValue["BoxRules"];
SetOptions[ToString, DelegateOptions[opts, boxesToString, ToString]];
makeString[boxes]
]
ClearAll[boxRulesToBoxPattern]
boxRulesToBoxPattern::usage = "\
boxRulesToBoxPattern[{boxPatt1 -> result1, boxPatt2 :> result2, ...}] \
returns pattern that matches any of boxPatti."
boxRulesToBoxPattern[boxRules_] :=
Alternatives @@ boxRules[[All, 1]] /. Verbatim[Pattern][_, patt_] :> patt
ClearAll[boxesOfTypeQ]
boxesOfTypeQ::usage = "\
boxesOfTypeQ[boxes, type] \
returns True if given boxes contain only subboxes mathcing given pattern \
type. Returns False otherwise."
boxesOfTypeQ[boxes_, type_] := FreeQ[boxes, Except[type], Heads -> False]
ClearAll[$commandChars];
$commandChars::usage = "\
$commandChars \
is a List of three rules. With left hand sides being: eascape character and \
argument delimiters used by TeX formatting commands. Right hand sides are \
strings used to represent escaped commandChars."
$commandChars = {"\\" -> "\\textbackslash{}", "{" -> "\\{", "}" -> "\\}"}
ClearAll[headRuleToBoxRule]
headRuleToBoxRule::usage = "\
headRuleToBoxRule[boxHead -> commandName] \
returns rule that transforms box with given head to TeX formatting command \
with given name."
headRuleToBoxRule[boxHead_ -> command_String] :=
boxHead[boxes___] :>
$commandChars[[1, 1]] <> command <> (
$commandChars[[2, 1]] <> makeString[#] <> $commandChars[[3, 1]]& /@
{boxes}
)
(* Dummy evaluation to laod System`Convert`TeXFormDump` context. *)
Convert`TeX`BoxesToTeX
ClearAll[characterToTeXVerbatimCode]
characterToTeXVerbatimCode::usage = "\
characterToTeXVerbatimCode[\"x\"] \
returns String with TeX code, representing given character, suitable for \
inclusion in Verbatim environment."
characterToTeXVerbatimCode[char_] :=
StringReplace[
StringJoin @ Replace[
System`Convert`TeXFormDump`TextExceptions @
System`Convert`TeXFormDump`TeXCharacters[char]
,
{"$", texStr_, "$"} :>
If[StringFreeQ[texStr, "\\"],
texStr
(* else *),
{"\(", texStr, "\)"}
]
]
,
" " -> ""
]
ClearAll[
$currentValueObj, $basicBoxes, $verbatimCodeBoxRulesLinear,
$verbatimCodeBoxHeadRulesFormatted, $verbatimCodeSubStringRulesFormatted,
$verbatimCodeBoxRulesFormatted
]
$currentValueObj::usage = "\
$currentValueObj \
is a notebook or front end object used as basis for CurrentValue evaluations \
extracting styles needed for conversion of some boxes."
$currentValueObj = Sequence[]
$basicBoxes::usage = "\
$basicBoxes \
is a pattern matching basic elements of box expressions."
$basicBoxes =
_BoxData | _TextData | _RowBox | _String | _List | _Symbol | _Rule |
_Integer | _Real | _Complex
$verbatimCodeBoxRulesLinear::usage = "\
$verbatimCodeBoxRulesLinear \
is a List of rules transforming \"linear\" boxes to TeX Verbatim code."
$verbatimCodeBoxRulesLinear = {
RowBox[l_List] :> makeString[l],
StyleBox[contents_, ___] :> makeString[contents],
ButtonBox[boxes_, ___] :> makeString[boxes],
InterpretationBox[boxes_, __] :> makeString[boxes],
FormBox[boxes_, ___] :> makeString[boxes],
TagBox[boxes_, ___] :> makeString[boxes],
TemplateBox[boxes_, style_, opts___] :>
Module[{displayFunction = Replace[DisplayFunction, {opts}]},
If[displayFunction === DisplayFunction,
displayFunction = Replace[TemplateBoxOptionsDisplayFunction
(* else *),
CurrentValue[$currentValueObj, {StyleDefinitions, style}]]
];
makeString[displayFunction @@ boxes]
]
}
$verbatimCodeBoxHeadRulesFormatted::usage = "\
$verbatimCodeBoxHeadRulesFormatted \
is a List of rules assigning TeX formatting commands to box heads."
$verbatimCodeBoxHeadRulesFormatted = {
SubscriptBox -> "mmaSub",
SuperscriptBox -> "mmaSup",
SubsuperscriptBox -> "mmaSupSup",
UnderscriptBox -> "mmaUnder",
OverscriptBox -> "mmaOver",
UnderoverscriptBox -> "mmaUnderOver",
FractionBox -> "mmaFrac",
SqrtBox -> "mmaSqrt",
RadicalBox -> "mmaRadical"
}
$verbatimCodeSubStringRulesFormatted::usage = "\
$verbatimCodeSubStringRulesFormatted \
is a List of rules transforming substrings to TeX code."
$verbatimCodeSubStringRulesFormatted = {
"\[RightSkeleton]" -> ">>",
char_ /; First@ToCharacterCode[char] > 126 :>
characterToTeXVerbatimCode[char]
}
$verbatimCodeBoxRulesFormatted::usage = "\
$verbatimCodeBoxRulesFormatted \
is a List of rules transforming formatting boxes to TeX code."
$verbatimCodeBoxRulesFormatted =
Join[
$verbatimCodeBoxRulesLinear,
headRuleToBoxRule /@ $verbatimCodeBoxHeadRulesFormatted,
{
str_String :>
StringReplace[
makeStringDefault[str],
Join[$verbatimCodeSubStringRulesFormatted, $commandChars]
]
}
];
ClearAll[linearBoxesQ]
linearBoxesQ::usage = "\
linearBoxesQ[boxes] \
returns True if given boxes contain only \"linear\" subboxes i.e ones that \
don't need formatting commands when exported to TeX. Returns False otherwise."
linearBoxesQ[boxes_] :=
boxesOfTypeQ[
boxes,
$basicBoxes | boxRulesToBoxPattern[$verbatimCodeBoxRulesLinear]
]
ClearAll[boxesToTeXVerbatimCode]
boxesToTeXVerbatimCode::usage = "\
boxesToTeXVerbatimCode[boxes] \
returns TeX verbatim code representing given boxes."
boxesToTeXVerbatimCode::unspt = "Following boxes are not supported: `1`"
Options[boxesToTeXVerbatimCode] = Options[boxesToString];
SetOptions[boxesToTeXVerbatimCode,
"BoxRules" :> $verbatimCodeBoxRulesFormatted
]
boxesToTeXVerbatimCode[boxes_, opts : OptionsPattern[]] :=
Module[{tag},
Catch[
boxesToString[
boxes,
DelegateOptions[
"BoxRules" ->
Append[
OptionValue["BoxRules"],
b:Except[$basicBoxes] :> Throw[b, tag]
]
,
opts,
boxesToTeXVerbatimCode,
boxesToString
]
]
,
tag
,
Function[{unsptBox, tagArg},
Message[boxesToTeXVerbatimCode::unspt, unsptBox];
$Failed
]
]
]
ClearAll[optionValueToTeX]
optionValueToTeX::usage = "\
optionValueToTeX[val] \
returns given TeX option value val suitable for inclusion as value in TeX \
key-value argument."
optionValueToTeX[val_] :=
With[{str = ToString[val]},
If[StringTake[str, 1] === "{" && StringTake[str, -1] === "}" ||
StringFreeQ[str, {"[", "]", ",", "="}]
,
str
(* else *),
"{" <> str <> "}"
]
]
ClearAll[optionsToTeX]
optionsToTeX::usage = "\
optionsToTeX[{key1 -> val1, key2 :> val2, ...}] \
returns String with given rules transformed to TeX key-value pairs."
optionsToTeX[opts : {(Rule | RuleDelayed)[_String, _] ...}] :=
StringJoin[Riffle[(#1 <> "=" <> optionValueToTeX[#2]) & @@@ opts, ","]]
ClearAll[cellDataFromLabel]
cellDataFromLabel::usage = "\
cellDataFromLabel[\"cellLabel\"] \
returns List containing three elements, extracted from given \"cellLabel\": \
cell type (In, Out, or None), \
cell index (Integer or None) and \
cell form (String or None)."
cellDataFromLabel[label_String] :=
Module[{result},
result =
StringCases[
label
,
StartOfString ~~ "In[" ~~ i : DigitCharacter .. ~~ "]:=" ~~
EndOfString :>
{In, ToExpression[i], None}
,
1
];
If[result === {},
result =
StringCases[
label
,
StartOfString ~~ "Out[" ~~ i : DigitCharacter .. ~~ "]" ~~
("//" ~~ form__) | "" ~~ "=" ~~ EndOfString :>
{Out, ToExpression[i], Replace[form, {"" -> None}]}
,
1
]
];
result = Flatten[result];
If[result === {},
{None, None, None}
(* else *),
result
]
]
ClearAll[mmaCellOptionsFromLabel];
mmaCellOptionsFromLabel::usage = "\
mmaCellOptionsFromLabel[\
showCellLabel, cellLabelFromCell, {cellLabel, cellIndex, cellForm}\
] \
returns List of rules representing key-value pairs for inclusion as optional \
argument of mmaCell TeX environment."
mmaCellOptionsFromLabel[
showCellLabel_, cellLabelFromCell_, optVals : {_, _, _}
] :=
Module[
{
cellLabel, cellIndex, cellForm, cellTypeFromCell,
cellIndexFromCell, cellFormFromCell
}
,
{cellLabel, cellIndex, cellForm} = optVals;
{cellTypeFromCell, cellIndexFromCell, cellFormFromCell} =
cellDataFromLabel[cellLabelFromCell];
If[cellTypeFromCell =!= None,
If[cellIndex === Automatic, cellIndex = cellIndexFromCell];
If[cellForm === Automatic, cellForm = cellFormFromCell];
];
If[cellLabel === Automatic,
If[!showCellLabel ||
cellTypeFromCell =!= None &&
cellIndex === cellIndexFromCell &&
cellForm === cellFormFromCell
,
cellLabel = None
(* else *),
cellLabel = cellLabelFromCell
]
];
If[cellIndex === $cellIndex,
cellIndex = None
(* else *),
If[IntegerQ[cellIndex], $cellIndex = cellIndex]
];
DeleteCases[
{"label" -> cellLabel, "index" -> cellIndex, "form" -> cellForm},
_ -> None | Automatic
]
]
ClearAll[$defaultCellStyleBoxRules, $defaultCellStyleFormatType]
$defaultCellStyleBoxRules::usage = "\
$defaultCellStyleBoxRules \
is List of rules assigning BoxRules to particular cell styles."
$defaultCellStyleBoxRules = {
"Code" :> $verbatimCodeBoxRulesLinear,
"Input" | "Output" | "Print" | "Message" :> $verbatimCodeBoxRulesFormatted
}
$defaultCellStyleFormatType::usage = "\
$defaultCellStyleFormatType \
is List of rules assigning String FormatType to particular cell styles."
$defaultCellStyleFormatType = {
"Code" | "Input" :> InputForm,
"Output" | "Print" | "Message" :> OutputForm
}
ClearAll[cellContentsToTeX]
cellContentsToTeX::usage = "\
cellContentsToTeX[contents, style] \
returns String with TeX mmaCell environment representing cell with given \
style and contents."
cellContentsToTeX::unspt = "Cell style: `1` is not supported.";
Options[cellContentsToTeX] =
Join[
Options[boxesToTeXVerbatimCode],
{
"StyleBoxRules" :> $defaultCellStyleBoxRules,
"StyleFormatType" :> $defaultCellStyleFormatType,
"MmaCellOptions" -> {},
"Indentation" -> " "
}
];
SetOptions[cellContentsToTeX,
"BoxRules" -> Automatic, FormatType -> Automatic
]
cellContentsToTeX[contents_List, style:_String, opts:OptionsPattern[]] :=
Module[
{
boxRules = OptionValue["BoxRules"],
formatType = OptionValue["FormatType"],
mmaCellOptions = OptionValue["MmaCellOptions"]
}
,
If[boxRules === Automatic,
boxRules = Replace[style, OptionValue["StyleBoxRules"]]
];
If[formatType === Automatic,
formatType = Replace[style, OptionValue["StyleFormatType"]]
];
If[boxRules === style || formatType === style,
Message[cellContentsToTeX::unspt, style];
Return[$Failed]
];
If[Length[ mmaCellOptions] > 0,
mmaCellOptions = "[" <> optionsToTeX[mmaCellOptions] <> "]"
];
StringJoin[
"\\begin{mmaCell}", mmaCellOptions, "{", style, "}",
StringReplace[
StringJoin[
"\n"
,
boxesToTeXVerbatimCode[
#,
DelegateOptions[
FormatType -> formatType,
"BoxRules" -> boxRules, opts, cellContentsToTeX,
boxesToTeXVerbatimCode
]
]& /@
contents
]
,
"\n" | "\[IndentingNewLine]" ->
"\n" <> OptionValue["Indentation"]
]
,
"\n\\end{mmaCell}"
]
]
cellContentsToTeX[BoxData[boxes_], style:_String, opts : OptionsPattern[]] :=
cellContentsToTeX[boxes, style, opts]
cellContentsToTeX[boxes_, style:_String, opts:OptionsPattern[]] :=
cellContentsToTeX[{boxes}, style, opts]
ClearAll[
$cellIndex, $previousIntype, $defaultIndexedStyles, $defaultIntypeStyles
]
$cellIndex = 0
$previousIntype = False
$defaultIndexedStyles = "Code" | "Input" | "Output"
$defaultIntypeStyles = "Code" | "Input"
ClearAll[cellToTeX]
cellToTeX::usage = "\
cellToTeX[cell, texStyle] \
returns String with TeX mmaCell environment representing given cell as cell \
with given texStyle. \
cellToTeX[cell] \
chooses texStyle based on cell style."
Options[cellToTeX] =
Join[
Options[cellContentsToTeX],
{
"CellLabel" -> Automatic, "CellIndex" -> Automatic,
"CellForm" -> Automatic,
"Indexed" -> Automatic, "IndexedStyles" :> $defaultIndexedStyles,
"IntypeStyles" :> $defaultIntypeStyles
}
];
cellToTeX[
Cell[BoxData[boxes_], styles__String, cellOpts___?OptionQ],
texStyle : _String | Automatic : Automatic, opts : OptionsPattern[]
] :=
Module[
{
style, indexed, mmaCellOptions, cellOptsList, cellLabelFromCell,
showCellLabel, finalTeXStyle, intype
}
,
{indexed, mmaCellOptions} = OptionValue[{"Indexed", "MmaCellOptions"}];
style = First[{styles}];
cellOptsList = Flatten[{cellOpts}];
cellLabelFromCell = Replace[CellLabel, {cellOpts}];
showCellLabel = Replace[ShowCellLabel, {cellOpts}];
finalTeXStyle = Replace[texStyle, Automatic -> style];
If[indexed === Automatic,
indexed = MatchQ[style, OptionValue["IndexedStyles"]]
];
If[indexed,
intype = MatchQ[finalTeXStyle, OptionValue["IntypeStyles"]];
If[intype || ! $previousIntype, $cellIndex++];
$previousIntype = intype
];
If[showCellLabel === ShowCellLabel,
showCellLabel =
CurrentValue[$currentValueObj,
{StyleDefinitions, style, ShowCellLabel}
]
];
If[cellLabelFromCell =!= CellLabel,
mmaCellOptions =
Join[
mmaCellOptionsFromLabel[
showCellLabel, cellLabelFromCell,
OptionValue[{"CellLabel", "CellIndex", "CellIndex"}]
],
mmaCellOptions
];
];
cellContentsToTeX[
boxes, finalTeXStyle,
DelegateOptions[
"MmaCellOptions" -> mmaCellOptions, opts,
cellToTeX, cellContentsToTeX
]
]
]
----------
# Usage examples
### Single cell
testCell =
Cell[
BoxData@ToBoxes[
Subscript[x, 1] == (-b \[PlusMinus] Sqrt[b^2 - 4 a c])/(2 a)
],
"Input",
CellLabel -> "In[153]:="
];
testCell // CellPrint
cellToTeX[testCell]
(*
\begin{mmaCell}[index=153]{Input}
\mmaSub{x}{1}==\mmaFrac{-b\(\pm\)\mmaSqrt{\mmaSup{b}{2}-4 a c}}{2 a}
\end{mmaCell}
*)
### Whole notebook
Create a notebook with some evaluated cells:
nbObj = CreateDocument[{
Cell[BoxData@MakeBoxes[Solve[a x^2 + b x + c == 0, x]], "Input"],
Cell[BoxData[
Append[
List @@ MakeBoxes /@ HoldComplete[
Print["a \" string with double quotes inside"],
Sin[m, n]
],
RowBox[{RowBox[{"1", "+", RowBox[{"2", " ", "x"}]}], "//", "FullForm"}]]
],
"Input"
]
}];
(* Switch off auto deleting of labels, so that we can extract some data from them. *)
CurrentValue[nbObj, CellLabelAutoDelete] = False;
SelectionMove[nbObj, All, Notebook];
SelectionEvaluate[nbObj];
Export this notebook to TeX:
ExportString[
(* Use ordinary replacement instead of ConversionRules since the latter
don't have access to CellLabel. *)
NotebookGet[nbObj] /. {
cell : Cell[BoxData[boxes_?linearBoxesQ], "Input", ___] :>
Cell[cellToTeX[cell, "Code"], "Final"],
cell : Cell[_, "Input" | "Output" | "Print" | "Message", ___] :>
Cell[cellToTeX[cell], "Final"]
}
,
"TeXFragment",
"ConversionRules" -> {"Final" -> Identity}
]
(*
\begin{mmaCell}[index=10]{Input}
Solve[a \mmaSup{x}{2}+b x+c==0,x]
\end{mmaCell}
\begin{mmaCell}{Output}
\{\{x\(\to\)\mmaFrac{-b-\mmaSqrt{\mmaSup{b}{2}-4 a c}}{2 a}\},\{x\(\to\)\mmaFrac{-b+\mmaSqrt{\mmaSup{b}{2}-4 a c}}{2 a}\}\}
\end{mmaCell}
\begin{mmaCell}{Code}
Print["a \" string with double quotes inside"]
Sin[m,n]
1+2 x//FullForm
\end{mmaCell}
\begin{mmaCell}{Print}
a " string with double quotes inside
\end{mmaCell}
\begin{mmaCell}{Message}
Sin::argx: Sin called with 2 arguments; 1 argument is expected. >>
\end{mmaCell}
\begin{mmaCell}[index=12]{Output}
Sin[m,n]
\end{mmaCell}
\begin{mmaCell}[form=FullForm]{Output}
Plus[1,Times[2,x]]
\end{mmaCell}
*)
----------
# TODO
* Code annotations reflecting syntax highlighting ([almost finished](https://i.sstatic.net/GfDZF.png)).
* Handling of inline cells.
* Handling of graphics.
* ...