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) 2009-2015, University of Amsterdam 7 VU University Amsterdam 8 All rights reserved. 9 10 Redistribution and use in source and binary forms, with or without 11 modification, are permitted provided that the following conditions 12 are met: 13 14 1. Redistributions of source code must retain the above copyright 15 notice, this list of conditions and the following disclaimer. 16 17 2. Redistributions in binary form must reproduce the above copyright 18 notice, this list of conditions and the following disclaimer in 19 the documentation and/or other materials provided with the 20 distribution. 21 22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 POSSIBILITY OF SUCH DAMAGE. 34*/ 35 36:- module(html_head, 37 [ html_resource/2, % +Resource, +Attributes 38 html_requires//1, % +Resource 39 40 html_current_resource/1 % ?Resource 41 ]). 42:- use_module(library(http/html_write)). 43:- use_module(library(http/mimetype)). 44:- use_module(library(http/http_path)). 45:- use_module(library(error)). 46:- use_module(library(lists)). 47:- use_module(library(occurs)). 48:- use_module(library(option)). 49:- use_module(library(ordsets)). 50:- use_module(library(assoc)). 51:- use_module(library(ugraphs)). 52:- use_module(library(apply)). 53:- use_module(library(debug)).
104:- dynamic 105 html_resource/3. % Resource, Source, Properties 106:- multifile 107 html_resource/3, 108 mime_include//2. % +Mime, +Path
true
(default false
), do not include About itself,
but only its dependencies. This allows for defining an
alias for one or more resources.Registering the same About multiple times extends the properties defined for About. In particular, this allows for adding additional dependencies to a (virtual) resource.
153html_resource(About, Properties) :- 154 assert_resource(About, -, Properties). 155 156assert_resource(About, Location, Properties) :- 157 retractall(html_resource(About, _, _)), 158 assert(html_resource(About, Location, Properties)), 159 clean_cache(About, Properties). 160 161systemterm_expansion((:-html_resource(About, Properties)), 162 html_head:html_resource(About, File:Line, Properties)) :- 163 source_location(File, Line), 164 clean_cache(About, Properties). 165 166clean_cache(_About, Properties) :- 167 clean_same_about_cache, 168 ( memberchk(aggregate(_), Properties) 169 -> clean_aggregate_cache 170 ; true 171 ).
178html_current_resource(About) :-
179 ( ground(About)
180 -> html_resource(About, _, _), !
181 ; html_resource(About, _, _)
182 ).
head
using html_post/2. The actual
dependencies are computed during the HTML output phase by
html_insert_resource//1.192html_requires(Required) --> 193 html_post(head, 'html required'(Required)). 194 195:- multifile 196 html_write:html_head_expansion/2. 197 198html_writehtml_head_expansion(In, Out) :- 199 require_commands(In, Required, Rest), 200 Required \== [], 201 !, 202 flatten(Required, Plain), 203 Out = [ html_head:(\html_insert_resource(Plain)) 204 | Rest 205 ]. 206 207require_commands([], [], []). 208require_commands([_:('html required'(Required))|T0], [Required|TR], R) :- 209 !, 210 require_commands(T0, TR, R). 211require_commands([R|T0], TR, [R|T]) :- 212 !, 213 require_commands(T0, TR, T).
228 % called from html_write:html_head_expansion/2 229:- public html_insert_resource//1. 230 231html_insert_resource(Required) --> 232 { requirements(Required, Paths), 233 debug(html(script), 'Requirements: ~q~nFinal: ~q', [Required, Paths]) 234 }, 235 html_include(Paths). 236 237requirements(Required, Paths) :- 238 phrase(requires(Required), List), 239 sort(List, Paths0), % remove duplicates 240 use_agregates(Paths0, Paths1, AggregatedBy), 241 order_html_resources(Paths1, AggregatedBy, Paths2), 242 exclude(virtual, Paths2, Paths). 243 244virtual('V'(_)).
254use_agregates(Paths, Aggregated, AggregatedBy) :- 255 empty_assoc(AggregatedBy0), 256 use_agregates(Paths, Aggregated, AggregatedBy0, AggregatedBy). 257 258use_agregates(Paths, Aggregated, AggregatedBy0, AggregatedBy) :- 259 current_aggregate(Aggregate, Parts, Size), 260 ord_subtract(Paths, Parts, NotCovered), 261 length(Paths, Len0), 262 length(NotCovered, Len1), 263 Covered is Len0-Len1, 264 Covered >= Size/2, 265 !, 266 ord_add_element(NotCovered, Aggregate, NewPaths), 267 add_aggregated_by(Parts, AggregatedBy0, Aggregate, AggregatedBy1), 268 use_agregates(NewPaths, Aggregated, AggregatedBy1, AggregatedBy). 269use_agregates(Paths, Paths, AggregatedBy, AggregatedBy). 270 271add_aggregated_by([], Assoc, _, Assoc). 272add_aggregated_by([H|T], Assoc0, V, Assoc) :- 273 put_assoc(H, Assoc0, V, Assoc1), 274 add_aggregated_by(T, Assoc1, V, Assoc). 275 276 277:- dynamic 278 aggregate_cache_filled/0, 279 aggregate_cache/3. 280:- volatile 281 aggregate_cache_filled/0, 282 aggregate_cache/3. 283 284clean_aggregate_cache :- 285 retractall(aggregate_cache_filled).
293current_aggregate(Path, Parts, Size) :- 294 aggregate_cache_filled, 295 !, 296 aggregate_cache(Path, Parts, Size). 297current_aggregate(Path, Parts, Size) :- 298 retractall(aggregate_cache(_,_, _)), 299 forall(uncached_aggregate(Path, Parts, Size), 300 assert(aggregate_cache(Path, Parts, Size))), 301 assert(aggregate_cache_filled), 302 aggregate_cache(Path, Parts, Size). 303 304uncached_aggregate(Path, APartsS, Size) :- 305 html_resource(Aggregate, _, Properties), 306 memberchk(aggregate(Parts), Properties), 307 http_absolute_location(Aggregate, Path, []), 308 absolute_paths(Parts, Path, AParts), 309 sort(AParts, APartsS), 310 length(APartsS, Size). 311 312absolute_paths([], _, []). 313absolute_paths([H0|T0], Base, [H|T]) :- 314 http_absolute_location(H0, H, [relative_to(Base)]), 315 absolute_paths(T0, Base, T).
326requires(Spec) --> 327 requires(Spec, /). 328 329requires([], _) --> 330 !, 331 []. 332requires([H|T], Base) --> 333 !, 334 requires(H, Base), 335 requires(T, Base). 336requires(Spec, Base) --> 337 requires(Spec, Base, _, true). 338 339requires('V'(Spec), Base, Properties, Virtual) --> 340 { nonvar(Spec) }, 341 !, 342 requires(Spec, Base, Properties, Virtual). 343requires(Spec, Base, Properties, Virtual) --> 344 { res_properties(Spec, Properties), 345 http_absolute_location(Spec, File, [relative_to(Base)]) 346 }, 347 ( { option(virtual(true), Properties) 348 ; Virtual == false 349 } 350 -> ['V'(Spec)] 351 ; [File] 352 ), 353 requires_from_properties(Properties, File). 354 355 356requires_from_properties([], _) --> 357 []. 358requires_from_properties([H|T], Base) --> 359 requires_from_property(H, Base), 360 requires_from_properties(T, Base). 361 362requires_from_property(requires(What), Base) --> 363 !, 364 requires(What, Base). 365requires_from_property(_, _) --> 366 [].
374order_html_resources(Requirements, AggregatedBy, Ordered) :-
375 requirements_graph(Requirements, AggregatedBy, Graph),
376 ( top_sort(Graph, Ordered)
377 -> true
378 ; connect_graph(Graph, Start, Connected),
379 top_sort(Connected, Ordered0),
380 Ordered0 = [Start|Ordered]
381 ).
389requirements_graph(Requirements, AggregatedBy, Graph) :- 390 phrase(prerequisites(Requirements, AggregatedBy, Vertices, []), Edges), 391 vertices_edges_to_ugraph(Vertices, Edges, Graph). 392 393prerequisites([], _, Vs, Vs) --> 394 []. 395prerequisites([R|T], AggregatedBy, Vs, Vt) --> 396 prerequisites_for(R, AggregatedBy, Vs, Vt0), 397 prerequisites(T, AggregatedBy, Vt0, Vt). 398 399prerequisites_for(R, AggregatedBy, Vs, Vt) --> 400 { phrase(requires(R, /, Properties, true), Req0), 401 delete(Req0, R, Req) 402 }, 403 prop_edges(Properties), 404 ( {Req == []} 405 -> {Vs = [R|Vt]} 406 ; req_edges(Req, AggregatedBy, R), 407 {Vs = Vt} 408 ). 409 410req_edges([], _, _) --> 411 []. 412req_edges([H|T], AggregatedBy, R) --> 413 ( { get_assoc(H, AggregatedBy, Aggregate) } 414 -> [Aggregate-R] 415 ; [H-R] 416 ), 417 req_edges(T, AggregatedBy, R).
ordered(true)
.424prop_edges(Properties) --> 425 { option(ordered(true), Properties) }, 426 !, 427 ordered_reqs(Properties). 428prop_edges(_) --> []. 429 430ordered_reqs([]) --> []. 431ordered_reqs([H|T]) --> ordered_req(H), ordered_reqs(T). 432 433ordered_req(requires([H|T])) --> 434 { T \== [], 435 !, 436 absolute_req(H, File) 437 }, 438 order_pairs(T, File). 439ordered_req(_) --> []. 440 441order_pairs([H|T], P) --> 442 !, 443 { absolute_req(H, File) 444 }, 445 [ P-File ], 446 order_pairs(T, File). 447order_pairs(_, _) --> []. 448 449absolute_req(Virtual, Abs) :- 450 html_resource(Virtual, _, Properties), 451 option(virtual(true), Properties), 452 !, 453 Abs = 'V'(Virtual). 454absolute_req(Spec, Abs) :- 455 http_absolute_location(Spec, Abs, [relative_to(/)]).
463connect_graph([], 0, []) :- !. 464connect_graph(Graph, Start, [Start-Vertices|Graph]) :- 465 vertices(Graph, Vertices), 466 Vertices = [First|_], 467 before(First, Start).
476before(X, _) :- 477 var(X), 478 !, 479 instantiation_error(X). 480before(Number, Start) :- 481 number(Number), 482 !, 483 Start is Number - 1. 484before(_, 0).
491res_properties(Spec, Properties) :- 492 findall(P, res_property(Spec, P), Properties0), 493 list_to_set(Properties0, Properties). 494 495res_property(Spec, Property) :- 496 same_about(Spec, About), 497 html_resource(About, _, Properties), 498 member(Property, Properties). 499 500:- dynamic 501 same_about_cache/2. 502:- volatile 503 same_about_cache/2. 504 505clean_same_about_cache :- 506 retractall(same_about_cache(_,_)). 507 508same_about(Spec, About) :- 509 same_about_cache(Spec, Same), 510 !, 511 member(About, Same). 512same_about(Spec, About) :- 513 findall(A, uncached_same_about(Spec, A), List), 514 assert(same_about_cache(Spec, List)), 515 member(About, List). 516 517uncached_same_about(Spec, About) :- 518 html_resource(About, _, _), 519 same_resource(Spec, About).
527same_resource(R, R) :- !. 528same_resource(R1, R2) :- 529 resource_base_name(R1, B), 530 resource_base_name(R2, B), 531 http_absolute_location(R1, Path, []), 532 http_absolute_location(R2, Path, []). 533 534:- dynamic 535 base_cache/2. 536:- volatile 537 base_cache/2. 538 539resource_base_name(Spec, Base) :- 540 ( base_cache(Spec, Base0) 541 -> Base = Base0 542 ; uncached_resource_base_name(Spec, Base0), 543 assert(base_cache(Spec, Base0)), 544 Base = Base0 545 ). 546 547uncached_resource_base_name(Atom, Base) :- 548 atomic(Atom), 549 !, 550 file_base_name(Atom, Base). 551uncached_resource_base_name(Compound, Base) :- 552 arg(1, Compound, Base0), 553 file_base_name(Base0, Base).
.js
and .css
files.
Extend this to support more header material. Do not use this
predicate directly. html_requires//1 is the public interface to
include HTML resources.
565html_include([]) --> !. 566html_include([H|T]) --> 567 !, 568 html_include(H), 569 html_include(T). 570html_include(Path) --> 571 { res_property(Path, mime_type(Mime)) 572 }, 573 !, 574 html_include(Mime, Path). 575html_include(Path) --> 576 { file_mime_type(Path, Mime) }, 577 !, 578 html_include(Mime, Path). 579 580html_include(Mime, Path) --> 581 mime_include(Mime, Path), 582 !. % user hook 583html_include(text/css, Path) --> 584 !, 585 html(link([ rel(stylesheet), 586 type('text/css'), 587 href(Path) 588 ], [])). 589html_include(text/javascript, Path) --> 590 !, 591 html(script([ type('text/javascript'), 592 src(Path) 593 ], [])). 594html_include(Mime, Path) --> 595 { print_message(warning, html_include(dont_know, Mime, Path)) 596 }.
text/css
and text/javascript
are tried. For example, to
include a =.pl= files as a Prolog script, use:
:- multifile html_head:mime_include//2. html_head:mime_include(text/'x-prolog', Path) --> !, html(script([ type('text/x-prolog'), src(Path) ], [])).
617 /******************************* 618 * CACHE CLEANUP * 619 *******************************/ 620 621:- multifile 622 user:message_hook/3, 623 prolog:message//1. 624:- dynamic 625 user:message_hook/3. 626 627user:message_hook(load_file(done(_Nesting, _File, _Action, 628 _Module, _Time, _Clauses)), 629 _Level, _Lines) :- 630 clean_same_about_cache, 631 clean_aggregate_cache, 632 fail. 633 634prologmessage(html_include(dont_know, Mime, Path)) --> 635 [ 'Don\'t know how to include resource ~q (mime-type ~q)'- 636 [Path, Mime] 637 ]. 638 639 640 /******************************* 641 * EDIT * 642 *******************************/ 643 644% Allow edit(Location) to edit the :- html_resource declaration. 645:- multifile 646 prolog_edit:locate/3. 647 648prolog_edit:locate(Path, html_resource(Spec), [file(File), line(Line)]) :- 649 atom(Path), 650 html_resource(Spec, File:Line, _Properties), 651 sub_term(Path, Spec)
Automatic inclusion of CSS and scripts links
This library allows for abstract declaration of available CSS and Javascript resources and their dependencies using html_resource/2. Based on these declarations, html generating code can declare that it depends on specific CSS or Javascript functionality, after which this library ensures that the proper links appear in the HTML head. The implementation is based on mail system implemented by html_post/2 of library
html_write.pl
.Declarations come in two forms. First of all http locations are declared using the
http_path.pl
library. Second, html_resource/2 specifies HTML resources to be used in thehead
and their dependencies. Resources are currently limited to Javascript files (.js) and style sheets (.css). It is trivial to add support for other material in the head. See html_include//1.For usage in HTML generation, there is the DCG rule html_requires//1 that demands named resources in the HTML head.
About resource ordering
All calls to html_requires//1 for the page are collected and duplicates are removed. Next, the following steps are taken:
Debugging dependencies
Use ?-
debug(html(script))
. to see the requested and final set of resources. All declared resources are in html_resource/3. The edit/1 command recognises the names of HTML resources.Predicates