11
$\begingroup$

Suppose I have a notebook with cells tagged with both numbers and words with numbers always list first, and some cells without cell tags e.g.:

Notebook[{
Cell[ ..., CellTags->{"1","cat"}],
Cell[ ..., CellTags->{"2","dog"}],
...
Cell[ ...],
Cell[ ..., CellTags->{"7","cow"}],
...
}, notebook options ...]

What is the easiest way to sort the cells based on 1) cell tag numbers, i.e. the first element in the cell tag list; and 2) cell tag words, i.e. the second element in the cell tag list?

Note that there could be additional tags but the first and second tags are always number and single word. Note also there there is unlikely to be grouped cells (none at this stage), although a solution that took cell grouping into account and rendered a sorted ungrouped answer would be fine. I want a fast way to sort a notebook and render the newly sorted form. I'm thinking ideally I'd like a palette button for this.

$\endgroup$
3
  • 2
    $\begingroup$ I am generally useless at this kind of programmatic Notebook manipulation. I appreciate questions like this because I learn a lot from the answers. $\endgroup$
    – Mr.Wizard
    Commented Feb 23, 2012 at 12:44
  • $\begingroup$ I might have another look at my answer today. I'm thinking that Cases is quite repetitive. Maybe just use it once on the content and then use Ordering would be more efficient. $\endgroup$ Commented Feb 23, 2012 at 20:51
  • $\begingroup$ I definitely didn't read this comment before I wrote my answer, but that's effectively what I did. Crumbs! $\endgroup$ Commented Nov 26, 2014 at 10:30

3 Answers 3

4
$\begingroup$

This is actually something I am working on for real at the moment. This is what i have got so far:

nb = NotebookOpen["path/to/notebook.nb"];
SelectionMove[nb, All, Notebook];
content = NotebookRead[nb];
NotebookDelete[nb];

For searching based on the number listed as the first tag:

sorted = SortBy[content, 
 Cases[#, HoldPattern[
     Rule[CellTags, {x_ /; StringMatchQ[x, NumberString], __}]] :> 
    ToExpression@x] &]

and for searching based on the word in the second tag:

sorted = SortBy[content, 
 Cases[#, HoldPattern[Rule[CellTags, {_, x_, __}]] :> x] &]

With both of these methods the untagged cells are returned first ...which is ok. Then

Scan[NotebookWrite[nb, #] &, sorted];
NotebookSave[nb];
NotebookClose[nb];

This is a little bit slow but seems to be doing the job.

Edit

So this code seems to be doing the job on the notebooks i needed to use this with. Would still be interested in suggestions from others.

ClearAll[sortNotebook];

Options[sortNotebook] = {"sortby" -> "Number"};

sortNotebook[file_String, OptionsPattern[]] := 
 Module[{nb, opt = OptionValue["sortby"], sorted},

  nb = NotebookOpen[file];
  SelectionMove[nb, All, Notebook];
  content = NotebookRead[nb];
  NotebookDelete[nb];
  Which[
   opt === "Number", 
   sorted = 
    SortBy[content, 
     Cases[#, 
       HoldPattern[
         Rule[CellTags, {x_ /; StringMatchQ[x, NumberString], __}]] :>
         ToExpression@x] &],
   opt === "Task", 
   sorted = 
    SortBy[content, 
     Cases[#, HoldPattern[Rule[CellTags, {_, x_, __}]] :> x] &],
   opt =!= "Number" && opt =!= "Task", 
   Print["you messed up with the option names"]
   ];
  Scan[NotebookWrite[nb, #] &, sorted];
  NotebookSave[nb];
  NotebookClose[nb];
  ]
$\endgroup$
2
$\begingroup$

I was searching for a way to reorder Sections alphabetically and found this question, which I think I've got an answer for.

It relies on evaluating the code in a separate notebook, targeting the notebook with your cell tags. In my target notebook (targetNb) I have the following cells:

Cell["Typed Cell 1", "Item", CellTags -> {"1", "Zebra", "2"}], 
Cell["Typed Cell 2", "Item", CellTags -> {"2", "cat", "3"}],
Cell["Typed Cell 3","Item", CellTags -> {"4", "bat", "6"}],
Cell["Typed Cell 4", "Item", CellTags -> {"4", "aardvark", "8"}]

I built the following function:

reOrderTaggedItems[nb_] := Block[{allCells = Cells[nb],targetCellTags,targetCells,orderedCells},
targetCellTags =Cases[NotebookRead /@ allCells, 
Cell[__, CellTags -> q : {_, word_, __}] :> q];
targetCells = allCells[[Flatten@Position[NotebookRead /@ allCells,Cell[__, CellTags -> {_, word_,__}]]]];
orderedCells =NotebookRead /@ targetCells[[Ordering[targetCellTags[[All, 2]]]]];
Table[NotebookWrite[targetCells[[i]], orderedCells[[i]]], {i, 4}]]

Applying the function reorders my cells:

In[1]:=reOrderTaggedItems[targetNb]
Out[1]=Cell["Typed Cell 4", "Item", CellTags -> {"4", "aardvark", "8"}], 
Cell["Typed Cell 3", "Item", CellTags -> {"4", "bat", "6"}], 
Cell["Typed Cell 2", "Item", CellTags -> {"2", "cat", "3"}], 
Cell["Typed Cell 1", "Item", CellTags -> {"1", "Zebra", "2"}]

I'm currently looking into doing this for Section cells, but correctly handling the CellGroupData which will be slightly trickier.

$\endgroup$
2
$\begingroup$

My answer is based a on Martin John Hadley's. It uses Cells so one need at least V9 for that. But it is quite compact:

cellSortByTag[nb_, tagOp_: ToExpression] := Composition[
    (SelectionMove[#, Cell, All]; NotebookWrite[nb, #2]) & @@@ # &,
    Transpose,
    { #, 
      NotebookRead /@ SortBy[#, tagOp @ CurrentValue[#, CellTags] &]} &

    ][Cells[nb]];


Now you can test it:

NB = Array[ Cell[BoxData[ToBoxes[#]], "Input", CellTags -> {ToString[11 - #]}] &,
            {10}
          ] // CreateDocument[#, ShowCellTags -> True] &;

You can evaluate this even inside NB:

 cellSortByTag[ EvaluationNotebook[] ];

If outside, you have to put proper NotebookObject of course. Moreover it will only affect those tagged Cells so you may have whatever else you want there.

If you have more tags, just add second arg to handle sorting, e.g.:

cellSortByTag[EvaluationNotebook[], Last]
$\endgroup$
2
  • $\begingroup$ Composition, @@@, and arbitrary tag patterns??? Way to make a man feel lazy. Your solution even handles the annoying way that singular CellTags aren't put in Lists. +1 $\endgroup$ Commented Nov 26, 2014 at 11:43
  • $\begingroup$ @MartinJohnHadley Unfortunately NotebookWrite alone is not enough. It works but it has some side effects, not obvious, due to incorrect usage. I had to add some things. $\endgroup$
    – Kuba
    Commented Nov 29, 2014 at 13:52

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.