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)  2018, VU University Amsterdam
    7    All rights reserved.
    8
    9    Redistribution and use in source and binary forms, with or without
   10    modification, are permitted provided that the following conditions
   11    are met:
   12
   13    1. Redistributions of source code must retain the above copyright
   14       notice, this list of conditions and the following disclaimer.
   15
   16    2. Redistributions in binary form must reproduce the above copyright
   17       notice, this list of conditions and the following disclaimer in
   18       the documentation and/or other materials provided with the
   19       distribution.
   20
   21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32    POSSIBILITY OF SUCH DAMAGE.
   33*/
   34
   35:- module(prolog_manual_index,
   36          [ clean_man_index/0,          %
   37            save_man_index/0,
   38            index_man_directory/2,      % +DirSpec, +Options
   39            index_man_file/2,           % +Class, +FileSpec
   40                                        % Query
   41            current_man_object/1,       % ?Object
   42            man_object_property/2,      % ?Object, ?Property
   43
   44            manual_object/5,            % ?Obj, ?Summary, ?File, ?Class, ?Offset
   45            doc_object_identifier/2     % +Obj, -Name
   46          ]).   47:- use_module(library(debug),[debug/3]).   48:- autoload(doc_util,[atom_to_object/2,normalise_white_space/3]).   49:- autoload(library(apply),[maplist/2]).   50:- autoload(library(assoc),[empty_assoc/1,get_assoc/3,put_assoc/4]).   51:- autoload(library(error),[type_error/2]).   52:- autoload(library(filesex),[directory_file_path/3]).   53:- autoload(library(lists),[member/2,reverse/2,append/3]).   54:- autoload(library(occurs),[sub_term/2]).   55:- autoload(library(option),[select_option/4]).   56:- autoload(library(sgml),
   57	    [ dtd/2,
   58	      new_sgml_parser/2,
   59	      set_sgml_parser/2,
   60	      sgml_parse/2,
   61	      free_sgml_parser/1,
   62	      get_sgml_parser/2
   63	    ]).

Index the HTML manuals

This module pre-processes the HTML files that constitute the manual such that we can access the summary documentation of all predicates for usage in IDE tools.

   72:- predicate_options(index_man_directory/2, 2,
   73                     [ class(oneof([manual,packages,misc])),
   74                       symbolic(any),
   75                       pass_to(system:absolute_file_name/3, 3)
   76                     ]).   77
   78
   79:- dynamic
   80    man_index/5.            % Object, Summary, File, Class, Offset
 manual_object(?Object, ?Summary, ?File, ?Class, ?Offset) is nondet
