function mk_pkg_doc(idir,odir);

% Function mk_pkg_doc
%          mk_pkg_doc(idir,odir)
% 
% produces documentation, mainly in html, of each package
% stored in the directory given by idir.
% 
% Output is stored in the directory given by odir.
% 
% See also the help text of mk_pkg.m.
% 
% If no input arguments are given, idir and odir are
% internally defined as follows:
% -- idir = ["tmp/test/mk_pkg_doc/input/"];
% -- odir = ["tmp/test/mk_pkg_doc/html/"];
% 
% When mk_pkg_doc() is called by post_install.m, idir and
% odir are given as follows:
% -- idir = [pkg("prefix") "/../../mk_pkg_doc/input/"];
% -- odir = [pkg("prefix") "/../../mk_pkg_doc/html/"];
% 
% At first, the function reads the general configuration
% file, which must have the path and filename:
%   "<idir>/general.cfg".
% 
% The general configuration file contains Octave code, and
% should end with some statement by which variable "ret" is
% defined. Variable "ret" holds a row cell-array of
% key-value pairs. The key-value pairs relevant tp
% mk_pkg_doc() are explained below, and other ones are
% explained in the help text of mk_pkg():
%   "homepage", {l1, t1, l2, t2, ...}
%     where l1, l2 etc. hold strings for the (relative or
%     absolute) web addresses to html pages, displayed as
%     buttons on top of the overview html page, and where
%     t1, t2 etc. hold strings of their respective titles.
%   "all_packages_title", "all packages <qq>fvm-*</qq>"
%     the title for the overview html page.
%   "all_packages_intro", "..."
%     the html code to be inserted just below the title on
%     the overview html page.
%   "html_index_top", "..."
%     the html code to be inserted on top of the package
%     index html page.
%   "html_top", "..."
%     the html code to be inserted on top of each
%     explanatory html page.
%   "html_index_footer", "<p>page produced on %s</p>"
%     the html code of the footer text on the package index
%     page. Placeholder %s will be replaced by the date and
%     time of production.
%   "html_footer", "<p>page produced on %s</p>", ...
%     the html code of the footer text on each explanatory
%     html page. Placeholder %s will be replaced by the date
%     and time of production.
% 
% Secondly, the function reads all the package
% configuration files in directory idir and produces the
% documentation for each of the packages. The first step in
% this process is to obtain the names of all octave
% functions defined in the packages, so that
% cross-references (html links) can be made, even across
% different packages.
% 
% Each package pname is described in directory idir by a
% package configuration file (.cfg), help text files
% (.helptext) and possibly other files:
% -- "<idir>/package-<pname>.cfg";
% -- "<idir>/<pname>/<functionname>.m.helptext"
%      (for an m-file in the package);
% -- "<idir>/<pname>/<functionname>.cc.helptext"
%      (for a cc-file in the package);
% -- "<idir>/<pname>/<fname>"
%      (for a file of an other type in the package).
% 
% The output will consist of html files:
% -- "<odir>/index.html"
%      (overview html page, with a list of all packages);
% -- "<odir>/filelist.html"
%      (html page, with a list of m-files and other files in all packages);
% -- "<odir>/<pname>/index.html"
%      (package index html page, with a list of files in
%      package "<pname>");
% -- "<odir>/<pname>/<functionname>.m.html"
%      (explanatory html page, with the help text of the
%      m-file in the package);
% -- "<odir>/<pname>/<functionname>.cc.html"
%      (explanatory html page, with the help text of the
%      cc-file in the package);
% -- "<odir>/<pname>/<fname>.html"
%      (explanatory html page, with possibly the contents
%      of the file of an other type in the package).
% 
% The function internally calls the following non-standard
% m-files:
% -- kvpairs2struct.m
% -- filewrite.m
% 
% 
% (c) 2026 fabien van mook
% 
% 2026.08.20 release of this file within package "fvm-hgeo" under GNU GPLv3+

% This program is free software: you can redistribute it
% and/or modify it under the terms of the GNU General Public
% License as published by the Free Software Foundation, either
% version 3 of the License, or (at your option) any later
% version.
% 
% This program is distributed in the hope that it will be
% useful, but WITHOUT ANY WARRANTY; without even the implied
% warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
% PURPOSE. See the GNU General Public License for more
% details.
% 
% You can read the GNU General Public License on
% <https://www.gnu.org/copyleft/gpl.html>.


