View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker, Michiel Hildebrand
    4    E-mail:        J.Wielemaker@uva.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2010-2025, University of Amsterdam
    7                              VU University Amsterdam
    8                              SWI-Prolog Solutions b.v.
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(javascript,
   38          [ js_script//1,               % +Content
   39
   40            js_call//1,                 % +Function(Arg..)
   41            js_new//2,                  % +Id, +Function(+Args)
   42            js_expression//1,           % +Expression
   43            js_arg_list//1,             % +ListOfExpressions
   44            js_arg//1,                  % +Arg
   45            js_args//1,                 % +Args
   46
   47            javascript/4                % Quasi Quotation handler
   48          ]).   49
   50:- use_module(library(http/html_write)).   51:- use_module(library(json)).   52:- use_module(library(apply)).   53:- use_module(library(error)).   54:- use_module(library(lists)).   55:- use_module(library(debug)).   56:- use_module(library(quasi_quotations)).   57:- use_module(library(dcg/basics)).   58:- use_module(library(json_grammar)).   59
   60:- html_meta
   61    js_script(html, ?, ?).   62
   63:- quasi_quotation_syntax(javascript).

Utilities for including JavaScript

This library is a supplement to library(http/html_write) for producing JavaScript fragments. Its main role is to be able to call JavaScript functions with valid arguments constructed from Prolog data. For example, suppose you want to call a JavaScript functions to process a list of names represented as Prolog atoms. This can be done using the call below, while without this library you would have to be careful to properly escape special characters.

