maclab@reed.UUCP (S.Gillespie/Mac Dev. Lab) (11/16/85)
I am currently in the midst of writing an indexing program which operates on MacWrite files. Needless to say, the first hurdle was to figure out how to read the text portion of a MacWrite file: the following source is an example of one way to do it. Hope someone finds this helpful...(you should definitely get your hands on the Mac Technical Note which describes the file format, if you wish to do anything very involved). Scott Gillespie Reed College {decvax, ucbvax, pur-ee, uw-beaver, masscomp, cbosg, aat, mit-ems, psu-cs, uoregon, orstcs, ihnp4, uf-cgrl, ssc-vax}!tektronix \ +--!reed!maclab {teneron, ogcvax, muddcs, cadic, oresoft, grpwre, / harvard, psu-cs, omen, isonvax, nsc-pdc}-------------+ ----------------------------------------------------------------- Program ReadMacWrite; (* Program to read the text portion of MacWrite Files, and write those chars to the screen. Written by Scott Gillespie Reed College Portland, OR 97202 See Macintosh Technical Notes, and Supplement documents for a complete description of the MacWrite file format. The key procedures are: ReadFile: Takes filename and volume ref, and outputs chars to Procedure OutChar. Decompress: Called by ReadFile for decompressed paragraphs. This could certainly be made faster.... *) Uses __OSTraps, (*$U+*) uMemtypes; Link __Uniform, (* Uniform File I/O Calls *) __IO, (* Built-in System Call interface *) __SFNames, (* Turnkey Standard File library *) __EasyMenus, (* Turnkey Menus library *) __OSTraps :; (* Operating System Routines *) Var NeedNib, (* For Decompr.: True = 2nd Nibble needed for ascii *) NextAsc: boolean; (* For Decompr.: True = Two Nibbles needed *) LastNib: byte; (* For Decompr.: Holds last nibble *) WrapCount: Integer; (* For screen wordwrapping *) (* Decompress takes a nibble at a time of compressed text. If it returns True, a character has been reconstructed, and is placed in c^. If False, more nibbles are needed to complete next character *) Function Decompress(b: byte; c: ^Integer): Boolean; { Decompress := False; if neednib Then { (* Low half of ascii nibble is needed. *) neednib := False; c^ := (LastNib or b); (* Put the two halves together *) Decompress := True; (* Character is ready *) } Else if nextasc Then { (* Two nibbles are needed *) nextasc := False; LastNib := b << 4; (* Save this one as the high nibble *) neednib := True; (* Need one more nibble *) } Else if b=15 Then (* Nibble of 15 means the next char is ascii *) nextasc := True Else { (* Add the nibble value to the English decompression *) (* key (saved as Resource Type "STR " 700 in file) *) (* to get the proper character *) c^ := ptrb(++b + " etnroaisdlhcfp")^; Decompress := True; }; }; (* ReadFile takes a name and volume reference number of a MacWrite File, and outputs the text characters to 'OutChar' (further below). *) Procedure ReadFile(np: ptrb; vref: Integer); Type IArray = Record (* Information array element *) height: integer; pagepos: integer; parahand: ^^longint; StPos: Union St: byte; (* first byte is status *) Pos: longint; End; DataLength: integer; formats: Integer; End; Var amt: longint; Buf: ^Byte[20]; press: Boolean; off: Longint; infohand: ^^Iarray[20]; f, count,i,c,j,d,k,len,ch,mods: integer; DocVars: Record IApos: Longint; IAlength: Integer; End; { Buf := NewPtr(0L); (* Will be used for reading in paragraphs *) fopen(@f,np,0,vref); (* Open the file *) fmoveto(f,252L); (* Move to the start of the main document information.*) fmove(f,12L); (* Move Offset of Information Array Position *) ffread(f,DocVars,6L); (* Read in position and length of Info. Array *) fmoveto(f,DocVars.IAPos); (* Move to start of information Array *) (* Read in the information array (could be big...) *) InfoHand := NewHandle(Longint(DocVars.IALength)); Hlock(InfoHand); ffread(f,InfoHand^,Longint(DocVars.IALength)); Hunlock(InfoHand); Count := DocVars.IALength/16; (* 16 is size of Paragraph Array element. Length/16 = Number of paragraphs *) (* Now, loop through each paragraph. If it is not text, go on to the next one. If it is not compressed, just write it out, otherwise decompress *) loop(count,i:=0,,++i=count) { CheckKey(@ch,@mods); (* Hitting the <Enter> key aborts the listing *) If (ch = 3) Then Break; if InfoHand^^[i].height <= 0 Then Continue; (* Not a text paragraph *) (* If the text is compressed, press will be true *) press := (InfoHand^^[i].stpos.st >> 3) and 1; (* Off will contain the file offset to this paragraph's data *) Off := InfoHand^^[i].stpos.pos and $00FFFFFF; (* clear status byte *) fMoveTo(f,off); (* Move to start of paragraph data *) fgetint(f,@len); (* First two bytes specify the data length *) SetPtrSize(buf,longint(len)); (* Make the pointer big enough *) (* Now, we read in the data. If this is compressed data, too many characters will be read in, but that's o.k., because the decompression loop just ignores the extras *) ffread(f,buf,longint(len)); If !press Then (* Paragraph is straight text: just output *) loop(len,j:=0,,++j=len) Outchar(Integer(buf^[j])) Else (* Initialize decompression vars to default, then send a nibble at a time to 'Decompress' procedure. If Decompress is True, output the character (d) *) loop(len,NextAsc:=False;NeedNib:=False;j:=0;k:=0,++k,) { If Decompress(buf^[k] >> 4 , @d) then { Outchar(d); If ++j>=len then break; (* len specifies how many characters should be read in, so when we have that many, we're done *) }; If Decompress(buf^[k] and Byte($0F) , @d) then { Outchar(d); If ++j>=len then break; }; }; }; Disposptr(Buf); Disposhandle(infohand); fclose(f); }; (* ffread is a lazy man's fread.... *) Procedure ffread(f: integer; b: ptrb; amt: longint); { fread(f,b,@amt) }; (* Outchar outputs a character, in this case to the screen. Although you can obviously modify this to output to another file, or to memory. A simple word wrap scheme is implemented here: if there are >60 characters on the line, do a wordwrap at the next space *) Proc OutChar(c: integer); { Writechar(c); If c=13 then { Writechar(10); WrapCount := 0; }; If ++WrapCount > 60 Then If c=32 Then { Writeln(); WrapCount := 0; }; }; (* NewFile() puts up a standard file dialog box, allowing user to select a file *) Proc NewFile(); Var vref, good : Integer; name: ptrb; { ngetfile(100,70,@name," WORD"+2,1,@vref,@good); if good then ReadFile(name,vref); Writeln(); Writeln(); Writeln(); }; Proc _Init(); { InitEasyMenus(); AddMenu(1000,"Read"); AddItem(1000,"Open..."); WrapCount := 0; }; Proc _Halt(); { HaltEasyMenus(); }; Proc _Menu(id,item: integer); { if id=1000 Then if item=1 Then NewFile(); }; (* End of ReadMacWrite.src *)