%
% keys in cfg.list{}:
%   "fun.m":
%     contents in package index html: "function file fun()"
%     contents in separate html: help text (given in ".../fun.m.helptext")
%                                [empty file if no help text is found]
%   "fun.cc":
%     contents in package index html: "function file fun()"
%     contents in separate html: help text (given in ".../fun.cc.helptext")
%                                [empty file if no help text is found]
%   "file.dat" (extention not important):
%     contents in package index html: "file file.dat"
%     contents in separate html: <pre>...</pre>
%   "file.html":
%     contents in package index html: "file file.html"
%     contents in separate html: as-is in frame with links at top
%   "subdir":
%     contents in package index html: "subdirectory subdir/"
%     no link, no documentation of its contents
%   "fun.m", "fun.cc", "file.dat", "file.html":
%     properties:
%       "ignore", 0|1 (optional, necessary to override default value 0)
%       "type", "m"|"cc"|"html","html frame","txt","bin" (optional, necessary to override)
%       "comment", "..." --> behind item in package index html
%       "top", "..." --> at top in separate html
%       "bottom", "..." --> at bottom in separate html
%   "":
%     inserted html at overview index html and/or package index html
%     properties:
%       "target", "overview|package"
%       "html", "<p>...</p>"
%

if (nargin < 2),
  % subdirectories of the present working directory pwd:
  idir = ["tmp/test/mk_pkg_doc/input/"]
  odir = ["tmp/test/mk_pkg_doc/html/"]
endif;

% read the general configuration file for documentation in
% the given directory idir:
%
f = [idir "general.cfg"];
printf("Reading the general configuration file \"%s\" for documentation...\n",f);
gcfg = read_config_file(f);
gcfg = kvpairs2struct(gcfg);

% read all package configuration files in the given
% directory idir:
%
%   the cfg files are sorted by their names!
%
acfg = read_package_config_files(idir);

% directory for (html) output:
%
if (~ isfolder(odir)),
  mkdir(odir);
endif;

% iterate over each help configuration data to obtain all
% names of functions or other files:
%
fnamepkg = {struct(),struct()};

for cfg = acfg,
  cfg = cfg{1};
  pname = cfg.packagename;

  % iterate over each uneven element in the cell-array of
  % cfg.list:
  %
  for ilist = [1:2:length(cfg.list)],
    f = cfg.list{ilist};
    p = kvpairs2struct(cfg.list{ilist+1});

    if (length(f) == 0),
      isection = 0;

    elseif ( isfield(p,"type") && strcmp(p.type,"dir") ),
      isection = 0;

    elseif ( endsWith(f,".m") || endsWith(f,".cc") ),
      isection = 1;

      % function name:
      fname = regexprep(f,"\.(m|cc)$","");

    else,
      isection = 2;

      % name of other file:
      fname = f;

    endif;

    if (isection ~= 0),
      if isfield(fnamepkg{isection},fname),
        if (~ isfield(fnamepkg{isection}.(fname),pname)),
          fnamepkg{isection}.(fname).(pname) = struct();
        endif;
      else,
        fnamepkg{isection}.(fname) = struct();
        fnamepkg{isection}.(fname).(pname) = struct();
      endif;

      if (~ isfield(fnamepkg{isection}.(fname).(pname),f)),
        fnamepkg{isection}.(fname).(pname).(f) = "";
      endif;
    endif;

  endfor;
endfor;

% convert the struct given by fnamepkg{isection}.(fname)
% into regexprep code given in fnamerep{isection} =
% {<fname>,<pattern>,<replacement>; ...}:
%
fnamerep = {{},{}};
done = '----done----';

for isection = [1 2],
  if (isection == 1),
    printf("A function is defined in package/file:\n");
    suffix = "()";
  else,
    printf("An other file is defined in package/file:\n");
    suffix = "";
  endif;
  for [pnames,fname] = fnamepkg{isection},
    pnames = sort(fieldnames(pnames));
    file = {};
    html = {};
    for pname = pnames,
      pname = pname{1};
      fs = sort(fieldnames(fnamepkg{isection}.(fname).(pname)));
      for f = fs,
        f = f{1};
        % href relative to odir:
        file{end+1} = [pname "/" f];
        html{end+1} = ["<a href=\"../" pname "/" done f ".html\">" done fname "</a>"];
      endfor;
    endfor;
    if (isection == 1),
      % ['\<fvm-xyz\>'] is replaced by ['fvm-' done 'xyz']:
      fnamerep{isection}(end+1,[1 2 3]) = {fname, ['\<fvm-' fname '\>'], ['fvm-' done fname]};
    endif;
    % ['(?<!' done ')xyz\>']is replaced by '<a....</a>':
    fnamerep{isection}(end+1,[1 2 3]) = {fname, ['\<(?<!' done ')' fname '\>'],strjoin(html,"|")};
    if (length(file) == 1),
      printf("-- %s%s in package \"%s\".\n",fname,suffix,pnames{1});
    else,
      printf("-- %s%s in: \"%s\".\n",fname,suffix,strjoin(file,"\", \""));
    endif;
  endfor;
