/* Part of SWI-Prolog Author: Jan Wielemaker E-mail: J.Wielemaker@cs.vu.nl WWW: http://www.swi-prolog.org Copyright (C): 2014, VU University Amsterdam This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA As a special exception, if you link this library with other files, compiled with a Free Software compiler, to produce an executable, this library does not by itself cause the resulting executable to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. */ :- module(swish_render, [ use_rendering/1, % +Renderer use_rendering/2, % +Renderer, +Options register_renderer/2, % Declare a rendering module current_renderer/2 % Name, Comment ]). :- use_module(library(pengines_io), []). :- use_module(library(http/html_write)). :- use_module(library(http/term_html)). :- use_module(library(option)). :- use_module(library(error)). :- meta_predicate register_renderer(:, +), use_rendering(:), use_rendering(:, +). /** SWISH term-rendering support This module manages rendering answers using alternative vizualizations. The idea is that a specific context _uses_ zero or more rendering modules. These rendering modules provide an alternative HTML representation for the target term. If multiple possible renderings are found, a =|
|= element is generated that contains the alternative renderings. The jQuery plugin =renderMulti=, defined in =answer.js= adds the behaviour to change rendering to the generated div. The user can import rendering schemes into the current context using the directive below. `Spec` is either an atom or string, making the system look for render(Spec), or it is a (single) file specification that can be used for use_module/1. == :- use_rendering(Spec). == A rendering module is a Prolog module that defines the non-terminal term_rendering//3, which will be called as below. `Term` is the (non-var) term that must be rendered, `Vars` is a list of variable names bound to this term and `Options` is a list of write options that would normally be passed to write_term/3. The grammar is executed by library(http/html_write) and must generate compatible tokens (which means it must call html//1 to generate HTML tokens). == phrase(Renderer:term_rendering(Term, Vars, Options), Tokens) == */ :- multifile user:file_search_path/2. user:file_search_path(render, swish('lib/render')). %% use_rendering(+FileOrID) % % Register an answer renderer. Same as use_rendering(FileOrID, % []). % % @see use_rendering/2. :- multifile system:term_expansion/2. use_rendering(Rendering) :- use_rendering(Rendering, []). %% use_rendering(:ID, +Options) % % Register an answer renderer with options. Options are merged % with write-options and passed to the non-terminal % term_rendering//3 defined in the rendering module. use_rendering(Rendering, Options) :- Rendering = Into:Renderer, must_be(atom, Renderer), ( renderer(Renderer, _, _) -> true ; existence_error(renderer, Renderer) ), retractall(Into:'swish renderer'(Renderer, _)), assertz(Into:'swish renderer'(Renderer, Options)). system:term_expansion((:- use_rendering(Renderer)), Expanded) :- expand_rendering(Renderer, [], Expanded). system:term_expansion((:- use_rendering(Renderer, Options)), Expanded) :- expand_rendering(Renderer, Options, Expanded). expand_rendering(Module:Renderer, Options, [ (:- discontiguous(Module:'swish renderer'/2)), Module:'swish renderer'(Renderer, Options) ]) :- !, must_be(atom, Module), must_be(atom, Renderer). expand_rendering(Renderer, Options, [ (:- discontiguous('swish renderer'/2)), 'swish renderer'(Renderer, Options) ]) :- must_be(atom, Renderer). %% pengines_io:binding_term(+Term, +Vars, +Options) is semidet. % % Produce alternative renderings for Term, which is a binding for % Vars. :- multifile pengines_io:binding_term//3. pengines_io:binding_term(Term, Vars, Options) --> { option(module(Module), Options), findall(Tokens, call_term_rendering(Module, Term, Vars, Options, Tokens), NestedTokens), NestedTokens \== [], ! }, alt_renderer(NestedTokens, Term, Options). %% call_term_rendering(+Module, +Term, +Vars, +Options, -Tokens) is nondet. % % Call term_rendering//3 in all modules from which Module % inherits. call_term_rendering(Module, Term, Vars, Options, Tokens) :- State = state([]), default_module(Module, Target), current_predicate(Target:'swish renderer'/2), Target:'swish renderer'(Name, RenderOptions), atom(Name), is_new(State, Name), renderer(Name, RenderModule, _Comment), merge_options(RenderOptions, Options, AllOptions), catch(phrase(RenderModule:term_rendering(Term, Vars, AllOptions), Tokens), E, rendering_error(E, Name, Tokens)). rendering_error(Error, Renderer, Tokens) :- message_to_string(Error, Msg), phrase(html(div(class('render-error'), [ 'Renderer ', span(Renderer), ' error: ', span(class(error), Msg) ])), Tokens). %% is_new(!State, +M) is semidet. % % Only succeeds once for each new ground value M. is_new(State, M) :- arg(1, State, Seen), ( memberchk(M, Seen) -> fail ; nb_linkarg(1, State, [M|Seen]) ). %% alt_renderer(+Specialised, +Term, +Options)// % % Create a rendering selection object after we have found at least % one alternative rendering for Term. alt_renderer(Specialised, Term, Options) --> html(div(class('render-multi'), \specialised(Specialised, Term, Options))). specialised([], Term, Options) --> html(span([ class('render-as-prolog'), 'data-render'('Prolog term') ], \term(Term, Options))). specialised([H|T], Term, Options) --> tokens(H), specialised(T, Term, Options). tokens([]) --> []. tokens([H|T]) --> [H], tokens(T). /******************************* * REGISTRATION * *******************************/ :- multifile renderer/3. %% current_renderer(Name, Comment) is nondet. % % True when renderer Name is declared with Comment. current_renderer(Name, Comment) :- renderer(Name, _Module, Comment). %% register_renderer(:Name, +Comment) % % Register a module as SWISH rendering component. register_renderer(Name, Comment) :- throw(error(context_error(nodirective, register_renderer(Name, Comment)), _)). system:term_expansion((:- register_renderer(Name, Comment)), swish_render:renderer(Name, Module, Comment)) :- prolog_load_context(module, Module).