Index: readbookmarks.pas ================================================================== --- readbookmarks.pas +++ readbookmarks.pas @@ -1,10 +1,40 @@ #!/usr/bin/env instantfpc {$mode objfpc}{$H+}{$ASSERTIONS ON} uses - Classes, SysUtils, StrUtils, DateUtils, + Classes, SysUtils, StrUtils, fgl, Regexpr; + +type + TStringArray = array of string; + +function Join(const Strings: TStringArray; const Separator: string = sLineBreak): string; +var + i: integer; +begin + if Length(Strings) = 0 then + Exit; + Result := Strings[Low(Strings)]; + for i := Low(Strings) + 1 to High(Strings) do begin + Result := Result + Separator + Strings[i]; + end; +end {Join}; + +function IndexOfText(const S: string; const Strings: TStringArray): integer; +var + i: integer; +begin + Result := -1; + for i := Low(Strings) to High(Strings) do begin + if SameText(Strings[i], S) then begin + Result := i; + Exit; + end; + end; +end {IndexOfText}; + + type TFolder = class; { TEntry } @@ -44,20 +74,25 @@ private FAdded: TDateTime; FDescription: string; FModified: TDateTime; FName: string; + FAttributes: TStringList; protected procedure DoSetProperty(const Key, Value: string; var HasBeenSet: boolean); virtual; function GetHTMLAttributes: string; virtual; public + constructor Create(const AParent: TFolder; const LineNumber: cardinal); override; + destructor Destroy; override; + procedure ReadProperties(HtmlAttributes: string); property Name: string read FName write FName; property Added: TDateTime read FAdded write FAdded; property LastModified: TDateTime read FModified write FModified; property Description: string read FDescription write FDescription; + property Attributes: TStringList read FAttributes; end; { TFolder } TFolder = class(TContentEntry) @@ -73,79 +108,101 @@ function ToString: AnsiString; override; procedure SaveToStrings(const Strings: TStrings); override; property Entries: TEntries read FEntries; - property IsToolbarFolder: boolean read FToolbar; + property IsToolbarFolder: boolean read FToolbar write FToolbar; end; { TBookmark } TBookmark = class(TContentEntry) private + FURL: string; FFeedURL: string; FKeyword: string; - FURL: string; + FTags: TStringList; protected procedure DoSetProperty(const Key, Value: string; var HasBeenSet: boolean); override; function GetHTMLAttributes: string; override; public + constructor Create(const AParent: TFolder; const LineNumber: cardinal); override; + destructor Destroy; override; + procedure SaveToStrings(const Strings: TStrings); override; - property URL: string read FURL write FURL; - property FeedURL: string read FFeedURL write FFeedURL; - property Keyword: string read FKeyword write FKeyword; + property URL: string read FURL write FURL; + property FeedURL: string read FFeedURL write FFeedURL; + property Keyword: string read FKeyword write FKeyword; + property Tags: TStringList read FTags; end; const // for use both in HTMLDecode and HTMLEncode cEntities: array[0..9] of string = ('amp', '&', 'lt', '<', 'gt', '>', 'quot', '"', 'apos', ''''); function HTMLDecode(const HTML: string): string; var - i, ei: integer; + i: integer; c: char; - InEntity, Found: boolean; + InEntity: boolean; Entity: string; - CharCode: integer; + { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } + procedure ProcessEntity(const FoundSemicolon: boolean); + var + ei: integer; + CharCode: integer; + Found: boolean; + begin + Found := False; + if Length(Entity) > 1 then begin + if Entity[1] = '#' then begin + CharCode := 0; + if Entity[2] = 'x' then begin + Found := TryStrToInt('$' + Copy(Entity, 3, Length(Entity)), CharCode); + end else begin + Found := TryStrToInt(Copy(Entity, 2, Length(Entity)), CharCode); + end; + if Found then + Entity := Char(CharCode); + end else begin + for ei := Low(cEntities) to High(cEntities) do begin + if (ei mod 2) = 0 then begin + if Entity = cEntities[ei] then begin + Entity := cEntities[ei + 1]; + Found := True; + Break; + end; + end; + end {for}; + end; + end; + if Found then + Result := Result + Entity + else begin + Result := Result + '&' + Entity; + if FoundSemicolon then + Result := Result + ';'; + end; + InEntity := False; + Entity := ''; + end {ProcessEntity}; + { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } begin Result := ''; Entity := ''; InEntity := False; for i := 1 to Length(HTML) do begin + if InEntity and (Length(Entity) > 6) then + ProcessEntity(False); c := HTML[i]; if InEntity then begin - if c = ';' then begin - Found := False; - if Length(Entity) > 1 then begin - if Entity[1] = '#' then begin - CharCode := 0; - if Entity[2] = 'x' then begin - Found := TryStrToInt('$' + Copy(Entity, 3, Length(Entity)), CharCode); - end else begin - Found := TryStrToInt(Copy(Entity, 2, Length(Entity)), CharCode); - end; - if Found then - Entity := Char(CharCode); - end else begin - for ei := Low(cEntities) to High(cEntities) do begin - if (ei mod 2) = 0 then begin - if Entity = cEntities[ei] then begin - Entity := cEntities[ei + 1]; - Found := True; - Break; - end; - end; - end {for}; - end; - end; - if Found then - Result := Result + Entity - else - Result := Result + '&' + Entity + ';'; - InEntity := False; - Entity := ''; + if c = '&' then begin + ProcessEntity(False); + InEntity := True; + end else if c = ';' then begin + ProcessEntity(True); end else begin Entity := Entity + c; end; end else begin if c = '&' then begin @@ -154,14 +211,16 @@ end else begin Result := Result + c; end; end; end; + if InEntity then + ProcessEntity(False); end {HTMLDecode}; type - THTMLEncodingContext = (hecText, hecAttribute); + THTMLEncodingContext = (hecText, hecAttribute, hecURI); function HTMLEncode(const Text: string; const Context: THTMLEncodingContext = hecText): string; var i: integer; { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } @@ -182,14 +241,17 @@ { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } begin Result := Text; for i := Length(Result) downto 1 do begin case Result[i] of - '&', '<', '>', #0: + '<', '>', #0: ReplaceEntity; + '&': + if Context <> hecURI then + ReplaceEntity; '"': - if Context = hecAttribute then + if Context in [hecAttribute, hecURI] then ReplaceEntity; end {case}; end; end {HTMLEncode}; @@ -247,10 +309,26 @@ FParent.Entries.Add(Self); end {TEntry.SetParent}; { TBookmark } + +constructor TBookmark.Create(const AParent: TFolder; const LineNumber: cardinal); +begin + inherited Create(AParent, LineNumber); + FTags := TStringList.Create; + FTags.Delimiter := ','; + FTags.StrictDelimiter := True; + FTags.Sorted := True; + FTags.Duplicates := dupIgnore; +end {TBookmark.Create}; + +destructor TBookmark.Destroy; +begin + FTags.Free; + inherited Destroy; +end {TBookmark.Destroy}; procedure TBookmark.DoSetProperty(const Key, Value: string; var HasBeenSet: boolean); begin inherited DoSetProperty(Key, Value, HasBeenSet); @@ -261,22 +339,28 @@ FFeedURL := Value; HasBeenSet := True; end else if SameText(Key, 'SHORTCUTURL') then begin FKeyword := Value; HasBeenSet := True; + end else if SameText(Key, 'TAGS') then begin + FTags.DelimitedText := Value; + HasBeenSet := True; end; end {TBookmark.DoSetProperty}; function TBookmark.GetHTMLAttributes: string; begin + Result := ''; if FURL <> '' then - Result := ' HREF="' + HTMLEncode(FURL, hecAttribute) + '"'; + Result := Result + ' HREF="' + HTMLEncode(FURL, hecURI) + '"'; if FFeedURL <> '' then - Result := Result + ' FEEDURL="' + HTMLEncode(FFeedURL, hecAttribute) + '"'; + Result := Result + ' FEEDURL="' + HTMLEncode(FFeedURL, hecURI) + '"'; if FKeyword <> '' then Result := Result + ' SHORTCUTURL="' + HTMLEncode(FKeyword, hecAttribute) + '"'; Result := Result + inherited GetHTMLAttributes; + if FTags.Count > 0 then + Result := Result + ' TAGS="' + HTMLEncode(FTags.DelimitedText, hecAttribute) + '"'; end {TBookmark.GetHTMLAttributes}; procedure TBookmark.SaveToStrings(const Strings: TStrings); begin Strings.Add(StringOfChar(' ', 4 * Level) + '
' @@ -320,13 +404,13 @@ end; end {TFolder.DoSetProperty}; function TFolder.GetHTMLAttributes: string; begin + Result := inherited GetHTMLAttributes; if FToolbar then - Result := ' PERSONAL_TOOLBAR_FOLDER="TRUE"'; - Result := inherited GetHTMLAttributes + Result; + Result := Result + ' PERSONAL_TOOLBAR_FOLDER="TRUE"'; end {TFolder.GetHTMLAttributes}; procedure TFolder.SaveToStrings(const Strings: TStrings); var Indent: string; @@ -342,134 +426,186 @@ Strings.Add(Indent+'