endfor;

% make two lists of all function names and other files,
% respectively, and put them into two html tables:
%
html_tables = {"",""};

for isection = [1 2],
  if (isection == 1),
    html_tables{isection} = [html_tables{isection} ...
      "<table class=\"mk_pkg_doc_table\">\n<tr><th>function (m-file or cc-file)</th><th>package</th></tr>\n"];
    suffix = "()";
  else,
    html_tables{isection} = [html_tables{isection} ...
      "<table class=\"mk_pkg_doc_table\">\n<tr><th>other file</th><th>package</th></tr>\n"];
    suffix = "";
  endif;
  fnames = sort(fieldnames(fnamepkg{isection}));
  for fname = fnames',
    fname = fname{1};
    pnames = sort(fieldnames(fnamepkg{isection}.(fname)));
    html = {};
    for pname = pnames',
      pname = pname{1};
      fs = sort(fieldnames(fnamepkg{isection}.(fname).(pname)));
      for f = fs,
        f = f{1};
        % one line, two columns at once:
        % href relative to odir:
        html{end+1} = ["<td><a href=\"" pname "/" f ".html\">" fname suffix "</a>" ...
          "</td><td><a href=\"" pname "/index.html\">" pname "</a></td>"];
      endfor;
    endfor;
    html_tables{isection} = [html_tables{isection} ...
      "<tr>" strjoin(html,"</tr>\n<tr>") "</tr>\n"];
    % FIXME show that these rows go together ?
    %       maybe: 3 classes for td elements: toprow, middlerow, bottomrow
  endfor;
  html_tables{isection} = [html_tables{isection} ...
    "</table>"];
endfor;

% insert date+time in the html footers:
%
html_index_footer = sprintf(gcfg.html_index_footer,datestr(now,"yyyy-mm-dd HH:MM:SS"));
html_footer = sprintf(gcfg.html_footer,datestr(now,"yyyy-mm-dd HH:MM:SS"));

% overview of all packages in file "<odir>/index.html":
%
html_index = "<!--cursor-->";

if isfield(gcfg,"homepage"),
  for ilink = [1:2:length(gcfg.homepage)],
    html_index = html_insert_link(html_index, ...
      gcfg.homepage{ilink},gcfg.homepage{ilink+1},"<class>mk_pkg_doc_button</class>");
  endfor;
endif;

% special <qq>...</qq> for quoting file and package names
% (<qq> is not an official html tag).

all_packages_title = gcfg.all_packages_title; % e.g. "all packages <qq>fvm-*</qq>"

html_index = html_insert_before_cursor(html_index, ...
  ["\n\n<h1>" all_packages_title "</h1>"]);

html_index = html_insert_before_cursor(html_index, ...
  ["\n\n" gcfg.all_packages_intro]);

