View source with raw comments or as raw
    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)  2014-2025, VU University Amsterdam
    7                              SWI-Prolog Solutions b.v.
    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(term_html,
   37          [ term//2                             % +Term, +Options
   38          ]).   39:- use_module(library(http/html_write)).   40:- use_module(library(option)).   41:- use_module(library(error)).   42:- use_module(library(debug)).   43:- use_module(library(http/json)).   44
   45:- multifile
   46    blob_rendering//3,              % +Type, +Blob, +Options
   47    portray//2,                     % +Term, +Options
   48    layout/3.                       % +Term, -Layout, +Options
   49
   50:- meta_predicate
   51    term(+, :, ?, ?).

Represent Prolog terms as HTML

This file is primarily designed to support running Prolog applications over the web. It provides a replacement for write_term/2 which renders terms as structured HTML. */

 term(@Term, +Options)// is det
Render a Prolog term as a structured HTML tree. Options are passed to write_term/3. In addition, the following options are processed:
format(+Format)
Used for atomic values. Typically this is used to render a single value.
emit(:Closure)
Call call(Closure, HTML)// rather than html//1 to emit the result.
To be done
- Cyclic terms.
- Attributed terms.
- Portray
- Test with Ulrich's write test set.
- Deal with numbervars and canonical.
   79term(Term, Options) -->
   80    { must_be(acyclic, Term),
   81      meta_options(is_meta, Options, Options1),
   82      merge_options(Options1,
   83                    [ priority(1200),
   84                      max_depth(1 000 000 000),
   85                      depth(0)
   86                    ],
   87                    Options2),
   88      dict_options(Dict, Options2)
   89    },
   90    any(Term, Dict),
   91    finalize_term(Term, Dict).
   92
   93is_meta(emit).
   94
   95:- html_meta
   96    embrace(html,+,?,?),
   97    emit(html,+,?,?).
 emit(:HTML, +Options)// is det
Support the option emit(Closure) to call call(Closure, HTML) for emitting the result rather than html//1.
  104emit(HTML, Options) -->
  105    { get_dict(emit, Options, Closure) },
  106    !,
  107    call(Closure, HTML).
  108emit(HTML, _Options) -->
  109    html(HTML).
 any(@Term, +Options:dict)// is det
Emit an arbitrary Prolog Term as HTML controlled by Options. @arg Options is a dict.
  116any(_, Options) -->
  117    { Options.depth >= Options.max_depth },
  118    !,
  119    emit(span(class('pl-ellipsis'), ...), Options).
  120any(Term, Options) -->
  121    (   {   nonvar(Term)
  122        ;   attvar(Term)
  123        }
  124    ->  portray(Term, Options)
  125    ),
  126    !.
  127any(Term, Options) -->
  128    { primitive(Term, Class0),
  129      !,
  130      quote_atomic(Term, S, Options),
  131      primitive_class(Class0, Term, S, Class)
  132    },
  133    emit(span([class(Class)], S), Options).
  134any(Term, Options) -->
  135    { blob(Term,Type), Term \== [] },
  136    !,
  137    (   blob_rendering(Type,Term,Options)
  138    ->  []
  139    ;   emit(span(class('pl-blob'),['<',Type,'>']), Options)
  140    ).
  141any(Term, Options) -->
  142    { is_dict(Term), !
  143    },
  144    dict(Term, Options).
  145any(Term, Options) -->
  146    { assertion((compound(Term);Term==[]))
  147    },
  148    compound(Term, Options).
 compound(+Compound, +Options)// is det