'); end {TFolder.SaveToStrings}; { TContentEntry } + +constructor TContentEntry.Create(const AParent: TFolder; const LineNumber: cardinal); +begin + inherited Create(AParent, LineNumber); + FAttributes := TStringList.Create; + FAttributes.NameValueSeparator := '='; +end {TContentEntry.Create}; + +destructor TContentEntry.Destroy; +begin + FAttributes.Free; + inherited Destroy; +end; procedure TContentEntry.ReadProperties(HtmlAttributes: string); +const + cKnownKeys: array[0..4] of string = ('ICON', 'ICON_URI', 'WEB_PANEL', 'UNFILED_BOOKMARKS_FOLDER', 'LAST_CHARSET'); var rxAttr: TRegExpr; Matches: boolean; Key, Value: string; HasBeenSet: boolean; begin + FAttributes.Clear; HtmlAttributes := TrimLeft(HtmlAttributes); rxAttr := TRegExpr.Create; rxAttr.Expression := '(\S+?)\s*=\s*"([^"]*)"'; rxAttr.Compile; Matches := rxAttr.Exec(HtmlAttributes); while Matches do begin - Key := HTMLDecode(LowerCase(rxAttr.Match[1])); + Key := HTMLDecode(rxAttr.Match[1]); Value := HTMLDecode(rxAttr.Match[2]); HasBeenSet := False; DoSetProperty(Key, Value, HasBeenSet); - if not HasBeenSet then - WriteLn(ErrOutput, '!!! Unprocessed property "', Key, '" in line ', Self.Line,' !!!'); + if not HasBeenSet then begin + if IndexOfText(Key, cKnownKeys) = -1 then + WriteLn(ErrOutput, '!!! Unprocessed property "', Key, '" in line ', Self.Line,' !!!'); + FAttributes.Add(Key + FAttributes.NameValueSeparator + Value); + end; Matches := rxAttr.ExecNext; end; end {TContentEntry.ReadProperties}; function TContentEntry.GetHTMLAttributes: string; +var + i: integer; + hec: THTMLEncodingContext; begin + Result := ''; if FAdded > 0 then - Result := ' ADD_DATE="' + IntToStr(SecondsBetween(FAdded, EncodeDate(1970, 1, 1))) + '"'; + Result := Result + ' ADD_DATE="' + IntToStr(Round((FAdded - EncodeDate(1970, 1, 1)) * SecsPerDay)) + '"'; if FModified > 0 then - Result := ' LAST_MODIFIED="' + IntToStr(SecondsBetween(FModified, EncodeDate(1970, 1, 1))) + '"'; + Result := Result + ' LAST_MODIFIED="' + IntToStr(Round((FModified - EncodeDate(1970, 1, 1)) * SecsPerDay)) + '"'; + for i := 0 to FAttributes.Count - 1 do begin + if AnsiContainsText(FAttributes.Names[i], 'URL') or AnsiContainsText(FAttributes.Names[i], 'HREF') or AnsiContainsText(FAttributes.Names[i], 'SRC') then + hec := hecURI + else + hec := hecAttribute; + Result := Result + ' ' + HTMLEncode(FAttributes.Names[i]) + '="' + HTMLEncode(FAttributes.ValueFromIndex[i], hec) + '"'; + end; end {TContentEntry.GetHTMLAttributes}; procedure TContentEntry.DoSetProperty(const Key, Value: string; var HasBeenSet: boolean); var Seconds: Integer; begin if SameText(Key, 'ADD_DATE') then begin if TryStrToInt(Value, Seconds) then - FAdded := EncodeDate(1970, 1, 1) + (Seconds / 86400) + FAdded := EncodeDate(1970, 1, 1) + (Seconds / SecsPerDay) else FAdded := 0; HasBeenSet := True; end else if SameText(Key, 'LAST_MODIFIED') then begin if TryStrToInt(Value, Seconds) then - FModified := EncodeDate(1970, 1, 1) + (Seconds / 86400) + FModified := EncodeDate(1970, 1, 1) + (Seconds / SecsPerDay) else FModified := 0; HasBeenSet := True; end; end {TContentEntry.DoSetProperty}; -type - TStringArray = array of string; - -function Join(const Strings: TStringArray; const Separator: string = sLineBreak): string; -var - i: integer; -begin - if Length(Strings) = 0 then - Exit; - Result := Strings[Low(Strings)]; - for i := Low(Strings) + 1 to High(Strings) do begin - Result := Result + Separator + Strings[i]; - end; -end {Join}; - type { TDuplicateSet } - TDuplicateSet = class(specialize TFPGObjectList) + TDuplicateSet = class(specialize TFPGObjectList) private FNames: TStringArray; FAdded: TDateTime; FLastModified: TDateTime; FMinLevel: cardinal; FMaxLevel: cardinal; FDescriptions: TStringArray; + FAttributes: TStringList; + FTags: TStringList; function AddStringIfUnique(const NewString: string; var Strings: TStringArray): boolean; public - constructor Create(const Initial: TBookmark); - function Add(const Bookmark: TBookmark): integer; + constructor Create(const Initial: TContentEntry); + destructor Destroy; override; + function Add(const Entry: TContentEntry): integer; property Names: TStringArray read FNames; property Added: TDateTime read FAdded; property LastModified: TDateTime read FLastModified; property Descriptions: TStringArray read FDescriptions; property MinLevel: cardinal read FMinLevel; property MaxLevel: cardinal read FMaxLevel; + property Attributes: TStringList read FAttributes; + property Tags: TStringList read FTags; end; -constructor TDuplicateSet.Create(const Initial: TBookmark); +constructor TDuplicateSet.Create(const Initial: TContentEntry); begin inherited Create(False); FAdded := 0; FLastModified := 0; SetLength(FNames, 1); FNames[0] := Initial.Name; SetLength(FDescriptions, 0); FMinLevel := High(FMinLevel); FMaxLevel := Low(FMaxLevel); + FAttributes := TStringList.Create; + FAttributes.NameValueSeparator := '='; + FTags := TStringList.Create; + FTags.Delimiter := ','; + FTags.StrictDelimiter := True; + FTags.Sorted := True; + FTags.Duplicates := dupIgnore; Add(Initial); end {TDuplicateSet.Create}; -function TDuplicateSet.Add(const Bookmark: TBookmark): integer; +destructor TDuplicateSet.Destroy; +begin + FTags.Free; + FAttributes.Free; + inherited Destroy; +end {TDuplicateSet.Destroy}; + +function TDuplicateSet.Add(const Entry: TContentEntry): integer; var + Bookmark: TBookmark; + AddName, AddDescription: boolean; D: TDateTime; L: integer; + IsNewer: boolean; + i: integer; begin - Result := inherited Add(Bookmark); - - if Pos(Bookmark.Name, Bookmark.URL) = 0 then - AddStringIfUnique(Bookmark.Name, FNames); - if (Length(Bookmark.Description) > 0) and (Pos(Bookmark.Description, Bookmark.Name) = 0) and (Pos(Bookmark.Description, Bookmark.URL) <> 1) then - AddStringIfUnique(Bookmark.Description, FDescriptions); - - D := Bookmark.Added; + Result := inherited Add(Entry); + + AddName := True; + AddDescription := (Length(Entry.Description) > 0) and (Pos(Entry.Description, Entry.Name) = 0); + if Entry is TBookmark then begin + Bookmark := TBookmark(Entry); + AddName := Pos(Bookmark.Name, Bookmark.URL) = 0; + AddDescription := AddDescription and (Pos(Bookmark.Description, Bookmark.URL) <> 1); + FTags.AddStrings(Bookmark.Tags); + end; + if AddName then + AddStringIfUnique(Entry.Name, FNames); + if AddDescription then + AddStringIfUnique(Entry.Description, FDescriptions); + + IsNewer := (Entry.LastModified > 0) and ((FLastModified = 0) or (Entry.LastModified > FLastModified)); + for i := 0 to Entry.Attributes.Count - 1 do begin + if IsNewer or (FAttributes.IndexOfName(Entry.Attributes.Names[i]) = -1) then + FAttributes.Values[Entry.Attributes.Names[i]] := Entry.Attributes.ValueFromIndex[i]; + end; + + D := Entry.Added; if (D > 0) and ((FAdded = 0) or (D < FAdded)) then FAdded := D; - D := Bookmark.LastModified; + D := Entry.LastModified; if (D > 0) and (D > FLastModified) then FLastModified := D; - L := Bookmark.Level; + L := Entry.Level; if L < FMinLevel then FMinLevel := L; if L > FMaxLevel then FMaxLevel := L; end {TDuplicateSet.Add}; @@ -621,17 +757,24 @@ TContentEntry(Entry).Description := TContentEntry(Entry).Description + sLineBreak + HTMLDecode(Copy(Line, 1, P - 1)); //WriteLn(StringOfChar(#9, Entry.Level - 1), ' "', TContentEntry(Entry).Description, '"'); end; ReadingDescription := False; end; + Line := Copy(Line, P + 1, Length(Line)); - if AnsiStartsText('HR>', Line) then begin + while AnsiStartsText('HR>', Line) do begin Entry := TDivider.Create(Folder, i); Bookmarks.Add(Entry); //WriteLn(StringOfChar(#9, Entry.Level - 1), '---- ---- ---- ---- ---- '); + Line := Copy(Line, 4, Length(Line)); + P := Pos('<', Line); + if P = 0 then + Continue; + Line := Copy(Line, P + 1, Length(Line)); + end; - end else if AnsiStartsText('DT>', Line, 6); TContentEntry(Entry).ReadProperties(Copy(Line, 6, P - 6)); @@ -733,11 +876,11 @@ URL: string; i: integer; Bookmark: TBookmark; begin Result := 0; - URL := DupeSet.Items[0].URL; + URL := TBookmark(DupeSet.Items[0]).URL; for i := StartIndex to AllEntries.Count - 1 do begin if (AllEntries[i] is TBookmark) then begin Bookmark := TBookmark(AllEntries[i]); if SameText(Bookmark.URL, URL) then begin DupeSet.Add(Bookmark); @@ -777,15 +920,29 @@ end {if TBookmark}; end {for i}; end {TBookmarkManager.FindDupes}; function TBookmarkManager.RemoveDuplicates(const AllEntries: TEntries): TEntries; + function IsInToolbar(const Bookmark: TContentEntry): boolean; + var + Folder: TFolder; + begin + Result := False; + Folder := Bookmark.Parent; + while Assigned(Folder) do begin + Result := Folder.IsToolbarFolder; + if Result then + Exit; + Folder := Folder.Parent; + end; + end {IsInToolbar}; var NumDupes: integer; Dupes: TDuplicateSet; - di, bi: integer; + di, bi, ni: integer; Bookmark: TBookmark; + Names: TStringArray; begin // Walk through all bookmarks; for each bookmark, walk through the entire list again, and gather all bookmarks with the same URL; // keeping track of the earliest add date, and the latest modify date. // Keep the names and descriptions in a list each, only adding them if they're not already in there. // Also keep track of the highest level. @@ -799,15 +956,15 @@ // Identify which bookmark should be kept. By default, if there's one on the toolbar, we keep // that one. Otherwise, we keep the one with the deepest level, on the assumption that nested // deeper means better organised. for bi := 0 to Dupes.Count - 1 do begin - if Assigned(Dupes[bi].Parent) and Dupes[bi].Parent.IsToolbarFolder then begin - Bookmark := Dupes[bi]; + if IsInToolbar(Dupes[bi]) then begin + Bookmark := TBookmark(Dupes[bi]); Break; - end else if not Assigned(Bookmark) and (Dupes[bi].Level = Dupes.MaxLevel) then - Bookmark := Dupes[bi]; + end else if (not Assigned(Bookmark)) and (Dupes[bi].Level = Dupes.MaxLevel) then + Bookmark := TBookmark(Dupes[bi]); end {for bi}; Assert(Assigned(Bookmark)); // apply the earliest add date and the latest modify date, // apply the combined names and descriptions, @@ -814,14 +971,26 @@ Bookmark.Added := Dupes.Added; Bookmark.LastModified := Dupes.LastModified; if Length(Dupes.Descriptions) > 0 then Bookmark.Description := Join(Dupes.Descriptions); if Length(Dupes.Names) > 1 then begin + SetLength(Names, Length(Dupes.Names)); + bi := 0; // offset due to deleted name(s) + for ni := Low(Names) to High(Names) do begin + if SameText(Bookmark.Name, Dupes.Names[ni]) then + Inc(bi) + else + Names[ni - bi] := Dupes.Names[ni]; + end; + if bi > 0 then + SetLength(Names, Length(Names) - bi); Bookmark.Description := IfThen(Bookmark.Description <> '', Bookmark.Description + sLineBreak, '') - + 'Alternative names:' + sLineBreak - + Join(Dupes.Names); + + 'Alternative name' + IfThen(Length(Names) = 1, ': ', 's:' + sLineBreak) + + Join(Names); end; + Bookmark.Attributes.Assign(Dupes.Attributes); + Bookmark.Tags.AddStrings(Dupes.Tags); // Instead of deleting them right now, move them to the result set of entries. The calling party can then free them. for bi := Dupes.Count - 1 downto 0 do begin if Dupes[bi] <> Bookmark then begin Result.Add(Dupes[bi]); @@ -859,10 +1028,11 @@ var fi, i, Index: integer; Folder, Preceding: TFolder; Entry: TEntry; Merged: TEntries; + FolderIsMoreRecent: boolean; begin Result := TEntries.Create(False); for fi := RootEntries.Count - 1 downto 0 do begin Entry := RootEntries[fi]; //WriteLn(StringOfChar('.', Entry.Level), Entry.ToString); @@ -877,12 +1047,25 @@ Break; end; end; if Assigned(Preceding) then begin + // merge the attributes of both folders + if Folder.IsToolbarFolder then + Preceding.IsToolbarFolder := True; + if (Folder.Added > 0) and ((Preceding.Added = 0) or (Folder.Added < Preceding.Added)) then + Preceding.Added := Folder.Added; + FolderIsMoreRecent := (Folder.LastModified > 0) and (Folder.LastModified > Preceding.LastModified); + if FolderIsMoreRecent then + Preceding.LastModified := Folder.LastModified; + for i := 0 to Folder.Attributes.Count - 1 do begin + if FolderIsMoreRecent or (Preceding.Attributes.IndexOfName(Folder.Attributes.Names[i]) = -1) then + Preceding.Attributes.Values[Folder.Attributes.Names[i]] := Folder.Attributes.ValueFromIndex[i]; + end; + //WriteLn(#9, 'Moving ', Folder.Entries.Count ,' entries to preceding ', Preceding.ToString); - // move all its RootEntries to the Preceding folder + // move all its entries to the Preceding folder Index := Preceding.Entries.Count; for i := Folder.Entries.Count - 1 downto 0 do begin Entry := Folder.Entries[i]; Preceding.Entries.Insert(Index, Entry); Entry.Parent := Preceding; @@ -1000,34 +1183,35 @@ Mgr := TBookmarkManager.Create; try Mgr.LoadFromFile(ParamStr(1)); WriteLn(Mgr.Bookmarks.Count, ' entries loaded.'); - Mgr.SaveToFile(ChangeFileExt(ParamStr(1), '-out-1' + ExtractFileExt(ParamStr(1)))); + //Mgr.SaveToFile(ChangeFileExt(ParamStr(1), '-out-1' + ExtractFileExt(ParamStr(1)))); WriteLn; // REMOVE DUPLICATES WriteLn('Looking for duplicate bookmarks...'); Mgr.RemoveDuplicates; WriteLn(Mgr.Bookmarks.Count, ' entries left.'); - Mgr.SaveToFile(ChangeFileExt(ParamStr(1), '-out-2' + ExtractFileExt(ParamStr(1)))); + //Mgr.SaveToFile(ChangeFileExt(ParamStr(1), '-out-2' + ExtractFileExt(ParamStr(1)))); WriteLn; // MERGE DUPLICATE FOLDERS AND PRUNE EMPTY ONES WriteLn('Merging duplicate folders and pruning empty ones...'); Mgr.MergeFolders; WriteLn(Mgr.Bookmarks.Count, ' entries left.'); - Mgr.SaveToFile(ChangeFileExt(ParamStr(1), '-out-3' + ExtractFileExt(ParamStr(1)))); + //Mgr.SaveToFile(ChangeFileExt(ParamStr(1), '-out-3' + ExtractFileExt(ParamStr(1)))); WriteLn; // MERGE CONSECUTIVE SEPARATORS WriteLn('Sanitizing dividers...'); Mgr.SanitizeDividers; WriteLn(Mgr.Bookmarks.Count, ' entries left.'); - Mgr.SaveToFile(ChangeFileExt(ParamStr(1), '-out-4' + ExtractFileExt(ParamStr(1)))); + //Mgr.SaveToFile(ChangeFileExt(ParamStr(1), '-out-4' + ExtractFileExt(ParamStr(1)))); WriteLn; + Mgr.SaveToFile(ChangeFileExt(ParamStr(1), '-new' + ExtractFileExt(ParamStr(1)))); finally Mgr.Free; end; except Beep;