View source with formatted comments or as raw
    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-2015, 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_svgtree,
   36	  [ term_rendering//3			% +Term, +Vars, +Options
   37	  ]).   38:- use_module(library(option)).   39:- use_module(library(http/html_write)).   40:- use_module(library(http/term_html)).   41:- use_module(library(http/js_write)).   42:- use_module(library(http/http_wrapper)).   43:- use_module(library(apply)).   44:- use_module(library(lists)).   45:- use_module(library(sandbox)).   46:- use_module('../render').   47
   48:- register_renderer(svgtree, "Render term as a tree").   49
   50/** <module> SWISH SVG tree renderer
   51
   52Render a term as an SVG tree.   This  renderer is intended to illustrate
   53the shape of terms or display a simple parse tree.
   54
   55This renderer is also an illustration of  using a JavaScript library and
   56SVG inside rendered elements. Note  that   the  use  of RequireJS avoids
   57loading the library multiple times as   well  as poluting the namespace.
   58
   59Note that while the  script  is   being  evaluated,  `$.ajaxScript` is a
   60jQuery object pointing to the executing script. This is used to find the
   61`span`  element  without  using  an  `id`    attribute.  Using  `id`  is
   62undesirable as it is hard  to   guarantee  their uniqueness. However, we
   63must find the desired  element  immediately   and  not  in the RequireJS
   64callback, so we need to put it in   a variable and scope the whole thing
   65in a function to avoid conflicts.  JavaScript is fun!
   66*/
   67
   68%%	term_rendering(+Term, +Vars, +Options)//
   69%
   70%	Render a compound term as a tree.  Options processed:
   71%
   72%	  - list(Boolean)
   73%	  If `false`, do not render lists.
   74%	  - filter(:NodeFilter)
   75%	  If present, use call(NodeFilter, Term, Label, Children)
   76%	  to extract the label and children of a term.  Operates
   77%	  on terms for which this call succeeds on the top node.
   78%	  If the call fails on a child, the child is rendered as
   79%	  a term.
   80
   81term_rendering(Term, _Vars, Options) -->
   82	{ is_term_tree(Term, How, Options),
   83	  call(How, Term, Dict)
   84	},
   85	html(div([ class('render-svg-tree'),
   86		   'data-render'('Term as SVG tree')
   87		 ],
   88		 [ span([]),
   89		   \js_script({|javascript(Dict)||
   90(function() {
   91  if ( $.ajaxScript ) {
   92    var span = $.ajaxScript.parent().find("span")[0];
   93
   94    require(["render/svg-tree-drawer", "jquery"], function(svgtree) {
   95      var tree = new TreeDrawer(span, Dict);
   96      if ( !tree.filters.label ) {
   97	tree.addFilter('label', function(label,node) {
   98	  return typeof(label) == "object" ? $(label.html)[0] : label;
   99	});
  100      }
  101      tree.draw();
  102    });
  103  }
  104})();
  105		  |})
  106		 ])).
  107
  108%%	is_term_tree(+Term, -Closure, +Options) is semidet.
  109%
  110%	True when Term  is  a  Prolog   term  that  can  meaningfully be
  111%	displayed as a tree. The  actual   rendering  is done by calling
  112%	call(Closure, Term, JSON), where the  called closure must return
  113%	a nested dict and each node contains:
  114%
  115%	  * label
  116%	  The label to display.  This is either a string or a dict
  117%	  containing html:HTMLString
  118%	  * children
  119%	  If present, this is a list of child nodes. If not, it is
  120%	  a leaf node.
  121
  122is_term_tree(Term, filtered_tree(QFilter, Options1), Options) :-
  123	option(filter(Filter), Options),
  124	callable(Filter),
  125	Filter \= _:_,
  126	option(module(Module), Options),
  127	QFilter = Module:Filter,
  128	catch(safe_filter(QFilter), _, fail),
  129	call(QFilter, Term, _Label, _Children), !,
  130	browser_option(Options, Options1).
  131is_term_tree(Term, compound_tree(Options1), Options) :-
  132	compound(Term),
  133	(   is_list(Term)
  134	->  \+ option(list(false), Options)
  135	;   true
  136	), !,
  137	browser_option(Options, Options1).
  138
  139:- public
  140	compound_tree/3,
  141	filtered_tree/4.  142
  143%%	compound_tree(+Options, +Term, -JSON) is det.
  144%
  145%	Render Term as a tree, considering every   compound term to be a
  146%	node.  Renders leafs using term//1.
  147
  148compound_tree(Options, Term, Tree) :-
  149	compound(Term), Term \= '$VAR'(_), !,
  150	Tree = json{label:Label, children:Children},
  151	compound_name_arguments(Term, Functor, Args),
  152	term_string(Functor, Label),
  153	maplist(compound_tree(Options), Args, Children).
  154compound_tree(Options, Term, json{label:Label}) :-
  155	term_label(Term, Label, Options).
  156
  157%%	term_label(+Term, -Label, +Options) is det.
  158%
  159%	Create a label for a term.  If   we  can, we generate HTML using
  160%	term//2, which is translated into   an  SVG `foreignObject`. The
  161%	Trident engine used by IE does  not support foreignObject though
  162%	:-(
  163
  164term_label(Term, String, Options) :-
  165	option(engine(trident), Options), !,
  166	term_string(Term, String, Options).
  167term_label(Term, json{html:String}, Options) :-
  168	phrase(term(Term, Options), Tokens),
  169	with_output_to(string(String), print_html(Tokens)).
  170
  171%%	filtered_tree(:Filter, +Options, +Term, -JSON) is det.
  172%
  173%	Render a filtered tree.
  174
  175:- meta_predicate filtered_tree(3,+,+,-).  176
  177filtered_tree(Filter, Options, Term, Tree) :-
  178	nonvar(Term),
  179	call(Filter, Term, LabelTerm, ChildNodes),
  180	is_list(ChildNodes), !,
  181	Tree = json{label:Label, children:Children},
  182	term_label(LabelTerm, Label, Options),
  183	maplist(filtered_tree(Filter, Options), ChildNodes, Children).
  184filtered_tree(_, Options, Term, json{label:Label}) :-
  185	term_label(Term, Label, Options).
  186
  187safe_filter(Module:Filter) :-
  188	Filter =.. List0,
  189	append(List0, [_, _, _], List),
  190	Filter1 =.. List,
  191	safe_goal(Module:Filter1).
  192
  193
  194		 /*******************************
  195		 *	      BROWSER		*
  196		 *******************************/
  197
  198browser_option(Options0, Options) :-
  199	is_trident, !,
  200	Options = [engine(trident)|Options0].
  201browser_option(Options, Options).
  202
  203%%	is_trident is semidet.
  204%
  205%	True if we know that the client is Trident-based (IE)
  206
  207is_trident :-
  208	http_current_request(Request),
  209	option(user_agent(Agent), Request),
  210	sub_string(Agent, _, _, _, " Trident/"), !