View source with raw 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-2023, University of Amsterdam
    7			      VU University Amsterdam
    8			      CWI, Amsterdam
    9                              SWI-Prolog Solutions b.v.
   10    All rights reserved.
   11
   12    Redistribution and use in source and binary forms, with or without
   13    modification, are permitted provided that the following conditions
   14    are met:
   15
   16    1. Redistributions of source code must retain the above copyright
   17       notice, this list of conditions and the following disclaimer.
   18
   19    2. Redistributions in binary form must reproduce the above copyright
   20       notice, this list of conditions and the following disclaimer in
   21       the documentation and/or other materials provided with the
   22       distribution.
   23
   24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   25    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   26    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   27    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   28    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   29    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   30    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   31    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   32    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   34    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   35    POSSIBILITY OF SUCH DAMAGE.
   36*/
   37
   38:- module(pldoc_html,
   39	  [ doc_for_file/2,             % +FileSpec, +Options
   40	    doc_write_html/3,           % +Stream, +Title, +Term
   41	    doc_for_wiki_file/2,        % +FileSpec, +Options
   42					% Support doc_index
   43	    doc_page_dom/3,             % +Title, +Body, -DOM
   44	    print_html_head/1,          % +Stream
   45	    predref//1,                 % +PI //
   46	    predref//2,                 % +PI, Options //
   47	    nopredref//1,               % +PI //
   48	    module_info/3,              % +File, +Options0, -Options
   49	    doc_hide_private/3,         % +Doc0, -Doc, +Options
   50	    edit_button//2,             % +File, +Options, //
   51	    source_button//2,           % +File, +Options, //
   52	    zoom_button//2,             % +File, +Options, //
   53            pred_edit_button//2,        % +PredInd, +Options, //
   54	    object_edit_button//2,      % +Obj, +Options, //
   55	    object_source_button//2,    % +Obj, +Options, //
   56	    doc_resources//1,           % +Options
   57	    ensure_doc_objects/1,       % +File
   58					% Support other backends
   59	    doc_file_objects/5,         % +FSpec, -File, -Objs, -FileOpts, +Opts
   60	    existing_linked_file/2,     % +FileSpec, -Path
   61	    unquote_filespec/2,         % +FileSpec, -Unquoted
   62	    doc_tag_title/2,            % +Tag, -Title
   63	    mode_anchor_name/2,         % +Mode, -Anchor
   64	    pred_anchor_name/3,         % +Head, -PI, -Anchor
   65	    private/2,                  % +Obj, +Options
   66	    (multifile)/2,              % +Obj, +Options
   67	    is_pi/1,                    % @Term
   68	    is_op_type/2,               % +Atom, ?Type
   69					% Output routines
   70	    file//1,                    % +File, //
   71	    file//2,                    % +File, +Options, //
   72	    include//3,                 % +File, +Type, +Options //
   73	    tags//1,                    % +Tags, //
   74	    term//3,                    % +Text, +Term, +Bindings, //
   75	    file_header//2,             % +File, +Options, //
   76	    flagref//1,                 % +Flag
   77	    objects//2,                 % +Objects, +Options, //
   78	    object_ref//2,              % +Object, +Options, //
   79	    object_name//2,             % +Object, +Object
   80	    object_href/2,              % +Object, -URL
   81	    object_tree//3,             % +Tree, +Current, +Options
   82	    object_page//2,             % +Object, +Options, //
   83	    object_page_header//2,      % +File, +Options, //
   84	    object_synopsis//2,         % +Object, +Options, //
   85	    object_footer//2,           % +Object, +Options, //
   86	    object_page_footer//2,      % +Object, +Options, //
   87	    cite//1                     % +Citations
   88	  ]).   89% Get HTTP server infrastructure when available
   90:- if(exists_source(library(http/http_dispatch))).   91:- use_module(library(http/http_dispatch)).   92:- use_module(library(http/http_wrapper)).   93:- use_module(library(http/jquery)).   94
   95pldoc_server(true).
   96:- else.   97
   98:- multifile
   99    http:location/3.  100
  101http:location(pldoc_resource, '/pldoc/res', []).
  102
  103pldoc_server(false).
  104:- endif.  105
  106:- use_module(library(lists)).  107:- use_module(library(option)).  108:- use_module(library(uri)).  109:- use_module(library(readutil)).  110:- use_module(library(http/html_write)).  111:- use_module(library(http/http_path)).  112:- use_module(library(http/html_head)).  113:- use_module(library(http/term_html)).  114:- use_module(library(debug)).  115:- use_module(library(apply)).  116:- use_module(library(pairs)).  117:- use_module(library(filesex)).  118:- use_module(doc_process).  119:- use_module(doc_man).  120:- use_module(doc_modes).  121:- use_module(doc_wiki).  122:- use_module(doc_search).  123:- use_module(doc_index).  124:- use_module(doc_util).  125:- use_module(library(solution_sequences)).  126:- use_module(library(error)).  127:- use_module(library(occurs)).  128:- use_module(library(prolog_source)).  129:- use_module(library(prolog_xref)).  130
  131:- include(hooks).

PlDoc HTML backend

This module translates the Herbrand term from the documentation extracting module doc_wiki.pl into HTML+CSS.