Process a compound term.
  154compound('$VAR'(Var), Options) -->
  155    { (   Options.get(numbervars) == true
  156      ;   Options.get(portray) == true
  157      ),
  158      !,
  159      format(string(S), '~W', ['$VAR'(Var), [numbervars(true)]]),
  160      (   S == "_"
  161      ->  Class = 'pl-anon'
  162      ;   Class = 'pl-var'
  163      )
  164    },
  165    emit(span([class(Class)], S), Options).
  166compound(List, Options) -->
  167    { (   List == []
  168      ;   List = [_|_]                              % May have unbound tail
  169      ),
  170      !,
  171      arg_options(Options, _{priority:999}, ArgOptions)
  172    },
  173    list(List, ArgOptions).
  174compound({X}, Options) -->
  175    !,
  176    { arg_options(Options, _{priority:1200}, ArgOptions) },
  177    emit(span(class('pl-curl'), [ '{', \any(X, ArgOptions), '}' ]), Options).
  178compound(OpTerm, Options) -->
  179    { compound_name_arity(OpTerm, Name, 1),
  180      is_op1(Name, Type, Pri, ArgPri, Options),
  181      \+ Options.get(ignore_ops) == true
  182    },
  183    !,
  184    op1(Type, Pri, OpTerm, ArgPri, Options).
  185compound(OpTerm, Options) -->
  186    { compound_name_arity(OpTerm, Name, 2),
  187      is_op2(Name, Type, LeftPri, Pri, RightPri, Options),
  188      \+ Options.get(ignore_ops) == true
  189    },
  190    !,
  191    op2(Pri, OpTerm, Type, LeftPri, RightPri, Options).
  192compound(Compound, Options) -->
  193    { compound_name_arity(Compound, Name, Arity),
  194      quote_atomic(Name, S, Options.put(embrace, never)),
  195      arg_options(Options, _{priority:999}, ArgOptions),
  196      extra_classes(Compound, Classes, Attrs, Options)
  197    },
  198    emit(span([ class(['pl-compound','pl-adaptive'|Classes]),
  199                'data-arity'(Arity),
  200                'data-name'(Name)
  201              | Attrs
  202              ],
  203              [ span(class(['pl-functor', 'pl-trigger']),
  204                     [ S, \punct('(', Options) ]),
  205                span(class('pl-compound-args'),
  206                     [ \args(0, Arity, Compound, ArgOptions)
  207                     ])
  208              ]), Options).
  209
  210extra_classes(Term, Classes, OAttrs, Options) :-
  211    findall(A, extra_attr(Term, A, Options), Attrs),
  212    partition(is_class_attr, Attrs, CAttrs, OAttrs),
  213    maplist(arg(1), CAttrs, Classes).
  214
  215is_class_attr(class(_)).
  216
  217extra_attr(_, class('pl-level-0'), Options) :-
  218    Options.depth == 0.
  219extra_attr(Term, 'data-layout'(Data), Options) :-
  220    layout(Term, Layout, Options),
  221    (   is_dict(Layout)
  222    ->  atom_json_dict(Data, Layout, [])
  223    ;   Data = Layout
  224    ).
 arg_options(+Options, -OptionsOut) is det
 arg_options(+Options, +Extra, -OptionsOut) is det
Increment depth in Options.
  232arg_options(Options, Options.put(depth, NewDepth)) :-
  233    NewDepth is Options.depth+1.
  234arg_options(Options, Extra, Options.put(depth, NewDepth).put(Extra)) :-
  235    NewDepth is Options.depth+1.
 args(+Arg0, +Arity, +Compound, +Options)//
Emit arguments of a compound term.
  241args(Arity, Arity, _, _) --> !.
  242args(I, Arity, Compound, Options) -->
  243    { NI is I + 1,
  244      arg(NI, Compound, Arg)
  245    },
  246    (   {NI == Arity}
  247    ->  emit([ span(class('pl-compound-arg'), \any(Arg, Options)),
  248               span(class(['pl-compound-close', 'pl-punct']), ')')
  249             ], Options)
  250    ;   emit(span(class('pl-compound-arg'),
  251                  [ \any(Arg, Options), \punct(',', Options) ]), Options),
  252        args(NI, Arity, Compound, Options)
  253    ).
  254
  255punct(Punct, Options) -->
  256    emit(span(class('pl-punct'), Punct), Options).
 list(+List, +Options)//