% iterate over each help configuration data to produce
% html:
%
ncfg = length(acfg);
for icfg = [1:ncfg],
  cfg = acfg{icfg};
  pname = cfg.packagename;

  % input of documentation of the package <pname> in
  % directory "<idir>/<pname>/":
  %
  ipdir = [idir "/" pname];

  % output for documentation of the package <pname> in
  % directory "<odir>/<pname>/":
  %
  opdir = [odir "/" pname];

  % ensure an empty output directory [odir "/" pname]:
  if isfolder(opdir),
    printf("Emptying directory \"%s\"...\n",opdir);
    confirm_recursive_rmdir(0,"local");
    rmdir(opdir,"s");
  endif;
  if (~ isfolder(opdir)),
    mkdir(opdir);
  endif;

  % description of the package, and other information on it:
  %
  html_description = cell(1,3);
  if ( isfield(cfg,"description") && (length(cfg.description) > 0) ),
    html_description{1} = ["<i>" cfg.description "</i>"];
  endif;
  html_description{2} = ["download the latest version of the package at " ...
    html_repository_link(pname,cfg)];
  if ( isfield(cfg,"octaveversion") && (length(cfg.octaveversion) > 0) ),
    html_description{3} = ["the package has been produced in version " cfg.octaveversion " of gnu octave"];
  endif;

  % overview of the package in file
  % "<odir>/<pname>/index.html":
  %
  html_index_package = "<!--cursor-->";
  html_index_package = html_insert_link(html_index_package, ...
    "../index.html",all_packages_title,"<class>mk_pkg_doc_button</class>");
  html_index_package = html_open_elem(html_index_package,"<p>","</p>");
  if (icfg > 1),
    pname_prev = acfg{icfg-1}.packagename;
    html_index_package = html_insert_link(html_index_package, ...
      ["../" pname_prev "/index.html"],["&leftarrow; package <qq>" pname_prev "</qq>"],"<class>mk_pkg_doc_button</class> no-para");
  endif;
  if (icfg < ncfg),
    pname_next = acfg{icfg+1}.packagename;
    html_index_package = html_insert_link(html_index_package, ...
      ["../" pname_next "/index.html"],["&rightarrow; package <qq>" pname_next "</qq>"],"<class>mk_pkg_doc_button</class> no-para");
  endif;
  html_index_package = html_close_elem(html_index_package);
  html_index_package = html_insert_before_cursor(html_index_package, ...
    ["\n\n<h1>package <qq>" pname "</qq></h1>" ...
    join_nonempty(html_description([1 3]),"<p>","</p>","")]);

  % overview of all packages in file "<odir>/index.html":
  %
  html_index = html_open_elem(html_index, ...
    ["\n\n<dl><dt>"],["</dt><dd>" join_nonempty(html_description([1 2]),"<p>","</p>","") "</dd></dl>"]);
  html_index = html_insert_link(html_index, ...
    [pname "/index.html"],["package <qq>" pname "</qq>"],"no-para");
  html_index = html_close_elem(html_index);

  % &nwarr; &UpperLeftArrow; &nwarrow; U+2196 &#nnnn;
  % &leftarrow; &uparrow; &rightarrow; &downarrow;

  % iterate over each uneven element in the cell-array of
  % cfg.list:
  %
  for ilist = [1:2:length(cfg.list)],
    f = cfg.list{ilist};
    p = kvpairs2struct(cfg.list{ilist+1});

    % FIXME: write link to previous and next item

    if (length(f) == 0),

      if (~ isfield(p,"target")),
        p.target = "";
      endif;
      if (~ isfield(p,"html")),
        p.html = "";
      endif;

      if (index(p.target,"overview") > 0),
        html_index = html_insert_before_cursor(html_index,p.html);
      endif;

      if (index(p.target,"package") > 0),
        html_index_package = html_insert_before_cursor(html_index_package,p.html);
      endif;

    else,

      if ( (~ isfield(p,"type")) || (length(p.type) == 0) || (~ any(strcmp(p.type,{"m","cc","html","html frame","txt","bin","code","dir"}))) ),
        if endsWith(f,".m"),
          p.type = "m";
        elseif endsWith(f,".cc"),
          p.type = "cc";
        elseif endsWith(f,".html"),
          p.type = "html";
        else,
          p.type = "txt";
        endif;
      endif;

      for key = {"comment","top","bottom"},
        key = key{1};
        if (~ isfield(p,key)),
          p.(key) = "";
        endif;
      endfor;

      fn = [ipdir "/" f];

      if strcmp(p.type,"dir"),

        % note that mk_pkg() imposes p.type == "dir" for each
        % subdirectory.

        html_index_package = html_insert_before_cursor(html_index_package, ...
          ["\n\n<dl><dt>subdirectory <qq>" f "/</qq></dt><dd>" p.comment "</dd></dl>"]);

      elseif any(strcmp(p.type,{"m","cc"})),

        fname = regexprep(f,"\.(m|cc)$","");

        fn_helptext = [fn ".helptext"];
        if isfile(fn_helptext),
          helptext = fileread(fn_helptext);
          if (length(helptext) == 0),
            % not certain whether the file is a function file or script file:
            filetype = "file";
            helptext = "... no help text found ...";
            indextitle = [filetype " <qq>" f "</qq>"];
          else,
            filetype = "function file";
            indextitle = ["function " fname "()"];
          endif;
        else,
          % should not happen
          filetype = "file";
          helptext = "... file not found ...";
          indextitle = [filetype " <qq>" f "</qq>"];
        endif;

        % remove "% " at the begin of each line:
        helptext = regexprep(helptext,"^% {0,1}","","lineanchors");

        % prepare for <pre>: replace by "&amp;", "&gt;" and "&lt;":
        helptext = replace_special_chars_for_pre(helptext);

        % replace "<fname>" in helptext by html links:
        helptext = replace_fname(helptext,fnamerep,fname,done);

        html_index_package = html_insert_link(html_index_package, ...
          [f ".html"],indextitle,["<describe>" p.comment "</describe>"]);

        html_file = [gcfg.html_top "<!--cursor-->"];
        html_file = html_open_elem(html_file, ...
          "\n\n<div class=\"mk_pkg_doc_header\">","\n\n</div>\n\n");
        html_file = html_insert_link(html_file, ...
          "../index.html",all_packages_title,"<class>mk_pkg_doc_button</class>");
        html_file = html_insert_link(html_file, ...
          "index.html",["package <qq>" pname "</qq>"],"<class>mk_pkg_doc_button</class>");
        html_file = html_insert_before_cursor(html_file, ...
          ["\n\n<h1>" filetype " <qq>" f "</qq></h1>"]);
        html_file = html_close_elem(html_file);
        html_file = html_insert_remark(html_file,p.top);
        html_file = html_open_elem(html_file, ...
          "\n\n<div class=\"mk_pkg_doc_contents\">","\n\n</div>\n\n");
        html_file = html_insert_before_cursor(html_file, ...
          ["\n\n<pre>" helptext "</pre>"]);
        html_file = html_close_elem(html_file);
        html_file = html_insert_remark(html_file,p.bottom);
        html_file = html_insert_before_cursor(html_file, ...
          ["\n\n<div class=\"mk_pkg_doc_footer\">" html_footer "\n\n</div>\n\n"]);

        html_write(html_file,indextitle,[opdir "/" f ".html"]);

      elseif ( any(strcmp(p.type,{"html","html frame"})) && isfile(fn) ),

        html_index_package = html_insert_link(html_index_package, ...
          [f ".html"],["file <qq>" f "</qq>"],["<describe>" p.comment "</describe>"]);

        if (index(p.type,"frame") > 0),

          copyfile(fn,[opdir "/" f],"f");

          % <iframe
          %   src="https://example.org"
          %   title="iframe Example 1"
          %   width="400"
          %   height="300">
          % </iframe>

          html_file = [gcfg.html_top "<!--cursor-->"];
          html_file = html_open_elem(html_file, ...
            "\n\n<div class=\"mk_pkg_doc_header\">","\n\n</div>\n\n");
          html_file = html_insert_link(html_file, ...
            "../index.html",all_packages_title,"<class>mk_pkg_doc_button</class>");
          html_file = html_insert_link(html_file, ...
            "index.html",["package <qq>" pname "</qq>"],"<class>mk_pkg_doc_button</class>");
          html_file = html_insert_before_cursor(html_file, ...
            ["\n\n<h1>" filetype " <qq>" f "</qq></h1>"]);
          html_file = html_close_elem(html_file);
          html_file = html_insert_remark(html_file,p.top);
          html_file = html_open_elem(html_file, ...
            "\n\n<div class=\"mk_pkg_doc_contents mk_pkg_doc_centre\">","\n\n</div>\n\n");
          % 90% or so is not accepted for iframe height, only px is accepted:
          html_file = html_insert_before_cursor(html_file, ...
            ["\n\n<iframe class=\"mk_pkg_doc_iframe\" src=\"" f "\" width=100% height=1000px></iframe>"]);
          html_file = html_close_elem(html_file);
          html_file = html_insert_remark(html_file,p.bottom);
          html_file = html_insert_before_cursor(html_file, ...
            ["\n\n<div class=\"mk_pkg_doc_footer\">" html_footer "\n\n</div>\n\n"]);

          html_write(html_file,["file <qq>" f "</qq>"],[opdir "/" f ".html"]);

        else,

          html_file = fileread(fn);

          html_file = strrep(html_file,"<body>",["<body>" gcfg.html_top "<!--cursor-->"]);

          html_file = html_insert_before_cursor(html_file, ...
            ["\n\n<style>" html_css_code(1) "</style>"]);

          html_file = html_open_elem(html_file, ...
            "\n\n<div class=\"mk_pkg_doc_header\">","\n\n</div>\n\n");
          html_file = html_insert_link(html_file, ...
            "../index.html",all_packages_title,["<class>mk_pkg_doc_font mk_pkg_doc_button</class>"]);
          html_file = html_insert_link(html_file, ...
            "index.html",["package <qq>" pname "</qq>"],["<class>mk_pkg_doc_font mk_pkg_doc_button</class>"]);
          html_file = html_insert_before_cursor(html_file, ...
            ["\n\n<h1 class=\"mk_pkg_doc_font\">file <qq>" f "</qq></h1>"]);
          html_file = html_close_elem(html_file);
          %html_file = html_insert_before_cursor(html_file, ...
          %  ["\n\n<hr>"]);
          html_file = html_insert_remark(html_file,p.top);
          html_file = html_insert_before_cursor(html_file, ...
            "\n\n<div class=\"mk_pkg_doc_contents\">");
          % go to the end and insert just before the </body> tag:
          html_file = html_insert_before_cursor(html_file, ...
            "\n\n</div>\n\n","</body>");
          html_file = html_insert_remark(html_file,p.bottom,"</body>");
          html_file = html_insert_before_cursor(html_file, ...
            ["\n\n<div class=\"mk_pkg_doc_font mk_pkg_doc_footer\">" html_footer "\n\n</div>\n\n"], ...
            "</body>");

          html_file = html_replace_qq_tags(html_file);

          filewrite([opdir "/" f ".html"],html_file);

        endif;

      else,
        % thus, p.type is "html"/"html frame" with non-existing file,
        % or p.type is "txt" or "bin":

        html_index_package = html_insert_link(html_index_package, ...
          [f ".html"],["file <qq>" f "</qq>"],["<describe>" p.comment "</describe>"]);

        if isfile(fn),
          if strcmp(p.type,"bin"),
            other_file = "... binary data not shown ...";
          elseif strcmp(p.type,"code"),
            other_file = "... code not shown ...";
          else,
            other_file = fileread(fn);
          endif;
        else,
          other_file = "... file not found ...";
        endif;

        % prepare for <pre>: replace by "&amp;", "&gt;" and "&lt;":
        other_file = replace_special_chars_for_pre(other_file);

        html_file = [gcfg.html_top "<!--cursor-->"];
        html_file = html_open_elem(html_file, ...
          "\n\n<div class=\"mk_pkg_doc_header\">","\n\n</div>\n\n");
        html_file = html_insert_link(html_file, ...
          "../index.html",all_packages_title,"<class>mk_pkg_doc_button</class>");
        html_file = html_insert_link(html_file, ...
          "index.html",["package <qq>" pname "</qq>"],"<class>mk_pkg_doc_button</class>");
        html_file = html_insert_before_cursor(html_file, ...
          ["\n\n<h1>file <qq>" f "</qq></h1>"]);
        %html_file = html_insert_before_cursor(html_file, ...
        %  ["\n\n<hr>"]);
        html_file = html_close_elem(html_file);
        html_file = html_insert_remark(html_file,p.top);
        html_file = html_open_elem(html_file, ...
          "\n\n<div class=\"mk_pkg_doc_contents\">","\n\n</div>\n\n");
        html_file = html_insert_before_cursor(html_file, ...
          ["\n\n<pre>" other_file "</pre>"]);
        html_file = html_close_elem(html_file);
        html_file = html_insert_remark(html_file,p.bottom);
        html_file = html_insert_before_cursor(html_file, ...
          ["\n\n<div class=\"mk_pkg_doc_footer\">" html_footer "\n\n</div>\n\n"]);

        html_write(html_file,["file <qq>" f "</qq>"],[opdir "/" f ".html"]);

      endif;

    endif;

  endfor;

  % html_index_package = html_insert_before_cursor(html_index_package, ...
  %   ["\n\n" html_index_footer "\n\n"]);

  html_index_package = [gcfg.html_index_top ...
    "<div class=\"mk_pkg_doc_header\">\n\n" html_index_package "\n\n</div>" ...
    html_index_footer "\n\n"];

  html_write(html_index_package,["package <qq>" pname "</qq>"],[opdir "/index.html"]);

