[net.sources.mac] ReadMacWrite.src -- Code to read MacWrite files

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 *)