program compilelatexchm;
// FPC script to compile latex manuals html to chm.
// (C) Marco van de Voort 2009 BSD license (no advocacy)
//
//   Requires Sergei's work on dom_html, which means trunk from after (mainly r13357
//   but maybe also one of the slightly later commits)
//  Rule of thumb: use version 2.3.1 of the same date or newer as the last commit to this file
//
//  1.0  2009-07-27 initial version
//  1.1  2009-07-28 Initial TOC support and fulltextsearch


{$ifdef fpc}
{$mode delphi}
{$else}
{$apptype console}
{$endif}
{$info only works properly with 2.3.1+ of july 2009 or newer }
{$ifdef ver2_2}
   Die.
{$endif}

Uses cthreads,chmfilewriter,Sax_HTML,dom,sysutils,classes,dom_html,xmlwrite,htmwrite8859,chmbase,chmwriter,chmsitemap;

{ Index generation }

const  
      attribstringsconstants : Array [0..3] of domstring =
                       ('sectionToc','appendixToc','subsectionToc','chapterToc');
      attriblevels           : array [0..3] of integer   =
                        (1,0,2,0);
				
var
    ChSitemap  : Array[0..3] of TCHMSiteMapItem =(nil,nil,nil,nil);
    toc        : TChmSiteMap;
    curlevel   : integer =-1;
    prefix     : string;
    prefixpath : string;
    kwdfile    : string ='';
    description: string;
    placeholdervalue : integer =1;

function sectiontype (s:string):integer;
var i : integer;

begin
  i:=0;
  while (i<=high(attribstringsconstants)) and (attribstringsconstants[i]<>s) do
   inc(i);
  if i>high(attribstringsconstants) then
    result:=-1
  else
    result:=attriblevels[i];
end;

procedure recursivelygettext(p:TDomNode;var s:domstring);

var p2:TDomNode;
begin
  if not assigned(p) then exit;
  s:=s+p.nodevalue;
  if p.haschildnodes then
    begin
	  p2:=p.firstchild;
	  while assigned(p2) do
	    begin
		  recursivelygettext(p2,s);
		  p2:=p2.nextsibling;
		end;
	end;
end;

function parsechildren(p:TDomNode;var href:domstring;var name:domstring):DOMString;

var p2 : tdomnode;
begin
  result:='';  href:=''; name:='';
  if assigned(p.FirstChild) then
    begin
      result:=p.firstchild.NodeValue;
      p2:=p.FirstChild.NextSibling;
      if assigned(p2) then
        begin
          href:=tdomelement(p2).attribstrings['href'];
		  recursivelygettext(p2,name);
		  {if assigned(p2.firstchild) then
		     name:=tdomelement(p2.firstchild).nodevalue;}
        end;
    end;
end;

procedure loadcur(SC:integer);
var j: integer;
begin
  j:=0;
  while j<=sc do
    begin
      if not assigned(chsitemap[j]) then
	    begin
		  if j=0 then
           chsitemap[j]:=TOC.Items.NewItem
          else
           chsitemap[j]:=chsitemap[j-1].children.NewItem;
		  chsitemap[j].text:='Placeholder '+inttostr(placeholdervalue); 
		  inc(placeholdervalue);
		end;
	  inc(j);
    end;
end;
procedure print(p:TDomNode;lvl:integer);

var c:TDomNode;
    name,s,href:domstring;
    sc :integer;
    cur : TCHMSitemapitem;
begin
  if p is TDomElement then s:=TDomElement(p).tagname else s:='';
  
  if s='span' then
    begin
      sc:=sectiontype(tdomelement(p).attribstrings['class']);
      if sc<>-1 then
        begin
		   
             if sc=0 then
               chsitemap[sc]:=TOC.Items.NewItem
             else
			   if not assigned(chsitemap[sc-1]) then
			     loadcur(sc)
				else
                chsitemap[sc]:=chsitemap[sc-1].children.NewItem;
             cur:=chsitemap[sc];
           s:=parsechildren(p,href,name);
		  {$ifdef chmdebug}
          writeln(curlevel:5,sc:5,' ',s);
		  {$endif}
          cur.text:=s+' '+name;
          cur.local:=prefixpath+href;
          curlevel:=sc;
        end;
   	end;
  c:=p.FirstChild;
  while assigned(c) do
    begin
      print(c,lvl+1);
      c:=c.NextSibling;
    end;
end;

procedure readindex(fn:string;tocfilename:string);
var  
    sx : THTMLDocument;
    I : integer;
    f : TFileStream;