endfor;

flist_title = ["file list of " all_packages_title];

% html_index = html_insert_before_cursor(html_index, ...
%   ["\n\n" html_index_footer "\n\n"]);

html_index = [gcfg.html_index_top ...
  "<div class=\"mk_pkg_doc_header\">\n\n" html_index "\n\n</div>\n\n" ...
  "<div class=\"mk_pkg_doc_header\">\n" ...
  "  <a href=\"filelist.html\">" flist_title "</a>\n" ...
  "</div>\n\n" ...
  html_index_footer "\n\n"];

f = [odir "/index.html"];
html_write(html_index,all_packages_title,f);

if isfile(f),
  printf("Open the documentation at url file://%s .\n",make_absolute_filename(f));
else,
  printf("Oops, no documentation has been generated.\n");
endif;

% file list of all packages in file "<odir>/filelist.html":
%
html_flist = "<!--cursor-->";

html_flist = html_insert_link(html_flist, ...
  "index.html",all_packages_title,"<class>mk_pkg_doc_button</class>");

html_flist = html_insert_before_cursor(html_flist, ...
  ["\n\n<h1>" flist_title "</h1>"]);

html_flist = html_insert_before_cursor(html_flist, ...
  ["\n\n<p>" html_tables{1} "</p>\n\n<p>" html_tables{2} "</p>\n\n"]);

