1 line
54 KiB
C
Executable File
1 line
54 KiB
C
Executable File
/* Copyright (c) 2017, Computer History Museum
|
||
All rights reserved.
|
||
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to
|
||
the limitations in the disclaimer below) provided that the following conditions are met:
|
||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||
disclaimer in the documentation and/or other materials provided with the distribution.
|
||
* Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products
|
||
derived from this software without specific prior written permission.
|
||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE
|
||
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||
DAMAGE. */
|
||
|
||
#include "concentrator.h"
|
||
#define FILE_NUM 146
|
||
|
||
ConConProH ConConProfiles;
|
||
|
||
/************************************************************************
|
||
* Local functions
|
||
************************************************************************/
|
||
OSErr ConConMessParse(ConConMessPrsPtr context);
|
||
OSErr ConConMessOutput(MessHandle messH, ConConElmH cceh, StackHandle stack, short baseQuote, PETEHandle pte,ConConOutFilterProcPtr filter,void *userData);
|
||
void ConConElmSetHeader(ConConElmH cceh,PStr headerName);
|
||
Boolean ConConParaAdvance(ConConMessPrsPtr context);
|
||
uLong HandleFindChar(UHandle h, uLong offset, uLong terminal, Byte cToFind);
|
||
ConConElmH ConConElmFind(ConConMessElmPtr me, ConConElmH cceh);
|
||
short ConConElmMatch(ConConMessElmPtr me, ConConElmH cceh);
|
||
OSErr ConConElmOutput(ConConMessElmPtr me,MessHandle messH,ConConElmH cceh,PETEHandle pte,StackHandle stack,ConConOutFilterProcPtr filter,void *userData);
|
||
void ConConFixTrimOffsets(UHandle text,long *start,long *stop);
|
||
OSErr ConConCopy(PETEHandle fromPTE, PETEHandle toPTE, long start, long stop, Boolean flatten);
|
||
Boolean ConConFixTruncOffsets(UHandle text,long start,long goalSize,long *stop,PStr ellipsis,ConConMessElmPtr oldME,StackHandle stack);
|
||
OSErr PeteParaInfoCopy(PETEHandle fromPTE,uLong fromOffset,PETEHandle toPTE,uLong toOffset,Boolean breakBefore);
|
||
PStr ConConTextSnipLo(PStr s,short sizeOfS,UHandle h,uLong start,uLong stop);
|
||
#define ConConTextSnip(s,h,start,stop) ConConTextSnipLo(s,sizeof(s),h,start,stop)
|
||
OSErr ConConInsertCRIfNeeded(ConConMessElmPtr lastME,ConConMessElmPtr me,ConConElmH cceh,PETEHandle fromPTE,PETEHandle toPTE);
|
||
#ifdef DEBUG
|
||
void PeteUpdate14(PETEHandle pte); void PeteUpdate14(PETEHandle pte) {if (BUG14) PeteUpdate(pte);}
|
||
#else
|
||
#define PeteUpdate14(pte)
|
||
#endif
|
||
Boolean ConConFixTruncByWord(UPtr start, UPtr *spotP, UPtr end,short *quoteChars);
|
||
Boolean ConConFixTruncByPara(UPtr start, UPtr *spotP, UPtr end);
|
||
Boolean ConConIsParaAttribution(ConConMessPrsPtr context);
|
||
Boolean ConConIsParaForwardOn(ConConMessPrsPtr context);
|
||
Boolean ConConIsParaForwardOff(ConConMessPrsPtr context);
|
||
Boolean ConConIsParaQuoteOn(ConConMessPrsPtr context);
|
||
Boolean ConConIsParaQuoteOff(ConConMessPrsPtr context);
|
||
Boolean ConConIsParaSigIntro(ConConMessPrsPtr context);
|
||
Boolean ConConIsAttachment(ConConMessPrsPtr context);
|
||
Boolean ConConMightAttribution(PStr s);
|
||
Boolean ConConMightForwardOn(PStr s,ConConMessPrsPtr context);
|
||
Boolean ConConMightForwardOff(PStr s,ConConMessPrsPtr context);
|
||
Boolean ConConMightQuoteOn(PStr s);
|
||
Boolean ConConMightQuoteOff(PStr s);
|
||
Boolean ConConDigestSeparator(PStr s);
|
||
Boolean ConConMightSigIntro(PStr s);
|
||
Boolean ConConMightAttachment(PStr s);
|
||
Boolean ConConAllWhite(PStr s);
|
||
#define ConConOrdinaryishType(t) ((t)==conConParaTypeOrdinary || (t)==conConParaTypeBlank || (t)==conConParaTypeWhite)
|
||
Boolean TSIsForward(TOCHandle tocH,short sumNum);
|
||
Boolean TSIsDigest(TOCHandle tocH,short sumNum);
|
||
void ConConDisposePro(ConConProH ccph);
|
||
void ConConDisposeElm(ConConElmH cceh);
|
||
ConConProH ConConXML2Profile(XMLBinPtr xmlbP);
|
||
ConConElmH ConConXML2Elm(XMLBinPtr xmlbP);
|
||
PStr ConConXMLContentShortValue(XMLBinPtr xmlbP,PStr string);
|
||
short ConConXMLContentID(XMLBinPtr xmlbP);
|
||
OSErr ConConXML2Output(XMLBinPtr xmlbP,ConConElmH cceh);
|
||
|
||
/************************************************************************
|
||
* ConConInit - initialize the content concentrator
|
||
************************************************************************/
|
||
OSErr ConConInit(void)
|
||
{
|
||
UHandle profileH = GetResource('TEXT',CONCON_PROFILES_TEXT);
|
||
StackHandle conConStack = nil;
|
||
OSErr err;
|
||
|
||
if (!profileH) return fnfErr;
|
||
|
||
err = StackInit(sizeof(XMLBinary),&conConStack);
|
||
if (!(err = XMLParse(profileH,ConConKeyStrn,nil,conConStack)))
|
||
{
|
||
XMLBinary xmlb;
|
||
ConConProH ccph;
|
||
#ifdef DEBUG
|
||
Accumulator a;
|
||
|
||
Zero(a);
|
||
XMLBinStackPrint(&a,conConStack);
|
||
LineLog(LOG_MORE,CONCON_DEBUG_FMT,LDRef(a.data),a.offset);
|
||
AccuZap(a);
|
||
#endif
|
||
|
||
while (!StackPop(&xmlb,conConStack))
|
||
{
|
||
if (ccph = ConConXML2Profile(&xmlb))
|
||
LL_Queue(ConConProfiles,ccph,(ConConProH));
|
||
XMLBinDispose(&xmlb);
|
||
}
|
||
|
||
}
|
||
XMLBinStackDispose(conConStack);
|
||
|
||
return noErr;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConXML2Profile - turn an xml binary into a concentrator profile
|
||
************************************************************************/
|
||
ConConProH ConConXML2Profile(XMLBinPtr xmlbP)
|
||
{
|
||
ConConProH ccph = NewZH(ConConProfile);
|
||
ConConElmH cceh;
|
||
XMLNVP nvp;
|
||
short i;
|
||
XMLBinary innerXMLB;
|
||
|
||
if (ccph)
|
||
{
|
||
// search for the name attribute
|
||
while (!StackPop(&nvp,xmlbP->attribs))
|
||
{
|
||
if (nvp.type==conConKeyName)
|
||
{
|
||
uLong hash = Hash(nvp.shortValue);
|
||
PSCopy((*ccph)->name,nvp.shortValue);
|
||
(*ccph)->hash = hash;
|
||
}
|
||
else
|
||
ASSERT(0);
|
||
XMLNVPDispose(&nvp);
|
||
}
|
||
ASSERT(*(*ccph)->name);
|
||
|
||
// Now grab the elements
|
||
for (i=0;!StackItem(&innerXMLB,i,xmlbP->contents);i++)
|
||
{
|
||
ASSERT(innerXMLB.self.type==kElementTag);
|
||
ASSERT(innerXMLB.self.id==conConKeyElement);
|
||
if (cceh=ConConXML2Elm(&innerXMLB))
|
||
LL_Queue((*ccph)->cceh,cceh,(ConConElmH));
|
||
else
|
||
ASSERT(0);
|
||
}
|
||
}
|
||
|
||
return ccph;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConXML2Elm - turn an xml binary into a concentrator profile element
|
||
************************************************************************/
|
||
ConConElmH ConConXML2Elm(XMLBinPtr xmlbP)
|
||
{
|
||
ConConElmH cceh = NewZH(ConConElement);
|
||
XMLBinary innerXMLB;
|
||
long num;
|
||
short i;
|
||
|
||
// we don't do attributes here
|
||
ASSERT(!xmlbP->attribs);
|
||
|
||
// we do do contents
|
||
for (i=0;!StackItem(&innerXMLB,i,xmlbP->contents);i++)
|
||
if (innerXMLB.self.type==kContent)
|
||
{
|
||
ASSERT(!(*cceh)->type);
|
||
(*cceh)->type = innerXMLB.self.id;
|
||
ASSERT((*cceh)->type>=conConTypeAny && (*cceh)->type<conConTypeComplete);
|
||
}
|
||
else if (innerXMLB.self.type==kElementTag)
|
||
{
|
||
switch (innerXMLB.self.id)
|
||
{
|
||
case conConKeyName:
|
||
ASSERT((*cceh)->type==conConTypeHeader);
|
||
ConConXMLContentShortValue(&innerXMLB,GlobalTemp);
|
||
PSCopy((*cceh)->headerName,GlobalTemp);
|
||
num = FindSTRNIndex(InterestHeadStrn,GlobalTemp);
|
||
(*cceh)->headerID = num;
|
||
break;
|
||
case conConKeyOutput:
|
||
ConConXML2Output(&innerXMLB,cceh);
|
||
break;
|
||
case conConKeyElement:
|
||
ASSERT(0); // hierarchy goes here ... someday
|
||
break;
|
||
default:
|
||
ASSERT(0);
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
ASSERT(0);
|
||
|
||
return cceh;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConXMLContentShortValue - grab the short name of the content inside a tag
|
||
************************************************************************/
|
||
PStr ConConXMLContentShortValue(XMLBinPtr xmlbP,PStr string)
|
||
{
|
||
XMLBinary innerXMLB;
|
||
|
||
*string = 0;
|
||
|
||
while (!StackPop(&innerXMLB,xmlbP->contents))
|
||
{
|
||
ASSERT(innerXMLB.self.type==kContent);
|
||
if (innerXMLB.self.type==kContent)
|
||
{
|
||
ASSERT(*innerXMLB.self.shortValue);
|
||
ASSERT(!*string);
|
||
PCopy(string,innerXMLB.self.shortValue);
|
||
}
|
||
XMLBinDispose(&innerXMLB);
|
||
}
|
||
|
||
return string;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConXMLContentID - grab the id of the content inside a tag
|
||
************************************************************************/
|
||
short ConConXMLContentID(XMLBinPtr xmlbP)
|
||
{
|
||
XMLBinary innerXMLB;
|
||
short id = -1;
|
||
|
||
while (!StackPop(&innerXMLB,xmlbP->contents))
|
||
{
|
||
ASSERT(innerXMLB.self.type==kContent);
|
||
if (innerXMLB.self.type==kContent)
|
||
id = innerXMLB.self.id;
|
||
else
|
||
ASSERT(0);
|
||
XMLBinDispose(&innerXMLB);
|
||
}
|
||
|
||
return id;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConXML2Output - turn some binary xml into an output descriptor
|
||
************************************************************************/
|
||
OSErr ConConXML2Output(XMLBinPtr xmlbP,ConConElmH cceh)
|
||
{
|
||
XMLBinary innerXMLB;
|
||
long num;
|
||
|
||
while (!StackPop(&innerXMLB,xmlbP->contents))
|
||
{
|
||
if (innerXMLB.self.type==kElementTag || innerXMLB.self.type==kEmptyElementTag)
|
||
{
|
||
switch (innerXMLB.self.id)
|
||
{
|
||
case conConOutModTrim:
|
||
(*cceh)->outDesc.trim = true;
|
||
break;
|
||
case conConOutModFlatten:
|
||
(*cceh)->outDesc.flatten = true;
|
||
break;
|
||
case conConQualBytes:
|
||
ConConXMLContentShortValue(&innerXMLB,GlobalTemp);
|
||
StringToNum(&GlobalTemp,&num);
|
||
(*cceh)->outDesc.bytes = num;
|
||
break;
|
||
case conConOutModQuoteIncrement:
|
||
ConConXMLContentShortValue(&innerXMLB,GlobalTemp);
|
||
StringToNum(&GlobalTemp,&num);
|
||
(*cceh)->outDesc.quoteIncrement = num ? num : 1;
|
||
break;
|
||
default:
|
||
ASSERT(0);
|
||
break;
|
||
}
|
||
}
|
||
else if (innerXMLB.self.type==kContent)
|
||
{
|
||
(*cceh)->outDesc.type = innerXMLB.self.id;
|
||
ASSERT(innerXMLB.self.id);
|
||
}
|
||
else
|
||
ASSERT(0);
|
||
|
||
XMLBinDispose(&innerXMLB);
|
||
}
|
||
|
||
return noErr;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConDispose - get rid of the content concentrator profiles
|
||
************************************************************************/
|
||
void ConConDispose(void)
|
||
{
|
||
ConConProH ccph;
|
||
|
||
while (ccph=ConConProfiles)
|
||
{
|
||
LL_Remove(ConConProfiles,ccph,(ConConProH));
|
||
ConConDisposePro(ccph);
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConDisposePro - get rid of a content concentrator profile
|
||
************************************************************************/
|
||
void ConConDisposePro(ConConProH ccph)
|
||
{
|
||
ConConElmH cceh;
|
||
|
||
while (cceh=(*ccph)->cceh)
|
||
{
|
||
LL_Remove((*ccph)->cceh,cceh,(ConConElmH));
|
||
ConConDisposeElm(cceh);
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConDisposeElm - get rid of a content concentrator profile
|
||
************************************************************************/
|
||
void ConConDisposeElm(ConConElmH cceh)
|
||
{
|
||
ZapHandle((*cceh)->outDesc.text);
|
||
ZapHandle(cceh);
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConMessR - concentrate a message into (the insertion point of)
|
||
* a PETEHandle, using a resource to find the concentrator profile
|
||
************************************************************************/
|
||
OSErr ConConMessR(MessHandle messH,PETEHandle pte,short conConID,ConConOutFilterProcPtr filter,void *userData)
|
||
{
|
||
Str63 profile;
|
||
return ConConMess(messH,pte,GetRString(profile,conConID),filter,userData);
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConTS - concentrate a message into (the insertion point of)
|
||
* a PETEHandle
|
||
************************************************************************/
|
||
OSErr ConConMess(MessHandle messH,PETEHandle pte,PStr profileStr,ConConOutFilterProcPtr filter,void *userData)
|
||
{
|
||
if (HasFeature(featureConCon))
|
||
{
|
||
ConConProH ccph = ConConProFind(profileStr);
|
||
StackHandle messStack;
|
||
OSErr err;
|
||
ConConMessParseContext context;
|
||
|
||
if (!ccph) return fnfErr;
|
||
|
||
if (err=StackInit(sizeof(ConConMessElm),&messStack)) return err;
|
||
|
||
// Make sure the message is ready to go
|
||
HiliteOddReply(messH);
|
||
|
||
// Boring context setup
|
||
Zero(context);
|
||
context.messH = messH;
|
||
context.messStack = messStack;
|
||
context.body = (*messH)->bodyPTE;
|
||
context.end = PeteLen(context.body);
|
||
context.presumeForward = TSIsForward((*messH)->tocH,(*messH)->sumNum);
|
||
context.isDigest = TSIsDigest((*messH)->tocH,(*messH)->sumNum);
|
||
context.state = conConTypeHeader;
|
||
PeteGetTextAndSelection(context.body,&context.text,nil,nil);
|
||
ConConParaAdvance(&context); // Load up the first paragraph
|
||
|
||
// Do the parsing
|
||
err = ConConMessParse(&context);
|
||
|
||
// Do the output
|
||
if (!err)
|
||
{
|
||
ConConElmOutput(nil,nil,nil,nil,nil,nil,nil);
|
||
err = ConConMessOutput(messH,(*ccph)->cceh,messStack,0,pte,filter,userData);
|
||
}
|
||
|
||
ZapHandle(messStack);
|
||
|
||
return err;
|
||
}
|
||
else
|
||
return fnfErr;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConMessParse - parse a message to get ready for the content concentrator
|
||
************************************************************************/
|
||
OSErr ConConMessParse(ConConMessPrsPtr context)
|
||
{
|
||
long spot;
|
||
Str255 s;
|
||
Boolean needRescan;
|
||
Boolean needQueue;
|
||
|
||
#ifdef DEBUG
|
||
if (RunType==Debugging)
|
||
ComposeLogS(LOG_MORE,nil,"\pConcentrating %p",MyGetWTitle(GetMyWindowWindowPtr((*context->messH)->win),s));
|
||
#endif
|
||
|
||
do
|
||
{
|
||
if (!ConConParaAdvance(context)) break; // grab the next paragraph
|
||
|
||
rescan:
|
||
Zero(context->thisElm);
|
||
needRescan = false;
|
||
needQueue = true;
|
||
|
||
if (context->quoteLevel>context->thisPara.quoteLevel) break; // if the quote level is now
|
||
// less than when we started
|
||
// we've come to the end of the
|
||
// quote we have been processing
|
||
|
||
// Assume this element is this paragraph
|
||
context->thisElm.start = context->thisPara.start;
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
context->thisElm.quoteLevel = context->thisPara.quoteLevel;
|
||
|
||
// Adjust the element
|
||
switch (context->state)
|
||
{
|
||
////////////////////////
|
||
case conConTypeHeader:
|
||
////////////////////////
|
||
{
|
||
if (context->thisElm.stop==context->thisElm.start+1)
|
||
{
|
||
// message separator found
|
||
context->thisElm.type = conConTypeSeparator;
|
||
context->state = conConTypeBodyInitial;
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
// We're in the headers. The next paragraph may belong with
|
||
// us, since the headers may be folded. Deal.
|
||
while (context->nextPara.stop>context->nextPara.start && // there is a next paragraph that's not empty
|
||
IsWhite((*context->text)[context->nextPara.start])) // the first character is whitespace
|
||
ConConParaAdvance(context); // include it!
|
||
|
||
// Point the end of the element at the end of this paragraph
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
|
||
#ifdef DEBUG
|
||
MakePStr(s,(*context->text)+context->thisElm.start,context->thisElm.stop-context->thisElm.start);
|
||
#endif
|
||
|
||
// Ok, header gathered. Let's see what we have
|
||
context->thisElm.type = conConTypeHeader;
|
||
spot = HandleFindChar(context->text,context->thisElm.start,context->thisElm.stop,':');
|
||
if (spot < context->thisElm.stop)
|
||
{
|
||
// we have a header name!
|
||
MakePStr(s,(*context->text)+context->thisElm.start,spot-context->thisElm.start);
|
||
TrimAllWhite(s);
|
||
PSCopy(context->thisElm.headerName,s);
|
||
context->thisElm.headerID = FindSTRNIndex(InterestHeadStrn,context->thisElm.headerName);
|
||
}
|
||
else
|
||
{
|
||
// hmmmmmmmmmm. No colon found, so this isn't really a header.
|
||
// We should do something clever here to figure out what this is,
|
||
// like look ahead, behind, or over our right shoulder. For now,
|
||
// however, we're just going to drool on ourselves
|
||
GetRString(context->thisElm.headerName,UNTITLED);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypeBodyInitial:
|
||
////////////////////////
|
||
{
|
||
if (context->thisElm.quoteLevel)
|
||
context->state = context->presumeForward ? conConTypeForward : conConTypeQuote;
|
||
else if (ConConIsParaAttribution(context))
|
||
context->state = conConTypeAttribution;
|
||
else if (ConConIsParaForwardOn(context))
|
||
context->state = conConTypeForwardOn;
|
||
else if (ConConIsParaQuoteOn(context))
|
||
context->state = conConTypeQuoteOn;
|
||
else if (ConConIsParaSigIntro(context))
|
||
context->state = conConTypeSigIntro;
|
||
else
|
||
context->state = conConTypeBody;
|
||
needRescan = true;
|
||
needQueue = false;
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypePlainForward:
|
||
////////////////////////
|
||
{
|
||
context->thisElm.type = conConTypePlainForward;
|
||
|
||
// We're in a plaintext quote; collect until we're not
|
||
do
|
||
{
|
||
if (ConConIsParaForwardOff(context))
|
||
{
|
||
context->state = conConTypeForwardOff;
|
||
needRescan = true;
|
||
break;
|
||
}
|
||
}
|
||
while (ConConParaAdvance(context));
|
||
|
||
if (context->state != conConTypeForward)
|
||
context->thisElm.stop = context->lastPara.stop;
|
||
else
|
||
{
|
||
context->thisElm.stop = context->lastPara.stop;
|
||
context->state = conConTypeComplete;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypeOutlookQuote:
|
||
////////////////////////
|
||
{
|
||
context->thisElm.type = conConTypeOutlookQuote;
|
||
|
||
// We're in a plaintext quote; collect until we're not
|
||
do
|
||
{
|
||
if (ConConIsParaQuoteOff(context))
|
||
{
|
||
context->state = conConTypeQuoteOff;
|
||
needRescan = true;
|
||
break;
|
||
}
|
||
}
|
||
while (ConConParaAdvance(context));
|
||
|
||
if (context->state != conConTypeOutlookQuote)
|
||
context->thisElm.stop = context->lastPara.stop;
|
||
else
|
||
{
|
||
context->thisElm.stop = context->lastPara.stop;
|
||
context->state = conConTypeComplete;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypeForward:
|
||
////////////////////////
|
||
{
|
||
context->thisElm.type = conConTypeForward;
|
||
|
||
// We're in a plaintext quote; collect until we're not
|
||
while (context->nextPara.quoteLevel>=context->thisElm.quoteLevel && ConConParaAdvance(context));
|
||
|
||
if (context->thisPara.type==conConParaTypeComplete)
|
||
context->thisElm.stop = context->lastPara.stop;
|
||
else
|
||
{
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
context->state = conConTypeBody;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
|
||
////////////////////////
|
||
case conConTypeSignature:
|
||
////////////////////////
|
||
{
|
||
context->thisElm.type = conConTypeSignature;
|
||
|
||
// We're in a signature; collect until something happens
|
||
while (ConConOrdinaryishType(context->nextPara.type) && !context->nextPara.quoteLevel && ConConParaAdvance(context));
|
||
|
||
if (context->thisPara.type!=conConParaTypeComplete)
|
||
{
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
context->state = conConTypeQuote;
|
||
}
|
||
else
|
||
{
|
||
context->thisElm.stop = context->lastPara.stop;
|
||
context->state = conConTypeComplete;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypeBody:
|
||
////////////////////////
|
||
{
|
||
context->thisElm.type = conConTypeBody;
|
||
|
||
// We're in the body, in the original text. Collect original text forever
|
||
do
|
||
{
|
||
if (ConConIsParaForwardOn(context))
|
||
{
|
||
context->state = conConTypeForwardOn;
|
||
break;
|
||
}
|
||
if (ConConIsParaQuoteOn(context))
|
||
{
|
||
context->state = conConTypeQuoteOn;
|
||
break;
|
||
}
|
||
else if (ConConIsParaSigIntro(context))
|
||
{
|
||
context->state = conConTypeSignature;
|
||
break;
|
||
}
|
||
else if (ConConIsParaAttribution(context))
|
||
{
|
||
context->state = conConTypeAttribution;
|
||
break;
|
||
}
|
||
}
|
||
while (!context->nextPara.quoteLevel && ConConParaAdvance(context));
|
||
|
||
if (context->state == conConTypeBody)
|
||
{
|
||
if (context->nextPara.quoteLevel)
|
||
{
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
context->state = context->presumeForward ? conConTypeForward : conConTypeQuote;
|
||
}
|
||
else
|
||
{
|
||
context->thisElm.stop = context->lastPara.stop;
|
||
context->state = conConTypeComplete;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
context->thisElm.stop = context->lastPara.stop;
|
||
needRescan = true;
|
||
}
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypeAttribution:
|
||
////////////////////////
|
||
{
|
||
context->thisElm.type = conConTypeAttribution;
|
||
context->thisElm.start = context->thisPara.start;
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
context->state = conConTypeQuote;
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypeForwardOn:
|
||
////////////////////////
|
||
{
|
||
context->thisElm.type = conConTypeForwardOn;
|
||
context->thisElm.start = context->thisPara.start;
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
context->state = conConTypePlainForward;
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypeQuoteOn:
|
||
////////////////////////
|
||
{
|
||
context->thisElm.type = conConTypeQuoteOn;
|
||
context->thisElm.start = context->thisPara.start;
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
context->state = conConTypeOutlookQuote;
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypeQuoteOff:
|
||
////////////////////////
|
||
{
|
||
context->thisElm.type = conConTypeQuoteOff;
|
||
context->thisElm.start = context->thisPara.start;
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
context->state = conConTypeBodyInitial;
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypeForwardOff:
|
||
////////////////////////
|
||
{
|
||
context->thisElm.type = conConTypeForwardOff;
|
||
context->thisElm.start = context->thisPara.start;
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
context->state = conConTypeBodyInitial;
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypeSigIntro:
|
||
////////////////////////
|
||
{
|
||
context->thisElm.type = conConTypeSigIntro;
|
||
context->thisElm.start = context->thisPara.start;
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
context->state = conConTypeSignature;
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
case conConTypeQuote:
|
||
////////////////////////
|
||
{
|
||
while (context->nextPara.quoteLevel && ConConParaAdvance(context));
|
||
|
||
context->thisElm.type = conConTypeQuote;
|
||
context->thisElm.stop = context->thisPara.stop;
|
||
context->state = conConTypeBodyInitial;
|
||
break;
|
||
}
|
||
|
||
////////////////////////
|
||
default:
|
||
////////////////////////
|
||
{
|
||
// we weren't supposed to get here
|
||
context->err = unimpErr;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Post-process the element
|
||
if (!context->err)
|
||
{
|
||
|
||
#ifdef DEBUG
|
||
// put the first part of the element in the "headerName"
|
||
if (!(*context->thisElm.headerName))
|
||
ConConTextSnip(context->thisElm.headerName,context->text,context->thisElm.start,context->thisElm.stop);
|
||
#endif
|
||
|
||
// Put our element on the stack
|
||
if (needQueue && context->thisElm.start!=context->thisElm.stop)
|
||
{
|
||
context->lastElm = context->thisElm;
|
||
context->err = StackQueue(&context->thisElm,context->messStack);
|
||
#ifdef DEBUG
|
||
if (RunType==Debugging)
|
||
ComposeLogS(LOG_MORE,nil,"\pElement %r q%d %d-%d <20>%e<>",
|
||
ConConKeyStrn+context->thisElm.type,
|
||
context->thisElm.quoteLevel,
|
||
context->thisElm.start,
|
||
context->thisElm.stop,
|
||
context->thisElm.headerName);
|
||
#endif
|
||
}
|
||
|
||
// rescan the current element?
|
||
if (needRescan) goto rescan;
|
||
}
|
||
}
|
||
while (!context->err);
|
||
|
||
return context->err;
|
||
}
|
||
|
||
/************************************************************************
|
||
* The ConConIsPara... routines determine if the current paragraph is a
|
||
* particular interesting element. They differ from the ConConMight...
|
||
* routines because they take into account the context of the parser,
|
||
* (ie, grammar) whereas the Might... routines look only at the syntax
|
||
* of the paragraph itself
|
||
************************************************************************/
|
||
|
||
Boolean ConConIsParaAttribution(ConConMessPrsPtr context)
|
||
{
|
||
// Must be less quoted than what follows
|
||
if (context->thisPara.quoteLevel>=context->nextPara.quoteLevel) return false;
|
||
|
||
// And must have parsed as such
|
||
return context->thisPara.type==conConParaTypeAttr;
|
||
}
|
||
|
||
Boolean ConConIsParaForwardOn(ConConMessPrsPtr context)
|
||
{
|
||
// Must not be quoting here
|
||
if (context->thisPara.quoteLevel || context->nextPara.quoteLevel) return false;
|
||
|
||
// And must have parsed as such
|
||
return context->thisPara.type==conConParaTypeForwardOn;
|
||
}
|
||
|
||
Boolean ConConIsParaForwardOff(ConConMessPrsPtr context)
|
||
{
|
||
// Must not be quoting here
|
||
if (context->thisPara.quoteLevel || context->nextPara.quoteLevel) return false;
|
||
|
||
// And must have parsed as such
|
||
return context->thisPara.type==conConParaTypeForwardOff;
|
||
}
|
||
|
||
Boolean ConConIsParaQuoteOn(ConConMessPrsPtr context)
|
||
{
|
||
// Must not be quoting here
|
||
if (context->thisPara.quoteLevel) return false;
|
||
|
||
// And must have parsed as such
|
||
return context->thisPara.type==conConParaTypeQuoteOn;
|
||
}
|
||
|
||
Boolean ConConIsParaQuoteOff(ConConMessPrsPtr context)
|
||
{
|
||
// Must not be quoting here
|
||
if (context->thisPara.quoteLevel) return false;
|
||
|
||
// And must have parsed as such
|
||
return context->thisPara.type==conConParaTypeQuoteOff;
|
||
}
|
||
|
||
Boolean ConConIsParaSigIntro(ConConMessPrsPtr context)
|
||
{
|
||
// Quoting should not vary here
|
||
if (context->thisPara.quoteLevel != context->nextPara.quoteLevel) return false;
|
||
|
||
// And must have parsed as such
|
||
return context->thisPara.type==conConParaTypeSigIntro;
|
||
}
|
||
|
||
Boolean ConConIsAttachment(ConConMessPrsPtr context)
|
||
{
|
||
// no quoting
|
||
if (context->thisPara.quoteLevel) return false;
|
||
|
||
// And must have parsed as such
|
||
return context->thisPara.type==conConParaTypeAttachment;
|
||
}
|
||
|
||
|
||
/************************************************************************
|
||
* The ConConMight... routines use paragraph syntax to tentatively
|
||
* identify message elements
|
||
************************************************************************/
|
||
|
||
Boolean ConConAllWhite(PStr s)
|
||
{
|
||
short i;
|
||
|
||
if (s[0]>254 || s[0]==1) return false;
|
||
for (i=s[0];i>0;i++) if (!IsAnySP(s[i])) return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
Boolean ConConMightForwardOn(PStr s,ConConMessPrsPtr context)
|
||
{
|
||
// is it too long or too short?
|
||
if (*s<10 || *s>40) return false;
|
||
|
||
// -----Original Message-----
|
||
// --- begin forwarded text
|
||
if (s[1]=='-' && s[2]=='-' && s[3]=='-' &&
|
||
ItemFromResAppearsInStr(CONCON_FORWARD_ON,s,","))
|
||
return !ConConMightQuoteOn(s) || context->presumeForward;
|
||
|
||
return false;
|
||
}
|
||
|
||
Boolean ConConMightForwardOff(PStr s,ConConMessPrsPtr context)
|
||
{
|
||
// is it too long or too short?
|
||
if (*s<10 || *s>40) return false;
|
||
|
||
// -----End Original Message-----
|
||
// --- end forwarded text
|
||
if (s[1]=='-' && s[2]=='-' && s[3]=='-' &&
|
||
ItemFromResAppearsInStr(CONCON_FORWARD_OFF,s,","))
|
||
return !ConConMightQuoteOn(s) || context->presumeForward;
|
||
|
||
return false;
|
||
}
|
||
|
||
Boolean ConConMightQuoteOn(PStr s)
|
||
{
|
||
// is it too long or too short?
|
||
if (*s<10 || *s>40) return false;
|
||
|
||
// -----Original Message-----
|
||
// --- begin forwarded text
|
||
if (s[1]=='-' && s[2]=='-' && s[3]=='-' &&
|
||
ItemFromResAppearsInStr(CONCON_QUOTE_ON,s,","))
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
Boolean ConConMightQuoteOff(PStr s)
|
||
{
|
||
// is it too long or too short?
|
||
if (*s<10 || *s>40) return false;
|
||
|
||
// -----End Original Message-----
|
||
// --- end forwarded text
|
||
if (s[1]=='-' && s[2]=='-' && s[3]=='-' &&
|
||
ItemFromResAppearsInStr(CONCON_QUOTE_OFF,s,","))
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
Boolean ConConDigestSeparator(PStr s)
|
||
{
|
||
// is it too long or too short?
|
||
if (*s<4 || *s>40) return false;
|
||
|
||
if (s[1]=='-' && s[2]=='-' && s[3]=='-' && s[4]=='-')
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
Boolean ConConMightAttribution(PStr s)
|
||
{
|
||
// Must be a reasonable length
|
||
if (*s<4 || *s>127) return false;
|
||
|
||
// Must end with : and return
|
||
if (s[*s]!='\015' || s[*s-1]!=':') return false;
|
||
|
||
// Must begin with a word char
|
||
if (!IsWordChar[s[1]]) return false;
|
||
|
||
// well, that's as smart as I am
|
||
return true;
|
||
}
|
||
|
||
Boolean ConConMightSigIntro(PStr s)
|
||
{
|
||
return s[0]==4 && s[1]=='-' && s[2]=='-' && s[3]==' ' && s[4]=='\015';
|
||
}
|
||
|
||
Boolean ConConMightAttachment(PStr s)
|
||
{
|
||
return !AttLine2Spec(s,nil,false);
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConTextSnipLo - put a snip of text in a string
|
||
************************************************************************/
|
||
PStr ConConTextSnipLo(PStr s, short sizeOfS,UHandle h,uLong start,uLong stop)
|
||
{
|
||
Str255 localStr;
|
||
short bite;
|
||
|
||
if (stop-start<=sizeOfS-1)
|
||
{
|
||
MakePStr(localStr,*h+start,stop-start);
|
||
PCopy(s,localStr);
|
||
}
|
||
else
|
||
{
|
||
bite = (sizeOfS-1)/2;
|
||
MakePStr(localStr,*h+start,bite);
|
||
PCopy(s,localStr);
|
||
|
||
MakePStr(localStr,*h+stop-bite,bite);
|
||
PCatC(s,'<EFBFBD>');
|
||
PCat(s,localStr);
|
||
}
|
||
|
||
return s;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConParaAdvance - advance paragraph records on the context
|
||
* returns false if this para is not real
|
||
************************************************************************/
|
||
Boolean ConConParaAdvance(ConConMessPrsPtr context)
|
||
{
|
||
Str15 quoteStr;
|
||
long spot;
|
||
|
||
CycleBalls();
|
||
|
||
GetRString(quoteStr,QUOTE_PREFIX);
|
||
|
||
// shuffle the records
|
||
context->lastPara = context->thisPara;
|
||
context->thisPara = context->nextPara;
|
||
|
||
// parse for the next paragraph
|
||
context->spot = context->thisPara.start;
|
||
context->nextPara.start = context->thisPara.stop;
|
||
context->nextPara.stop = HandleFindChar(context->text,context->nextPara.start,context->end,'\015');
|
||
if (context->nextPara.stop<context->end) context->nextPara.stop++; // include the terminal return
|
||
#ifdef DEBUG
|
||
Zero(context->nextPara.paraStr);
|
||
ConConTextSnip(context->nextPara.paraStr,context->text,context->nextPara.start,context->nextPara.stop);
|
||
#endif
|
||
context->nextPara.quoteChars = 0;
|
||
|
||
// quote levels?
|
||
if (context->nextPara.start < context->end)
|
||
{
|
||
context->nextPara.quoteLevel = PeteIsExcerptAt(context->body,context->nextPara.start);
|
||
// ok, now here is an ugliness; we may have quote characters on top of excerpt bars
|
||
// These may simply be characters the user has chosen to put in, or they may
|
||
// indicate that excerpt was not used consistently. Bleah.
|
||
// in fullness of time, we may pay attention to the "lay of the land"
|
||
// in terms of whether or not this number of quote chars is used consistently
|
||
// from one para to the next. For now, we're keeping it simple.
|
||
for (spot=context->nextPara.start;spot<context->nextPara.stop;spot++)
|
||
{
|
||
if ((*context->text)[spot]==quoteStr[1]) context->nextPara.quoteChars++;
|
||
else break;
|
||
}
|
||
context->nextPara.quoteLevel += context->nextPara.quoteChars;
|
||
}
|
||
|
||
// set the type
|
||
if (context->nextPara.start >= context->end)
|
||
context->nextPara.type = conConParaTypeComplete;
|
||
else
|
||
{
|
||
Str255 s;
|
||
|
||
// Make it a string for convenience
|
||
MakePStr(s,*context->text+context->nextPara.start,context->nextPara.stop-context->nextPara.start);
|
||
|
||
if (s[0]==1 && s[1] == '\015')
|
||
context->nextPara.type = conConParaTypeBlank;
|
||
else if (ConConAllWhite(s))
|
||
context->nextPara.type = conConParaTypeWhite;
|
||
else if (ConConMightAttribution(s))
|
||
context->nextPara.type = conConParaTypeAttr;
|
||
else if (ConConMightForwardOn(s,context))
|
||
context->nextPara.type = conConParaTypeForwardOn;
|
||
else if (ConConMightForwardOff(s,context))
|
||
context->nextPara.type = conConParaTypeForwardOff;
|
||
else if (ConConMightQuoteOn(s))
|
||
context->nextPara.type = conConParaTypeQuoteOn;
|
||
else if (ConConMightQuoteOff(s) || context->isDigest && ConConDigestSeparator(s))
|
||
context->nextPara.type = conConParaTypeQuoteOff;
|
||
else if (ConConMightSigIntro(s))
|
||
context->nextPara.type = conConParaTypeSigIntro;
|
||
else if (ConConMightAttachment(s))
|
||
context->nextPara.type = conConParaTypeAttachment;
|
||
else
|
||
context->nextPara.type = conConParaTypeOrdinary;
|
||
}
|
||
|
||
// if we have data in "thisPara", return true
|
||
return context->thisPara.start < context->end;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConMessOutput - given a parsed message, output it to a PETEHandle
|
||
************************************************************************/
|
||
OSErr ConConMessOutput(MessHandle messH, ConConElmH cceh, StackHandle stack, short baseQuote, PETEHandle pte,ConConOutFilterProcPtr filter,void *userData)
|
||
{
|
||
ConConElmH thisEH;
|
||
ConConMessElm thisME;
|
||
OSErr err = noErr;
|
||
|
||
while (!StackPop(&thisME,stack))
|
||
{
|
||
// if we're less quoted than our base, bail
|
||
if (thisME.quoteLevel < baseQuote)
|
||
{
|
||
// put it back!
|
||
StackPush(&thisME,stack);
|
||
return noErr;
|
||
}
|
||
|
||
// find the profile element that matches us
|
||
if (thisEH = ConConElmFind(&thisME,cceh))
|
||
err = ConConElmOutput(&thisME,messH,thisEH,pte,stack,filter,userData);
|
||
else
|
||
err = fnfErr; // ack! no instructions on what to do with this!
|
||
// perhaps the right thing to do is just display
|
||
// here, but we'll worry about that later
|
||
}
|
||
|
||
return err;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConElmFind - find a message element in a list of profile elements
|
||
************************************************************************/
|
||
ConConElmH ConConElmFind(ConConMessElmPtr me, ConConElmH cceh)
|
||
{
|
||
ConConElmH foundEH = nil;
|
||
short match;
|
||
|
||
CycleBalls();
|
||
|
||
// We'll take all the matches
|
||
for (;cceh;cceh=(*cceh)->next)
|
||
{
|
||
match = ConConElmMatch(me,cceh);
|
||
|
||
if (match==0) foundEH = cceh;
|
||
if (match==1) break;
|
||
// match==-1 means we didn't match but should continue looking
|
||
}
|
||
|
||
return foundEH;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConElmMatch - does a message element match a profile element?
|
||
* -1 - element doesn't match, but matches may be found later
|
||
* 0 - element matches
|
||
* 1 - element doesn't match, and no more elements are allowed to match it
|
||
************************************************************************/
|
||
short ConConElmMatch(ConConMessElmPtr me, ConConElmH cceh)
|
||
{
|
||
if ((*cceh)->type == conConTypeAny) return 0; // wildcard
|
||
|
||
if (me->type != (*cceh)->type) return -1; // haven't gotten to a meaty enough element
|
||
|
||
// element types are the same. Now must qualify
|
||
if (me->type==conConTypeHeader)
|
||
{
|
||
if (*me->headerName && *(*cceh)->headerName)
|
||
{
|
||
Str63 name;
|
||
PSCopy(name,(*cceh)->headerName);
|
||
if (!StringSame(name,me->headerName)) return -1; // keep looking; names don't match
|
||
}
|
||
}
|
||
|
||
// do qualifiers here... someday
|
||
return 0; // it matches!
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConElmOutput - output a concentrator element
|
||
************************************************************************/
|
||
OSErr ConConElmOutput(ConConMessElmPtr me,MessHandle messH,ConConElmH cceh,PETEHandle pte,StackHandle stack,ConConOutFilterProcPtr filter,void *userData)
|
||
{
|
||
OSErr err = unimpErr;
|
||
long trimStart, trimStop;
|
||
UHandle text;
|
||
Boolean muckedWith;
|
||
Str63 ellipsis;
|
||
static ConConMessElm lastME;
|
||
long newTextStart, newTextEnd;
|
||
ConConOutDesc outDesc;
|
||
|
||
// reinit?
|
||
if (!me)
|
||
{
|
||
Zero(lastME);
|
||
return noErr;
|
||
}
|
||
|
||
// filter?
|
||
outDesc = (*cceh)->outDesc;
|
||
if (filter) (*filter)(&outDesc,me,messH,cceh,stack,pte,userData);
|
||
|
||
trimStart = me->start;
|
||
trimStop = me->stop;
|
||
|
||
PeteGetTextAndSelection(TheBody,&text,nil,nil);
|
||
PeteGetTextAndSelection(pte,nil,&newTextStart,nil);
|
||
|
||
// trim?
|
||
if (outDesc.trim)
|
||
ConConFixTrimOffsets(text,&trimStart,&trimStop);
|
||
|
||
switch (outDesc.type)
|
||
{
|
||
// The easy one; do nothing
|
||
case conConOutTypeRemove:
|
||
err = noErr;
|
||
break;
|
||
|
||
// Just copy it into the output
|
||
case conConOutTypeDisplay:
|
||
if (!(err=ConConInsertCRIfNeeded(&lastME,me,cceh,TheBody,pte)))
|
||
err = ConConCopy(TheBody,pte,trimStart,trimStop,outDesc.flatten);
|
||
lastME = *me;
|
||
break;
|
||
|
||
// Truncate after a certain size
|
||
case conConOutTypeTruncate:
|
||
muckedWith = ConConFixTruncOffsets(text,trimStart,outDesc.bytes,&trimStop,ellipsis,me,stack);
|
||
if (!(err=ConConInsertCRIfNeeded(&lastME,me,cceh,TheBody,pte)))
|
||
err = ConConCopy(TheBody,pte,trimStart,trimStop,outDesc.flatten);
|
||
if (!err && muckedWith)
|
||
{
|
||
long oldSel = PeteBumpSelection(pte,0);
|
||
|
||
if (outDesc.text)
|
||
{
|
||
err = PeteInsert(pte,-1,outDesc.text);
|
||
if (PeteCharAt(pte,oldSel-1)=='\015') PeteEnsureBreak(pte,oldSel);
|
||
PeteBumpSelection(pte,GetHandleSize(outDesc.text));
|
||
}
|
||
else
|
||
{
|
||
err = PeteInsertPtr(pte,-1,ellipsis+1,*ellipsis);
|
||
if (PeteCharAt(pte,oldSel-1)=='\015') PeteEnsureBreak(pte,oldSel);
|
||
PeteBumpSelection(pte,*ellipsis);
|
||
}
|
||
}
|
||
lastME = *me;
|
||
break;
|
||
}
|
||
|
||
if (outDesc.quoteIncrement)
|
||
{
|
||
short i;
|
||
|
||
newTextEnd = PeteBumpSelection(pte,0);
|
||
for (i=0;i<outDesc.quoteIncrement;i++)
|
||
PeteExcerpt(pte,newTextStart,newTextEnd);
|
||
}
|
||
|
||
PeteUpdate14(pte);
|
||
|
||
return err;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConInsertCRIfNeeded - in certain situations, we need to add returns
|
||
* while outputting elements
|
||
************************************************************************/
|
||
OSErr ConConInsertCRIfNeeded(ConConMessElmPtr lastME,ConConMessElmPtr me,ConConElmH cceh,PETEHandle fromPTE,PETEHandle toPTE)
|
||
{
|
||
long offset;
|
||
OSErr err = noErr;
|
||
|
||
PeteGetTextAndSelection(toPTE,nil,&offset,nil);
|
||
|
||
// are we inserting plain(er?) text after a quote?
|
||
if (lastME->quoteLevel > me->quoteLevel)
|
||
{
|
||
err = PeteInsertChar(toPTE,offset,'\015',nil);
|
||
if (!err) err = PeteEnsureBreak(toPTE,offset);
|
||
if (!err) err = PeteSetExcerptLevelAt(toPTE,offset,me->quoteLevel);
|
||
if (!err) PeteBumpSelection(toPTE,1);
|
||
}
|
||
|
||
// are we inserting a quote after non-attributional plain(er) text?
|
||
else if (lastME->type!=conConTypeSeparator && lastME->quoteLevel < me->quoteLevel && lastME->type!=conConTypeAttribution)
|
||
{
|
||
err = PeteInsertChar(toPTE,offset,'\015',nil);
|
||
if (!err) err = PeteEnsureBreak(toPTE,offset);
|
||
if (!err) err = PeteSetExcerptLevelAt(toPTE,offset,lastME->quoteLevel);
|
||
if (!err) PeteBumpSelection(toPTE,1);
|
||
}
|
||
else if (/*offset==PeteLen(toPTE) && */PeteCharAt(toPTE,offset-1)=='\015')
|
||
PeteEnsureBreak(toPTE,offset);
|
||
|
||
PeteUpdate14(toPTE);
|
||
|
||
return err;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConCopy - copy from one pte to another, playing the proper games for
|
||
* the Content Concentrator
|
||
************************************************************************/
|
||
OSErr ConConCopy(PETEHandle fromPTE, PETEHandle toPTE, long start, long stop, Boolean flatten)
|
||
{
|
||
short oldID;
|
||
OSErr err;
|
||
uLong selStart, selEnd;
|
||
|
||
// we're going to need this later...
|
||
PeteGetTextAndSelection(toPTE,nil,&selStart,nil);
|
||
selEnd = PeteLen(toPTE);
|
||
|
||
// Set the copy mask to include the reply-to label for now
|
||
PETESetLabelCopyMask(PETE,LABEL_COPY_MASK|pReplyLabel);
|
||
|
||
// do the copy, but fool the graphics into thinking it's the same Pete record
|
||
oldID = (*PeteExtra(fromPTE))->id;
|
||
(*PeteExtra(toPTE))->id = (*PeteExtra(fromPTE))->id;
|
||
// Ick. If we copy something with para styles, we need to not trample on the para
|
||
// styles of what we're copying just after. This is voodoo, so far as I'm concerned, but
|
||
// it's going to have to do
|
||
if (PeteCrAt(toPTE,PeteBumpSelection(toPTE,0)-1) && PeteBumpSelection(toPTE,0)==PeteLen(toPTE))
|
||
PeteInsertPlainParaAtEnd(toPTE);
|
||
err = PeteCopy(fromPTE,toPTE,start,stop,-1,nil,flatten);
|
||
(*PeteExtra(toPTE))->id = oldID;
|
||
|
||
// Reset the copy mask
|
||
PETESetLabelCopyMask(PETE,LABEL_COPY_MASK);
|
||
|
||
if (!err)
|
||
{
|
||
PeteUpdate14(toPTE);
|
||
|
||
// ok, now we have a messy business. We've got to make the para styles
|
||
// of the beginning and end of what we've done match properly.
|
||
selEnd = selStart + PeteLen(toPTE)-selEnd;
|
||
|
||
if (PeteCrAt(toPTE,selStart-1))
|
||
{
|
||
err = PeteParaInfoCopy(fromPTE,start,toPTE,selStart,true);
|
||
PeteUpdate14(toPTE);
|
||
}
|
||
|
||
if (!err)
|
||
{
|
||
err = PeteParaInfoCopy(fromPTE,stop-1,toPTE,selEnd-1,false);
|
||
PeteUpdate14(toPTE);
|
||
}
|
||
|
||
// finally, let's update the selection
|
||
PeteBumpSelection(toPTE,stop-start);
|
||
}
|
||
|
||
return err;
|
||
}
|
||
|
||
/************************************************************************
|
||
* PeteParaInfoCopy - copy para info from one place to another
|
||
************************************************************************/
|
||
OSErr PeteParaInfoCopy(PETEHandle fromPTE,uLong fromOffset,PETEHandle toPTE,uLong toOffset,Boolean breakBefore)
|
||
{
|
||
OSErr err;
|
||
long fromPara, toPara;
|
||
PETEParaInfo pInfo;
|
||
|
||
// get old para #
|
||
fromPara = PeteParaAt(fromPTE,fromOffset);
|
||
|
||
// insert para break if wanted & needed
|
||
if (breakBefore) PeteEnsureBreak(toPTE,toOffset);
|
||
|
||
// get new para number
|
||
toPara = PeteParaAt(toPTE,toOffset);
|
||
|
||
// get/set info
|
||
Zero(pInfo);
|
||
if (!(err=PETEGetParaInfo(PETE,fromPTE,fromPara,&pInfo)))
|
||
err = PETESetParaInfo(PETE,toPTE,toPara,&pInfo,peAllParaValidButTabs);
|
||
|
||
return err;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConFixTrimOffsets - reduce offsets that include all-white lines,
|
||
* until we wind up with either no all-white lines or the whole text
|
||
* is collapsed to a single all-white line
|
||
************************************************************************/
|
||
void ConConFixTrimOffsets(UHandle text,long *start,long *stop)
|
||
{
|
||
UPtr startP, stopP, endP;
|
||
|
||
startP = *text+*start;
|
||
endP = *text+*stop;
|
||
|
||
// find the first non-white character
|
||
for (stopP=startP;stopP<endP;stopP++)
|
||
if (!IsAnySP(*stopP)) break;
|
||
|
||
// Back up to just after the last return
|
||
while (stopP>startP && stopP[-1]!='\015') stopP--;
|
||
|
||
// Set it!
|
||
startP = stopP;
|
||
*start = startP - *text;
|
||
|
||
// find the last non-white character
|
||
stopP = endP;
|
||
while (stopP>startP && IsAnySP(stopP[-1])) stopP--;
|
||
|
||
// extend to next return
|
||
while (stopP<endP && *stopP++!='\015');
|
||
|
||
// Set it!
|
||
*stop = stopP - *text;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConFixWordOffset - adjust an offset so that we don't break off
|
||
* in the middle of a word, and p
|
||
************************************************************************/
|
||
Boolean ConConFixTruncOffsets(UHandle text,long start,long goalSize,long *stop,PStr ellipsis,ConConMessElmPtr oldME,StackHandle stack)
|
||
{
|
||
UPtr startP, endP, trunc1P, trunc2P;
|
||
uLong len = *stop-start;
|
||
uLong truncSize = len - goalSize;
|
||
ConConMessElm newME;
|
||
uLong cr;
|
||
Boolean para1, para2;
|
||
short quoteChars = 0;
|
||
|
||
// A little setup
|
||
startP = *text + start;
|
||
endP = *text + *stop;
|
||
|
||
// Don't truncate if we're within 20% of the end of our element
|
||
if (goalSize > (len*8)/10)
|
||
return false;
|
||
|
||
// We want to truncate from the middle, so let's setup offsets to
|
||
// the beginning and end of the truncation
|
||
trunc1P = startP + goalSize/2;
|
||
trunc2P = endP - goalSize/2;
|
||
|
||
// For grins, let's see where the paragraph boundaries are,
|
||
// since we like to trim at paragraph boundaries if possible
|
||
if (!(para1=ConConFixTruncByPara(startP,&trunc1P,endP)))
|
||
// nope, do words instead
|
||
ConConFixTruncByWord(startP,&trunc1P,endP,nil);
|
||
|
||
// And the second offset?
|
||
if (!(para2=ConConFixTruncByPara(startP,&trunc2P,endP)))
|
||
// nope, do words instead
|
||
ConConFixTruncByWord(startP,&trunc2P,endP,"eChars);
|
||
|
||
// Another sanity check; are we taking out enough to matter?
|
||
if (trunc1P-startP + endP-trunc2P > (len*8)/10)
|
||
return false;
|
||
|
||
// Ok, it's a go
|
||
*stop = trunc1P-*text;
|
||
|
||
newME = *oldME;
|
||
newME.start = trunc2P-*text;
|
||
StackPush(&newME,stack);
|
||
|
||
// Now, figure out the ellipsis
|
||
|
||
// If we're in the body, check to see if there any cr's in what we removed?
|
||
cr = oldME->type!=conConTypeHeader && HandleFindChar(text,*stop,newME.start,'\015');
|
||
*ellipsis = 0;
|
||
|
||
// if we removed a cr and truncated the first paragraph,
|
||
// put an ellipsis and return on the first paragraph
|
||
if (cr && !para1)
|
||
{
|
||
PCatR(ellipsis,CONCON_ELLIPSIS_TRAIL);
|
||
PCatC(ellipsis,'\015');
|
||
}
|
||
|
||
// the main ellipsis
|
||
PCatR(ellipsis,CONCON_ELLIPSIS_CENTER);
|
||
|
||
// if we removed a cr, we need a trailing cr,
|
||
// and possibly an ellipsis at the start of the next
|
||
// paragraph, if we didn't cut cleanly there
|
||
if (cr)
|
||
if (!para2)
|
||
{
|
||
PCatC(ellipsis,'\015');
|
||
if (quoteChars)
|
||
{
|
||
Str15 quoteStr;
|
||
GetRString(quoteStr,QUOTE_PREFIX);
|
||
quoteChars = MIN(quoteChars,10);
|
||
while (quoteChars--) PCatC(ellipsis,quoteStr[1]);
|
||
}
|
||
PCatR(ellipsis,CONCON_ELLIPSIS_LEAD);
|
||
}
|
||
else
|
||
PCatC(ellipsis,'\015');
|
||
|
||
return true;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConFixTruncByWord - adjust a truncation spot to end on a word
|
||
* boundary. Returns true if it was adjusted
|
||
************************************************************************/
|
||
Boolean ConConFixTruncByWord(UPtr start, UPtr *spotP, UPtr end,short *quoteChars)
|
||
{
|
||
UPtr spot = *spotP;
|
||
Boolean reverse = *spotP-start > end-*spotP;
|
||
Str15 quoteStr;
|
||
|
||
ASSERT(start<=end);
|
||
ASSERT(*spotP>=start);
|
||
ASSERT(*spotP<=end);
|
||
|
||
if (quoteChars)
|
||
{
|
||
UPtr cr;
|
||
GetRString(quoteStr,QUOTE_PREFIX);
|
||
cr = *spotP;
|
||
while (cr>start && cr[-1]!='\-015') cr--;
|
||
while (cr<end && *cr++==quoteStr[1]) ++*quoteChars;
|
||
}
|
||
|
||
// Are we in a word?
|
||
if (IsWordChar[*spot])
|
||
{
|
||
if (reverse)
|
||
{
|
||
// back up until a non-word char is before us
|
||
while (spot > start && IsWordChar[spot[-1]]) spot--;
|
||
}
|
||
else
|
||
{
|
||
// continue until we point at a non-word char
|
||
while (++spot<end)
|
||
if (!IsWordChar[*spot]) break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// not in a word.
|
||
if (reverse)
|
||
{
|
||
// move forward until a word char is found
|
||
while (++spot<end)
|
||
if (IsWordChar[*spot]) break;
|
||
}
|
||
else
|
||
{
|
||
// back up until a word char is before us
|
||
while (spot > start && !IsWordChar[spot[-1]]) spot--;
|
||
}
|
||
}
|
||
|
||
// did we do all that work just to end up where we started?
|
||
if (spot == *spotP) return false;
|
||
|
||
// These would be silly...
|
||
if (spot == start || spot==end) return false;
|
||
|
||
// Set it!
|
||
*spotP = spot;
|
||
|
||
return true; // we mucked
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConFixTruncByPara - adjust a truncation spot to end on a paragaph
|
||
* boundary. Returns true if it succeeded in putting it on a proper
|
||
* boundary. Returns false if it seems not to make sense to adjust
|
||
* the truncation paragraphwise
|
||
************************************************************************/
|
||
Boolean ConConFixTruncByPara(UPtr start, UPtr *spotP, UPtr end)
|
||
{
|
||
uLong len = end-start;
|
||
Boolean reverse = *spotP-start > end-*spotP;
|
||
long wantToRemove = len - 2 * (reverse? end-*spotP : *spotP-start);
|
||
long removing;
|
||
UPtr side1, side2, theNominee;
|
||
uLong diff;
|
||
|
||
// point at cr's before and after us
|
||
for (side1=*spotP;side1>start;side1--)
|
||
if (*side1=='\015')
|
||
if (side1==start+1 || side1[-1]!='\015') break; // find the first one of a run of cr's
|
||
if (side1==start) side1 = nil;
|
||
|
||
for (side2=*spotP;side2<end;side2++)
|
||
if (*side2=='\015')
|
||
if (side2==end-1 || side2[2]!='\015') break; // find the last one of a run of cr's
|
||
if (side2==end) side2 = nil;
|
||
|
||
// did we get any?
|
||
if (!side1 && !side2)
|
||
return false; // no cr's in sight; bail
|
||
else if (!side1)
|
||
theNominee = side2;
|
||
else if (!side2)
|
||
theNominee = side1;
|
||
else
|
||
// pick the closest one
|
||
theNominee = *spotP-side1 < side2-*spotP ? side1 : side2;
|
||
|
||
// Actually, truncate AFTER the cr
|
||
theNominee++;
|
||
|
||
// Now, let's see how far off we are
|
||
removing = len - 2 * (reverse? end-theNominee : theNominee-start);
|
||
|
||
// figure out how far off we are from the removal we want
|
||
diff = ABS(wantToRemove - removing);
|
||
|
||
// if the variance is more than 20%, forget it
|
||
if (diff*5 > wantToRemove) return false;
|
||
|
||
// hey... we found a good one!
|
||
*spotP = theNominee;
|
||
|
||
return true;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConMultipleAppropriate - is is appropriate to concentrate all
|
||
* these messages?
|
||
************************************************************************/
|
||
Boolean ConConMultipleAppropriate(TOCHandle tocH)
|
||
{
|
||
short sumNum;
|
||
short count = 0;
|
||
Str63 sub1, sub2;
|
||
Str63 who1, who2;
|
||
short maxCount = GetRLong(CONCON_MULTI_MAX);
|
||
Boolean needSame = ConConPrefSameSubj();
|
||
|
||
if (!HasFeature(featureConCon)) return false;
|
||
|
||
(*tocH)->conConMultiScan = false;
|
||
|
||
for (sumNum=(*tocH)->count-1;sumNum>=0;sumNum--)
|
||
if ((*tocH)->sums[sumNum].selected)
|
||
{
|
||
if (++count > maxCount) return false;
|
||
if (needSame)
|
||
{
|
||
if (count==1)
|
||
{
|
||
PSCopy(sub1,(*tocH)->sums[sumNum].subj);
|
||
PSCopy(who1,(*tocH)->sums[sumNum].from);
|
||
}
|
||
else
|
||
{
|
||
PSCopy(sub2,(*tocH)->sums[sumNum].subj);
|
||
PSCopy(who2,(*tocH)->sums[sumNum].from);
|
||
if (!StringSame(who1,who2) && SubjCompare(sub1,sub2)) return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
return count > 1;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConMultipleR - concentrate the selected messages in a toc into
|
||
* (the insertion point of) a PETEHandle, using a resource to find
|
||
* the concentrator profile
|
||
************************************************************************/
|
||
OSErr ConConMultipleR(TOCHandle tocH,PETEHandle pte,short conConID,short separatorType,ConConOutFilterProcPtr filter,void *userData)
|
||
{
|
||
Str63 profile;
|
||
return ConConMultiple(tocH,pte,GetRString(profile,conConID),separatorType,filter,userData);
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConMultiple - concentrate the selected messages in a toc into
|
||
* (the insertion point of) a PETEHandle
|
||
************************************************************************/
|
||
OSErr ConConMultiple(TOCHandle tocH,PETEHandle pte,PStr profileStr,short separatorType,ConConOutFilterProcPtr filter,void *userData)
|
||
{
|
||
if (HasFeature(featureConCon))
|
||
{
|
||
OSErr err = noErr;
|
||
short sumNum;
|
||
short oldLen;
|
||
Boolean opened;
|
||
MessHandle messH;
|
||
Str255 scratch;
|
||
Boolean first = true;
|
||
|
||
PeteCalcOff(pte);
|
||
PETEAllowUndo(PETE,pte,false,true);
|
||
UseFeature(featureConCon);
|
||
|
||
// any junk?
|
||
(*PeteExtra(pte))->containsJunkMail = false;
|
||
for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
|
||
if ((*tocH)->sums[sumNum].selected && (*tocH)->sums[sumNum].selected >= GetRLong(JUNK_MAILBOX_THRESHHOLD))
|
||
{
|
||
(*PeteExtra(pte))->containsJunkMail = true;
|
||
break;
|
||
}
|
||
|
||
// now, concentrate!
|
||
for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
|
||
if ((*tocH)->sums[sumNum].selected)
|
||
{
|
||
opened = false;
|
||
oldLen = PeteLen(pte);
|
||
messH = (*tocH)->sums[sumNum].messH;
|
||
if (messH==nil)
|
||
{
|
||
TOCHandle realTocH;
|
||
short realSumNum;
|
||
|
||
if (realTocH = GetRealTOC(tocH,sumNum,&realSumNum))
|
||
{
|
||
EnsureMsgDownloaded(realTocH,realSumNum,false); // FSOIMAP
|
||
GetAMessage(realTocH,realSumNum,nil,nil,false);
|
||
messH = (*realTocH)->sums[realSumNum].messH;
|
||
opened = messH!=nil;
|
||
}
|
||
}
|
||
if (!messH) {err = fnfErr; break;}
|
||
if (oldLen)
|
||
{
|
||
if (separatorType==conConOutSeparatorRule)
|
||
{
|
||
PeteInsertPlainParaAtEnd(pte);
|
||
if (err = PeteInsertRule(pte,-1,0,0,true,true,true)) break;
|
||
PeteBumpSelection(pte,PeteLen(pte)-oldLen);
|
||
}
|
||
else if (separatorType==conConTypeAttribution)
|
||
{
|
||
PeteInsertPlainParaAtEnd(pte);
|
||
GrabAttribution(ATTRIBUTION,(*messH)->win,scratch);
|
||
PCatC(scratch,'\015');
|
||
if (!first) PInsertC(scratch,sizeof(scratch),'\015',scratch+1);
|
||
if (err = PeteInsertStr(pte,-1,scratch)) break;
|
||
PeteBumpSelection(pte,PeteLen(pte)-oldLen);
|
||
}
|
||
}
|
||
if (err = ConConMess(messH,pte,profileStr,filter,userData)) break;
|
||
if (opened) CloseMyWindow(GetMyWindowWindowPtr((*messH)->win));
|
||
first = false;
|
||
}
|
||
|
||
if (err) PeteSetTextPtr(pte,"",0);
|
||
|
||
PeteCalcOn(pte);
|
||
|
||
return err;
|
||
}
|
||
else
|
||
return fnfErr;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConAddItems - add the current set of concentrator profiles to a menu
|
||
************************************************************************/
|
||
OSErr ConConAddItems(MenuHandle mh)
|
||
{
|
||
ConConProH ccph;
|
||
Str63 name;
|
||
|
||
if (!ConConProfiles) return fnfErr;
|
||
|
||
for (ccph=ConConProfiles;ccph;ccph=(*ccph)->next)
|
||
{
|
||
PSCopy(name,(*ccph)->name);
|
||
MyAppendMenu(mh,name);
|
||
}
|
||
|
||
return noErr;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConProFind - find a concentrator profile
|
||
************************************************************************/
|
||
ConConProH ConConProFind(PStr profileStr)
|
||
{
|
||
ConConProH ccph;
|
||
Str63 name;
|
||
|
||
for (ccph=ConConProfiles;ccph;ccph=(*ccph)->next)
|
||
{
|
||
PCopy(name,(*ccph)->name);
|
||
if (StringSame(name,profileStr)) return ccph;
|
||
}
|
||
|
||
return nil;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConProFindHash - find a concentrator profile by hash
|
||
************************************************************************/
|
||
ConConProH ConConProFindHash(uLong profileHash)
|
||
{
|
||
ConConProH ccph;
|
||
|
||
for (ccph=ConConProfiles;ccph;ccph=(*ccph)->next)
|
||
{
|
||
if ((*ccph)->hash==profileHash) return ccph;
|
||
}
|
||
|
||
return nil;
|
||
}
|
||
|
||
/************************************************************************
|
||
* ConConElmSetHeader - copy a header into an element, setting the id, too
|
||
************************************************************************/
|
||
void ConConElmSetHeader(ConConElmH cceh,PStr headerName)
|
||
{
|
||
short id = FindSTRNIndex(InterestHeadStrn,headerName);
|
||
|
||
(*cceh)->type = conConTypeHeader;
|
||
(*cceh)->headerID = id;
|
||
PSCopy((*cceh)->headerName,headerName);
|
||
}
|
||
|
||
/************************************************************************
|
||
* TSIsForward - is a message a forwarded message?
|
||
************************************************************************/
|
||
Boolean TSIsForward(TOCHandle tocH,short sumNum)
|
||
{
|
||
Str15 subjStart;
|
||
PSCopy(subjStart,(*tocH)->sums[sumNum].subj);
|
||
|
||
if (*subjStart < *Fwd) return false;
|
||
*subjStart = *Fwd;
|
||
return StringSame(subjStart,Fwd);
|
||
}
|
||
|
||
/************************************************************************
|
||
* TSIsDigest - is a message a digest message?
|
||
************************************************************************/
|
||
Boolean TSIsDigest(TOCHandle tocH,short sumNum)
|
||
{
|
||
Str63 subj;
|
||
PSCopy(subj,(*tocH)->sums[sumNum].subj);
|
||
|
||
// does it look like a digest?
|
||
return ItemFromResAppearsInStr(CONCON_DIGEST_ITEMS,subj,",");
|
||
}
|
||
|
||
|
||
/************************************************************************
|
||
* HandleFindChar - find a character in a handle, between an offset
|
||
* and a terminal value. If not found, return terminal value
|
||
************************************************************************/
|
||
uLong HandleFindChar(UHandle h, uLong offset, uLong terminal, Byte cToFind)
|
||
{
|
||
UPtr spot;
|
||
UPtr end = *h + terminal;
|
||
|
||
for (spot = *h + offset; spot<end; spot++)
|
||
if (*spot == cToFind) break;
|
||
|
||
return spot-*h;
|
||
}
|