True if Object is documented. Arguments:
Arguments:
Object- is the object documented, described by a Prolog term. Defined shapes are:
  • section(Level, Number, Label, File)
  • Name/Arity
  • Name//Arity
  • Module:Name/Arity
  • Module:Name//Arity
  • f(Name/Arity
  • c(Name)
Summary- is a string object providing a summary of object
File- is the HTML file in which the object is documented
Class- is one of manual or packages
Offset- is the character offset at which the DOM element describing Object appears. This is used by doc_man.pl to quickly extract the node.
  104manual_object(Object, Summary, File, Class, Offset) :-
  105    index_manual,
  106    man_index(Object, Summary, File, Class, Offset).
 clean_man_index is det
Clean already loaded manual index.
  112clean_man_index :-
  113    retractall(man_index(_,_,_,_,_)).
 manual_directory(-Class, -Dir)// is nondet
True if Dir is a directory holding manual files. Class is an identifier used by doc_object_summary/4.
  121user:file_search_path(swi_man_manual,   swi('doc/Manual')).
  122user:file_search_path(swi_man_packages, swi('doc/packages')).
  123user:file_search_path(swi_man_xpce,     swi('xpce/man/refmanual')).
  124
  125manual_directory(Class, Spec, Dir) :-
  126    man_path_spec(Class, Spec),
  127    absolute_file_name(Spec, Dir,
  128                       [ file_type(directory),
  129                         access(read),
  130                         solutions(all)
  131                       ]).
  132
  133man_path_spec(manual,   swi_man_manual(.)).
  134man_path_spec(packages, swi_man_packages(.)).
  135man_path_spec(packages, swi_man_xpce(.)).
  136
  137
  138                 /*******************************
  139                 *          PARSE MANUAL        *
  140                 *******************************/
 save_man_index
Create swi('doc/manindex.db'), containing an index into the HTML manuals for use with help/1 and similar predicates. This predicate is called during the build process.
  148save_man_index :-
  149    cached_index_file(write, File),
  150    setup_call_cleanup(
  151        open(File, write, Out, [encoding(utf8)]),
  152        (   format(Out, '/*  Generated manual index.~n', []),
  153            format(Out, '    Do not edit.~n', []),
  154            format(Out, '*/~n~n', []),
  155            setup_call_cleanup(
  156                b_setval(pldoc_save_index, Out),
  157                do_index_manual,
  158                nb_delete(pldoc_save_index))
  159        ),
  160        close(Out)).
 index_manual is det
Load the manual index if not already done.
  167index_manual :-
  168    man_index(_,_,_,_,_),
  169    !.
  170index_manual :-
  171    with_mutex(pldoc_man,
  172               locked_index_manual).
  173
  174locked_index_manual :-
  175    man_index(_,_,_,_,_),
  176    !.
  177locked_index_manual :-
  178    cached_index_file(read, File),
  179    catch(read_index(File), E,
  180          print_message(warning, E)),
  181    !.
  182locked_index_manual :-
  183    do_index_manual.
  184
  185do_index_manual :-
  186    forall(manual_directory(Class, Symbolic, Dir),
  187           index_man_directory(Dir,
  188                               [ class(Class),
  189                                 symbolic(Symbolic),
  190                                 file_errors(fail)
  191                               ])).
 read_index(+File)
Read the manual index from File.
  197read_index(File) :-
  198    context_module(M),
  199    empty_assoc(State0),
  200    setup_call_cleanup(
  201        open(File, read, In, [encoding(utf8)]),
  202        read_man_index(In, State0, [module(M)]),
  203        close(In)).
  204
  205read_man_index(In, State0, Options) :-
  206    read_term(In, TermIn, Options),
  207    (   TermIn == end_of_file
  208    ->  true
  209    ;   valid_term(TermIn, Term, State0, State),
  210        assert(Term),
  211        read_man_index(In, State, Options)
  212    ).
  213
  214valid_term(TermIn, Term, State0, State) :-
  215    ground(TermIn),
  216    resolve_index(TermIn, Term, State0, State),
  217    !.
  218valid_term(Term, _, _, _) :-
  219    type_error(man_index_term, Term).
  220
  221resolve_index(i(Object0, Summary, File0, Class, Offset),
  222              man_index(Object, Summary, File, Class, Offset),
  223              State0, State) :-
  224    map_section(Object0, Object, File),
  225    resolve_index_file(File0, File, State0, State).
  226
  227resolve_index_file(File0, File, State, State) :-
  228    get_assoc(File0, State, File),
  229    !.
  230resolve_index_file(File0, File, State0, State) :-
  231    absolute_file_name(File0, File, [access(read)]),
  232    put_assoc(File0, State0, File, State).
  233
  234map_section(section(Level, Nr, Label), section(Level, Nr, Label, File), File) :-
  235    !.
  236map_section(Object, Object, _).
  237
  238cached_index_file(Access, File) :-
  239    absolute_file_name(swi('doc/manindex.db'), File,
  240                       [ access(Access),
  241                         file_errors(fail)
  242                       ]).
 check_duplicate_ids
Maintenance utility to check that we do not have duplicate section identifiers in the documentation.
  250:- public
  251    check_duplicate_ids/0.  252
  253check_duplicate_ids :-
  254    findall(Id, man_index(section(_,_,Id),_,_,_,_), Ids),
  255    msort(Ids, Sorted),
  256    duplicate_ids(Sorted, Duplicates),
  257    (   Duplicates == []
  258    ->  true
  259    ;   print_message(warning, pldoc(duplicate_ids(Duplicates)))
  260    ).
  261
  262duplicate_ids([], []).
  263duplicate_ids([H,H|T0], [H|D]) :-
  264    !,
  265    take_prefix(H,T0,T),
  266    duplicate_ids(T, D).
  267duplicate_ids([_|T], D) :-
  268    duplicate_ids(T, D).
  269
  270take_prefix(H, [H|T0], T) :-
  271    !,
  272    take_prefix(H, T0, T).
  273take_prefix(_, L, L).
 index_man_directory(+Dir, +Options) is det
Index the HTML directory Dir. Options are:
class(Class)
Define category of the found objects.
symbolic(+Term)
Symbolic (file search) specification for Dir

Remaining Options are passed to absolute_file_name/3.

  287index_man_directory(Spec, Options) :-
  288    select_option(class(Class), Options, Options1, misc),
  289    select_option(symbolic(Symbolic), Options1, Options2, Spec),
  290    absolute_file_name(Spec, Dir,
  291                       [ file_type(directory),
  292                         access(read)
  293                       | Options2
  294                       ]),
  295    atom_concat(Dir, '/*.html', Pattern),
  296    expand_file_name(Pattern, Files),
  297    maplist(index_man_file(Class, Symbolic), Files).
 index_man_file(+Class, +File) is det
 index_man_file(+Class, +File, +Symbolic) is det
Collect the documented objects from the SWI-Prolog manual file File.
  306index_man_file(Class, File) :-
  307    index_man_file(Class, File, File).
  308index_man_file(Class, Symbolic, File) :-
  309    absolute_file_name(File, Path,
  310                       [ access(read)
  311                       ]),
  312    debug(pldoc(man_index), 'Indexing ~p ~p', [Class, File]),
  313    open(Path, read, In, [type(binary)]),
  314    dtd(html, DTD),
  315    new_sgml_parser(Parser, [dtd(DTD)]),
  316    set_sgml_parser(Parser, file(File)),
  317    set_sgml_parser(Parser, dialect(sgml)),
  318    set_sgml_parser(Parser, shorttag(false)),
  319    nb_setval(pldoc_man_index, []),
  320    nb_setval(pldoc_index_class, Class),
  321    nb_setval(pldoc_man_dir, Symbolic),
  322    call_cleanup(sgml_parse(Parser,
  323                            [ source(In),
  324                              syntax_errors(quiet),
  325                              call(begin, index_on_begin)
  326                            ]),
  327                 (   free_sgml_parser(Parser),
  328                     close(In),
  329                     nb_delete(pldoc_man_index),
  330                     nb_delete(pldoc_man_dir)
  331                 )).
 index_on_begin(+Element, +Attributes, +Parser) is semidet
Called from sgml_parse/2 in index_man_file/2. Element is the name of the element, Attributes the list of Name=Value pairs of the open attributes. Parser is the parser objects.
  340index_on_begin(dt, Attributes, Parser) :-
  341    memberchk(class=Def, Attributes),
  342    public_def(Def),
  343    get_sgml_parser(Parser, charpos(Offset)),
  344    get_sgml_parser(Parser, file(File)),
  345    sgml_parse(Parser,
  346               [ document(DT),
  347                 syntax_errors(quiet),
  348                 parse(content)
  349               ]),
  350    (   sub_term(element(a, AA, _), DT),
  351        member(Attr, ['data-obj', id, name]),
  352        memberchk(Attr=Id, AA),
  353        atom_to_object(Id, PI)
  354    ->  true
  355    ),
  356    nb_getval(pldoc_man_index, DD0),
  357    (   memberchk(dd(PI, File, _), DD0)
  358    ->  true
  359    ;   nb_setval(pldoc_man_index, [dd(PI, File, Offset)|DD0])
  360    ).
  361index_on_begin(dd, _, Parser) :-
  362    !,
  363    nb_getval(pldoc_man_index, DDList0), DDList0 \== [],
  364    nb_setval(pldoc_man_index, []),
  365    sgml_parse(Parser,
  366               [ document(DD),
  367                 syntax_errors(quiet),
  368                 parse(content)
  369               ]),
  370    summary(DD, Summary),
  371    nb_getval(pldoc_index_class, Class),
  372    reverse(DDList0, [dd(Object, File, Offset)|DDTail]),
  373    assert_index(Object, Summary, File, Class, Offset),
  374    forall(member(dd(Obj2,_,_), DDTail),
  375           assert_index(Obj2, Summary, File, Class, Offset)).
  376index_on_begin(div, Attributes, Parser) :-
  377    !,
  378    memberchk(class=title, Attributes),
  379    get_sgml_parser(Parser, charpos(Offset)),
  380    get_sgml_parser(Parser, file(File)),
  381    sgml_parse(Parser,
  382               [ document(DOM),
  383                 syntax_errors(quiet),
  384                 parse(content)
  385               ]),
  386    dom_to_text(DOM, Title),
  387    nb_getval(pldoc_index_class, Class),
  388    swi_local_path(File, Local),
  389    assert_index(section(0, '0', Local, File),
  390                 Title, File, Class, Offset).
  391index_on_begin(H, Attributes, Parser) :- % TBD: add class for document title.
  392    heading(H, Level),
  393    get_sgml_parser(Parser, charpos(Offset)),
  394    get_sgml_parser(Parser, file(File)),
  395    sgml_parse(Parser,
  396               [ document(Doc),
  397                 syntax_errors(quiet),
  398                 parse(content)
  399               ]),
  400    dom_section(Doc, Nr, Title),
  401    nb_getval(pldoc_index_class, Class),
  402    section_id(Attributes, Title, File, ID),
  403    assert_index(section(Level, Nr, ID, File),
  404                 Title, File, Class, Offset).
  405
  406assert_index(Object, Summary, File, Class, Offset) :-
  407    nb_current(pldoc_save_index, Out),
  408    !,
  409    map_section(Object1, Object, File),
  410    symbolic_file(File, Symbolic),
  411    format(Out, '~k.~n', [i(Object1, Summary, Symbolic, Class, Offset)]).
  412assert_index(Object, Summary, File, Class, Offset) :-
  413    assertz(man_index(Object, Summary, File, Class, Offset)).
  414
  415symbolic_file(File, Symbolic) :-
  416    nb_getval(pldoc_man_dir, Dir),
  417    Dir =.. [Alias,'.'],
  418    !,
  419    file_base_name(File, Base),
  420    Symbolic =.. [Alias,Base].
  421symbolic_file(File, File).
  422
  423section_id(Attributes, _Title, _, ID) :-
  424    memberchk(id=ID, Attributes),
  425    !.
  426section_id(_, "Bibliography", _, 'sec:bibliography') :- !.
  427section_id(_Attributes, Title, File, ID) :-
  428    atomic_list_concat(Words, ' ', Title),
  429    atomic_list_concat(Words, '_', ID0),
  430    atom_concat('sec:', ID0, ID),
  431    print_message(warning, pldoc(no_section_id(File, Title))).
  432
  433public_def(pubdef).
  434public_def(multidef).
 dom_section(+HeaderDOM, -NR, -Title) is semidet
NR is the section number (e.g. 1.1, 1.23) and Title is the title from a section header. The first clauses processes the style information from latex2html, emitting sections as:
<HN> <A name="sec:nr"><span class='sec-nr'>NR</span>|_|
                      <span class='sec-title'>Title</span>
  447dom_section(DOM, Nr, Title) :-
  448    sub_term([ element(span, A1, [Nr]) | Rest ], DOM),
  449    append(_Sep, [element(span, A2, TitleDOM)], Rest),
  450    memberchk(class='sec-nr', A1),
  451    memberchk(class='sec-title', A2),
  452    !,
  453    dom_to_text(TitleDOM, Title).
  454dom_section(DOM, Nr, Title) :-
  455    dom_to_text(DOM, Title),
  456    section_number(Title, Nr, Title).
  457
  458section_number(Title, Nr, PlainTitle) :-
  459    sub_atom(Title, 0, 1, _, Start),
  460    (   char_type(Start, digit)
  461    ->  true
  462    ;   char_type(Start, upper),
  463        sub_atom(Title, 1, 1, _, '.')       % A., etc: Appendices
  464    ),
  465    sub_atom(Title, B, _, A, ' '),
  466    !,
  467    sub_atom(Title, 0, B, _, Nr),
  468    sub_string(Title, _, A, 0, PlainTitle).
  469
  470heading(h1, 1).
  471heading(h2, 2).
  472heading(h3, 3).
  473heading(h4, 4).
 summary(+DOM, -Summary:string) is det
Summary is the first sentence of DOM.
  480summary(DOM, Summary) :-
  481    phrase(summary(DOM, _), SummaryCodes0),
  482    phrase(normalise_white_space(SummaryCodes), SummaryCodes0),
  483    string_codes(Summary, SummaryCodes).
  484
  485summary([], _) -->
  486    !,
  487    [].
  488summary(_, Done) -->
  489    { Done == true },
  490    !,
  491    [].
  492summary([element(_,_,Content)|T], Done) -->
  493    !,
  494    summary(Content, Done),
  495    summary(T, Done).
  496summary([CDATA|T], Done) -->
  497    { atom_codes(CDATA, Codes)
  498    },
  499    (   { Codes = [Period|Rest],
  500          code_type(Period, period),
  501          space(Rest)
  502        }
  503    ->  [ Period ],
  504        { Done = true }
  505    ;   { append(Sentence, [C, Period|Rest], Codes),
  506          code_type(Period, period),
  507          \+ code_type(C, period),
  508          space(Rest)
  509        }
  510    ->  string(Sentence),
  511        [C, Period],
  512        { Done = true }
  513    ;   string(Codes),
  514        summary(T, Done)
  515    ).
  516
  517string([]) -->
  518    [].
  519string([H|T]) -->
  520    [H],
  521    string(T).
  522
  523space([C|_]) :- code_type(C, space), !.
  524space([]).
 dom_to_text(+DOM, -Text)
Extract the text of a parsed HTML term. White-space in the result is normalised. See normalise_white_space//1.
  531dom_to_text(Dom, Text) :-
  532    phrase(cdata_list(Dom), CDATA),
  533    with_output_to(codes(Codes0),
  534                   forall(member(T, CDATA),
  535                          write(T))),
  536    phrase(normalise_white_space(Codes), Codes0),
  537    string_codes(Text, Codes).
  538
  539cdata_list([]) -->
  540    [].
  541cdata_list([H|T]) -->
  542    cdata(H),
  543    cdata_list(T).
  544
  545cdata(element(_, _, Content)) -->
  546    !,
  547    cdata_list(Content).
  548cdata(CDATA) -->
  549    { atom(CDATA) },
  550    !,
  551    [CDATA].
  552cdata(_) -->
  553    [].
 current_man_object(?Object) is nondet
  557current_man_object(Object) :-
  558    index_manual,
  559    man_index(Object, _, _, _, _).
 man_object_property(?Object, ?Property) is nondet
True when Property is a property of the given manual object. Defined properties are:
summary(-Text)
Summary text for the object.
id(ID)
Return unique id for the text, so we can remove duplicates
  571man_object_property(Object, summary(Summary)) :-
  572    index_manual,
  573    man_index(Object, Summary, _, _, _).
  574man_object_property(Object, id(File-CharNo)) :-
  575    manual_object(Object, _, File, _, CharNo).
  576
  577swi_local_path(Path, Local) :-
  578    atom(Path),
  579    is_absolute_file_name(Path),
  580    manual_root(RootSpec, Dir),
  581    absolute_file_name(RootSpec, SWI,
  582                       [ file_type(directory),
  583                         solutions(all)
  584                       ]),
  585    directory_file_path(SWI, ManLocal, Path),
  586    !,
  587    directory_file_path(Dir, ManLocal, Local).
  588
  589manual_root(swi_man_manual(.),   'Manual').
  590manual_root(swi_man_packages(.), 'packages').
 doc_object_identifier(+DocObject, -Identifier) is semidet
True when Identifier is the name of DocObject.
  596doc_object_identifier(Name/_, Name).
  597doc_object_identifier(Name//_, Name).
  598doc_object_identifier(_:Name/_, Name).
  599doc_object_identifier(_:Name//_, Name).
  600doc_object_identifier(M:_, M).
  601doc_object_identifier(f(Name/_), Name).
  602doc_object_identifier(c(Name), Name).
  603doc_object_identifier(xpce(_, _, Name), Name)