html_flist = [gcfg.html_index_top ...
  "<div class=\"mk_pkg_doc_header\">\n\n" html_flist "\n\n</div>" ...
  html_index_footer "\n\n"];

f = [odir "/filelist.html"];
html_write(html_flist,flist_title,f);

endfunction;


function cfg = read_config_file(f);

  cfg = {};

  % a configuration file consists of octave code ending
  % with a return statement of pattern:
  %  "ret = ... cell-array ...;"
  %
  c = fileread(f);

  try,
    eval(["function ret = in_separate_scope;\n " c "\nendfunction;"]);
    cfg = in_separate_scope();
  catch,
    warning("Error at evaluating octave code in file \"%s\".\n",f);
  end_try_catch;

endfunction;


function acfg = read_package_config_files(d);

  [f, err, msg] = readdir(d);
  if (err ~= 0),
    warning("Error at reading directory \"%s\": %s\n",d,msg);
  endif;

  f = sort(f);

  acfg = {};

  printf("Reading package configuration files:\n");

  for j = [1:length(f)],
    fj = [d "/" f{j}];
    if isfile(fj),
      [~, f_name, f_ext] = fileparts(fj);
      if ( strcmp(f_ext,".cfg") && startsWith(f_name,"package-") ),
        cfg = read_config_file(fj);
        if isempty(cfg),
          printf("-- \"%s\" is empty or wrongly formatted, and thus ignored.\n",fj);
        else,
          printf("-- \"%s\".\n",fj);
          % turn the cell-array into a struct:
          %
          acfg{end+1} = kvpairs2struct(cfg);
        endif;
      endif;
    endif;
  endfor;

