35
36:- module(pldoc_htmlsrc,
37 [ source_to_html/3 38 ]). 39:- use_module(library(apply)). 40:- use_module(library(option)). 41:- use_module(library(debug)). 42:- use_module(library(lists)). 43:- use_module(library(prolog_colour)). 44:- use_module(doc_colour). 45:- use_module(doc_html). 46:- use_module(doc_wiki). 47:- use_module(doc_modes). 48:- use_module(doc_process). 49:- use_module(library(http/html_write)). 50:- use_module(library(http/http_path)). 51:- use_module(library(prolog_xref)). 52
53:- meta_predicate
54 source_to_html(+, +, :). 55
56
65
66:- predicate_options(source_to_html/3, 3,
67 [ format_comments(boolean),
68 header(boolean),
69 skin(callable),
70 stylesheets(list),
71 title(atom)
72 ]). 73
74
75:- thread_local
76 lineno/0, 77 nonl/0, 78 id/1. 79
104
105source_to_html(Src, stream(Out), MOptions) :-
106 !,
107 meta_options(is_meta, MOptions, Options),
108 ( option(title(_), Options)
109 -> HeadOptions = Options
110 ; file_base_name(Src, Title),
111 HeadOptions = [title(Title)|Options]
112 ),
113 retractall(lineno), 114 retractall(nonl), 115 retractall(id(_)),
116 colour_fragments(Src, Fragments),
117 setup_call_cleanup(
118 ( open_source(Src, In),
119 asserta(user:thread_message_hook(_,_,_), Ref)
120 ),
121 ( print_html_head(Out, HeadOptions),
122 html_fragments(Fragments, In, Out, [], State, Options),
123 copy_rest(In, Out, State, State1),
124 pop_state(State1, Out, In)
125 ),
126 ( erase(Ref),
127 close(In)
128 )),
129 print_html_footer(Out, Options).
130source_to_html(Src, FileSpec, Options) :-
131 absolute_file_name(FileSpec, OutFile, [access(write)]),
132 setup_call_cleanup(
133 open(OutFile, write, Out, [encoding(utf8)]),
134 source_to_html(Src, stream(Out), Options),
135 close(Out)).
136
137open_source(Id, Stream) :-
138 prolog:xref_open_source(Id, Stream),
139 !.
140open_source(File, Stream) :-
141 open(File, read, Stream).
142
143is_meta(skin).
144
165
166print_html_head(Out, Options) :-
167 option(header(true), Options, true),
168 !,
169 option(title(Title), Options, 'Prolog source'),
170 http_absolute_location(pldoc_resource('pldoc.css'), PlDocCSS, []),
171 http_absolute_location(pldoc_resource('pllisting.css'), PlListingCSS, []),
172 option(stylesheets(Sheets), Options, [PlListingCSS, PlDocCSS]),
173 format(Out, '<!DOCTYPE html', []),
174 format(Out, '<html>~n', []),
175 format(Out, ' <head>~n', []),
176 format(Out, ' <title>~w</title>~n', [Title]),
177 forall(member(Sheet, Sheets),
178 format(Out, ' <link rel="stylesheet" type="text/css" href="~w">~n', [Sheet])),
179 format(Out, ' </head>~n', []),
180 format(Out, '<body>~n', []),
181 skin_hook(Out, header, Options).
182print_html_head(Out, Options) :-
183 skin_hook(Out, header, Options).
184
(Out, Options) :-
186 option(header(true), Options, true),
187 !,
188 skin_hook(Out, footer, Options),
189 format(Out, '~N</body>~n', []),
190 format(Out, '</html>', []).
191print_html_footer(Out, Options) :-
192 skin_hook(Out, footer, Options).
193
194skin_hook(Out, Where, Options) :-
195 option(skin(Skin), Options),
196 call(Skin, Where, Out),
197 !.
198skin_hook(_, _, _).
199
200
204
205html_fragments([], _, _, State, State, _).
206html_fragments([H|T], In, Out, State0, State, Options) :-
207 html_fragment(H, In, Out, State0, State1, Options),
208 html_fragments(T, In, Out, State1, State, Options).
209
215
216html_fragment(fragment(Start, End, comment(structured), []),
217 In, Out, State0, [], Options) :-
218 option(format_comments(true), Options, true),
219 !,
220 copy_without_trailing_white_lines(In, Start, Out, State0, State1),
221 pop_state(State1, Out, In),
222 Len is End - Start,
223 read_n_codes(In, Len, Comment),
224 is_structured_comment(Comment, Prefix),
225 indented_lines(Comment, Prefix, Lines0),
226 ( section_comment_header(Lines0, Header, Lines1)
227 -> wiki_lines_to_dom(Lines1, [], DOM),
228 phrase(pldoc_html:html(div(class(comment),
229 [Header|DOM])), Tokens),
230 print_html(Out, Tokens)
231 ; stream_property(In, file_name(File)),
232 line_count(In, Line),
233 ( xref_module(File, Module)
234 -> true
235 ; Module = user
236 ),
237 process_modes(Lines0, Module, File:Line, Modes, Args, Lines1),
238 maplist(assert_seen_mode, Modes),
239 DOM = [\pred_dt(Modes, pubdef, []), dd(class=defbody, DOM1)],
240 wiki_lines_to_dom(Lines1, Args, DOM0),
241 strip_leading_par(DOM0, DOM1),
242 phrase(pldoc_html:html(DOM), Tokens), 243 format(Out, '<dl class="comment">~n', [Out]),
244 print_html(Out, Tokens),
245 format(Out, '</dl>~n', [Out])
246 ).
247html_fragment(fragment(Start, End, structured_comment, []),
248 In, Out, State0, State, _Options) :-
249 !,
250 copy_to(In, Start, Out, State0, State1),
251 line_count(In, StartLine),
252 Len is End - Start,
253 read_n_codes(In, Len, Comment),
254 is_structured_comment(Comment, Prefix),
255 indented_lines(Comment, Prefix, Lines),
256 ( section_comment_header(Lines, _Header, _RestSectionLines)
257 -> true
258 ; stream_property(In, file_name(File)),
259 line_count(In, Line),
260 ( xref_module(File, Module)
261 -> true
262 ; Module = user
263 ),
264 process_modes(Lines, Module, File:Line, Modes, _Args, _Lines1),
265 maplist(mode_anchor(Out), Modes)
266 ),
267 start_fragment(structured_comment, In, Out, State1, State2),
268 copy_codes(Comment, StartLine, Out, State2, State3),
269 end_fragment(Out, In, State3, State).
270html_fragment(fragment(Start, End, Class, Sub),
271 In, Out, State0, State, Options) :-
272 copy_to(In, Start, Out, State0, State1),
273 start_fragment(Class, In, Out, State1, State2),
274 html_fragments(Sub, In, Out, State2, State3, Options),
275 copy_to(In, End, Out, State3, State4), 276 end_fragment(Out, In, State4, State).
277
278start_fragment(atom, In, Out, State0, State) :-
279 !,
280 ( peek_code(In, C),
281 C == 39
282 -> start_fragment(quoted_atom, In, Out, State0, State)
283 ; State = [nop|State0]
284 ).
285start_fragment(Class, _, Out, State, [Push|State]) :-
286 element(Class, Tag, CSSClass),
287 !,
288 Push =.. [Tag,class(CSSClass)],
289 ( anchor(Class, ID)
290 -> format(Out, '<~w id="~w" class="~w">', [Tag, ID, CSSClass])
291 ; format(Out, '<~w class="~w">', [Tag, CSSClass])
292 ).
293start_fragment(Class, _, Out, State, [span(class(SpanClass))|State]) :-
294 functor(Class, SpanClass, _),
295 format(Out, '<span class="~w">', [SpanClass]).
296
297end_fragment(_, _, [nop|State], State) :- !.
298end_fragment(Out, In, [span(class(directive))|State], State) :-
299 !,
300 copy_full_stop(In, Out),
301 format(Out, '</span>', []),
302 ( peek_code(In, 10),
303 \+ nonl
304 -> assert(nonl)
305 ; true
306 ).
307end_fragment(Out, _, [Open|State], State) :-
308 retractall(nonl),
309 functor(Open, Element, _),
310 format(Out, '</~w>', [Element]).
311
312pop_state([], _, _) :- !.
313pop_state(State, Out, In) :-
314 end_fragment(Out, In, State, State1),
315 pop_state(State1, Out, In).
316
317
323
324anchor(head(_, Head), Id) :-
325 callable(Head),
326 functor(Head, Name, Arity),
327 format(atom(Id), '~w/~w', [Name, Arity]),
328 ( id(Id)
329 -> fail
330 ; assertz(id(Id))
331 ).
332
333mode_anchor(Out, Mode) :-
334 mode_anchor_name(Mode, Id),
335 ( id(Id)
336 -> true
337 ; format(Out, '<span id="~w"><span>', [Id]),
338 assertz(id(Id))
339 ).
340
341assert_seen_mode(Mode) :-
342 mode_anchor_name(Mode, Id),
343 ( id(Id)
344 -> true
345 ; assertz(id(Id))
346 ).
347
354
355copy_to(In, End, Out, State, State) :-
356 member(pre(_), State),
357 !,
358 copy_to(In, End, Out).
359copy_to(In, End, Out, State, [pre(class(listing))|State]) :-
360 format(Out, '<pre class="listing">~n', [Out]),
361 line_count(In, Line0),
362 read_to(In, End, Codes0),
363 delete_leading_white_lines(Codes0, Codes, Line0, Line),
364 assert(lineno),
365 write_codes(Codes, Line, Out).
366
367copy_codes(Codes, Line, Out, State, State) :-
368 member(pre(_), State),
369 !,
370 write_codes(Codes, Line, Out).
371copy_codes(Codes0, Line0, Out, State, State) :-
372 format(Out, '<pre class="listing">~n', [Out]),
373 delete_leading_white_lines(Codes0, Codes, Line0, Line),
374 assert(lineno),
375 write_codes(Codes, Line, Out).
376
377
381
382copy_full_stop(In, Out) :-
383 get_code(In, C0),
384 copy_full_stop(C0, In, Out).
385
386copy_full_stop(0'., _, Out) :-
387 !,
388 put_code(Out, 0'.).
389copy_full_stop(C, In, Out) :-
390 put_code(Out, C),
391 get_code(In, C2),
392 copy_full_stop(C2, In, Out).
393
394
400
401delete_leading_white_lines(Codes0, Codes, Line0, Line) :-
402 append(LineCodes, [10|Rest], Codes0),
403 all_spaces(LineCodes),
404 !,
405 Line1 is Line0 + 1,
406 delete_leading_white_lines(Rest, Codes, Line1, Line).
407delete_leading_white_lines(Codes, Codes, Line, Line).
408
413
414copy_without_trailing_white_lines(In, End, Out, State, State) :-
415 member(pre(_), State),
416 !,
417 line_count(In, Line),
418 read_to(In, End, Codes0),
419 delete_trailing_white_lines(Codes0, Codes),
420 write_codes(Codes, Line, Out).
421copy_without_trailing_white_lines(In, End, Out, State0, State) :-
422 copy_to(In, End, Out, State0, State).
423
424delete_trailing_white_lines(Codes0, []) :-
425 all_spaces(Codes0),
426 !.
427delete_trailing_white_lines(Codes0, Codes) :-
428 append(Codes, Tail, [10|Rest], Codes0),
429 !,
430 delete_trailing_white_lines(Rest, Tail).
431delete_trailing_white_lines(Codes, Codes).
432
436
437append(T, T, L, L).
438append([H|T0], Tail, L, [H|T]) :-
439 append(T0, Tail, L, T).
440
441all_spaces([]).
442all_spaces([H|T]) :-
443 code_type(H, space),
444 all_spaces(T).
445
446copy_to(In, End, Out) :-
447 line_count(In, Line),
448 read_to(In, End, Codes),
449 ( debugging(htmlsrc)
450 -> length(Codes, Count),
451 debug(htmlsrc, 'Copy ~D chars: ~s', [Count, Codes])
452 ; true
453 ),
454 write_codes(Codes, Line, Out).
455
456read_to(In, End, Codes) :-
457 character_count(In, Here),
458 Len is End - Here,
459 read_n_codes(In, Len, Codes).
460
464
465write_codes([], _, _).
466write_codes([H|T], L0, Out) :-
467 content_escape(H, Out, L0, L1),
468 write_codes(T, L1, Out).
469
479
480content_escape(_, Out, L, _) :-
481 ( lineno
482 -> retractall(lineno),
483 write_line_no(L, Out),
484 fail
485 ; fail
486 ).
487content_escape(0'\n, Out, L0, L) :-
488 !,
489 L is L0 + 1,
490 ( retract(nonl)
491 -> true
492 ; nl(Out)
493 ),
494 assert(lineno).
495content_escape(0'<, Out, L, L) :-
496 !,
497 format(Out, '<', []).
498content_escape(0'>, Out, L, L) :-
499 !,
500 format(Out, '>', []).
501content_escape(0'&, Out, L, L) :-
502 !,
503 format(Out, '&', []).
504content_escape(C, Out, L, L) :-
505 put_code(Out, C).
506
507write_line_no(LineNo, Out) :-
508 format(Out, '<span class="line-no">~|~t~d~5+</span>', [LineNo]).
509
513
514copy_rest(In, Out, State0, State) :-
515 copy_to(In, -1, Out, State0, State).
516
521
522read_n_codes(_, N, Codes) :-
523 N =< 0,
524 !,
525 Codes = [].
526read_n_codes(In, N, Codes) :-
527 get_code(In, C0),
528 read_n_codes(N, C0, In, Codes).
529
530read_n_codes(_, -1, _, []) :- !.
531read_n_codes(1, C, _, [C]) :- !.
532read_n_codes(N, C, In, [C|T]) :-
533 get_code(In, C2),
534 N2 is N - 1,
535 read_n_codes(N2, C2, In, T).
536
537
543
544term_expansion(element(_,_,_), Clauses) :-
545 findall(C, element_clause(C), Clauses).
546
548element_tag(_, span).
549
550element_clause(element(Term, Tag, CSS)) :-
551 span_term(Term, CSS),
552 element_tag(Term, Tag).
553
554span_term(Classification, Class) :-
555 syntax_colour(Classification, _Attributes),
556 css_class(Classification, Class).
557
558css_class(Class, Class) :-
559 atom(Class),
560 !.
561css_class(Term, Class) :-
562 Term =.. [P1,A|_],
563 ( var(A)
564 -> Class = P1
565 ; css_class(A, P2),
566 atomic_list_concat([P1, -, P2], Class)
567 ).
568
569element(_,_,_).