Emit a list. The List may have an unbound tail.
  262list(List, Options) -->
  263    { '$skip_list'(Length, List, Tail),
  264      (   Tail == []
  265      ->  Attr = ['data-length'(Length)]
  266      ;   Attr = ['data-length'(Length), 'data-partial'(true)]
  267      )
  268    },
  269    emit(span([ class(['pl-list','pl-adaptive'])
  270              | Attr
  271              ],
  272              [ span(class(['pl-list-open', 'pl-trigger', 'pl-punct']), '['),
  273                \list_content(List, Options),
  274                span(class(['pl-list-close', 'pl-punct']), ']')
  275              ]), Options).
  276
  277list_content([], _Options) -->
  278    !,
  279    [].
  280list_content([H|T], Options) -->
  281    !,
  282    { arg_options(Options, ArgOptions),
  283      (   T == []
  284      ->  Sep = [],
  285          Next = end
  286      ;   Options.depth + 1 >= Options.max_depth
  287      ->  Sep = [span(class('pl-punct'), '|')],
  288          Next = depth_limit
  289      ;   (var(T) ; \+ T = [_|_])
  290      ->  Sep = [span(class('pl-punct'), '|')],
  291          Next = tail
  292      ;   Sep = [span(class('pl-punct'), [',', ' '])],
  293          Next = list
  294      )
  295    },
  296    emit(span(class('pl-list-el'),
  297              [ \any(H, Options) | Sep ]), Options),
  298    list_next(Next, T, ArgOptions).
  299
  300list_next(end, _, _) --> !.
  301list_next(depth_limit, _, Options) -->
  302    !,
  303    emit(span(class('pl-ellipsis'), ...), Options).
  304list_next(tail, Value, Options) -->
  305    {   var(Value)
  306    ->  Class = 'pl-var-tail'
  307    ;   Class = 'pl-nonvar-tail'
  308    },
  309    emit(span(class(Class), \any(Value, Options)), Options).
  310list_next(list, Tail, Options) -->
  311    list_content(Tail, Options).
 is_op1(+Name, -Type, -Priority, -ArgPriority, +Options) is semidet
True if Name is an operator taking one argument of Type.
  317is_op1(Name, Type, Pri, ArgPri, Options) :-
  318    operator_module(Module, Options),
  319    current_op(Pri, OpType, Module:Name),
  320    argpri(OpType, Type, Pri, ArgPri),
  321    !.
  322
  323argpri(fx, prefix,  Pri0, Pri) :- Pri is Pri0 - 1.
  324argpri(fy, prefix,  Pri,  Pri).
  325argpri(xf, postfix, Pri0, Pri) :- Pri is Pri0 - 1.
  326argpri(yf, postfix, Pri,  Pri).
  327
  328% ! is_op2(+Name, -Type, -LeftPri, -Pri, -RightPri, +Options) is semidet.
  329%
  330%   True if Name is an operator taking two arguments of Type.
  331
  332is_op2(Name, Type, LeftPri, Pri, RightPri, Options) :-
  333    operator_module(Module, Options),
  334    current_op(Pri, Type, Module:Name),
  335    infix_argpri(Type, LeftPri, Pri, RightPri),
  336    !.
  337
  338infix_argpri(xfx, ArgPri, Pri, ArgPri) :- ArgPri is Pri - 1.
  339infix_argpri(yfx, Pri, Pri, ArgPri) :- ArgPri is Pri - 1.
  340infix_argpri(xfy, ArgPri, Pri, Pri) :- ArgPri is Pri - 1.
 operator_module(-Module, +Options) is det