endfunction;


function s = fill_within_unique_tags(s,tag1,t,tag2);

  itag1 = index(s,tag1);
  itag2 = index(s,tag2);

  if ((itag1 > 0) && (itag2 > 0) && (itag1 < itag2)),
    s = [s(1:(itag1-1+length(tag1))) t s(itag2:length(s))];
  endif;

endfunction;


function c = html_css_code(for_insert);

  c = "";

  % at default, pre elements have monospace, even if html
  % elements are set to an other font.
  c_font = "  font-family: 'DejaVu Sans Mono', monospace;\n";

  if (~ for_insert),
    c = [c ...
      "* {\n" ...
      c_font ...
      "}\n" ...
    ];
  endif;

  c = [c ...
    ".mk_pkg_doc_font {\n" ...
    c_font ...
    "}\n" ...
    % https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Colors
    ".mk_pkg_doc_header {\n" ...
    "  margin: 0.1em;\n" ...
    "  border: 0.3em solid rgb(26 95 180 / 0.6);\n" ...
    "  border-radius: 1em;\n" ...
    "  padding: 0.1em 0.5em;\n" ...
    "}\n" ...
    ".mk_pkg_doc_button {\n" ...
    %  font: inherit; /* buttons have normally a different font (size) than other texts */
    "  margin: 0.1em;\n" ...
    "  border: 0.15em solid;\n" ... %  border: 2px solid #8c8c8c;
    "  border-radius: 1em;\n" ...
    "  padding: 0.1em 0.5em;\n" ...
    %  background-color: #8c8c8c; */
    "}\n" ...
    ".mk_pkg_doc_contents {\n" ...
    "  margin: 0.1em;\n" ...
    "  border: 0.3em solid rgb(38 162 105 / 1.0);\n" ...
    "  border-radius: 1em;\n" ...
    "  padding: 0.1em 0.5em;\n" ...
    "}\n" ...
    ".mk_pkg_doc_centre {\n" ...
    "  display: flex;\n" ...
    "  justify-content: center;\n" ...
    "}\n" ...
    ".mk_pkg_doc_iframe {\n" ...
    "  border: 2em solid rgb(222 221 218 / 0.2);\n" ...
    "}\n" ...
    ".mk_pkg_doc_remark {\n" ...
    "  color: rgb(198 70 0 / 0.9);\n" ...
    "  margin: 0.1em;\n" ...
    "  border: 0.15em solid;\n" ...
    "  border-radius: 1em;\n" ...
    "  padding: 0.1em 0.5em;\n" ...
    "}\n" ...
    ".mk_pkg_doc_footer {\n" ...
    "}\n" ...
    "table.mk_pkg_doc_table th {\n" ...
    "  border: 1px solid rgb(160 160 160);\n" ...
    "}\n" ...
  ];

