WideIdEMailAddress is same IdEMailAddress unit but works with unicode strings
Download fixed wideidemailaddress.pas
unit WideIdEMailAddress;
interface
uses
Classes, TntClasses;
type
TWideIdEMailAddressItem = class(TCollectionItem)
protected
FAddress: Widestring;
FName: Widestring;
function GetText: Widestring;
procedure SetText(AText: Widestring);
function ConvertAddress: Widestring;
function GetDomain: Widestring;
procedure SetDomain(const ADomain: Widestring);
function GetUsername: Widestring;
procedure SetUsername(const AUsername: Widestring);
public
procedure Assign(Source: TPersistent); override;
published
{This is the E-Mail address itself }
property Address: Widestring read FAddress write FAddress;
{This is the person's name}{Do not Localize}
property Name: Widestring read FName write FName;
{This is the combined person's name and E-Mail address}{Do not Localize}
property Text: Widestring read GetText write SetText;
{Extracted domain for some types of E-Mail processing}
property Domain: Widestring read GetDomain write SetDomain;
property User: Widestring read GetUsername write SetUsername;
end;
TWideIdEMailAddressList = class(TOwnedCollection)
protected
function GetItem(Index: Integer): TWideIdEMailAddressItem;
procedure SetItem(Index: Integer; const Value: TWideIdEMailAddressItem);
function GetEMailAddresses: Widestring;
procedure SetEMailAddresses(AList: Widestring);
public
constructor Create(AOwner: TPersistent); reintroduce;
{This returns formatted list of formated
addresses including the names from the collection }
procedure FillTStrings(AStrings: TTntStrings);
function Add: TWideIdEMailAddressItem;
//get all of the domains in the list so we can process those individually with
//TIdDirectSMTP
procedure GetDomains(AStrings: TTntStrings);
{Sort by domains for making it easier to process E-Mails directly in
TIdDirectSMTP}
procedure SortByDomain;
//Get all of the E-Mail addresses for a particular domain so we can
//send E-Mail to recipients at one domain with only one connection for
//speed with TIdDirectSMTP.
procedure AddressesByDomain(AList: TWideIdEMailAddressList; const ADomain: Widestring);
property Items[Index: Integer]: TWideIdEMailAddressItem read GetItem write SetItem; default;
{This is a comma separated list of formated
addresses including the names from the collection }
property EMailAddresses: Widestring read GetEMailAddresses write SetEMailAddresses;
end;
const
WideTab: widechar = #9;
implementation
uses
SysUtils, TntSysUtils;
const
// This is actually the ATEXT without the '"' and space characters... {Do not Localize}
IETF_ATEXT: Widestring = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' {Do not Localize}
+ '1234567890!#$%&''*+-/=?_`{}|~'; {Do not Localize}
// ATEXT without the '"' {Do not Localize}
IETF_ATEXT_SPACE: Widestring = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' {Do not Localize}
+ '1234567890!#$%&''*+-/=?_`{}|~ '; {Do not Localize}
IETF_QUOTABLE: Widestring = '\"'; {Do not Localize}
// Three functions for easier manipulating of strings.
// Don't know of any system functions to perform these actions. {Do not Localize}
// If there aren't & someone can find an optimised way of performing {Do not Localize}
// then please implement...
function FindFirstOf(const AFind, AText: Widestring): Integer;
var
nCount, nPos: Integer;
begin
Result := 0;
for nCount := 1 to Length(AFind) do begin
nPos := Pos(AFind[nCount], AText);
if nPos > 0 then begin
if Result = 0 then begin
Result := nPos;
end else if Result > nPos then begin
Result := nPos;
end;
end;
end;
end;
function FindFirstNotOf(const AFind, AText: Widestring): Integer;
var
i: Integer;
begin
Result := 0;
if AFind = '' then
begin
Result := 1;
Exit;
end;
if AText = '' then
begin
Exit;
end;
for i := 1 to Length(AText) do
begin
if Pos(AText[i], AFind) = 0 then
begin
Result := i;
Exit;
end;
end;
end;
function TrimAllOf(const ATrim, AText: Widestring): Widestring;
var
Len: Integer;
begin
Result := AText;
Len := Length(Result);
while Len > 0 do
begin
if Pos(Result[1], ATrim) > 0 then
begin
Delete(Result, 1, 1);
Dec(Len);
end else Break;
end;
while Len > 0 do begin
if Pos(Result[Len], ATrim) > 0 then
begin
Delete(Result, Len, 1);
Dec(Len);
end else Break;
end;
end;
{ TWideIdEMailAddressItem }
procedure TWideIdEMailAddressItem.Assign(Source: TPersistent);
var Addr: TWideIdEMailAddressItem;
begin
if ClassType <> Source.ClassType then
begin
inherited
end
else
begin
Addr := TWideIdEMailAddressItem(Source);
Address := Addr.Address;
Name := Addr.Name;
end;
end;
function TWideIdEMailAddressItem.ConvertAddress: Widestring;
var
i: Integer;
domainPart, tempAddress, localPart: Widestring;
begin
if FAddress = '' then
begin
if FName <> '' then
begin
Result := '<>'; {Do not Localize}
end else
begin
Result := ''; {Do not Localize}
end;
Exit;
end;
// First work backwards to the @ sign.
tempAddress := FAddress;
domainPart := '';
for i := Length(FAddress) downto 1 do
begin
if FAddress[i] = '@' then {Do not Localize}
begin
domainPart := Copy(FAddress, i, MaxInt);
tempAddress := Copy(FAddress, 1, i - 1);
Break;
end;
end;
i := FindFirstNotOf(IETF_ATEXT, tempAddress);
if (i = 0) or (Copy(tempAddress, i, 1) = #46) then //hack to accomodate periods in emailaddress
// if i = 0 then
begin
if FName <> '' then
begin
Result := '<' + tempAddress + domainPart + '>'; {Do not Localize}
end else
begin
Result := tempAddress + domainPart;
end;
end else
begin
localPart := '"'; {Do not Localize}
while i > 0 do
begin
localPart := localPart + Copy(tempAddress, 1, i - 1);
if Pos(tempAddress[i], IETF_QUOTABLE) > 0 then
begin
localPart := localPart + '\'; {Do not Localize}
end;
localPart := localPart + tempAddress[i];
System.Delete(tempAddress, 1, i);
i := FindFirstNotOf(IETF_ATEXT, tempAddress);
end;
Result := '<' + localPart + tempAddress + '"' + domainPart + '>'; {Do not Localize}
end;
end;
function TWideIdEMailAddressItem.GetDomain: Widestring;
var i: Integer;
begin
Result := '';
for i := Length(FAddress) downto 1 do
begin
if FAddress[i] = '@' then {Do not Localize}
begin
Result := Copy(FAddress, i + 1, MaxInt);
Break;
end;
end;
end;
procedure TWideIdEMailAddressItem.SetDomain(const ADomain: Widestring);
var
Result: Widestring;
begin
Result := FAddress;
Delete(Result, Pos('@', Result) - 1, Length(Result));
Result := Result + '@' + ADomain;
FAddress := Result;
end;
function TWideIdEMailAddressItem.GetUsername: Widestring;
var i: Integer;
begin
Result := '';
for i := Length(FAddress) downto 1 do
begin
if FAddress[i] = '@' then {Do not Localize}
begin
Result := Copy(FAddress, 1, i - 1);
Break;
end;
end;
end;
procedure TWideIdEMailAddressItem.SetUsername(const AUsername: Widestring);
var
Result: Widestring;
begin
Result := FAddress;
Delete(Result, 1, Pos('@', Result) + 1);
Result := AUsername + '@' + Result;
FAddress := Result;
end;
function TWideIdEMailAddressItem.GetText: Widestring;
var
i: Integer;
tempName, resName: Widestring;
begin
if (FName <> '') and (UpperCase(FAddress) <> FName) then
begin
i := FindFirstNotOf(IETF_ATEXT_SPACE, FName);
if i > 0 then
begin
// Need to quote the FName.
resName := '"' + Copy(FName, 1, i - 1); {Do not Localize}
if Pos(FName[i], IETF_QUOTABLE) > 0 then
begin
resName := resName + '\'; {Do not Localize}
end;
resName := resName + FName[i];
tempName := Copy(FName, i + 1, MaxInt);
while tempName <> '' do
begin
i := FindFirstNotOf(IETF_ATEXT_SPACE, tempName);
if i = 0 then
begin
Result := resName + tempName + '" ' + ConvertAddress; {Do not Localize}
Exit;
end;
resName := resName + Copy(tempName, 1, i - 1);
if Pos(tempName[i], IETF_QUOTABLE) > 0 then
begin
resName := resName + '\'; {Do not Localize}
end;
resName := resName + tempName[i];
System.Delete(tempName, 1, i);
end;
Result := resName + '" ' + ConvertAddress; {Do not Localize}
end else
begin
Result := FName + ' ' + ConvertAddress; {Do not Localize}
end;
end // if
else
begin
Result := ConvertAddress;
end; // else .. if
end;
procedure TWideIdEMailAddressItem.SetText(AText: Widestring);
var
nFirst,
nBracketCount: Integer;
bInAddress,
bAddressInLT,
bAfterAt,
bInQuote: Boolean;
begin
FAddress := ''; {Do not Localize}
FName := ''; {Do not Localize}
AText := Trim(AText);
if AText = '' then
Exit;
// Find the first known character type.
nFirst := FindFirstOf('("< @' + WideTab, AText); {Do not Localize}
if nFirst <> 0 then
begin
nBracketCount := 0;
bInAddress := False;
bAddressInLT := False;
bInQuote := False;
bAfterAt := False;
repeat
case AnsiChar(AText[nFirst]) of
' ', #9: {Do not Localize}
begin
if nFirst = 1 then
begin
Delete(AText, 1, 1);
end else
begin
// Only valid if in a name not contained in quotes - keep the space.
if bAfterAt then begin
FAddress := FAddress + Trim(Copy(AText, 1, nFirst - 1));
end else begin
FName := FName + Copy(AText, 1, nFirst);
end;
System.Delete(AText, 1, nFirst);
end;
end;
'(': {Do not Localize}
begin
Inc(nBracketCount);
if (nFirst > 1) then
begin
// There's at least one character to the name {Do not Localize}
if bInAddress then
begin
FAddress := FAddress + Trim(Copy(AText, 1, nFirst - 1));
end else
begin
if nBracketCount = 1 then
begin
FName := FName + Copy(AText, 1, nFirst - 1);
end;
end;
System.Delete(AText, 1, nFirst);
end else
begin
Delete(AText, 1, 1);
end;
end;
')': {Do not Localize}
begin
Dec(nBracketCount);
System.Delete(AText, 1, nFirst);
end;
'"': {Do not Localize}
begin
if bInQuote then
begin
if bAddressInLT then
begin
FAddress := FAddress + Trim(Copy(AText, 1, nFirst - 1));
end else
begin
FName := FName + Trim(Copy(AText, 1, nFirst - 1));
end;
System.Delete(AText, 1, nFirst);
bInQuote := False;
end else
begin
bInQuote := True;
Delete(AText, 1, 1);
end;
end;
'<': {Do not Localize}
begin
if nFirst > 1 then
begin
FName := FName + Copy(AText, 1, nFirst - 1);
end;
FName := TrimAllOf(' ' + #9, Trim(FName)); {Do not Localize}
bAddressInLT := True;
bInAddress := True;
Delete(AText, 1, nFirst);
end;
'>': {Do not Localize}
begin
// Only searched for if the address starts with '<' {Do not Localize}
bInAddress := False;
bAfterAt := False;
FAddress := FAddress +
TrimAllOf(' ' + #9, Trim(Copy(AText, 1, nFirst - 1))); {Do not Localize}
System.Delete(AText, 1, nFirst);
end;
'@': {Do not Localize}
begin
bAfterAt := True;
if bInAddress then
begin
FAddress := FAddress + Copy(AText, 1, nFirst);
System.Delete(AText, 1, nFirst);
end else
begin
if bAddressInLT then
begin
// Strange use. For now raise an exception until a real-world
// example can be found.
// Basically, it's formatted as follows: {Do not Localize}
// <someguy@domain.example> some-text @ some-text
// or:
// some-text <someguy@domain.example> some-text @ some-text
// where some text may be blank.
//CC: Note you used to arrive here if the From header in an email
//included more than one address (which was subsequently changed)
//because our code did not parse the From header for multiple
//addresses. That may have been the reason for this code.
// raise EIdEmailParseError.Create(RSEMailSymbolOutsideAddress);
fName := fName + AText;
Exit;
end else
begin
// If at this point, we're either supporting an e-mail address {Do not Localize}
// on it's own, or the old-style valid format: {Do not Localize}
// "Name" name@domain.example
bInAddress := True;
FAddress := FAddress + Copy(AText, 1, nFirst);
System.Delete(AText, 1, nFirst);
end;
end;
end;
'.': {Do not Localize}
begin
// Must now be a part of the domain part of the address.
if bAddressInLT then
begin
// Whitespace is possible around the parts of the domain.
FAddress := FAddress +
TrimAllOf(' ' + #9, Trim(Copy(AText, 1, nFirst - 1))) + '.'; {Do not Localize}
AText := TrimLeft(Copy(AText, nFirst + 1, MaxInt));
end else
begin
// No whitespace is allowed if no wrapping <> characters.
FAddress := FAddress + Copy(AText, 1, nFirst);
System.Delete(AText, 1, nFirst);
end;
end;
'\': {Do not Localize}
begin
// This will only be discovered in a bracketted or quoted section.
// It's an escape character indicating the next cahracter is {Do not Localize}
// a literal.
if bInQuote then
begin
// Need to retain the second character
if bInAddress then
begin
FAddress := FAddress + Copy(AText, 1, nFirst - 1);
FAddress := FAddress + AText[nFirst + 1];
end else
begin
FName := FName + Copy(AText, 1, nFirst - 1);
FName := FName + AText[nFirst + 1];
end;
end;
System.Delete(AText, 1, nFirst + 1);
end;
end;
// Check for bracketted sections first: ("<>" <> "" <"">) - all is ignored
if nBracketCount > 0 then
begin
// Inside a bracket, only three charatcers are special.
// '(' Opens a nested bracket: (One (Two (Three ))) {Do not Localize}
// ')' Closes a bracket {Do not Localize}
// '/' Escape character: (One /) /( // (Two /) )) {Do not Localize}
nFirst := FindFirstOf('()\', AText); {Do not Localize}
// Check if in quote before address: <"My Name"@domain.example> is valid
end else if bInQuote then
begin
// Inside quotes, only the end quote and escape character are special.
nFirst := FindFirstOf('"\', AText); {Do not Localize}
// Check if after the @ of the address: domain.example>
end else if bAfterAt then
begin
if bAddressInLT then
begin
// If the address is enclosed, then only the '(', '.' & '>' need be {Do not Localize}
// looked for, trimming all content when found: domain . example >
nFirst := FindFirstOf('.>(', AText); {Do not Localize}
end else
begin
nFirst := FindFirstOf('.( ', AText); {Do not Localize}
end;
// Check if in address: <name@domain.example>
end else if bInAddress then
begin
nFirst := FindFirstOf('"(@>', AText); {Do not Localize}
// Not in anything - check for opening charactere
end else
begin
// Outside brackets
nFirst := FindFirstOf('("< @' + WideTab, AText); {Do not Localize}
end;
until nFirst = 0;
if bInAddress and not bAddressInLT then
begin
FAddress := FAddress + TrimAllOf(' ' + #9, Trim(AText)); {Do not Localize}
end;
end else
begin
// No special characters, so assume a simple address
FAddress := AText;
end;
end;
{ TWideIdEMailAddressList }
function TWideIdEMailAddressList.Add: TWideIdEMailAddressItem;
begin
Result := TWideIdEMailAddressItem(inherited Add);
end;
constructor TWideIdEMailAddressList.Create(AOwner: TPersistent);
begin
inherited Create(AOwner, TWideIdEMailAddressItem);
end;
procedure TWideIdEMailAddressList.FillTStrings(AStrings: TTntStrings);
var
idx: Integer;
begin
for idx := 0 to Count - 1 do
begin
AStrings.Add(GetItem(idx).Text);
end;
end;
function TWideIdEMailAddressList.GetItem(Index: Integer): TWideIdEMailAddressItem;
begin
Result := TWideIdEMailAddressItem(inherited Items[Index]);
end;
function TWideIdEMailAddressList.GetEMailAddresses: Widestring;
var
idx: Integer;
begin
Result := ''; {Do not Localize}
for idx := 0 to Count - 1 do
begin
if Result = '' then
Result := GetItem(idx).Text
else
Result := Result + ', ' + GetItem(idx).Text; {Do not Localize}
end;
end;
procedure TWideIdEMailAddressList.SetItem(Index: Integer;
const Value: TWideIdEMailAddressItem);
begin
inherited SetItem(Index, Value);
end;
procedure TWideIdEMailAddressList.SetEMailAddresses(AList: Widestring);
var
EMail: TWideIdEMailAddressItem;
iStart: Integer;
sTemp: Widestring;
nInBracket: Integer;
bInQuote: Boolean;
begin
Clear;
if (Trim(AList) = '') then Exit; {Do not Localize}
iStart := FindFirstOf(':;(", ' + WideTab, AList); {Do not Localize}
if iStart = 0 then begin
EMail := Add;
EMail.Text := TrimLeft(AList);
end else begin
sTemp := ''; {Do not Localize}
nInBracket := 0;
bInQuote := False;
repeat
case AList[iStart] of
' ', #9: begin {Do not Localize}
if iStart = 1 then begin
sTemp := sTemp + AList[iStart];
System.Delete(AList, 1, 1);
end else begin
sTemp := sTemp + Copy(AList, 1, iStart);
System.Delete(AList, 1, iStart);
end;
end;
':': {Do not Localize}
begin
// The start of a group - ignore the lot.
System.Delete(AList, 1, iStart);
sTemp := ''; {Do not Localize}
end;
';': {Do not Localize}
begin
// End of a group. If we have something (groups can be empty),
// then process it.
sTemp := sTemp + Copy(AList, 1, iStart - 1);
if Trim(sTemp) <> '' then begin
EMail := Add;
EMail.Text := TrimLeft(sTemp);
sTemp := ''; {Do not Localize}
end;
// Now simply remove the end of the group.
System.Delete(AList, 1, iStart);
end;
'(': begin {Do not Localize}
Inc(nInBracket);
sTemp := sTemp + Copy(AList, 1, iStart);
System.Delete(AList, 1, iStart);
end;
')': begin {Do not Localize}
Dec(nInBracket);
sTemp := sTemp + Copy(AList, 1, iStart);
System.Delete(AList, 1, iStart);
end;
'"': begin {Do not Localize}
sTemp := sTemp + Copy(AList, 1, iStart);
System.Delete(AList, 1, iStart);
bInQuote := not bInQuote;
end;
',': begin {Do not Localize}
sTemp := sTemp + Copy(AList, 1, iStart - 1);
EMail := Add;
EMail.Text := sTemp;
// added - Allen .. saves blank entries being added
if (Trim(Email.Text) = '') or (Trim(Email.Text) = '<>') then {Do not Localize}
begin
FreeAndNil(Email);
end;
sTemp := ''; {Do not Localize}
System.Delete(AList, 1, iStart);
end;
'\': begin {Do not Localize}
// Escape character - simply copy this char and the next to the buffer.
sTemp := sTemp + Copy(AList, 1, iStart + 1);
System.Delete(AList, 1, iStart + 1);
end;
end;
if nInBracket > 0 then begin
iStart := FindFirstOf('(\)', AList); {Do not Localize}
end else if bInQuote then begin
iStart := FindFirstOf('"\', AList); {Do not Localize}
end else begin
iStart := FindFirstOf(':;(", ' + WideTab, AList); {Do not Localize}
end;
until iStart = 0;
// Clean up the content in sTemp
if (Trim(sTemp) <> '') or (Trim(AList) <> '') then begin
sTemp := sTemp + AList;
EMail := Add;
EMail.Text := TrimLeft(sTemp);
// added - Allen .. saves blank entries being added
if (Trim(Email.Text) = '') or (Trim(Email.Text) = '<>') then {Do not Localize}
begin
FreeAndNil(Email);
end;
end;
end;
end;
procedure TWideIdEMailAddressList.SortByDomain;
var
i, j: Integer;
LTemp: Widestring;
begin
for i := Count - 1 downto 0 do
begin
for j := 0 to Count - 2 do
begin
if WideCompareStr(Items[J].Domain, Items[J + 1].Domain) > 0 then begin
LTemp := Items[j].Text;
Items[j].Text := Items[j + 1].Text;
Items[j + 1].Text := LTemp;
end;
end;
end;
end;
procedure TWideIdEMailAddressList.GetDomains(AStrings: TTntStrings);
var
i: Integer;
LCurDom: Widestring;
begin
if Assigned(AStrings) then
begin
AStrings.Clear;
for i := 0 to Count - 1 do
begin
LCurDom := Lowercase(Items[i].Domain);
if AStrings.IndexOf(LCurDom) = -1 then
begin
AStrings.Add(LCurDom);
end;
end;
end;
end;
procedure TWideIdEMailAddressList.AddressesByDomain(AList: TWideIdEMailAddressList;
const ADomain: Widestring);
var
i: Integer;
LDomain: Widestring;
LCurDom: Widestring;
LEnt: TWideIdEMailAddressItem;
begin
LDomain := LowerCase(ADomain);
AList.Clear;
for i := 0 to Count - 1 do
begin
LCurDom := LowerCase(Items[i].Domain);
if LCurDom = LDomain then
begin
LEnt := AList.Add;
LEnt.Text := Items[i].Text;
end;
end;
end;
end.
See also:
INDY (Internet Direct) changes
IdAttachmentMemory fixed for unicode file names
IdCoderHeader fixed and added decoding headers to unicode
IdCoder3to4 fixed for wrong attachments
IdMessageCoderMIME fixed RemoveInvalidCharsFromFilename function
IdEMailAddress deleted exception on wrong addresses
|