PROGRAM relabelmac; {For Turbo Pascal for Macintosh.} {Peter Ungar, 914 723 7187, June 7, 1994; Mar. 5th, Mar. 31th 1995} {Change of Mar. 5, 1995: In case of a reference to a nonexistent label, RELABEL is supposed to print out the nonexistent label. Before this correction this failed to work in most cases. Change of Mar. 31: The messages to input the numbers of the first chapter and the first section were changed. The old messages said -1 was allowed, but RELABEL is programmed to regard the symbol - as not part of a label. Also, the error message for nonexistent labels was made clearer.} LABEL 1,3,8,9; TYPE stringarray=array[1..6] of string[12]; (* LaTeX calls the identifier "label" when it occurs next to the item, and "reference" elsewhere. I apologize for not having observed this distinction. I should have used "identifier" for an occurrence of a string which could be a label or a reference, but for the most part I called it a label. In text written for RELABEL, as in typeset books, labels and references are not marked as such by the author. RELABEL regards the first occurrence of an identifier as the label, unless it is marked as a forward reference. The program makes two passes over the text. The first involves reading only. The program takes down each label at its first nonforward occurrence and assigns to it the next available serial number. These pairs of old and new labels are stored in the labelentry file. Next, the text is read again and written out to a new file, with each label changed to its new value. Stringarrays will hold labels, including those parts of the label which are implicit, i.e. if we omit the chapter label or the chapter and section label or the chapter, section and subsection label when those are the current ones. The entries of the stringarray are: a) If SectionReset=TRUE: chapterlabel, sectionlabel, subsection label and the identifier of the object of the label. If the item does not apply, e.g. if we are not in a subsection, the corresponding entry is the empty string ''; b) If SectionReset=FALSE, i.e. sections are numbered consecutively, then chapter numbers are not needed in labels and the first entry of the string array is empty except in the label of a chapter. *) (* We use the following pointer construction to get around the fact that Turbo Pascal allots a space of only 32K to all regularly declared variables, but can utilize memory outside that limited space for variables created by NEW statements. *) labelentry=RECORD oldlabel:stringarray; newlabel:array[1..4] of integer; secondpassoccurred:boolean; END; labelentryp=^labelentry; sublistp=array[-1..2000] of labelentryp; mainlistp=^sublistp; (* Oldlabel is a stringarray. The labels in the old text may contain lowercase letters as well as digits. Newlabel is represented by an array of integers, the chapter number etc. This will be made into a string when there is a reference to the label.*) VAR i,j,k,n,implicit,ImplAdj,SerialNo, FirstChapter,FirstSection,LabelKind:integer; (* ImplAdj (ImplicitAdjustment) is 0 if section numbers are reset at the beginning of each chapter and 1 if they are not, and hence chapter numbers are not needed in labels other than chapter labels. SerialNo will hold the serial number of the first occurrence of a label in the array holding all labels of its kind. FirstChapter, FirstSection are the numbers to be assigned to the first chapter and the first section of the file. RELABEL asks an input of this when the program starts, in case you want 0, 1 or larger numbers if the file is from the middle of a manuscript. *) totalcount, currentcount:array[0..9] of integer; (* countstarts gives where the numbering of chapters, sections subsections and other labeled items begins; the program sets the last two equal to 1. *) s,filename,outputfilename, filenameend:string; labelstring:string[51]; sta,empty,current:stringarray; (* totalcount will say how many labels of the 10 kinds we have so far. currentcount gives the counts of the 10 kinds since they were last reset. current contains the current chapterlabel, section label and subsection label. *) mainlist:array[0..9] of mainlistp; SectionReset,ForwardRef, LabeltagsInOutput, omit, PeriodFollows, FirstChapterLabel, FirstSectionLabel: boolean; (* Sectionreset: Each chapter starts with section 1.*) incoming, outgoing:text; c,ans:char; FUNCTION min(i,j:integer):integer; BEGIN IF i0 DO BEGIN s:=s+chr(48+(a DIV d)); a:= a MOD d; d:=d DIV 10 END; 9:END; FUNCTION labelsymbol(c:char):boolean; (* TRUE if c is a symbol allowed in a label, i.e. a digit or a lowercase letter. *) VAR x:integer; BEGIN x:=ord(c); labelsymbol:=(((48<=x) AND (x<=57)) OR ((97<=x) AND (x<=122)) OR ((65<=x) AND (x<=90))) END; PROCEDURE ReadLabel(VAR star:stringarray; VAR c:char); (* Reads the components of a label from the incoming stream and puts its components into the stringarray star, including the implicit items. If section numbers are not reset in each chapter (2 levels of test units) then the empty string is put in the chapter designator location star[1] except in the case of chapter labels. *) LABEL 2,5,9; VAR i,implicit:integer; str:string[12]; BEGIN k:=0; star:=empty; PeriodFollows:=FALSE; (* k will count the number of parts separated by .'s in the label. *) 2: str:=''; WHILE labelsymbol(c) DO BEGIN str:=str+c; read(incoming,c); END; k:=k+1; star[k+ImplAdj]:=str; IF c='.' THEN BEGIN (* Another part of the label is coming, or there is a . after the label. *) read(incoming,c); IF labelsymbol(c) THEN GOTO 2 ELSE PeriodFollows:=TRUE; END; (* Now we have all k items of the label in the stringarray star. We want the program to work so that, if desired, the chapter, section and subsection labels may be omitted and then they are understood to refer to the current chapter, section or subsection. We reconstruct the omitted parts of the label. We assemble it in star and then if it is a new label we put it into the mainlist. If the number of levels is 3, then the components of star are: Chapter label, Section label, Subsection label and item label. If any of these are missing or not applicable, the corresponding label is ''. A subsection label is missing if the section is not divided into subsections. A subsection label is inapplicable if the label is a chapter or section label. If the number of levels is 2, (ImplAdj=1) then a label of anything except a chapter is complete without the chapter label, and star[1]:='' for all such items. *) IF (LabelKind > 1) AND ((k+ImplAdj) > min(LabelKind, 4)) THEN BEGIN writeln('Found a label with too many parts. If you have 2 levels,'); writeln('only a chapter label should contain a chapter identifier.'); GOTO 5; END; IF ((LabelKind=0) AND (k>1)) THEN BEGIN writeln(' An at-large item label with more than one part: '); 5: write('LabelKind: ',Labelkind,' Label: '); FOR i:=1+ImplAdj TO k+ImplAdj-1 DO write(sta[i],'.'); writeln(sta[k+ImplAdj]); writeln('Continue? y or n '); readln(ans); IF ans='n' THEN BEGIN close(incoming); halt END; END; (* Of error handling. *) IF LabelKind=0 THEN BEGIN star[4]:=star[1+ImplAdj]; star[1+ImplAdj]:=''; END; IF LabelKind=1 THEN BEGIN star[1]:=star[1+ImplAdj]; star[2]:='';GOTO 9 END; IF (2<=LabelKind) AND (LabelKind<=3) THEN BEGIN implicit:=LabelKind-k-ImplAdj; IF implicit>0 THEN BEGIN FOR i:=LabelKind DOWNTO implicit+1 DO star[i]:=star[i-implicit]; FOR i:=1+ImplAdj TO implicit+ImplAdj DO star[i]:=current[i]; END; END; IF (labelkind >= 4) THEN BEGIN IF k+ImplAdj<4 THEN BEGIN star[4]:=star[k+ImplAdj]; star[k+ImplAdj]:='' END; IF k=1 THEN FOR i:=1+ImplAdj to 3 DO star[i]:=current[i]; (*This must be a label of an item in the current text unit.*) END; 9:END; (* of procedure readlabel. Note that c is the first symbol after the label at this stage, or the first symbol after the period if there was a period immediately after the label. *) FUNCTION InList(st:StringArray;VAR SerialNo:integer):boolean; (* TRUE if the label st is already in the mainlist. This function is also used to compute the index SerialNo of the label in the array of that LabelKind*) LABEL 9; VAR i:integer; bool:boolean; BEGIN IF totalcount[LabelKind]=0 THEN InList:=FALSE ELSE FOR i:=totalcount[LabelKind] DOWNTO 1 DO BEGIN bool:=TRUE; WITH mainlist[LabelKind]^[i]^ DO FOR j:=1 TO 4 DO bool:=bool AND (oldlabel[j]=st[j]); IF bool THEN BEGIN InList:=TRUE; SerialNo:=i; (* mainlist[LabelKind]^[SerialNo] is the record where this label and the new label which is to replace it can be found.*) GOTO 9 END; END; InList:=FALSE; (* The label is not in mainlist. *) 9:END; PROCEDURE AddToList(sta:stringarray); (* We have a new label which is not a forward reference. Add it to mainlist*) VAR i:integer; BEGIN totalcount[LabelKind]:=totalcount[LabelKind]+1; currentcount[LabelKind]:=currentcount[LabelKind]+1; (* If the new label is a chapter, section or subsection label then the counts of subordinate items have to be reset.*) IF (0=2 THEN FOR i:=1+ImplAdj TO min(3,LabelKind) DO newlabel[i]:=currentcount[i]; IF LabelKind>=4 THEN newlabel[4]:=currentcount[labelkind]; (* Newlabel[1] is 0 if sections are numbered consecutively and chapter numbers occur only in chapter labels.*) SecondPassOccurred:=FALSE; END; (* of preparing new mainlist entry *) END; (* of processing the new label *) PROCEDURE MakeNewLabelstring(VAR newlabelstring:string; labelkind,SerialNo:integer); LABEL 9; VAR j:integer;s:string; InUnitRef:boolean; BEGIN WITH mainlist[LabelKind]^[SerialNo]^ DO BEGIN newlabelstring:=''; IF LabelKind=0 THEN BEGIN IntToStr(newlabel[4],newlabelstring); GOTO 9 END; (* Of LabelKind=0 (at large label) case *) IF LabelKind=1 THEN BEGIN IntToStr(newlabel[1],newlabelstring); GOTO 9 END; (* Of LabelKind=1 (i.e. chapterlabel) case *) IF omit AND (LabelKind>3) THEN BEGIN (* Find whether the reference is to the current unit of the text.*) InUnitRef:=TRUE; FOR j:=1+ImplAdj TO 3 DO InUnitRef:=InUnitRef AND (newlabel[j]=currentcount[j]); IF InUnitRef THEN BEGIN IntToStr(newlabel[4],newlabelstring); GOTO 9 END; END; (* Of making label without chapter,section and subsection number. Note we exit to 9 only if such a label has been made. *) FOR j:=1+ImplAdj TO min(LabelKind,4) DO IF OldLabel[j] <> '' THEN BEGIN IntToStr(newlabel[j],s); IF newlabelstring='' THEN newlabelstring:=s ELSE newlabelstring:=newlabelstring+'.'+s; END; END; (* of WITH statement *) 9:END; FUNCTION LabelTag(LabelKind:integer):char; (* For error message.*) BEGIN CASE LabelKind OF 0: LabelTag:= ''; {Option a, At large item ASCII 140} 1: LabelTag:= ''; {Option c, Chapter ASCII 141} 2: LabelTag:= ''; {Option s, Section ASCII 167} 3: LabelTag:= ''; {Option b, suBsection ASCII 186} 4: LabelTag:= ''; {Option g, fiGure ASCII 169} 5: LabelTag:= ''; {Option f, Formula ASCII 196} 6: LabelTag:= ''; {Option p, Problem ASCII 185} 7: LabelTag:= ''; {Option d, Definition ASCII 182} 8: LabelTag:= ''; {Option t, Theorem ASCII 160} 9: LabelTag:= ''; {Option l, Lemma ASCII 194} (* In the IBM-compatible version of RELABEL different label tags are used. Their ASCII numbers are 227, 228, 229, 221, 222,..., 226 *) END; END; (* Main program begins here.*) BEGIN 1: FOR i:=1 TO 4 DO empty[i]:=''; current:=empty; writeln('File to be renumbered. If it is not in the same folder as RENUMBER'); writeln('then give path name, e.g. HardDisk:Calculus Book Folder:Ch3 '); readln(filename); writeln('Do you have a 2 level (Section, possibly subsection) or'); writeln('3 level (Chapter, section, subsection) hierarchy of text units?'); write('Input 2 or 3 '); readln(i); SectionReset:= i=3; ImplAdj:=3-i; (*ImplAdj (Implicit Adjustment) = 1 if chapter numbers are superfluous in labels, 0 otherwise.*) writeln('Do you want the chapter, section and subsection numbers'); writeln('in the new file if they are all the current ones (y or n) '); writeln('(Chapter numbers are included only if you have 3 levels)'); readln(ans); omit:=ans='n'; writeln('Do you want the label tags to remain in the output? y or n: '); readln(ans); LabeltagsInOutput:=ans='y'; IF labeltagsinoutput THEN filenameend:='R' ELSE filenameend:='T'; FOR i:=0 TO 9 DO currentcount[i]:=0; TotalCount:=CurrentCount; writeln('Input the number >= 0 you want for the first chapter'); readln(FirstChapter); currentcount[1]:=FirstChapter-1; FirstChapterLabel:=TRUE; writeln('Input the number >= 0 you want for the first section'); readln(FirstSection); currentcount[2]:=FirstSection-1; FirstSectionLabel:=TRUE; writeln('I will beep when ready'); (* Next create the arrays for storing the various types of labels. *) FOR i:=0 TO 9 DO new(mainlist[i]); Reset(incoming,filename{,50000}); (* Use large buffer to spare disk drive *) read(incoming, c); WHILE eof(incoming)=FALSE DO BEGIN IF ord(c) > 128 THEN BEGIN CASE c OF '': LabelKind:=0; '': LabelKind:=1; '': LabelKind:=2; '': LabelKind:=3; '': LabelKind:=4; '': LabelKind:=5; '': LabelKind:=6; '': LabelKind:=7; '': LabelKind:=8; '': LabelKind:=9; OTHERWISE (* In case the file comes from MS-DOS user of RELABEL *) IF ((227 <= ord(c)) AND (ord(c)<= 229)) THEN LabelKind:= ord(c)-226 ELSE IF ((221 <= ord(c)) AND (ord(c)<= 226)) THEN LabelKind:= ord(c)-217 ELSE IF ord(c)=220 THEN LabelKind:=0 ELSE GOTO 8; END; read(incoming, c); IF (c<>'') THEN readlabel(sta,c) ELSE GOTO 8; (* Do readlabel and the work below only if this is not labeled to be a reference. Next, we check if this is a repeat occurrence of a label. If not, enter in the list of labels and increase the count of labels of the kind we found.*) IF NOT InList(sta,SerialNo) THEN AddToList(sta); END; (* of processing a label *) 8:read(incoming,c) END; (* of loop which reads characters of the file *) writeln('First pass completed. I am starting to write the new file.'); Reset(incoming); outputfilename:=filename+filenameend; (*If we choose to retain the tags of the labels in the renumbered file then its name is original file name with R appended, if the tags are stripped from the labels, S is appended.*) TextCreator:='*TEX'; Rewrite(outgoing,outputfilename{,50000}); (* Use large buffers to avoid frequent disk operation *) current:=empty; FOR i:=0 TO 9 DO currentcount[i]:=0; currentcount[1]:=FirstChapter-1; currentcount[2]:=FirstSection-1; (* We need to redo current to be able to restore the implicit parts of the labels as we encounter them on the second pass. *) 3:read(incoming,c); WHILE eof(incoming)=FALSE DO BEGIN IF ord(c) >=128 THEN BEGIN (* A label may begin here. *) (* Check for and convert MS-DOS label tags *) IF ord(c)=220 THEN BEGIN LabelKind:=0; c:=labeltag(0) END; IF ((227 <= ord(c)) AND (ord(c)<= 229)) THEN BEGIN LabelKind:= ord(c)-226; c:=labeltag(labelkind) END; IF ((221 <= ord(c)) AND (ord(c)<= 226)) THEN BEGIN LabelKind:= ord(c)-217; c:=labeltag(labelkind) END; {Of DOS conv.} ForwardRef:=FALSE; CASE c OF '': LabelKind:=0; '': LabelKind:=1; '': LabelKind:=2; '': LabelKind:=3; '': LabelKind:=4; '': LabelKind:=5; '': LabelKind:=6; '': LabelKind:=7; '': LabelKind:=8; '': LabelKind:=9 OTHERWISE BEGIN (*The symbol is not a labelkind indicator*) write(outgoing,c); GOTO 3; END; END; (* of CASEs. We did not GOTO 3, c is a labeltag. *) IF LabeltagsInOutput THEN write(outgoing,c); read(incoming,c); IF c=chr(222) THEN c:=''; IF c='' THEN BEGIN ForwardRef:=TRUE; IF LabeltagsInOutput THEN write(outgoing,c); read(incoming,c) END; readlabel(sta,c); IF NOT(InList(sta,SerialNo)) THEN BEGIN writeln('LABEL NOT FOUND IN LIST ON SECOND PASS. ERROR'); writeln('YOU MAY HAVE REFERRED TO A NONEXISTENT LABEL'); writeln('OR YOU MAY HAVE ENTERED THE WRONG LEVEL NUMBER.'); write('LabelKind: ',Labelkind,' Label in full form: '); FOR i:=1+ImplAdj TO min(LabelKind-1, 3) DO IF sta[i] <> '' THEN write(sta[i],'.'); IF ((LabelKind = 0) OR (LabelKind > 3)) THEN writeln(sta[4]) ELSE writeln(sta[LabelKind]); writeln('Continue (with ??? in this reference)? y or n '); readln(ans); IF ans='n' THEN BEGIN close(incoming); close(outgoing); halt END; write(outgoing,'???'); GOTO 9; END; (* Of message and output, we had a reference to unknown label.*) IF NOT ForwardRef THEN BEGIN WITH mainlist[labelkind]^[SerialNo]^ DO BEGIN IF (NOT secondpassoccurred)THEN BEGIN IF (0FirstChapter) OR (currentcount[2]>FirstSection) THEN BEGIN FOR i:=max(labelkind+1,ImplAdj+2) TO 3 DO BEGIN current[i]:=''; currentcount[i]:=0; END; END; END; (* Of dealing with the label of a new heading. *) secondpassoccurred:=TRUE; END ELSE IF LabeltagsInOutput THEN write(outgoing,''); (* We have a reference.*) END; (* Of WITH mainlist...*) END; (* Of NOT ForwardRef *) (* Now we are ready to get the new label and convert it to a string. This can not be done once for all since the string may be shorter in a reference to the item from within the same subunit. Doing the job from scratch each time just because we may need one or the other of two expressions is admittedly inelegant but seldom are there many references to one item. *) MakeNewLabelstring(s,LabelKind, SerialNo); write(outgoing,s); 9: IF PeriodFollows THEN write(outgoing,'.'); END (* of assembling and writing the new label. *) ELSE BEGIN (* ord(c)<128, certainly not a labelkind symbol*) write(outgoing,c); read(incoming,c); END; (* of IF ord(c)>=128..ELSE statement. *) END; (* Return to the beginning of the WHILE eof(incoming)=FALSE cycle *) write(outgoing,c); (* write the last symbol of the file *) close(outgoing);close(incoming); writeln(chr(7),'"',outputfilename,'"', 'written on disk.'); FOR i:=0 TO 9 DO dispose(mainlist[i]); write('Relabel another file? (y or n)'); readln(c); IF c='y' THEN GOTO 1; END.