(* :Title: Corruption.m *) (* :Context: ` *) (* :Author: Lou D'Andria *) (* :Summary: The Corruption package attempts to fix corrupted notebooks. *) (* :Copyright: 1997 by Lou D'Andria *) (* :Package Version: 0.72 Alpha *) (* :Mathematica Version: 3.0 *) (* :History: + Started life as OpenCorruptedNotebooks.nb (97/01) + Then became OpenCorruptedNotebooks.m (97/01) + Then because Corruption.m (97/02/27) + Added DeleteTypesetCells and DeleteGraphicCells options at the suggestion of StanWagon (97/06/17) + Worked around a bug in SetStreamPosition reported by Xah Lee (97/09/10) *) (* :Keywords: Notebook, FrontEnd, Corruption *) (* :Sources: *) (* :Warnings: *) (* :Limitations: + Notebook options are lost, including style sheet. + Comments at the top and bottom of notebook are lost, including cache information. + Uses raw strings to determine where to break cells, which has the possibility of being fooled by notebooks containing certain strings. *) (* :Discussion: *) (* :Requirements: *) (* :Examples: *) (* set up the package context, including public imports *) BeginPackage["Corruption`"] (* usage messages for the exported functions and the context itself *) Unprotect[OpenCorruptedNotebook, SaveCorruptedNotebook]; ClearAll[OpenCorruptedNotebook, SaveCorruptedNotebook]; OpenCorruptedNotebook::usage = "OpenCorruptedNotebook[file.nb] creates a \ new notebook window in the front end, and creates a copy of file.nb with \ any corrupted parts highlighted in red text cells with cell tag \"Corrupt\"."; SaveCorruptedNotebook::usage = ""; ShowProgress::usage = "ShowProgress is an option for OpenCorruptedNotebook \ that determines whether a cell counter, with some indication of whether the \ current cell is corrupt or not, is displayed. Default: True."; OpenPalette::usage = "OpenPalette is an option for OpenCorruptedNotebook \ that determines whether to open a palette with a button that will allow you \ to jump from one \"Corrupt\" cell to the next."; PutCellsAsCreated::usgae = "PutCellsAsCreated is an option for \ OpenCorruptedNotebook that determines whether the cells will be written to \ the untitiled notebook as they are found, or whether they will be all saved \ up until the end, and put all at once. It doesn't affect the cells at all, \ it's matter of opinion and occasionally helps in finding parsing bugs."; DeleteGraphicsCells::usage = "DeleteGraphicsCells is an option for \ OpenCorruptedNotebook that determines whether graphics cells will be \ deleted. If True, then all graphics cells will be replaced \ by a blue text cell denoting how large the cell was, with cell tag \ \"GraphicsDeleted\". Default: True. See also: DeleteTypesetCells."; DeleteTypesetCells::usage = "DeleteTypesetCells is an option for \ OpenCorruptedNotebook that determines whether typeset cells will be \ deleted. If True, then all typeset cells will be replaced \ by a green text cell denoting how large the cell was, with cell tag \ \"TypesettingDeleted\". Default: False. See also: DeleteGraphicsCells."; Options[OpenCorruptedNotebook] = {ShowProgress->True, OpenPalette->"Not Hooked Up", PutCellsAsCreated->True, DeleteGraphicsCells->True, DeleteTypesetCells->False}; Options[SaveCorruptedNotebook] = {ShowProgress->False}; (********************************************************************) Begin["`Private`"] currentProgress[opts___] := Block[{total = $GoodCellCounter + $BadCellCounter}, If[$PreviousCellCount < total, If[$CurrentCell==="good", WriteString["stdout","[",total,"]"], WriteString["stdout","*",total,"*"] ]; $ProgressLength += StringLength[ToString[total]]+2; If[$ProgressLength > 55,WriteString["stdout","\n"];$ProgressLength=0]; $PreviousCellCount = total; ] ]; combineLines[lis_] := StringJoin @ Drop[#,-1]& @ Flatten[{#,"\n"}& /@ lis] groupingDataQ[lis_] := Module[{lis2}, lis2 = DeleteCases[lis, "" | "\n" | "Notebook[{" | "Cell[CellGroupData[{" | "}, Open]]" | "}, Open]]," | "}, Open ]]" | "}, Open ]]," | "}, Closed]]" | "}, Closed]],"]; If[lis2==={}, True, False] ] createBadCell[lis_] := (* if the info is just grouping info, there's no error*) If[groupingDataQ[lis], {}, $BadCellCounter += 1; $CurrentCell="bad"; Cell[combineLines[lis],"Text", FontFamily->"Courier", FontWeight->"Bold", FontColor->RGBColor[1,0,0], CellTags->"Corrupt"] ]; checkSyntax[lis_] := Module[{str = combineLines[lis],cellstr, cell, synlen}, synlen = SyntaxLength[str]; If[synlen > StringLength[str], createBadCell[lis], (*otherwise, there's a chance the syntax is ok*) cellstr = StringTake[str,SyntaxLength[str]]; cell = ToExpression[cellstr]; If[cell===$Failed || Head[cell] =!= Cell, createBadCell[lis], $GoodCellCounter += 1; $CurrentCell="good"; cell ] ] ]; startingLineQ[str_] := StringMatchQ[str, "Cell[*"] && Not[StringMatchQ[str,"Cell[CellGroupData[{*"]]; endingLineQ[str_] := Or[ StringMatchQ[str, "Cell[*"], StringMatchQ[str, "Cell[CellGroupData[{*"], StringMatchQ[str, "}, Open]]*"], StringMatchQ[str, "}, Open ]]*"], StringMatchQ[str, "}, Closed]]*"], StringMatchQ[str, "},"], StringMatchQ[str, "Cached data follows*"] ]; moveToStartingLine[st_] := Module[{temp="", pos}, While[temp=!=EndOfFile && Not[startingLineQ[temp]], pos = StreamPosition[st]; temp = Read[st,String] ]; If[temp =!= EndOfFile, SetStreamPosition[st, pos] ]; ]; getNextCell[st_] := Module[{celllis, temp, pos, c}, (* read the first, good line off the stream immediately *) celllis = {Read[st,String]}; pos = StreamPosition[st]; temp = Read[st, String]; (* if the first line indicates a graphics cell or typeset cell follows, then simply keep count of the lines as they pass by, provided that the $Delete*CellsQ symbol is True *) If[($DeleteGraphicsCellsQ && StringMatchQ[First @ celllis,"Cell[GraphicsData[*"]) || ($DeleteTypesetCellsQ && StringMatchQ[First @ celllis,"Cell[BoxData[*"]), c=1; While[temp =!= EndOfFile && Not[endingLineQ[temp]], c++; pos = StreamPosition[st]; temp = Read[st,String] ], (* otherwise, keep reading until you encounter an endingLine *) While[temp=!=EndOfFile && Not[endingLineQ[temp]], celllis = Join[celllis, {temp}]; pos = StreamPosition[st]; temp = Read[st,String] ] ]; (* replace celllis with a placeholder if the user wants typeset or graphics cells removed *) If[$DeleteGraphicsCellsQ && StringMatchQ[First @ celllis,"Cell[GraphicsData[*"], celllis = {"Cell[\"This was a graphics cell with "<> ToString[c] <> " lines.\", \"Text\", FontColor->RGBColor[0,0,1],"<> "CellTags->\"GraphicsDeleted\"]" }]; If[$DeleteTypesetCellsQ && StringMatchQ[First @ celllis,"Cell[BoxData[*"], celllis = {"Cell[\"This was a typeset cell with "<> ToString[c] <> " lines.\", \"Text\", FontColor->RGBColor[0,1,0],"<> "CellTags->\"TypesettingDeleted\"]" }]; (* "Cached data follows" is the marker for the end of usable cells *) If[temp=!=EndOfFile && StringMatchQ[temp,"Cached data follows*"], SetStreamPosition[st, Infinity]; (* There's a bug in the 3.0.1 kernel, fixed in 3.1 that prevents SetStreamPosition[st,Infinity] from setting the position to the end of the file for some (large) files. In those cases, the following line is also needed. In the non-buggy cases, the following won't slow things up that much. *) While[Read[st, String] =!= EndOfFile]; temp = EndOfFile ]; If[temp === EndOfFile, createBadCell[celllis], SetStreamPosition[st,pos]; checkSyntax[celllis] ] ]; openPalette[] := Null; OpenCorruptedNotebook[file_, opts___] := Module[{st, cellList, pos, temp, progressQ, putQ, openQ, nb}, If[Head[$FrontEnd] =!= FrontEndObject, Abort[]]; $GoodCellCounter = 0; $BadCellCounter = 0; $PreviousCellCount = 0; $ProgressLength = 0; {$DeleteGraphicsCellsQ, $DeleteTypesetCellsQ} = {DeleteGraphicsCells, DeleteTypesetCells} /. {opts} /. Options[OpenCorruptedNotebook]; If[(st = OpenRead[file])===$Failed, Abort[]]; cellList = {}; {progressQ, putQ, openQ} = {ShowProgress, PutCellsAsCreated, OpenPalette} /. {opts} /. Options[OpenCorruptedNotebook]; (*skip the comment at the top of the notebook*) moveToStartingLine[st]; If[putQ, nb = NotebookCreate[]]; While[(pos = StreamPosition[st]; temp = Read[st,String]; SetStreamPosition[st,pos]; temp) =!= EndOfFile, If[putQ, NotebookWrite[nb, getNextCell[st]], cellList = Join[cellList,{getNextCell[st]}] ]; If[progressQ, currentProgress[]]; ]; Close[st]; If[openQ, openPalette[]]; If[!putQ, nb = NotebookCreate[]; NotebookPut[Notebook @ Flatten @ cellList,nb]]; {ToString[$GoodCellCounter]<>" good cells", ToString[$BadCellCounter] <>" bad cells", nb} ]; (*********************************************** SaveCorruptedNotebook[infile_,outfile_, opts___] := Module[{inst, outst, lis, pos, temp, progressQ}, counter = 2 + Length[FindList[infile, "Cell[", AnchoredSearch->True]]; inst = OpenRead[infile]; outst = OpenWrite[outfile, FormatType->OutputForm]; progressQ = ShowProgress /. {opts} /. Options[SaveCorruptedNotebooks]; removeJunk[inst]; Write[outst, "Notebook[{"]; While[(pos = StreamPosition[inst]; temp = Read[inst,String]; SetStreamPosition[inst,pos]; temp) =!= EndOfFile, If[temp =!= {}, SetOptions[outst, FormatType->InputForm]; Write[outst, getNextCell[inst]]; SetOptions[outst, FormatType->OutputForm]; Write[outst,","]; Write[outst, ""]; ]; If[progressQ, Print[counter--]]; ]; Close[inst]; Write[outst, "}]"]; Write[outst,""]; Close[outst] ]; **********************************************) End[ ] (***************************************************************) Protect[ OpenCorruptedNotebook, SaveCorruptedNotebook ]; EndPackage[ ] (* end the package context *)