source: trunk/Packages/VCard/NameDetails.pas

Last change on this file was 173, checked in by chronos, 5 months ago
  • Modified: TNameDetails class moved into separate file.
File size: 11.0 KB
Line 
1unit NameDetails;
2
3interface
4
5uses
6 Classes, SysUtils, Common, LazUTF8;
7
8type
9 TNamePartKind = (npNone, npPrefix, npFirst, npMiddle, npLast, npSuffix);
10
11 TNamePart = record
12 Index: Integer;
13 Text: string;
14 PartKind: TNamePartKind;
15 NamePart: ^TNamePart;
16 Previous: ^TNamePart;
17 Next: ^TNamePart;
18 end;
19
20 TNameParts = array of TNamePart;
21
22 { TNameDetails }
23
24 TNameDetails = class
25 private
26 function GetAsNameParts: TNameParts;
27 function GetDetail(NamePartKind: TNamePartKind): string;
28 function IsSuffix(Text: string): Boolean;
29 public
30 Prefix: string;
31 First: string;
32 Middle: string;
33 Last: string;
34 Suffix: string;
35 procedure Split(FullName: string);
36 function GetCombined: string;
37 end;
38
39
40implementation
41
42function IsNumber(Text: string): Boolean;
43var
44 Value: Integer;
45begin
46 Result := TryStrToInt(Text, Value);
47end;
48
49function IsRomanNumber(Text: string): Boolean;
50var
51 I: Integer;
52begin
53 Result := True;
54 for I := 1 to Length(Text) do
55 if not (Text[I] in ['I', 'V', 'X', 'L', 'C', 'D', 'M']) then begin
56 Result := False;
57 Break;
58 end;
59end;
60
61procedure SearchPart(var NameParts: TNameParts; var NamePart: TNamePart);
62var
63 I: Integer;
64begin
65 for I := 0 to Length(NameParts) - 1 do begin
66 if (NameParts[I].PartKind = npNone) and (NameParts[I].Text = NamePart.Text) then begin
67 NameParts[I].PartKind := NamePart.PartKind;
68 NameParts[I].NamePart := @NamePart;
69 NamePart.NamePart := @NameParts[I];
70 Break;
71 end;
72 end;
73end;
74
75procedure SearchPartBackward(var NameParts: TNameParts; var NamePart: TNamePart);
76var
77 I: Integer;
78begin
79 for I := Length(NameParts) - 1 downto 0 do begin
80 if (NameParts[I].PartKind = npNone) and (NameParts[I].Text = NamePart.Text) then begin
81 NameParts[I].PartKind := NamePart.PartKind;
82 NameParts[I].NamePart := @NamePart;
83 NamePart.NamePart := @NameParts[I];
84 Break;
85 end;
86 end;
87end;
88
89function UsedInNameParts(NamePart: TNamePartKind; NameParts: TNameParts): Boolean;
90var
91 I: Integer;
92begin
93 I := 0;
94 while (I < Length(NameParts)) and (NameParts[I].PartKind <> NamePart) do Inc(I);
95 Result := I < Length(NameParts);
96end;
97
98{ TNameDetails }
99
100function TNameDetails.GetAsNameParts: TNameParts;
101var
102 I: Integer;
103 K: TNamePartKind;
104 Parts: TStringArray;
105begin
106 Result := Default(TNameParts);
107 for K := Low(TNamePartKind) to High(TNamePartKind) do begin
108 if GetDetail(K) <> '' then begin
109 Parts := Explode(' ', GetDetail(K));
110 for I := 0 to Length(Parts) - 1 do begin
111 SetLength(Result, Length(Result) + 1);
112 Result[Length(Result) - 1].Text := Parts[I];
113 Result[Length(Result) - 1].PartKind := K;
114 Result[Length(Result) - 1].Index := Length(Result) - 1;
115 end;
116 end;
117 end;
118
119 // Update previous and next links
120 for I := 0 to Length(Result) - 1 do begin
121 if I > 0 then
122 Result[I].Previous := @Result[I - 1];
123 if (I + 1) < Length(Result) then
124 Result[I].Next := @Result[I + 1];
125 end;
126end;
127
128function TNameDetails.GetDetail(NamePartKind: TNamePartKind): string;
129begin
130 case NamePartKind of
131 npPrefix: Result := Prefix;
132 npFirst: Result := First;
133 npMiddle: Result := Middle;
134 npLast: Result := Last;
135 npSuffix: Result := Suffix;
136 end;
137end;
138
139function TNameDetails.IsSuffix(Text: string): Boolean;
140begin
141 Result := (Pos('.', Text) > 0) or IsNumber(Text) or
142 IsRomanNumber(Text);
143end;
144
145procedure TNameDetails.Split(FullName: string);
146var
147 Parts: TStringArray;
148 NewNameParts: TNameParts;
149 OldNameParts: TNameParts;
150 I: Integer;
151 J: Integer;
152 Text: string;
153 NextKind: TNamePartKind;
154begin
155 OldNameParts := GetAsNameParts;
156
157 Text := FullName;
158 while Pos(' ', FullName) > 0 do
159 FullName := StringReplace(FullName, ' ', ' ', [rfReplaceAll]);
160 Text := Trim(Text);
161
162 Parts := Explode(' ', Text);
163 NewNameParts := Default(TNameParts);
164 SetLength(NewNameParts, Length(Parts));
165 for I := 0 to Length(NewNameParts) - 1 do begin
166 NewNameParts[I].Index := I;
167 NewNameParts[I].PartKind := npNone;
168 NewNameParts[I].Text := Parts[I];
169 if I > 0 then
170 NewNameParts[I].Previous := @NewNameParts[I - 1];
171 if (I + 1) < Length(NewNameParts) then
172 NewNameParts[I].Next := @NewNameParts[I + 1];
173 end;
174
175 // Match existing parts
176 for I := 0 to Length(OldNameParts) - 1 do begin
177 if OldNameParts[I].Text <> '' then
178 SearchPart(NewNameParts, OldNameParts[I]);
179 end;
180
181 // Check incorrect matches
182 for I := 0 to Length(OldNameParts) - 1 do begin
183 for J := I + 1 to Length(OldNameParts) - 1 do
184 if Assigned(OldNameParts[I].NamePart) and Assigned(OldNameParts[J].NamePart) and
185 (OldNameParts[I].NamePart^.Index >= OldNameParts[J].NamePart^.Index) then begin
186 if Abs(I - OldNameParts[I].NamePart^.Index) >
187 Abs(J - OldNameParts[J].NamePart^.Index) then begin
188 OldNameParts[I].NamePart^.PartKind := npNone;
189 OldNameParts[I].NamePart^.NamePart := nil;
190 OldNameParts[I].NamePart := nil;
191 end else begin
192 OldNameParts[J].NamePart^.PartKind := npNone;
193 OldNameParts[J].NamePart^.NamePart := nil;
194 OldNameParts[J].NamePart := nil;
195 end;
196 end;
197 end;
198 for I := Length(OldNameParts) - 1 downto 0 do begin
199 for J := I - 1 downto 0 do
200 if Assigned(OldNameParts[I].NamePart) and Assigned(OldNameParts[J].NamePart) and
201 (OldNameParts[I].NamePart^.Index <= OldNameParts[J].NamePart^.Index) then begin
202 if Abs(I - OldNameParts[I].NamePart^.Index) >
203 Abs(J - OldNameParts[J].NamePart^.Index) then begin
204 OldNameParts[I].NamePart^.PartKind := npNone;
205 OldNameParts[I].NamePart^.NamePart := nil;
206 OldNameParts[I].NamePart := nil;
207 end else begin
208 OldNameParts[J].NamePart^.PartKind := npNone;
209 OldNameParts[J].NamePart^.NamePart := nil;
210 OldNameParts[J].NamePart := nil;
211 end;
212 end;
213 end;
214
215 // Match existing parts backqards
216 for I := Length(OldNameParts) - 1 downto 0 do begin
217 if (OldNameParts[I].Text <> '') and not Assigned(OldNameParts[I].NamePart) then
218 SearchPartBackward(NewNameParts, OldNameParts[I]);
219 end;
220
221 // Match uncertain parts
222 for I := 0 to Length(OldNameParts) - 1 do
223 if not Assigned(OldNameParts[I].NamePart) then begin
224 if Assigned(OldNameParts[I].Next) and
225 Assigned(OldNameParts[I].Next^.NamePart) and
226 Assigned(OldNameParts[I].Next^.NamePart^.Previous) and
227 (OldNameParts[I].Next^.NamePart^.Previous^.PartKind = npNone) then begin
228 OldNameParts[I].NamePart := OldNameParts[I].Next^.NamePart^.Previous;
229 OldNameParts[I].Next^.NamePart^.Previous^.NamePart := @OldNameParts[I];
230 OldNameParts[I].Next^.NamePart^.Previous^.PartKind := OldNameParts[I].PartKind;
231 end else
232 if Assigned(OldNameParts[I].Previous) and
233 Assigned(OldNameParts[I].Previous^.NamePart) and
234 Assigned(OldNameParts[I].Previous^.NamePart^.Next) and
235 (OldNameParts[I].Previous^.NamePart^.Next^.PartKind = npNone) then begin
236 OldNameParts[I].NamePart := OldNameParts[I].Previous^.NamePart^.Next;
237 OldNameParts[I].Previous^.NamePart^.Next^.NamePart := @OldNameParts[I];
238 OldNameParts[I].Previous^.NamePart^.Next^.PartKind := OldNameParts[I].PartKind;
239 end;
240 end;
241
242 // Mark new unknown parts according existing parts
243 for I := Length(Parts) - 1 downto 0 do
244 if (NewNameParts[I].PartKind = npNone) and
245 Assigned(NewNameParts[I].Next) and
246 (NewNameParts[I].Next^.PartKind <> npNone) then begin
247 if (NewNameParts[I].Next^.PartKind = npFirst) and
248 EndsWith(NewNameParts[I].Text, '.') then begin
249 NewNameParts[I].PartKind := npPrefix;
250 end else NewNameParts[I].PartKind := NewNameParts[I].Next^.PartKind;
251 end;
252
253 // Mark unknown parts according to neighbouring parts
254 for I := 0 to Length(Parts) - 1 do
255 if (NewNameParts[I].PartKind = npNone) and
256 Assigned(NewNameParts[I].Previous) and
257 (NewNameParts[I].Previous^.PartKind <> npNone) then begin
258 if (NewNameParts[I].Previous^.PartKind in [npLast, npMiddle]) and
259 IsSuffix(NewNameParts[I].Text) then begin
260 NewNameParts[I].PartKind := npSuffix;
261 end else
262 if (NewNameParts[I].Previous^.PartKind = npFirst) and
263 (Last = '') then begin
264 NewNameParts[I].PartKind := npLast;
265 end else
266 if (NewNameParts[I].Previous^.PartKind = npLast) and
267 (Middle = '') then begin
268 NewNameParts[I].PartKind := npLast;
269 NewNameParts[I].Previous^.PartKind := npMiddle;
270 end else
271 if (NewNameParts[I].Previous^.PartKind = npPrefix) then begin
272 NewNameParts[I].PartKind := npFirst;
273 end else
274 NewNameParts[I].PartKind := NewNameParts[I].Previous^.PartKind;
275 end;
276
277 // Mark remaining unknown parts based on defined filling sequence
278 NextKind := npFirst;
279 for I := 0 to Length(Parts) - 1 do
280 if NewNameParts[I].PartKind = npNone then begin
281 if EndsWith(NewNameParts[I].Text, '.') and (NextKind = npFirst) then begin
282 NewNameParts[I].PartKind := npPrefix;
283 end else
284 if (NextKind = npMiddle) and IsSuffix(NewNameParts[I].Text) then begin
285 NewNameParts[I].PartKind := npSuffix;
286 NextKind := npSuffix;
287 end else
288 if NextKind = npMiddle then begin
289 NewNameParts[I].Previous^.PartKind := npMiddle;
290 NewNameParts[I].PartKind := npLast;
291 end else begin
292 NewNameParts[I].PartKind := NextKind;
293 if NextKind = npFirst then NextKind := npLast
294 else if NextKind = npLast then NextKind := npMiddle;
295 end;
296 end;
297
298 // Combine multiple parts to base parts
299 Prefix := '';
300 First := '';
301 Middle := '';
302 Last := '';
303 Suffix := '';
304 for I := 0 to Length(Parts) - 1 do
305 case NewNameParts[I].PartKind of
306 npPrefix: Prefix := Trim(Prefix + ' ' + Parts[I]);
307 npFirst: First := Trim(First + ' ' + Parts[I]);
308 npMiddle: Middle := Trim(Middle + ' ' + Parts[I]);
309 npLast: Last := Trim(Last + ' ' + Parts[I]);
310 npSuffix: Suffix := Trim(Suffix + ' ' + Parts[I]);
311 end;
312
313{
314 // Title Prefix
315 while (Length(Parts) > 0) and EndsWith(Parts[0], '.') do begin
316 Prefix := Trim(Prefix + ' ' + Parts[0]);
317 Delete(Parts, 0, 1);
318 end;
319
320 // Title Suffix
321 if ProcessAfter then
322 for I := 0 to High(Parts) do
323 if (Pos('.', Parts[I]) > 0) or IsNumber(Parts[I]) or IsRomanNumber(Parts[I]) then begin
324 for J := I to High(Parts) do
325 Suffix := Trim(Suffix + ' ' + Parts[J]);
326 SetLength(Parts, I);
327 Break;
328 end;
329
330 if Length(Parts) = 0 then begin
331 end else
332 if Length(Parts) = 1 then begin
333 First := Parts[0];
334 end else
335 if Length(Parts) = 2 then begin
336 First := Parts[0];
337 Last := Parts[1];
338 end else begin
339 First := Parts[0];
340 for I := 0 to Length(Parts) - 3 do
341 Middle := Trim(Middle + ' ' + Parts[I + 1]);
342 Last := Parts[High(Parts)];
343 end;}
344end;
345
346function TNameDetails.GetCombined: string;
347begin
348 Result := '';
349 if Prefix <> '' then Result := Result + ' ' + Prefix;
350 if First <> '' then Result := Result + ' ' + First;
351 if Middle <> '' then Result := Result + ' ' + Middle;
352 if Last <> '' then Result := Result + ' ' + Last;
353 if Suffix <> '' then Result := Result + ' ' + Suffix;
354 Result := Trim(Result);
355end;
356
357end.
358
Note: See TracBrowser for help on using the repository browser.