1/* Part of SWISH 2 3 Author: Jan Wielemaker 4 E-mail: J.Wielemaker@vu.nl 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2014-2016, 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(swish_render, 36 [ use_rendering/1, % +Renderer 37 use_rendering/2, % +Renderer, +Options 38 39 register_renderer/2, % Declare a rendering module 40 current_renderer/2 % Name, Comment 41 ]). 42:- use_module(library(pengines_io), []). 43:- use_module(library(http/html_write)). 44:- use_module(library(http/term_html)). 45:- use_module(library(option)). 46:- use_module(library(error)). 47 48:- meta_predicate 49 register_renderer( , ), 50 use_rendering( ), 51 use_rendering( , ). 52 53/** <module> SWISH term-rendering support 54 55This module manages rendering answers using alternative vizualizations. 56The idea is that a specific context _uses_ zero or more rendering 57modules. These rendering modules provide an alternative HTML 58representation for the target term. If multiple possible renderings are 59found, a =|<div class="render-multi">|= element is generated that 60contains the alternative renderings. The jQuery plugin =renderMulti=, 61defined in =answer.js= adds the behaviour to change rendering to the 62generated div. 63 64The user can import rendering schemes into the current context using the 65directive below. `Spec` is either an atom or string, making the system 66look for render(Spec), or it is a (single) file specification that can 67be used for use_module/1. 68 69 == 70 :- use_rendering(Spec). 71 == 72 73A rendering module is a Prolog module that defines the non-terminal 74term_rendering//3, which will be called as below. `Term` is the 75(non-var) term that must be rendered, `Vars` is a list of variable names 76bound to this term and `Options` is a list of write options that would 77normally be passed to write_term/3. The grammar is executed by 78library(http/html_write) and must generate compatible tokens (which 79means it must call html//1 to generate HTML tokens). 80 81 == 82 phrase(Renderer:term_rendering(Term, Vars, Options), Tokens) 83 == 84*/ 85 86:- multifile user:file_search_path/2. 87 88user:file_search_path(render, swish('lib/render')). 89 90 91%% use_rendering(+FileOrID) 92% 93% Register an answer renderer. Same as use_rendering(FileOrID, 94% []). 95% 96% @see use_rendering/2. 97 98:- multifile system:term_expansion/2. 99 100use_rendering(Rendering) :- 101 use_rendering(Rendering, []). 102 103%% use_rendering(:ID, +Options) 104% 105% Register an answer renderer with options. Options are merged 106% with write-options and passed to the non-terminal 107% term_rendering//3 defined in the rendering module. 108 109use_rendering(Rendering, Options) :- 110 Rendering = Into:Renderer, 111 must_be(atom, Renderer), 112 ( renderer(Renderer, _, _) 113 -> true 114 ; existence_error(renderer, Renderer) 115 ), 116 retractall(Into:'swish renderer'(Renderer, _)), 117 assertz(Into:'swish renderer'(Renderer, Options)). 118 119systemterm_expansion((:- use_rendering(Renderer)), Expanded) :- 120 expand_rendering(Renderer, [], Expanded). 121systemterm_expansion((:- use_rendering(Renderer, Options)), Expanded) :- 122 expand_rendering(Renderer, Options, Expanded). 123 124expand_rendering(Module:Renderer, Options, 125 [ (:- discontiguous(Module:'swish renderer'/2)), 126 Module:'swish renderer'(Renderer, Options) 127 ]) :- !, 128 must_be(atom, Module), 129 must_be(atom, Renderer). 130expand_rendering(Renderer, Options, 131 [ (:- discontiguous('swish renderer'/2)), 132 'swish renderer'(Renderer, Options) 133 ]) :- 134 must_be(atom, Renderer). 135 136%% pengines_io:binding_term(+Term, +Vars, +Options) is semidet. 137% 138% Produce alternative renderings for Term, which is a binding for 139% Vars. 140 141:- multifile pengines_io:binding_term//3. 142 143pengines_iobinding_term(Term, Vars, Options) --> 144 { option(module(Module), Options), 145 findall(Tokens, 146 call_term_rendering(Module, Term, Vars, Options, Tokens), 147 NestedTokens), 148 NestedTokens \== [], ! 149 }, 150 alt_renderer(NestedTokens, Term, Options). 151 152%% call_term_rendering(+Module, +Term, +Vars, +Options, -Tokens) is nondet. 153% 154% Call term_rendering//3 in all modules from which Module 155% inherits. 156 157call_term_rendering(Module, Term, Vars, Options, Tokens) :- 158 State = state([]), 159 default_module(Module, Target), 160 current_predicate(Target:'swish renderer'/2), 161 Target:'swish renderer'(Name, RenderOptions), 162 atom(Name), 163 is_new(State, Name), 164 renderer(Name, RenderModule, _Comment), 165 merge_options(RenderOptions, Options, AllOptions), 166 catch(phrase(RenderModule:term_rendering(Term, Vars, AllOptions), Tokens), 167 E, rendering_error(E, Name, Tokens)). 168 169rendering_error(Error, Renderer, Tokens) :- 170 message_to_string(Error, Msg), 171 phrase(html(div(class('render-error'), 172 [ 'Renderer ', span(Renderer), 173 ' error: ', span(class(error), Msg) 174 ])), Tokens). 175 176 177%% is_new(!State, +M) is semidet. 178% 179% Only succeeds once for each new ground value M. 180 181is_new(State, M) :- 182 arg(1, State, Seen), 183 ( memberchk(M, Seen) 184 -> fail 185 ; nb_linkarg(1, State, [M|Seen]) 186 ). 187 188%% alt_renderer(+Specialised, +Term, +Options)// 189% 190% Create a rendering selection object after we have found at least 191% one alternative rendering for Term. 192 193alt_renderer(Specialised, Term, Options) --> 194 html(div(class('render-multi'), 195 \specialised(Specialised, Term, Options))). 196 197specialised([], Term, Options) --> 198 html(span([ class('render-as-prolog'), 199 'data-render'('Prolog term') 200 ], 201 \term(Term, Options))). 202specialised([H|T], Term, Options) --> 203 tokens(H), 204 specialised(T, Term, Options). 205 206tokens([]) --> []. 207tokens([H|T]) --> [H], tokens(T). 208 209 210 /******************************* 211 * REGISTRATION * 212 *******************************/ 213 214:- multifile 215 renderer/3. 216 217%% current_renderer(Name, Comment) is nondet. 218% 219% True when renderer Name is declared with Comment. 220 221current_renderer(Name, Comment) :- 222 renderer(Name, _Module, Comment). 223 224%% register_renderer(:Name, +Comment) 225% 226% Register a module as SWISH rendering component. 227 228register_renderer(Name, Comment) :- 229 throw(error(context_error(nodirective, register_renderer(Name, Comment)), 230 _)). 231 232systemterm_expansion((:- register_renderer(Name, Comment)), 233 swish_render:renderer(Name, Module, Comment)) :- 234 prolog_load_context(module, Module)