To be done
- Split put generation from computation as computation is reusable in other backends. */
  143:- public
  144    args//1,                        % Called from \Term output created
  145    pred_dt//3,                     % by the wiki renderer
  146    section//2,
  147    tag//2.  148
  149
  150:- predicate_options(doc_for_wiki_file/2, 2,
  151		     [ edit(boolean)
  152		     ]).  153:- predicate_options(doc_hide_private/3, 3,
  154		     [module(atom), public(list), public_only(boolean)]).  155:- predicate_options(edit_button//2, 2,
  156		     [ edit(boolean)
  157		     ]).  158:- predicate_options(file//2, 2,
  159		     [ label(any),
  160		       absolute_path(atom),
  161		       href(atom),
  162		       map_extension(list),
  163		       files(list),
  164		       edit_handler(atom)
  165		     ]).  166:- predicate_options(file_header//2, 2,
  167		     [ edit(boolean),
  168		       files(list),
  169		       public_only(boolean)
  170		     ]).  171:- predicate_options(include//3, 3,
  172		     [ absolute_path(atom),
  173		       class(atom),
  174		       files(list),
  175		       href(atom),
  176		       label(any),
  177		       map_extension(list)
  178		     ]).  179:- predicate_options(object_edit_button//2, 2,
  180		     [ edit(boolean),
  181		       pass_to(pred_edit_button//2, 2)
  182		     ]).  183:- predicate_options(object_page//2, 2,
  184		     [ for(any),
  185		       header(boolean),
  186		       links(boolean),
  187		       no_manual(boolean),
  188		       try_manual(boolean),
  189		       search_in(oneof([all,app,man])),
  190		       search_match(oneof([name,summary])),
  191		       search_options(boolean)
  192		     ]).  193:- predicate_options(object_ref//2, 2,
  194		     [ files(list),
  195		       qualify(boolean),
  196		       style(oneof([number,title,number_title])),
  197		       secref_style(oneof([number,title,number_title]))
  198		     ]).  199:- predicate_options(object_synopsis//2, 2,
  200		     [ href(atom)
  201		     ]).  202:- predicate_options(pred_dt//3, 3,
  203		     [ edit(boolean)
  204		     ]).  205:- predicate_options(pred_edit_button//2, 2,
  206		     [ edit(boolean)
  207		     ]).  208:- predicate_options(predref//2, 2,
  209		     [ files(list),
  210		       prefer(oneof([manual,app])),
  211		       pass_to(object_ref/4, 2)
  212		     ]).  213:- predicate_options(private/2, 2,
  214		     [ module(atom),
  215		       public(list)
  216		     ]).  217:- predicate_options(source_button//2, 2,
  218		     [ files(list)
  219		     ]).  220
  221
  222		 /*******************************
  223		 *           RESOURCES          *
  224		 *******************************/
  225
  226:- if(pldoc_server(true)).  227:- html_resource(pldoc_css,
  228		 [ virtual(true),
  229		   requires([ pldoc_resource('pldoc.css')
  230			    ])
  231		 ]).  232:- html_resource(pldoc_resource('pldoc.js'),
  233		 [ requires([ jquery
  234			    ])
  235		 ]).  236:- html_resource(pldoc_js,
  237		 [ virtual(true),
  238		   requires([ pldoc_resource('pldoc.js')
  239			    ])
  240		 ]).  241:- html_resource(pldoc,
  242		 [ virtual(true),
  243		   requires([ pldoc_css,
  244			      pldoc_js
  245			    ])
  246		 ]).  247:- else.  248:- html_resource(pldoc_css, [virtual(true)]).  249:- html_resource(pldoc_resource('pldoc.js'), [virtual(true)]).  250:- html_resource(pldoc_js, [virtual(true)]).  251:- html_resource(pldoc, [virtual(true)]).  252:- endif.  253
  254
  255		 /*******************************
  256		 *       FILE PROCESSING        *
  257		 *******************************/
 doc_for_file(+File, +Options) is det
HTTP handler that writes documentation for File as HTML. Options:
public_only(+Bool)
If true (default), only emit documentation for exported predicates.
edit(Bool)
If true, provide edit buttons. Default, these buttons are suppressed.
title(+Title)
Specify the page title. Default is the base name of the file.
Arguments:
File- Prolog file specification or xref source id.
  278doc_for_file(FileSpec, Options) :-
  279    doc_file_objects(FileSpec, File, Objects, FileOptions, Options),
  280    doc_file_title(File, Title, FileOptions, Options),
  281    doc_write_page(
  282	pldoc(file(File, Title)),
  283	title(Title),
  284	\prolog_file(File, Objects, FileOptions, Options),
  285	Options).
  286
  287doc_file_title(_, Title, _, Options) :-
  288    option(title(Title), Options),
  289    !.
  290doc_file_title(File, Title, FileOptions, _) :-
  291    memberchk(file(Title0, _Comment), FileOptions),
  292    !,
  293    file_base_name(File, Base),
  294    atomic_list_concat([Base, ' -- ', Title0], Title).
  295doc_file_title(File, Title, _, _) :-
  296    file_base_name(File, Title).
  297
  298:- html_meta doc_write_page(+, html, html, +).  299
  300doc_write_page(Style, Head, Body, Options) :-
  301    option(files(_), Options),
  302    !,
  303    phrase(page(Style, Head, Body), HTML),
  304    print_html(HTML).
  305doc_write_page(Style, Head, Body, _) :-
  306    reply_html_page(Style, Head, Body).
  307
  308
  309prolog_file(File, Objects, FileOptions, Options) -->
  310    { b_setval(pldoc_file, File),   % TBD: delete?
  311      file_directory_name(File, Dir)
  312    },
  313    html([ \doc_resources(Options),
  314	   \doc_links(Dir, FileOptions),
  315	   \file_header(File, FileOptions)
  316	 | \objects(Objects, FileOptions)
  317	 ]),
  318    undocumented(File, Objects, FileOptions).
 doc_resources(+Options)// is det
Include required resources (CSS, JS) into the output. The first clause supports doc_files.pl. A bit hacky ...
  325doc_resources(Options) -->
  326    { option(resource_directory(ResDir), Options),
  327      nb_current(pldoc_output, OutputFile),
  328      !,
  329      directory_file_path(ResDir, 'pldoc.css', Res),
  330      relative_file_name(Res, OutputFile, Ref)
  331    },
  332    html_requires(Ref).
  333doc_resources(Options) -->
  334    { option(html_resources(Resoures), Options, pldoc)
  335    },
  336    html_requires(Resoures).
 doc_file_objects(+FileSpec, -File, -Objects, -FileOptions, +Options) is det
Extracts relevant information for FileSpec from the PlDoc database. FileOptions contains:

Objects contains

We distinguish three different states for FileSpec:

  1. File was cross-referenced with collection enabled. All information is in the xref database.
  2. File was loaded. If comments are not loaded, cross-reference the file, while storing the comments as the compiler would do.
  3. Neither of the above. In this case we cross-reference the file.
Arguments:
FileSpec- File specification as used for load_files/2.
File- Prolog canonical filename
  365doc_file_objects(FileSpec, File, Objects, FileOptions, Options) :-
  366    xref_current_source(FileSpec),
  367    xref_option(FileSpec, comments(collect)),
  368    !,
  369    File = FileSpec,
  370    findall(Object, xref_doc_object(File, Object), Objects0),
  371    reply_file_objects(File, Objects0, Objects, FileOptions, Options).
  372doc_file_objects(FileSpec, File, Objects, FileOptions, Options) :-
  373    absolute_file_name(FileSpec, File,
  374		       [ file_type(prolog),
  375			 access(read)
  376		       ]),
  377    source_file(File),
  378    !,
  379    ensure_doc_objects(File),
  380    Pos = File:Line,
  381    findall(Line-doc(Obj,Pos,Comment),
  382	    doc_comment(Obj, Pos, _, Comment), Pairs),
  383    sort(Pairs, Pairs1),            % remove duplicates
  384    keysort(Pairs1, ByLine),
  385    pairs_values(ByLine, Objs0),
  386    reply_file_objects(File, Objs0, Objects, FileOptions, Options).
  387doc_file_objects(FileSpec, File, Objects, FileOptions, Options) :-
  388    absolute_file_name(FileSpec, File,
  389		       [ file_type(prolog),
  390			 access(read)
  391		       ]),
  392    xref_source(File, [silent(true)]),
  393    findall(Object, xref_doc_object(File, Object), Objects0),
  394    reply_file_objects(File, Objects0, Objects, FileOptions, Options).
  395
  396
  397reply_file_objects(File, Objs0, Objects, FileOptions, Options) :-
  398    module_info(File, ModuleOptions, Options),
  399    file_info(Objs0, Objs1, FileOptions, ModuleOptions),
  400    doc_hide_private(Objs1, ObjectsSelf, ModuleOptions),
  401    include_reexported(ObjectsSelf, Objects1, File, FileOptions),
  402    remove_doc_duplicates(Objects1, Objects, []).
  403
  404remove_doc_duplicates([], [], _).
  405remove_doc_duplicates([H|T0], [H|T], Seen) :-
  406    H = doc(_, _, Comment),
  407    \+ memberchk(Comment, Seen),
  408    !,
  409    remove_doc_duplicates(T0, T, [Comment|Seen]).
  410remove_doc_duplicates([_|T0], T, Seen) :-
  411    remove_doc_duplicates(T0, T, Seen).
  412
  413include_reexported(SelfObjects, Objects, File, Options) :-
  414    option(include_reexported(true), Options),
  415    option(module(Module), Options),
  416    option(public(Exports), Options),
  417    select_undocumented(Exports, Module, SelfObjects, Undoc),
  418    re_exported_doc(Undoc, File, Module, REObjs, _),
  419    REObjs \== [],
  420    !,
  421    append(SelfObjects, REObjs, Objects).
  422include_reexported(Objects, Objects, _, _).
 xref_doc_object(File, DocObject) is nondet
  427xref_doc_object(File, doc(M:module(Title),File:0,Comment)) :-
  428    xref_comment(File, Title, Comment),
  429    xref_module(File, M).
  430xref_doc_object(File, doc(M:Name/Arity,File:0,Comment)) :-
  431    xref_comment(File, Head, _Summary, Comment),
  432    xref_module(File, Module),
  433    strip_module(Module:Head, M, Plain),
  434    functor(Plain, Name, Arity).
 ensure_doc_objects(+File) is det
Ensure we have documentation about File. If we have no comments for the file because it was loaded before comment collection was enabled, run the cross-referencer on it to collect the comments and meta-information.
Arguments:
File- is a canonical filename that is loaded.
  445:- dynamic
  446    no_comments/2.  447
  448ensure_doc_objects(File) :-
  449    source_file(File),
  450    !,
  451    (   doc_file_has_comments(File)
  452    ->  true
  453    ;   no_comments(File, TimeChecked),
  454	time_file(File, TimeChecked)
  455    ->  true
  456    ;   xref_source(File, [silent(true), comments(store)]),
  457	retractall(no_comments(File, _)),
  458	(   doc_file_has_comments(File)
  459	->  true
  460	;   time_file(File, TimeChecked),
  461	    assertz(no_comments(File, TimeChecked))
  462	)
  463    ).
  464ensure_doc_objects(File) :-
  465    xref_source(File, [silent(true)]).
 module_info(+File, -ModuleOptions, +OtherOptions) is det
Add options module(Name), public(Exports) to OtherOptions if File is a module file.
  472module_info(File, [module(Module), public(Exports)|Options], Options) :-
  473    module_property(Module, file(File)),
  474    !,
  475    module_property(Module, exports(Exports)).
  476module_info(File, [module(Module), public(Exports)|Options], Options) :-
  477    xref_module(File, Module),
  478    !,
  479    findall(PI, xref_exported_pi(File, PI), Exports).
  480module_info(_, Options, Options).
  481
  482xref_exported_pi(Src, Name/Arity) :-
  483    xref_exported(Src, Head),
  484    functor(Head, Name, Arity).
 doc_hide_private(+Objs, +Public, +Options)
Remove the private objects from Objs according to Options.
  490doc_hide_private(Objs, Objs, Options) :-
  491    option(public_only(false), Options, true),
  492    !.
  493doc_hide_private(Objs0, Objs, Options) :-
  494    hide_private(Objs0, Objs, Options).
  495
  496hide_private([], [], _).
  497hide_private([H|T0], T, Options) :-
  498    obj(H, Obj),
  499    private(Obj, Options),
  500    !,
  501    hide_private(T0, T, Options).
  502hide_private([H|T0], [H|T], Options) :-
  503    hide_private(T0, T, Options).
 obj(+Term, -Object) is det
Extract the documented object from its environment. It is assumed to be the first term. Note that if multiple objects are described by the same comment Term is a list.
  511obj(doc(Obj0, _Pos, _Summary), Obj) :-
  512    !,
  513    (   Obj0 = [Obj|_]
  514    ->  true
  515    ;   Obj = Obj0
  516    ).
  517obj(Obj0, Obj) :-
  518    (   Obj0 = [Obj|_]
  519    ->  true
  520    ;   Obj = Obj0
  521    ).
 private(+Obj, +Options) is semidet
True if Obj is not exported from Options. This means Options defined a module and Obj is not member of the exports of the module.
  530:- multifile
  531    prolog:doc_is_public_object/1.  532
  533private(Object, _Options) :-
  534    prolog:doc_is_public_object(Object), !, fail.
  535private(Module:PI, Options) :-
  536    multifile(Module:PI, Options), !, fail.
  537private(Module:PI, Options) :-
  538    public(Module:PI, Options), !, fail.
  539private(Module:PI, Options) :-
  540    option(module(Module), Options),
  541    option(public(Public), Options),
  542    !,
  543    \+ ( member(PI2, Public),
  544	 eq_pi(PI, PI2)
  545       ).
  546private(Module:PI, _Options) :-
  547    module_property(Module, file(_)),      % A loaded module
  548    !,
  549    module_property(Module, exports(Exports)),
  550    \+ ( member(PI2, Exports),
  551	 eq_pi(PI, PI2)
  552       ).
  553private(Module:PI, _Options) :-
  554    \+ (pi_to_head(PI, Head),
  555	xref_exported(Source, Head),
  556	xref_module(Source, Module)).
 prolog:doc_is_public_object(+Object) is semidet
Hook that allows objects to be displayed with the default public-only view.
 multifile(+Obj, +Options) is semidet
True if Obj is a multifile predicate.
  567multifile(Obj, _Options) :-
  568    strip_module(user:Obj, Module, PI),
  569    pi_to_head(PI, Head),
  570    (   predicate_property(Module:Head, multifile)
  571    ;   xref_module(Source, Module),
  572	xref_defined(Source, Head, multifile(_Line))
  573    ),
  574    !.
 public(+Options, +Options)
True if Obj is declared using public/1.
  580public(Obj, _Options) :-
  581    strip_module(user:Obj, Module, PI),
  582    pi_to_head(PI, Head),
  583    (   predicate_property(Module:Head, public)
  584    ;   xref_module(Source, Module),
  585	xref_defined(Source, Head, public(_Line))
  586    ),
  587    !.
  588
  589pi_to_head(Var, _) :-
  590    var(Var), !, fail.
  591pi_to_head(Name/Arity, Term) :-
  592    functor(Term, Name, Arity).
  593pi_to_head(Name//DCGArity, Term) :-
  594    Arity is DCGArity+2,
  595    functor(Term, Name, Arity).
 file_info(+Comments, -RestComment, -FileOptions, +OtherOptions) is det
Add options file(Title, Comment) to OtherOptions if available.
  601file_info(Comments, RestComments, [file(Title, Comment)|Opts], Opts) :-
  602    select(doc(_:module(Title),_,Comment), Comments, RestComments),
  603    !.
  604file_info(Comments, Comments, Opts, Opts).
 file_header(+File, +Options)// is det
Create the file header.
  611file_header(File, Options) -->
  612    { memberchk(file(Title, Comment), Options),
  613      !,
  614      file_base_name(File, Base)
  615    },
  616    file_title([Base, ' -- ', Title], File, Options),
  617    { is_structured_comment(Comment, Prefixes),
  618      string_codes(Comment, Codes),
  619      indented_lines(Codes, Prefixes, Lines),
  620      section_comment_header(Lines, _Header, Lines1),
  621      wiki_lines_to_dom(Lines1, [], DOM)
  622    },
  623    html(DOM).
  624file_header(File, Options) -->
  625    { file_base_name(File, Base)
  626    },
  627    file_title([Base], File, Options).
 file_title(+Title:list, +File, +Options)// is det
Emit the file-header and manipulation buttons.
  634file_title(Title, File, Options) -->
  635    prolog:doc_file_title(Title, File, Options),
  636    !.
  637file_title(Title, File, Options) -->
  638    { file_base_name(File, Base)
  639    },
  640    html(h1(class(file),
  641	    [ span(style('float:right'),
  642		   [ \reload_button(File, Base, Options),
  643		     \zoom_button(Base, Options),
  644		     \source_button(Base, Options),
  645		     \edit_button(File, Options)
  646		   ])
  647	    | Title
  648	    ])).
 reload_button(+File, +Base, +Options)// is det
Create a button for reloading the sources and updating the documentation page. Note that the button is not shown if the file is not loaded because we do not want to load files through the documentation system.
  658reload_button(File, _Base, Options) -->
  659    { \+ source_file(File),
  660      \+ option(files(_), Options)
  661    },
  662    !,
  663    html(span(class(file_anot), '[not loaded]')).
  664reload_button(_File, Base, Options) -->
  665    { option(edit(true), Options),
  666      !,
  667      option(public_only(Public), Options, true)
  668    },
  669    html(a(href(Base+[reload(true), public_only(Public)]),
  670	   img([ class(action),
  671		 alt('Reload'),
  672		 title('Make & Reload'),
  673		 src(location_by_id(pldoc_resource)+'reload.png')
  674	       ]))).
  675reload_button(_, _, _) --> [].
 edit_button(+File, +Options)// is det
Create an edit button for File. If the button is clicked, JavaScript sends a message to the server without modifying the current page. JavaScript code is in the file pldoc.js.
  683edit_button(File, Options) -->
  684    { option(edit(true), Options)
  685    },
  686    !,
  687    html(a([ onClick('HTTPrequest(\'' +
  688		     location_by_id(pldoc_edit) + [file(File)] +
  689		     '\')')
  690	   ],
  691	   img([ class(action),
  692		 alt(edit),
  693		 title('Edit file'),
  694		 src(location_by_id(pldoc_resource)+'edit.png')
  695	     ]))).
  696edit_button(_, _) -->
  697    [].
 zoom_button(BaseName, +Options)// is det
Add zoom in/out button to show/hide the private documentation.
  704zoom_button(_, Options) -->
  705    { option(files(_Map), Options) },
  706    !.    % generating files
  707zoom_button(Base, Options) -->
  708    {   (   option(public_only(true), Options, true)
  709	->  Zoom = 'public.png',
  710	    Alt = 'Public',
  711	    Title = 'Click to include private',
  712	    PublicOnly = false
  713	;   Zoom = 'private.png',
  714	    Alt = 'All predicates',
  715	    Title = 'Click to show exports only',
  716	    PublicOnly = true
  717	)
  718    },
  719    html(a(href(Base+[public_only(PublicOnly)]),
  720	   img([ class(action),
  721		 alt(Alt),
  722		 title(Title),
  723		 src(location_by_id(pldoc_resource)+Zoom)
  724	       ]))).
 source_button(+File, +Options)// is det
Add show-source button.
  731source_button(_File, Options) -->
  732    { option(files(_Map), Options) },
  733    !.    % generating files
  734source_button(File, _Options) -->
  735    { (   is_absolute_file_name(File)
  736      ->  doc_file_href(File, HREF0)
  737      ;   HREF0 = File
  738      )
  739    },
  740    html(a(href(HREF0+[show(src)]),
  741	   img([ class(action),
  742		 alt('Show source'),
  743		 title('Show source'),
  744		 src(location_by_id(pldoc_resource)+'source.png')
  745	       ]))).
 objects(+Objects:list, +Options)// is det
Emit the documentation body. Options includes:
navtree(+Boolean)
If true, provide a navitation tree.
  755objects(Objects, Options) -->
  756    { option(navtree(true), Options),
  757      !,
  758      objects_nav_tree(Objects, Tree)
  759    },
  760    html([ div(class(navtree),
  761	       div(class(navwindow),
  762		   \nav_tree(Tree, Objects, Options))),
  763	   div(class(navcontent),
  764	       \objects_nt(Objects, Options))
  765	 ]).
  766objects(Objects, Options) -->
  767    objects_nt(Objects, Options).
  768
  769objects_nt(Objects, Options) -->
  770    objects(Objects, [body], Options).
  771
  772objects([], Mode, _) -->
  773    pop_mode(body, Mode, _).
  774objects([Obj|T], Mode, Options) -->
  775    object(Obj, Mode, Mode1, Options),
  776    objects(T, Mode1, Options).
 object(+Spec, +ModeIn, -ModeOut, +Options)// is det
Emit the documentation of a single object.
Arguments:
Spec- is one of doc(Obj,Pos,Comment), which is used to list the objects documented in a file or a plain Obj, used for documenting the object regardless of its location.
  787object(doc(Obj,Pos,Comment), Mode0, Mode, Options) -->
  788    !,
  789    object(Obj, [Pos-Comment], Mode0, Mode, [scope(file)|Options]).
  790object(Obj, Mode0, Mode, Options) -->
  791    { findall(Pos-Comment,
  792	      doc_comment(Obj, Pos, _Summary, Comment),
  793	      Pairs)
  794    },
  795    !,
  796    { b_setval(pldoc_object, Obj) },
  797    object(Obj, Pairs, Mode0, Mode, Options).
  798
  799object(Obj, Pairs, Mode0, Mode, Options) -->
  800    { is_pi(Obj),
  801      !,
  802      maplist(pred_dom(Obj, Options), Pairs, DOMS),
  803      append(DOMS, DOM)
  804    },
  805    need_mode(dl, Mode0, Mode),
  806    html(DOM).
  807object([Obj|_Same], Pairs, Mode0, Mode, Options) -->
  808    !,
  809    object(Obj, Pairs, Mode0, Mode, Options).
  810object(Obj, _Pairs, Mode, Mode, _Options) -->
  811    { debug(pldoc, 'Skipped ~p', [Obj]) },
  812    [].
  813
  814pred_dom(Obj, Options, Pos-Comment, DOM) :-
  815    is_structured_comment(Comment, Prefixes),
  816    string_codes(Comment, Codes),
  817    indented_lines(Codes, Prefixes, Lines),
  818    strip_module(user:Obj, Module, _),
  819    process_modes(Lines, Module, Pos, Modes, Args, Lines1),
  820    (   private(Obj, Options)
  821    ->  Class = privdef             % private definition
  822    ;   multifile(Obj, Options)
  823    ->  (   option(scope(file), Options)
  824	->  (   more_doc(Obj, Pos)
  825	    ->  Class = multidef(object(Obj))
  826	    ;   Class = multidef
  827	    )
  828	;   Class = multidef(file((Pos)))
  829	)
  830    ;   public(Obj, Options)
  831    ->  Class = publicdef           % :- public definition
  832    ;   Class = pubdef              % exported definition
  833    ),
  834    (   Obj = Module:_
  835    ->  POptions = [module(Module)|Options]
  836    ;   POptions = Options
  837    ),
  838    Pos = File:Line,
  839    DTOptions = [file(File),line(Line)|POptions],
  840    DOM = [\pred_dt(Modes, Class, DTOptions), dd(class=defbody, DOM1)],
  841    wiki_lines_to_dom(Lines1, Args, DOM0),
  842    strip_leading_par(DOM0, DOM1).
  843
  844more_doc(Obj, File:_) :-
  845    doc_comment(Obj, File2:_, _, _),
  846    File2 \== File,
  847    !.
 need_mode(+Mode:atom, +Stack:list, -NewStack:list)// is det
While predicates are part of a description list, sections are not and we therefore need to insert <dl>...</dl> into the output. We do so by demanding an outer environment and push/pop the required elements.
  856need_mode(Mode, Stack, Stack) -->
  857    { Stack = [Mode|_] },
  858    !,
  859    [].
  860need_mode(Mode, Stack, Rest) -->
  861    { memberchk(Mode, Stack)
  862    },
  863    !,
  864    pop_mode(Mode, Stack, Rest).
  865need_mode(Mode, Stack, [Mode|Stack]) -->
  866    !,
  867    html_begin(Mode).
  868
  869pop_mode(Mode, Stack, Stack) -->
  870    { Stack = [Mode|_] },
  871    !,
  872    [].
  873pop_mode(Mode, [H|Rest0], Rest) -->
  874    html_end(H),
  875    pop_mode(Mode, Rest0, Rest).
 undocumented(+File, +Objects, +Options)// is det
Describe undocumented predicates if the file is a module file.
  881undocumented(File, Objs, Options) -->
  882    { memberchk(module(Module), Options),
  883      memberchk(public(Exports), Options),
  884      select_undocumented(Exports, Module, Objs, Undoc),
  885      re_exported_doc(Undoc, File, Module, REObjs, ReallyUnDoc)
  886    },
  887    !,
  888    re_exported_doc(REObjs, Options),
  889    undocumented(ReallyUnDoc, Options).
  890undocumented(_, _, _) -->
  891    [].
  892
  893re_exported_doc([], _) --> !.
  894re_exported_doc(Objs, Options) -->
  895    reexport_header(Objs, Options),
  896    objects(Objs, Options).
  897
  898reexport_header(_, Options) -->
  899    { option(reexport_header(true), Options, true)
  900    },
  901    !,
  902    html([ h2(class(wiki), 'Re-exported predicates'),
  903	   p([ "The following predicates are exported from this file \c
  904                while their implementation is defined in imported modules \c
  905                or non-module files loaded by this module."
  906	     ])
  907	 ]).
  908reexport_header(_, _) -->
  909    [].
  910
  911undocumented([], _) --> !.
  912undocumented(UnDoc, Options) -->
  913    html([ h2(class(undoc), 'Undocumented predicates'),
  914	   p(['The following predicates are exported, but not ',
  915	      'or incorrectly documented.'
  916	     ]),
  917	   dl(class(undoc),
  918	      \undocumented_predicates(UnDoc, Options))
  919	 ]).
  920
  921
  922undocumented_predicates([], _) -->
  923    [].
  924undocumented_predicates([H|T], Options) -->
  925    undocumented_pred(H, Options),
  926    undocumented_predicates(T, Options).
  927
  928undocumented_pred(Name/Arity, Options) -->
  929    { functor(Head, Name, Arity) },
  930    html(dt(class=undoc, \pred_mode(Head, [], _, Options))).
  931
  932select_undocumented([], _, _, []).
  933select_undocumented([PI|T0], M, Objs, [PI|T]) :-
  934    is_pi(PI),
  935    \+ in_doc(M:PI, Objs),
  936    !,
  937    select_undocumented(T0, M, Objs, T).
  938select_undocumented([_|T0], M, Objs, T) :-
  939    select_undocumented(T0, M, Objs, T).
  940
  941in_doc(PI, Objs) :-
  942    member(doc(O,_,_), Objs),
  943    (   is_list(O)
  944    ->  member(O2, O),
  945	eq_pi(PI, O2)
  946    ;   eq_pi(PI, O)
  947    ).
 eq_pi(PI1, PI2) is semidet
True if PI1 and PI2 refer to the same predicate.
  954eq_pi(PI, PI) :- !.
  955eq_pi(M:PI1, M:PI2) :-
  956    atom(M),
  957    !,
  958    eq_pi(PI1, PI2).
  959eq_pi(Name/A, Name//DCGA) :-
  960    A =:= DCGA+2,
  961    !.
  962eq_pi(Name//DCGA, Name/A) :-
  963    A =:= DCGA+2.
 is_pi(@Term) is semidet
True if Term is a predicate indicator.
  969is_pi(Var) :-
  970    var(Var),
  971    !,
  972    fail.
  973is_pi(_:PI) :-
  974    !,
  975    is_pi(PI).
  976is_pi(_/_).
  977is_pi(_//_).
 re_exported_doc(+Undoc:list(pi), +File:atom, +Module:atom, -ImportedDoc, -ReallyUnDoc:list(pi))
  983re_exported_doc([], _, _, [], []).
  984re_exported_doc([PI|T0], File, Module, [doc(Orig:PI,Pos,Comment)|ObjT], UnDoc) :-
  985    pi_to_head(PI, Head),
  986    (   predicate_property(Module:Head, imported_from(Orig))
  987    ->  true
  988    ;   predicate_property(Module:Head, exported)
  989    ->  Orig = Module
  990    ;   xref_defined(File, Head, imported(File2)),
  991	ensure_doc_objects(File2),
  992	xref_module(File2, Orig)
  993    ),
  994    doc_comment(Orig:PI, Pos, _, Comment),
  995    !,
  996    re_exported_doc(T0, File, Module, ObjT, UnDoc).
  997re_exported_doc([PI|T0], File, Module, REObj, [PI|UnDoc]) :-
  998    re_exported_doc(T0, File, Module, REObj, UnDoc).
  999
 1000
 1001		 /*******************************
 1002		 *      SINGLE OBJECT PAGE      *
 1003		 *******************************/
 object_page(+Obj, +Options)// is semidet
Generate an HTML page describing Obj. The top presents the file the object is documented in and a search-form. Options:
header(+Boolean)
Show the navigation and search header.
 1013object_page(Obj, Options) -->
 1014    prolog:doc_object_page(Obj, Options),
 1015    !,
 1016    object_page_footer(Obj, Options).
 1017object_page(Obj, Options) -->
 1018    { doc_comment(Obj, File:_Line, _Summary, _Comment)
 1019    },
 1020    !,
 1021    (   { \+ ( doc_comment(Obj, File2:_, _, _),
 1022	       File2 \== File )
 1023	}
 1024    ->  html([ \html_requires(pldoc),
 1025	       \object_page_header(File, Options),
 1026	       \object_synopsis(Obj, []),
 1027	       \objects([Obj], Options)
 1028	     ])
 1029    ;   html([ \html_requires(pldoc),
 1030	       \object_page_header(-, Options),
 1031	       \objects([Obj], [synopsis(true)|Options])
 1032	     ])
 1033    ),
 1034    object_page_footer(Obj, Options).
 1035object_page(M:Name/Arity, Options) -->          % specified module, but public
 1036    { functor(Head, Name, Arity),
 1037      (   predicate_property(M:Head, exported)
 1038      ->  module_property(M, class(library))
 1039      ;   \+ predicate_property(M:Head, defined)
 1040      )
 1041    },
 1042    prolog:doc_object_page(Name/Arity, Options),
 1043    !,
 1044    object_page_footer(Name/Arity, Options).
 1045
 1046object_page_header(File, Options) -->
 1047    prolog:doc_page_header(file(File), Options),
 1048    !.
 1049object_page_header(File, Options) -->
 1050    { option(header(true), Options, true) },
 1051    !,
 1052    html(div(class(navhdr),
 1053	     [ div(class(jump), \file_link(File)),
 1054	       div(class(search), \search_form(Options)),
 1055	       br(clear(right))
 1056	     ])).
 1057object_page_header(_, _) --> [].
 1058
 1059file_link(-) -->
 1060    !,
 1061    places_menu(-).
 1062file_link(File) -->
 1063    { file_directory_name(File, Dir)
 1064    },
 1065    places_menu(Dir),
 1066    html([ div(a(href(location_by_id(pldoc_doc)+File), File))
 1067	 ]).
 object_footer(+Obj, +Options)// is det
Call the hook doc_object_footer//2. This hook will be used to deal with examples.
 1074object_footer(Obj, Options) -->
 1075    prolog:doc_object_footer(Obj, Options),
 1076    !.
 1077object_footer(_, _) --> [].
 object_page_footer(+Obj, +Options)// is det
Call the hook doc_object_page_footer//2. This hook will be used to deal with annotations.
 1085object_page_footer(Obj, Options) -->
 1086    prolog:doc_object_page_footer(Obj, Options),
 1087    !.
 1088object_page_footer(_, _) --> [].
 object_synopsis(Obj, Options)// is det
Provide additional information about Obj. Note that due to reexport facilities, predicates may be available from multiple modules.
To be done
- Currently we provide a synopsis for the one where the definition resides. This is not always correct. Notably there are cases where multiple implementation modules are bundled in a larger interface that is the `preferred' module.
 1102object_synopsis(Name/Arity, _) -->
 1103    { functor(Head, Name, Arity),
 1104      predicate_property(system:Head, built_in)
 1105    },
 1106    synopsis([span(class(builtin), 'built-in')]).
 1107object_synopsis(Name/Arity, Options) -->
 1108    !,
 1109    object_synopsis(_:Name/Arity, Options).
 1110object_synopsis(M:Name/Arity, Options) -->
 1111    { functor(Head, Name, Arity),
 1112      (   option(source(Spec), Options)
 1113      ->  absolute_file_name(Spec, File,
 1114			     [ access(read),
 1115			       file_type(prolog),
 1116			       file_errors(fail)
 1117			     ])
 1118      ;   predicate_property(M:Head, exported),
 1119	  \+ predicate_property(M:Head, imported_from(_)),
 1120	  module_property(M, file(File)),
 1121	  file_name_on_path(File, Spec)
 1122      ),
 1123      !,
 1124      unquote_filespec(Spec, Unquoted),
 1125      (   predicate_property(Head, autoload(FileBase)),
 1126	  file_name_extension(FileBase, _Ext, File)
 1127      ->  Extra = [span(class(autoload), '(can be autoloaded)')]
 1128      ;   Extra = []
 1129      )
 1130    },
 1131    (   { option(href(HREF), Options) }
 1132    ->  synopsis([code([':- use_module(',a(href(HREF), '~q'-[Unquoted]),').'])|Extra])
 1133    ;   synopsis([code(':- use_module(~q).'-[Unquoted])|Extra])
 1134    ).
 1135object_synopsis(Name//Arity, Options) -->
 1136    !,
 1137    { DCGArity is Arity+2 },
 1138    object_synopsis(Name/DCGArity, Options).
 1139object_synopsis(Module:Name//Arity, Options) -->
 1140    !,
 1141    { DCGArity is Arity+2 },
 1142    object_synopsis(Module:Name/DCGArity, Options).
 1143object_synopsis(f(_/_), _) -->
 1144    synopsis(span(class(function),
 1145		  [ 'Arithmetic function (see ',
 1146		    \object_ref(is/2, []),
 1147		    ')'
 1148		  ])).
 1149object_synopsis(c(Func), _) -->
 1150    {   sub_atom(Func, 0, _, _, 'PL_')
 1151    ;   sub_atom(Func, 0, _, _, 'S')
 1152    },
 1153    !,
 1154    synopsis([span(class(cfunc), 'C-language interface function')]).
 1155object_synopsis(_, _) --> [].
 1156
 1157synopsis(Text) -->
 1158    html(div(class(synopsis),
 1159	     [ span(class('synopsis-hdr'), 'Availability:')
 1160	     | Text
 1161	     ])).
 unquote_filespec(+Spec, -Unquoted) is det
Translate e.g. library('semweb/rdf_db') into library(semweb/rdf_db).
 1168unquote_filespec(Spec, Unquoted) :-
 1169    compound(Spec),
 1170    Spec =.. [Alias,Path],
 1171    atom(Path),
 1172    atomic_list_concat(Parts, /, Path),
 1173    maplist(need_no_quotes, Parts),
 1174    !,
 1175    parts_to_path(Parts, UnquotedPath),
 1176    Unquoted =.. [Alias, UnquotedPath].
 1177unquote_filespec(Spec, Spec).
 1178
 1179need_no_quotes(Atom) :-
 1180    format(atom(A), '~q', [Atom]),
 1181    \+ sub_atom(A, 0, _, _, '\'').
 1182
 1183parts_to_path([One], One) :- !.
 1184parts_to_path(List, More/T) :-
 1185    (   append(H, [T], List)
 1186    ->  parts_to_path(H, More)
 1187    ).
 1188
 1189
 1190		 /*******************************
 1191		 *             PRINT            *
 1192		 *******************************/
 doc_write_html(+Out:stream, +Title:atomic, +DOM) is det
Write HTML for the documentation page DOM using Title to Out.
 1198doc_write_html(Out, Title, Doc) :-
 1199    doc_page_dom(Title, Doc, DOM),
 1200    phrase(html(DOM), Tokens),
 1201    print_html_head(Out),
 1202    print_html(Out, Tokens).
 doc_page_dom(+Title, +Body, -DOM) is det
Create the complete HTML DOM from the Title and Body. It adds links to the style-sheet and javaScript files.
 1209doc_page_dom(Title, Body, DOM) :-
 1210    DOM = html([ head([ title(Title),
 1211			link([ rel(stylesheet),
 1212			       type('text/css'),
 1213			       href(location_by_id(pldoc_resource)+'pldoc.css')
 1214			     ]),
 1215			script([ src(location_by_id(pldoc_resource)+'pldoc.js'),
 1216				 type('text/javascript')
 1217			       ], [])
 1218		      ]),
 1219		 body(Body)
 1220	       ]).
 print_html_head(+Out:stream) is det
Print the DOCTYPE line.
 1226print_html_head(Out) :-
 1227    format(Out,
 1228	   '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" \c
 1229	       "http://www.w3.org/TR/html4/strict.dtd">~n', []).
 1230
 1231% Rendering rules
 1232%
 1233% These rules translate \-terms produced by wiki.pl
 tags(+Tags)// is det
Emit the @tag tags of a description. Tags is produced by tags/3.
See also
- combine_tags/2.
 1241tags(Tags) -->
 1242    html(dl(class=tags, Tags)).
 tag(+Tag, +Values:list)// is det
Called from \tag(Name, Values) terms produced by doc_wiki.pl.
 1248tag(Tag, Values) -->
 1249    {   doc_tag_title(Tag, Title),
 1250	atom_concat('keyword-', Tag, Class)
 1251    },
 1252    html([ dt(class=Class, Title),
 1253	   \tag_values(Values, Class)
 1254	 ]).
 1255
 1256tag_values([], _) -->
 1257    [].
 1258tag_values([H|T], Class) -->
 1259    html(dd(class=Class, ['- '|H])),
 1260    tag_values(T, Class).
 doc_tag_title(+Tag, -Title) is det
Title is the name to use for Tag in the generated documentation.
 1267doc_tag_title(Tag, Title) :-
 1268    tag_title(Tag, Title),
 1269    !.
 1270doc_tag_title(Tag, Tag).
 1271
 1272tag_title(compat, 'Compatibility').
 1273tag_title(tbd,    'To be done').
 1274tag_title(see,    'See also').
 1275tag_title(error,  'Errors').
 1276tag_title(since,  'Since').
 args(+Params:list) is det
Called from \args(List) created by doc_wiki.pl. Params is a list of arg(Name, Descr).
 1283args(Params) -->
 1284    html([ dt(class=tag, 'Arguments:'),
 1285	   dd(table(class=arglist,
 1286		    \arg_list(Params)))
 1287	 ]).
 1288
 1289arg_list([]) -->
 1290    [].
 1291arg_list([H|T]) -->
 1292    argument(H),
 1293    arg_list(T).
 1294
 1295argument(arg(Name,Descr)) -->
 1296    html(tr([td(var(Name)), td(class=argdescr, ['- '|Descr])])).
 1297
 1298
 1299		 /*******************************
 1300		 *         NAVIGATION TREE      *
 1301		 *******************************/
 objects_nav_tree(+Objects, -Tree) is det
Provide a navigation tree showing the context of Object. Tree is of the form node(Object, Children).
 1308objects_nav_tree(Objects, Tree) :-
 1309    maplist(object_nav_tree, Objects, Trees),
 1310    union_trees(Trees, Tree0),
 1311    remove_unique_root(Tree0, Tree).
 1312
 1313object_nav_tree(Obj, Tree) :-
 1314    Node = node(directory(Dir), FileNodes),
 1315    FileNode = node(file(File), Siblings),
 1316    doc_comment(Obj, File:_Line, _Summary, _Comment),
 1317    !,
 1318    file_directory_name(File, Dir),
 1319    sibling_file_nodes(Dir, FileNodes0),
 1320    selectchk(node(file(File),[]), FileNodes0, FileNode, FileNodes),
 1321    findall(Sibling, doc_comment(Sibling, File:_, _, _), Siblings0),
 1322    delete(Siblings0, _:module(_), Siblings1),
 1323    doc_hide_private(Siblings1, Siblings2, []),
 1324    flatten(Siblings2, Siblings),   % a comment may describe objects
 1325    embed_directories(Node, Tree).
 1326
 1327sibling_file_nodes(Dir, Nodes) :-
 1328    findall(node(file(File), []),
 1329	    (   source_file(File),
 1330		file_directory_name(File, Dir)
 1331	    ),
 1332	    Nodes).
 1333
 1334embed_directories(Node, Tree) :-
 1335    Node = node(file(File), _),
 1336    !,
 1337    file_directory_name(File, Dir),
 1338    Super = node(directory(Dir), [Node]),
 1339    embed_directories(Super, Tree).
 1340embed_directories(Node, Tree) :-
 1341    Node = node(directory(Dir), _),
 1342    file_directory_name(Dir, SuperDir),
 1343    SuperDir \== Dir,
 1344    !,
 1345    Super = node(directory(SuperDir), [Node]),
 1346    embed_directories(Super, Tree).
 1347embed_directories(Tree, Tree).
 1348
 1349
 1350union_trees([Tree], Tree) :- !.
 1351union_trees([T1,T2|Trees], Tree) :-
 1352    merge_trees(T1, T2, M1),
 1353    union_trees([M1|Trees], Tree).
 1354
 1355merge_trees(node(R, Ch1), node(R, Ch2), node(R, Ch)) :-
 1356    merge_nodes(Ch1, Ch2, Ch).
 1357
 1358merge_nodes([], Ch, Ch) :- !.
 1359merge_nodes(Ch, [], Ch) :- !.
 1360merge_nodes([node(Root, Ch1)|T1], N1, [T1|Nodes]) :-
 1361    selectchk(node(Root, Ch2), N1, N2),
 1362    !,
 1363    merge_trees(node(Root, Ch1), node(Root, Ch2), T1),
 1364    merge_nodes(T1, N2, Nodes).
 1365merge_nodes([Node|T1], N1, [Node|Nodes]) :-
 1366    merge_nodes(T1, N1, Nodes).
 remove_unique_root(+TreeIn, -Tree)
Remove the root part that does not branch
 1372remove_unique_root(node(_, [node(R1, [R2])]), Tree) :-
 1373    !,
 1374    remove_unique_root(node(R1, [R2]), Tree).
 1375remove_unique_root(Tree, Tree).
 nav_tree(+Tree, +Current, +Options)// is det
Render the navigation tree
 1381nav_tree(Tree, Current, Options) -->
 1382    html(ul(class(nav),
 1383	    \object_tree(Tree, Current, Options))).
 object_tree(+Tree, +Current, +Options)// is det
Render a tree of objects used for navigation.
 1389object_tree(node(Id, []), Target, Options) -->
 1390    !,
 1391    { node_class(Id, Target, Class) },
 1392    html(li(class(Class),
 1393	    \node(Id, Options))).
 1394object_tree(node(Id, Children), Target, Options) -->
 1395    !,
 1396    { node_class(Id, Target, Class) },
 1397    html(li(class(Class),
 1398	    [ \node(Id, Options),
 1399	      ul(class(nav),
 1400		 \object_trees(Children, Target, Options))
 1401	    ])).
 1402object_tree(Id, Target, Options) -->
 1403    !,
 1404    { node_class(Id, Target, Class) },
 1405    html(li(class([obj|Class]), \node(Id, Options))).
 1406
 1407object_trees([], _, _) --> [].
 1408object_trees([H|T], Target, Options) -->
 1409    object_tree(H, Target, Options),
 1410    object_trees(T, Target, Options).
 1411
 1412node_class(Ids, Current, Class) :-
 1413    is_list(Ids),
 1414    !,
 1415    (   member(Id, Ids), memberchk(Id, Current)
 1416    ->  Class = [nav,current]
 1417    ;   Class = [nav]
 1418    ).
 1419node_class(Id, Current, Class) :-
 1420    (   memberchk(Id, Current)
 1421    ->  Class = [nav,current]
 1422    ;   Class = [nav]
 1423    ).
 1424
 1425node(file(File), Options) -->
 1426    !,
 1427    object_ref(file(File), [style(title)|Options]).
 1428node(Id, Options) -->
 1429    object_ref(Id, Options).
 1430
 1431
 1432		 /*******************************
 1433		 *            SECTIONS          *
 1434		 *******************************/
 1435
 1436section(Type, Title) -->
 1437    { string_codes(Title, Codes),
 1438      wiki_codes_to_dom(Codes, [], Content0),
 1439      strip_leading_par(Content0, Content),
 1440      make_section(Type, Content, HTML)
 1441    },
 1442    html(HTML).
 1443
 1444make_section(module,  Title, h1(class=module,  Title)).
 1445make_section(section, Title, h1(class=section, Title)).
 1446
 1447
 1448		 /*******************************
 1449		 *       PRED MODE HEADER       *
 1450		 *******************************/
 pred_dt(+Modes, +Class, Options)// is det
Emit the predicate header.
Arguments:
Modes- List as returned by process_modes/5.
 1458pred_dt(Modes, Class, Options) -->
 1459    pred_dt(Modes, Class, [], _Done, Options).
 1460
 1461pred_dt([], _, Done, Done, _) -->
 1462    [].
 1463pred_dt([H|T], Class, Done0, Done, Options) -->
 1464    { functor(Class, CSSClass, _) },
 1465    html(dt(class=CSSClass,
 1466	    [ \pred_mode(H, Done0, Done1, Options),
 1467	      \mode_anot(Class)
 1468	    ])),
 1469    pred_dt(T, Class, Done1, Done, Options).
 1470
 1471mode_anot(privdef) -->
 1472    !,
 1473    html(span([class(anot), style('float:right')],
 1474	      '[private]')).
 1475mode_anot(multidef(object(Obj))) -->
 1476    !,
 1477    { object_href(Obj, HREF) },
 1478    html(span([class(anot), style('float:right')],
 1479	      ['[', a(href(HREF), multifile), ']'
 1480	      ])).
 1481mode_anot(multidef(file(File:_))) -->
 1482    !,
 1483    { file_name_on_path(File, Spec),
 1484      unquote_filespec(Spec, Unquoted),
 1485      doc_file_href(File, HREF)
 1486    },
 1487    html(span([class(anot), style('float:right')],
 1488	      ['[multifile, ', a(href(HREF), '~q'-[Unquoted]), ']'
 1489	      ])).
 1490mode_anot(multidef) -->
 1491    !,
 1492    html(span([class(anot), style('float:right')],
 1493	      '[multifile]')).
 1494mode_anot(_) -->
 1495    [].
 1496
 1497pred_mode(mode(Head,Vars), Done0, Done, Options) -->
 1498    !,
 1499    { bind_vars(Head, Vars) },
 1500    pred_mode(Head, Done0, Done, Options).
 1501pred_mode(Head is Det, Done0, Done, Options) -->
 1502    !,
 1503    anchored_pred_head(Head, Done0, Done, Options),
 1504    pred_det(Det).
 1505pred_mode(Head, Done0, Done, Options) -->
 1506    anchored_pred_head(Head, Done0, Done, Options).
 1507
 1508bind_vars(Term, Bindings) :-
 1509    bind_vars(Bindings),
 1510    anon_vars(Term).
 1511
 1512bind_vars([]).
 1513bind_vars([Name=Var|T]) :-
 1514    Var = '$VAR'(Name),
 1515    bind_vars(T).
 anon_vars(+Term) is det
Bind remaining variables in Term to '$VAR'('_'), so they are printed as '_'.
 1522anon_vars(Var) :-
 1523    var(Var),
 1524    !,
 1525    Var = '$VAR'('_').
 1526anon_vars(Term) :-
 1527    compound(Term),
 1528    !,
 1529    Term =.. [_|Args],
 1530    maplist(anon_vars, Args).
 1531anon_vars(_).
 1532
 1533
 1534anchored_pred_head(Head, Done0, Done, Options) -->
 1535    { pred_anchor_name(Head, PI, Name) },
 1536    (   { memberchk(PI, Done0) }
 1537    ->  { Done = Done0 },
 1538	pred_head(Head)
 1539    ;   html([ span(style('float:right'),
 1540		    [ \pred_edit_or_source_button(Head, Options),
 1541		      &(nbsp)
 1542		    ]),
 1543	       a(name=Name, \pred_head(Head))
 1544	     ]),
 1545	{ Done = [PI|Done0] }
 1546    ).
 1547
 1548
 1549pred_edit_or_source_button(Head, Options) -->
 1550    { option(edit(true), Options) },
 1551    !,
 1552    pred_edit_button(Head, Options).
 1553pred_edit_or_source_button(Head, Options) -->
 1554    { option(source_link(true), Options) },
 1555    !,
 1556    pred_source_button(Head, Options).
 1557pred_edit_or_source_button(_, _) --> [].
 pred_edit_button(+PredIndicator, +Options)// is det
Create a button for editing the given predicate. Options processed:
module(M)
Resolve to module M
file(F)
For multi-file predicates: link to version in file.
line(L)
Line to edit (in file)
 1571pred_edit_button(_, Options) -->
 1572    { \+ option(edit(true), Options) },
 1573    !.
 1574pred_edit_button(PI0, Options0) -->
 1575    { canonicalise_predref(PI0, PI, Options0, Options) },
 1576    pred_edit_button2(PI, Options).
 1577
 1578pred_edit_button2(Name/Arity, Options) -->
 1579    { \+ ( memberchk(file(_), Options), % always edit if file and line
 1580	   memberchk(line(_), Options)  % are given.
 1581	 ),
 1582      functor(Head, Name, Arity),
 1583      option(module(M), Options, _),
 1584      \+ ( current_module(M),
 1585	   source_file(M:Head, _File)
 1586	 )
 1587    },
 1588    !.
 1589pred_edit_button2(Name/Arity, Options) -->
 1590    { include(edit_param, Options, Extra),
 1591      http_link_to_id(pldoc_edit,
 1592		      [name(Name),arity(Arity)|Extra],
 1593		      EditHREF)
 1594    },
 1595    html(a(onClick('HTTPrequest(\'' + EditHREF + '\')'),
 1596	   img([ class(action),
 1597		 alt('Edit predicate'),
 1598		 title('Edit predicate'),
 1599		 src(location_by_id(pldoc_resource)+'editpred.png')
 1600	       ]))).
 1601pred_edit_button2(_, _) -->
 1602    !,
 1603    [].
 1604
 1605edit_param(module(_)).
 1606edit_param(file(_)).
 1607edit_param(line(_)).
 object_edit_button(+Object, +Options)// is det
Create a button for editing Object.
 1614object_edit_button(_, Options) -->
 1615    { \+ option(edit(true), Options) },
 1616    !.
 1617object_edit_button(PI, Options) -->
 1618    { is_pi(PI) },
 1619    !,
 1620    pred_edit_button(PI, Options).
 1621object_edit_button(_, _) -->
 1622    [].
 pred_source_button(+PredIndicator, +Options)// is det
Create a button for viewing the source of a predicate.
 1629pred_source_button(PI0, Options0) -->
 1630    { canonicalise_predref(PI0, PI, Options0, Options),
 1631      option(module(M), Options, _),
 1632      pred_source_href(PI, M, HREF), !
 1633    },
 1634    html(a([ href(HREF)
 1635	   ],
 1636	   img([ class(action),
 1637		 alt('Source'),
 1638		 title('Show source'),
 1639		 src(location_by_id(pldoc_resource)+'source.png')
 1640	       ]))).
 1641pred_source_button(_, _) -->
 1642    [].
 object_source_button(+Object, +Options)// is det
Create a button for showing the source of Object.
 1649object_source_button(PI, Options) -->
 1650    { is_pi(PI),
 1651      option(source_link(true), Options, true)
 1652    },
 1653    !,
 1654    pred_source_button(PI, Options).
 1655object_source_button(_, _) -->
 1656    [].
 canonicalise_predref(+PredRef, -PI:Name/Arity, +Options0, -Options) is det
Canonicalise a predicate reference. A possible module qualifier is added as module(M) to Options.
 1664canonicalise_predref(M:PI0, PI, Options0, [module(M)|Options]) :-
 1665    !,
 1666    canonicalise_predref(PI0, PI, Options0, Options).
 1667canonicalise_predref(//(Head), PI, Options0, Options) :-
 1668    !,
 1669    functor(Head, Name, Arity),
 1670    PredArity is Arity + 2,
 1671    canonicalise_predref(Name/PredArity, PI, Options0, Options).
 1672canonicalise_predref(Name//Arity, PI, Options0, Options) :-
 1673    integer(Arity), Arity >= 0,
 1674    !,
 1675    PredArity is Arity + 2,
 1676    canonicalise_predref(Name/PredArity, PI, Options0, Options).
 1677canonicalise_predref(PI, PI, Options, Options) :-
 1678    PI = Name/Arity,
 1679    atom(Name), integer(Arity), Arity >= 0,
 1680    !.
 1681canonicalise_predref(Head, PI, Options0, Options) :-
 1682    functor(Head, Name, Arity),
 1683    canonicalise_predref(Name/Arity, PI, Options0, Options).
 pred_head(+Term) is det
Emit a predicate head. The functor is typeset as a span using class pred and the arguments and var using class arglist.
 1691pred_head(Var) -->
 1692    { var(Var),
 1693      !,
 1694      instantiation_error(Var)
 1695    }.
 1696pred_head(//(Head)) -->
 1697    !,
 1698    pred_head(Head),
 1699    html(//).
 1700pred_head(M:Head) -->
 1701    html([span(class=module, M), :]),
 1702    pred_head(Head).
 1703pred_head(Head) -->
 1704    { atom(Head) },
 1705    !,
 1706    html(b(class=pred, Head)).
 1707pred_head(Head) -->                     % Infix operators
 1708    { Head =.. [Functor,Left,Right],
 1709      is_op_type(Functor, infix)
 1710    },
 1711    !,
 1712    html([ var(class=arglist, \pred_arg(Left, 1)),
 1713	   ' ', b(class=pred, Functor), ' ',
 1714	   var(class=arglist, \pred_arg(Right, 2))
 1715	 ]).
 1716pred_head(Head) -->                     % Prefix operators
 1717    { Head =.. [Functor,Arg],
 1718      is_op_type(Functor, prefix)
 1719    },
 1720    !,
 1721    html([ b(class=pred, Functor), ' ',
 1722	   var(class=arglist, \pred_arg(Arg, 1))
 1723	 ]).
 1724pred_head(Head) -->                     % Postfix operators
 1725    { Head =.. [Functor,Arg],
 1726      is_op_type(Functor, postfix)
 1727    },
 1728    !,
 1729    html([ var(class=arglist, \pred_arg(Arg, 1)),
 1730	   ' ', b(class=pred, Functor)
 1731	 ]).
 1732pred_head({Head}) -->
 1733    !,
 1734    html([ b(class=pred, '{'),
 1735	   var(class=arglist,
 1736	       \pred_args([Head], 1)),
 1737	   b(class=pred, '}')
 1738	 ]).
 1739pred_head(Head) -->                     % Plain terms
 1740    { Head =.. [Functor|Args] },
 1741    html([ b(class=pred, Functor),
 1742	   var(class=arglist,
 1743	       [ '(', \pred_args(Args, 1), ')' ])
 1744	 ]).
 is_op_type(+Atom, ?Type)
True if Atom is an operator of Type. Type is one of prefix, infix or postfix.
 1751is_op_type(Functor, Type) :-
 1752    current_op(_Pri, F, Functor),
 1753    op_type(F, Type).
 1754
 1755op_type(fx,  prefix).
 1756op_type(fy,  prefix).
 1757op_type(xf,  postfix).
 1758op_type(yf,  postfix).
 1759op_type(xfx, infix).
 1760op_type(xfy, infix).
 1761op_type(yfx, infix).
 1762op_type(yfy, infix).
 1763
 1764
 1765pred_args([], _) -->
 1766    [].
 1767pred_args([H|T], I) -->
 1768    pred_arg(H, I),
 1769    (   {T==[]}
 1770    ->  []
 1771    ;   html(', '),
 1772	{ I2 is I + 1 },
 1773	pred_args(T, I2)
 1774    ).
 1775
 1776pred_arg(Var, I) -->
 1777    { var(Var) },
 1778    !,
 1779    html(['Arg', I]).
 1780pred_arg(...(Term), I) -->
 1781    !,
 1782    pred_arg(Term, I),
 1783    html('...').
 1784pred_arg(Term, I) -->
 1785    { Term =.. [Ind,Arg],
 1786      mode_indicator(Ind)
 1787    },
 1788    !,
 1789    html([Ind, \pred_arg(Arg, I)]).
 1790pred_arg(Arg:Type, _) -->
 1791    !,
 1792    html([\argname(Arg), :, \argtype(Type)]).
 1793pred_arg(Arg, _) -->
 1794    argname(Arg).
 1795
 1796argname('$VAR'(Name)) -->
 1797    !,
 1798    html(Name).
 1799argname(Name) -->
 1800    !,
 1801    html(Name).
 1802
 1803argtype(Term) -->
 1804    { format(string(S), '~W',
 1805	     [ Term,
 1806	       [ quoted(true),
 1807		 numbervars(true)
 1808	       ]
 1809	     ]) },
 1810    html(S).
 1811
 1812pred_det(unknown) -->
 1813    [].
 1814pred_det(Det) -->
 1815    html([' is ', b(class=det, Det)]).
 term(+Text, +Term, +Bindings)// is det
Process the \term element as produced by doc_wiki.pl.
To be done
- Properly merge with pred_head//1
 1824term(_, Atom, []) -->
 1825    { atomic(Atom),
 1826      !,
 1827      format(string(S), '~W', [Atom,[quoted(true)]])
 1828    },
 1829    html(span(class=functor, S)).
 1830term(_, Key:Type, [TypeName=Type]) -->
 1831    { atomic(Key)
 1832    },
 1833    !,
 1834    html([span(class='pl-key', Key), :, span(class('pl-var'), TypeName)]).
 1835term(_, Term, Bindings) -->
 1836    { is_mode(Term is det),         % HACK. Bit too strict?
 1837      bind_vars(Bindings)
 1838    },
 1839    !,
 1840    pred_head(Term).
 1841term(_, Term, Bindings) -->
 1842    term(Term,
 1843	 [ variable_names(Bindings),
 1844	   quoued(true)
 1845	 ]).
 1846
 1847
 1848		 /*******************************
 1849		 *             PREDREF          *
 1850		 *******************************/
 predref(+PI)// is det
 predref(+PI, +Options)// is det
Create a reference to a predicate. The reference consists of the relative path to the file using the predicate indicator as anchor.

Current file must be available through the global variable pldoc_file. If this variable not set it creates a link to /doc/<file>#anchor. Such links only work in the online browser.

 1863predref(Term) -->
 1864    { catch(nb_getval(pldoc_options, Options), _, Options = []) },
 1865    predref(Term, Options).
 1866
 1867predref(Obj, Options) -->
 1868    { Obj = _:_,
 1869      doc_comment(Obj, File:_Line, _, _),
 1870      (   (   option(files(Map), Options)
 1871	  ->  memberchk(file(File,_), Map)
 1872	  ;   true
 1873	  )
 1874      ->  object_href(Obj, HREF, Options)
 1875      ;   manref(Obj, HREF, Options)
 1876      )
 1877    },
 1878    !,
 1879    html(a(href(HREF), \object_name(Obj, [qualify(true)|Options]))).
 1880predref(M:Term, Options) -->
 1881    !,
 1882    predref(Term, M, Options).
 1883predref(Term, Options) -->
 1884    predref(Term, _, Options).
 1885
 1886predref(Name/Arity, _, Options) -->             % Builtin; cannot be overruled
 1887    { prolog:doc_object_summary(Name/Arity, manual, _, _),
 1888      !,
 1889      manref(Name/Arity, HREF, Options)
 1890    },
 1891    html(a([class=builtin, href=HREF], [Name, /, Arity])).
 1892predref(Name/Arity, _, Options) -->             % From packages
 1893    { option(prefer(manual), Options),
 1894      prolog:doc_object_summary(Name/Arity, Category, _, _),
 1895      !,
 1896      manref(Name/Arity, HREF, Options)
 1897    },
 1898    html(a([class=Category, href=HREF], [Name, /, Arity])).
 1899predref(Obj, Module, Options) -->               % Local
 1900    { doc_comment(Module:Obj, File:_Line, _, _),
 1901      (   option(files(Map), Options)
 1902      ->  memberchk(file(File,_), Map)
 1903      ;   true
 1904      )
 1905    },
 1906    !,
 1907    object_ref(Module:Obj, Options).
 1908predref(Name/Arity, Module, Options) -->
 1909    { \+ option(files(_), Options),
 1910      pred_href(Name/Arity, Module, HREF)
 1911    },
 1912    !,
 1913    html(a(href=HREF, [Name, /, Arity])).
 1914predref(Name//Arity, Module, Options) -->
 1915    { \+ option(files(_), Options),
 1916      PredArity is Arity + 2,
 1917      pred_href(Name/PredArity, Module, HREF)
 1918    },
 1919    !,
 1920    html(a(href=HREF, [Name, //, Arity])).
 1921predref(PI, _, Options) -->             % From packages
 1922    { canonical_pi(PI, CPI, HTML),
 1923      (   option(files(_), Options)
 1924      ->  Category = extmanual
 1925      ;   prolog:doc_object_summary(CPI, Category, _, _)
 1926      ),
 1927      manref(CPI, HREF, Options)
 1928    },
 1929    html(a([class=Category, href=HREF], HTML)).
 1930predref(PI, _, _Options) -->
 1931    { canonical_pi(PI, _CPI, HTML)
 1932    },
 1933    !,
 1934    html(span(class=undef, HTML)).
 1935predref(Callable, Module, Options) -->
 1936    { callable(Callable),
 1937      functor(Callable, Name, Arity)
 1938    },
 1939    predref(Name/Arity, Module, Options).
 1940
 1941canonical_pi(Name/Arity, Name/Arity, [Name, /, Arity]) :-
 1942    atom(Name), integer(Arity),
 1943    !.
 1944canonical_pi(Name//Arity, Name/Arity2, [Name, //, Arity]) :-
 1945    atom(Name), integer(Arity),
 1946    !,
 1947    Arity2 is Arity+2.
 nopredref(+PI)//
Result of name/arity, non-linking predicate indicator.
 1953nopredref(PI) -->
 1954    { canonical_pi(PI, _CPI, HTML)
 1955    },
 1956    !,
 1957    html(span(class=nopredref, HTML)).
 flagref(+Flag)//
Reference to a Prolog flag.
To be done
- generate a link to the Prolog website?
 1965flagref(Flag) -->
 1966    html(code(Flag)).
 cite(+Citations)// is det
Emit citations. This is indented to allow for [@cite1;@cite2] for generating LaTex.
 1973cite(Citations) -->
 1974    html('['), citations(Citations), html(']').
 1975
 1976citations([]) --> [].
 1977citations([H|T]) -->
 1978    citation(H),
 1979    (   {T==[]}
 1980    ->  []
 1981    ;   [';'],
 1982	citations(T)
 1983    ).
 1984
 1985citation(H) -->
 1986    html([@,H]).
 manref(+NameArity, -HREF, +Options) is det
Create reference to a manual page. When generating files, this listens to the option man_server(+Server).
 1994manref(PI, HREF, Options) :-
 1995    predname(PI, PredName),
 1996    (   option(files(_Map), Options)
 1997    ->  option(man_server(Server), Options,
 1998	       'http://www.swi-prolog.org/pldoc'),
 1999	uri_components(Server, Comp0),
 2000	uri_data(path, Comp0, Path0),
 2001	directory_file_path(Path0, man, Path),
 2002	uri_data(path, Comp0, Path, Components),
 2003	uri_query_components(Query, [predicate=PredName]),
 2004	uri_data(search, Components, Query),
 2005	uri_components(HREF, Components)
 2006    ;   http_link_to_id(pldoc_man, [predicate=PredName], HREF)
 2007    ).
 2008
 2009predname(Name/Arity, PredName) :-
 2010    !,
 2011    format(atom(PredName), '~w/~d', [Name, Arity]).
 2012predname(Module:Name/Arity, PredName) :-
 2013    !,
 2014    format(atom(PredName), '~w:~w/~d', [Module, Name, Arity]).
 pred_href(+NameArity, +Module, -HREF) is semidet
Create reference. Prefer:
  1. Local definition
  2. If from package and documented: package documentation
  3. From any file
bug
- Should analyse import list to find where the predicate comes from.
 2028pred_href(Name/Arity, Module, HREF) :-
 2029    format(string(FragmentId), '~w/~d', [Name, Arity]),
 2030    uri_data(fragment, Components, FragmentId),
 2031    functor(Head, Name, Arity),
 2032    (   catch(relative_file(Module:Head, File), _, fail)
 2033    ->  uri_data(path, Components, File),
 2034	uri_components(HREF, Components)
 2035    ;   in_file(Module:Head, File)
 2036    ->  (   current_prolog_flag(home, SWI),
 2037	    sub_atom(File, 0, _, _, SWI),
 2038	    prolog:doc_object_summary(Name/Arity, packages, _, _)
 2039	->  http_link_to_id(pldoc_man, [predicate=FragmentId], HREF)
 2040	;   http_location_by_id(pldoc_doc, DocHandler),
 2041	    atom_concat(DocHandler, File, Path),
 2042	    uri_data(path, Components, Path),
 2043	    uri_components(HREF, Components)
 2044	)
 2045    ).
 2046
 2047relative_file(Head, '') :-
 2048    b_getval(pldoc_file, CurrentFile), CurrentFile \== [],
 2049    in_file(Head, CurrentFile),
 2050    !.
 2051relative_file(Head, RelFile) :-
 2052    b_getval(pldoc_file, CurrentFile), CurrentFile \== [],
 2053    in_file(Head, DefFile),
 2054    relative_file_name(DefFile, CurrentFile, RelFile).
 pred_source_href(+Pred:predicate_indicator, +Module, -HREF) is semidet
HREF is a URL to show the predicate source in its file.
 2060pred_source_href(Name/Arity, Module, HREF) :-
 2061    format(string(FragmentId), '~w/~d', [Name, Arity]),
 2062    uri_data(fragment, Components, FragmentId),
 2063    uri_query_components(Query, [show=src]),
 2064    uri_data(search, Components, Query),
 2065    functor(Head, Name, Arity),
 2066    (   catch(relative_file(Module:Head, File), _, fail)
 2067    ->  uri_data(path, Components, File),
 2068	uri_components(HREF, Components)
 2069    ;   in_file(Module:Head, File0)
 2070    ->  insert_alias(File0, File),
 2071	http_location_by_id(pldoc_doc, DocHandler),
 2072	atom_concat(DocHandler, File, Path),
 2073	uri_data(path, Components, Path),
 2074	uri_components(HREF, Components)
 2075    ).
 object_ref(+Object, +Options)// is det
Create a hyperlink to Object. Points to the /doc_for URL. Object is as the first argument of doc_comment/4. Note this can be a list of objects.
 2084object_ref([], _) -->
 2085    !,
 2086    [].
 2087object_ref([H|T], Options) -->
 2088    !,
 2089    object_ref(H, Options),
 2090    (   {T == []}
 2091    ->  html(', '),
 2092	object_ref(T, Options)
 2093    ;   []
 2094    ).
 2095object_ref(Obj, Options) -->
 2096    { object_href(Obj, HREF, Options)
 2097    },
 2098    html(a(href(HREF), \object_name(Obj, Options))).
 object_href(+Object, -HREF) is det
 object_href(+Object, -HREF, +Options) is det
HREF is the URL to access Object.
 2105object_href(Obj, HREF) :-
 2106    object_href(Obj, HREF, []).
 2107
 2108object_href(M:PI0, HREF, Options) :-
 2109    option(files(Map), Options),
 2110    (   module_property(M, file(File))
 2111    ->  true
 2112    ;   xref_module(File, M)
 2113    ),
 2114    memberchk(file(File, DocFile), Map),
 2115    !,
 2116    file_base_name(DocFile, LocalFile),     % TBD: proper directory index
 2117    expand_pi(PI0, PI),
 2118    term_to_string(PI, PIS),
 2119    uri_data(path, Components, LocalFile),
 2120    uri_data(fragment, Components, PIS),
 2121    uri_components(HREF, Components).
 2122object_href(file(File), HREF, _Options) :-
 2123    doc_file_href(File, HREF),
 2124    !.
 2125object_href(directory(Dir), HREF, _Options) :-
 2126    directory_file_path(Dir, 'index.html', Index),
 2127    doc_file_href(Index, HREF),
 2128    !.
 2129object_href(Obj, HREF, _Options) :-
 2130    prolog:doc_object_href(Obj, HREF),
 2131    !.
 2132object_href(Obj0, HREF, _Options) :-
 2133    localise_object(Obj0, Obj),
 2134    term_to_string(Obj, String),
 2135    http_link_to_id(pldoc_object, [object=String], HREF).
 2136
 2137expand_pi(Name//Arity0, Name/Arity) :-
 2138    !,
 2139    Arity is Arity0+2.
 2140expand_pi(PI, PI).
 localise_object(+ObjIn, -ObjOut) is det
Abstract path-details to make references more stable over versions.
 2148localise_object(Obj0, Obj) :-
 2149    prolog:doc_canonical_object(Obj0, Obj),
 2150    !.
 2151localise_object(Obj, Obj).
 term_to_string(+Term, -String) is det
Convert Term, possibly holding variables, into a canonical string using A, B, ... for variables and _ for singletons.
 2159term_to_string(Term, String) :-
 2160    State = state(-),
 2161    (   numbervars(Term, 0, _, [singletons(true)]),
 2162	with_output_to(string(String),
 2163		       write_term(Term,
 2164				  [ numbervars(true),
 2165				    quoted(true)
 2166				  ])),
 2167	nb_setarg(1, State, String),
 2168	fail
 2169    ;   arg(1, State, String)
 2170    ).
 object_name(+Obj, +Options)// is det
HTML description of documented Obj. Obj is as the first argument of doc_comment/4. Options:
style(+Style)
One of inline or title
qualify(+Boolean)
Qualify predicates by their module
secref_style(Style)
One of number, title or number_title
 2184object_name(Obj, Options) -->
 2185    { option(style(Style), Options, inline)
 2186    },
 2187    object_name(Style, Obj, Options).
 2188
 2189object_name(title, Obj, Options) -->
 2190    { merge_options(Options, [secref_style(title)], Options1) },
 2191    prolog:doc_object_link(Obj, Options1),
 2192    !.
 2193object_name(inline, Obj, Options) -->
 2194    prolog:doc_object_link(Obj, Options),
 2195    !.
 2196object_name(title, f(Name/Arity), _Options) -->
 2197    !,
 2198    html(['Function ', Name, /, Arity]).
 2199object_name(inline, f(Name/Arity), _Options) -->
 2200    !,
 2201    html([Name, /, Arity]).
 2202object_name(Style, PI, Options) -->
 2203    { is_pi(PI) },
 2204    !,
 2205    pi(Style, PI, Options).
 2206object_name(inline, Module:module(_Title), _) -->
 2207    !,
 2208    { module_property(Module, file(File)),
 2209      file_base_name(File, Base)
 2210    },
 2211    !,
 2212    html(Base).
 2213object_name(title, Module:module(Title), _) -->
 2214    { module_property(Module, file(File)),
 2215      file_base_name(File, Base)
 2216    },
 2217    !,
 2218    html([Base, ' -- ', Title]).
 2219object_name(title, file(File), _) -->
 2220    { module_property(Module, file(File)),
 2221      doc_comment(Module:module(Title), _, _, _),
 2222      !,
 2223      file_base_name(File, Base)
 2224    },
 2225    html([Base, ' -- ', Title]).
 2226object_name(_, file(File), _) -->
 2227    { file_base_name(File, Base) },
 2228    html(Base).
 2229object_name(_, directory(Dir), _) -->
 2230    { file_base_name(Dir, Base) },
 2231    html(Base).
 2232object_name(_, module(Title), _Options) -->
 2233    { print_message(warning,
 2234		    pldoc(module_comment_outside_module(Title)))
 2235    }.
 2236
 2237pi(title, PI, Options) -->
 2238    pi_type(PI),
 2239    pi(PI, Options).
 2240pi(inline, PI, Options) -->
 2241    pi(PI, Options).
 2242
 2243pi(M:PI, Options) -->
 2244    !,
 2245    (   { option(qualify(true), Options) }
 2246    ->  html([span(class(module), M), :])
 2247    ;   []
 2248    ),
 2249    pi(PI, Options).
 2250pi(Name/Arity, _) -->
 2251    !,
 2252    html([Name, /, Arity]).
 2253pi(Name//Arity, _) -->
 2254    html([Name, //, Arity]).
 2255
 2256pi_type(_:PI) -->
 2257    !,
 2258    pi_type(PI).
 2259pi_type(_/_) -->
 2260    html(['Predicate ']).
 2261pi_type(_//_) -->
 2262    html(['Grammar rule ']).
 in_file(+Head, ?File) is nondet
File is the name of a file containing the Predicate Head. Head may be qualified with a module.
To be done
- Prefer local, then imported, then `just anywhere'
- Look for documented and/or public predicates.
 2274in_file(Module:Head, File) :-
 2275    !,
 2276    distinct(File, in_file(Module, Head, File)).
 2277in_file(Head, File) :-
 2278    distinct(File, in_file(_, Head, File)).
 2279
 2280in_file(Module, Head, File) :-
 2281    var(Module),
 2282    (   predicate_property(system:Head, foreign)
 2283    ->  !,
 2284	fail
 2285    ;   predicate_property(system:Head, file(File)),
 2286	\+ system_arithmetic_function(Head)
 2287    ->  !
 2288    ;   predicate_property(Head, autoload(File0))
 2289    ->  !,
 2290	file_name_extension(File0, pl, File)
 2291    ;   exported_from(Module, Head, File),
 2292	module_property(Module, class(library))
 2293    ).
 2294in_file(Module, Head, File) :-
 2295    nonvar(Module),
 2296    predicate_property(Module:Head, file(File)),
 2297    \+ predicate_property(Module:Head, imported_from(_)).
 2298in_file(Module, Head, File) :-
 2299    xref_defined(File, Head, How),
 2300    xref_current_source(File),
 2301    atom(File),                     % only plain files
 2302    xref_module(File, Module),
 2303    How \= imported(_From).
 2304in_file(Module, Head, File) :-
 2305    exported_from(Module, Head, File).
 2306in_file(Module, Head, File) :-
 2307    predicate_property(Module:Head, file(File)),
 2308    \+ predicate_property(Module:Head, imported_from(_)).
 2309in_file(Module, Head, File) :-
 2310    current_module(Module),
 2311    source_file(Module:Head, File).
 2312
 2313exported_from(Module, Head, File) :-
 2314    distinct(Primary,
 2315	     (   predicate_property(Module:Head, exported),
 2316		 (   predicate_property(Module:Head, imported_from(Primary))
 2317		 ->  true
 2318		 ;   Primary = Module
 2319		 ))),
 2320    module_property(Primary, file(File)).
 2321
 2322:- multifile
 2323    arithmetic:evaluable/2. 2324
 2325system_arithmetic_function(Head) :-
 2326    functor(Head, Name, Arity),
 2327    FArith is Arity-1,
 2328    FArith >= 0,
 2329    functor(FHead, Name, FArith),
 2330    arithmetic:evaluable(FHead, system).
 file(+FileName)// is det
 file(+FileName, +Options)// is det
Create a link to another filename if the file exists. Called by \file(File) terms in the DOM term generated by wiki.pl. Supported options are:
label(+Label)
Label to use for the link to the file.
absolute_path(+Path)
Absolute location of the referenced file.
href(+HREF)
Explicitely provided link; overrule link computation.
map_extension(+Pairs)
Map the final extension if OldExt-NewExt is in Pairs.
files(+Map)
List of file(Name, Link) that specifies that we must user Link for the given physical file Name.
edit_handler(+Id)
HTTP handler Id to call if the user clicks the edit button.
To be done
- Translation of files to HREFS is a mess. How to relate these elegantly?
 2361file(File) -->
 2362    file(File, []).
 2363
 2364file(File, Options) -->
 2365    { catch(nb_getval(pldoc_options, GenOptions), _, GenOptions = []),
 2366      merge_options(Options, GenOptions, FinalOptions)
 2367    },
 2368    link_file(File, FinalOptions),
 2369    !.
 2370file(File, Options) -->
 2371    { option(edit_handler(Handler), Options),
 2372      http_current_request(Request),
 2373      memberchk(path(Path), Request),
 2374      absolute_file_name(File, Location,
 2375			 [ relative_to(Path)
 2376			 ]),
 2377      http_link_to_id(Handler, [location(Location)], HREF),
 2378      format(atom(Title), 'Click to create ~w', [File])
 2379    },
 2380    html(a([href(HREF), class(nofile), title(Title)], File)).
 2381file(File, _) -->
 2382    html(code(class(nofile), File)).
 2383
 2384link_file(File, Options) -->
 2385    { file_href(File, HREF, Options),
 2386      option(label(Label), Options, File),
 2387      option(class(Class), Options, file)
 2388    },
 2389    html(a([class(Class), href(HREF)], Label)).
 file_href(+FilePath, -HREF, +Options) is det
Find URL for refering to FilePath based on Options.
 2395file_href(_, HREF, Options) :-
 2396    option(href(HREF), Options),
 2397    !.
 2398file_href(File, HREF, Options) :-
 2399    file_href_real(File, HREF0, Options),
 2400    map_extension(HREF0, HREF, Options).
 map_extension(+HREFIn, -HREFOut, Options) is det
Replace extension using the option
 2408map_extension(HREF0, HREF, Options) :-
 2409    option(map_extension(Map), Options),
 2410    file_name_extension(Base, Old, HREF0),
 2411    memberchk(Old-New, Map),
 2412    !,
 2413    file_name_extension(Base, New, HREF).
 2414map_extension(HREF, HREF, _).
 2415
 2416
 2417file_href_real(File, HREF, Options) :-
 2418    (   option(absolute_path(Path), Options)
 2419    ;   existing_linked_file(File, Path)
 2420    ),
 2421    !,
 2422    (   option(files(Map), Options),
 2423	memberchk(file(Path, LinkFile), Map)
 2424    ->  true
 2425    ;   LinkFile = Path
 2426    ),
 2427    file_href(LinkFile, HREF).
 2428file_href_real(File, HREF, _) :-
 2429    directory_alias(Alias),
 2430    Term =.. [Alias,File],
 2431    absolute_file_name(Term, _,
 2432		       [ access(read),
 2433			 file_errors(fail)
 2434		       ]),
 2435    !,
 2436    http_absolute_location(Term, HREF, []).
 2437
 2438directory_alias(icons).
 2439directory_alias(css).
 file_href(+FilePath, -HREF) is det
Create a relative URL from the current location to the given absolute file name. It resolves the filename relative to the file being processed that is available through the global variable pldoc_file.
 2449file_href(Path, HREF) :-                % a loaded Prolog file
 2450    source_file(Path),
 2451    !,
 2452    doc_file_href(Path, HREF).
 2453file_href(Path, HREF) :-
 2454    (   nb_current(pldoc_output, CFile)
 2455    ;   nb_current(pldoc_file, CFile)
 2456    ),
 2457    CFile \== [],
 2458    !,
 2459    relative_file_name(Path, CFile, HREF).
 2460file_href(Path, Path).
 existing_linked_file(+File, -Path) is semidet
True if File is a path to an existing file relative to the current file. Path is the absolute location of File.
 2468existing_linked_file(File, Path) :-
 2469    catch(b_getval(pldoc_file, CurrentFile), _, fail),
 2470    CurrentFile \== [],
 2471    absolute_file_name(File, Path,
 2472		       [ relative_to(CurrentFile),
 2473			 access(read),
 2474			 file_errors(fail)
 2475		       ]).
 include(+FileName, +Type, +Options)// is det
Inline FileName. If this is an image file, show an inline image. Else we create a link like file//1. Called by \include(File, Type) terms in the DOM term generated by wiki.pl if it encounters [[file.ext]].
 2485include(PI, predicate, _) -->
 2486    !,
 2487    (   html_tokens_for_predicates(PI, [])
 2488    ->  []
 2489    ;   html(['[[', \predref(PI), ']]'])
 2490    ).
 2491include(File, image, Options) -->
 2492    { file_name_extension(_, svg, File),
 2493      file_href(File, HREF, Options),
 2494      !,
 2495      include(image_attribute, Options, Attrs0),
 2496      merge_options(Attrs0,
 2497		    [ alt(File),
 2498		      data(HREF),
 2499		      type('image/svg+xml')
 2500		    ], Attrs)
 2501    },
 2502    (   { option(caption(Caption), Options) }
 2503    ->  html(div(class(figure),
 2504		 [ div(class(image), object(Attrs, [])),
 2505		   div(class(caption), Caption)
 2506		 ]))
 2507    ;   html(object(Attrs, []))
 2508    ).
 2509include(File, image, Options) -->
 2510    { file_href(File, HREF, Options),
 2511      !,
 2512      include(image_attribute, Options, Attrs0),
 2513      merge_options(Attrs0,
 2514		    [ alt(File),
 2515		      border(0),
 2516		      src(HREF)
 2517		    ], Attrs)
 2518    },
 2519    (   { option(caption(Caption), Options) }
 2520    ->  html(div(class(figure),
 2521		 [ div(class(image), img(Attrs)),
 2522		   div(class(caption), Caption)
 2523		 ]))
 2524    ;   html(img(Attrs))
 2525    ).
 2526include(File, wiki, _Options) -->       % [[file.txt]] is included
 2527    { access_file(File, read),
 2528      !,
 2529      read_file_to_codes(File, String, []),
 2530      wiki_codes_to_dom(String, [], DOM)
 2531    },
 2532    html(DOM).
 2533include(File, _Type, Options) -->
 2534    link_file(File, Options),
 2535    !.
 2536include(File, _, _) -->
 2537    html(code(class(nofile), ['[[',File,']]'])).
 2538
 2539image_attribute(src(_)).
 2540image_attribute(alt(_)).
 2541image_attribute(title(_)).
 2542image_attribute(align(_)).
 2543image_attribute(width(_)).
 2544image_attribute(height(_)).
 2545image_attribute(border(_)).
 2546image_attribute(class(_)).
 2547image_attribute(style(_)).
 html_tokens_for_predicates(+PI, +Options)// is semidet
Inline description for a predicate as produced by the text below from wiki processing.
        * [[member/2]]
        * [[append/3]]
 2560html_tokens_for_predicates([], _Options) -->
 2561    [].
 2562html_tokens_for_predicates([H|T], Options) -->
 2563    !,
 2564    html_tokens_for_predicates(H, Options),
 2565    html_tokens_for_predicates(T, Options).
 2566html_tokens_for_predicates(PI, Options) -->
 2567    { PI = _:_/_,
 2568      !,
 2569      (   doc_comment(PI, Pos, _Summary, Comment)
 2570      ->  true
 2571      ;   Comment = ''
 2572      )
 2573    },
 2574    object(PI, [Pos-Comment], [dl], _, Options).
 2575html_tokens_for_predicates(Spec, Options) -->
 2576    { findall(PI, documented_pi(Spec, PI), List),
 2577      List \== [], !
 2578    },
 2579    html_tokens_for_predicates(List, Options).
 2580html_tokens_for_predicates(Spec, Options) -->
 2581    man_page(Spec,
 2582	     [ links(false),                % no header
 2583	       navtree(false),              % no navigation tree
 2584	       footer(false),               % no footer
 2585	       synopsis(false)              % no synopsis
 2586	     | Options
 2587	     ]).
 2588
 2589
 2590documented_pi(Spec, PI) :-
 2591    generalise_spec(Spec, PI),
 2592    doc_comment(PI, _Pos, _Summary, _Comment).
 2593
 2594generalise_spec(Name/Arity, _M:Name/Arity).
 2595generalise_spec(Name//Arity, _M:Name//Arity).
 2596
 2597
 2598		 /*******************************
 2599		 *           WIKI FILES         *
 2600		 *******************************/
 doc_for_wiki_file(+File, +Options) is det
Write HTML for the File containing wiki data.
 2607doc_for_wiki_file(FileSpec, Options) :-
 2608    absolute_file_name(FileSpec, File,
 2609		       [ access(read)
 2610		       ]),
 2611    read_file_to_codes(File, String, []),
 2612    b_setval(pldoc_file, File),
 2613    call_cleanup(reply_wiki_page(File, String, Options),
 2614		 nb_delete(pldoc_file)).
 2615
 2616reply_wiki_page(File, String, Options) :-
 2617    wiki_codes_to_dom(String, [], DOM0),
 2618    title(DOM0, File, Title),
 2619    insert_edit_button(DOM0, File, DOM, Options),
 2620    reply_html_page(pldoc(wiki),
 2621		    title(Title),
 2622		    [ \html_requires(pldoc)
 2623		    | DOM
 2624		    ]).
 2625
 2626title(DOM, _, Title) :-
 2627    sub_term(h1(_,Title), DOM),
 2628    !.
 2629title(_, File, Title) :-
 2630    file_base_name(File, Title).
 2631
 2632insert_edit_button(DOM, _, DOM, Options) :-
 2633    option(edit(false), Options, false),
 2634    !.
 2635insert_edit_button([h1(Attrs,Title)|DOM], File,
 2636		   [h1(Attrs,[ span(style('float:right'),
 2637				   \edit_button(File, [edit(true)]))
 2638			     | Title
 2639			     ])|DOM], _) :- !.
 2640insert_edit_button(DOM, File,
 2641		   [ h1(class(wiki),
 2642			[ span(style('float:right'),
 2643			       \edit_button(File, [edit(true)]))
 2644			])
 2645		   | DOM
 2646		   ], _).
 2647
 2648
 2649		 /*******************************
 2650		 *            ANCHORS           *
 2651		 *******************************/
 mode_anchor_name(+Mode, -Anchor:atom) is det
Get the anchor name for a mode.
 2657mode_anchor_name(Var, _) :-
 2658    var(Var),
 2659    !,
 2660    instantiation_error(Var).
 2661mode_anchor_name(mode(Head, _), Anchor) :-
 2662    !,
 2663    mode_anchor_name(Head, Anchor).
 2664mode_anchor_name(Head is _Det, Anchor) :-
 2665    !,
 2666    mode_anchor_name(Head, Anchor).
 2667mode_anchor_name(Head, Anchor) :-
 2668    pred_anchor_name(Head, _, Anchor).
 pred_anchor_name(+Head, -PI:atom/integer, -Anchor:atom) is det
Create an HTML anchor name from Head.
 2675pred_anchor_name(//(Head), Name/Arity, Anchor) :-
 2676    !,
 2677    functor(Head, Name, DCGArity),
 2678    Arity is DCGArity+2,
 2679    format(atom(Anchor), '~w/~d', [Name, Arity]).
 2680pred_anchor_name(Head, Name/Arity, Anchor) :-
 2681    functor(Head, Name, Arity),
 2682    format(atom(Anchor), '~w/~d', [Name, Arity]).
 2683
 2684:- multifile prolog:message//1. 2685
 2686prolog:message(pldoc(module_comment_outside_module(Title))) -->
 2687    [ 'PlDoc comment <module> ~w does not appear in a module'-[Title] ]