1 % Heinrich Heine Universitaet Duesseldorf
2 % (c) 2021-2026 Lehrstuhl fuer Softwaretechnik und Programmiersprachen,
3 % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html)
4
5 :- module(visb_html_export,[generate_visb_html_for_history/1, generate_visb_html_for_history/2,
6 generate_visb_html_for_history_with_vars/1,
7 generate_visb_html_for_history_with_source/1,
8 generate_visb_html_for_current_state/1, generate_visb_html_for_current_state/2,
9 generate_visb_html_codes_for_states/3,
10 generate_visb_html/3,
11 get_visb_default_svg_file_contents/1]).
12
13 :- use_module(probsrc(module_information),[module_info/2]).
14 :- module_info(group,visualization).
15 :- module_info(description,'This module provides the VisB HTML export.').
16
17 :- use_module(library(lists)).
18 :- use_module(library(codesio),[write_to_codes/2]).
19 :- use_module(library(file_systems), [file_exists/1, file_property/3, delete_file/1]).
20 :- use_module(library(system), [datime/1]).
21 :- use_module(probsrc(error_manager)).
22 :- use_module(probsrc(preferences), [get_preference/2]).
23 :- use_module(probsrc(debug)).
24 :- use_module(probsrc(state_space), [visited_expression/2,
25 visited_state_corresponds_to_initialised_b_machine/1,
26 transition/4, invariant_violated/1, invariant_not_yet_checked/1,
27 current_state_id/1, time_out_for_node/3,
28 get_action_trace_with_limit/2, get_state_id_trace/1]).
29 :- use_module(probsrc(translate), [translate_bvalue_to_codes/2,
30 translate_bvalue_to_codes_with_limit/3,
31 translate_bexpression_with_limit/3, pretty_type/2,
32 set_unicode_mode/0, unset_unicode_mode/0,
33 translate_event_with_src_and_target_id/5]).
34 :- use_module(probsrc(specfile), [xtl_mode/0, b_or_z_mode/0,
35 state_corresponds_to_fully_setup_b_machine/2,
36 expand_to_constants_and_variables/3,
37 extract_variables_from_state/2, currently_opened_file/1,
38 get_internal_representation/1,
39 get_operation_description_for_transition/4,
40 get_specification_description/2]).
41 :- use_module(probsrc(tools), [start_ms_timer/1, stop_ms_walltimer_with_msg/2,
42 html_escape_codes/2, read_string_from_file/2,
43 b_string_escape_codes/2, gen_relative_path/3, string_escape/2]).
44 :- use_module(probsrc(tools_io), [safe_open_file/4, with_open_stream_to_codes/4,
45 write_file_to_stream/2]).
46 :- use_module(probsrc(tools_strings), [ajoin/2, number_codes_min_length/3, format_modified_info/2,
47 atom_tail_to_lower_case/2]).
48 :- use_module(probsrc(bmachine), [b_machine_name/1,
49 b_get_machine_variables/1, b_get_machine_constants/1,
50 b_top_level_operation/1, b_get_machine_animation_expression/2,
51 b_get_operation_variant/3, b_nth1_non_ignored_invariant/3,
52 b_get_machine_operation_signature/2, b_get_machine_operation_max/2]).
53 :- use_module(probsrc(bsyntaxtree), [get_texpr_type/2, get_texpr_id/2,
54 get_texpr_description/2, safe_create_texpr/3]).
55 :- use_module(probsrc(gensym), [gensym/2]).
56 :- use_module(probsrc(version), [version_str/1]).
57 :- use_module(extrasrc(bvisual2), [bv_get_top_level/1, bv_get_value_unlimited/3, bv_top_level_set/2]).
58 :- use_module(extrasrc(meta_interface),[command_description/3, command_category/2, command_unavailable/2,
59 call_command/5, call_dot_command/2]).
60 :- use_module(probsrc(tcltk_interface),[tcltk_forward/0, tcltk_fast_backtrack/0]).
61 :- use_module(visb_visualiser, [get_change_attribute_for_state/4, get_visb_attributes_for_state/2,
62 get_and_eval_special_definition/6, evaluate_visb_formula/6,
63 b_value_to_string/2, add_visb_lint_message/3,
64 no_svg_available/0, match_operation/2,
65 visb_file_loaded/4, visb_svg_file/5, visb_empty_svg_box_height_width/3,
66 visb_svg_object/5, visb_svg_child/2, visb_svg_parent/2,
67 visb_svg_child_of_object_from_svg_file/1,
68 visb_has_hovers/1, visb_hover/6]).
69
70 % Note: errors and warnings are still reported with source visb_visualiser
71
72 % --------------------------
73
74 % store templates as facts, so that compiled version of probcli does not need to find files
75 :- dynamic visb_template_file_codes/2.
76
77 assert_from_template(Filename) :- %formatsilent('Loading ~w~n',[Filename]),
78 absolute_file_name(visbsrc(Filename), Absolute, []),
79 read_string_from_file(Absolute,String),
80 assertz(visb_template_file_codes(Filename,String)).
81
82 :- assert_from_template('visb_template_header.html').
83 :- assert_from_template('visb_template_svg_downloads.html').
84 :- assert_from_template('visb_template_replayTrace.html').
85 :- assert_from_template('visb_template_middle.html').
86 :- assert_from_template('visb_template_footer.html').
87
88 write_visb_template(HtmlFile,Stream) :-
89 visb_template_file_codes(HtmlFile,Codes),
90 format(Stream,'~s~n',[Codes]).
91
92
93 % ----------------------------------
94
95 % generate a stand-alone HTML file with a visualisation for all states in the list
96
97 generate_visb_html(StateIds,File,Options) :-
98 safe_open_file(File,write,Stream,[encoding(utf8)]),
99 call_cleanup(generate_html_to_stream(StateIds,Stream,[html_file/File|Options]),
100 close(Stream)).
101
102 generate_visb_html_to_codes(StateIds,Options,Codes) :-
103 with_open_stream_to_codes(
104 generate_html_to_stream(StateIds,Stream,Options),
105 Stream,Codes,[]).
106
107
108 % generate an HTML file to visualise the given StateId
109 generate_html_to_stream(StateIds,Stream,Options) :-
110 set_id_namespace_prefix_for_states(StateIds,Options),
111 write_visb_template('visb_template_header.html',Stream),
112 (member(show_svg_downloads,Options) -> write_visb_template('visb_template_svg_downloads.html',Stream) ; true),
113 format(Stream,' <script>~n',[]),
114 retractall(js_generated_function(_)),
115 retractall(last_attribute_values(_,_,_)), % in case a previous export was aborted
116 length(StateIds,Len),
117 format(Stream,' const differences = [~n',[]),
118 maplist(gen_visb_visualise_function(Stream,Options,Len,StateIds),StateIds),
119 write_visb_template('visb_template_replayTrace.html',Stream),
120 format(Stream,' </script>~n~n',[]),
121 gen_registerHovers_script(Stream,Options),
122 write_visb_template('visb_template_middle.html',Stream),
123 (no_svg_available
124 -> format(Stream,' <button type="button" class="collapsible-style">No SVG Visualisation Available</button>~n',[])
125 ; % generate SVG section
126 (member(no_header,Options) -> true
127 ; format(Stream,' <button type="button" class="collapsible collapsible-style active">SVG Visualisation</button>~n',[])
128 ),
129 format(Stream,' <div style="position: relative;">~n',[]), % container for SVG download buttons
130 format(Stream,' <div text-align="left" id="visb_svg_outer_container" class="svg-outer-container">~n',[]),
131 format(Stream,' <div id="visb_svg_inner_container" class="svg-inner-container">~n',[]),
132 %format(Stream,' <fieldset> <legend>Visualisation:</legend>~n',[]), % comment in to draw a border
133 (StateIds = [SingleState] -> get_state_and_action(SingleState,GenForState,_,_) % can be change_to_state_via_action/3
134 ; GenForState=root),
135 copy_svg_file_and_gen_objects(Stream,GenForState,_SVGinlined),
136 % TO DO: avoid creating JS code below if _SVGinlined
137 %format(Stream,' </fieldset>~n',[]),
138 format(Stream,' </div>~n',[]), % inner container
139 format(Stream,' </div>~n',[]), % outer container
140 format(Stream,' <button id="btnResetScale" class="visualisation-button" onclick="resetScale()">Reset View</button>~n',[]),
141 (member(show_svg_downloads,Options) ->
142 format(Stream,' <button onclick="downloadSVG()">Save SVG (Current State)</button>~n',[]),
143 format(Stream,' <button onclick="downloadAllSVGs()">Save all SVGs</button>~n',[])
144 ; true),
145 format(Stream,' </div>~n',[]) % container for SVG download buttons
146 ),
147 (Len>1, memberchk(show_sequence_chart,Options) % don't export PlantUML for list of state IDs (sequence chart depends on trace)
148 -> (create_temp_puml_files(Len,Files),
149 uml_generator:write_uml_sequence_chart_all_states(Files), % generate .puml files
150 tools_commands:gen_plantuml_output(Files,svg,[]) % generate .svg files
151 -> format(Stream,' <button type="button" class="collapsible collapsible-style active">Sequence Chart Visualisation</button>~n',[]),
152 format(Stream,' <div class="seq-chart-container">~n',[]),
153 reverse(Files,RFiles),
154 write_temp_puml_outputs(1,RFiles,Stream), % write SVG contents to HTML
155 format(Stream,' </div>~n',[])
156 ; add_warning(visb_visualiser,'HTML export: did not write the Sequence Diagram Visualisation due to an error in the PlantUML call.'),
157 format(Stream,' <button type="button" class="collapsible-style">No Sequence Chart Visualisation Available</button>~n',[])
158 )
159 ; true),
160 (Len>1
161 -> generate_graph_table_visualisations(Len,Options,Stream),
162 %format(Stream,'~n<h3>Run Trace</h3>~n',[]),
163 format(Stream,' <button type="button" class="collapsible-style">Replay Trace</button>~n',[]),
164 format(Stream,' <div class="coll-content-vis">~n',[]),
165 format(Stream,' <button id="btnPrev" onclick="backStep()" title="Go back one step">« Back</button>~n',[]),
166 format(Stream,' <button id="btnNext" onclick="forwardStep()" title="Go forward one step">Forward »</button>~n',[]),
167 format(Stream,' <button onclick="runAll(10)" title="Fast replay of entire trace">Run Trace (10 ms delay)</button>~n',[]),
168 format(Stream,' <button onclick="runAll(500)" title="Slow replay of entire trace">Run Trace (500 ms delay)</button>~n',[]),
169 format(Stream,' <button onclick="runAll(parseInt(delayInput.value))" title="Custom replay of entire trace">Run Trace with Delay (ms):</button>~n',[]),
170 format(Stream,' <input id="delayInput" type="number" value="1000" min="0" step="100" onkeydown="if(event.key===\'Enter\') runAll(parseInt(this.value))">~n',[]),
171 format(Stream,' <label class="visb-messages" style="float:right">~n',[]),
172 format(Stream,' <input type="checkbox" id="checkbox_focus_svg"> Focus changes in SVG~n',[]),
173 format(Stream,' </label>~n',[]),
174 % We could decide to provide an option/preference for generating the visb_debug_messages field:
175 % (check that ProB2-UI also generates this text field)
176 format(Stream,' <br><label id="visb_debug_messages" class="visb-messages"> </label>~n',[]),
177 format(Stream,' </div>~n',[]),
178 format(Stream,' <progress id="trace_meter" min="0" max="~w" value="0"></progress>~n',[Len]) % progress of trace replay; note <meter> element also works
179 ; true
180 ),
181 % generate a table with variables, constants, ...
182 gen_state_table(Stream,Options,StateIds),
183 gen_source_code_section(Stream,Options),
184 % generate a table with the saved trace steps:
185 (Len>1 -> gen_trace_table(Stream,StateIds) ; true),
186 write_version_info(Stream,Options),
187 (last(StateIds,_)
188 -> format(Stream,' <script> replayStep(~w,false); </script>~n',[Len])
189 % show the visualisation of the last state by default (without focussing affected elements)
190 ; true % trace is empty, there is no last item
191 ),
192 write_visb_template('visb_template_footer.html',Stream),
193 clear_id_namespace_prefix.
194
195 create_temp_puml_files(Len,[]) :- Len<1, !.
196 create_temp_puml_files(Len,[Path|T]) :-
197 system_call:get_temporary_filename('for_html.puml',Path),
198 NewLen is Len-1,
199 create_temp_puml_files(NewLen,T).
200
201
202 write_temp_puml_outputs(_,[],_).
203 write_temp_puml_outputs(Nr,[File|T],Stream) :-
204 tools:split_filename(File,FileRoot,_Ext),
205 ajoin([FileRoot,'.svg'],SvgFile),
206 format(Stream,' <div id="seq_chart_~w" style="display:none">~n',[Nr]), % container for seq. charts
207 write_file_to_stream(SvgFile,Stream),
208 format(Stream,' </div>~n',[]), % container for seq. charts
209 delete_temp_file(File), delete_temp_file(SvgFile),
210 NNr is Nr+1,
211 write_temp_puml_outputs(NNr,T,Stream).
212
213 % -----------------
214 % GRAPHVIZ / TABLE SECTIONS
215
216 generate_graph_table_visualisations(Len,Options,Stream) :-
217 member(gen_graph_table(Cmd),Options),
218 command_category(Cmd,Cat), (Cat=dot ; Cat=table),
219 command_description(Cmd,CmdName,CmdDesc),
220 (\+ command_unavailable(Cmd,_) % TODO: only for current history, not for list of state ids
221 -> html_text_codes(CmdDesc,ECmdDesc), html_text_codes(CmdName,ECmdName),
222 format(Stream,' <button type="button" class="collapsible collapsible-style active" title="~s">~s</button>~n',[ECmdDesc,ECmdName]),
223 format(Stream,' <div class="coll-content-vis">~n',[]),
224 tcltk_fast_backtrack,
225 (tcltk_forward
226 -> (Cat=dot -> write_dot_graphs(Cmd,1,Len,Stream) ;
227 Cat=table -> write_table_vis(Cmd,1,Len,Stream))
228 ; true),
229 format(Stream,' </div>~n',[])
230 ; format(Stream,' <button type="button" class="collapsible-style">No ~w Available</button>~n',[CmdName])),
231 fail.
232 generate_graph_table_visualisations(_,_,_).
233
234 % write the graph for the current state Nr and then move forward until state Len of the history is reached
235 % (i.e., the state the animator was in before the export, also when there is a forward history)
236 write_dot_graphs(Cmd,Nr,Len,Stream) :-
237 write_dot_graph(Cmd,Nr,Stream),
238 NNr is Nr+1,
239 (Nr<Len, tcltk_forward -> write_dot_graphs(Cmd,NNr,Len,Stream) ; true).
240
241 write_dot_graph(Cmd,Nr,Stream) :-
242 \+ state_space:current_state_corresponds_to_initialised_b_machine, !,
243 format(Stream,' <div id="~w_~w" style="display:none">No Graph Visualisation Available</div>~n',[Cmd,Nr]).
244 write_dot_graph(Cmd,Nr,Stream) :-
245 system_call:get_temporary_filename('for_html.dot',DotFile),
246 system_call:get_temporary_filename('for_html.svg',File),
247 call_dot_command(Cmd,DotFile),
248 tools_commands:gen_dot_output(DotFile,dot,svg,File),
249 tools:split_filename(File,FileRoot,_Ext),
250 ajoin([FileRoot,'.svg'],SvgFile),
251 format(Stream,' <div id="~w_~w" style="display:none">~n',[Cmd,Nr]),
252 write_file_to_stream(SvgFile,Stream),
253 format(Stream,' </div>~n',[]),
254 delete_temp_file(DotFile), delete_temp_file(File), delete_temp_file(SvgFile).
255
256 delete_temp_file(File) :-
257 (file_exists(File)
258 -> catch(delete_file(File),E,add_debug_message(visb_visualiser,'Could not delete temporary file: ',E:File))
259 ; true).
260
261 write_table_vis(Cmd,Nr,Len,Stream) :-
262 write_table_vis_for_state(Cmd,Nr,Stream),
263 NNr is Nr+1,
264 (Nr<Len, tcltk_forward -> write_table_vis(Cmd,NNr,Len,Stream) ; true).
265
266 write_table_vis_for_state(Cmd,Nr,Stream) :-
267 \+ state_space:current_state_corresponds_to_initialised_b_machine, !,
268 format(Stream,' <div id="~w_~w" style="display:none">No Table Available</div>~n',[Cmd,Nr]).
269 write_table_vis_for_state(Cmd,Nr,Stream) :-
270 call_command(table,Cmd,_,[TableResult],[]),
271 format(Stream,' <div id="~w_~w" style="display:none">~n',[Cmd,Nr]),
272 format(Stream,' <table>~n',[]),
273 TableResult = list([list(Keys)|Elements]),
274 % Keys:
275 format(Stream,' <tr>',[]),
276 write_html_table_elements(th,Keys,Stream),
277 format(Stream,' </tr>',[]),
278 % Elements:
279 write_html_table(Elements,Stream),
280 format(Stream,' </table>~n',[]),
281 format(Stream,' </div>~n',[]).
282
283 write_html_table([],_).
284 write_html_table([list(Elements)|T],Stream) :-
285 format(Stream,' <tr>',[]),
286 write_html_table_elements(td,Elements,Stream),
287 format(Stream,' </tr>',[]),
288 write_html_table(T,Stream).
289
290 write_html_table_elements(_,[],_).
291 write_html_table_elements(Tag,[Element|T],Stream) :-
292 format(Stream,' <~w>',[Tag]), format_html_text(Stream,Element), format(Stream,'</~w>~n',[Tag]),
293 write_html_table_elements(Tag,T,Stream).
294
295 % -----------------
296
297 write_version_info(_,Options) :-
298 member(no_version_info,Options),!.
299 write_version_info(Stream,Options) :-
300 version_str(Str),datime(datime(Yr,Mon,Day,Hr,Min,_Sec)),
301 number_codes_min_length(Min,2,MC),
302 format(Stream,' <button type="button" class="collapsible-style" title="Information about ProB and model">Info</button>~n',[]),
303 format(Stream,'<div class="coll-content-vis visb-messages">~n',[]),
304 format(Stream,'Generated on ~w/~w/~w at ~w:~s using <a href="https://prob.hhu.de/">ProB</a> version ~w~n',[Day,Mon,Yr,Hr,MC,Str]),
305 (currently_opened_file(File)
306 -> (File=package(Type) ->
307 format(Stream,'<br>Main specification package: ',[]), format_html_text(Stream,Type), nl(Stream)
308 ; atom(File), get_relative_path(File,Options,Suffix) ->
309 format(Stream,'<br>Main specification file: ',[]), format_html_text(Stream,Suffix),
310 format_file_info(Stream,File)
311 ; format(Stream,'<br>Unknown main specification file: ',[]), format_html_text(Stream,File), nl(Stream)
312 )
313 ; true),
314 (b_machine_name(MainName)
315 -> format(Stream,'<br>Main specification name: ',[]), format_html_text(Stream,MainName), nl(Stream)
316 ; true),
317 (visb_file_loaded(JSONFile,_,ModLocTime,_),
318 JSONFile \= ''
319 -> get_relative_path(JSONFile,Options,JSuffix),
320 format(Stream,'<br>Main VisB JSON file: ',[]), format_html_text(Stream,JSuffix),
321 format_modified_info(Stream,ModLocTime),
322 (get_non_empty_svg_file(SVGFile,ModLocTime2)
323 -> get_relative_path(SVGFile,Options,SSuffix),
324 format(Stream,'<br>VisB SVG file: ',[]), format_html_text(Stream,SSuffix),
325 format_modified_info(Stream,ModLocTime2)
326 ; true
327 )
328 ; true).
329
330 get_non_empty_svg_file(SVGFile,ModLocTime2) :-
331 visb_svg_file(RelFile,SVGFile,_,_,ModLocTime2),
332 RelFile \= ''.
333
334 format_file_info(Stream,File) :-
335 catch(file_property(File, modify_localtime, ModLocTime),
336 E,
337 (add_visb_lint_message('Could not obtain modification time for file',E,unknown), fail)),
338 !,
339 format_modified_info(Stream,ModLocTime).
340 format_file_info(Stream,_) :- nl(Stream).
341
342
343 get_relative_path(File,Options,RelPath) :-
344 member(html_file/HF,Options),
345 absolute_file_name(File,AF,[]),
346 absolute_file_name(HF,AHF,[]),
347 !,
348 gen_relative_path(AF,AHF,RelPath). % only print relative information
349 get_relative_path(File,_,File).
350
351
352 % -----------------
353
354 % gen a table with all the individual steps of the trace
355 gen_trace_table(Stream,StateIds) :-
356 length(StateIds,Len),
357 %format(Stream,'~n<h3>Trace (length=~w)</h3>~n',[Len]),
358 format(Stream,' <button type="button" class="collapsible collapsible-style active">Trace (length=~w)</button>~n',[Len]),
359 format(Stream,'<div class="coll-content-vis">~n',[]),
360 (member(Obj,StateIds), get_state_and_action(Obj,_,_,DescStr), DescStr \= ''
361 -> Opt=with_description ; Opt=no_description),
362 gen_trace_table2(Stream,StateIds,Opt).
363
364 gen_trace_table2(Stream,StateIds,no_description) :-
365 format(Stream,' <table> <tr> <th>Nr</th> <th>Event</th> <th>Target State ID</th> </tr>~n',[]),
366 nth1(Nr,StateIds,Obj),
367 get_state_and_action(Obj,StateId,ActionStr,_),
368 atom_codes(ActionStr,ActionCodes),html_escape_codes(ActionCodes,EAS),
369 ((visited_state_corresponds_to_initialised_b_machine(StateId) ; xtl_mode) % allow onclick to all rows in xtl_mode
370 -> (invariant_violated(StateId) -> BtnStyle='style="background-color:rgb(255,179,179);border-radius:6px" ' ; BtnStyle=''),
371 format(Stream,'~n <tr id="row~w" onclick="replayStep(~w)"><td>~w</td><td style="cursor:pointer">~s</td><td><button ~wonclick="replayStep(~w);">State ~w</button></td></tr>~n',
372 [Nr,Nr,Nr,EAS,BtnStyle,Nr,StateId])
373 ; format(Stream,'~n <tr id="row~w"><td>~w</td><td style="cursor:not-allowed">~s</td><td>State ~w</td></tr>~n',
374 [Nr,Nr,EAS,StateId])
375 ),
376 fail.
377 gen_trace_table2(Stream,StateIds,with_description) :-
378 format(Stream,' <table> <tr> <th>Nr</th> <th>Event</th> <th>Description</th> <th>Target State ID</th> </tr>~n',[]),
379 nth1(Nr,StateIds,Obj),
380 get_state_and_action(Obj,StateId,ActionStr,DescStr),
381 atom_codes(ActionStr,ActionCodes),html_escape_codes(ActionCodes,EAS),
382 atom_codes(DescStr,DescCodes),html_escape_codes(DescCodes,EDS),
383 ((visited_state_corresponds_to_initialised_b_machine(StateId) ; xtl_mode) % allow onclick to all rows in xtl_mode
384 -> (invariant_violated(StateId) -> BtnStyle='style="background-color:rgb(255,179,179);border-radius:6px" ' ; BtnStyle=''),
385 format(Stream,'~n <tr id="row~w" onclick="replayStep(~w)"><td>~w</td><td style="cursor:pointer">~s</td><td>~s</td><td><button ~wonclick="replayStep(~w);">State ~w</button></td></tr>~n',
386 [Nr,Nr,Nr,EAS,EDS,BtnStyle, Nr,StateId])
387 ; format(Stream,'~n <tr id="row~w"><td>~w</td><td style="cursor:not-allowed">~s</td><td>~s</td><td>State ~w</td></tr>~n',
388 [Nr,Nr,EAS,EDS,StateId])
389 ),
390 fail.
391 gen_trace_table2(Stream,_,_) :-
392 format(Stream,' </table>~n </div>~n',[]).
393
394
395 % utility to allow passing information about name of event/operation/action leading to state
396 get_state_and_action(change_to_state_via_action(StateId,ActionStr,DescStr),StateId,ActionStr,DescStr) :- !.
397 get_state_and_action(StateId,StateId,'','').
398
399 % -----------------
400
401 % inline provided svg file into html file:
402 % second argument is either root or a particular state id for which the SVG file is to be created
403 % the last argument is static_svg_inlined if all SVG objects were "inlined" into a single
404 % SVG object (rather than via JavaScript functions to create the objects)
405 copy_svg_file_and_gen_objects(Stream,_,static_svg_not_inlined) :- visb_svg_file(_,File,_,_,_), file_exists(File),
406 !,
407 write_file_to_stream(File,Stream),
408 ( get_visb_contents_def(Contents,DefName,DefPos,static_svg_not_inlined),
409 (DefName = default_svg_contents -> true % no need to inform user about our own default contents
410 ; ajoin(['Copying ',DefName,' *after* SVG file: '],Msg),
411 add_message(visb_visualiser,Msg,File,DefPos)),
412 format(Stream,'~w~n',[Contents]),
413 fail
414 ; true
415 ),
416 gen_new_svg_objects(Stream).
417 copy_svg_file_and_gen_objects(_,_,_) :-
418 visb_svg_file(File,AFile,_,Pos,_),
419 File \= '',
420 add_error(visb_visualiser,'The specified VisB SVG file does not exist:',AFile,Pos),
421 fail.
422 copy_svg_file_and_gen_objects(Stream,SingleStateId,static_svg_inlined) :- % copy an empty SVG stub
423 visb_file_loaded(_,_,_,_),!,
424 write_default_svg_contents(Stream,inline_objects(SingleStateId)).
425 copy_svg_file_and_gen_objects(_,_,static_svg_not_inlined). % no SVG required; we have no items or events: probably just want HTML export of variables,...
426
427 % TODO: precompile and always assert as visb_special_definition (we could also evaluate it; could use template strings):
428 get_visb_contents_def(Contents,DefName,DefPos,InlineObjects) :-
429 get_and_eval_special_definition('VISB_SVG_CONTENTS',DefName,visb_contents,DefPos,ResValue,InlineObjects),
430 b_value_to_string(ResValue,Contents).
431 get_visb_contents_def(Contents,default_svg_contents,unknown,_InlineObjects) :-
432 % default content, for VISB_CLICK_META_INFOS'jsVars
433 Contents = '<script>let visb_vars = {};</script>'.
434
435 write_default_svg_contents(Stream,InlineObjects) :-
436 (visb_empty_svg_box_height_width(H,W,ViewBox) -> true ; H=400, W=400, ViewBox=''),
437 format(Stream,'<svg xmlns=\"http://www.w3.org/2000/svg\"~n',[]),
438 (ViewBox=''
439 -> format(Stream,' width="~w" height="~w" viewBox="0 0 ~w ~w" >~n',[W,H,W,H])
440 ; format(Stream,' width="~w" height="~w" viewBox="~w" >~n',[W,H,ViewBox])
441 ),
442 ( get_visb_contents_def(Contents,_,_,InlineObjects),
443 format(Stream,'~w~n',[Contents]),
444 fail
445 ; true),
446 (InlineObjects=inline_objects(StateID) -> inline_new_svg_objects(Stream,StateID) ; true),
447 format(Stream,'</svg>~n',[]).
448
449 % get SVG file contents in case no SVG file is provided by user
450 get_visb_default_svg_file_contents(Codes) :-
451 with_open_stream_to_codes(
452 write_default_svg_contents(Stream,inline_objects(root)),
453 % allow inlining for groups and title children in ProB2-UI
454 Stream,
455 Codes, []).
456
457 % -----------------
458
459 % create a script to add all additional SVG objects
460 gen_new_svg_objects(_) :- \+ visb_svg_object(_,_,_,_,_),!.
461 gen_new_svg_objects(Stream) :-
462 format(Stream, '<script>~n{ // block scope: the footer script also declares a global const svg~n',[]),
463 format(Stream, ' const svg = document.querySelector("svg");~n',[]),
464 format(Stream, ' const svgns = "http://www.w3.org/2000/svg";~n',[]),
465 (gen_new_svg_object(Stream,svg_root,_),fail ; true),
466 format(Stream, '}~n</script>~n',[]).
467
468 % generate JS script to create an individual new object
469 gen_new_svg_object(Stream,ParentID,SVGID) :-
470 visb_svg_object(SVGID,SVG_Class,AttrList,_,_Pos),
471 (ParentID=svg_root
472 -> (visb_svg_child(SVGID,RealParentID) % only generate root svg objects that have no parents
473 -> visb_svg_child_of_object_from_svg_file(SVGID) % unless the parent will not be created here in this loop
474 ; RealParentID=ParentID
475 )
476 ; RealParentID=ParentID),
477 add_svg_id_prefix(SVGID,SVGID2),
478 format(Stream,' if(document.getElementById("',[]), format_js_string(Stream,SVGID2),
479 format(Stream,'") == null) {~n',[]),
480 format(Stream,' var new___obj = document.createElementNS(svgns,"',[]), format_js_string(Stream,SVG_Class),
481 format(Stream,'");~n',[]),
482 gen_new_svg_attr(Stream,SVGID2,svg_attribute(id,SVGID2)),
483 maplist(gen_new_svg_attr(Stream,SVGID2),AttrList),
484 (RealParentID \= svg_root
485 -> opt_add_svg_id_prefix(RealParentID,RealParentID2), % parent may come from SVG file and have no prefix
486 format(Stream,' document.getElementById("',[]), format_js_string(Stream,RealParentID2),
487 format(Stream,'").appendChild(new___obj);~n',[])
488 ; format(Stream,' svg.appendChild(new___obj);~n',[])
489 ),
490 format(Stream,' }~n',[]),
491 (visb_svg_parent(SVGID,ChildID), % we can now generate the children
492 gen_new_svg_object(Stream,SVGID,ChildID),
493 fail
494 ; true).
495
496 gen_new_svg_attr(Stream,_SVGID,svg_attribute(text,Val)) :- !,
497 format(Stream,' new___obj.textContent = "',[]), format_js_string(Stream,Val),
498 format(Stream,'";~n',[]).
499 gen_new_svg_attr(Stream,_SVGID,svg_attribute(Attr,Val)) :-
500 format(Stream,' new___obj.setAttribute("',[]), format_js_string(Stream,Attr),
501 format(Stream,'","',[]), format_js_string(Stream,Val),
502 format(Stream,'");~n',[]).
503
504 % instead of creating a script to create new SVG objects,
505 % create an inlined textual XML representation of the objects
506 % this can be used to obtain a static SVG file which can be modified in an editor
507 % and then later used instead of the VISB_SVG_OBJECTS definitions
508 inline_new_svg_objects(Stream,StateId) :-
509 get_dynamic_svgid_attributes_for_state(StateId,DynUpdateList), % compute updates only once
510 visb_svg_object(SVGID,_,_,_,_), % lookup static SVG object
511 \+ visb_svg_child(SVGID,_), % children will be created in the scope of their parent
512 inline_new_svg_object(Stream,StateId,DynUpdateList,0,unknown,SVGID),
513 fail.
514 inline_new_svg_objects(_,_).
515
516 inline_new_svg_object(Stream,StateId,DynUpdateList,IndentLevel,ParentPos,SVGID) :-
517 (visb_svg_object(SVGID,SVG_Class,StaticAttrList,_,Pos) -> true % lookup static SVG object
518 ; add_warning(visb_visualiser,'Unknown SVG child object: ',SVGID,ParentPos),fail
519 ),
520 add_svg_id_prefix(SVGID,SVGID2), % for Jupyter
521 indent_ws(Stream,IndentLevel),
522 atom_codes(SVGID2,IDCodes), xml_escape_codes(IDCodes,EIDCodes),
523 format(Stream,' <~w id="~s"',[SVG_Class,EIDCodes]),
524 (member(svgid_updates(SVGID,DynAttrList),DynUpdateList) % TODO: use avl_fetch
525 -> override_attributes(StaticAttrList,DynAttrList,AttrList)
526 ; AttrList=StaticAttrList),
527 maplist(format_new_svg_attr(Stream,SVGID,Pos,TextContent),AttrList),
528 (TextContent=''
529 -> EscapedTextContent="" % set Text variable to empty if unbound
530 ; do_not_escape_text_for_class(SVG_Class) -> atom_codes(TextContent,EscapedTextContent)
531 ; atom_codes(TextContent,TCC), xml_escape_codes(TCC,EscapedTextContent)),
532 format(Stream,'>~s',[EscapedTextContent]), % Note: this text is not inside quotes, we need HTML escape
533 IL1 is IndentLevel+1,
534 ( visb_svg_parent(SVGID,Child),
535 nl(Stream),
536 inline_new_svg_object(Stream,StateId,DynUpdateList,IL1,Pos,Child),
537 fail
538 ; true),
539 format(Stream,'</~w>~n',[SVG_Class]), % end marker
540 fail.
541 inline_new_svg_object(_,_,_,_,_,_).
542
543 % for foreignObjects the text may contain HTML tags which should not be escaped:
544 do_not_escape_text_for_class(foreignObject).
545 do_not_escape_text_for_class(script). % otherwise " quotes will be escaped to "
546
547 indent_ws(_,X) :- X<1,!.
548 indent_ws(Stream,X) :- write(Stream,' '), X1 is X-1, indent_ws(Stream,X1).
549
550 % combinde svg_attribute/2 terms from two sorted lists, giving precedence to second list
551 override_attributes([],L,R) :- !, R=L.
552 override_attributes(L,[],R) :- !, R=L.
553 override_attributes([svg_attribute(Attr1,Val1)|T1],[svg_attribute(Attr2,Val2)|T2],[svg_attribute(Attr3,Val3)|T3]) :-
554 (Attr1=Attr2
555 -> Attr3=Attr1, Val3=Val2, % dynamic value Val2 takes precedence
556 override_attributes(T1,T2,T3)
557 ; Attr1 @< Attr2 -> Attr3=Attr1, Val3=Val1, override_attributes(T1,[svg_attribute(Attr2,Val2)|T2],T3)
558 ; Attr3=Attr2, Val3=Val2, override_attributes([svg_attribute(Attr1,Val1)|T1],T2,T3)
559 ).
560
561 % get dynamic svg_attribute/2 list for a given state and SVG ID
562 get_dynamic_svgid_attributes_for_state(root,List) :- !, List=[].
563 get_dynamic_svgid_attributes_for_state(StateId,UpdateList) :-
564 get_visb_attributes_for_state(StateId,List),
565 sort(List,SList),
566 group_set_attr(SList,'$$UNKNOWN$$',_,UpdateList).
567
568 group_set_attr([],_,[],[]).
569 group_set_attr([set_attr(SvgID,Attr,Val)|T],SvgID,[svg_attribute(Attr,Val)|T1],T2) :- !,
570 group_set_attr(T,SvgID,T1,T2).
571 group_set_attr([set_attr(SvgID,Attr,Val)|T],_OldID,[],[svgid_updates(SvgID,L)|T2]) :- % a new SVGId is encountered
572 group_set_attr([set_attr(SvgID,Attr,Val)|T],SvgID,L,T2).
573
574 % print svg attribute and extract text attribute
575 format_new_svg_attr(Stream,SVGID,Pos,TextContent,svg_attribute(Attr,Val)) :- % TODO: text content
576 (Attr=text
577 -> (TextContent=Val -> true % TextContent is initially a variable and can be unified once
578 ; add_warning(inline_new_svg_objects,'Multiple text attributes for: ',SVGID,Pos),
579 add_debug_message(inline_new_svg_objects,' * Using text 1: ',TextContent),
580 add_debug_message(inline_new_svg_objects,' * Ignoring text 2: ',Val)
581 )
582 ; number(Val) ->
583 format(Stream,' ~w="~w"',[Attr,Val])
584 ; atom(Val) ->
585 atom_codes(Val,Codes), xml_escape_codes(Codes,ECodes), % we are inside an XML attribute, not JavaScript
586 format(Stream,' ~w="~s"',[Attr,ECodes])
587 ; compound(Val), functor(Val,F,N) ->
588 ajoin(['Cannot escape ',Attr,' *compound* ',F,'/',N,' value for ', SVGID,': '],Msg),
589 add_error(visb_visualiser,Msg,Val,Pos),
590 format(Stream,' ~w="~w"',[Attr,Val])
591 ; ajoin(['Cannot escape ',Attr,' value for ', SVGID,': '],Msg),
592 add_error(visb_visualiser,Msg,Val,Pos),
593 format(Stream,' ~w="~w"',[Attr,Val])
594 ).
595
596
597 :- dynamic id_namespace_prefix/1.
598 % prefix that can be useful for Jupyter notebooks so that for each state we have a different namespace
599 % can also be useful for exporting a trace into a single HTML
600 % TODO: does not work with JSON and SVG files
601
602
603 set_id_namespace_prefix_for_states(_,Options) :-
604 member(id_namespace_prefix(Pre),Options),!,
605 (Pre = auto
606 -> gensym('visb',ID), % for Jupyter this relies on the fact the gensym is *not* reset when loading another spec
607 atom_concat(ID,'.',Prefix)
608 ; Prefix=Pre
609 ), set_id_namespace_prefix(Prefix).
610 set_id_namespace_prefix_for_states(_,_).
611
612 set_id_namespace_prefix(Prefix) :- clear_id_namespace_prefix,
613 assert(id_namespace_prefix(Prefix)).
614 clear_id_namespace_prefix :- retractall(id_namespace_prefix(_)).
615
616 % add a namespace prefix to an SVG ID
617 add_svg_id_prefix(SvgID,Res) :- id_namespace_prefix(Prefix),!,
618 % option where we prefix all Ids with a namespace for Jupyter to combine multiple SVGs in single HTML page
619 atom_concat(Prefix,SvgID,Res).
620 add_svg_id_prefix(SvgID,SvgID).
621
622 % only add for known objects
623 opt_add_svg_id_prefix(SvgID,Res) :- id_namespace_prefix(Prefix),
624 visb_svg_object(SvgID,_,_,_,_),!, % only add prefix for SVG objects we know about (that were created by VisB)
625 % option where we prefix all Ids with a namespace for Jupyter to combine multiple SVGs in single HTML page
626 atom_concat(Prefix,SvgID,Res).
627 opt_add_svg_id_prefix(SvgID,SvgID).
628
629 % -----------------
630
631 :- dynamic js_generated_function/1.
632 :- dynamic last_attribute_values/3.
633
634 % define a visualise JS function to set the attributes for the given StateId
635 gen_visb_visualise_function(Stream,Options,Len,StateIds,Obj) :-
636 get_state_and_action(Obj,StateId,ActionStr,DescStr),
637 nth1(Nr,StateIds,Obj),
638 \+ js_generated_function(Nr), !,
639 assertz(js_generated_function(Nr)),
640 (Nr > 1 ->
641 PrevNr is Nr-1, nth1(PrevNr,StateIds,PrevObj), get_state_and_action(PrevObj,PrevStateId,_,_)
642 ; PrevStateId = -1),
643 visb_visualise_function_aux(Stream,Options,Len,StateId,PrevStateId,Nr,ActionStr,DescStr),
644 (Nr == Len ->
645 retractall(last_attribute_values(_,_,_)),
646 format(Stream,' ]~n',[])
647 ; true).
648 gen_visb_visualise_function(_,_,_,_,_).
649
650
651 visb_visualise_function_aux(Stream,_,Len,StateId,_,Nr,ActionStr,DescStr) :-
652 format(Stream,' [~n',[]),
653 (Len>1 -> format(Stream,' {id: "trace_meter", attr: "value", val: "~w"},~n',[Nr]) ; true),
654 (escape_value_to_codes_for_js(ActionStr,EAS), escape_value_to_codes_for_js(DescStr,EDS) ->
655 % was only string_escape before; however, this turns e.g. |-> into ↦ which we don't want (we are in JS part here)
656 format(Stream,' {id: "visb_debug_messages", attr: "text", val: "Step ~w/~w, State ID: ~w, Event: ~s ~s"},~n',[Nr,Len,StateId,EAS,EDS])
657 ; string_escape(ActionStr,SEAS), string_escape(DescStr,SEDS), % fallback
658 format(Stream,' {id: "visb_debug_messages", attr: "text", val: "Step ~w/~w, State ID: ~w, Event: ~w ~w"},~n',[Nr,Len,StateId,SEAS,SEDS])
659 ),
660 get_change_attribute_for_state(StateId,SvgID,SvgAttribute,Value),
661 \+ last_attribute_values(SvgID,SvgAttribute,Value),
662 retractall(last_attribute_values(SvgID,SvgAttribute,_)),
663 assertz(last_attribute_values(SvgID,SvgAttribute,Value)),
664 opt_add_svg_id_prefix(SvgID,SvgID2),
665 (escape_value_to_codes_for_js(Value,ECodes)
666 -> format(Stream,' {id: "~w", attr: "~w", val: "~s"},~n',[SvgID2,SvgAttribute,ECodes])
667 ; format(Stream,' {id: "~w", attr: "~w", val: "',[SvgID2,SvgAttribute]),
668 format_js_string(Stream,Value), format(Stream,'"},~n',[])
669 ),
670 fail.
671 visb_visualise_function_aux(Stream,Options,Len,StateId,PrevStateId,_,_,_) :-
672 b_or_z_mode,
673 member(show_variables(VarList),Options),
674
675 visited_expression(StateId,State),
676 (visited_expression(PrevStateId,PrevState) -> true ; PrevState=root),
677
678 show_var_in_visb(VarList,ID),
679 get_state_value_change(StateId,State,ID,Options,ResValCodes),
680
681 % current value:
682 ajoin(['bVar_',ID],SvgID),
683 change_svg_attribute_differences_aux(Stream,SvgID,'text',ResValCodes),
684
685 Len > 1, % don't add previous value and change markers for state-only HTML
686 (state_corresponds_to_fully_setup_b_machine(PrevState,_), % don't show prev values before initialisation
687 get_state_value_change(PrevStateId,PrevState,ID,Options,PrevResValCodes)
688 -> true
689 ; PrevResValCodes = "?"),
690
691 % mark row if value has changed in the last step
692 ajoin(['row_bVar_',ID],SvgRowID),
693 (ResValCodes = PrevResValCodes -> RowColor = white; RowColor = '#fffacd'),
694 change_svg_attribute_differences_aux(Stream,SvgRowID,'bgcolor',RowColor),
695
696 % previous value:
697 ajoin(['bVarPrev_',ID],SvgPrevID),
698 change_svg_attribute_differences_aux(Stream,SvgPrevID,'text',PrevResValCodes),
699
700 fail.
701 visb_visualise_function_aux(Stream,_Options,Len,StateId,PrevStateId,_,_,_) :-
702 xtl_mode,
703
704 bv_get_top_level(IDs),
705 member(ID,IDs),
706 (bv_get_value_unlimited(ID,StateId,v(Val))
707 -> atom_codes(Val,ValCodes), js_escape_codes(ValCodes,ResValCodes)
708 ; ResValCodes = ""),
709
710 % current value:
711 ajoin(['bVar_',ID],SvgID),
712 change_svg_attribute_differences_aux(Stream,SvgID,'text',ResValCodes),
713
714 Len > 1, % don't add previous value and change markers for state-only HTML
715 (PrevStateId \= root,
716 bv_get_value_unlimited(ID,PrevStateId,v(PrevVal))
717 -> atom_codes(PrevVal,PrevValCodes), js_escape_codes(PrevValCodes,PrevResValCodes)
718 ; PrevResValCodes = ""),
719
720 % mark row if value has changed in the last step
721 ajoin(['row_bVar_',ID],SvgRowID),
722 (ResValCodes = PrevResValCodes -> RowColor = white; RowColor = '#fffacd'),
723 change_svg_attribute_differences_aux(Stream,SvgRowID,'bgcolor',RowColor),
724
725 % previous value:
726 ajoin(['bVarPrev_',ID],SvgPrevID),
727 change_svg_attribute_differences_aux(Stream,SvgPrevID,'text',PrevResValCodes),
728
729 fail.
730 visb_visualise_function_aux(Stream,Options,_,StateId,_,_,_,_) :-
731 member(show_events(OpList),Options),
732 get_state_value_enabled_operation_status(StateId,OpID,Enabled,ResValCodes,TransStr),
733 show_var_in_visb(OpList,OpID),
734 ajoin(['bOperation_',OpID],SvgID),
735 change_svg_attribute_differences_aux(Stream,SvgID,'text',ResValCodes),
736 (Enabled=true -> BGCol=white ; BGCol='#f1f1f1'),
737 ajoin(['row_',SvgID],SvgRowID),
738 change_svg_attribute_differences_aux(Stream,SvgRowID,'bgcolor',BGCol),
739 % the transition string (with parameters) can change even if the status text remains the same:
740 escape_value_to_codes_for_js(TransStr,ETS),
741 ajoin(['name_',SvgID],SvgNameID),
742 change_svg_attribute_differences_aux(Stream,SvgNameID,'text',ETS),
743 % TO DO: highlight next event in trace, but then function needs one more parameter
744 fail.
745 visb_visualise_function_aux(Stream,Options,Len,_,_,Nr,_,_) :-
746 (Len>1, member(show_sequence_chart,Options)
747 -> format(Stream,' {id: "seq_chart_~w", attr: "style", val: "display:block"},~n',[Nr]),
748 (Nr>1
749 -> PNr is Nr-1, format(Stream,' {id: "seq_chart_~w", attr: "style", val: "display:none"},~n',[PNr])
750 ; gen_graph_setup(Stream,seq_chart,Len))), % all sequence charts except the first should be invisible
751 fail.
752 visb_visualise_function_aux(Stream,Options,Len,_,_,Nr,_,_) :-
753 member(gen_graph_table(Cmd),Options),
754 (Len>1
755 -> format(Stream,' {id: "~w_~w", attr: "style", val: "display:block"},~n',[Cmd,Nr]),
756 (Nr>1
757 -> PNr is Nr-1, format(Stream,' {id: "~w_~w", attr: "style", val: "display:none"},~n',[Cmd,PNr])
758 ; gen_graph_setup(Stream,Cmd,Len))),
759 fail.
760 visb_visualise_function_aux(Stream,_,Len,_,_,Nr,_,_) :-
761 Nr == Len ->
762 format(Stream,' ]~n',[])
763 ; format(Stream,' ],~n',[]).
764
765 gen_graph_setup(_,_,Len) :- Len<2, !.
766 gen_graph_setup(Stream,Cmd,Len) :-
767 format(Stream,' {id: "~w_~w", attr: "style", val: "display:none"},~n',[Cmd,Len]),
768 NLen is Len-1,
769 gen_graph_setup(Stream,Cmd,NLen).
770
771 escape_value_to_codes_for_js(Atom,ECodes) :- atom(Atom),
772 atom_codes(Atom,Codes), js_escape_codes(Codes,ECodes).
773
774 % escape codes for use inside a "..." JavaScript string literal within an HTML <script> element;
775 % < is escaped as \u003c so that a value cannot end the script with </script> or start a comment <!--
776 js_escape_codes(Codes,ECodes) :-
777 b_string_escape_codes(Codes,Codes2), escape_lt_for_js(Codes2,ECodes).
778 escape_lt_for_js([],[]).
779 escape_lt_for_js([0'<|T],[0'\\,0'u,0'0,0'0,0'3,0'c|ET]) :- !, escape_lt_for_js(T,ET).
780 escape_lt_for_js([H|T],[H|ET]) :- escape_lt_for_js(T,ET).
781
782 % write any value as escaped contents of a "..." JavaScript string literal
783 format_js_string(Stream,Val) :- number(Val),!, write(Stream,Val).
784 format_js_string(Stream,Val) :-
785 (atom(Val) -> atom_codes(Val,Codes) ; write_to_codes(Val,Codes)),
786 js_escape_codes(Codes,ECodes),
787 format(Stream,'~s',[ECodes]).
788
789 % escape codes for use inside a "..." XML/HTML attribute value or as XML text;
790 % unlike html_escape_codes we do not generate HTML entities like ä which are not valid in SVG files
791 xml_escape_codes([],[]).
792 xml_escape_codes([C|T],Res) :-
793 (xml_escape_code(C,EC) -> append(EC,ET,Res) ; Res=[C|ET]),
794 xml_escape_codes(T,ET).
795 xml_escape_code(0'&,"&").
796 xml_escape_code(0'<,"<").
797 xml_escape_code(0'>,">").
798 xml_escape_code(0'",""").
799
800 % write any value as escaped HTML text
801 format_html_text(Stream,Val) :-
802 html_text_codes(Val,ECodes),
803 format(Stream,'~s',[ECodes]).
804
805 html_text_codes(Val,ECodes) :-
806 (atom(Val) -> atom_codes(Val,Codes) ; write_to_codes(Val,Codes)),
807 html_escape_codes(Codes,ECodes).
808
809 change_svg_attribute_differences_aux(Stream,ID,Attr,ValueCodes) :-
810 (last_attribute_values(ID,Attr,ValueCodes)
811 -> true
812 ; retractall(last_attribute_values(ID,Attr,_)),
813 assertz(last_attribute_values(ID,Attr,ValueCodes)),
814 format(Stream,' {id: "~w", attr: "~w", val: "~s"},~n',[ID,Attr,ValueCodes])
815 ).
816
817
818 % generate JS script to register hovers
819 gen_registerHovers_script(Stream,Options) :- \+ visb_has_hovers(_),
820 nonmember(debugging_hovers,Options),
821 !, % no hover exists
822 format(Stream,' <script> function registerHovers() {} </script>~n',[]).
823 gen_registerHovers_script(Stream,Options) :-
824 (member(debugging_hovers,Options) -> GenDebug=true ; GenDebug=false), % gen debugging hovers
825 % Jquery is no longer required:
826 %format(Stream,' <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>~n',[]),
827 format(Stream,' <script>~n',[]),
828 format(Stream,' function registerHovers() {~n',[]),
829 format(Stream,' var obj;~n',[]),
830 visb_has_hovers(SVGID), % only generate hover function if necessary; TODO: examine other IDs for GenDebug
831 opt_add_svg_id_prefix(SVGID,SVGID2),
832 format(Stream,' obj = document.getElementById("',[]), format_js_string(Stream,SVGID2),
833 format(Stream,'");~n',[]),
834 format(Stream,' if (obj != null) {~n',[]), % do not abort registerHovers if the SVG object does not exist
835 format(Stream,' obj.onmouseover = function(ev){~n',[]),
836 (visb_hover(SVGID,ID,Attr,EnterVal,_,_),
837 opt_add_svg_id_prefix(ID,ID2),
838 (GenDebug=false -> true
839 ; ajoin(['SVG ID: ',SVGID2],DebugMsg),
840 gen_js_set_attr(Stream,visb_debug_messages,text,DebugMsg)),
841 gen_js_set_attr(Stream,ID2,Attr,EnterVal), fail ;
842 format(Stream,' };~n',[])
843 ),
844 format(Stream,' obj.onmouseout = function(){~n',[]),
845 (visb_hover(SVGID,ID,Attr,_,LeaveVal,_),
846 opt_add_svg_id_prefix(ID,ID2),
847 (GenDebug=false -> true
848 ; gen_js_set_attr(Stream,visb_debug_messages,text,'...')
849 ),
850 gen_js_set_attr(Stream,ID2,Attr,LeaveVal), fail ;
851 format(Stream,' };~n',[])
852 ),
853 format(Stream,' }~n',[]),
854 fail.
855 gen_registerHovers_script(Stream,_) :-
856 format(Stream,' }~n </script>~n',[]).
857
858 gen_js_set_attr(Stream,ID,Attr,Val) :-
859 format(Stream,' setAttr("',[]), format_js_string(Stream,ID),
860 format(Stream,'","',[]), format_js_string(Stream,Attr),
861 format(Stream,'","',[]), format_js_string(Stream,Val),
862 format(Stream,'")~n',[]).
863
864
865 % ----------------------------------
866
867
868 generate_visb_html_for_history_with_source(File) :-
869 generate_visb_html_for_history(File,
870 [show_constants(all),show_sets(all),show_variables(all),show_events(all),show_invariants,show_source]).
871 generate_visb_html_for_history_with_vars(File) :-
872 generate_visb_html_for_history(File,[show_constants(all),show_sets(all),show_variables(all),show_events(all)]).
873 generate_visb_html_for_history(File) :-
874 generate_visb_html_for_history(File,[]).
875 generate_visb_html_for_history(File,Options) :-
876 start_ms_timer(Timer),
877 get_state_id_trace(T),
878 T = [root|StateIds],
879 set_unicode_mode,
880 call_cleanup(get_action_trace_with_limit(120,Actions), % TODO: also get description of JSON traces
881 unset_unicode_mode),
882 reverse(Actions,FActions),
883 combine_state_and_actions(StateIds,[root|StateIds],FActions,List),
884 generate_visb_html(List,File,Options),
885 stop_ms_walltimer_with_msg(Timer,'exporting history to VisB HTML file: ').
886
887
888 combine_state_and_actions([],_,[],[]).
889 combine_state_and_actions([StateId|TS],[PrevId|TP],[action(ActionStr,ATerm)|TA],
890 [change_to_state_via_action(StateId,ActionStr,Desc)|TRes]) :-
891 (get_operation_description_for_transition(PrevId,ATerm,StateId,D) -> Desc=D ; Desc=''),
892 combine_state_and_actions(TS,TP,TA,TRes).
893
894 generate_visb_html_for_current_state(File) :-
895 generate_visb_html_for_current_state(File,[show_variables(all)]).
896 generate_visb_html_for_current_state(File,Options) :-
897 start_ms_timer(Timer),
898 current_state_id(ID),
899 generate_visb_html([ID],File,Options),
900 stop_ms_walltimer_with_msg(Timer,'exporting current state to VisB HTML file: ').
901 generate_visb_html_codes_for_states(StateIds,Options,Codes) :-
902 generate_visb_html_to_codes(StateIds,Options,Codes). % write to codes list
903
904 % ------------------------------
905
906
907 show_typed_var_in_visb(all,_) :- !.
908 show_typed_var_in_visb(VarList,b(identifier(ID),_,_)) :- !, show_var_in_visb(VarList,ID).
909 show_var_in_visb(VarList,ID ) :- (VarList=all -> true ; member(ID,VarList) -> true).
910
911 % generate a table for the state (we could use bvisual2)
912 gen_state_table(Stream,Options,StateIds) :-
913 b_or_z_mode,
914 member(show_variables(VarList),Options),
915 b_get_machine_variables(TypedVars),
916 % is this useful if Len > 1, but StateIds are not from a trace? (see also below for formulas)
917 (length(StateIds,Len), Len > 1 -> NOptions = [show_prev_value|Options] ; NOptions = Options),
918 gen_state_table_aux(Stream,NOptions,'Variables',TypedVars,VarList,[],'Value','bVar_'),fail.
919 gen_state_table(Stream,Options,StateIds) :-
920 b_or_z_mode,
921 member(show_variables(VarList),Options),
922 b_get_machine_expressions(AExprs,Options), AExprs \= [],
923 (length(StateIds,Len), Len > 1 -> NOptions = [show_prev_value|Options] ; NOptions = Options),
924 gen_state_table_aux(Stream,NOptions,'Formulas',AExprs,VarList,[],'Value','bVar_'),fail.
925 gen_state_table(Stream,Options,StateIds) :-
926 b_or_z_mode,
927 member(show_constants(IDList),Options),
928 b_get_machine_constants(TypedVars),
929 last(StateIds,Last), % we assume there is only one constant valuation in the trace; TO DO: generalise
930 get_state_and_action(Last,LastId,_,_),
931 visited_expression(LastId,BState),
932 expand_to_constants_and_variables(BState,ConstState,_),
933 gen_state_table_aux(Stream,Options,'Constants',TypedVars,IDList,ConstState,'Value','bConstant_'),fail.
934 gen_state_table(Stream,Options,_) :-
935 b_or_z_mode,
936 member(show_sets(IDList),Options),
937 findall(TSet,bv_top_level_set(TSet,_),TypedVars),
938 findall(bind(Set,Val),(bv_top_level_set(TSet,Val),get_texpr_id(TSet,Set)),BState),
939 gen_state_table_aux(Stream,Options,'Sets',TypedVars,IDList,BState,'Value','bSet_'),fail.
940 % TO DO: maybe show also top-level guards specfile_possible_trans_name or get_top_level_guard
941 gen_state_table(Stream,Options,_StateIds) :-
942 b_or_z_mode,
943 member(show_events(IDList),Options),
944 get_specification_description(operations,SN),
945 atom_tail_to_lower_case(SN,SectionName),
946 findall(b(identifier(OpName),subst,[]),b_top_level_operation(OpName),TypedVars),
947 % TODO: op(Params,Results) instead of subst
948 gen_state_table_aux(Stream,Options,SectionName,TypedVars,IDList,[],'Enabled','bOperation_'),fail.
949 gen_state_table(Stream,Options,StateIds) :-
950 xtl_mode,
951 (length(StateIds,Len), Len > 1 -> NOptions = [show_prev_value|Options] ; NOptions = Options),
952 bv_get_top_level(IDs),
953 findall(b(identifier(ID),string,[]), member(ID,IDs), Props),
954 gen_state_table_aux(Stream,NOptions,'Properties',Props,IDs,[],'Value','bVar_'), fail.
955 gen_state_table(_Stream,_,_).
956
957
958 gen_source_code_section(Stream,Options) :-
959 member(show_source,Options), % we could also include original sources, rather than just internal rep.
960 !,
961 Section = 'Source',
962 format(Stream,' <button type="button" class="collapsible collapsible-style" title="Inspect source code of internal representation of ProB of model">~w</button>~n',[Section]),
963 format(Stream,'<div class="coll-content-hid">~n',[]),
964 get_internal_representation(PP),
965 html_escape_codes(PP,EPP),
966 format(Stream,'<pre>~s</pre>~n',[EPP]),
967 format(Stream,'</div>~n',[]).
968 gen_source_code_section(_Stream,_).
969
970
971 % creates a HTML table with header name Section and including one row per ID in TypedVars;
972 % id of value for ID is prefixed with IDPrefix
973 % VarListToShow is either all or a list of ids
974 gen_state_table_aux(Stream,Options,Section,TypedVars,VarListToShow,BState,ValueStr,IDPrefix) :-
975 include(show_typed_var_in_visb(VarListToShow),TypedVars,SVars), % check which ids should be shown
976 length(SVars,SLen), SLen>0,
977 length(TypedVars,Len),
978 (SLen=Len
979 -> format(Stream,' <button type="button" class="collapsible collapsible-style">~w (~w)</button>~n',[Section,SLen])
980 ; format(Stream,' <button type="button" class="collapsible collapsible-style">~w (~w/~w)</button>~n',
981 [Section,SLen,Len])
982 ),
983 format(Stream,'<div class="coll-content-hid">~n',[]),
984 (member(show_prev_value,Options) % add extra column for previous value of variables (and animation expressions)
985 -> format(Stream,' <table> <tr> <th>Nr</th> <th>Name</th> <th>Value</th> <th>Previous Value</th> </tr>~n',[])
986 ; format(Stream,' <table> <tr> <th>Nr</th> <th>Name</th> <th>~w</th> </tr>~n',[ValueStr])),
987 ( nth1(Nr,SVars,IDorExprToShow),
988 get_state_table_name_and_expr(IDorExprToShow,ID,LongID,TExpr),
989 (get_state_value_codes_for_id(BState,ID,Options,_,ValCodes)
990 -> html_escape_codes(ValCodes,EVC) % Value provided in BState; may be overriden in trace export
991 ; EVC = "?"),
992 html_text_codes(LongID,ELongID), % LongID can contain a pretty-printed expression
993 get_table_hover_message_codes(ID,TExpr,HoverMsgC),
994 (member(show_prev_value,Options) % add previous value of variables (and animation expressions)
995 -> % for variables the values are added later in JS
996 % do we want to show the previous value in the state only HTML?
997 format(Stream,'~n <tr id="row_~w~w" title="~s"> <td>~w</td> <td id="name_~w~w">~s</td> <td id="~w~w">?</td> <td id="bVarPrev_~w">?</td> </tr>~n',
998 [IDPrefix,ID, HoverMsgC, Nr, IDPrefix,ID,ELongID, IDPrefix,ID, ID ])
999 ;
1000 format(Stream,'~n <tr id="row_~w~w" title="~s"> <td>~w</td> <td id="name_~w~w">~s</td> <td id="~w~w">~s</td> </tr>~n',
1001 [IDPrefix,ID, HoverMsgC, Nr, IDPrefix,ID,ELongID, IDPrefix,ID,EVC])),
1002 fail
1003 ;
1004 format(Stream,' </table>~n </div>~n',[])
1005 ).
1006
1007 %get_state_table_name_and_expr(bexpr(Kind,TExpr),Name,LongName,Expr) :- !, % we have special expression to show
1008 % Name=Kind,LongName=Kind,Expr=TExpr.
1009 get_state_table_name_and_expr(bexpr(Kind,TExpr),ID,LongName,Expr) :- !, % we have special expression to show
1010 ID=Kind, % ID for SVG updates, ...
1011 translate_bexpression_with_limit(TExpr,200,TS),
1012 ajoin([Kind,' == ',TS],LongName),
1013 Expr=TExpr.
1014 get_state_table_name_and_expr(TID,ID,ID,TID) :- get_texpr_id(TID,ID). % normal case: variable name
1015
1016
1017 visb_machine_animation_expression(variant,AEName,Variant,_) :-
1018 b_get_operation_variant(Name,ConvOrAnt,Variant),
1019 ajoin(['Variant for ',Name, ' (',ConvOrAnt,')'],AEName).
1020 visb_machine_animation_expression(animation_expression,Name,TExpr,_) :-
1021 b_get_machine_animation_expression(Name,TExpr).
1022 visb_machine_animation_expression(invariant,Name,TPred,Options) :-
1023 member(show_invariants,Options),
1024 b_nth1_non_ignored_invariant(Nr,TPred,_),
1025 ajoin(['INVARIANT-',Nr],Name).
1026 % TODO: we should also adapt get_state_value_change to return true when invariant_violated is false for the state
1027 % we could also only include this section when there is an invariant violation in the trace?
1028
1029 b_get_machine_expressions(List,Options) :-
1030 findall(bexpr(AE_Name,AExpr),visb_machine_animation_expression(_,AE_Name,AExpr,Options),List).
1031 % 'ANIMATION_EXPRESSION' or invariants or variants
1032
1033
1034 % get hover message for entries in variables/constants/... rows:
1035 get_table_hover_message_codes(ID,TExpr,EscHoverMsg) :-
1036 get_texpr_type(TExpr,Type),
1037 (Type=subst,b_get_machine_operation_signature(ID,TS)
1038 -> ajoin(['Operation: ',TS],HoverMsg)
1039 ; pretty_type(Type,TS),
1040 (get_texpr_description(TExpr,Desc)
1041 -> ajoin(['Type: ',TS,'\nDesc: ',Desc],HoverMsg)
1042 ; ajoin(['Type: ',TS],HoverMsg)
1043 )
1044 ),
1045 atom_codes(HoverMsg,HoverCodes),
1046 html_escape_codes(HoverCodes,EscHoverMsg).
1047
1048 % get the values that should be displayed in the variables table
1049 get_state_value_change(StateId,State,ID,Options,ECodes) :-
1050 extract_variables_from_state(State,VariableBState),
1051 ( get_state_value_codes_for_id(VariableBState,ID,Options,Val,ValCodes) % look up variable values
1052 ; visb_machine_animation_expression(_,_,_,Options) ->
1053 state_corresponds_to_fully_setup_b_machine(State,FullState),
1054 get_state_value_codes_for_anim_expr(StateId,FullState,ID,Options,Val,ValCodes) % evaluate animation expressions
1055 ),
1056 escape_if_necessary(Val,ValCodes,ECodes). % change " to \" for JavaScript
1057 % formatsilent('Value for ~w~nunescaped: "~s",~n escaped: "~s"~n',[ID,ValCodes,ECodes]).
1058
1059 % avoid unnecessary escaping traversals:
1060 escape_if_necessary([],ValCodes,Res) :- !, Res=ValCodes.
1061 escape_if_necessary(int(_),ValCodes,Res) :- !, Res=ValCodes.
1062 escape_if_necessary(pred_true,ValCodes,Res) :- !, Res=ValCodes.
1063 escape_if_necessary(pred_false,ValCodes,Res) :- !, Res=ValCodes.
1064 escape_if_necessary(_,ValCodes,ECodes) :- js_escape_codes(ValCodes,ECodes).
1065
1066
1067 get_state_value_codes_for_id(BState,ID,Options,Val,ValCodes) :-
1068 member(bind(ID,Val),BState),
1069 translate_values_for_state_table(Val,Options,ValCodes).
1070
1071 % translate a value for display in state table (not yet escaped)
1072 translate_values_for_state_table(Val,Options,ValCodes) :-
1073 (member(limit_for_values(Nr),Options)
1074 -> translate_bvalue_to_codes_with_limit(Val,Nr,ValCodes)
1075 ; get_preference(expand_avl_upto,Max), Max<0
1076 -> translate_bvalue_to_codes(Val,ValCodes) % unlimited
1077 ; translate_bvalue_to_codes_with_limit(Val,10000,ValCodes)
1078 ).
1079
1080
1081 get_state_value_codes_for_anim_expr(StateId,BState,AE_Name,Options,ResValue,ValCodes) :-
1082 visb_machine_animation_expression(Kind,AE_Name,AExpr,Options),
1083 (Kind=invariant
1084 -> ((invariant_not_yet_checked(StateId) ; invariant_violated(StateId))
1085 -> (safe_create_texpr(convert_bool(AExpr),boolean,TExpr),
1086 evaluate_visb_formula(TExpr,AE_Name,'',BState,ResValue,unknown)
1087 -> translate_pred_val(ResValue,ValCodes)
1088 ; ResValue=pred_unknown, ValCodes = "?" % WD error?
1089 )
1090 ; ResValue=pred_true, translate_pred_val(pred_true,ValCodes) % no need to evaluate invariant
1091 )
1092 ; evaluate_visb_formula(AExpr,AE_Name,'',BState,ResValue,unknown),
1093 translate_values_for_state_table(ResValue,Options,ValCodes)
1094 ).
1095 translate_pred_val(pred_true,[8868]). % see translate:unicode_translation(truth,X).
1096 translate_pred_val(pred_false,[8869]). % see translate:unicode_translation(falsity,X).
1097
1098
1099 get_state_value_enabled_operation_status(StateId,OpName,Enabled,EnabledValue,TransStr) :-
1100 b_top_level_operation(OpName),
1101 findall(TransID, (transition(StateId,OpTerm,TransID,_NewStateId),
1102 match_operation(OpTerm,OpName)), TransList),
1103 length(TransList,Len),
1104 (Len>0
1105 -> Enabled=true,
1106 EnabledValue = "\x2705\", % green check mark, TRUE alternatives \x2705\ \x2713\ \x2714\
1107 (TransList=[TID], transition(StateId,OpTerm,TID,NewStateId),
1108 translate_event_with_src_and_target_id(OpTerm,StateId,NewStateId,200,TransStr)
1109 -> true
1110 ; ajoin([OpName,' (',Len,'\xd7\)'],TransStr) %
1111 )
1112 ; time_out_for_node(StateId,OpName,TypeOfTimeOut) ->
1113 Enabled=TypeOfTimeOut,
1114 EnabledValue = [8987], % four o'clock symbol
1115 TransStr = OpName
1116 %ajoin(['No transition found due to: ',TypeOfTimeOut],HoverMsg)
1117 ; get_preference(maxNrOfEnablingsPerOperation,0) ->
1118 Enabled=not_computed,
1119 EnabledValue = [10067], % red question mark
1120 TransStr = OpName
1121 %'No transitions were computed because MAX_OPERATIONS = 0' = HoverMsg
1122 ; b_get_machine_operation_max(OpName,0) ->
1123 Enabled=not_computed,
1124 EnabledValue = [10067], % red question mark
1125 TransStr = OpName
1126 %ajoin(['No transitions were computed because MAX_OPERATIONS_',OpName,' = 0'],HoverMsg)
1127 ; Enabled=false,
1128 EnabledValue = [9940], % stop sign, FALSE, alternatives \x10102\
1129 TransStr = OpName
1130 %ajoin(['Not enabled'],HoverMsg)
1131 ). % TO DO: show whether this is the next step in the trace