# ------------------------------------------------------------
# Copyright (c) 2012-2020
# Q.W.Page Associates Inc.
# www.qwpage.com
# All rights reserved.
# ------------------------------------------------------------
::namespace eval ::qw::chtml {}
::itcl::class ::qw::chtml::compiler {
protected variable _help ""; # The help structure that we are compiling into a binary .chm file.
protected variable _directory ""; # Directory of target compiled binary .chm file.
protected variable _name ""; # Name of target compiled binary .chm file.
protected variable _paths ""; # List of paths to every html page in the source help structure.
protected variable _ids; # Array of page ids indexed by page path.
protected variable _bodies; # Array of formatted bodies indexed by page path.
protected variable _working_directory "";
protected variable _contents_name "contents.hhc";
protected variable _index_name "index.hhk";
protected variable _chtml_title "NewViews Help";
protected variable _chtml_language "0x409 English (United States)";
protected variable _project_handle "";
protected variable _contents_handle "";
protected variable _index_handle "";
protected variable _topic_default "";
protected variable _chtml_version "1.1";
protected variable _formatter "";
protected variable _generator "qw help generator";
protected variable _sargs "";
protected variable _command "";
# protected variable _saved_current_directory "";
method constructor {} {
::array set _ids {};
::array set _bodies {};
}
destructor {
::if {$_formatter ne ""} {
::itcl::delete object $_formatter;
::set _formatter "";
}
close_files;
working_directory_delete;
}
method working_directory_create {} {
/* {
Creates a temporary directory to work in when building the chm.
Then we copy hhc.exe from the lib qw directory into the working directory.
We do this so that hhc.exe can work on its files in its own directory.
Although there are probably ways around this, it keeps things simple.
2.26.0
Keep working folder where is was but copy hhc.exe and hha.dll into the
program folder on demand. Windows looks for exe's and dll's according to
a pre-defined prority list of paths. The program folder is in the list
and so is the working directory, but the working directory should never
have been in the list.
*/ }
::set _working_directory [::file normalize [::qw::fileutil::temporary_path .prefix "qwpage_" .suffix ".tmp"]];
::file mkdir $_working_directory;
# 2.26.0 copy files into program folder instead of system folder
::set DstPath [::file join $::qw_program_folder hhc.exe];
::if {![::file exists $DstPath]} {
::file copy -force [::file join $::qw_library lib qw hhc.exe] $DstPath;
}
::set DstPath [::file join $::qw_program_folder hha.dll];
::if {![::file exists $DstPath]} {
::file copy -force [::file join $::qw_library lib qw hha.dll] $DstPath;
}
}
method working_directory_delete {} {
::if {$_working_directory eq ""} {
::return;
}
::qw::try {
::file delete -force -- _working_directory;
} catch Exception {
::qw::warning 314120031117084855 "Deleting chtml compiler working folder $_working_directory generated. Exception:\"$Exception\"";
}
}
method process_pages {} {
/* {
We start by getting a list of paths to every page in the help structure.
Each page has a unique id obtained from its .id field, or
for now, if there is no .id, it is generated from the structure field id.
*/ }
::set rwb1_debug 0;
# ::set _paths [::sargs::select_field $_help .field ".title"];
/* {
::foreach Path [::sargs::select_field .structure $_help .field ".title"] {
::if {[::sargs::get $_help ${Path}.id] eq ""} {
::qw::throw "Encountered help page $Path with empty .id field."
}
}
*/ }
::foreach Path [::sargs::select_field .structure $_help .field ".id"] {
/* {
::if {[::lsearch -exact [::sargs::get $_help ${Path}.tags] tag_manual]<0&&[::lsearch -exact [::sargs::get $_help ${Path}.tags] tag_user]>=0} {
::qw::throw "Encountered page $Path where tag_manual and tag_user are mismatched."
}
::if {[::lsearch -exact [::sargs::get $_help ${Path}.tags] tag_manual]>=0&&[::lsearch -exact [::sargs::get $_help ${Path}.tags] tag_user]<0} {
::qw::throw "Encountered page $Path where tag_manual and tag_user are mismatched."
}
*/ }
/* {
::set Tail [::lindex [::split $Path /] end];
::if {$Tail ne ""} {
::set Id [::sargs::get $_help ${Path}.id];
::if {$Tail ne $Id} {
::qw::throw "Encountered page $Path whose last path tail does not match .id \"$Id\".";
}
}
*/ }
/* {
::if {[::sargs::get $_help ${Path}.title] eq ""} {
::qw::throw "Encountered help page $Path with empty .title field."
}
*/ }
/* {
::if {[::sargs::get $_help ${Path}.tags] eq ""} {
::qw::throw "Encountered help page $Path with empty .tags field."
}
*/ }
}
::set Paths [::sargs::select_field .structure $_help .field .title];
::foreach Path $Paths {
/* {
We select only those pages that are a hit.
*/ }
::set PageTagList [::sargs::get $_help ${Path}.page_tag_list];
::if {[::llength $PageTagList]!=0} {
::if {[chtml_page_tags_hit [::sargs::get $_help ${Path}]]} {
::lappend _paths $Path;
}
} else {
/* {
2.34.0
Changed .tags to .page_tag_list but maintaining backward compatibility.
*/ }
::set PageTagList [::sargs::get $_help ${Path}.tags];
::if {[chtml_page_tags_hit [::sargs::get $_help ${Path}]]} {
::lappend _paths $Path;
}
}
/* {
::set Tags [::sargs::get $_help ${Path}.tags];
if {[chtml_page_tags_hit .tags $Tags]} {
::lappend _paths $Path;
}
*/ }
}
::if {$rwb1_debug} {
::puts "root .title==[::sargs::get $_help .title]";
::puts "root .body==[::sargs::get $_help .body]";
::puts "root .id==[::sargs::get $_help .id]";
::puts "root .subs==[::sargs::subs .structure $_help]";
::puts "paths=="
::foreach Path $_paths {::puts "$Path"}
}
::if {$rwb1_debug} {
::set i 0;
::foreach Path $_paths {
::puts "rwb1_debug,2223.0,_paths\[$i\]==$Path";
::incr i;
}
}
::foreach Path $_paths {
# ::qw::warning "314120050324083416" "Encountered help page with no id,title==[::sargs::get $_help $Path.title]";
::if {[::sargs::get $_help $Path.id] eq ""} {
/* {
Some pages forgot to set an .id so we set it from the last element
in its path which should be unique id anyway.
*/ }
::sargs::var::set _help $Path.id [::lindex [::split $Path /] end];
}
::sargs::var::set _help $Path.path $Path;
::set Page [::sargs::get $_help $Path];
::set PageId [::sargs::get $Page .id];
# don't see how this is consistent with anything
# ::if {[::info exists _ids($PageId)]} {::qw::throw "Encountered duplicate page id \"$PageId\".";}
::if {$Path eq ""} {
::set Path "root";
}
::set _ids($Path) $PageId;
::qw::try {
::sargs::var::+= Page $_sargs;
::set RenderedPage [$_formatter page_render $Page];
} catch Exception {
::qw::throw [::qw::exception::parent $Exception "Could not render the page identified by path \"$Path\"."];
}
::set _bodies($Path) [::sargs::get $RenderedPage .body];
}
::foreach Path $_paths {
::if {$Path eq ""} {
::set Path "root";
}
::set Handle [::open [::file join $_working_directory $_ids($Path).htm] w+];
::puts -nonewline $Handle $_bodies($Path);
::close $Handle;
}
}
method check_images {} {
/* {
Gets the list of used images and the list of all images in the help folder
and determines which ones are never actually used. Write these to a sub-folder.
*/ }
::return;
::set UsedImages "";
::foreach {SrcPath DstName} [$_formatter images] {
::lappend UsedImages [::string tolower [::file tail $SrcPath]];
}
::set Folder [::file join c:/ qw_manual_216];
::set ImagesInDirectory "";
::foreach Pattern {*.jpg *.gif *.png *.bmp} {
::foreach Path [::glob [::file join $Folder $Pattern]] {
::lappend ImagesInDirectory [::string tolower [::file tail $Path]];
}
}
::set Result [::qw::intersect3 $UsedImages $ImagesInDirectory];
::set ImagesUsedButNotFound [::lindex $Result 0];
::set UnusedImages [::lindex $Result 2];
::puts "Images used but not found:"
::set Handle [::open [::file join $Folder images_used_but_not_found.txt] w+];
::foreach Image $ImagesUsedButNotFound {
::puts "$Image";
::puts $Handle "$Image";
}
::close $Handle;
::set OldFolder [::file join $Folder images_unreferenced];
::file mkdir $OldFolder;
::puts "Images found but not used:"
::set Handle [::open [::file join $Folder images_unused.txt] w+];
::foreach File $UnusedImages {
::puts $Handle "$File";
::set Name [::file tail $File];
::puts "Moving and deleting $File";
::file copy -force [::file join $Folder $File] [::file join $OldFolder $Name];
::file delete -force -- [::file join $Folder $File];
}
/* {
::foreach Image $UnusedImages {
::puts $Handle "$Image";
::set Name [::file tail $Image];
::set Files [::glob -nocomplain [::file join $Folder [::file rootname $Image]].*];
::foreach File $Files {
::puts "Moving and deleting $File";
::file copy -force $File [::file join $OldFolder [::file tail $File]];
::file delete -force -- File;
}
}
*/ }
}
method generate_css_file {} {
# 2.31.0
::if {!$::qw::control(browser_help)} {
/* {
When browser_help is true we get the style from the qw web server. But
when producing chm we need the css file available in the destination
folder.
*/ }
::file copy -force [::file join $::qw_program_path doc qw_style0.css] [::file join $_working_directory qw_style0.css];
}
}
method generate_images {} {
check_images;
::qw::try {
::foreach {SrcPath DstName} [$_formatter images] {
# ::puts "314120050408173235,Image,$SrcPath,$DstName";
::qw::try {
::set DstPath [::file join $_working_directory $DstName];
::file copy -force $SrcPath $DstPath;
} catch Exception {
::qw::throw [::qw::exception::parent $Exception "Could not copy image file \"$SrcPath\" to \"$DstPath\"."];
}
}
} catch Exception {
::qw::throw [::qw::exception::parent $Exception "Could not generate the images."];
}
}
method working_directory_cleanup {} {
::if {![::file exists $_working_directory]} {return;}
# ::file delete -force -- $_working_directory;
::set List "";
::lappend List [::file join $_working_directory project.hhp];
::lappend List [::file join $_working_directory $_contents_name];
::lappend List [::file join $_working_directory $_index_name];
::lappend List [::file join $_working_directory project.chm];
::set List [concat $List [::glob -nocomplain $_working_directory/*.htm]]; #*/
::eval ::file delete -force -- $List;
}
method project_options {} {
::puts $_project_handle "\[FILES\]";
::foreach Path $_paths {
::if {$Path eq ""} {
::set Path "root";
}
::puts $_project_handle html\\$_ids($Path).htm;
}
}
method generate_contents_node {Path} {
::qw::try {
::set Node [::sargs::get $_help $Path];
::set Subs [::sargs::subs .structure $Node];
::if {![::sargs::exists $Node .title]} {
::qw::throw "Encountered a page with no .title field."
}
::set Title [::sargs::get $_help $Path.title];
::set Title [$_formatter render [::sargs .body $Title]];
::if {$Path ne ""} {
# ::set _topic_default "$_ids().htm";
# ::puts $_contents_handle "