home *** CD-ROM | disk | FTP | other *** search
- /*
- * Convert text file to MacWrite document, using formatting
- * specifications contained in current map.
- *
- * Possibly add:
- * Text compression
- */
-
- # include "MWMaca.h"
- # include "MWFileStuff.h"
- # include "MakeWrite.h"
-
- # ifdef debug
- # include "TransDisplay.h"
- # endif
-
-
- # define maxTBufSize 1024 /* text file input buffer size */
- # define maxCBufSize 4000 /* MacWrite limit on chars in paragraph */
-
- /*
- * MacWrite's maximum main doc text para count is something over
- * 2000. I choose 2000 as a reasonable limit.
- */
-
- # define maxMWParas 2000
-
-
- /*
- * Vars for text file streaming
- */
-
- static short fText; /* input text file reference number */
- static char tFileBuf[maxTBufSize]; /* file buffer */
- static long tFileSize; /* file size */
- static long tFileRead; /* # chars read */
- static long tFileLeft; /* # chars still to read */
- static long tBufSize; /* number of chars currently in buffer */
- static short tBufPos; /* current text buffer position */
- static short percent; /* percent of input read so far */
-
-
- /*
- * fWord - output MacWrite file reference number
- * pushBack - text file input pushback queue
- * outBuf - output paragraph buffer
- * outChars - number of chars into current output paragraph so far
- * blankCount - number of blanks to put out for line joining
- * fmtCount - number of formats in the current paragraph so far
- * fmtBuf - format information storage array
- * maxFormats - number of formats the format array handle can hold
- * before needing expansion
- * needFormat - true if need to generate a format when the next output
- * char is added to current paragraph
- * paraInfo - paragraph information storage array
- * maxParas - number of elements paraInfo can hold before needing expansion
- * paraCount - current number of paragraphs (doesn't count initial ruler)
- *
- * font, size, style - current format
- */
-
- static short fWord;
- static Str255 pushBack;
- static char outBuf[maxCBufSize];
- static short outChars;
- static short blankCount;
-
- static FAHandle fmtBuf;
- static short fmtCount;
- static short maxFormats;
- static Boolean needFormat;
-
- static PIAHandle paraInfo;
- static short maxParas;
- static short paraCount;
-
- static short font;
- static short size;
- static short style;
-
-
- /*
- * Initialize text streaming variables
- */
-
- static void
- InitTextStream (short f)
- {
- (void) GetEOF (f, &tFileSize); /* set up streaming variables */
- tFileLeft = tFileSize;
- tFileRead = 0;
- tBufSize = 0;
- tBufPos = 0;
- percent = -1;
- }
-
-
- /*
- * Get next char from text file. This has to take account of the
- * state of the pushback stack. File is read from disk a block
- * at a time and dribbled out to avoid making a file system call
- * for every character.
- *
- * Return false on end of file or file read error.
- */
-
- static Boolean
- ReadTextChar (char *c)
- {
- short newPercent;
-
- if (pushBack[0] > 0) /* pushback stack not empty */
- {
- *c = pushBack[pushBack[0]--]; /* return top char */
- return (true);
- }
-
- if (tBufPos >= tBufSize) /* need to read in a new block */
- {
- tBufSize = tFileLeft;
- if (tBufSize == 0) /* end of file */
- return (false);
- if (tBufSize > maxTBufSize) /* don't read more than a block */
- tBufSize = maxTBufSize;
- tFileLeft -= maxTBufSize;
- if (tFileLeft < 0)
- tFileLeft = 0;
-
- if (!FileRead (fText, tFileBuf, (long) tBufSize))
- return (false); /* read error */
- tBufPos = 0;
- }
- *c = tFileBuf[tBufPos++];
-
- # ifdef debug
- DisplayChar (*c);
- # endif
-
- newPercent = (++tFileRead * 100) / tFileSize;
- if (newPercent != percent)
- {
- percent = newPercent;
- SetMeterPercent (percent);
- }
- return (true);
- }
-
-
- /*
- * Figure out how many blanks need to be added to the output buffer
- * if another line is joined on. Default is one. If smart join is
- * on, then two spaces if something like a period is at the end now.
- * Remember to account for spaces that may already be on the end. If
- * a tab is found at the end, then throw hands up in disgust, because
- * who knows how wide it is?
- */
-
- static void
- SetBlankCount (void)
- {
- short i, endBlanks, needBlanks = 1;
- char c;
-
- i = outChars;
- while (i > 0) /* count up spaces on end currently */
- {
- if ((c = outBuf[i-1]) == '\t')
- {
- blankCount = 0;
- return; /* tab found - give up */
- }
- if (c != ' ')
- break;
- --i;
- }
- endBlanks = outChars - i;
-
- if (paraStyle.pSmartJoin) /* smart join - try to be intelligent */
- {
- while (i > 0)
- {
- c = outBuf[i-1];
- if (InString (quoteStr, c)) /* quote char - ignore */
- --i;
- else /* not quote char - is it period char? */
- {
- if (InString (periodStr, c))
- needBlanks = 2; /* yes - need two chars */
- break;
- }
- }
- }
-
- if (i == 0) /* empty buffer - nothing needed */
- needBlanks = 0;
-
- needBlanks -= endBlanks; /* account for those already there */
- if (needBlanks < 0)
- needBlanks = 0;
- blankCount = needBlanks;
- }
-
-
- /*
- * Push saved text back onto the pushback stack
- */
-
- static void
- PushSavedText (StringPtr s)
- {
- while (s[0] > 0)
- {
- pushBack[++pushBack[0]] = s[s[0]--];
- }
- }
-
-
- static Boolean
- NewFormat (void)
- {
- Format *f;
-
- # ifdef debug
- DisplayString ("\p {NewFormat}");
- # endif
-
- if (fmtCount >= maxFormats) /* need to make format array bigger */
- {
- if (!ExpandHandle ((Handle) fmtBuf, 1024L))
- {
- Message1 ("\pOut of memory, can't continue (NewFormat)");
- return (false);
- }
- maxFormats = GetHandleSize ((Handle) fmtBuf) / sizeof (Format);
- }
-
- f = &((**fmtBuf)[fmtCount++]);
- f->fmtPos = outChars; /* starting pos of format */
- f->fmtFont = font; /* current font */
- f->fmtSize = size; /* current size */
- f->fmtStyle = style; /* current style */
- return (true);
- }
-
-
- static Boolean
- AddChar (char c)
- {
- Str255 s;
-
- if (needFormat)
- {
- needFormat = false;
- if (!NewFormat ())
- return (false); /* couldn't get memory for format */
- }
- if (outChars >= maxCBufSize)
- {
- NumToString ((long) maxCBufSize, s);
- Message ("\pParagraph contains more than ", s,
- "\p chars (MacWrite limit exceeded).",
- "\p Check your Paragraph Style settings.");
- return (false);
- }
- outBuf[outChars++] = c;
- return (true);
- }
-
-
- static void
- ChangeCurFormat (MapSpec *m)
- {
- Str255 infoStr;
-
- if (m->font != sameFont && m->font != font)
- {
- font = m->font;
- needFormat = true;
- }
- if (m->size != sameSize && m->size != size)
- {
- size = m->size;
- needFormat = true;
- }
- if (m->style != sameStyle && m->style != style)
- {
- style = m->style;
- needFormat = true;
- }
-
- # ifdef debug
- if (needFormat)
- {
- DisplayChar ('{');
- FontToStr (font, infoStr);
- DisplayString (infoStr);
- DisplayChar ('/');
- DisplayShort (size);
- DisplayChar ('/');
- StyleToStr (style, infoStr);
- DisplayString (infoStr);
- DisplayString ("\p} ");
- }
- # endif
- }
-
-
- /*
- * Write stored paragraph contents to disk:
- * number bytes of text (word)
- * the text
- * zero pad if necessary to align to word boundary
- * number bytes of format information
- * the format information
- *
- * Construct a new element in the paragraph information array
- * as well, and reset the paragraph state vars.
- *
- * The data position pointer in the para info element is set
- * to the current file position, since that's where the data
- * will be written.
- */
-
- static Boolean
- FlushPara (void)
- {
- short fmtLen, dataLen;
- Boolean result;
- Str255 s;
-
- # ifdef debug
- DisplayString ("\p{FlushPara}");
- # endif
-
- if (paraCount >= maxMWParas)
- {
- NumToString ((long) maxMWParas, s);
- Message3 ("\pYour document contains more than ",
- s, "\p paragraphs. Split the input file and try again.");
- return (false);
- }
-
- if (paraCount >= maxParas) /* need to make para info array bigger */
- {
- if (!ExpandHandle ((Handle) paraInfo, 1024L))
- {
- Message1 ("\pOut of memory, can't continue (FlushPara)");
- return (false);
- }
- maxParas = GetHandleSize ((Handle) paraInfo) / sizeof (ParaInfo6);
- }
-
- fmtLen = fmtCount * sizeof (Format);
- dataLen = sizeof (short) /* word for length of text */
- + ((outChars + 1) & ~1) /* text ( + pad if necessary) */
- + sizeof (short) /* word for length of formats */
- + fmtLen; /* formats */
- HLock ((Handle) paraInfo);
- SetParaInfo (&((**paraInfo)[paraCount++]),
- 16, 0, FilePos (fWord), dataLen, 0);
- HUnlock ((Handle) paraInfo);
- HLock ((Handle) fmtBuf);
- result =
- WriteInteger (fWord, outChars)
- && FileWrite (fWord, outBuf, (long) outChars)
- && ZeroPad (fWord, 0)
- && WriteInteger (fWord, fmtLen)
- && FileWrite (fWord, (Ptr) *fmtBuf, (long) fmtLen);
- HUnlock ((Handle) fmtBuf);
- outChars = 0; /* reset para state vars */
- fmtCount = 0;
- needFormat = true;
- blankCount = 0; /* don't need blanks for line joining now */
- return (result);
- }
-
-
- /*
- * Flush current paragraph, if there's any text in it
- */
-
- static Boolean
- DoBreak (void)
- {
- return (outChars == 0 || (AddChar (cr) && FlushPara ()));
- }
-
-
- /*
- * Flush current paragraph, regardless of whether there's anything
- * in it or not.
- */
-
- static Boolean
- DoSpace (void)
- {
- return (AddChar (cr) && FlushPara ());
- }
-
-
- /*
- * FormatText - convert text file to MacWrite file
-
- * Winner of 1987 "Ugliest Function" award. That goes for the
- * comments, too. They've been revised so often, I barely
- * understand them myself, so good luck.
- *
- * saveQueue is the text that's been matched partially. It's saved
- * for writing to output in case no match is found: the text is
- * put back on the pushback stack, the first char is written out
- * and a match is sought starting with the next char.
- *
- * The biggest headache about this thing is getting it to work properly
- * with all legal combinations of paragraph style settings.
- *
- * "Each line a paragraph" is never on if blank line paragraphing is
- * on, and vice-versa. Use of a paragraph marker is compatible with
- * all options.
- *
- * Input lines containing only imbedded commands are always ignored
- * for paragraphing purposes - that is, they're not treated as blank
- * lines.
- */
-
- static Boolean
- FormatText (void)
- {
- char c; /* input char */
- Str255 saveQueue; /* input save queue */
- short index; /* command index */
- short inLineChars; /* # chars from current line added to */
- /* output (for blank line and beginning */
- /* of line detection) */
- Boolean cmdsInLine; /* whether commands were found in line */
- Boolean xNeedFormat;
-
- if (!WritePrelude (fWord))
- return (false);
-
- font = applFont;
- size = 12;
- style = 0; /* plain */
-
- InitMarkStates (); /* clear match info */
- pushBack[0] = 0; /* and file queues */
- saveQueue[0] = 0;
- inLineChars = 0; /* no chars added from current line */
- blankCount = 0; /* no blanks needed for line joining yet */
- cmdsInLine = false; /* no commands in line yet */
- paraCount = 0; /* no paragraphs yet */
- outChars = 0; /* no chars in current paragraph */
- fmtCount = 0; /* no formats yet, either */
- needFormat = true; /* generate format on next char written */
-
- while (ReadTextChar (&c)) /* while not eof on text file */
- {
- saveQueue[++saveQueue[0]] = c;
- switch (index = CheckMarkers (c))
- {
-
- case -2: /* no match */
- /*
- * Push back saved text, read first char and add to current
- * paragraph. If the paragraph is done, flush it to disk, and
- * start looking for a new marker.
- *
- * Note what happens on cr. It is read, saved, fails to match, and
- * is pushed back. If there are other chars before it in pushback,
- * they'll be reprocessed (except first of them, which is added to
- * para). Eventually, the cr becomes first thing in pushback. It is
- * read, saved, fails to match, and is pushed back, Then it's read
- * again, and causes para flushing according to para style rules.
- *
- * Carriage return check is here only. Cr never matches any marker
- * char, since it can't be typed into one. Therefore it's not possible
- * to get any other return value from CheckMarkers when c == cr.
- */
-
- PushSavedText (saveQueue);
- InitMarkStates ();
- (void) ReadTextChar (&c);
-
- /*
- * If the character is not a carriage return, then it is simply
- * added to the current paragraph. The exception occur if this is
- * the first char of the line:
- *
- * (i) If the character is whitespace at the beginning of the line,
- * then the line begins a new paragraph, so flush the previous
- * paragraph, if there's anything there.
- *
- * (ii) Dribble out any blanks needed to take care of any line
- * joining that might be going on. Delay putting out any format
- * change that might be pending until right before the char following
- * the blanks - it's safe to do this because a format must have
- * already been written for there to be any need to join with spaces.
- */
-
- if (c != cr)
- {
- if (inLineChars == 0) /* if start of line... */
- {
- if (c == ' ' || c == '\t')
- {
- if (!DoBreak ())
- return (false);
- }
- else
- {
- xNeedFormat = needFormat;
- needFormat = false;
- while (blankCount > 0)
- {
- if (!AddChar (' '))
- return (false);
- --blankCount;
- }
- needFormat = xNeedFormat;
- }
- }
- if (!AddChar (c))
- return (false);
- ++inLineChars;
- }
-
- /*
- * Char is cr. If line only contained commands, ignore it.
- * Else if every line begins a paragraph, flush it. Else if
- * line is blank and blank lines begin paragraphs. Else it's just
- * the end of the current line and it will be joined to the text of
- * the next line unless the paragraph gets flushed first - so figure
- * out how many spaces to put out before the next line is written.
- * The writing happens when we actually get a line to be joined, not
- * here, so that spaces don't get tacked onto lines that may not need
- * them.
- */
-
- else
- {
- if (!(cmdsInLine && inLineChars == 0))
- {
- if (paraStyle.pEachLine) /* always force line */
- {
- if (!DoSpace ())
- return (false);
- }
- else if (inLineChars == 0) /* if blank line */
- {
- if (paraStyle.pBlankLine) /* and they are paras */
- {
- if (!DoBreak () || !DoSpace ())
- return (false);
- }
- else /* ignore the line, but set blank count */
- {
- SetBlankCount ();
- }
- }
- else
- {
- SetBlankCount ();
- }
- }
- inLineChars = 0;
- cmdsInLine = false;
- }
- break;
-
- case -1: /* match, but not entirely yet */
- break; /* just keep looking */
-
- default: /* found complete match */
- /*
- * Since the text matched a marker, it either appears in the
- * output as a format change or signals the beginning of a
- * paragraph. In either case, it doesn't appear in the output
- * as literal text, so can toss the save queue.
- *
- * If it's a format, process the format spec, setting the flag
- * indicating that a new format will be needed on the next char
- * written out (but only if the format actually *changes*).
- *
- * If it's a para marker, flush the current paragraph.
- */
- cmdsInLine = true;
-
- if (index == paraMarkIdx)
- {
- if (!DoSpace ())
- return (false);
- }
- else if (index == pageMarkIdx)
- {
- /* not implemented */
- # ifdef debug
- DisplayString ("\p{Page break not implemented}");
- # endif
- }
- else
- ChangeCurFormat (&mapSpec[index]);
-
- saveQueue[0] = 0; /* toss pushback */
- InitMarkStates (); /* start looking for new marker */
- break;
-
- }
- }
-
- /*
- * It's the end of the file, but we might have been left in the middle
- * of matching a mark, so return saved text to the pushback queue and
- * write it out. Note that if we *were* in the middle of mark testing,
- * then this is not the beginning of a line and no spaces need to be
- * dribbled.
- */
-
- PushSavedText (saveQueue);
- while (ReadTextChar (&c))
- {
- if (!AddChar (c))
- return (false);
- }
-
- /*
- * There must be at least one text paragraph. If there aren't any
- * make sure one is generated. If nothing at all has been formatted,
- * there won't even be any formats generated, so force one. Then
- * flush the paragraph.
- *
- * If there already were paragraphs, then see whether unflushed
- * characters are being held and flush if so.
- */
-
- if (paraCount == 0)
- {
- if (fmtCount == 0)
- {
- if (!NewFormat ())
- return (false);
- }
- if (!FlushPara ())
- return (false);
- }
- else if (outChars != 0)
- {
- if (!FlushPara ())
- return (false);
- }
-
- if (!WritePostlude (fWord, paraCount, paraInfo))
- return (false);
-
- return (true);
- }
-
-
- void
- TextToWrite (void)
- {
- SFReply inFile;
- SFReply outFile;
- Boolean result;
-
- if (FindEmptyMark ())
- return;
-
- if (!GetInputFile ("\pOpen", 'TEXT', &inFile))
- return;
- CopyString (inFile.fName, outFile.fName); /* make default output name */
- AppendString ("\p.mw", outFile.fName);
- if (!GetOutputFile (true, outFile.fName, 0, &outFile))
- return;
-
- if (!OpenInputFile (&inFile, &fText))
- return;
-
- InitTextStream (fText);
-
- if (!OpenOutputFile (&outFile, 'MACA', 'WORD', &fWord))
- {
- (void) FSClose (fText);
- return;
- }
-
- MeterBegin ();
- MeterPos (5, 0);
- MeterString ("\pFormatting: ");
- MeterString (inFile.fName);
- MeterPos (64, 1);
- MeterString ("\pTo: ");
- MeterString (outFile.fName);
- StartMeterPercentInfo ();
-
- /*
- * Fire up a debug output window if that option is true. If it's
- * false, then not executing DisplayWindow results in all output calls
- * being ignored. Not as quick as testing them all, but easier.
- * (None of it will be true if compilation has all this stuff turned
- * off, anyway.)
- */
-
- # ifdef debug
- if (debugOut)
- DisplayWindow (outFile.fName, true);
- DisplayString ("\pInput file size ");
- DisplayLong (tFileSize);
- DisplayLn ();
- # endif
-
- fmtBuf = (FAHandle) NewHandle (0L); /* get format array buffer */
- maxFormats = 0;
- paraInfo = (PIAHandle) NewHandle (0L); /* get para info array buffer */
- maxParas = 0;
-
- result = FormatText ();
- (void) FSClose (fText);
- (void) FSClose (fWord);
- (void) FlushVol (/*fWord*/ nil, outFile.vRefNum);
- if (result == false)
- FSDelete (outFile.fName, outFile.vRefNum);
-
- DisposeHandle ((Handle) fmtBuf);
- DisposeHandle ((Handle) paraInfo);
- MeterEnd ();
- }
-