Find the module for evaluating operators.
  346operator_module(Module, Options) :-
  347    Module = Options.get(module),
  348    !.
  349operator_module(TypeIn, _) :-
  350    '$module'(TypeIn, TypeIn).
 op1(+Type, +Pri, +Term, +ArgPri, +Options)// is det
  354op1(Type, Pri, Term, ArgPri, Options) -->
  355    { Pri > Options.priority },
  356    !,
  357    embrace(\op1(Type, Term, ArgPri, Options), Options).
  358op1(Type, _, Term, ArgPri, Options) -->
  359    op1(Type, Term, ArgPri, Options).
  360
  361op1(prefix, Term, ArgPri, Options) -->
  362    { Term =.. [Functor,Arg],
  363      arg_options(Options, DepthOptions),
  364      FuncOptions = DepthOptions.put(embrace, never),
  365      ArgOptions  = DepthOptions.put(priority, ArgPri),
  366      quote_atomic(Functor, S, FuncOptions),
  367      extra_classes(Term, Classes, Attrs, Options.put(op, prefix))
  368    },
  369    emit(span([ class(['pl-compound', 'pl-op', 'pl-prefix-op'|Classes]),
  370                'data-arity'(1),
  371                'data-name'(Functor)
  372              | Attrs
  373              ],
  374              [ span(class(['pl-functor', 'pl-trigger']), S),
  375                \space(Functor, Arg, o, a, FuncOptions, ArgOptions),
  376                \op_arg(Arg, ArgOptions)
  377              ]), Options).
  378op1(postfix, Term, ArgPri, Options) -->
  379    { Term =.. [Functor,Arg],
  380      arg_options(Options, DepthOptions),
  381      ArgOptions = DepthOptions.put(priority, ArgPri),
  382      FuncOptions = DepthOptions.put(embrace, never),
  383      quote_atomic(Functor, S, FuncOptions),
  384      extra_classes(Term, Classes, Attrs, Options.put(op, postfix))
  385    },
  386    emit(span([ class(['pl-compound', 'pl-op', 'pl-postfix-op'|Classes]),
  387                'data-arity'(1),
  388                'data-name'(Functor)
  389              | Attrs
  390              ],
  391              [ \op_arg(Arg, ArgOptions),
  392                \space(Arg, Functor, a, o, ArgOptions, FuncOptions),
  393                span(class('pl-functor'), S)
  394              ]), Options).
 op2(+Pri, +Term, +Type, +LeftPri, +RightPri, +Options)// is det
  398op2(Pri, Term, Type, LeftPri, RightPri, Options) -->
  399    { Pri > Options.priority },
  400    !,
  401    embrace(\op2(Term, Type, LeftPri, RightPri, Options), Options).
  402op2(_, Term, Type, LeftPri, RightPri, Options) -->
  403    op2(Term, Type, LeftPri, RightPri, Options).
  404
  405op2(Term, xfy, LeftPri, RightPri, Options) -->
  406    { functor(Term, Functor, 2),
  407      quote_op(Functor, S, Options),
  408      xfy_list(Term, Functor, List),
  409      List \== [],
  410      !,
  411      arg_options(Options, DepthOptions),
  412      ArgOptions  = DepthOptions.put(#{priority:LeftPri, quoted_op:S}),
  413      extra_classes(Term, Classes, Attrs, Options.put(op, infix))
  414    },
  415    emit(span([ class(['pl-op-seq', 'pl-adaptive'|Classes])
  416              | Attrs
  417              ],
  418              \op_seq(List, Functor, RightPri, ArgOptions)), Options).
  419op2(Term, _Type, LeftPri, RightPri, Options) -->
  420    { Term =.. [Functor,Left,Right],
  421      arg_options(Options, DepthOptions),
  422      LeftOptions  = DepthOptions.put(priority, LeftPri),
  423      FuncOptions  = DepthOptions.put(embrace, never),
  424      RightOptions = DepthOptions.put(priority, RightPri),
  425      (   (   need_space(Left, Functor, a, o, LeftOptions, FuncOptions)
  426          ;   need_space(Functor, Right, o, a, FuncOptions, RightOptions)
  427          )
  428      ->  Space = ' '
  429      ;   Space = ''
  430      ),
  431      quote_op(Functor, S, Options),
  432      extra_classes(Term, Classes, Attrs, Options.put(op, infix))
  433    },
  434    emit(span([ class([ 'pl-compound', 'pl-adaptive', 'pl-op', 'pl-infix-op'
  435                      | Classes
  436                      ]),
  437                'data-arity'(2),
  438                'data-name'(Functor)
  439              | Attrs
  440              ],
  441              [ \op_arg(Left, LeftOptions),
  442                Space,
  443                span(class(['pl-functor', 'pl-trigger']), S),
  444                Space,
  445                \op_arg(Right, RightOptions)
  446              ]), Options).
 op_arg(+Term, +Options)// is det
  450op_arg(Atom, Options) -->
  451    { atom(Atom),
  452      operator_module(Module, Options),
  453      current_op(_,_,Module:Atom)
  454    }, !,
  455    embrace(\any(Atom, Options.put(embrace, never)), Options).
  456op_arg(Any, Options) -->
  457    any(Any, Options).
  458
  459op_seq([Last], _Functor, LastPri, Options) -->
  460    !,
  461    { LastOptions = Options.put(priority, LastPri)
  462    },
  463    emit(span(class('pl-op-seq-el'), \op_arg(Last, LastOptions)), Options).
  464op_seq([H|T], Functor, LastPri, Options) -->
  465    emit(span(class('pl-op-seq-el'),
  466              [ \op_arg(H, Options),
  467                \left_space(H, Functor, Options),
  468                span(class('pl-infix'), Options.quoted_op)
  469              ]), Options),
  470    op_seq(T, Functor, LastPri, Options).
  471
  472left_space(Left, Functor, Options) -->
  473    { need_space(Left, Functor, a, o, Options, Options.put(embrace, never))
  474    },
  475    !,
  476    emit(' ', Options).
  477left_space(_,_,_) -->
  478    [].
  479
  480xfy_list(Term, Name, List),
  481    compound(Term),
  482    compound_name_arguments(Term, Name, [A,B]) =>
  483    List = [A|T],
  484    xfy_list(B, Name, T).
  485xfy_list(Term, _, List) =>
  486    List = [Term].
 embrace(+HTML, +Options)//
Place parenthesis around HTML with a DOM that allows to easily justify the height of the parenthesis.
  493embrace(HTML, Options) -->
  494    emit(span(class('pl-embrace'),
  495              [ span(class('pl-parenthesis'), '('),
  496                span(class('pl-embraced'),\emit(HTML, Options)),
  497                span(class('pl-parenthesis'), ')')
  498              ]), Options).
 space(@T1, @T2, +C1, +C2, +Options)//
Emit a space if omitting a space between T1 and T2 would cause the two terms to join.
  505space(T1, T2, C1, C2, LeftOptions, RightOptions) -->
  506    { need_space(T1, T2, C1, C2, LeftOptions, RightOptions) },
  507    emit(' ', RightOptions).
  508space(_, _, _, _, _, _) -->
  509    [].
  510
  511need_space(T1, T2, _, _, _, _) :-
  512    (   is_solo(T1)
  513    ;   is_solo(T2)
  514    ),
  515    !,
  516    fail.
  517need_space(T1, T2, C1, C2, LeftOptions, RightOptions) :-
  518    end_code_type(T1, C1, TypeR, LeftOptions.put(side, right)),
  519    end_code_type(T2, C2, TypeL, RightOptions.put(side, left)),
  520    \+ no_space(TypeR, TypeL).
  521
  522no_space(punct, _).
  523no_space(_, punct).
  524no_space(quote(R), quote(L)) :-
  525    !,
  526    R \== L.
  527no_space(alnum, symbol).
  528no_space(symbol, alnum).
 end_code_type(+Term, +Class, -Type, +Options) is det
True when code is the first/last character code that is emitted by printing Term using Options. Options.side is either left or right.
  536end_code_type(Atom, a, Type, Options) :-
  537    atom(Atom),
  538    operator_module(Module, Options),
  539    current_op(_,_,Module:Atom),
  540    !,
  541    Type = punct.
  542end_code_type(Atom, _, Type, Options) :-
  543    end_code_type(Atom, Type, Options).
  544
  545end_code_type(_, Type, Options) :-
  546    Options.depth >= Options.max_depth,
  547    !,
  548    Type = symbol.
  549end_code_type(Term, Type, Options) :-
  550    primitive(Term, _),
  551    !,
  552    quote_atomic(Term, S, Options),
  553    end_type(S, Type, Options).
  554end_code_type(Dict, Type, Options) :-
  555    is_dict(Dict, Tag),
  556    !,
  557    (   Options.side == left
  558    ->  end_code_type(Tag, Type, Options)
  559    ;   Type = punct
  560    ).
  561end_code_type('$VAR'(Var), Type, Options) :-
  562    Options.get(numbervars) == true,
  563    !,
  564    format(string(S), '~W', ['$VAR'(Var), [numbervars(true)]]),
  565    end_type(S, Type, Options).
  566end_code_type(List, Type, _) :-
  567    (   List == []
  568    ;   List = [_|_]
  569    ),
  570    !,
  571    Type = punct.
  572end_code_type(Blob, Type, Options) :-
  573    blob(Blob, Tag),
  574    !,                                  % assumes writing as tag<...>
  575    (   Options.side == left
  576    ->  end_code_type(Tag, Type, Options)
  577    ;   Type = symbol
  578    ).
  579end_code_type(OpTerm, Type, Options) :-
  580    compound_name_arity(OpTerm, Name, 1),
  581    is_op1(Name, OpType, Pri, ArgPri, Options),
  582    \+ Options.get(ignore_ops) == true,
  583    !,
  584    (   Pri > Options.priority
  585    ->  Type = punct
  586    ;   (   OpType == prefix, Options.side == left
  587        ->  end_code_type(Name, Type, Options)
  588        ;   OpType == postfix, Options.side == right
  589        ->  end_code_type(Name, Type, Options)
  590        ;   arg(1, OpTerm, Arg),
  591            arg_options(Options, ArgOptions),
  592            op_end_code_type(Arg, Type, ArgOptions.put(priority, ArgPri))
  593        )
  594    ).
  595end_code_type(OpTerm, Type, Options) :-
  596    compound_name_arity(OpTerm, Name, 2),
  597    is_op2(Name, _Type, LeftPri, Pri, RightPri, Options),
  598    \+ Options.get(ignore_ops) == true,
  599    !,
  600    (   Pri > Options.priority
  601    ->  Type = punct
  602    ;   Options.side == left
  603    ->  arg(1, OpTerm, Arg),
  604        arg_options(Options, ArgOptions),
  605        op_end_code_type(Arg, Type, ArgOptions.put(priority, LeftPri))
  606    ;   Options.side == right
  607    ->  arg(2, OpTerm, Arg),
  608        arg_options(Options, ArgOptions),
  609        op_end_code_type(Arg, Type, ArgOptions.put(priority, RightPri))
  610    ).
  611end_code_type(Compound, Type, Options) :-
  612    compound_name_arity(Compound, Name, _),
  613    end_code_type(Name, Type, Options).
  614
  615op_end_code_type(Atom, Type, Options) :-
  616    end_code_type(Atom, a, Type, Options).
  617
  618end_type(S, Type, Options) :-
  619    number(S),
  620    !,
  621    (   (S < 0 ; S == -0.0),
  622        Options.side == left
  623    ->  Type = symbol
  624    ;   Type = alnum
  625    ).
  626end_type(S, Type, Options) :-
  627    Options.side == left,
  628    !,
  629    sub_string(S, 0, 1, _, Start),
  630    syntax_type(Start, Type).
  631end_type(S, Type, _) :-
  632    sub_string(S, _, 1, 0, End),
  633    syntax_type(End, Type).
  634
  635syntax_type("\"", quote(double)) :- !.
  636syntax_type("\'", quote(single)) :- !.
  637syntax_type("\`", quote(back))   :- !.
  638syntax_type(S, Type) :-
  639    string_code(1, S, C),
  640    (   code_type(C, prolog_identifier_continue)
  641    ->  Type = alnum
  642    ;   code_type(C, prolog_symbol)
  643    ->  Type = symbol
  644    ;   code_type(C, space)
  645    ->  Type = layout
  646    ;   Type = punct
  647    ).
 dict(+Term, +Options)//
  652dict(Term, Options) -->
  653    { dict_pairs(Term, Tag, Pairs),
  654      quote_atomic(Tag, S, Options.put(embrace, never)),
  655      arg_options(Options, ArgOptions)
  656    },
  657    emit(span(class(['pl-dict', 'pl-adaptive']),
  658              [ span(class(['pl-tag', 'pl-trigger']), S),
  659                span(class(['pl-dict-open', 'pl-punct']), '{'),
  660                span(class('pl-dict-body'),
  661                     [ span(class('pl-dict-kvs'),
  662                            \dict_kvs(Pairs, ArgOptions)),
  663                       span(class(['pl-dict-close', 'pl-punct']), '}')
  664                     ])
  665              ]), Options).
  666
  667dict_kvs([], _) --> [].
  668dict_kvs(_, Options) -->
  669    { Options.depth >= Options.max_depth },
  670    !,
  671    emit(span(class('pl-ellipsis'), ...), Options).
  672dict_kvs(KVs, Options) -->
  673    dict_kvs2(KVs, Options).
  674
  675dict_kvs2([], _) -->
  676    [].
  677dict_kvs2([K-V|T], Options) -->
  678    { quote_atomic(K, S, Options),
  679      end_code_type(V, VType, Options.put(side, left)),
  680      (   VType == symbol
  681      ->  VSpace = ' '
  682      ;   VSpace = ''
  683      ),
  684      arg_options(Options, ArgOptions),
  685      (   T == []
  686      ->  Sep = []
  687      ;   Sep = [\punct(',', Options), ' ']
  688      )
  689    },
  690    emit(span(class('pl-dict-kv'),
  691              [ span(class('pl-key'), [S, \punct(:, Options)]),
  692                VSpace,
  693                span(class('pl-dict-value'),
  694                     [ \any(V, ArgOptions)
  695                     | Sep
  696                     ])
  697              ]), Options),
  698    dict_kvs2(T, Options).
  699
  700quote_atomic(Float, String, Options) :-
  701    float(Float),
  702    Format = Options.get(float_format),
  703    !,
  704    format(string(String), Format, [Float]).
  705quote_atomic(Plain, String, Options) :-
  706    atomic(Plain),
  707    Format = Options.get(format),
  708    !,
  709    format(string(String), Format, [Plain]).
  710quote_atomic(Plain, String, Options) :-
  711    rational(Plain),
  712    \+ integer(Plain),
  713    !,
  714    operator_module(Module, Options),
  715    format(string(String), '~W', [Plain, [module(Module)]]).
  716quote_atomic(Plain, Plain, _) :-
  717    number(Plain),
  718    !.
  719quote_atomic(Plain, String, Options) :-
  720    Options.get(quoted) == true,
  721    !,
  722    (   Options.get(embrace) == never
  723    ->  format(string(String), '~q', [Plain])
  724    ;   format(string(String), '~W', [Plain, Options])
  725    ).
  726quote_atomic(Var, String, Options) :-
  727    var(Var),
  728    !,
  729    format(string(String), '~W', [Var, Options]).
  730quote_atomic(Plain, Plain, _).
  731
  732quote_op(Op, S, _Options) :-
  733    is_solo(Op),
  734    !,
  735    S = Op.
  736quote_op(Op, S, Options) :-
  737    quote_atomic(Op, S, Options.put(embrace,never)).
  738
  739is_solo(Var) :-
  740    var(Var), !, fail.
  741is_solo(',').
  742is_solo(';').
  743is_solo('!').
 primitive(+Term, -Class) is semidet
True if Term is a primitive term, rendered using the CSS class Class.
  750primitive(Term, Type) :- var(Term),      !, Type = 'pl-avar'.
  751primitive(Term, Type) :- atom(Term),     !, Type = 'pl-atom'.
  752primitive(Term, Type) :- string(Term),   !, Type = 'pl-string'.
  753primitive(Term, Type) :- integer(Term),  !, Type = 'pl-int'.
  754primitive(Term, Type) :- rational(Term), !, Type = 'pl-rational'.
  755primitive(Term, Type) :- float(Term),    !, Type = 'pl-float'.
 primitive_class(+Class0, +Value, -String, -Class) is det
Fixup the CSS class for lexical variations. Used to find quoted atoms.
  762primitive_class('pl-atom', Atom, String, Class) :-
  763    \+ atom_string(Atom, String),
  764    !,
  765    Class = 'pl-quoted-atom'.
  766primitive_class(Class, _, _, Class).
 finalize_term(+Term, +Dict)// is det
Handle the full_stop(Bool) and nl(Bool) options.
  772finalize_term(Term, Options) -->
  773    (   { true == Options.get(full_stop) }
  774    ->  space(Term, '.', o, o, Options, Options),
  775        (   { true == Options.get(nl) }
  776        ->  emit(['.', br([])], Options)
  777        ;   emit('. ', Options)
  778        )
  779    ;   (   { true == Options.get(nl) }
  780        ->  emit(br([]), Options)
  781        ;   []
  782        )
  783    ).
  784
  785
  786                 /*******************************
  787                 *             HOOKS            *
  788                 *******************************/
 blob_rendering(+BlobType, +Blob, +WriteOptions)// is semidet
Hook to render blob atoms as HTML. This hook is called whenever a blob atom is encountered while rendering a compound term as HTML. The blob type is provided to allow efficient indexing without having to examine the blob. If this predicate fails, the blob is rendered as an HTML SPAN with class 'pl-blob' containing BlobType as text.