View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2006-2020, University of Amsterdam
    7			      VU University Amsterdam
    8			      CWI, Amsterdam
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(pldoc_man,
   38	  [ man_page//2,                % +Obj, +Options
   39	    man_overview//1,            % +Options
   40
   41	    man_content_tree/2,         % +Dir, -Tree
   42	    man_packages_tree/1         % -Tree
   43	  ]).   44:- use_module(library(xpath),[xpath/3, op(_,_,_)]).   45:- use_module(library(http/html_write)).   46:- use_module(library(debug),[assertion/1,debug/3]).   47
   48:- autoload(doc_html,
   49	    [ object_tree/5, private/2, object_page_header/4, objects/4,
   50	      object_href/2, object_synopsis/4, object_footer/4,
   51	      object_page_footer/4,
   52	      object_ref/4, object_page/4,
   53	      object_source_button//2
   54	    ]).   55:- autoload(doc_process,[doc_comment/4]).   56:- autoload(doc_search,[search_form/3]).   57:- autoload(doc_util,[atom_to_object/2,atom_pi/2]).   58:- autoload(man_index,[manual_object/5]).   59:- autoload(library(apply),[maplist/2,maplist/3,convlist/3]).   60:- autoload(library(error),[permission_error/3,existence_error/2]).   61:- autoload(library(filesex),
   62	    [directory_file_path/3,relative_file_name/3]).   63:- autoload(library(lists),
   64	    [select/4,append/3,member/2,last/2,selectchk/3]).   65:- autoload(library(option),[merge_options/3,option/2,option/3]).   66:- autoload(library(pairs),[pairs_values/2,pairs_keys/2]).   67:- autoload(library(prolog_xref),[xref_public_list/3]).   68:- autoload(library(sgml),
   69	    [ load_html/3, dtd/2, new_sgml_parser/2, set_sgml_parser/2,
   70	      sgml_parse/2, free_sgml_parser/1
   71	    ]).   72:- autoload(library(uri),[uri_encoded/3]).   73:- autoload(library(www_browser),[expand_url_path/2]).   74:- autoload(library(http/html_head),[html_requires/3]).   75:- if(exists_source(library(http/http_dispatch))).   76:- autoload(library(http/http_dispatch),
   77	    [ http_link_to_id/3, http_location_by_id/2,
   78	      http_handler/3, http_reply_file/3, http_redirect/3
   79	    ]).   80:- endif.   81:- autoload(library(http/http_path),[http_absolute_location/3]).   82:- autoload(library(http/mimetype),[file_mime_type/2]).   83
   84:- include(hooks).   85
   86/** <module> Process SWI-Prolog HTML manuals
   87
   88*/
   89
   90:- predicate_options(man_page//2, 2,
   91		     [ for(atom),
   92		       links(boolean),
   93		       navtree(boolean),
   94		       synopsis(boolean),
   95		       footer(boolean),
   96		       link_source(boolean),
   97		       no_manual(oneof([fail,error])),
   98		       search_in(oneof([all, app, man])),
   99		       search_match(oneof([name, summary])),
  100		       search_options(boolean)
  101		     ]).  102
  103
  104		 /*******************************
  105		 *           HIERARCHY          *
  106		 *******************************/
  107
  108%!  man_nav_tree(+Obj, +Options) is semidet.
  109%
  110%   Create a navigation tree consisting of   a nested =ul= list that
  111%   reflects the location of Obj in the manual.
  112
  113man_nav_tree(Obj, Options) -->
  114    { ensure_man_tree,
  115      man_nav_tree(Obj, Tree, Options),
  116      TreeOptions = [ secref_style(title)
  117		    | Options
  118		    ]
  119    },
  120    html(ul(class(nav),
  121	    \object_tree(Tree, [Obj], TreeOptions))).
  122
  123
  124%!  man_nav_tree(+Obj, -Tree, +Options) is semidet.
  125%
  126%   True when Tree is the navigation tree  for Obj. By default, this
  127%   is the tree going from  the  leaf   to  the  root, unfolding the
  128%   neighbors of Obj.
  129
  130man_nav_tree(Obj, Tree, _Options) :-
  131    man_child_of(Obj, Parent),
  132    !,
  133    findall(Neighbour, man_child_of(Neighbour, Parent), Neighbours0),
  134    (   findall(Child, man_child_of(Child, Obj), Children),
  135	Children \== []
  136    ->  select(Obj, Neighbours0, node(Obj, Children), Neighbours)
  137    ;   Neighbours = Neighbours0
  138    ),
  139    path_up(node(Parent, Neighbours), Tree).
  140man_nav_tree(Obj, node(Obj, Children), _Options) :-
  141    findall(Child, man_child_of(Child, Obj), Children).
  142
  143
  144path_up(Node, Tree) :-
  145    node_id(Node, Id),
  146    man_child_of(Id, Parent),
  147    !,
  148    (   Parent == root
  149    ->  findall(Neighbour, man_child_of(Neighbour, Parent), Neighbours0),
  150	select(Id, Neighbours0, Node, Neighbours),
  151	Tree = node(root, Neighbours)
  152    ;   path_up(node(Parent, [Node]), Tree)
  153    ).
  154path_up(Tree, Tree).
  155
  156
  157%!  man_child_of(?Child, ?Parent) is nondet.
  158%
  159%   Query the manual hierarchy.
  160
  161man_child_of(Child, Parent) :-
  162    term_hash(Child, ChildHash),
  163    term_hash(Parent, ParentHash),
  164    man_child_of(ChildHash, Child, ParentHash, Parent).
  165
  166:- dynamic
  167    man_child_of/4,
  168    man_tree_done/0.  169
  170%!  ensure_man_tree
  171%
  172%   Materialize the manual tree as a binary relation.
  173
  174ensure_man_tree :-
  175    man_tree_done,
  176    !.
  177ensure_man_tree :-
  178    with_mutex(man_tree,
  179	       make_man_tree).
  180
  181make_man_tree :-
  182    man_tree_done,
  183    !.
  184make_man_tree :-
  185    man_content_tree(swi_man_manual('.'), ManTree),
  186    man_packages_tree(PkgTree),
  187    assert_tree(node(root, [ManTree, PkgTree])),
  188    assertz(man_tree_done).
  189
  190assert_tree(node(Id, Children)) :-
  191    !,
  192    maplist(assert_parent(Id), Children),
  193    maplist(assert_tree, Children).
  194assert_tree(_).
  195
  196assert_parent(Id, Child) :-
  197    node_id(Child, ChildId),
  198    term_hash(Id, ParentHash),
  199    term_hash(ChildId, ChildHash),
  200    assertz(man_child_of(ChildHash, ChildId, ParentHash, Id)).
  201
  202node_id(node(Id, _), Id) :- !.
  203node_id(Id, Id).
  204
  205
  206%!  man_content_tree(+Dir, -Tree) is det.
  207%
  208%   Compute the content tree for a   multi-file HTML document. We do
  209%   this by processing =Contents.html= for  making the toplevel tree
  210%   that   links   to   the   individual    files.   Then   we   use
  211%   html_content_tree/2 to materialize the trees for the files.
  212
  213man_content_tree(Spec, node(manual, Chapters)) :-
  214    absolute_file_name(Spec, Dir,
  215		       [ file_type(directory),
  216			 access(read)
  217		       ]),
  218    directory_file_path(Dir, 'Contents.html', ContentsFile),
  219    load_html(ContentsFile, DOM, [cdata(string)]),
  220    findall(Level-Path,
  221	    ( xpath(DOM, //div(@class=Class), DIV),
  222	      class_level(Class, Level),
  223	      xpath(DIV, a(@class=sec,@href=File), _),
  224	      \+ sub_atom(File, _, _, _, #),
  225	      directory_file_path(Dir, File, Path)
  226	    ),
  227	    Pairs),
  228    index_chapters(Pairs, Chapters).
  229
  230class_level('toc-h1', 1).
  231class_level('toc-h2', 2).
  232class_level('toc-h3', 3).
  233class_level('toc-h4', 4).
  234
  235index_chapters([], []).
  236index_chapters([Level-File|T0], [node(Chapter, Children)|T]) :-
  237    html_content_tree(File, Node),
  238    Node = node(Chapter, Children0),
  239    append(Children0, Sections, Children),
  240    index_sections(T0, Level, Sections, T1),
  241    index_chapters(T1, T).
  242
  243index_sections([], _, [], []) :- !.
  244index_sections([SLevel-File|T0], Level, [Node|T], Rest) :-
  245    SLevel > Level,
  246    !,
  247    html_content_tree(File, Node),
  248    index_sections(T0, Level, T, Rest).
  249index_sections(Rest, _, [], Rest).
  250
  251
  252%!  man_packages_tree(-Tree) is det.
  253%
  254%   Tree is the content tree of all packages
  255
  256man_packages_tree(node(packages, Packages)) :-
  257    Section = section(0, _, _, _),
  258    findall(File,
  259	    manual_object(Section, _Title, File, packages, _),
  260	    Files),
  261    maplist(package_node, Files, Packages).
  262
  263package_node(File, Tree) :-
  264    html_content_tree(File, Tree).
  265
  266%!  html_content_tree(+ManualFile, -Tree) is det.
  267%
  268%   True when Tree represents the  hierarchical structure of objects
  269%   documented in the HTML file ManualFile. Tree  is a term where of
  270%   the form below. Object is a   documentation  object (typically a
  271%   section  or  predicate  indicator)  that    may   be  handed  to
  272%   object_link//1  and  similar  predicates  to  make  a  table  of
  273%   contents.
  274%
  275%       node(Object, ListOfTree).
  276
  277html_content_tree(FileIn, Tree) :-
  278    absolute_file_name(FileIn, File),
  279    findall(Offset-Obj,
  280	    manual_object(Obj, _Summary, File, _Class, Offset),
  281	    Pairs),
  282    keysort(Pairs, Sorted),
  283    pairs_values(Sorted, Objects),
  284    make_tree(Objects, Trees),
  285    assertion(Trees = [_]),
  286    Trees = [Tree].
  287
  288make_tree([], []).
  289make_tree([Obj|T0], [node(Obj, Children)|T]) :-
  290    children(T0, Obj, Children, T1),
  291    make_tree(T1, T).
  292
  293children([], _, [], []) :- !.
  294children([Obj|T0], Root, [Node|T], Rest) :-
  295    section_level(Obj, ObjLevel),
  296    section_level(Root, Level),
  297    ObjLevel > Level,
  298    !,
  299    Node = node(Obj, Children),
  300    children(T0, Obj, Children, T1),
  301    children(T1, Root, T, Rest).
  302children([Obj|T0], Root, [Obj|T], Rest) :-
  303    \+ section_level(Obj, _),
  304    !,
  305    children(T0, Root, T, Rest).
  306children(Rest, _, [], Rest).
  307
  308section_level(section(Level, _Nr, _Id, _File), Level).
  309
  310
  311		 /*******************************
  312		 *            RETRIEVE          *
  313		 *******************************/
  314
  315%!  load_man_object(+Obj, -Parent, -Path, -DOM) is nondet.
  316%
  317%   load the desription of the  object   matching  Obj from the HTML
  318%   sources and return the DT/DD pair in DOM.
  319%
  320%   @tbd    Nondet?
  321
  322load_man_object(Obj, ParentSection, Path, DOM) :-
  323    resolve_section(Obj, For),
  324    For = section(_,SN,_ID,Path),
  325    parent_section(For, ParentSection),
  326    findall(Nr-Pos, section_start(Path, Nr, Pos), Pairs),
  327    (   (   Pairs = [SN-_|_]
  328	;   Pairs == []
  329	)
  330    ->  !,
  331	load_html(Path, DOM, [cdata(string)])           % Load whole file
  332    ;   append(_, [SN-Start|Rest], Pairs)
  333    ->  !,
  334	(   member(N-End, Rest),
  335	    \+ sub_atom(N, 0, _, _, SN),
  336	    Len is End - Start,
  337	    Options = [content_length(Len)]
  338	->  true
  339	;   Options = []
  340	),
  341	open(Path, read, In, [type(binary)]),
  342	seek(In, Start, bof, _),
  343	dtd(html, DTD),
  344	new_sgml_parser(Parser,
  345			[ dtd(DTD)
  346			]),
  347	set_sgml_parser(Parser, file(Path)),
  348	set_sgml_parser(Parser, dialect(sgml)),
  349	set_sgml_parser(Parser, shorttag(false)),
  350	set_sgml_parser(Parser, defaults(false)),
  351	call_cleanup(sgml_parse(Parser,
  352				[ document(DOM),
  353				  source(In),
  354				  syntax_errors(quiet),
  355				  cdata(string)
  356				| Options
  357				]),
  358		     ( free_sgml_parser(Parser),
  359		       close(In)
  360		     ))
  361    ).
  362load_man_object(For, Parent, Path, DOM) :-
  363    object_spec(For, Obj),
  364    manual_object(Obj, _, Path, _, Position),
  365    (   object_section(Path, Position, Parent)
  366    ->  true
  367    ;   Parent = Path
  368    ),
  369    open(Path, read, In, [type(binary)]),
  370    seek(In, Position, bof, _),
  371    dtd(html, DTD),
  372    new_sgml_parser(Parser,
  373		    [ dtd(DTD)
  374		    ]),
  375    set_sgml_parser(Parser, file(Path)),
  376    set_sgml_parser(Parser, dialect(sgml)),
  377    set_sgml_parser(Parser, shorttag(false)),
  378    set_sgml_parser(Parser, defaults(false)),
  379    call_cleanup(parse_dts_upto_dd(Parser, In, DOM),
  380		 ( free_sgml_parser(Parser),
  381		   close(In)
  382		 )).
  383
  384parse_dts_upto_dd(Parser, In, Description) :-
  385    sgml_parse(Parser,
  386	       [ document(DOM0),
  387		 cdata(string),
  388		 source(In),
  389		 parse(element),
  390		 syntax_errors(quiet)
  391	       ]),
  392    (   DOM0 = [Element],
  393	Element = element(dt, _, _)
  394    ->  Description = [Element|More],
  395	parse_dts_upto_dd(Parser, In, More)
  396    ;   Description = DOM0
  397    ).
  398
  399section_start(Path, Nr, Pos) :-
  400    manual_object(section(_,Nr,_,_), _, Path, _, Pos).
  401
  402%!  resolve_section(+SecIn, -SecOut) is det.
  403%
  404%   Resolve symbolic path reference and fill   in  level and section
  405%   number if this information is missing.   The latter allows us to
  406%   refer to files of the manual.
  407
  408resolve_section(section(Level, No, Spec), Section) :-
  409    !,
  410    resolve_section(section(Level, No, _, Spec), Section).
  411resolve_section(section(Level, No, ID, Path),
  412		section(Level, No, ID, Path)) :-
  413    nonvar(ID),
  414    manual_object(section(Level,No,ID,Path), _, _, _, _),
  415    !.
  416resolve_section(section(Level, No, ID, Spec),
  417		section(Level, No, ID, Path)) :-
  418    ground(Spec),
  419    absolute_file_name(Spec, Path,
  420		       [ access(read)
  421		       ]),
  422    (   manual_object(section(Level, No, ID, Path), _, _, _, _)
  423    ->  true
  424    ;   path_allowed(Path)
  425    ->  true
  426    ;   permission_error(read, manual_file, Spec)
  427    ).
  428
  429
  430path_allowed(Path) :-                   % allow all files from swi/doc
  431    absolute_file_name(swi(doc), Parent,
  432		       [ access(read),
  433			 file_type(directory)
  434		       ]),
  435    sub_atom(Path, 0, _, _, Parent).
  436
  437
  438%!  parent_section(+Section, -Parent) is det.
  439%
  440%   Parent is the parent-section  of   Section.  First  computes the
  441%   section number and than finds the   required  number in the same
  442%   file or same directory. If this doesn't exist, get the file as a
  443%   whole.
  444
  445parent_section(section(Level, Nr, _ID, File), Parent) :-
  446    integer(Level),
  447    Parent = section(PL, PNr, _PID, _PFile),
  448    PL is Level - 1,
  449    findall(B, sub_atom(Nr, B, _, _, '.'), BL),
  450    last(BL, Before),
  451    sub_atom(Nr, 0, Before, _, PNr),
  452    (   manual_object(Parent, _, File, _, _)
  453    ->  true
  454    ;   manual_object(Parent, _, ParentFile, _, _),
  455	same_dir(File, ParentFile)
  456    ->  true
  457    ;   manual_object(Parent, _, _, _, _)
  458    ),
  459    !.
  460parent_section(section(Level, _, _, File), Parent) :-
  461    Parent = section(ParentLevel, _, _, File),
  462    manual_object(Parent, _, _, _, _),
  463    ParentLevel < Level,
  464    !.
  465parent_section(section(_, _, _, File), File).
  466
  467
  468%!  object_section(+Path, +Position, -Section) is semidet.
  469%
  470%   Section is the section in which object appears.  This is the
  471%   last section object before position.
  472
  473object_section(Path, Pos, Section) :-
  474    Section = section(_,_,_,_),
  475    findall(Section,
  476	   (manual_object(Section, _, Path, _, SecPos), SecPos =< Pos),
  477	    List),
  478    last(List, Section).
  479
  480same_dir(File1, File2) :-
  481    file_directory_name(File1, Dir),
  482    file_directory_name(File2, Dir).
  483
  484%!  object_spec(+Atom, -SpecTerm)
  485%
  486%   Tranform the Name/Arity, etc strings as   received from the HTTP
  487%   into a term.  Must return unique results.
  488
  489object_spec(Spec, Spec) :-
  490    compound(Spec),
  491    !.
  492object_spec(Atom, Spec) :-
  493    catch(atom_to_term(Atom, Spec, _), _, fail),
  494    !,
  495    Atom \== Spec.
  496object_spec(Atom, PI) :-
  497    atom_to_object(Atom, PI).
  498
  499
  500		 /*******************************
  501		 *            EMIT              *
  502		 *******************************/
  503
  504%!  man_page(+Obj, +Options)// is semidet.
  505%
  506%   Produce a Prolog manual page for  Obj.   The  page consists of a
  507%   link to the section-file and  a   search  field, followed by the
  508%   predicate description.  Obj is one of:
  509%
  510%       * Name/Arity
  511%       Predicate indicator: display documentation of the predicate
  512%
  513%       * f(Name/Arity)
  514%       display documentation of an arithmetic function
  515%
  516%       * c(Function)
  517%       display documentation of a C API function
  518%
  519%       * section(Level, Number, Id, File)
  520%       Display a section of the manual
  521%
  522%       * sec(DocFile#Id)
  523%       Display a section of the manual (from short form)
  524%
  525%   Options:
  526%
  527%           * no_manual(Action)
  528%           If Action = =fail=, fail instead of displaying a
  529%           not-found message.
  530%
  531%           * synopsis(Bool)
  532%           If `false`, omit the synopsis line
  533%
  534%           * links(Bool)
  535%           If =true= (default), include links to the parent object;
  536%           if =false=, just emit the manual material.
  537%
  538%           * navtree(Bool)
  539%           If `true` (default), display the navigation tree, otherwise
  540%           suppress it.
  541
  542man_page(Obj, Options) -->
  543    { ground(Obj),
  544      special_node(Obj)
  545    },
  546    !,
  547    html_requires(pldoc),
  548    man_links([], Options),
  549    man_matches([Obj], Obj, Options).
  550man_page(Obj0, Options) -->                     % Manual stuff
  551    { full_page(Obj0, Obj),
  552      findall((Parent+Path)-(Obj+DOM),
  553	      load_man_object(Obj, Parent, Path, DOM),
  554	      Matches),
  555      Matches = [_|_],
  556      !,
  557      pairs_keys(Matches, ParentPaths),
  558      Matches = [Parent+Path-_|_]
  559    },
  560    html_requires(pldoc),
  561    man_links(ParentPaths, Options),
  562    man_matches(Matches, Obj, Options).
  563man_page(Obj, Options) -->                      % PlDoc predicates, etc.
  564    { full_object(Obj, Full),
  565      findall(Full-File, visible_doc_comment(Full, File, Options), Pairs),
  566      Pairs \== [],
  567      pairs_keys(Pairs, Objs)
  568    },
  569    !,
  570    html_requires(pldoc),
  571    (   { Pairs = [_-File] }
  572    ->  object_page_header(File, Options)
  573    ;   object_page_header(-, Options)
  574    ),
  575    { merge_options(Options,
  576		    [ synopsis(true),
  577		      navtree(true)
  578		    ], Options2)
  579    },
  580    objects(Objs, Options2).
  581man_page(Obj, Options) -->                      % failure
  582    { \+ option(no_manual(fail), Options)
  583    },
  584    html_requires(pldoc),
  585    man_links([], Options),
  586    html(p(class(noman),
  587	   [ 'Sorry, No manual entry for ',
  588	     b('~w'-[Obj])
  589	   ])).
  590
  591% Show an object if it is not private   or  it is fully qualified and we
  592% want to show all fully qualified objects. Used by help/1.
  593
  594visible_doc_comment(Obj, File, Options) :-
  595    doc_comment(Obj, File:_, _, _),
  596    \+ ( private(Obj, Options),
  597         \+ ( Obj = _:_,
  598              option(qualified(always), Options)
  599            )
  600       ).
  601
  602%special_node(manual).          % redirected to the Introduction section
  603special_node(root).
  604special_node(packages).
  605
  606full_page(Obj, _) :-
  607    var(Obj), !, fail.
  608full_page(Obj, Obj) :-
  609    Obj = section(_,_,_,_),
  610    !.
  611full_page(section(ID), section(_,_,ID,_)) :- !.
  612full_page(manual, section(_,_,'sec:intro',_)) :- !.
  613full_page(Obj0, Obj) :-
  614    ground(Obj0),
  615    alt_obj(Obj0, Obj),
  616    manual_object(Obj, _, _, _, _),
  617    !.
  618full_page(Obj, Obj) :-
  619    ground(Obj).
  620
  621alt_obj(Obj, Obj).
  622alt_obj(Name/Arity, Name//DCGArity) :-
  623    integer(Arity),
  624    Arity >= 2,
  625    DCGArity is Arity - 2.
  626alt_obj(Name//DCGArity, Name/Arity) :-
  627    integer(DCGArity),
  628    Arity is DCGArity + 2.
  629
  630%!  full_object(+Object, -Full) is semidet.
  631%
  632%   Translate to canonical PlDoc object
  633
  634full_object(Object, M:Obj) :-
  635    qualify(Object, M:Obj0),
  636    alt_obj(Obj0, Obj),
  637    doc_comment(M:Obj, _, _, _),
  638    !.
  639
  640qualify(M:O, M:O).
  641qualify(O, _:O).
  642
  643%!  man_qualified_object(+Object, +Parent,
  644%!                       -LibraryOpt, -QObject, -Section) is semidet.
  645%
  646%   Get a qualified predicate description from  Text that appears in
  647%   the section Parent.
  648%
  649%   The tricky part is that there   are cases where multiple modules
  650%   export the same predicate. We must find   from  the title of the
  651%   manual section which library is documented.
  652
  653man_qualified_object(Text, Parent, LibOpt, Object, Section) :-
  654    atom(Text),
  655    atom_pi(Text, PI),
  656    ground(PI),
  657    !,
  658    man_qualified_object_2(PI, Parent, LibOpt, Object, Section).
  659man_qualified_object(Object0, Parent, LibOpt, Object, Section) :-
  660    man_qualified_object_2(Object0, Parent, LibOpt, Object, Section).
  661
  662man_qualified_object_2(Name/Arity, Parent,
  663		       LibOpt, Module:Name/Arity, Section) :-
  664    object_module(Parent, Module, Section, LibOpt),
  665    !.
  666man_qualified_object_2(Object, Parent, [], Object, Parent).
  667
  668
  669%!  man_synopsis(+Object, +Section)//
  670%
  671%   Give synopsis details for a  fully specified predicate indicator
  672%   and link this to the section.
  673
  674:- public
  675    man_synopsis//2.                % called from man_match//2
  676
  677man_synopsis(PI, Section) -->
  678    man_synopsis(PI, Section, []).
  679
  680man_synopsis(PI, Section, Options) -->
  681    { object_href(Section, HREF)
  682    },
  683    object_synopsis(PI, [href(HREF)|Options]).
  684
  685%!  object_module(+Section0, -Module, -Section, -LibOpt) is semidet.
  686%
  687%   Find the module documented by Section.
  688
  689object_module(Section0, Module, Section, [source(Term)]) :-
  690    parent_section_ndet(Section0, Section),
  691    manual_object(Section, Title, _File, _Class, _Offset),
  692    (   once(sub_atom(Title, B, _, _, :)),
  693	sub_atom(Title, 0, B, _, Atom),
  694	catch(term_to_atom(Term, Atom), _, fail),
  695	ground(Term),
  696	Term = library(_)
  697    ->  !,
  698	absolute_file_name(Term, PlFile,
  699			   [ file_type(prolog),
  700			     access(read),
  701			     file_errors(fail)
  702			   ]),
  703	(   module_property(Module, file(PlFile))
  704	->  true
  705	;   xref_public_list(PlFile, -,         % module is not loaded
  706			     [ module(Module)
  707			     ])
  708	)
  709    ).
  710
  711parent_section_ndet(Section, Section).
  712parent_section_ndet(Section, Parent) :-
  713    parent_section(Section, Parent0),
  714    parent_section_ndet(Parent0, Parent).
  715
  716
  717man_matches(Matches, Object, Options) -->
  718    { option(navtree(false), Options) },
  719    !,
  720    man_matches_nt(Matches, Object, Options).
  721man_matches(Matches, Object, Options) -->
  722    html([ div(class(navtree),
  723	       div(class(navwindow),
  724		   \man_nav_tree(Object, Options))),
  725	   div(class(navcontent),
  726	       \man_matches_nt(Matches, Object, Options))
  727	 ]).
  728
  729
  730man_matches_nt([Match], Object, Options) -->
  731    { option(footer(true), Options, true) },
  732    !,
  733    man_match(Match, Object, Options),
  734    object_page_footer(Object, []).
  735man_matches_nt(Matches, Object, Options) -->
  736    man_matches_list(Matches, Object, Options).
  737
  738man_matches_list([], _, _) --> [].
  739man_matches_list([H|T], Obj, Options) -->
  740    man_match(H, Obj, Options),
  741    man_matches_list(T, Obj, Options).
  742
  743%!  man_match(+Term, +Object, +Options)// is det.
  744%
  745%   If  possible,  insert  the  synopsis  into   the  title  of  the
  746%   description.
  747
  748man_match(packages, packages, _) -->
  749    !,
  750    html({|html||
  751	  <p>
  752	  Packages are relatively independent add-on libraries that
  753	  may not be available in all installations.  Packages are
  754	  part of the source code releases of SWI-Prolog and may be
  755	  enabled or disabled during the build.</p>
  756
  757	  <p>
  758	  See also <a href="/pack/list">Add-ons</a> for extensions
  759	  provided by the community that must be installed separately
  760	  using
  761	  <a href="/pldoc/doc_for?object=pack_install/1">pack_install/1</a>.</p>
  762	 |}).
  763man_match(root, root, _) -->
  764    !,
  765    man_overview([]).
  766man_match((Parent+Path)-(Obj+DOM), Obj, Options) -->
  767    { \+ option(synopsis(false), Options),
  768      DOM = [element(dt,A,C0)|DD],
  769      convlist(dt_obj, DOM, Objs),
  770      option(link_source(Link), Options, true),
  771      man_qualified_object(Obj, Parent, LibOpt, QObj, Section),
  772      !,
  773      C = [ span(style('float:right;margin-left:5px;'),
  774		 \object_source_button(QObj, [source_link(Link)]))
  775	  | C0
  776	  ]
  777    },
  778    dom_list([ element(dt,[],[\man_synopsis(QObj, Section, LibOpt)]),
  779	       element(dt,A,C)
  780	     | DD
  781	     ], Path, Options),
  782    object_footer(Objs, Options).
  783man_match((_Parent+Path)-(Obj+DOM), Obj, Options) -->
  784    dom_list(DOM, Path, Options).
  785
  786dt_obj(element(dt,_,C), Obj) :-
  787    xpath(C, //a(@id=Atom), _),
  788    atom_to_object(Atom, Obj).
  789
  790:- html_meta
  791    dom_list(html, +, +, ?, ?).  792
  793dom_list(_:[], _, _) -->
  794    !,
  795    [].
  796dom_list(M:[H|T], Path, Options) -->
  797    dom(H, Path, Options),
  798    dom_list(M:T, Path, Options).
  799
  800dom(element(E, Atts, Content), Path, Options) -->
  801    !,
  802    dom_element(E, Atts, Content, Path, Options).
  803dom(CDATA, _, _) -->
  804    html(CDATA).
  805
  806dom_element(a, _, [], _, _) -->                % Useless back-references
  807    !,
  808    [].
  809dom_element(a, Att, Content, Path, Options) -->
  810    { memberchk(href=HREF, Att),
  811      (   memberchk(class=Class, Att)
  812      ->  true
  813      ;   Class = unknown
  814      ),
  815      rewrite_ref(Class, HREF, Path, Myref, Options)
  816    },
  817    !,
  818    html(a(href(Myref), \dom_list(Content, Path, Options))).
  819dom_element(span, Att, [CDATA], _, Options) -->
  820    { memberchk(class='pred-ext', Att),
  821      atom_pi(CDATA, PI),
  822      documented(PI),
  823      (   option(server(false), Options)
  824      ->  public_link(predicate(CDATA), HREF)
  825      ;   http_link_to_id(pldoc_man, [predicate=CDATA], HREF)
  826      )
  827    },
  828    !,
  829    html(a(href(HREF), CDATA)).
  830dom_element(img, Att0, [], Path, _Options) -->
  831    { selectchk(src=Src, Att0, Att1),
  832      relative_file_name(ImgFile, Path, Src),
  833      handler_alias(Handler, DirAlias),
  834      absolute_file_name(DirAlias, Dir,
  835			 [ file_errors(fail),
  836			   solutions(all),
  837			   file_type(directory)
  838			 ]),
  839      ensure_slash(Dir, DirS),
  840      atom_concat(DirS, NewSrc, ImgFile),
  841      !,
  842      http_link_to_id(Handler, [], ManRef),
  843      directory_file_path(ManRef, NewSrc, NewPath),
  844      Begin =.. [img, src(NewPath) | Att1]
  845    },
  846    html_begin(Begin),
  847    html_end(img).
  848dom_element(div, Att, _, _, _) -->
  849    { memberchk(class=navigate, Att) },
  850    !.
  851dom_element(html, _, Content, Path, Options) -->
  852    !,                              % do not emit a html for the second time
  853    dom_list(Content, Path, Options).
  854dom_element(head, _, Content, Path, Options) -->
  855    !,                              % do not emit a head for the second time
  856    dom_list(Content, Path, Options).
  857dom_element(title, _, _, _, _) --> !.
  858dom_element(link, _, _, _, _) --> !.
  859dom_element(body, _, Content, Path, Options) -->
  860    !,                              % do not emit a body for the second time
  861    dom_list(Content, Path, Options).
  862dom_element(Name, Attrs, Content, Path, Options) -->
  863    { Begin =.. [Name|Attrs] },
  864    html_begin(Begin),
  865    dom_list(Content, Path, Options),
  866    html_end(Name).
  867
  868handler_alias(manual_file,   swi_man_manual(.)).
  869handler_alias(pldoc_package, swi_man_packages(.)).
  870
  871ensure_slash(Dir, DirS) :-
  872    (   sub_atom(Dir, _, _, 0, /)
  873    ->  DirS = Dir
  874    ;   atom_concat(Dir, /, DirS)
  875    ).
  876
  877%!  public_link(+Spec, -HREF)
  878%
  879%   We do not have a web server.  Create   a  link  to the public server
  880%   instead.
  881%
  882%   @bug The predicate may not be there.
  883
  884public_link(predicate(CDATA), HREF) :-
  885    uri_encoded(query_value, CDATA, Encoded),
  886    atom_concat('https://www.swi-prolog.org/pldoc/doc_for?object=',
  887		Encoded, HREF).
  888
  889
  890%!  documented(+PI) is semidet.
  891%
  892%   True if we have documentation about PI
  893
  894documented(PI) :-
  895    manual_object(PI, _, _, _, _),
  896    !.
  897documented(PI) :-
  898    full_object(PI, _Obj).
  899
  900%!  rewrite_ref(+Class, +Ref0, +Path, -ManRef, +Options) is semidet.
  901%
  902%   Rewrite Ref0 from the HTML reference manual format to the server
  903%   format. Reformatted:
  904%
  905%       $ File#Name/Arity :
  906%       Local reference using the manual presentation
  907%       =|/man?predicate=PI|=.
  908%
  909%       $ File#sec:NR :
  910%       Rewrite to =|section(Level, NT, ID, FilePath)|=
  911%
  912%       $ File#flag:Name :
  913%       Rewrite to =|section(Level, NT, ID, FilePath)#flag:Name|=
  914%
  915%       $ File#gloss:Name :
  916%       Rewrite to =|section(Level, NT, ID, FilePath)#gloss:Name|=
  917%
  918%       $ File#Name()
  919%       Rewrite to /man/CAPI=Name
  920%
  921%   @param Class    Class of the <A>.  Supported classes are
  922%
  923%           | sec   | Link to a section     |
  924%           | pred  | Link to a predicate   |
  925%           | flag  | Link to a Prolog flag |
  926%           | gloss | Link to a glossary    |
  927%
  928%   @param Ref0     Initial reference from the =a= element
  929%   @param Path     Currently loaded file
  930%   @param ManRef   PlDoc server reference
  931
  932rewrite_ref(_Class, Ref, _Path, Ref, Options) :-
  933    option(server(false), Options),
  934    !.
  935rewrite_ref(Class, Ref0, Path, ManRef, _Options) :-
  936    rewrite_ref(Class, Ref0, Path, ManRef).
  937
  938rewrite_ref(pred, Ref0, _, Ref) :-              % Predicate/DCG reference
  939    sub_atom(Ref0, _, _, A, '#'),
  940    !,
  941    sub_atom(Ref0, _, A, 0, Fragment),
  942    atom_to_object(Fragment, PI),
  943    manual_object(PI, _, _, _, _),
  944    uri_encoded(query_value, Fragment, Enc),
  945    http_location_by_id(pldoc_man, ManHandler),
  946    format(string(Ref), '~w?predicate=~w', [ManHandler, Enc]).
  947rewrite_ref(function, Ref0, _, Ref) :-          % Arithmetic function reference
  948    sub_atom(Ref0, _, _, A, '#'),
  949    !,
  950    sub_atom(Ref0, _, A, 0, Fragment),
  951    atom_to_object(Fragment, PI),
  952    manual_object(PI, _, _, _, _),
  953    PI=f(Name/Arity),
  954    format(atom(PIName), '~w/~w', [Name,Arity]),
  955    uri_encoded(query_value, PIName, Enc),
  956    http_location_by_id(pldoc_man, ManHandler),
  957    format(string(Ref), '~w?function=~w', [ManHandler, Enc]).
  958rewrite_ref(func, Ref0, _, Ref) :-              % C-API reference
  959    sub_atom(Ref0, _, _, A, '#'),
  960    !,
  961    sub_atom(Ref0, _, A, 0, Fragment),
  962    atom_to_object(Fragment, Obj),
  963    manual_object(Obj, _, _, _, _),
  964    Obj = c(Function),
  965    uri_encoded(query_value, Function, Enc),
  966    http_location_by_id(pldoc_man, ManHandler),
  967    format(string(Ref), '~w?CAPI=~w', [ManHandler, Enc]).
  968rewrite_ref(sec, Ref0, Path, Ref) :-            % Section inside a file
  969    sub_atom(Ref0, B, _, A, '#'),
  970    !,
  971    sub_atom(Ref0, _, A, 0, Fragment),
  972    sub_atom(Ref0, 0, B, _, File),
  973    referenced_section(Fragment, File, Path, Section),
  974    object_href(Section, Ref).
  975rewrite_ref(sec, File, Path, Ref) :-            % Section is a file
  976    file_directory_name(Path, Dir),
  977    atomic_list_concat([Dir, /, File], SecPath),
  978    Obj = section(_, _, _, SecPath),
  979    manual_object(Obj, _, _, _, _),
  980    !,
  981    object_href(Obj, Ref).
  982rewrite_ref(cite, Ref0, Path, Ref) :-           % Citation (bit hard-wired)
  983    debug(pldoc(cite), 'Cite ref ~q ~q', [Ref0, Path]),
  984    sub_atom(Ref0, _, _, A, '#'),
  985    !,
  986    sub_atom(Ref0, _, A, 0, Fragment),
  987    uri_encoded(query_value, Fragment, Enc),
  988    http_location_by_id(pldoc_man, ManHandler),
  989    format(string(Ref), '~w?section=bibliography#~w', [ManHandler, Enc]).
  990rewrite_ref(flag, Ref0, Path, Ref) :-
  991    sub_atom(Ref0, B, _, A, '#'),
  992    !,
  993    sub_atom(Ref0, 0, B, _, File),
  994    sub_atom(Ref0, _, A, 0, Fragment),
  995    file_directory_name(Path, Dir),
  996    atomic_list_concat([Dir, /, File], SecPath),
  997    Obj = section(_, _, _, SecPath),
  998    manual_object(Obj, _, _, _, _),
  999    !,
 1000    object_href(Obj, Ref1),
 1001    format(string(Ref), '~w#~w', [Ref1, Fragment]).
 1002rewrite_ref(gloss, Ref0, Path, Ref) :-
 1003    sub_atom(Ref0, B, _, A, '#'),
 1004    !,
 1005    sub_atom(Ref0, 0, B, _, File),
 1006    sub_atom(Ref0, _, A, 0, Fragment),
 1007    file_directory_name(Path, Dir),
 1008    atomic_list_concat([Dir, /, File], SecPath),
 1009    Obj = section(_, _, _, SecPath),
 1010    manual_object(Obj, _, _, _, _),
 1011    !,
 1012    object_href(Obj, Ref1),
 1013    format(string(Ref), '~w#~w', [Ref1, Fragment]).
 1014
 1015%!  referenced_section(+Fragment, +File, +Path, -Section)
 1016
 1017referenced_section(Fragment, File, Path, section(Level, Nr, ID, SecPath)) :-
 1018    atom_concat('sec:', Nr, Fragment),
 1019    (   File == ''
 1020    ->  SecPath = Path
 1021    ;   file_directory_name(Path, Dir),
 1022	atomic_list_concat([Dir, /, File], SecPath)
 1023    ),
 1024    manual_object(section(Level, Nr, ID, SecPath), _, _, _, _).
 1025
 1026
 1027%!  man_links(+ParentPaths, +Options)// is det.
 1028%
 1029%   Create top link structure for manual pages.
 1030
 1031man_links(ParentPaths, Options) -->
 1032    prolog:doc_page_header(parents(ParentPaths), Options),
 1033    !.
 1034man_links(ParentPaths, Options) -->
 1035    { option(links(true), Options, true),
 1036      option(header(true), Options, true)
 1037    },
 1038    !,
 1039    html([ div(class(navhdr),
 1040	       [ div(class(jump), \man_parent(ParentPaths)),
 1041		 div(class(search), \search_form(Options)),
 1042		 br(clear(right))
 1043	       ]),
 1044	   p([])
 1045	 ]).
 1046man_links(_, _) -->
 1047    [].
 1048
 1049man_parent(ParentPaths) -->
 1050    { maplist(parent_to_section, ParentPaths, [Section|MoreSections]),
 1051      maplist(=(Section), MoreSections)
 1052    },
 1053    !,
 1054    object_ref(Section, [secref_style(number_title)]).
 1055man_parent(_) --> [].
 1056
 1057parent_to_section(X+_, X) :-
 1058    X = section(_,_,_,_),
 1059    !.
 1060parent_to_section(File+_, Section) :-
 1061    atom(File),
 1062    manual_object(Section, _Title, File, _Class, _Offset),
 1063    !.
 1064
 1065%!  section_link(+Obj, +Options)// is det.
 1066%
 1067%   Create link to a section.  Options recognised:
 1068%
 1069%           * secref_style(+Style)
 1070%           One of =number=, =title= or =number_title=.
 1071
 1072section_link(Section, Options) -->
 1073    { option(secref_style(Style), Options, number)
 1074    },
 1075    section_link(Style, Section, Options).
 1076
 1077section_link(number, section(_, Number, _, _), _Options) -->
 1078    !,
 1079    (   {Number == '0'}             % Title.  Package?
 1080    ->  []
 1081    ;   html(['Sec. ', Number])
 1082    ).
 1083section_link(title, Obj, _Options) -->
 1084    !,
 1085    { manual_object(Obj, Title, _File, _Class, _Offset)
 1086    },
 1087    html(Title).
 1088section_link(_, Obj, _Options) -->
 1089    !,
 1090    { Obj = section(_, Number, _, _),
 1091      manual_object(Obj, Title, _File, _Class, _Offset)
 1092    },
 1093    (   { Number == '0' }
 1094    ->  html(Title)
 1095    ;   html([Number, ' ', Title])
 1096    ).
 1097
 1098%!  function_link(+Function, +Options) is det.
 1099%
 1100%   Create a link to a C-function
 1101
 1102function_link(Function, _) -->
 1103    html([Function, '()']).
 1104
 1105
 1106		 /*******************************
 1107		 *       INDICES & OVERVIEW     *
 1108		 *******************************/
 1109
 1110%!  man_overview(+Options)// is det.
 1111%
 1112%   Provide a toplevel overview on the  manual: the reference manual
 1113%   and the available packages.
 1114
 1115man_overview(Options) -->
 1116    { http_absolute_location(pldoc_man(.), RefMan, [])
 1117    },
 1118    html([ h1('SWI-Prolog documentation'),
 1119	   blockquote(class(refman_link),
 1120		      a(href(RefMan),
 1121			'SWI-Prolog reference manual')),
 1122	   \package_overview(Options),
 1123	   \paperback(Options)
 1124	 ]).
 1125
 1126package_overview(Options) -->
 1127    html([ h2(class(package_doc_title),
 1128	      'SWI-Prolog package documentation'),
 1129	   blockquote(class(package_overview),
 1130		      \packages(Options))
 1131	 ]).
 1132
 1133packages(Options) -->
 1134    { findall(Pkg, current_package(Pkg), Pkgs)
 1135    },
 1136    packages(Pkgs, Options).
 1137
 1138packages([], _) -->
 1139    [].
 1140packages([Pkg|T], Options) -->
 1141    package(Pkg, Options),
 1142    packages(T, Options).
 1143
 1144package(pkg(Title, HREF, HavePackage), Options) -->
 1145    { package_class(HavePackage, Class, Options)
 1146    },
 1147    html(div(class(Class),
 1148	     a([href(HREF)], Title))).
 1149
 1150package_class(true,  pkg_link, _).
 1151package_class(false, no_pkg_link, _).
 1152
 1153current_package(pkg(Title, HREF, HavePackage)) :-
 1154    manual_object(section(0, _, _, _), Title, File, packages, _),
 1155    file_base_name(File, FileNoDir),
 1156    file_name_extension(Base, _, FileNoDir),
 1157    (   exists_source(library(Base))
 1158    ->  HavePackage = true
 1159    ;   HavePackage = false
 1160    ),
 1161    http_absolute_location(pldoc_pkg(FileNoDir), HREF, []).
 1162
 1163
 1164:- if(current_predicate(http_handler/3)). 1165:- http_handler(pldoc(jpl),      pldoc_jpl,              [prefix]). 1166:- http_handler(pldoc_pkg(.),    pldoc_package,          [prefix]). 1167:- http_handler(pldoc_man(.),    pldoc_refman,           [prefix]). 1168:- http_handler(pldoc(packages), pldoc_package_overview, []). 1169
 1170%!  pldoc_jpl(+Request)
 1171%
 1172%   Hack to include JPL documentation in server.
 1173
 1174pldoc_jpl(Request) :-
 1175    memberchk(path_info(JPLFile), Request),
 1176    atom_concat('doc/packages/jpl', JPLFile, Path),
 1177    http_reply_file(swi(Path), [], Request).
 1178
 1179%!  pldoc_package(+Request)
 1180%
 1181%   HTTP  handler  for   PlDoc    package   documentation.   Accepts
 1182%   /pldoc/package/<package>.{html,gif}.          The           path
 1183%   =/pldoc/package/<package>= is redirected to the canonical object
 1184%   version.
 1185
 1186pldoc_package(Request) :-
 1187    (   \+ option(path_info(_), Request)
 1188    ->  true
 1189    ;   option(path_info(/), Request)
 1190    ),
 1191    http_link_to_id(pldoc_object, [object=packages], HREF),
 1192    http_redirect(see_other, HREF, Request).
 1193pldoc_package(Request) :-
 1194    memberchk(path_info(Img), Request),
 1195    file_mime_type(Img, image/_),
 1196    !,
 1197    http_reply_file(swi_man_packages(Img), [], Request).
 1198pldoc_package(Request) :-
 1199    memberchk(path_info('jpl'), Request),
 1200    !,
 1201    memberchk(path(Path0), Request),
 1202    atom_concat(Path0, /, Path),
 1203    http_redirect(moved, Path, Request).
 1204pldoc_package(Request) :-
 1205    memberchk(path_info(JPLFile), Request),
 1206    (   JPLFile == 'jpl/'
 1207    ->  Path = 'doc/packages/jpl/index.html'
 1208    ;   sub_atom(JPLFile, 0, _, _, 'jpl/')
 1209    ->  atom_concat('doc/packages/', JPLFile, Path)
 1210    ),
 1211    http_reply_file(swi(Path), [], Request).
 1212pldoc_package(Request) :-
 1213    memberchk(path_info(PkgDoc), Request),
 1214    ensure_html_ext(PkgDoc, PkgHtml),
 1215    atom_concat('packages/', PkgHtml, Path),
 1216    term_to_atom(section(Path), Object),
 1217    http_link_to_id(pldoc_object, [object=Object], HREF),
 1218    http_redirect(see_other, HREF, Request).
 1219
 1220ensure_html_ext(Pkg, PkgHtml) :-
 1221    file_name_extension(_, html, Pkg),
 1222    !,
 1223    PkgHtml = Pkg.
 1224ensure_html_ext(Pkg, PkgHtml) :-
 1225    file_name_extension(Pkg, html, PkgHtml).
 1226
 1227%!  pldoc_package_overview(+Request)
 1228%
 1229%   Provide an overview of the package documentation
 1230
 1231pldoc_package_overview(_Request) :-
 1232    reply_html_page(
 1233	pldoc(packages),
 1234	title('SWI-Prolog package documentation'),
 1235	\package_overview([])).
 1236:- endif. 1237
 1238%!  paperback(+Options)//
 1239%
 1240%   Link to the paperback version of the manual.
 1241
 1242paperback(_Options) -->
 1243    { expand_url_path(swipl_book(.), HREF)
 1244    },
 1245    html([ h2('The manual as a book'),
 1246	   p([ 'A paperback version of the manual is ',
 1247	       a(href(HREF), 'available'), '.'
 1248	     ])
 1249	 ]).
 1250
 1251%!  pldoc_refman(+Request)
 1252%
 1253%   HTTP handler for PlDoc Reference Manual access.  Accepts
 1254%   /refman/[<package>.html.]
 1255
 1256pldoc_refman(Request) :-
 1257    memberchk(path_info(Section), Request),
 1258    \+ sub_atom(Section, _, _, _, /),
 1259    Obj = section(0,_,_,_),
 1260    manual_object(Obj, Title, File, manual, _),
 1261    file_base_name(File, Section),
 1262    !,
 1263    reply_html_page(pldoc(man),
 1264		    title(Title),
 1265		    \object_page(Obj, [])).
 1266pldoc_refman(Request) :-                % server Contents.html
 1267    \+ memberchk(path_info(_), Request),
 1268    !,
 1269    http_link_to_id(pldoc_object, [object(manual)], HREF),
 1270    http_redirect(see_other, HREF, Request).
 1271pldoc_refman(Request) :-
 1272    memberchk(path(Path), Request),
 1273    existence_error(http_location, Path).
 1274
 1275
 1276		 /*******************************
 1277		 *          HOOK SEARCH         *
 1278		 *******************************/
 1279
 1280prolog:doc_object_summary(section(ID), Class, File, Summary) :-
 1281    nonvar(ID),                     % when generating, only do full ones
 1282    manual_object(section(_Level, _No, ID, _Path), Summary, File, Class, _Offset).
 1283prolog:doc_object_summary(Obj, Class, File, Summary) :-
 1284    manual_object(Obj, Summary, File, Class, _Offset).
 1285
 1286prolog:doc_object_page(Obj, Options) -->
 1287    man_page(Obj, [no_manual(fail),footer(false)|Options]).
 1288
 1289%!  prolog:doc_object_link(+Obj, +Options)//
 1290%
 1291%   Provide the HTML to describe Obj for linking purposes.
 1292
 1293prolog:doc_object_link(Obj, Options) -->
 1294    { Obj = section(_,_,_,_) },
 1295    !,
 1296    section_link(Obj, Options).
 1297prolog:doc_object_link(Obj0, Options) -->
 1298    { Obj0 = section(ID),
 1299      Obj = section(_Level, _No, ID, _Path),
 1300      manual_object(Obj, _, _, _, _)
 1301    },
 1302    !,
 1303    section_link(Obj, Options).
 1304prolog:doc_object_link(Obj, Options) -->
 1305    { Obj = c(Function) },
 1306    !,
 1307    function_link(Function, Options).
 1308prolog:doc_object_link(root, _) -->
 1309    !,
 1310    html('Documentation').
 1311prolog:doc_object_link(manual, _Options) -->
 1312    !,
 1313    html('Reference manual').
 1314prolog:doc_object_link(packages, _) -->
 1315    html('Packages').
 1316
 1317prolog:doc_category(manual,   30, 'Reference Manual').
 1318prolog:doc_category(packages, 40, 'Packages').
 1319
 1320prolog:doc_file_index_header(File, Options) -->
 1321    { Section = section(_Level, _No, _ID, File),
 1322      manual_object(Section, _Summary, File, _Cat, _Offset)
 1323    },
 1324    !,
 1325    html(tr(th([colspan(3), class(section)],
 1326	       [ \object_ref(Section,
 1327			     [ secref_style(number_title)
 1328			     | Options
 1329			     ])
 1330	       ]))).
 1331
 1332prolog:doc_object_title(Obj, Title) :-
 1333    Obj = section(_,_,_,_),
 1334    manual_object(Obj, Title, _, _, _),
 1335    !.
 1336
 1337prolog:doc_canonical_object(section(_Level, _No, ID, _Path),
 1338			    section(ID)).
 1339
 1340%!  prolog:doc_object_href(+Object, -HREF) is semidet.
 1341%
 1342%   Produce a HREF for section objects.
 1343
 1344prolog:doc_object_href(section(ID), HREF) :-
 1345    nonvar(ID),
 1346    atom_concat('sec:', Sec, ID),
 1347    http_link_to_id(pldoc_man, [section(Sec)], HREF).
 1348prolog:doc_object_href(section(_Level, _No, ID, _Path), HREF) :-
 1349    nonvar(ID),
 1350    atom_concat('sec:', Sec, ID),
 1351    http_link_to_id(pldoc_man, [section(Sec)], HREF).
 1352
 1353		 /*******************************
 1354		 *           NO HTTP		*
 1355		 *******************************/
 1356
 1357:- if(\+current_predicate(http_link_to_id/3)). 1358
 1359http_link_to_id(Id, Parameters, HREF) :-
 1360    must_be(list, Parameters),
 1361    format(atom(Location), '/pldoc/~w', [Id]),
 1362    (   Parameters == []
 1363    ->  HREF = Location
 1364    ;   uri_data(path, Components, Location),
 1365        uri_query_components(String, Parameters),
 1366        uri_data(search, Components, String),
 1367        uri_components(HREF, Components)
 1368    ).
 1369
 1370:- endif. 1371
 1372		 /*******************************
 1373		 *           MESSAGES           *
 1374		 *******************************/
 1375
 1376:- multifile prolog:message//1. 1377
 1378prolog:message(pldoc(no_section_id(File, Title))) -->
 1379    [ 'PlDoc: ~w: no id for section "~w"'-[File, Title] ].
 1380prolog:message(pldoc(duplicate_ids(L))) -->
 1381    [ 'PlDoc: duplicate manual section IDs:'-[], nl
 1382    ],
 1383    duplicate_ids(L).
 1384
 1385duplicate_ids([]) --> [].
 1386duplicate_ids([H|T]) --> duplicate_id(H), duplicate_ids(T).
 1387
 1388duplicate_id(Id) -->
 1389    { findall(File, manual_object(section(_,_,Id,File),_,_,_,_), Files) },
 1390    [ '    ~w: ~p'-[Id, Files], nl ]