1unit PoFamilies;
2
3{ $define DebugSimplePoFiles}
4
5{$mode objfpc}{$H+}
6
7interface
8
9uses
10  Classes, SysUtils, ContNrs, Math,
11  // LCL
12  LCLProc, Masks,
13  // LazUtils
14  FileUtil, LazFileUtils, Translations, StringHashList,
15  // PoChecker
16  PoCheckerConsts;
17
18Type
19
20  TPoTestType = (pttCheckNrOfItems, pttCheckFormatArgs, pttCheckMissingIdentifiers,
21                 pttCheckMismatchedOriginals);
22  TPoTestTypes = Set of TPoTestType;
23
24  TPoTestOption = (ptoFindAllChildren);
25  TPoTestOptions = set of TPoTestOption;
26
27const
28    optRunAllTests: TPoTestTypes = [];
29
30    PoTestTypeNames: array[TPoTestType] of String = (
31      sCheckNumberOfItems,
32      sCheckForIncompatibleFormatArguments,
33      sCheckMissingIdentifiers,
34      sCheckForMismatchesInUntranslatedStrings
35    );
36
37Type
38  { TPoFamily }
39
40  TTestStartEvent = procedure(const ATestName, APoFileName: String) of object;
41  TTestEndEvent = procedure(const ATestName: String; const ErrorCount: Integer) of object;
42
43  TPoFamilyStats = class;
44
45  TPoFamily = class
46  private
47    FMaster: TPOFile;
48    FChild: TPOFile;
49    FMasterName: String;
50    FChildName: String;
51    FOnTestStart: TTestStartEvent;
52    FOnTestEnd: TTestEndEvent;
53    FPoFamilyStats: TPoFamilyStats;
54    FTestOptions: TPoTestOptions;
55    FTestTypes: TPoTestTypes;
56    procedure SetChildName(AValue: String);
57    procedure SetMasterName(AValue: String);
58    function GetShortMasterName: String;
59    function GetShortChildName: String;
60  protected
61    procedure DoTestStart(const ATestName, APoFileName: String);
62    procedure DoTestEnd(const ATestName: String; const ErrorCount: Integer);
63  public
64    constructor Create;
65    constructor Create(const MasterName: String);
66    constructor Create(const AMasterName, AChildName: String);
67    destructor Destroy; override;
68
69  protected
70    procedure CheckNrOfItems(out ErrorCount: Integer; ErrorLog: TStrings);
71    procedure CheckFormatArgs(out ErrorCount, NonFuzzyErrorCount: Integer; ErrorLog: TStrings);
72    procedure CheckMissingIdentifiers(out ErrorCount: Integer; ErrorLog: TStrings);
73    procedure CheckMismatchedOriginals(out ErrorCount: Integer; ErrorLog: TStrings);
74    procedure CheckDuplicateOriginals(out WarningCount: Integer; ErrorLog: TStrings);
75    procedure CheckStatistics(ErrorCnt: Integer);
76
77  public
78    procedure RunTests(out ErrorCount, NonFuzzyErrorCount, WarningCount,
79      TranslatedCount, UntranslatedCount, FuzzyCount: Integer; ErrorLog, StatLog, DupLog: TStringList);
80
81    property Master: TPOFile read FMaster;
82    property Child: TPOFile read FChild;
83    property MasterName: String read FMasterName write SetMasterName;
84    property ChildName: String read FChildName write SetChildName;
85    property TestTypes: TPoTestTypes read FTestTypes write FTestTypes;
86    property TestOptions: TPoTestOptions read FTestOptions write FTestOptions;
87    property PoFamilyStats: TPoFamilyStats read FPoFamilyStats;
88    property ShortMasterName: String read  GetShortMasterName;
89    property ShortChildName: String read GetShortChildName;
90    property OnTestStart: TTestStartEvent read FOnTestStart write FOnTestStart;
91    property OnTestEnd: TTestEndEvent read FOnTestEnd write FOnTestEnd;
92  end;
93
94  { TPoFamilyStats }
95
96  { TStat }
97
98  TStat = class
99  private
100    FPoName: String;
101    FNrTotal: Integer;
102    FNrTranslated: Integer;
103    FNrUnTranslated: Integer;
104    FNrFuzzy: Integer;
105    FNrErrors: Integer;
106  public
107    constructor Create(APoName: String; ANrTotal, ANrTranslated, ANrUntranslated, ANrFuzzy, ANrErrors: Integer);
108    function ShortPoName: String;
109    property PoName: string read FPoName;
110    property NrTotal: Integer read FNrTotal;
111    property NrTranslated: Integer read FNrTranslated;
112    property NrUnTranslated: Integer read FNrUnTranslated;
113    property NrFuzzy: Integer read FNrFuzzy;
114    property NrErrors: Integer read FNrErrors;
115    function PercTranslated: Double; inline;
116    function PercUnTranslated: Double; inline;
117    function PercFuzzy: Double; inline;
118    function FracTranslated: Double;
119    function FracUnTranslated: Double;
120    function FracFuzzy: Double;
121  end;
122
123  TPoFamilyStats = class
124  private
125    FList: TFPObjectList;
126    function GetCount: Integer;
127    function GetItems(Index: Integer): TStat;
128  public
129    procedure Clear;
130    procedure Add(AName: String; ANrTotal, ANrTranslated, ANrUnTranslated, ANrFuzzy, ANrErrors: Integer);
131    procedure AddItemsTo(APoFamilyStats: TPoFamilyStats);
132    constructor Create;
133    destructor Destroy; override;
134    procedure AddStatisticsToLog(ALog: TStrings);
135
136    property Items[Index: Integer]: TStat read GetItems;
137    property Count: Integer read GetCount;
138  end;
139
140function IsMasterPoName(const Fn: String): Boolean;
141function ExtractMasterNameFromChildName(const AChildName: String): String;
142function ExtractLanguageFromChildName(const AChildName: string): TLangID;
143procedure LocalizePoTestTypeNames;
144
145const
146  NoError = 0;
147
148implementation
149
150const
151  sCommentIdentifier = '#: ';
152  //sCharSetIdentifier = '"Content-Type: text/plain; charset=';
153  sMsgID = 'msgid "';
154  sMsgStr = 'msgstr "';
155  //sMsgCtxt = 'msgctxt "';
156  //sFlags = '#, ';
157  //sPrevMsgID = '#| msgid "';
158  //sPrevStr = '#| "';
159
160  Divider = '-------------------------------------------------------';
161
162  sFormatArgsID = '%s %s';
163  sFormatArgsValues = '%s%s"   (= %s)';
164
165  sMismatchOriginalsID = '%s';
166  sMismatchOriginalsM = '%s: %s';
167  sMismatchOriginalsC = '%s: %s';
168
169  sShortCheckFormatArgs = 'CheckFormatArgs';
170  sShortCheckNrOfItems =  'CheckNrOfItems';
171  sShortCheckMissingIdentifiers = 'CheckMissingIdentifiers';
172  sShortCheckMismatchedOriginals = 'CheckMismatchedOriginals';
173
174//Helper functions
175
176
177function IsMasterPoName(const Fn: String): Boolean;
178//Returns True if Fn is like '[Path/To/]somename.po'
179var
180  Ext: String;
181  FnOnly: String;
182  IsInValidFn: Boolean;
183begin
184  FnOnly := ExtractFileNameOnly(Fn);
185  //check if filename is like 'af_ZA.po', which is an invalid name for a master po-file
186  //a bit crude, but will do now (at least for Lazarus)
187  IsInValidFn := MatchesMaskList(FnOnly, '??;??_??',';',False);
188  Ext := ExtractFileExt(Fn);
189  Result := not IsInValidFn and
190            (Length(FnOnly) > 0) and
191            (CompareText(Ext, ExtensionSeparator + 'po') = 0) and
192            (Pos(ExtensionSeparator, FnOnly) = 0);
193end;
194
195function ExtractMasterNameFromChildName(const AChildName: String): String;
196{
197  Pre condition: AChildName is like: somename.some_language_specifier.po
198  Post condition: Result  = somename.po
199}
200var
201  Ext: String;
202  EndSep: Set of Char;
203  Len: Integer;
204begin
205  EndSep := AllowDirectorySeparators + AllowDriveSeparators + [ExtensionSeparator];
206  Ext := ExtractFileExt(AChildName);
207  Result := Copy(AChildName, 1, Length(AChildName) - Length(Ext));
208  Len := Length(Result);
209  While (Len > 0) and (not (Result[Len] in EndSep)) do Dec(Len);
210
211  //debugln('Len = ',DbgS(Len));
212  //debugln('Length(Result) = ',DbgS(Length(result)));
213  //if Len > 0 then debugln('Result[Len] = ',Result[len]);
214
215  if (Len > 1) and (Len < Length(Result)) and (Result[Len] = ExtensionSeparator) then
216    Result := Copy(Result, 1, Len - 1) + Ext
217  else
218    Result := '';
219end;
220
221function ExtractLanguageFromChildName(const AChildName: string): TLangID;
222Var
223  Mn, Abbr: string;
224  P1,P2: Integer;
225begin
226  Mn := ExtractMasterNameFromChildName(AChildName);
227  Mn := ExtractFileNameWithoutExt(Mn);
228  P1 := Length(Mn);
229  P2 := Length(AChildName);
230  Abbr := Copy(AChildName,P1+2,P2-(P1+1));
231  Abbr := ExtractFileNameWithoutExt(Abbr);
232  Result := LangAbbrToLangId(Abbr);
233end;
234
235procedure LocalizePoTestTypeNames;
236begin
237  PoTestTypeNames[pttCheckNrOfItems] := sCheckNumberOfItems;
238  PoTestTypeNames[pttCheckFormatArgs] := sCheckForIncompatibleFormatArguments;
239  PoTestTypeNames[pttCheckMissingIdentifiers] := sCheckMissingIdentifiers;
240  PoTestTypeNames[pttCheckMismatchedOriginals] := sCheckForMismatchesInUntranslatedStrings;
241end;
242
243{ TStat }
244
245constructor TStat.Create(APoName: String; ANrTotal, ANrTranslated, ANrUntranslated, ANrFuzzy, ANrErrors: Integer);
246begin
247  FPoName := APoName;
248  FNrTotal := ANrTotal;
249  FNrTranslated := ANrTranslated;
250  FNrUntranslated := ANrUntranslated;
251  FNrFuzzy := ANrFuzzy;
252  FNrErrors := ANrErrors;
253end;
254
255function TStat.ShortPoName: String;
256begin
257  Result := ExtractFilename(FPoName);
258end;
259
260function TStat.PercTranslated: Double;
261begin
262  Result := 100 * FracTranslated;
263end;
264
265function TStat.PercUnTranslated: Double;
266begin
267  Result := 100 * FracUnTranslated;
268end;
269
270function TStat.PercFuzzy: Double;
271begin
272  Result := 100 * FracFuzzy;
273end;
274
275function TStat.FracTranslated: Double;
276begin
277  Result := (FNrTranslated / FNrTotal);
278end;
279
280function TStat.FracUnTranslated: Double;
281begin
282  Result := (FNrUnTranslated / FNrTotal);
283end;
284
285function TStat.FracFuzzy: Double;
286begin
287  Result := (FNrFuzzy / FNrTotal);
288end;
289
290{ TPoFamilyStats }
291
292function TPoFamilyStats.GetCount: Integer;
293begin
294  Result := FList.Count;
295end;
296
297function TPoFamilyStats.GetItems(Index: Integer): TStat;
298begin
299  Result := TStat(FList.Items[Index]);
300end;
301
302procedure TPoFamilyStats.Clear;
303begin
304  FList.Clear;
305end;
306
307procedure TPoFamilyStats.Add(AName: String; ANrTotal, ANrTranslated, ANrUnTranslated, ANrFuzzy, ANrErrors: Integer);
308begin
309  FList.Add(TStat.Create(AName, ANrTotal, ANrTranslated, ANrUntranslated, ANrFuzzy, ANrErrors));
310end;
311
312procedure TPoFamilyStats.AddItemsTo(APoFamilyStats: TPoFamilyStats);
313var
314  i: Integer;
315  AStat: TStat;
316begin
317  for i := 0 to FList.Count - 1 do
318  begin
319    AStat := GetItems(i);
320    APoFamilyStats.Add(AStat.PoName, AStat.NrTotal, AStat.NrTranslated,
321                       AStat.NrUntranslated, AStat.NrFuzzy, AStat.NrErrors);
322  end;
323end;
324
325constructor TPoFamilyStats.Create;
326begin
327  FList := TFPObjectList.Create(True);
328end;
329
330destructor TPoFamilyStats.Destroy;
331begin
332  FList.Free;
333  inherited Destroy;
334end;
335
336procedure TPoFamilyStats.AddStatisticsToLog(ALog: TStrings);
337var
338  i: Integer;
339  Stat: TStat;
340  function Bar(Nr, Total: Integer; RoundDown: Boolean): String;
341  const
342    Max = 50;
343  var
344    Count: Integer;
345  begin
346    if RoundDown then
347      Count := Floor(Max * (Nr/Total))
348    else
349      Count := Ceil(Max * (Nr/Total));
350    Result := StringOfChar('x', Count);
351    Result := Result + StringOfChar(#32, Max - Count);
352  end;
353begin
354  if (FList.Count = 0) then Exit;
355  for i := 0 to FList.Count - 1 do
356  begin
357    Stat := TStat(FList.Items[i]);
358    ALog.Add(Stat.PoName);
359    ALog.Add(Format(sPercTranslated,[Bar(Stat.NrTranslated, Stat.NrTotal, True),Stat.PercTranslated]));
360    ALog.Add(Format(sPercUntranslated,[Bar(Stat.NrUnTranslated, Stat.NrTotal, False), Stat.PercUnTranslated]));
361    ALog.Add(Format(sPercFuzzy,[Bar(Stat.NrFuzzy, Stat.NrTotal, False), Stat.PercFuzzy]));
362    ALog.Add('');
363    ALog.Add('');
364  end;
365end;
366
367{ TPoFamily }
368
369procedure TPoFamily.SetMasterName(AValue: String);
370begin
371  if FMasterName = AValue then Exit;
372  FMaster.Free;
373  FMaster := nil;
374  FMasterName := '';
375  if (AValue <> '') then FMaster := TPOFile.Create(AValue, True, False);
376  FMasterName := AValue;
377end;
378
379function TPoFamily.GetShortMasterName: String;
380begin
381  Result := ExtractFileName(FMasterName);
382end;
383
384function TPoFamily.GetShortChildName: String;
385begin
386  Result := ExtractFileName(FChildName);
387end;
388
389procedure TPoFamily.DoTestStart(const ATestName, APoFileName: String);
390begin
391  if Assigned(FOnTestStart) then FOnTestStart(ATestName, APoFileName);
392end;
393
394procedure TPoFamily.DoTestEnd(const ATestName: String; const ErrorCount: Integer);
395begin
396  if Assigned(FOnTestEnd) then FOnTestEnd(ATestName, ErrorCount);
397end;
398
399
400procedure TPoFamily.SetChildName(AValue: String);
401begin
402  if FChildName = AValue then Exit;
403  FChild.Free;
404  FChild := nil;
405  FChildName := '';
406  if (AValue <> '') then FChild := TPOFile.Create(AValue, True, False);
407  FChildName := AValue;
408end;
409
410constructor TPoFamily.Create;
411begin
412  Create('','');
413end;
414
415constructor TPoFamily.Create(const MasterName: String);
416begin
417  Create(MasterName, '');
418end;
419
420constructor TPoFamily.Create(const AMasterName, AChildName: String);
421begin
422  if (AMasterName <> '') then
423  begin
424    FMaster := TPOFile.Create(AMasterName, True, False);
425    FMasterName := AMasterName;
426    //debugln('TPoFamily.Create: created ',FMasterName);
427  end;
428  if (AChildName <> '') then
429  begin
430    FChild := TPOFile.Create(AChildName, True, False);
431    FChildName := AChildName;
432    //debugln('TPoFamily.Create: created ',FChildName);
433  end;
434  FPoFamilyStats := TPoFamilyStats.Create;
435end;
436
437destructor TPoFamily.Destroy;
438begin
439  if Assigned(FMaster) then FMaster.Free;
440  if Assigned(FChild) then FChild.Free;
441  FPoFamilyStats.Free;
442  inherited Destroy;
443end;
444
445procedure TPoFamily.CheckNrOfItems(out ErrorCount: Integer; ErrorLog: TStrings);
446begin
447  //debugln('TPoFamily.CheckNrOfItems');
448  DoTestStart(PoTestTypeNames[pttCheckNrOfItems], ShortChildName);
449  if (FMaster.Count <> FChild.Count) then
450  begin
451    ErrorCount := 1;
452    ErrorLog.Add(Divider);
453    ErrorLog.Add(Format(sErrorsByTest,[sShortCheckNrOfItems]));
454    ErrorLog.Add(ShortChildName);
455    ErrorLog.Add(Divider);
456    ErrorLog.Add('');
457    ErrorLog.Add(sNrOfItemsMismatch);
458    ErrorLog.Add(Format(sNrOfItemsMismatchD,[ShortMasterName,FMaster.Count]));
459    ErrorLog.Add(Format(sNrOfItemsMismatchD,[ShortChildName,FChild.Count]));
460    ErrorLog.Add(Divider);
461    ErrorLog.Add('');
462    ErrorLog.Add('');
463  end
464  else ErrorCount := NoError;
465  DoTestEnd(PoTestTypeNames[pttCheckNrOfItems], ErrorCount);
466  //debugln('TPoFamily.CheckNrOfItemsMismatch: ',Dbgs(ErrorCount),' Errors');
467end;
468
469procedure TPoFamily.CheckFormatArgs(out ErrorCount, NonFuzzyErrorCount: Integer
470  ; ErrorLog: TStrings);
471var
472  i: Integer;
473  CPoItem: TPOFileItem;
474  IsFuzzy: Boolean;
475  IsBadFormat: Boolean;
476begin
477  //debugln('TPoFamily.CheckFormatArgs');
478  DoTestStart(PoTestTypeNames[pttCheckFormatArgs], ShortChildName);
479  ErrorCount := NoError;
480  NonFuzzyErrorCount := NoError;
481  for i := 0 to FChild.Count - 1 do
482  begin
483    //debugln('  i = ',DbgS(i));
484    //MPoItem := FMaster.PoItems[i];
485    CPoItem := FChild.PoItems[i];
486    //CPoItem := FChild.FindPoItem(MPoItem.IdentifierLow);
487    if Assigned(CPoItem) then
488    begin
489      IsFuzzy := (Pos(sFuzzyFlag, CPoItem.Flags) > 0);
490      IsBadFormat := (Pos(sBadFormatFlag, CPoItem.Flags) > 0);
491      //if (IgnoreFuzzyStrings and IsFuzzy) then debugln('Skipping fuzzy translation: ',CPoItem.Translation);
492      if (Length(CPoItem.Translation) > 0) and IsBadFormat then
493      begin
494        if (ErrorCount = 0) then
495        begin
496          ErrorLog.Add(Divider);
497          ErrorLog.Add(Format(sErrorsByTest,[sShortCheckFormatArgs]));
498          ErrorLog.Add(ShortChildName);
499          ErrorLog.Add(Divider);
500          ErrorLog.Add('');
501        end;
502        Inc(ErrorCount);
503        if not IsFuzzy then
504          Inc(NonFuzzyErrorCount);
505        ErrorLog.Add(Format(sIncompatibleFormatArgs,[CPoItem.LineNr]));
506        ErrorLog.Add(Format(sFormatArgsID,[sCommentIdentifier, CPoItem.IdentifierLow]));
507        ErrorLog.Add(Format(sFormatArgsValues,[sMsgID,CPoItem.Original,sOriginal]));
508        ErrorLog.Add(Format(sFormatArgsValues,[sMsgStr,CPoItem.Translation,sTranslation]));
509        if IsFuzzy then ErrorLog.Add(sNoteTranslationIsFuzzy);
510        ErrorLog.Add('');
511      end;
512    end;
513  end;
514  if (ErrorCount > 0) then
515  begin
516    ErrorLog.Add(Format(sNrErrorsFound,[ErrorCount]));
517    ErrorLog.Add(Divider);
518    ErrorLog.Add('');
519    ErrorLog.Add('');
520  end;
521  DoTestEnd(PoTestTypeNames[pttCheckFormatArgs], ErrorCount);
522  //debugln('TPoFamily.CheckIncompatibleFormatArgs: ',Dbgs(ErrorCount),' Errors');
523end;
524
525procedure TPoFamily.CheckMissingIdentifiers(out ErrorCount: Integer;
526  ErrorLog: TStrings);
527var
528  i: Integer;
529  MPoItem, CPoItem: TPOFileItem;
530begin
531  //debugln('TPoFamily.CheckMissingIdentifiers');
532  DoTestStart(PoTestTypeNames[pttCheckMissingIdentifiers], ShortChildName);
533  ErrorCount := NoError;
534  for i := 0 to FMaster.Count - 1 do
535  begin
536    MPoItem := FMaster.PoItems[i];
537    if Assigned(MPoItem) and (MPoItem.IdentifierLow <> '') then
538    begin
539      CPoItem := FChild.FindPoItem(MPoItem.IdentifierLow);
540      if not Assigned(CPoItem) then
541      begin
542        if (ErrorCount = 0) then
543        begin
544          ErrorLog.Add(Divider);
545          ErrorLog.Add(Format(sErrorsByTest,[sShortCheckMissingIdentifiers]));
546          ErrorLog.Add(ShortChildName);
547          ErrorLog.Add(Divider);
548          ErrorLog.Add('');
549        end;
550        Inc(ErrorCount);
551        ErrorLog.Add(Format(sLineInFileName,
552                            [MPoItem.LineNr,ShortMasterName]));
553        ErrorLog.Add(Format(sIdentifierNotFoundIn,
554                            [MPoItem.IdentifierLow,ShortChildName]));
555        ErrorLog.Add('');
556      end;
557    end;
558  end;
559  //Now reverse the search
560  for i := 0 to FChild.Count - 1 do
561  begin
562    CPoItem := FChild.PoItems[i];
563    if Assigned(CPoItem) and (CPoItem.IdentifierLow <> '') then
564    begin
565      MPoItem := FMaster.FindPoItem(CPoItem.IdentifierLow);
566      if not Assigned(MPoItem) then
567      begin
568        if (ErrorCount = 0) then
569        begin
570          ErrorLog.Add(Divider);
571          ErrorLog.Add(Format(sErrorsByTest,[sShortCheckMissingIdentifiers]));
572          ErrorLog.Add(ShortChildName);
573          ErrorLog.Add(Divider);
574          ErrorLog.Add('');
575        end;
576        Inc(ErrorCount);
577        ErrorLog.Add(Format(sLineNr,
578                            [CPoItem.LineNr]));
579        ErrorLog.Add(Format(sMissingMasterIdentifier,
580                            [CPoItem.IdentifierLow,ShortChildName,ShortMasterName]));
581        ErrorLog.Add('');
582      end;
583    end;
584  end;
585  if (ErrorCount > 0) then
586  begin
587    ErrorLog.Add(Format(sNrErrorsFound,[ErrorCount]));
588    ErrorLog.Add(Divider);
589    ErrorLog.Add('');
590    ErrorLog.Add('');
591  end;
592  DoTestEnd(PoTestTypeNames[pttCheckMissingIdentifiers], ErrorCount);
593  //debugln('TPoFamily.CheckMissingIdentifiers: ',Dbgs(ErrorCount),' Errors');
594end;
595
596procedure TPoFamily.CheckMismatchedOriginals(out ErrorCount: Integer;
597  ErrorLog: TStrings);
598var
599  i: Integer;
600  MPoItem, CPoItem: TPOFileItem;
601begin
602  //debugln('TPoFamily.CheckMismatchedOriginals');
603  DoTestStart(PoTestTypeNames[pttCheckMismatchedOriginals], ShortChildName);
604  ErrorCount := NoError;
605  for i := 0 to FMaster.Count - 1 do
606  begin
607    MPoItem := FMaster.PoItems[i];
608    CPoItem := FChild.FindPoItem(MpoItem.IdentifierLow);
609    if Assigned(CPoItem) then
610    begin
611      if (MPoItem.Original <> CPoItem.Original) then
612      begin
613        if (ErrorCount = 0) then
614        begin
615          ErrorLog.Add(Divider);
616          ErrorLog.Add(Format(sErrorsByTest,[sShortCheckMismatchedOriginals]));
617          ErrorLog.Add(ShortChildName);
618          ErrorLog.Add(Divider);
619          ErrorLog.Add('');
620        end;
621        Inc(ErrorCount);
622        ErrorLog.Add(Format(sLineInFileName,[CpoItem.LineNr, ShortChildName]));
623        ErrorLog.Add(Format(sMismatchOriginalsID,[CPoItem.IdentifierLow]));
624        ErrorLog.Add(Format(sMismatchOriginalsM,[ShortMasterName,MPoItem.Original]));
625        ErrorLog.Add(Format(sMismatchOriginalsC,[ShortChildName, CPoItem.Original]));
626        ErrorLog.Add('');
627      end;
628    end;
629  end;
630  if (ErrorCount > 0) then
631  begin
632    ErrorLog.Add(Format(sNrErrorsFound,[ErrorCount]));
633    ErrorLog.Add(Divider);
634    ErrorLog.Add('');
635    ErrorLog.Add('');
636  end;
637  DoTestEnd(PoTestTypeNames[pttCheckMismatchedOriginals], ErrorCount);
638  //debugln('TPoFamily.CheckMismatchedOriginals: ',Dbgs(ErrorCount),' Errors');
639end;
640
641procedure TPoFamily.CheckDuplicateOriginals(out WarningCount: Integer;
642  ErrorLog: TStrings);
643var
644  i: Integer;
645  PoItem: TPOFileItem;
646  DupItemsList: TStringHashList;
647  LastHash, CurHash: Cardinal;
648begin
649  //debugln('TPoFamily.CheckMismatchedOriginals');
650  WarningCount := 0;
651
652  DupItemsList := TStringHashList.Create(true);
653  for i := 0 to FMaster.Items.Count - 1 do begin
654    PoItem := TPOFileItem(FMaster.Items[i]);
655    if PoItem.Duplicate = true then
656      DupItemsList.Add(PoItem.Original, PoItem);
657  end;
658
659  //debugln('TPoFamily.CehckDuplicateOriginals');
660  //debugln('DupItemsList.Count = ',DbgS(DupItemsList.Count));
661  LastHash := 0;
662  for i := DupItemsList.Count - 1 downto 0 do
663  begin
664    PoItem := TPoFileItem(DupItemsList.List[i]^.Data);
665    if Assigned(PoItem) then
666    begin
667      CurHash := DupItemsList.List[i]^.HashValue;
668      if (WarningCount = 0) then
669      begin
670        ErrorLog.Add(Format(sFile, [ShortMasterName]));
671        ErrorLog.Add('');
672      end;
673      if (CurHash <> LastHash) then
674      begin//new value for PoItem.Original
675        LastHash := CurHash;
676        Inc(WarningCount);
677        if (WarningCount > 1) then ErrorLog.Add('');
678        ErrorLog.Add(Format(sDuplicateOriginals,[PoItem.Original]));
679        //debugln(format('The (untranslated) value "%s" is used for more than 1 entry:',[PoItem.Original]));
680      end;
681      ErrorLog.Add(format(sDuplicateLineNrWithValue,[PoItem.LineNr,PoItem.IdentifierLow]));
682      //debugln(format(sDuplicateLineNrWithValue,[PoItem.LineNr,PoItem.IdentifierLow]));
683    end;
684  end;
685
686  if (WarningCount > 0) then
687  begin
688    ErrorLog.Add('');
689    ErrorLog.Add(Format(sNrWarningsFound,[WarningCount]));
690    ErrorLog.Add(Divider);
691    ErrorLog.Add('');
692  end;
693
694  DupItemsList.Free;
695
696  //debugln('TPoFamily.CheckDuplicateOriginals: ',Dbgs(WarningCount),' Errors');
697end;
698
699procedure TPoFamily.CheckStatistics(ErrorCnt: Integer);
700var
701  NrTranslated, NrUntranslated, NrFuzzy, NrTotal: Integer;
702begin
703  //debugln('TPoFamily.CheckStatistics');
704  NrTranslated := FChild.Statistics.Translated;
705  NrUntranslated := FChild.Statistics.Untranslated;
706  NrFuzzy := FChild.Statistics.Fuzzy;
707  NrTotal := NrTranslated + NrUntranslated + NrFuzzy;
708  if (NrTotal > 0) then
709  begin
710    FPoFamilyStats.Add(ChildName, NrTotal, NrTranslated, NrUntranslated, NrFuzzy, ErrorCnt);
711  end;
712  //debugln('TPoFamily.CheckIncompatibleFormatArgs: ',Dbgs(ErrorCount),' Errors');
713end;
714
715{
716procedure TPoFamily.RunTests
717Pre conditions:
718  * Master and a matching Child must be assigned at start ot testing
719  * If a Child is assigned it must be child of Master
720}
721procedure TPoFamily.RunTests(out ErrorCount, NonFuzzyErrorCount, WarningCount,
722  TranslatedCount, UntranslatedCount, FuzzyCount: Integer; ErrorLog, StatLog,
723  DupLog: TStringList);
724var
725  SL: TStringList;
726  CurrErrCnt, CurrNonFuzzyErrCnt, CurrWarnCnt, ThisErrCnt: Integer;
727  i: Integer;
728  CurrChildName: String;
729  S: String;
730begin
731  SL := nil;
732  FPoFamilyStats.Clear;
733  ErrorCount := NoError;
734  NonFuzzyErrorCount := NoError;
735  WarningCount := NoError;
736  TranslatedCount := 0;
737  UntranslatedCount := 0;
738  FuzzyCount := 0;
739  if (not Assigned(FMaster)) and (not Assigned(FChild)) then
740  begin
741    {$ifdef DebugSimplePoFiles}
742    debugln('TPoFamily.RunTests: Both master and child are unassigned.');
743    {$endif}
744    Exit;
745  end;
746  if not Assigned(FMaster) then
747  begin
748    S := ExtractMasterNameFromChildName(FChildName);
749    if (S <> '') and FileExistsUtf8(S) then
750    begin
751      SetMasterName(S);
752    end
753    else
754    begin
755      {$ifdef DebugSimplePoFiles}
756      Debugln('TPoFamily.RunTests: Cannot find master for ',ShortChildName);
757      {$endif}
758      Exit;
759    end
760  end;
761
762  if (ptoFindAllChildren in FTestOptions) then
763  begin
764    SL := FindAllTranslatedPoFiles(FMasterName);
765    //We want current Child (if currently assigned) at index 0
766    if Assigned(FChild) then
767    begin
768      for i := 0 to SL.Count - 1 do
769      begin
770        if (CompareFileNames(Sl.Strings[i], FChildName) = 0) then
771        begin
772          if (i <> 0) then SL.Exchange(i,0);
773          Break;
774        end;
775      end;
776    end;
777  end
778  else
779  begin
780    SL := TStringList.Create;
781    if Assigned(FChild) then Sl.Add(FChildName);
782  end;
783
784//  for i := 0 to sl.count - 1 do debugln(extractfilename(sl.strings[i]));
785
786  try
787
788    //First run checks that are Master-only
789    CheckDuplicateOriginals(CurrWarnCnt, DupLog);
790    WarningCount := CurrWarnCnt + WarningCount;
791
792    {$ifdef DebugSimplePoFiles}
793    Debugln('TPoFamily.RunTests: number of childs for testing = ',DbgS(Sl.Count));
794    {$endif}
795
796    if (FTestTypes <> []) and (Sl.Count = 0) then
797    begin
798      {$ifdef DebugSimplePoFiles}
799      Debugln('TPoFamily.RunTests: Warning: No child selected or found for selected tests');
800      {$endif}
801      ErrorLog.Add(Divider);
802      ErrorLog.Add('Warning: No child selected (or found) for selected tests.');
803      ErrorLog.Add(Divider);
804    end;
805
806    //then iterate all Children
807    for i := 0 to SL.Count - 1 do
808    begin
809      ThisErrCnt:= 0;
810      CurrChildName := SL.Strings[i];
811      //debugln('TPoFamily.RunTests: setting ChildName to ',CurrChildName);
812      SetChildName(CurrChildName);
813
814      if (pttCheckNrOfItems in FTestTypes) then
815      begin
816        CheckNrOfItems(CurrErrCnt, ErrorLog);
817        ErrorCount := CurrErrCnt + ErrorCount;
818        ThisErrCnt := ThisErrCnt + CurrErrCnt;
819      end;
820
821      if (pttCheckFormatArgs in FTestTypes) then
822      begin
823        CheckFormatArgs(CurrErrCnt, CurrNonFuzzyErrCnt, ErrorLog);
824        ErrorCount := CurrErrCnt + ErrorCount;
825        ThisErrCnt := ThisErrCnt + CurrErrCnt;
826        NonFuzzyErrorCount := CurrNonFuzzyErrCnt + NonFuzzyErrorCount;
827      end;
828
829
830      if (pttCheckMissingIdentifiers in FTestTypes) then
831      begin
832        CheckMissingIdentifiers(CurrErrCnt, ErrorLog);
833        ErrorCount := CurrErrCnt + ErrorCount;
834        ThisErrCnt := ThisErrCnt + CurrErrCnt;
835      end;
836
837      if (pttCheckMismatchedOriginals in FTestTypes) then
838      begin
839        CheckMismatchedOriginals(CurrErrCnt, ErrorLog);
840        ErrorCount := CurrErrCnt + ErrorCount;
841        ThisErrCnt := ThisErrCnt + CurrErrCnt;
842      end;
843
844      //Always run this as the last test please
845      TranslatedCount := FChild.Statistics.Translated;
846      UntranslatedCount := FChild.Statistics.Untranslated;
847      FuzzyCount := FChild.Statistics.Fuzzy;
848      CheckStatistics(ThisErrCnt);
849       {
850        if (ptt in FTestTypes) then
851        begin
852          Check(CurrErrCnt, ErrorLog);
853          ErrorCount := CurrErrCnt + ErrorCount;
854        end;
855        }
856    end;
857    //Generate statistics
858    if FPoFamilyStats.Count > 0 then
859      FPoFamilyStats.AddStatisticsToLog(StatLog);
860  finally
861    SL.Free;
862  end;
863  //debugln('TPoFamilyRunTests: ErrorCount = ',DbgS(ErrorCount));
864end;
865
866procedure InitTestOptions;
867var
868  Index: TPoTestType;
869begin
870  for Index := Low(TPoTestType) to High(TPoTestType) do optRunAllTests := optRunAllTests + [Index];
871end;
872
873Initialization
874
875InitTestOptions;
876
877end.
878
879