Syntax10.Scn.Fnt5InfoElemsAllocVSyntax10.Scn.Fnt+StampElemsAlloc11 May 99u"Title": AddrBook "Author": Lichtenberger Robert "Abstract": AddrBook provides address book functionality for e-mail "Keywords": E-Mail, Addresses "Version": 1.0 beta "From": 18.02.98 "Until":  "Changes": 21 Oct 1998 Hacked the ChangeAddress procedure "Hints": Two binary search trees for nickname and realname are implemented. The trees are balanced when loaded. `BalloonElemsAlloc#Syntax10.Scn.Fnt}}"Add" AddrBook.Add nickname realname email "AddGroup" AddrBook.AddGroup groupname "AddToGroup" AddrBook.AddToGroup groupname (nickname | realname) {nickname | realname} "Address" Address is an entry in the Addressbook. The fields nickname, realname and email can be accessed read-only. When working with Addresses use NewAddress to create and InsertAddress to insert the address into the book. "AddressEnumProc" An AddressEnumProc type procedure can be used to enumerate all addresses in the addressbook or a group respectively. See EnumerateAdrReal, EnumerateNick and EnumerateAdrGroup. "AdrNotify" See Notify for a general description of the notification mechanism. The field a is accessible read-only. It represents the concerned Address object. "Delete" AddrBook.Delete (nickname | realname) "DeleteGroup" AddrBook.DeleteGroup groupname "DeleteFromGroup" AddrBook.DeleteFromGroup groupname (nickname | realname) {nickname | realname} "EnumerateGroups" EnumerateGroups calls the enumeration procedure p n times, passing each group of the addressbook as argument. "EnumerateAdrReal" EnumerateAdrReal calls the enumeration procedure p n times, passing each address of the addressbook as argument. The addresses are passed to p in alphabetical order with respect to their realnames "EnumerateAdrNick" EnumerateAdrReal calls the enumeration procedure p n times, passing each address of the addressbook as argument. The addresses are passed to p in alphabetical order with respect to their nicknames "EnumerateAdrGroup" EnumerateAdrReal calls the enumeration procedure p n times, passing each address of the group g as argument. The addresses are passed to p in alphabetical order with respect to their realnames "Find" AddrBook.Find (nickname | realname) "GetEmail" GetEmail searches for an address whose nickname or realname matches name and gives its email address. The search is case sensitive and does not support wildcards. "Group" A group is a set of addresses with a common property. e.g. create groups of addresses for friends, business contacts and family. The field name can be accessed read-only. Use NewGroup, InsertGroup to create and insert a new group into the addressbook. "GroupEnumProc" A GroupEnumProc type procedure can be used to enumerate all groups in the addressbook. See EnumerateGroups. "GroupNotify" See Notify for a general description of the notification mechanism. The fields a and g are accessible read-only. They represent the conerned Group and Address objects. a is only defined if op is addAdr or removeAdr. It then represents the Address object that has been added or removed from the group g respectively. Otherwise a is NIL and g represents the group that has been newly inserted or deleted respectively. "HasGroup" HasGroup returns TRUE iff the address a is in the group g. "Insert" Insert tries to add a new address to the addressbook yielding the result ok if the operation was successful or alreadyExists if the given nickname or realname was not unique. "InsertAddressIntoGroup" InsertAddressIntoGroup tries to add the address a to the group g, yielding the result ok, if the operation was successful or alreadyExists if the given address is already part of the group. "InsertGroup" InsertGroup tries to insert a new group to the addressbook yielding the result ok if the operation was successful or alreadyExists if the given groupname was not unique. "InstallNotifier" See Notify for a general description of the notification mechanism. InstallNotifier is used to install a notification procedure. "Load" Loads the addressbook. "ListNick" Lists all addresses in alphabetical order with respect to the nickname. "ListReal" Lists all addresses in alphabetical order with respect to the realname. "ListGroup" AddrBook.ListGroup groupname Lists all addresses of the given group in alphabetical order with respect to the realname. "NewAddress" NewAddress creates a new address object with the given nickname, realname and email-address. a points to the newly created object. a is not inserted into the addressbook; "NewGroup" NewGroup creates a new group object with the given groupname. g points to the newly created object. g is not inserted into the addressbook. "Notifier" See Notify. "Notify" Notifiers can be installed to trigger actions when changes are made to the Addressbook. Use AddrBook.InstallNotifier(p) to install a procedure p as notifier for the addressbook. p hast to be of type Notifier, i.e. it is passed a Notify - object which indicates the type of change: - If a change was made to addresses then p is passed an AdrNotify object - If a change was made to groups then p is passed an GroupNotify object The field op indicates the type of operation on the addressbook: - add: An address or group has been added - remove: An address or group has been removed - addAdr: An address has been added to a group - removeAdr: An address has been removed from a group Note that addAdr and removeAdr are valid values for op iff a GroupNotify object has been passed to p. "PrintNickFirst" PrintNickFirst prints the given address using Out. Its output looks like "PrintRealFirst" PrintRealFirst prints the given address using Out. Its output looks like "ReadAddress" ReadAddress reads from the file pointed at by r and creates a new Address object in a. "Remove" Remove tries to remove the address a from the addressbook, yielding the result ok if the operation was successful or notFound if the given address could not be found in the addressbook. a must be identical (i.e. point to the same AddressDesc) to the address you want to remove from the addressbook. Use SearchByName(...) and Remove(...) instead of NewAddress(...) and Remove(...) if you want to remove an address with given nick or realname. "RemoveGroup" RemoveGroup tries to remove the group g from the addressbook, yielding the result ok if the operation was successful or notFound if the given group could not be found in the addressbook. g must be identical (i.e. point to the same GroupDesc) to the group you want to remove from the addressbook. Use SearchGroupByName(...) and RemoveGroup(...) instead of NewGroup(...) and Remove(...) if you want to remove a group with given nick or realname. "RemoveAddressFromGroup" RemoveAddressFromGroup tries to remove address a from group g. result is ok if the operation was successful or notFound if the given address is not in group g. "RemoveNotifier" See Notify for a general description of the notification mechanism. RemoveNotifier is used to remove a notification procedure. "SearchByName" SearchByName searches for an address, where nickname or realname match the given name. The search is case sensitive and does not support wildcards. If the address could be found adr will point to it, adr is NIL else. "SearchGroupByName" SearchGroupByName searches for a group, where the groupname matches the given name. The search is case sensitive and does not support wildcards. If the group could be found g will point to it, g is NIL else. "SetGroupname" SetGroupname renames the group g. "Store" Stores the Addressbook. "Write" Write writes the address a to the file pointed to by the Rider r. Use ReadAddress(...) to load an address. Note that loading and storing of the addressbook is done by the commands Load and Store so you will usually not need to call Write or ReadAddress. "SetValue" Sets the additional field 'fieldname' to 'content' in the given address. Precondition: fieldname IN {organization, title, address, city, state, zip, country, workPhone, homePhone, fax}. "SetNotes" Sets the notes for the given address. "SetField" AddrBook.SetField (nickname | realname) fieldname value;Syntax10b.Scn.Fnt  )Syntax10i.Scn.Fnt  -    \  l   6    ]     2}8FoldElemsNew#Syntax10.Scn.Fntaa BEGIN IF In.Next() = In.name THEN In.Name(str) ELSE In.String(str) END END InNameString; 80M8#Syntax10.Scn.Fnt BEGIN IF a.nickname^ < b.nickname^ THEN RETURN less ELSIF a.nickname^ > b.nickname^ THEN RETURN greater ELSE RETURN equal END END CompNick; 80M8#Syntax10.Scn.Fnt BEGIN IF a.realname^ < b.realname^ THEN RETURN less ELSIF a.realname^ > b.realname^ THEN RETURN greater ELSE RETURN equal END END CompReal; 87Z8#Syntax10.Scn.Fnt VAR res: SHORTINT; BEGIN res := CompReal(a, b); IF res = equal THEN res := CompNick(a, b) END; RETURN res END CompRealAndNick; 87Z8#Syntax10.Scn.Fnt VAR res: SHORTINT; BEGIN res := CompNick(a, b); IF res = equal THEN res := CompReal(a, b) END; RETURN res END CompNickAndReal; 8>V8#Syntax10.Scn.Fnt VAR cur: AddressList; BEGIN cur := list; WHILE (cur # NIL) & (cur.a # a) DO cur := cur.next END; RETURN cur # NIL END Contains; 8P88S8#Syntax10.Scn.Fnt00 VAR cur, prev: AddressList; BEGIN cur := list; prev := NIL; WHILE (cur # NIL) & (cur.a # a) DO prev := cur; cur := cur.next END; IF cur # NIL THEN IF prev = NIL THEN list := list.next ELSE prev.next := cur.next END; result := ok ELSE result := notFound END END RemoveAddress; 82!8CSyntax10.Scn.FntSyntax10i.Scn.Fnt (* Bubble Sort *) VAR swapped: BOOLEAN; cur, prev: AddressList; hlp: Address; BEGIN IF (list = NIL) OR (list.next = NIL) THEN RETURN END; REPEAT swapped := FALSE; cur := list.next; prev := list; WHILE cur # NIL DO IF p(cur.a, prev.a) = less THEN hlp := cur.a; cur.a := prev.a; prev.a := hlp; swapped := TRUE END; prev := cur; cur := cur.next END UNTIL ~swapped END Sort; 8>l8#Syntax10.Scn.Fntrr VAR cur: AddressList; BEGIN cur := list; WHILE cur # NIL DO p(cur.a); cur := cur.next END END Enumerate; 8(8#Syntax10.Scn.Fnt VAR cur: AddressList; BEGIN nrEntries := 0; cur := head; WHILE cur # NIL DO cur.a.id := nrEntries; INC(nrEntries); cur := cur.next END; nextID := nrEntries END ReorgId; 8S8#Syntax10.Scn.Fnt BEGIN Out.String(a.nickname^); Out.Char(09X); Out.String(a.realname^); Out.Char(09X); Out.String(a.email^); Out.Ln END PrintNickFirst; 8S8#Syntax10.Scn.Fnt BEGIN Out.String(a.realname^); Out.Char(09X); Out.String(a.nickname^); Out.Char(09X); Out.String(a.email^); Out.Ln END PrintRealFirst; 8:r8#Syntax10.Scn.Fntll BEGIN IF x = NIL THEN Files.WriteString(r, "") ELSE Files.WriteString(r, x^) END END CheckAndWrite; 88#Syntax10.Scn.Fnt-- BEGIN Files.WriteNum(r, a.id); Files.WriteString(r, a.nickname^); Files.WriteString(r, a.realname^); Files.WriteString(r, a.email^); CheckAndWrite(r, a.organization); CheckAndWrite(r, a.title); CheckAndWrite(r, a.address); CheckAndWrite(r, a.city); CheckAndWrite(r, a.state); CheckAndWrite(r, a.zip); CheckAndWrite(r, a.country); CheckAndWrite(r, a.workPhone); CheckAndWrite(r, a.homePhone); CheckAndWrite(r, a.fax); IF a.notes = NIL THEN Files.WriteBool(r, FALSE) ELSE Files.WriteBool(r, TRUE); Texts.Store(r, a.notes) END END Write; 8# "8#Syntax10.Scn.Fnt VAR new: NotifierQ; BEGIN NEW(new); new.p := p; new.next := NIL; IF notifiers = NIL THEN notifiers := new ELSE new.next := notifiers; notifiers := new END END InstallNotifier; 8 8#Syntax10.Scn.Fnt VAR cur, prev: NotifierQ; BEGIN cur := notifiers; prev := NIL; WHILE (cur # NIL) & (cur.p # p) DO prev := cur; cur := cur.next END; IF cur # NIL THEN IF prev = NIL THEN notifiers := notifiers.next ELSE prev.next := cur.next END END END RemoveNotifier; 8!j8#Syntax10.Scn.Fnttt VAR cur: NotifierQ; BEGIN cur := notifiers; WHILE cur # NIL DO cur.p(n); cur := cur.next END END DoNotify; 8 $88#Syntax10.Scn.Fnt,,NEW(n); n.a := a; n.op := add; DoNotify(n);  88  "8Syntax10.Scn.FntSyntax10b.Scn.Fnt8FoldElemsNew#Syntax10.Scn.Fnt88NEW(n); n.g := g; n.a := NIL; n.op := add; DoNotify(n);  8  VAR cur, prev: Group; n: GroupNotify; BEGIN cur := groups; prev := NIL; WHILE (cur # NIL) & (cur.name^ < g.name^) DO prev := cur; cur := cur.next END; IF (cur # NIL) & (cur.name^ = g.name^) THEN result := alreadyExists; RETURN END; IF prev = NIL THEN g.next := groups; groups := g ELSE g.next := prev.next; prev.next := g END; INC(nrGroups); Notification result := ok END InsertGroup; 8 2Y8Syntax10.Scn.Fnt8FoldElemsNew#Syntax10.Scn.Fnt99NEW(n); n.g := g; n.a := a; n.op := addAdr; DoNotify(n);  8" VAR n: GroupNotify; BEGIN IF (a.nickname^ = "") OR (a.realname^ = "") THEN result := 1; RETURN; END; AddAddress(g.members, a, result); IF result = ok THEN INC(g.nrMembers); Notification END END InsertAddressIntoGroup; 8  5(8#Syntax10.Scn.Fnt BEGIN NEW(a); NEW(a.nickname, Strings.Length(nick) + 1); COPY(nick, a.nickname^); NEW(a.realname, Strings.Length(real) + 1); COPY(real, a.realname^); NEW(a.email, Strings.Length(email) + 1); COPY(email, a.email^); a.organization := NIL; a.title := NIL; a.address := NIL; a.city := NIL; a.state := NIL; a.zip := NIL; a.country := NIL; a.workPhone := NIL; a.homePhone := NIL; a.fax := NIL; a.notes := NIL END NewAddress; 8  &_8#Syntax10.Scn.Fnt BEGIN NEW(g); NEW(g.name, Strings.Length(name) + 1); COPY(name, g.name^); g.members := NIL; g.nrMembers := 0 END NewGroup; 8 *8#Syntax10.Scn.Fnt VAR cur: AddressList; BEGIN cur := head; WHILE (cur # NIL) & (cur.a.realname^ # name) & (cur.a.nickname^ # name) DO cur := cur.next END; IF cur # NIL THEN adr := cur.a ELSE adr := NIL END END SearchByName; 8 &x8#Syntax10.Scn.Fntff BEGIN g := groups; WHILE (g # NIL) & (g.name^ # name) DO g := g.next END END SearchGroupByName; 8=18#Syntax10.Scn.Fnt VAR buf: ARRAY 1024 OF CHAR; BEGIN Files.ReadString(r, buf); IF buf = "" THEN x := NIL ELSE NEW(x, Strings.Length(buf) + 1); COPY(buf, x^) END END ReadAndCheck; 8  'g8#Syntax10.Scn.Fntww VAR nick, real, email: Name; id, pos, version: LONGINT; hasNotes: BOOLEAN; BEGIN Files.ReadNum(r, id); Files.ReadString(r, nick); Files.ReadString(r, real); Files.ReadString(r, email); NewAddress(nick, real, email, a); a.id := id; ReadAndCheck(r, a.organization); ReadAndCheck(r, a.title); ReadAndCheck(r, a.address); ReadAndCheck(r, a.city); ReadAndCheck(r, a.state); ReadAndCheck(r, a.zip); ReadAndCheck(r, a.country); ReadAndCheck(r, a.workPhone); ReadAndCheck(r, a.homePhone); ReadAndCheck(r, a.fax); Files.ReadBool(r, hasNotes); IF hasNotes THEN NEW(a.notes); Texts.Load(r, a.notes) END END ReadAddress; 8 28Syntax10.Scn.Fntm8FoldElemsNew#Syntax10.Scn.Fnt<<NEW(n); n.g := g; n.a := a; n.op := removeAdr; DoNotify(n);  8" VAR n: GroupNotify; BEGIN RemoveAddress(g.members, a, result); IF result = ok THEN DEC(g.nrMembers); Notification END END RemoveAddressFromGroup; 8 $8Syntax10.Scn.Fnt8FoldElemsNew#Syntax10.Scn.Fnt//NEW(n); n.a := a; n.op := remove; DoNotify(n);  82 VAR n: AdrNotify; g: Group; res2: INTEGER; BEGIN g := groups; WHILE g # NIL DO IF Contains(g.members, a) THEN RemoveAddressFromGroup(g, a, res2); ASSERT(res2 = ok) END; g := g.next END; RemoveAddress(head, a, result); IF result = ok THEN DEC(nrEntries); Notification END END Remove; 8  "8Syntax10.Scn.Fnt:8FoldElemsNew#Syntax10.Scn.Fnt;;NEW(n); n.g := g; n.a := NIL; n.op := remove; DoNotify(n);  8_ VAR cur, prev: Group; n: GroupNotify; BEGIN ASSERT(g # NIL); cur := groups; prev := NIL; WHILE (cur # NIL) & (g # cur) DO prev := cur; cur := cur.next END; IF g # cur THEN result := notFound ELSE IF prev = NIL THEN groups := groups.next ELSE prev.next := cur.next END; DEC(nrGroups); Notification END END RemoveGroup; 8  2`8#Syntax10.Scn.Fnt~~ VAR a: Address; BEGIN SearchByName(name, a); IF a # NIL THEN COPY(a.email^, email) ELSE COPY("", email) END END GetEmail; 8 l8#Syntax10.Scn.Fntrr VAR cur: Group; BEGIN cur := groups; WHILE cur # NIL DO p(cur); cur := cur.next END END EnumerateGroups; 8 8#Syntax10.Scn.FntHH BEGIN Sort(head, CompReal); Enumerate(head, p) END EnumerateAdrReal; 8 8#Syntax10.Scn.FntHH BEGIN Sort(head, CompNick); Enumerate(head, p) END EnumerateAdrNick; 8 .8#Syntax10.Scn.FntLL BEGIN Sort(g.members, c); Enumerate(g.members, p) END EnumerateAdrGroup; 8 "8#Syntax10.Scn.FntVV BEGIN NEW(g.name, Strings.Length(name) + 1); COPY(name, g.name^) END SetGroupname; 8  "8#Syntax10.Scn.Fnt44 BEGIN RETURN Contains(g.members, a) END HasGroup; 8  KW8#Syntax10.Scn.Fnt VAR x: String; BEGIN Strings.Cap(fieldName); NEW(x, Strings.Length(content) + 1); COPY(content, x^); IF fieldName = "ORGANIZATION" THEN a.organization := x ELSIF fieldName = "TITLE" THEN a.title := x ELSIF fieldName = "ADDRESS" THEN a.address := x ELSIF fieldName = "CITY" THEN a.city := x ELSIF fieldName = "STATE" THEN a.state := x ELSIF fieldName = "ZIP" THEN a.zip := x ELSIF fieldName = "COUNTRY" THEN a.country := x ELSIF fieldName = "WORKPHONE" THEN a.workPhone := x ELSIF fieldName = "HOMEPHONE" THEN a.homePhone := x ELSIF fieldName = "FAX" THEN a.fax := x ELSE result := notFound END END SetValue; 8  &8#Syntax10.Scn.Fnt'' BEGIN a.notes := notes END SetNotes; 8 F<8Syntax10.Scn.Fnt8FoldElemsNew#Syntax10.Scn.Fnt Notification-8"7 VAR n: AdrNotify; BEGIN NEW(a.nickname, Strings.Length(nick) + 1); COPY(nick, a.nickname^); NEW(a.realname, Strings.Length(real) + 1); COPY(real, a.realname^); NEW(a.email, Strings.Length(email) + 1); COPY(email, a.email^); NEW(n); n.a := a; n.op := edit; DoNotify(n);  result := ok END ChangeAddress; 8  8cSyntax10.Scn.FntSyntax10i.Scn.FntFSyntax10b.Scn.Fnt (** AddrBook.Add nickname realname email {nickname realname email }**) VAR nick, real, email: Name; a: Address; res: INTEGER; BEGIN In.Open; InNameString(nick); InNameString(real); InNameString(email); IF ~In.Done THEN Out.String("Use AddrBook.Add nickname realname email$"); RETURN END; WHILE In.Done DO NewAddress(nick, real, email, a); Insert(a, res); IF res = 1 THEN Out.String("AddrBook: Error. This nick or realname already exists$") END; InNameString(nick); InNameString(real); InNameString(email); END; END Add; 8  d8\Syntax10i.Scn.Fnt#Syntax10.Scn.FntSyntax10b.Scn.FntA(** AddrBook.AddGroup groupname **) VAR name: Name; g: Group; res: INTEGER; BEGIN In.Open; InNameString(name); IF ~In.Done THEN Out.String("Use AddrBook.NewGroup groupname$"); RETURN END; NewGroup(name, g); InsertGroup(g, res); IF res = alreadyExists THEN Out.String("Groupname already exists$") END END AddGroup; 8  68qSyntax10.Scn.FntSyntax10i.Scn.FntQSyntax10b.Scn.Fnt}Z (** AddrBook.AddToGroup groupname (nickname | realname) {nickname | realname} **) VAR groupname, name: Name; g: Group; a: Address; res: INTEGER; BEGIN In.Open; InNameString(groupname); InNameString(name); IF ~In.Done THEN Out.String("Use AddrBook.AddToGroup groupname (nickname | realname) {nickname | realname}$"); RETURN END; SearchGroupByName(groupname, g); IF g = NIL THEN Out.String("AddrBook: Group "); Out.String(groupname); Out.String(" not found$"); RETURN END; WHILE In.Done DO SearchByName(name, a); IF a = NIL THEN Out.String("AddrBook: Address "); Out.String(name); Out.String(" not found$") END; IF (a # NIL) & (g # NIL) THEN InsertAddressIntoGroup(g, a, res); IF res = alreadyExists THEN Out.String("AddrBook: "); Out.String(name); Out.String(" already in group$") END END; InNameString(name) END END AddToGroup; 8  8qSyntax10.Scn.FntSyntax10i.Scn.Fnt?Syntax10b.Scn.Fnty (** AddrBook.SetField (nickname | realname) fieldname value **) VAR name: Name; a: Address; res: INTEGER; fieldName, value: ARRAY 512 OF CHAR; BEGIN In.Open; InNameString(name); InNameString(fieldName); InNameString(value); IF ~In.Done THEN Out.String("Use AddrBook.SetField (nickname | realname) fieldname value$"); RETURN END; SearchByName(name, a); IF a = NIL THEN Out.String("AddrBook: Address "); Out.String(name); Out.String(" not found$"); RETURN END; SetValue(a, fieldName, value, res); IF res = notFound THEN Out.String("AddrBook: Field "); Out.String(fieldName); Out.String(" not found") END END SetField; 8 _8\Syntax10i.Scn.Fnt+Syntax10.Scn.FntSyntax10b.Scn.FntF(** AddrBook.Find (nickname | realname) **) VAR name: Name; a: Address; BEGIN In.Open; InNameString(name); IF ~In.Done THEN Out.String("Use AddrBook.Search [nickname | realname]$"); RETURN END; SearchByName (name, a); IF a = NIL THEN Out.String(name); Out.String(" not found$") ELSE a.PrintRealFirst END END Find; 8 8-^8  8jSyntax10i.Scn.Fnt&Syntax10.Scn.FntSyntax10b.Scn.Fnt|(** AddrBook.DeleteGroup groupname **) VAR groupname: Name; g: Group; res: INTEGER; BEGIN In.Open; InNameString(groupname); IF ~In.Done THEN Out.String("Use AddrBook.DeleteGroup groupname$"); RETURN END; SearchGroupByName (groupname, g); IF g = NIL THEN Out.String("AddrBook: No group "); Out.String(groupname); Out.Ln; RETURN END; RemoveGroup (g, res); IF res = notFound THEN Out.String("AddrBook: Could not delete group "); Out.String(groupname); Out.Ln END END DeleteGroup; 8 98jSyntax10i.Scn.FntVSyntax10.Scn.FntSyntax10b.Scn.Fntw^(** AddrBook.DeleteFromGroup groupname (nickname | realname) {nickname | realname} **) VAR groupname, name: Name; g: Group; a: Address; res: INTEGER; BEGIN In.Open; InNameString(groupname); InNameString(name); IF ~In.Done THEN Out.String("Use AddrBook.DeleteFriomGroup groupname (nickname | realname) {nickname | realname}$"); RETURN END; SearchGroupByName(groupname, g); IF g = NIL THEN Out.String("AddrBook: Group "); Out.String(groupname); Out.String(" not found$"); RETURN END; WHILE In.Done DO SearchByName(name, a); IF a = NIL THEN Out.String("AddrBook: Address "); Out.String(name); Out.String(" not found$") END; IF (a # NIL) & (g # NIL) THEN RemoveAddressFromGroup(g, a, res); IF res = notFound THEN Out.String("Address "); Out.String(name); Out.String(" not in group$") END END; InNameString(name) END END DeleteFromGroup; 88#Syntax10.Scn.Fnt$$ BEGIN a.PrintNickFirst END pNick; 8  L8<Syntax10i.Scn.Fnt5Syntax10.Scn.FntDy(** List addresses in nickname alphabetical order **) BEGIN Sort(head, CompNick); Enumerate(head, pNick) END ListNick; 88#Syntax10.Scn.Fnt$$ BEGIN a.PrintRealFirst END pReal; 8  L8<Syntax10i.Scn.Fnt5Syntax10.Scn.FntDy(** List addresses in realname alphabetical order **) BEGIN Sort(head, CompReal); Enumerate(head, pNick) END ListReal; 8  G8\Syntax10i.Scn.Fnt6Syntax10.Scn.FntSyntax10b.Scn.Fnt^(** AddrBook.ListGroup groupname ("real" | "nick") **) VAR groupname, sort: Name; g: Group; nick: BOOLEAN; BEGIN In.Open; InNameString(groupname); IF ~In.Done THEN Out.String("Use AddrBook.ListGroup groupname$"); RETURN END; InNameString(sort); IF ~In.Done THEN nick := FALSE ELSE nick := sort = "nick" END; SearchGroupByName (groupname, g); IF g = NIL THEN Out.String("AddrBook: Group "); Out.String(groupname); Out.String(" not found$") ELSE IF nick THEN Sort(head, CompNick); Enumerate(head, pNick) ELSE Sort(head, CompReal); Enumerate(head, pReal) END; END END ListGroup; 8!8#Syntax10.Scn.Fnt// BEGIN Files.WriteNum(rid, a.id) END WriteId; 8 8#Syntax10.Scn.Fnt__ VAR f: Files.File; res: INTEGER; fullName, bakFullName: ARRAY 512 OF CHAR; cur: AddressList; g: Group; BEGIN ReorgId; COPY(PostOffice.mDir, fullName); Strings.Append(fileName, fullName); COPY(PostOffice.mDir, bakFullName); Strings.Append(bakfileName, bakFullName); Files.Rename(fullName, bakFullName, res); f := Files.New(fullName); Files.Register(f); Files.Set(rid, f, 0); Files.WriteNum(rid, fileVersion); Files.WriteNum(rid, nrEntries); Files.WriteNum(rid, nextID); cur := head; WHILE cur # NIL DO cur.a.Write(rid); cur := cur.next; END; Files.WriteNum(rid, nrGroups); g := groups; WHILE g # NIL DO Files.WriteNum(rid, g.nrMembers); Files.WriteString(rid, g.name^); Enumerate (g.members, WriteId); g := g.next; END; Out.String("AddrBook.Store "); Out.String(fileName); Out.F(" #$", Files.Pos(rid)); Files.Close(f) END Store; 88#Syntax10.Scn.FntZZ BEGIN head := NIL; groups := NIL; nrEntries := 0; nextID := 0; nrGroups := 0 END Init; 8Z8CSyntax10.Scn.FntmSyntax10b.Scn.Fntd VAR fullName, groupname: Name; version, i,j,id, nrGrp, nrMembers: LONGINT; idx: POINTER TO ARRAY OF Address; a: Address; res: INTEGER; f: Files.File; r: Files.Rider; g: Group; BEGIN Init; COPY(PostOffice.mDir, fullName); Strings.Append(fileName, fullName); f := Files.Old(fullName); IF f = NIL THEN Out.String("AddrBook: Address - file not found$"); RETURN END; Files.Set(r, f, 0); Files.ReadNum(r, version); ASSERT(version = fileVersion); Files.ReadNum(r, nrEntries); Files.ReadNum(r, nextID); ASSERT(nrEntries = nextID); IF nrEntries = 0 THEN Out.String("AddrBook: No Addresses in Address - file found$"); RETURN END; NEW(idx, nrEntries); FOR i := 0 TO nrEntries-1 DO ReadAddress(r, a); AddAddress(head, a, res); ASSERT(res = ok); idx[i] := a; END; Files.ReadNum(r, nrGrp); FOR i := 0 TO nrGrp-1 DO Files.ReadNum(r, nrMembers); Files.ReadString(r, groupname); NewGroup(groupname, g); InsertGroup(g, res); ASSERT(res = ok); g.nrMembers := nrMembers; FOR j := 0 TO nrMembers - 1 DO Files.ReadNum(r, id); AddAddress(g.members, idx[id], res); ASSERT(res = ok); END; END; END Load; 8D"8#Syntax10.Scn.Fnt VAR idx: INTEGER; ch: CHAR; BEGIN ch := 0X; idx := 0; WHILE ~r.eot & (ch # 0DX) & (ch # 0AX) DO Texts.Read(r, ch); line[idx] := ch; INC(idx) END; line[idx - 1] := 0X END ReadLine; 8:o8#Syntax10.Scn.Fntoo VAR x: INTEGER; BEGIN x := Strings.Pos(":", line, 0); Strings.Extract(line, 0, x, field) END GetFieldName; 8:08#Syntax10.Scn.Fnt VAR x: INTEGER; BEGIN x := Strings.Pos(":", line, 0) + 1; WHILE line[x] = " " DO INC(x) END; Strings.Extract(line, x, Strings.Length(line) - x, content) END GetContent; 878#Syntax10.Scn.Fnt(( BEGIN RETURN (line = "") END isEmpty; 8 8a 8 8Syntax10.Scn.Fnt8FoldElemsNew#Syntax10.Scn.Fnt List = POINTER TO ListDesc; ListDesc = RECORD data: ARRAY 256 OF CHAR; next: List END; VAR l: List; curName, filename: ARRAY 255 OF CHAR; f: Files.File; r: Files.Rider; success: BOOLEAN; adr: Address; ok2: INTEGER; g: Group; 8>]8#Syntax10.Scn.Fnt VAR buf: ARRAY 6 OF CHAR; BEGIN Files.ReadBytes(r, buf, 5); buf[5] := 0X; ok := buf = "alias"; ASSERT(ok) END ExpectAlias; 8V8#Syntax10.Scn.FntXX VAR buf: ARRAY 256 OF CHAR; ch: CHAR; i: INTEGER; BEGIN Files.Read(r, ch); WHILE (ch = " ") DO Files.Read(r, ch) END; i := 0; WHILE ~r.eof & ((CAP(ch) >= "A") & (CAP(ch) <= "Z")) OR ((ch >= "0") & (ch <= "9")) DO IF (ch # NL) THEN buf[i] := ch; INC(i) END; Files.Read(r, ch) END; buf[i] := 0X; COPY(buf, name) END ExpectName; 8GD8#Syntax10.Scn.Fnt VAR ch: CHAR; i: INTEGER; BEGIN i := 0; Files.Read(r, ch); WHILE (ch = CR) DO Files.Read(r, ch) END; WHILE ~r.eof & (ch # ",") & (ch # " ") & (ch # NL) DO IF ch = '"' THEN Files.Read(r, ch); WHILE ~r.eof & (ch # '"') DO entry[i] := ch; INC(i); Files.Read(r, ch) END ELSIF ch # NL THEN entry[i] := ch; INC(i) END; Files.Read(r, ch) END; entry[i] := 0X END ExpectEntry; 8J;8#Syntax10.Scn.Fnt VAR pos: LONGINT; entry: ARRAY 256 OF CHAR; cur: List; BEGIN ExpectEntry(r, entry); NEW(l); COPY(entry, l.data); cur := l; pos := Files.Pos(r); WHILE ~r.eof & (entry # "alias") DO pos := Files.Pos(r); ExpectEntry(r, entry); IF ~r.eof & (entry # "alias") THEN NEW(cur.next); COPY(entry, cur.next.data); cur := cur.next END END; IF ~r.eof THEN Files.Set(r, Files.Base(r), pos) END END ExpectList; 8Syntax10i.Scn.Fnt TYPE PROCEDURE ExpectAlias (VAR r: Files.Rider; VAR ok: BOOLEAN);  PROCEDURE ExpectName (VAR r: Files.Rider; VAR name: ARRAY OF CHAR; VAR ok: BOOLEAN);  PROCEDURE ExpectEntry (VAR r: Files.Rider; VAR entry: ARRAY OF CHAR);  PROCEDURE ExpectList (VAR r: Files.Rider; VAR l: List; VAR ok: BOOLEAN);  BEGIN In.Open; In.Name(filename); IF ~In.Done THEN In.Open; In.String(filename) END; ASSERT(In.Done, 100); f := Files.Old(filename); IF f = NIL THEN Out.String("Import: Cannot open "); Out.String(filename); Out.Ln; RETURN END; Files.Set(r, f, 0); success := TRUE; REPEAT ExpectAlias(r, success); IF success THEN ExpectName(r, curName, success); IF success THEN ExpectList(r, l, success); IF success THEN IF l.next = NIL THEN (* single address *) NewAddress(curName, curName, l.data, adr); Insert(adr, ok2); IF ok2 # ok THEN Out.String("AddrBook: Could not insert "); Out.String(curName); Out.String(" into addressbook$") END ELSE NewGroup(curName, g); InsertGroup(g, ok2); IF ok2 # ok THEN Out.String("AddrBook: Could not insert group "); Out.String(curName); Out.String(" into addressbook$") END; WHILE l # NIL DO NewAddress(l.data, l.data, l.data, adr); Insert(adr, ok2); IF ok2 # ok THEN Out.String("AddrBook: Could not insert "); Out.String(l.data); Out.String(" into addressbook$") END; InsertAddressIntoGroup(g, adr, ok2); IF ok2 # ok THEN Out.String("AddrBook: Could not insert address into group$") END; l := l.next END END END END END UNTIL r.eof OR ~success END ImportEudora; 88#Syntax10.Scn.Fnt Init; 8e%MODULE AddrBook;   IMPORT PostOffice, Files, In, Out, Strings, Texts; CONST fileName* = "Addresses.Adr"; bakfileName* = "Addresses.Bak"; fileVersion = 3; (* -- operation results -- *) ok* = 0; alreadyExists* = 1; notFound* = 2; (* -- comparison results -- *) less* = -1; equal* = 0; greater* = 1; (* -- notifier op's -- *) load* = 0; add* = 1; remove* = 2; addAdr* = 3; removeAdr* = 4; edit* = 5; NL = 0DX; CR = 0AX; LF = 0DX; TYPE String* = PostOffice.String; Address* = POINTER TO AddressDesc; AddressDesc* = RECORD id: LONGINT; nickname-, realname-, email-: String; organization-, title-, address-, city-, state-, zip-, country-, workPhone-, homePhone-, fax-: String; notes-: Texts.Text END; AddressList = POINTER TO AddressListDesc; AddressListDesc = RECORD a: Address; next: AddressList END; Group* = POINTER TO GroupDesc; GroupDesc* = RECORD name-: POINTER TO ARRAY OF CHAR; nrMembers-: LONGINT; members: AddressList; next: Group END; Notify* = POINTER TO NotifyDesc; NotifyDesc* = RECORD op-: INTEGER END; AdrNotify* = POINTER TO AdrNotifyDesc; AdrNotifyDesc* = RECORD (NotifyDesc); a-: Address END; GroupNotify* = POINTER TO GroupNotifyDesc; GroupNotifyDesc* = RECORD (NotifyDesc); g-: Group; a-: Address END; Name = ARRAY 512 OF CHAR; CompProc = PROCEDURE (a, b: Address): SHORTINT; GroupEnumProc* = PROCEDURE (g: Group); AddressEnumProc* = PROCEDURE (a: Address); Notifier* = PROCEDURE (n: Notify); NotifierQ = POINTER TO NotifierQDesc; NotifierQDesc = RECORD p: Notifier; next: NotifierQ END; VAR head: AddressList; groups: Group; notifiers: NotifierQ; nrEntries-, nextID, nrGroups-: LONGINT; rid: Files.Rider; (* --- auxiliary ---*) PROCEDURE InNameString (VAR str: ARRAY OF CHAR);  (* --- Addresses --- *) PROCEDURE CompNick* (a, b: Address): SHORTINT;  PROCEDURE CompReal* (a, b: Address): SHORTINT;  PROCEDURE CompRealAndNick* (a, b: Address): SHORTINT;  PROCEDURE CompNickAndReal* (a, b: Address): SHORTINT;  PROCEDURE Contains (list: AddressList; a: Address): BOOLEAN;  PROCEDURE AddAddress (VAR list: AddressList; a: Address; VAR result: INTEGER);  VAR n: AddressList; BEGIN IF Contains(list, a) THEN result := alreadyExists ELSE NEW(n); n.a := a; n.next := list; list := n; result := ok END END AddAddress;  PROCEDURE RemoveAddress (VAR list: AddressList; a: Address; VAR result: INTEGER);  PROCEDURE Sort (list: AddressList; p: CompProc);  PROCEDURE Enumerate (list: AddressList; p: AddressEnumProc);  PROCEDURE ReorgId;  PROCEDURE (a: Address) PrintNickFirst*;  PROCEDURE (a: Address) PrintRealFirst*;  PROCEDURE CheckAndWrite (VAR r: Files.Rider; x: String);  PROCEDURE (a: Address) Write* (VAR r: Files.Rider);  (* --- Programming interface --- *) PROCEDURE InstallNotifier* (p: Notifier);  PROCEDURE RemoveNotifier* (p: Notifier);  PROCEDURE DoNotify (n: Notify);  PROCEDURE Insert* (a: Address; VAR result: INTEGER);  VAR n: AdrNotify; cur: AddressList; BEGIN IF (a.nickname^ = "") OR (a.realname^ = "") THEN result := alreadyExists; RETURN; END; cur := head; WHILE (cur # NIL) & (cur.a.nickname^ # a.nickname^) & (cur.a.realname^ # a.realname^) DO cur := cur.next; END; IF cur # NIL THEN result := alreadyExists; RETURN; END; AddAddress(head, a, result); IF result = ok THEN a.id := nextID; INC(nrEntries); INC(nextID); Notification END END Insert;  PROCEDURE InsertGroup* (g: Group; VAR result: INTEGER);  PROCEDURE InsertAddressIntoGroup* (VAR g: Group; a: Address; VAR result: INTEGER);  PROCEDURE NewAddress* (nick, real, email: ARRAY OF CHAR; VAR a: Address);  PROCEDURE NewGroup* (name: ARRAY OF CHAR; VAR g: Group);  PROCEDURE SearchByName* (name: ARRAY OF CHAR; VAR adr: Address);  PROCEDURE SearchGroupByName* (name: ARRAY OF CHAR; VAR g: Group);  PROCEDURE ReadAndCheck (VAR r: Files.Rider; VAR x: String);  PROCEDURE ReadAddress* (VAR r: Files.Rider; VAR a: Address);  PROCEDURE RemoveAddressFromGroup* (VAR g: Group; a: Address; VAR result: INTEGER);  PROCEDURE Remove* (a: Address; VAR result: INTEGER);  PROCEDURE RemoveGroup* (g: Group; VAR result: INTEGER);  PROCEDURE GetEmail* (name: ARRAY OF CHAR; VAR email: ARRAY OF CHAR);  PROCEDURE EnumerateGroups* (p: GroupEnumProc);  PROCEDURE EnumerateAdrReal* (p: AddressEnumProc);  PROCEDURE EnumerateAdrNick* (p: AddressEnumProc);  PROCEDURE EnumerateAdrGroup* (g: Group; c: CompProc; p: AddressEnumProc);  PROCEDURE SetGroupname* (g: Group; name: ARRAY OF CHAR);  PROCEDURE HasGroup* (a: Address; g: Group): BOOLEAN;  PROCEDURE SetValue* (VAR a: Address; fieldName, content: ARRAY OF CHAR; VAR result: INTEGER);  PROCEDURE SetNotes* (VAR a: Address; notes: Texts.Text);  PROCEDURE ChangeAddress* (a: Address; nick, real, email: ARRAY OF CHAR; VAR result: INTEGER);  (* --- Command interface ---- *) PROCEDURE Add*;  PROCEDURE AddGroup*;  PROCEDURE AddToGroup*;  PROCEDURE SetField*;  PROCEDURE Find*;  PROCEDURE Delete*; (** AddrBook.Delete (nickname | realname) **) VAR name: Name; a: Address; res: INTEGER; BEGIN In.Open; InNameString(name); IF ~In.Done THEN Out.String("Use AddrBook.Delete [nickname | realname]$"); RETURN END; SearchByName (name, a); IF a = NIL THEN Out.String(name); Out.String(" not found$"); RETURN END; Remove(a, res); IF res # ok THEN Out.String("AddrBook: could not delete address"); Out.String(name); Out.Ln END END Delete;  PROCEDURE DeleteGroup*;  PROCEDURE DeleteFromGroup*;  PROCEDURE pNick (a: Address);  PROCEDURE ListNick*;  PROCEDURE pReal (a: Address);  PROCEDURE ListReal*;  PROCEDURE ListGroup*;  PROCEDURE WriteId (a: Address);  PROCEDURE Store*;  PROCEDURE Init;  PROCEDURE Load*;  (* --- Import filters --- *) PROCEDURE ReadLine (VAR r: Texts.Reader; VAR line: ARRAY OF CHAR);  PROCEDURE GetFieldName (VAR line, field: ARRAY OF CHAR);  PROCEDURE GetContent (VAR line, content: ARRAY OF CHAR);  PROCEDURE isEmpty (VAR line: ARRAY OF CHAR): BOOLEAN;  PROCEDURE ImportNetscape*;  TYPE nsRecord = RECORD name, email, nickname, organization, title, address, city, state, zip, country, workPhone, homePhone, fax: ARRAY 512 OF CHAR; class: ARRAY 32 OF CHAR END; VAR filename: ARRAY 512 OF CHAR; f: Files.File; t: Texts.Text; r: Texts.Reader; line: ARRAY 1024 OF CHAR; nsR: nsRecord; field, content, member: ARRAY 512 OF CHAR; x: INTEGER; g: Group; a: Address; res: INTEGER; BEGIN In.Open; In.Name(filename); IF ~In.Done THEN In.Open; In.String(filename) END; ASSERT(In.Done, 100); f := Files.Old(filename); IF f = NIL THEN Out.String("Import: Cannot open "); Out.String(filename); Out.Ln; RETURN END; NEW(t); Texts.Open(t, filename); Texts.OpenReader(r, t, 0); ReadLine(r, line); WHILE ~r.eot DO nsR.name := ""; nsR.email := ""; nsR.nickname := ""; nsR.class := ""; nsR.organization := ""; nsR.title := ""; nsR.address := ""; nsR.city := ""; nsR.state := ""; nsR.zip := ""; nsR.country := ""; nsR.workPhone := ""; nsR.homePhone := ""; nsR.fax := ""; WHILE ~isEmpty(line) DO ReadLine(r, line); GetFieldName(line, field); GetContent(line, content); IF field = "cn" THEN COPY(content, nsR.name) ELSIF field = "mail" THEN COPY(content, nsR.email) ELSIF field = "xmozillanickname" THEN COPY(content, nsR.nickname) ELSIF field = "locality" THEN COPY(content, nsR.city) ELSIF field = "st" THEN COPY(content, nsR.state) ELSIF field = "title" THEN COPY(content, nsR.title) ELSIF field = "postOfficeBox" THEN COPY(content, nsR.address) ELSIF field = "postalcode" THEN COPY(content, nsR.zip) ELSIF field = "countryname" THEN COPY(content, nsR.country) ELSIF field = "telephonenumber" THEN COPY(content, nsR.workPhone) ELSIF field = "facsimiletelephonenumber" THEN COPY(content, nsR.fax) ELSIF field = "homephone" THEN COPY(content, nsR.homePhone) ELSIF field = "o" THEN COPY(content, nsR.organization) ELSIF field = "objectclass" THEN COPY(content, nsR.class); IF content = "groupOfNames" THEN NewGroup(nsR.name, g); InsertGroup(g, res); ASSERT(res = ok, 101) END ELSIF field = "member" THEN x := Strings.Pos("," , content, 3); Strings.Extract(content, 3, x - 3, member); SearchByName(member, a); IF (a # NIL) THEN InsertAddressIntoGroup(g, a, res); IF res # ok THEN Out.String("Could not insert "); Out.String(member); Out.String(" into group "); Out.String(g.name^); Out.Ln; END; ELSE Out.String("Could not insert "); Out.String(member); Out.String(" into group "); Out.String(g.name^); Out.Ln; END; END END; IF nsR.class = "person" THEN IF nsR.nickname = "" THEN nsR.nickname := nsR.name END; NewAddress(nsR.nickname, nsR.name, nsR.email, a); Insert(a, res); IF res # ok THEN Out.String("AddrBook: Could not import address: "); Out.String(nsR.name); Out.Ln ELSE Out.String("AddrBook: Imported "); Out.String(nsR.name); Out.Ln; SetValue(a, "organization", nsR.organization, res); SetValue(a, "title", nsR.title, res); SetValue(a, "address", nsR.address, res); SetValue(a, "city", nsR.city, res); SetValue(a, "state", nsR.state, res); SetValue(a, "zip", nsR.zip, res); SetValue(a, "country", nsR.country, res); SetValue(a, "workPhone", nsR.workPhone, res); SetValue(a, "homePhone", nsR.homePhone, res); SetValue(a, "fax", nsR.fax, res) END END; ReadLine(r, line) END END ImportNetscape;  PROCEDURE ImportEudora*;  BEGIN END AddrBook.