HTML term rendering

I guess not :frowning: There are surely many options for enhancement and fixes :slight_smile: Please use Issues · SWI-Prolog/swish · GitHub I need this for a non-SWISH based project for making intermediate results easier to digest :slight_smile:

I’ve become a firm believer in keeping html, css, js, svg… (ie browser stuff) files static and doing the “dynamic stuff” by message passing with JSON.

SWI Prolog makes this a pleasure (at least next to Golang which I’ve spent the past few weeks trying to learn), with the basic server template I use looking like this:

:- use_module([ library(http/http_unix_daemon)
              , library(http/http_server)
              , message_broker
              ]).

:- http_handler(/, message_handler, []).
:- initialization(run, main).

run :-
  http_daemon.

message_handler(Request) :-
  http_read_json_dict(Request, DictIn),
  message_broker(DictIn, DictOut),
  reply_json_dict(DictOut).

And then my message_broker.pl module looks something like:

message_broker(DictIn, DictOut) :-
  method(DictIn.method, DictIn, DictOut).

method("method1", DictIn, DictOut) :-
  % Do whatever method1 does to create DictOut.

method("method2", DictIn, DictOut) :-
  % Do whatever method2 does to create DictOut.

...

The SWI Prolog server then listens to whatever port number, proxied to a subdirectory (say /json-rpc) using nginx in my case, but Apache etc would work pretty much identically.

My client-side JavaScript code follows the vanilla fetch example for JSON in Mozilla’s documentation.

async function server_call(jsonIn) {
  const response = await fetch("/rpc", 
    { method: "POST"
    , headers: {"Content-Type": "application/json"}
    , body: JSON.stringify(jsonIn)
    });
  return response.json();
}

The whole translation of JavaScript’s jsonIn to SWI Prolog’s DictIn and then DictOut to the JSON the above returns happens automagically (something I only appreciated after spending the past few weeks bashing my head against Golang which sees JSON as map[string]interface{} requiring the desired JSON to be statically typed using struct MyObject {Key1 type,...}. I’m ready to concede defeat on go.

I was just arbitrarily giving key-name values to my JSON objects, and only recently discovered there are at least two conventions for this JSON-RPC and JSON-WSP.

Long story short, keeping html, css, js etc as static files makes keeping them neat a lot easier than generating them dynamically. Furthermore, messaging little JSON strings rather than entire HTML pages to do dynamic stuff is just better netiquette.

So I’m all for making SWI Prolog’s already excellent JSON messaging support even better.

1 Like

I mostly agree. The only drawback I see is that, so far, I’ve not seen anything comparable to Prolog’s library(html_write) when it comes to generating dynamic HTML from data. I have been using laconic for this in SWISH. It is better than string concatenation, but still no fun to work with. Did I miss something really usable? Otherwise we should run Prolog in the browser :slight_smile:

Something I only recently discovered is the repl for Postgres, psql, has a command \H where instead of “pretty printing” query results as text tables, it outputs HTML table snippets which can be cut 'n pasted to be rendered in browsers.

Something similar in SWI Prolog – ie just to output tables as SWISH already does but I’m not sure how to do in the swipl repl, would be nice.

If I understand you correctly, then

The ANTLR suite of tools has

ANTLR - Parser generator (Human → (Data or AST))
StringTemplate - a java template engine for generating source code, web pages, emails, or any other formatted text output. ((Data or AST) → Human)

StringTemplate is used to build the ANTLR web site.

These class of tools are know as template generators.

The normal SWISH reply also consists of JSON with HTML rendering for e.g., Prolog terms of the answer. So, the reply to a query is a JSON object holding the variable names and for each variable name an HTML string that represents the value. I don’t consider this optimal. A JSON representation of the Prolog term and doing the HTML serialization in the browser seems more attractive, but a lot of JavaScript writing :frowning:

If you have a DCG for library(html_write) that translates an object into HTML, you get the HTML as a string using

    phrase(my_grammar(Data), Tokens),
    with_output_to_string(string(HTML), print_html(Tokens)).

My Prolog to JSON conversion needs are fairly simple in that I use lists rather than objects.

For example:

row(a, b, c).
row(d, e, f).
...

Which I then convert to JSON as:

[["row", "a", "b", "c"], ["row", "d", "e", "f"], ...]

The above could easily be passed to a browser for a JavaScript function to insert into a table.

I wrote my own little module to do Prolog term to JSON list conversion because I found the builtin libraries assumed JSON objects rather than lists. My way works around the snag that each JSON object key name has to be unique, whereas in Prolog something like row can be used multiple times.

I also know what data is being passed back and forth, so don’t need to supply the type for conversion.

My json_terms.pl module is as follows (very simple, and works for me).

:- module(json_terms, [ json_terms/2
                      , list_object/2
                      ]).

/** <module> Bi-directional conversion between list of Prolog facts and a Json nested array.

@author Robert Laing
@license GPL
*/

/**
 * json_terms(?Json:list(text), ?Terms:list(term)) is semidet
 *
 * Bi-directional conversion between states represented in Json and Prolog
 *
 * ```prolog
 * json_terms(Json,
 *            [cell(1, 1, x), cell(1, 2, x), cell(1, 3, o),
 *             cell(2, 1, b), cell(2, 2, o), cell(2, 3, b),
 *             cell(3, 1, b), cell(3, 2, o), cell(3, 3, x),
 *             control(white)]).
 * Json = '[["cell", 1, 1, "x"], ["cell", 1, 2, "x"], ["cell", 1, 3, "o"],
 *          ["cell", 2, 1, "b"], ["cell", 2, 2, "o"], ["cell", 2, 3, "b"],
 *          ["cell", 3, 1, "b"], ["cell", 3, 2, "o"], ["cell", 3, 3, "x"],
 *          ["control", "white"]]'.
 * ```
 */

atom_json(true, true) :- !.
atom_json(false, false) :- !.
atom_json(null, null) :- !.

atom_json(Compound, JsonArray) :-
  is_list(JsonArray), !,
  maplist(atom_json, AtomList, JsonArray),
  Compound =.. AtomList.

/*
atom_json(AtomList, JsonArray) :-
  is_list(AtomList), !,
  maplist(atom_json, AtomList, JsonArray).
*/

atom_json(Compound, List) :-
  \+is_list(Compound),
  compound(Compound), !,
  Compound =.. AtomList,
  jsonify(AtomList, List).

atom_json(Number, Number) :- number(Number), !.
atom_json(Atom, String) :- atom_string(Atom, String).
 
jsonify(AtomList, JsonArray) :-
    maplist(atom_json, AtomList, JsonArray).

json_terms(Json, Terms) :-
    ground(Terms), !,
    maplist(=.., Terms, List),
    maplist(jsonify, List, Strings),
    term_to_atom(Strings, Json).

json_terms(Json, Terms) :-
    term_string(Strings, Json),
    maplist(jsonify, List, Strings),
    maplist(=.., Terms, List).

/**
 * list_object(+Terms:list(term), -Json:object(text)) is semidet
 *
 * Converts a list of Prolog terms to a curly-bracketed Json key:value list
 *
 * ```prolog
 * list_object([goal(red, 1), goal(teal, 0)], Json).
 * Json = '{"red": 1, "teal": 0}'
 * ```
 */
 
keyval([_, Key, Value], Str) :-
  atomics_to_string(["\"", Key, "\"", ': ', Value], Str).

list_object(Terms, Json) :-
  maplist(=.., Terms, L2),
  maplist(keyval, L2, L3),
  atomics_to_string(L3, ', ', Str),
  atomics_to_string(['{', Str, '}'], '', SJson),
  atom_string(Json, SJson).