Unit1.pas 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. unit Unit1;
  2. interface
  3. uses
  4. Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  5. Dialogs, StdCtrls, sdDB, sdProvider, DB, ADODB, ComCtrls;
  6. type
  7. TForm1 = class(TForm)
  8. mmError: TMemo;
  9. btnCheck: TButton;
  10. edtFile: TEdit;
  11. acTree: TADOConnection;
  12. sapTree: TsdADOProvider;
  13. sdsTree: TsdDataSet;
  14. ProgressBar: TProgressBar;
  15. OpenDialog: TOpenDialog;
  16. lblError: TLabel;
  17. btnRecheck: TButton;
  18. procedure btnCheckClick(Sender: TObject);
  19. procedure FormDestroy(Sender: TObject);
  20. procedure btnRecheckClick(Sender: TObject);
  21. private
  22. { Private declarations }
  23. FIdx: TsdIndex;
  24. procedure CheckTree;
  25. public
  26. { Public declarations }
  27. end;
  28. var
  29. Form1: TForm1;
  30. implementation
  31. uses
  32. ScUtils;
  33. {$R *.dfm}
  34. const
  35. SAdoConnectStr = 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=%s;User ID=%s;Password=%s;Persist Security Info=True';
  36. procedure TForm1.btnCheckClick(Sender: TObject);
  37. begin
  38. if OpenDialog.Execute and FileExists(OpenDialog.FileName) then
  39. begin
  40. edtFile.Text := OpenDialog.FileName;
  41. Screen.Cursor := crHourGlass;
  42. try
  43. sdsTree.Close;
  44. acTree.Close;
  45. sdsTree.ClearIndex;
  46. acTree.ConnectionString := Format(SAdoConnectStr, [edtFile.Text, 'Admin', '']);
  47. acTree.Open;
  48. sapTree.TableName := 'Bills';
  49. sdsTree.Open;
  50. FIdx := sdsTree.AddIndex('idxParent', 'ParentID;SerialNo');
  51. mmError.Clear;
  52. CheckTree;
  53. sdsTree.Close;
  54. acTree.Close;
  55. finally
  56. Screen.Cursor := crDefault;
  57. end;
  58. end;
  59. end;
  60. procedure TForm1.btnRecheckClick(Sender: TObject);
  61. begin
  62. if not FileExists(edtFile.Text) then Exit;
  63. Screen.Cursor := crHourGlass;
  64. try
  65. sdsTree.ClearIndex;
  66. acTree.Open;
  67. sdsTree.Open;
  68. FIdx := sdsTree.AddIndex('idxParent', 'ParentID;SerialNo');
  69. mmError.Clear;
  70. CheckTree;
  71. sdsTree.Close;
  72. acTree.Close;
  73. finally
  74. Screen.Cursor := crDefault;
  75. end;
  76. end;
  77. procedure TForm1.CheckTree;
  78. var
  79. I, J, K, iError, iFirstChild, iEndlessNextID, iLength: Integer;
  80. // [[ID, NextSibingID], []...]
  81. arrChild: array of array of Integer;
  82. Rec1, Rec2, Rec3: TsdDataRecord;
  83. bOnlyOneChild, bHasNext, bHasMoreThanOneNext, bHasLastNode, bHasMoreThanOneLastNode, bIsFirstChild: Boolean;
  84. function FindEndlessNext(AID: Integer): Integer;
  85. var
  86. L, M, iID, iLastID: Integer;
  87. arrNext: array of Integer;
  88. bHasNext: Boolean;
  89. begin
  90. Result := -1;
  91. if Length(arrChild) <= 1 then Exit;
  92. iLastID := AID;
  93. repeat
  94. iID := iLastID;
  95. bHasNext := False;
  96. for L := Low(arrChild) to High(arrChild) do
  97. begin
  98. if arrChild[L, 0] = iID then
  99. begin
  100. iLastID := arrChild[L, 1];
  101. if ArrayIndexOf(iLastID, arrNext) > 0 then
  102. Exit;
  103. bHasNext := True;
  104. SetLength(arrNext, Length(arrNext) + 1);
  105. arrNext[Length(arrNext) - 1] := iLastID;
  106. Break;
  107. end;
  108. end;
  109. until (iLastID = AID) or (iLastID = -1) or not bHasNext;
  110. Result := iLastID;
  111. end;
  112. begin
  113. ProgressBar.Max := FIdx.RecordCount - 2;
  114. ProgressBar.Position := 0;
  115. bIsFirstChild := True;
  116. iError := 0;
  117. iFirstChild := 0;
  118. for I := 0 to FIdx.RecordCount - 2 do
  119. begin
  120. Rec1 := FIdx.Records[I];
  121. bOnlyOneChild := False;
  122. bHasNext := False;
  123. bHasMoreThanOneNext := False;
  124. bHasLastNode := False;
  125. bHasMoreThanOneLastNode := False;
  126. iEndlessNextID := -1;
  127. // 生产所有兄弟的ID关系数组,后面检查兄弟关系死循环用
  128. if bIsFirstChild then
  129. for K := iFirstChild to FIdx.RecordCount - 1 do
  130. begin
  131. Rec3 := FIdx.Records[K];
  132. // 是否换Parent
  133. if Rec1.ValueByName('ParentID').AsInteger <> Rec3.ValueByName('ParentID').AsInteger then Break;
  134. iLength := Length(arrChild) + 1;
  135. SetLength(arrChild, iLength, 2);
  136. arrChild[iLength - 1, 0] := Rec3.ValueByName('ID').AsInteger;
  137. arrChild[iLength - 1, 1] := Rec3.ValueByName('NextSiblingID').AsInteger;
  138. end;
  139. for J := iFirstChild to FIdx.RecordCount - 1 do
  140. begin
  141. ProgressBar.Position := I;
  142. Rec2 := FIdx.Records[J];
  143. // 判断是否到下一个父节点
  144. if Rec1.ValueByName('ParentID').AsInteger <> Rec2.ValueByName('ParentID').AsInteger then
  145. begin
  146. // 父节点只有一个儿子
  147. if J = I + 1 then
  148. begin
  149. bOnlyOneChild := True;
  150. if Rec1.ValueByName('NextSiblingID').AsInteger <> -1 then
  151. begin
  152. mmError.Lines.Add(Format('Node[ID: %d] has Only one Child and no LastChild.', [Rec1.ValueByName('ParentID').AsInteger]));
  153. Inc(iError);
  154. end;
  155. end;
  156. Break;
  157. end;
  158. // 检查最后兄弟
  159. if bIsFirstChild then
  160. begin
  161. if Rec2.ValueByName('NextSiblingID').AsInteger = -1 then
  162. begin
  163. if bHasLastNode then
  164. bHasMoreThanOneLastNode := True;
  165. bHasLastNode := True;
  166. end;
  167. end;
  168. // 寻找后兄弟
  169. if Rec1.ValueByName('NextSiblingID').AsInteger = Rec2.ValueByName('ID').AsInteger then
  170. begin
  171. if bHasNext then
  172. bHasMoreThanOneNext := True;
  173. bHasNext := True;
  174. // 检查兄弟关系死循环
  175. iEndlessNextID := FindEndlessNext(Rec1.ValueByName('ID').AsInteger);
  176. end;
  177. if Rec2.ValueByName('NextSiblingID').AsInteger = Rec1.ValueByName('ID').AsInteger then
  178. end;
  179. if not bOnlyOneChild then
  180. begin
  181. if not bHasNext then
  182. begin
  183. mmError.Lines.Add(Format('Node[ID: %d] has no NextSibling.', [Rec1.ValueByName('ID').AsInteger]));
  184. Inc(iError);
  185. end;
  186. if bHasMoreThanOneNext then
  187. begin
  188. mmError.Lines.Add(Format('Node[ID: %d] has more than one NextSibling.', [Rec1.ValueByName('ID').AsInteger]));
  189. Inc(iError);
  190. end;
  191. if bIsFirstChild and not bHasLastNode then
  192. begin
  193. mmError.Lines.Add(Format('Node[ID: %d] has no LastChild.', [Rec1.ValueByName('ParentID').AsInteger]));
  194. Inc(iError);
  195. end;
  196. if bHasMoreThanOneLastNode then
  197. begin
  198. mmError.Lines.Add(Format('Node[ID: %d] has more than one LastChld.', [Rec1.ValueByName('ParentID').AsInteger]));
  199. Inc(iError);
  200. end;
  201. if iEndlessNextID >= 0 then
  202. begin
  203. mmError.Lines.Add(Format('Node[ID: %d] have endless loop.', [Rec1.ValueByName('ID').AsInteger]));
  204. Inc(iError);
  205. end;
  206. end;
  207. Rec2 := FIdx.Records[I + 1];
  208. bIsFirstChild := Rec1.ValueByName('ParentID').AsInteger <> Rec2.ValueByName('ParentID').AsInteger;
  209. if bIsFirstChild then
  210. begin
  211. iFirstChild := I + 1;
  212. SetLength(arrChild, 0, 0);
  213. end;
  214. end;
  215. lblError.Caption := Format('共%d条记录,%d条错误', [sdsTree.RecordCount, iError]);
  216. end;
  217. procedure TForm1.FormDestroy(Sender: TObject);
  218. begin
  219. sdsTree.Close;
  220. acTree.Close;
  221. end;
  222. end.