eudora-mac/concentrator.c

1 line
54 KiB
C
Executable File
Raw Permalink Blame History

/* 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,&quoteChars);
// 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;
}