numbers_script(Names) -->
    html(script(type('text/javascript'),
         [ \js_call('ProcessNumbers'(Names)
         ]),

The accepted arguments are described with js_expression//1. */

 js_script(+Content)// is det
Generate a JavaScript script element with the given content.
   89js_script(Content) -->
   90    html(script(type('text/javascript'),
   91                Content)).
   92
   93
   94                 /*******************************
   95                 *        QUASI QUOTATION       *
   96                 *******************************/
 javascript(+Content, +Vars, +VarDict, -DOM) is det
Quasi quotation parser for JavaScript that allows for embedding Prolog variables to substitude identifiers in the JavaScript snippet. Parameterizing a JavaScript string is achieved using the JavaScript + operator, which results in concatenation at the client side.
    ...,
    js_script({|javascript(Id, Config)||
                $(document).ready(function() {
                   $("#"+Id).tagit(Config);
                 });
               |}),
    ...

The current implementation tokenizes the JavaScript input and yields syntax errors on unterminated comments, strings, etc. No further parsing is implemented, which makes it possible to produce syntactically incorrect and partial JavaScript. Future versions are likely to include a full parser, generating syntax errors.

The parser produces a term \List, which is suitable for js_script//1 and html//1. Embedded variables are mapped to \js_expression(Var), while the remaining text is mapped to atoms.

To be done
- Implement a full JavaScript parser. Users should not rely on the ability to generate partial JavaScript snippets.
  132javascript(Content, Vars, Dict, \Parts) :-
  133    include(qq_var(Vars), Dict, QQDict),
  134    phrase_from_quasi_quotation(
  135        js(QQDict, Parts),
  136        Content).
  137
  138qq_var(Vars, _=Var) :-
  139    member(V, Vars),
  140    V == Var,
  141    !.
  142
  143js(Dict, [Pre, Subst|More]) -->
  144    here(Here0),
  145    js_tokens(_),
  146    here(Here1),
  147    json_token(identifier(Name)),
  148    { memberchk(Name=Var, Dict),
  149      !,
  150      Subst = \js_expression(Var),
  151      diff_to_atom(Here0, Here1, Pre)
  152    },
  153    js(Dict, More).
  154js(_, [Last]) -->
  155    string(Codes),
  156    \+ [_],
  157    !,
  158    { atom_codes(Last, Codes) }.
  159
  160js_tokens([]) --> [].
  161js_tokens([H|T]) -->
  162    json_token(H),
  163    js_tokens(T).
  164
  165
  166%       diff_to_atom(+Start, +End, -Atom)
  167%
  168%       True when Atom is an atom that represents the characters between
  169%       Start and End, where End must be in the tail of the list Start.
  170
  171diff_to_atom(Start, End, Atom) :-
  172    diff_list(Start, End, List),
  173    atom_codes(Atom, List).
  174
  175diff_list(Start, End, List) :-
  176    Start == End,
  177    !,
  178    List = [].
  179diff_list([H|Start], End, [H|List]) :-
  180    diff_list(Start, End, List).
  181
  182here(Here, Here, Here).
  183
  184
  185                 /*******************************
  186                 *     PROLOG --> JAVASCRIPT    *
  187                 *******************************/
 js_call(+Term)// is det
Emit a call to a Javascript function. The Prolog functor is the name of the function. The arguments are converted from Prolog to JavaScript using js_arg_list//1. Please not that Prolog functors can be quoted atom and thus the following is legal:
    ...
    html(script(type('text/javascript'),
         [ \js_call('x.y.z'(hello, 42))
         ]),
  203js_call(Term) -->
  204    { Term =.. [Function|Args] },
  205    html(Function), js_arg_list(Args), [';\n'].
 js_new(+Id, +Term)// is det
Emit a call to a Javascript object declaration. This is the same as:
['var ', Id, ' = new ', \js_call(Term)]
  218js_new(Id, Term) -->
  219    { Term =.. [Function|Args] },
  220    html(['var ', Id, ' = new ', Function]), js_arg_list(Args), [';\n'].
 js_arg_list(+Expressions:list)// is det
Write javascript (function) arguments. This writes "(", Arg, ..., ")". See js_expression//1 for valid argument values.
  228js_arg_list(Args) -->
  229    ['('], js_args(Args), [')'].
  230
  231js_args([]) -->
  232    [].
  233js_args([H|T]) -->
  234    js_expression(H),
  235    (   { T == [] }
  236    ->  []
  237    ;   html(', '),
  238        js_args(T)
  239    ).
 js_expression(+Expression)// is det
Emit a single JSON argument. Expression is one of:
Variable
Emitted as Javascript null
List
Produces a Javascript list, where each element is processed by this library.
object(Attributes)
Where Attributes is a Key-Value list where each pair can be written as Key-Value, Key=Value or Key(Value), accommodating all common constructs for this used in Prolog.< $ { K:V, ... } Same as object(Attributes), providing a more JavaScript-like syntax. This may be useful if the object appears literally in the source-code, but is generally less friendly to produce as a result from a computation.
Dict
Emit a dict as a JSON object using json_write_dict/3.
json(Term)
Emits a term using json_write/3.
@(Atom)
Emits these constants without quotes. Normally used for the symbols true, false and null, but can also be use for emitting JavaScript symbols (i.e. function- or variable names).
Number
Emitted literally
symbol(Atom)
Synonym for @(Atom). Deprecated.
Atom or String
Emitted as quoted JavaScript string.
  275js_expression(Expr) -->
  276    js_arg(Expr),
  277    !.
  278js_expression(Expr) -->
  279    { type_error(js(expression), Expr) }.
 js_arg(+Expression)// is semidet
Same as js_expression//1, but fails if Expression is invalid, where js_expression//1 raises an error.
deprecated
- New code should use js_expression//1.
  288js_arg(H) -->
  289    { var(H) },
  290    !,
  291    [null].
  292js_arg(object(H)) -->
  293    { is_list(H) },
  294    !,
  295    html([ '{', \js_kv_list(H), '}' ]).
  296js_arg({}(Attrs)) -->
  297    !,
  298    html([ '{', \js_kv_cslist(Attrs), '}' ]).
  299js_arg(@(Id)) --> js_identifier(Id).
  300js_arg(symbol(Id)) --> js_identifier(Id).
  301js_arg(json(Term)) -->
  302    { json_to_string(json(Term), String),
  303      debug(json_arg, '~w~n', String)
  304    },
  305    [ String ].
  306js_arg(Dict) -->
  307    { is_dict(Dict),
  308      !,
  309      with_output_to(string(String),
  310                     json_write_dict(current_output, Dict, [width(0)]))
  311    },
  312    [ String ].
  313js_arg(H) -->
  314    { is_list(H) },
  315    !,
  316    html([ '[', \js_args(H), ']' ]).
  317js_arg(H) -->
  318    { number(H) },
  319    !,
  320    [H].
  321js_arg(H) -->
  322    { atomic(H),
  323      !,
  324      js_quoted_string(H, Q)
  325    },
  326    [ '"', Q, '"'
  327    ].
  328
  329js_kv_list([]) --> [].
  330js_kv_list([H|T]) -->
  331    (   js_kv(H)
  332    ->  (   { T == [] }
  333        ->  []
  334        ;   html(', '),
  335            js_kv_list(T)
  336        )
  337    ;   { type_error(javascript_key_value, H) }
  338    ).
  339
  340js_kv(Key:Value) -->
  341    !,
  342    js_key(Key), [:], js_expression(Value).
  343js_kv(Key-Value) -->
  344    !,
  345    js_key(Key), [:], js_expression(Value).
  346js_kv(Key=Value) -->
  347    !,
  348    js_key(Key), [:], js_expression(Value).
  349js_kv(Term) -->
  350    { compound(Term),
  351      Term =.. [Key,Value]
  352    },
  353    !,
  354    js_key(Key), [:], js_expression(Value).
  355
  356js_key(Key) -->
  357    (   { must_be(atom, Key),
  358          js_identifier(Key)
  359        }
  360    ->  [Key]
  361    ;   { js_quoted_string(Key, QKey) },
  362        html(['\'', QKey, '\''])
  363    ).
  364
  365js_kv_cslist((A,B)) -->
  366    !,
  367    js_kv(A),
  368    html(', '),
  369    js_kv_cslist(B).
  370js_kv_cslist(A) -->
  371    js_kv(A).
 js_quoted_string(+Raw, -Quoted)
Quote text for use in JavaScript. Quoted does not include the leading and trailing quotes.
To be done
- Join with json stuff.
  380js_quoted_string(Raw, Quoted) :-
  381    atom_codes(Raw, Codes),
  382    phrase(js_quote_codes(Codes), QuotedCodes),
  383    atom_codes(Quoted, QuotedCodes).
  384
  385js_quote_codes([]) -->
  386    [].
  387js_quote_codes([0'\r,0'\n|T]) -->
  388    !,
  389    "\\n",
  390    js_quote_codes(T).
  391js_quote_codes([0'<,0'/|T]) -->        % Avoid XSS scripting hacks
  392    !,
  393    "<\\/",
  394    js_quote_codes(T).
  395js_quote_codes([H|T]) -->
  396    js_quote_code(H),
  397    js_quote_codes(T).
  398
  399js_quote_code(0'') -->
  400    !,
  401    "\\'".
  402js_quote_code(0'") -->
  403    !,
  404    "\\\"".
  405js_quote_code(0'\\) -->
  406    !,
  407    "\\\\".
  408js_quote_code(0'\n) -->
  409    !,
  410    "\\n".
  411js_quote_code(0'\r) -->
  412    !,
  413    "\\r".
  414js_quote_code(0'\t) -->
  415    !,
  416    "\\t".
  417js_quote_code(C) -->
  418    [C].
 js_identifier(+Id:atom)// is det
Emit an identifier if it is a valid one
  424js_identifier(Id) -->
  425    { must_be(atom, Id),
  426      js_identifier(Id)
  427    },
  428    !,
  429    [ Id ].
  430js_identifier(Id) -->
  431    { domain_error(js(identifier), Id)
  432    }.
 js_identifier(+Id:atom) is semidet
True if Id is a valid identifier. In traditional JavaScript, this means it starts with [$_:letter:] and is followed by [$_:letter:digit:]
  440js_identifier(Id) :-
  441    sub_atom(Id, 0, 1, _, First),
  442    char_type(First, csymf),
  443    forall(sub_atom(Id, _, 1, _, Char), char_type(Char, csym)).
 json_to_string(+JSONTerm, -String)
Write JSONTerm to String.
  450json_to_string(JSON, String) :-
  451    with_output_to(string(String),
  452                   json_write(current_output,JSON,[width(0)])).
  453
  454
  455                /*******************************
  456                *           SANDBOX            *
  457                *******************************/
  458
  459:- multifile sandbox:safe_primitive/1.  460
  461sandbox:safe_primitive(javascript:javascript(_,_,_,_))