begin
  Toc := TChmSiteMap.Create(stTOC);
  chsitemap[0]:=TOC.Items.NewItem; 
  chsitemap[0].text:='Contents';
  chsitemap[0].local:=prefixpath+prefix+'.html';
  f:=TFileStream.Create(tocfilename,fmcreate);
  sx:=THtmlDocument.create;
  ReadHtmlFile(sx,fn);
  print(sx,0);
  toc.savetostream(f);
  toc.free;
  sx.free;
  f.free;
end;
						
// main part
procedure scandir(filespec:string;recursive:boolean;fn:TStrings);

var d : TSearchRec;
    lfilespec : string;
begin
  filespec:=excludetrailingpathdelimiter(filespec);
  lfilespec:=filespec+'/';
  filespec:=includetrailingpathdelimiter(filespec);
  if findfirst(filespec+'*',faanyfile and fadirectory,d)=0 then
    begin
      repeat
        if (d.attr and fadirectory = fadirectory)  then
          begin
            // if recursive this needs to be fixed. E.g. for multiple chms in one.
 	        writeln('skipping '+d.name);
	      end
        else
         begin
          fn.add(lfilespec+d.name);
          writeln(filespec+d.name);
         end;
      until findnext(d)<>0;
     findclose(d);
    end;
end;

procedure usage;

begin
  Writeln('CHMCreate [prefix] "[Description]"'#13#10'   where prefix is prog,ref or user'#13#10);
  halt;
end; 

function TOCSort(Item1, Item2: TChmSiteMapItem): Integer;
begin
  Result := CompareText(LowerCase(Item1.Text), LowerCase(Item2.Text));
end;


procedure processkwd(x: TCHMProject;kwdfilename:string);
var t      : TStringList;
    i,j    : integer;
    Index  : TChmSiteMap;
    Stream : TFileStream;
    ChdItem,
    TmpItem: TChmSiteMapItem;
    s,s2   : String;
    f : text;
    chd : TStringList;

begin
  if fileexists(kwdfilename) then
    begin
      t:=TStringlist.create;
      t.sorted:=true;
      assignfile(f,kwdfilename);
      reset(f);    
      while not EOF(F) do
        begin
          readln(f,s);
          i:=pos('=',s);
          s2:=copy(s,i+1,length(s)-i);
	  delete(s,i,length(s)-i+1);
          i:=t.indexof(s);
          if i<>-1 then
            begin
              chd:=TStringList(t.objects[i]);
              chd.add(s2);
            end
          else
            begin
              chd:=TStringList.create;
              chd.sorted:=true;  chd.duplicates:=dupignore;
              chd.add(s2);
              t.addobject(s,chd); 
            end;
         end;
     closefile(f);

      Index := TChmSiteMap.Create(stIndex);
      Stream := TFilestream.Create(x.indexfilename,fmcreate);
      for i:=0 to t.count-1 do
        begin
          TmpItem := Index.Items.NewItem;
          TmpItem.Text := t[i];
	  chd:=TStringList(t.objects[i]);  
          tmpitem.local:=prefixpath+chd[0];
          if chd.count>1 then
            begin 
              for j:=0 to chd.count-1 do
                begin
                  chditem:=TmpItem.Children.NewItem;  
		  chditem.Text:=inttostr(j+1);
		  chditem.local:=prefixpath+chd[j];
                end;
            end;
        end; 
      Index.Items.Sort(TListSortCompare(@TOCSort));
      Index.SaveToStream(Stream);
      Index.Free;
      Stream.Free;
  end;
end;

var x : TCHMProject;
    f : TFileStream;
     
begin
  if paramcount<2 then
    usage;
  prefix:=paramstr(1);
  description:=paramstr(2);
  if paramcount>=3 then
   kwdfile:=paramstr(3);
  if not directoryexists(prefix) then
    usage;
  prefixpath:=includetrailingpathdelimiter(prefix);
   
  x := TCHMProject.create;
  x.MakeBinaryToc:=True;
  x.MakeSearchable:=true;
  x.OutputFilename:=prefix+'.chm';
  x.Defaultpage:=prefixpath+prefix+'.html';
  x.Title:=description;
  x.IndexFileName:=prefixpath+'default.hhk';
  x.TableOfContentsFileName:=prefixpath+'default.hhc';
  if fileexists(x.TableOfContentsFileName) then deletefile(x.TableOfContentsFileName);
  scandir(prefix,false,x.files);
  flush(output);
  readindex(x.defaultpage,x.TableOfContentsFileName);
  if kwdfile<>'' then
     processkwd(x,kwdfile);
// xml stuff doesn't seme to work ?
  x.savetofile(prefix+'proj.xml');
    
  f:=TFileStream.Create(prefix+'.chm',fmcreate);
  x.writechm(f);
  x.free;
  f.free;
end.