0

Note: Spelunking will likely be needed.

Execute the following. You should get back and error along the lines of First::first:... This error is unimportant except it helps illustrate a potential issue with Cases internal code.

Print[Button["Print Cell",
   Cell[BoxData[
     ToBoxes@
      First@Cases[NotebookGet[EvaluationNotebook[]][[1]], 
        Cell[___, CellTags -> "MyCode", ___]]
     ], "Input", CellTags -> "MyGraphic"]
   ]
  ];
CellPrint@
  Cell[BoxData[
    ToBoxes[Cases[NotebookGet[EvaluationNotebook[]], 
      Cell[___, CellTags -> "MyGraphic", ___], Infinity], StandardForm]
    ], "Output", CellTags -> "MyCode"];

Now ultimately although the notebook is outputting an error the Notebook itself should have a valid structure, considering the notebook can be saved and duplicated like so NotebookPut@NotebookGet[EvaluationNotebook[]].

Interestingly if execute the following code in the same Notebook.

Cases[NotebookGet[EvaluationNotebook[]]
 , Cell[__, CellTags -> "MyGraphic"], Infinity]

Cases return the appropriate values and you get back a First::first error.

My question: Why does Cases return a message from a perfectly valid Notebook structure. Other then using Quiet to suppress an error, how might I solve such a problem?

William
  • 7,595
  • 2
  • 22
  • 70
  • You're not calling Cases at the proper level. See the third argument of Cases... use level Infinity – rm -rf Aug 08 '13 at 18:33
  • @rm-rf I'm sorry not sure what you mean Infinity means at calling at every level, yes? – William Aug 08 '13 at 18:35
  • See the difference between Cases[{{Cell["foo", CellTags -> "bar"]}}, Cell[__, CellTags -> "bar", ___]] and Cases[{Cell["foo", CellTags -> "bar"]}, Cell[__, CellTags -> "bar", ___]]... by default Cases operates at level spec {1}, which is why the first returns {} whereas the second returns a cell. In a generic notebook expression, the cell tag rule is at an arbitrary depth, which you may or may not know in advance. It certainly isn't at level {1}. So you use Infinity to tell Cases to look for the pattern at all levels. – rm -rf Aug 08 '13 at 18:40
  • @rm-rf but the 2nd Cases is set to Infinity. Read the question again. My question isn't why the 1st error is outputted, but why a perfectly valid Notebook structure causes the 2nd Cases to return a message in addition to returning the output. Something internally on how Cases is structure is causing the issue. – William Aug 08 '13 at 18:43
  • @rm-rf In the following example the Notebook structure is somehow causing Cases to fail http://pastebin.com/uP7CNrUS – William Aug 08 '13 at 18:47
  • Ok, my apologies for misinterpreting. In any case, the error your seeing with the second example is because Cases evaluates the arguments. Your button is what's causing it to throw an error. While the rest of the notebook is a bunch of inert strings in boxes, the button function is present as an evaluatable expression. When Cases encounters it, it evaluates it and this expression is First@Cases[...], which as I explained earlier, should throw an error. – rm -rf Aug 08 '13 at 18:50
  • @rm-rf I had guessed Cases was evaluating something ;). Might you know of a fix the prevents it from evaluating the expressions and therefore allows the code to work properly. – William Aug 08 '13 at 19:02
  • The "fix" is to use the correct levelspec... As I said, in general you'll not find a cell tag at that level, so this button is designed to generate an error. If you're asking for a version of Cases that doesn't evaluate its leaves, that's a bigger task which possibly deserves its own question. I don't know how to write such a version of Cases off the top of my head, but you should read this answer by Leonid. You might be able to use it to "shield" certain expressions (like ButtonFunction's RHS, for example). – rm -rf Aug 08 '13 at 19:08

2 Answers2

4

The following expression should result in an error:

First@Cases[NotebookGet[EvaluationNotebook[]][[1]], Cell[___, CellTags -> "MyCode", ___]]]

because Cases, by default, operates at level {1} and in a notebook's expression, CellTags will never be at level {1}. Thus, Cases returns {} and First throws an error. The solution here, is to use level Infinity.

Now coming to the second Cases — you've now evaluated the first code block and you have a button in your notebook. This button's expression is:

ButtonBox["\"Print Cell\"", Appearance -> Automatic, 
    ButtonFunction :> Cell[
       BoxData[
           ToBoxes[First[Cases[NotebookGet[EvaluationNotebook[]][[1]], 
               Cell[___, CellTags -> "MyCode", ___]]]
           ]
       ], 
       "Input", CellTags -> "MyGraphic"
   ], Evaluator -> Automatic, Method -> "Preemptive"
]

Note that this expression is stored as an input expression instead of parsed boxform expression. As Cases walks through the tree, it evaluates the leaves and the RHS of ButtonFunction gets evaluated, which is what results in the error (for reasons explained above).

rm -rf
  • 88,781
  • 21
  • 293
  • 472
  • @Liam The "fix" is to use the correct levelspec in the 1st Cases... As I said, in general you'll not find a cell tag at that level, so this button is designed to generate an error. So as long as that incorrect button is present, the 2nd Cases will throw an error. You should also read this answer by Leonid. You might be able to use it to "shield" certain expressions (like ButtonFunction's RHS, for example). – rm -rf Aug 08 '13 at 19:10
2

This "answer", if it can be considered an answer, consists of a mixture of comments and suggestions.


Aside from dealing with levels as rm-rf has shown in another answer, one probably ought to deal with the possibility of Cases returning {}, even if it should not happen. How to deal with it is something you should decide. Here's a way to return Null, if it's of any help:

First[Cases[..] /. {} -> {Null}]

You can avoid the Cell pattern matching itself if, as I surmise, the "MyCode" and "MyGraphics" tags indicate the type of cells. Code and graphics cells start Cell[BoxData[..],..]. So the patterns could be these:

Cell[_BoxData, ___, CellTags -> "MyCode", ___]
Cell[_BoxData, ___, CellTags -> "MyGraphic", ___]

You can get the cells in a notebook, except inline cells, at level 1 in a List, with the following (not extensively tested, however):

Clear[cells];
cells[nb_NotebookObject] := cells[nb, _];
cells[nb_NotebookObject, pat_] := Flatten @ cells[First @ NotebookGet[nb], pat];
cells[cellList_List, pat_] := cells[#, pat] & /@ cellList;
cells[Cell[CellGroupData[group_List, ___]], pat_] := cells[#, pat] & /@ group;
cells[c_Cell, pat_] := If[MatchQ[c, pat], c, {}];
cells[__] := {};

All cells:

cells[EvaluationNotebook[]]

You can even get only the cells matching a pattern:

cells[EvaluationNotebook[],
      _?(MemberQ[Flatten[{CellTags} /. Options[#]], "MyCode"] &)]
Michael E2
  • 235,386
  • 17
  • 334
  • 747
  • This question was a follow up from here http://mathematica.stackexchange.com/questions/30069/alternative-to-notebooklocate-or-notebookfind If you would like you are welcome to post an answer and receive more points (like you really need them) – William Aug 10 '13 at 01:59
  • Genious technique, you are welcome to the following function as an answer at the above question. http://pastebin.com/mvFPqvVY – William Aug 10 '13 at 02:37
  • @Liam Thanks for pointing it out. I've been busy and haven't kept up with all the questions. – Michael E2 Aug 10 '13 at 14:10