{
 *****************************************************************************
  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************

  Copyright (C) <2005> <Andrew Haines> chmspecialparser.pas
}
unit ChmSpecialParser;

{$mode objfpc}{$H+}

interface

uses
  Classes, Forms, SysUtils, Controls, ComCtrls, chmsitemap;
  
type

  TContentTreeNode = class(TTreeNode)
  private
    fUrl: String;
  public
    property Url:String read fUrl write fUrl;
  end;

  TIndexItem = class(TListITem)
  private
    fUrl: String;
  public
    property Url:String read fUrl write fUrl;
  end;
  
  
  { TContentsFiller }

  TContentsFiller = class(TObject)
  private
    fTreeView: TTreeView;
    fSitemap: TChmSiteMap;
    fChm: TObject;
    fBranchCount: DWord;
    fStop: PBoolean;
    fLastNode: TTreeNode;
    procedure AddSiteMapItem(AItem: TChmSiteMapItem; AParentNode: TTreeNode; ANextNode: TTreeNode);
  public
    constructor Create(ATreeView: TTreeView; ASitemap: TChmSiteMap; StopBoolean: PBoolean; AChm: TObject);
    destructor Destroy; override;
    procedure DoFill(ParentNode: TTreeNode; ASortRoot: Boolean);
  end;

implementation

uses
  LConvEncoding, LazUTF8, HTMLDefs;

function ToUTF8(const AText: AnsiString): String;
var
  encoding: String;
begin
  encoding := GuessEncoding(AText);
  if (encoding <> EncodingUTF8) then
    Result := ConvertEncoding(AText, encoding, EncodingUTF8)
  else
    Result := AText;
end;

function FixEscapedHTML(const AText: String): String;
var
  AmpPos, i: Integer;
  AmpStr: String;
  ws: WideString;
  Entity: WideChar;
begin
  Result := '';
  i := 1;
  while i <= Length(AText) do
  begin
    if AText[i]='&' then
    begin
      AmpPos:= i;
      repeat
        inc(i);   // First round passes beyond '&', then search for ';'.
      until (i > Length(AText)) or (AText[i] = ';');
      if i > Length(AText) then
        // Not HTML Entity, only ampersand by itself. Copy the rest of AText at one go.
        Result := Result + RightStr(AText, i-AmpPos)
      else
      begin            // ';' was found, this may be an HTML entity like "&xxx;".
        AmpStr := Copy(AText, AmpPos+1, i-AmpPos-1);
        ws := UTF8ToUTF16(UTF8Encode(AmpStr));
        if ResolveHTMLEntityReference(ws, Entity) then
          Result := Result + UnicodeToUTF8(cardinal(Entity))
        else
          Result := Result + '?';
      end;
    end
    else
      Result := Result + AText[i];
    inc(i);
  end;
end;


{ TForm1 }

// Replace %20 with space, \ with /
function FixURL(URL: String):String;
var
  X: LongInt;
begin
  X := Pos('%20', Url);
  while X > 0 do
  begin
    Delete(Url, X, 3);
    Insert(' ', Url, X);
    X := Pos('%20', Url);
  end;
  Result := StringReplace(Url, '\', '/', [rfReplaceAll]);
end;

{ TContentsFiller }

procedure TContentsFiller.AddSiteMapItem(AItem: TChmSiteMapItem;
  AParentNode: TTreeNode; ANextNode: TTreeNode);
var
  NewNode: TContentTreeNode;
  X: Integer;
  txt, URL: string;
begin
  if fStop^ then Exit;
  txt := AItem.KeyWord;
  // Fallback:
  if txt = '' then txt := AItem.Text;
  txt := FixEscapedHTML(ToUTF8(Trim(txt)));
  if not Assigned(fLastNode) or (fLastNode.Text <> txt) then
  begin
    // Add new child node
    fLastNode := AParentNode;
    if Assigned(ANextNode) then
      NewNode := TContentTreeNode(fTreeView.Items.Insert(ANextNode, txt))
    else
      NewNode := TContentTreeNode(fTreeView.Items.AddChild(AParentNode, txt));
    URL:='';
    for x:=0 to AItem.SubItemcount-1 do
    begin
      URL:=AItem.SubItem[x].URL;
      if URL<>'' then
        break;
      URL:=AItem.SubItem[x].Local;
      if URL<>'' then
        break;
    end;
    NewNode.Url := FixURL('/'+URL);
    NewNode.Data := fChm;
    if fTreeView.Images <> nil then
    begin
      NewNode.ImageIndex := 3;
      NewNode.SelectedIndex := 3;

      if (AParentNode.ImageIndex < 0) or (AParentNode.ImageIndex > 2) then
      begin
        AParentNode.ImageIndex := 1;
        AParentNode.SelectedIndex := 1;
      end;
    end;
  end
  else
    NewNode := TContentTreeNode(fLastNode);

  Inc(fBranchCount);

  if fBranchCount mod 200 = 0 then
    Application.ProcessMessages;

  for X := 0 to AItem.Children.Count-1 do
    AddSiteMapItem(AItem.Children.Item[X], NewNode, nil);
end;

constructor TContentsFiller.Create(ATreeView: TTreeView; ASitemap: TChmSiteMap; StopBoolean: PBoolean; AChm: TObject);
begin
  inherited Create;
  fTreeView := ATreeView;
  fSitemap := ASitemap;
  fStop := StopBoolean;
  fChm := AChm;
end;

destructor TContentsFiller.Destroy;
begin
  inherited Destroy;
end;

procedure TContentsFiller.DoFill(ParentNode: TTreeNode; ASortRoot: Boolean);
var
  IdxSm, IdxSrc: Integer;
begin
  fTreeView.BeginUpdate;
  fTreeView.Enabled:= False;
  if ASortRoot and (fTreeView.Items.Count > 0) and not Assigned(ParentNode) then
  begin;
    // merge sorted TreeNodes
    IdxSrc:=0;
    IdxSm:=0;
    while (IdxSrc <> fTreeView.Items.TopLvlCount-1 ) and (IdxSm <> fSitemap.Items.Count-1) do
    begin
      // ToDo: Use AnsiCompareText() but only when the data actually has Unicode.
      //       FPC and Lazarus help files have none.
      if (CompareText(fSitemap.Items.Item[IdxSm].Text,
                      fTreeView.Items.TopLvlItems[IdxSrc].Text) <= 0)
      then begin
        // insert sitemap before fTreeView Node
        AddSiteMapItem(fSitemap.Items.Item[IdxSm], ParentNode, fTreeView.Items.TopLvlItems[IdxSrc]);
        if IdxSm < fSitemap.Items.Count-1 then
          Inc(IdxSm);
      end
        else
      begin
        if IdxSrc < fTreeView.Items.TopLvlCount-1 then
          Inc(IdxSrc)
      end;
      // Add a rest of nodes from sitemap
      if (IdxSrc = fTreeView.Items.TopLvlCount-1)  then
      begin
        AddSiteMapItem(fSitemap.Items.Item[IdxSm], ParentNode, ParentNode);
        Inc(IdxSm);
      end;
    end;
  end
    else
  begin
    // Simple add of nodes
    for IdxSm := 0 to fSitemap.Items.Count-1 do
      AddSiteMapItem(fSitemap.Items.Item[IdxSm], ParentNode, nil);
  end;
  fTreeView.Enabled:= True;
  fTreeView.EndUpdate;
end;


end.

