9
$\begingroup$

Suppose I have a Mathematica notebook saved to a certain location on disk. Given a pattern I need to get the list of all expressions matching this pattern that appear in input, output or print cells in the notebook, every expression being wrapped in Hold to prevent its evaluation. Ideally, I also want to extract expressions from cells with erroneous/incomplete input, if the expression itself is complete, but that is not strictly necessary.

How can I do this?

$\endgroup$
1
  • $\begingroup$ Related: (6072) $\endgroup$
    – Mr.Wizard
    Jul 27, 2014 at 8:41

1 Answer 1

11
$\begingroup$

The following functions will load the expressions and erroneous cells from a notebook:

notebookExpressions[path_, pattern_:_] :=
  Cases[Import[path, "Notebook"] // First
  , c:Cell[_, "Input"|"Output"|"Print", ___] :>
      Module[{v = eval[c]}, v /; MatchQ[v, _$Failed | Hold[pattern]]]
  , Infinity
  ]

eval[cell_] :=
  Quiet @ Check[
    ToExpression[First@cell, StandardForm, Hold]
  , $Failed @ cell
  ]

notebookExpressions is the main function. First, it imports the cell expressions from a file. Then it filters out all but the Input, Output and Print cells. For each such cell, it attempts to convert the cell's contents to an expression. If it succeeds, the expression is wrapped in Hold. If it fails, then the original cell expression is wrapped in $Failed. The function eval performs the actual conversion.

To illustrate the use of this function, let's use the following notebook as input:

notebook screenshot

We can load and view the expressions as follows:

$file = "c:\\some\\path\\to\\se-notebook-expressions-test.nb";

$expressions = notebookExpressions[$file];

Column[$expressions, Frame -> All]

notebook expression list

We can see that the expressions are held, and that the cell with an invalid expression is wrapped in $Failed. The text cell was not read, but the input, output and printed cells were.

The valid expressions are readily singled out based upon their Hold wrapper:

Cases[$expressions, _Hold]

(* { Hold[zot[x_]:=x+1], Hold[zot[3]], Hold[4], Hold[Print[{this,is,a,printed,cell}]],
     Hold[{this,is,a,printed,cell}] } *)

We can also print the failed expressions for diagnostic purposes:

Scan[CellPrint @@ # &, Cases[$expressions, _$Failed]]

invalid cell screenshot

By default, notebookExpressions does not filter out any expressions based upon their content. If a second pattern argument is passed, expressions will only be shown if they match that pattern. Invalid cells are always returned since the pattern-matching is inconclusive. Here, we match all cells containing a SetDelayed expression (:=) or errors:

Column[notebookExpressions[$file, _SetDelayed], Frame -> All]

expression list screenshot

Observe that the specified pattern does not specify the Hold wrapper.

Beware that ToExpression has historically sometimes had trouble interpreting box expressions for unusual forms. The "real" parser appears to be unavailable to user code.

$\endgroup$
5
  • $\begingroup$ Good answer. A few thoughts: (1) Your comment above deserves inclusion in the answer. (2) ToHeldExpression is shorter, even if deprecated, and I believe it uses the same conversion rules. (3) Is wrapping an expression in $Failed common practice? Perhaps I'm having another memory glitch but I thought it was usually returned as a bare expression rather than applied as a head. $\endgroup$
    – Mr.Wizard
    Jul 27, 2014 at 8:47
  • $\begingroup$ @WReach Thank you very much! Does naming of some user-defined symbols using $ ($file, $expressions) have some semantic meaning, or stylistic grounds? $\endgroup$ Jul 27, 2014 at 16:18
  • 1
    $\begingroup$ @Mr.Wizard Thanks for the comments. (1) Done. (2) Agreed, I just have the habit of using the long form. (3) It is not common, but I have seen it. It is similar to the Missing idiom. My choice was arbitrary and I was attempting to convey the notion of failure. The wrapper is strictly speaking not necessary -- the only requirement is that the head is different from Hold so that failures can be distinguished. Cell would be good enough. I leave it to the taste of the user. $\endgroup$
    – WReach
    Jul 27, 2014 at 18:47
  • $\begingroup$ @VladimirReshetnikov The $ prefix is purely stylistic. I use it so that global variable names stand out. I've been burnt too many times when I forget that x is bound to something, so I use $x instead. Mathematica uses the same convention for built-in globals, except those variables start with a capital letter after the $. $\endgroup$
    – WReach
    Jul 27, 2014 at 18:50
  • 1
    $\begingroup$ FYI: For v10 or later it seems one should use Failure $\endgroup$
    – Mr.Wizard
    Jul 28, 2014 at 18:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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