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).
script element with the given content.89js_script(Content) --> 90 html(script(type('text/javascript'), 91 Content)). 92 93 94 /******************************* 95 * QUASI QUOTATION * 96 *******************************/
+ 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.
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 *******************************/
...
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'].['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'].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 ).
nullobject(Attributes)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.json(Term)true, false and null, but can also be use for
emitting JavaScript symbols (i.e. function- or variable
names).symbol(Atom)275js_expression(Expr) --> 276 js_arg(Expr), 277 !. 278js_expression(Expr) --> 279 { type_error(js(expression), Expr) }.
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).
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].
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 }.
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)).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(_,_,_,_))
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. */