dubois@uwmacc.UUCP (Paul DuBois) (03/17/86)
Program __StreamLib; (* __StreamLib - set of routines for treating TEXT or WORD files as a stream of characters or lines. To use: Call InitStream() first. Call OpenStream() to open a stream. If it returns noErr, a stream was opened ok. To read characters, call StreamGetC(). This returns the next character or -1 on end of stream (and closes the stream). To read lines, call StreamGetS(buf). This fills up the buffer passed to it and returns a pointer to it, or nil on end of stream (and closes the stream). To close a stream early, call CloseStream(). There are also corresponding routines for reading TEXT files only, or WORD files only. See below. These are useful if you want to end up with less code linked into your program. To compile this library: Select Batch... from the Options Menu Select Compile Same (or Compile...) for Step 1). Select Combine Same for Step 2). Click OK. Version 1.0 11 March 1986 Paul DuBois Wisconsin Regional Primate Research Center 1220 Capitol Court University of Wisconsin-Madison Madison, WI 53706 UUCP: {allegra, ihnp4, seismo}!uwvax!uwmacc!dubois *) Uses __OSTraps (*$U+*) uOSIntf uPackIntf ; Link __OSTraps :; Const bufSiz = 1024; wordStream = 1; (* stream is of 'WORD' file *) textStream = 2; (* stream is of 'TEXT' file *) paraLen = 16; (* paragraph information 16 bytes long *) 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 theReply: SFReply; (* SFGetFile reply record *) f: Integer; (* input file reference number *) streamOpen: Boolean; (* whether stream currently open *) streamType: Integer; (* wordStream or textStream *) (* vars needed for TEXT stream only *) filBuf: Byte[bufSiz]; (* file buffer *) fChars: LongInt; (* number of chars gotten on last read *) fIndex: Integer; (* current position in filBuf *) (* vars needed for WORD stream only *) paraBuf: ^^Byte[1]; infoHand: ^^Iarray[1]; compressed: Boolean; inPara: Boolean; nParas: Integer; (* number of paragraphs *) paraNum: Integer; (* current paragraph number *) pIndex: Integer; (* index into paragraph *) pChars: Integer; (* number of chars extracted from paragraph *) firstHalf: Boolean; (* which half of current index char *) pLen: Integer; (* number of chars in paragraph *) needNib, (* For Decompr.: true = 2nd Nibble needed for ascii *) nextAsc: boolean; (* For Decompr.: true = Two Nibbles needed *) lastNib: byte; (* For Decompr.: Holds last nibble *) lineLen: Integer; (* for line wrapping *) (* -------------------------------- *) (* miscellaneous utility routines *) (* -------------------------------- *) Proc _ffRead (f: Integer; b: PtrB; amount: LongInt); { amount := FSRead(f, @amount, b); }; Proc _fMoveTo (f: Integer; amt: LongInt); Var result: Integer; { result := SetFPos (f, fsFromStart, amt); }; (* --------------------------------- *) (* stream init/open/close routines *) (* --------------------------------- *) (* InitStream - must be called before you do anything else. _FSOpen - like FSOpen (in __OSTraps), but has open mode parameter. _OpenStream - Open a file for stream I/O. numTypes is either 1 or 2, and typeList is either 'TEXT', 'WORD', or 'TEXTWORD'. Return noErr if file opened OK, or fnOpnErr if not. if OK, set streamType to wordStream or textStream, according to the type of the opened file, and set fileOpen true. OpenTextSream - open 'TEXT' file stream. Returns: noErr - file open OK fnOpnErr - file not open OpenWordSream - open 'WORD' file stream. Returns: noErr - file open OK fnOpnErr - file not open mFulErr - couldn't get memory to read paragraph info into memory. file not open. OpenSream - open 'TEXT' or 'WORD' stream. Return values same as for OpenWordS. Returns stream type in function argument sType. _WordStreamInit does special handling necessary for 'WORD' stream: Open the file, advance 252 + 12 bytes (to start of main document info + offset of Information Array. Then read position and length of Info Array, move to it, read it in, and calculate number of paragraphs. (16 bytes info per paragraph.) _TextStreamInit does TEXT stream specific initialization. *) Proc InitStream (); { streamOpen := false; lineLen := 65; paraBuf := nil; infoHand := nil; }; Proc CloseStream (); Var result : OSErr; { if streamOpen then { result := FSClose (f); streamOpen := false; if streamType = wordStream then { if paraBuf <> nil then DisposHandle (paraBuf); if infoHand <> nil then DisposHandle (infoHand); }; }; }; Func _FSOpen (fName: PtrB; vRefNum: Integer; refNum: ^Integer; mode: Integer): OSErr; Var p: ParamBlockRec; { p.ioNamePtr := fName; p.ioVRefNum := vRefNum; p.ioPermssn := mode; p.ioVersNum := 0; p.ioMisc := 0; _FSOpen := PBOpen (p, false); refnum^ := p.ioRefNum; }; Func _OpenStream (numTypes: Integer; typeList: OSType): OSErr; { CloseStream (); (* close any currently open stream *) _OpenStream := fnOpnErr; streamOpen := false; ToolBox ($A9EA, 100, 70, "", nil, numTypes, typeList, nil, @theReply, 2); if theReply.good then { if _FSOpen(theReply.fName, theReply.vRefNum, @f, fsRdPerm) = noErr then { streamType := wordStream; if theReply.fType = PtrL (" TEXT"+2)^ then streamType := textStream; streamOpen := true; _OpenStream := noErr; }; }; }; Proc _TextStreamInit (); { fChars := 0; (* set these to trigger a read on the first *) fIndex := 0; (* call to TextStreamGetC() *) }; Func _WordStreamInit (): OSErr; Type DocVars = Record IApos: Longint; IAlength: Integer; End; Var docVars: DocVars; { paraBuf := NewHandle (0L); (* Will be used for reading in paragraphs *) _fmoveto (f, 264L); (* 252 + 12 *) _ffRead (f, docVars, LongInt (SizeOf (DocVars))); _fMoveTo (f, docVars.IAPos); infoHand := NewHandle (Longint (docVars.IALength)); if infoHand = nil then { CloseStream (); _WordStreamInit := mFulErr; } else { HLock (InfoHand); _ffRead (f, infoHand^, Longint (docVars.IALength)); HUnlock (InfoHand); nParas := docVars.IALength/paraLen; paraNum := -1; inPara := false; (* not in any paragraph yet *) _WordStreamInit := noErr; }; }; Func OpenTextStream (): OSErr; { OpenTextStream := _OpenStream (1, " TEXT"+2); _TextStreamInit (); }; Func OpenWordStream (): OSErr; Var result: OSErr; { OpenWordStream := fnOpnErr; if _OpenStream (1, " WORD"+2) = noErr then OpenWordStream := _WordStreamInit (); }; Func OpenStream (): OSErr; { OpenStream := fnOpnErr; if _OpenStream (2, " TEXTWORD"+2) = noErr then { OpenStream := noErr; case streamType of wordStream: OpenStream := _WordStreamInit (); textStream: _TextStreamInit (); end; }; }; Proc GetStreamInfo (reply: SFReply); { reply := theReply; }; (* 'Get a character' routines TextStreamGetC - get character from 'TEXT' stream. WordStreamGetC - get character from 'WORD' stream. StreamGetC - get character from stream. *) Func TextStreamGetC (): Integer; Var err: OSErr; { TextStreamGetC := -1; if streamOpen = false then return; if fIndex >= fChars then (* need to read in a new block *) { fChars := bufSiz; err := FSRead (f, @fChars, filBuf); if fChars = 0 then { CloseStream (); return; }; fIndex := 0; }; TextStreamGetC := filBuf[fIndex]; ++fIndex; }; (* _Decompress takes a nibble at a time of compressed text. If more nibbles are needed to complete the next character, return -1, else returns the character. *) Func _Decompress (b: Byte): Integer; { _Decompress := -1; if needNib then (* Low half of ascii nibble is needed. *) { needNib := false; _Decompress := (lastNib or b); (* Put the two halves together *) } 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 *) { _Decompress := PtrB (++b + " etnroaisdlhcfp")^; }; }; Func WordStreamGetC (): Integer; (* return -1 on EOF *) Var c: Integer; offset: LongInt; { WordStreamGetC := -1; if streamOpen = false then return; if inPara = false then (* must read in next paragraph *) { loop (,,,) { if ++paraNum >= nparas then { CloseStream (); return; }; if infoHand^^[paranum].height <= 0 then continue; (* offset will contain the file offset to this paragraph's data. must mask the high byte. Move to the paragraph, get its length, make the pointer big enough, and read it in. (skip to next para if this one is empty, though.) compressed will be set true if the paragraph is compressed. *) offset := infoHand^^[paranum].stpos.pos and $00FFFFFF; _fMoveTo (f, offset); _ffRead (f, @plen, LongInt (SizeOf (Integer))); (* get length *) if plen = 0 then continue; SetHandleSize (paraBuf, LongInt (plen)); (* make big enough *) if MemError () <> noErr then { paranum := nparas; (* force close and exit of loop *) continue; }; _ffRead (f, paraBuf^, LongInt (plen)); compressed := (infoHand^^[paranum].stpos.st >> 3) and 1; inPara := true; nextAsc := false; needNib := false; pIndex := 0; (* index into current paragraph *) pChars := 0; (* chars extracted from current paragraph *) firstHalf := true; (* use first half of current index char *) break; }; }; (* At this point, know eitherthat we have a new non-empty paragraph, or are still in the previous one. *) if !compressed then (* uncompressed *) { c := paraBuf^^[pchars]; } else { loop (,,, c <> -1) { c := paraBuf^^[pIndex]; if firstHalf then { c := _Decompress (Byte (c >> 4)); } else { c := _Decompress (Byte (c and $0f)); ++pIndex; (* go to next char at next index *) }; firstHalf := !firstHalf; }; }; if ++pChars >= pLen then (* see if need new paragraph next time *) inPara := false; WordStreamGetC := c; }; Func StreamGetC (): Integer; { case streamType of wordStream: StreamGetC := WordStreamGetC (); textStream: StreamGetC := TextStreamGetC (); end; }; (* 'Get a string' routines TextStreamGetS - get string from 'TEXT' stream. WordStreamGetS - get string from 'WORD' stream. StreamGetS - get string from stream. All return nil if no string obtained, otherwise a pointer to the argument. *) Func TextStreamGetS (s: StringPtr): StringPtr; Var c: Integer; { TextStreamGetS := nil; if !streamOpen then return; s[0] := 0; (* clear string *) loop (,,,) { c := TextStreamGetC (); if c = -1 then break; TextStreamGetS := s; (* got something, so NextLine succeeds *) if c = 13 then break; s[++s[0]] := c; (* add char to end *) }; }; Func WordStreamGetS (s: StringPtr): StringPtr; Var c: Integer; { WordStreamGetS := nil; if !streamOpen then return; s[0] := 0; (* clear string *) loop (,,,) { c := WordStreamGetC (); if c = -1 then break; WordStreamGetS := s; (* got something, so NextLine succeeds *) if (c = 13) or ((s[0] > lineLen) and (c = ' ')) then break; s[++s[0]] := c; (* add char to end *) }; }; Func StreamGetS (s: StringPtr): StringPtr; { case streamType of wordStream: StreamGetS := WordStreamGetS (s); textStream: StreamGetS := TextStreamGetS (s); end; }; (* Set wrap length (number of chars after which point the line will be broken at the next space if a carriage return is not seen first). This only affects TextStreamGetS (or StreamGetS when the current stream is a WORD stream). *) Proc SetLineLen (len: Integer); { lineLen := len; }; -- | Paul DuBois {allegra,ihnp4,seismo}!uwvax!uwmacc!dubois --+-- | |