endfunction;


function h = html_replace_qq_tags(h);

  h = strrep(h,"<qq>","\"");
  h = strrep(h,"</qq>","\"");

endfunction;


function html_write(s,t,f);

  f_templ = file_in_loadpath("doc_template.html");
  html = fileread(f_templ);

  html = fill_within_unique_tags(html,"<style>",html_css_code(0),"</style>");
  html = fill_within_unique_tags(html,"<title>",t,"</title>");
  html = fill_within_unique_tags(html,"<body>",s,"</body>");

  html = html_replace_qq_tags(html);

  printf("Writing html file \"%s\"...\n",f);
  filewrite(f,html);

endfunction;


function h = html_insert_before_cursor(h,s,c);

  if ( (nargin < 3) || (length(c) == 0) ),
    c = "<!--cursor-->";
  endif;

  ic = index(h,c);

  if (ic > 0),
    h = [h(1:(ic-1)) s h(ic:length(h))];
  endif;

endfunction;


function h = html_replace_cursor(h,s,c);

  if ( (nargin < 3) || (length(c) == 0) ),
    c = "<!--cursor-->";
  endif;

  ic = index(h,c);

  if (ic > 0),
    h = [h(1:(ic-1)) s h(ic+length(c):length(h))];
  endif;

endfunction;


function h = html_open_elem(h,s1,s2);

  s = [s1 "<!--cursor-->" s2 "<!--cursor-after-->"];

  h = html_replace_cursor(h,s);

endfunction;


function h = html_close_elem(h);

  h = html_replace_cursor(h,"","<!--cursor-->");

  h = html_replace_cursor(h,"<!--cursor-->","<!--cursor-after-->");

endfunction;


function h = html_insert_link(h,to,te,m,c);

  % FIXME: escapes in to-address ?

  if (nargin < 4),
    m = "";
  endif;

  linkclass = "";
  ilinkclass1 = index(m,"<class>");
  ilinkclass2 = index(m,"</class>");
  if (ilinkclass1 < ilinkclass2),
    linkclass = [" class=\"" m((ilinkclass1+length("<class>")):(ilinkclass2-1)) "\""];
  endif;

  describe = "";
  idescribe1 = index(m,"<describe>");
  idescribe2 = index(m,"</describe>");
  if (idescribe1 < idescribe2),
    describe = m((idescribe1+length("<describe>")):(idescribe2-1));
  endif;

  s = ["<a" linkclass " href=\"" to "\">" te "</a>"];

  if (idescribe1 > 0),
    s = ["\n\n<dl><dt>" s "</dt><dd>" describe "</dd></dl>"];
  elseif (index(m,"no-para") == 0),
    s = ["\n\n<p>" s "</p>"];
  endif;

  if (nargin < 5),
    h = html_insert_before_cursor(h,s);
  else,
    h = html_insert_before_cursor(h,s,c);
  endif;

endfunction;


function h = html_insert_remark(h,r,c);

  if (length(r) > 0),
    s = ["<div class=\"mk_pkg_doc_font mk_pkg_doc_remark\">" r "</div>"];

    if (nargin < 3),
      h = html_insert_before_cursor(h,s);
    else,
      h = html_insert_before_cursor(h,s,c);
    endif;
  endif;

endfunction;


function h = html_repository_link(pname,cfg);

  if isfield(cfg,"repository"),
    a = cfg.repository;
    a = strrep(a,"<pname>",pname);
    h = ["<a href=\"" a "\">" a "</a>"];
  else,
    h = "???";
  endif;

endfunction;


function h = replace_special_chars_for_pre(h);

  r = {"&","&amp;","<","&lt;",">","&gt"};

  for ir = [1:2:length(r)],
    h = strrep(h,r{ir},r{ir+1});
  endfor;

endfunction;


function s = replace_fname(s,fnamerep,fname_except,done);

  for isection = [2 1],
    for ir = [1:size(fnamerep{isection},1)],
      fnamerep1 = fnamerep{isection}(ir,:);
      if (strcmp(fnamerep1{1},fname_except) == 0),
        s = regexprep(s,fnamerep1{2},fnamerep1{3});
      endif;
    endfor;
  endfor;

  s = strrep(s,done,"");

endfunction;

function s = join_nonempty(c,t1,t2,tdelim);

  s = "";
  nc = numel(c);
  for ic = [1:nc],
    if (~ isempty(c{ic})),
      if (ic == nc),
        s = [s t1 c{ic} t2];
      else,
        s = [s t1 c{ic} t2 tdelim];
      endif;
    endif;
  endfor;

endfunction;
