Logic and Design
All of the examples so far have been displayed on very simple web pages, so that you could concentrate on the programming principles involved, rather than simply look in wonder (or something!) at a carefully crafted web application.
A main aim of Weblog is to simplify the process of making interactive, intelligent web pages, and this example is the first of several which will hopefully start to show you how to embded Prolog logic in web pages that have been designed independently, complete with style sheets and graphics elements.
Tables and Forms
You have seen how easily Weblog can let you display Prolog results in a pre-written web page, and in the previous example, saw a first glimpse of multi-line output, in the form of a program listing. But that is just a hint at the real possibilities.
An important thing to remember, is that you can replace a Weblog label with absolutely anything: a single word or number, maybe a few lines of program listing, or indeed, chunks of Prolog-generated HTML. In this next example, you will explore this further, with the creation of tables and enumerated elements in dynamic forms.
It is worth noting that this example still leaves the design and appearance of the web page to the source HTML files, together with a CSS style sheet and some background graphics. The same program could run in a plain, black-text-on-white-background page, just like the previous examples. The key point is that the design of your pages is under the control of your web designer; the content is created by your Prolog programmer.
Great Oaks From Little Acorns Grow
This Weblog demonstration loads and runs the "FAMILY.PL" example, in which relationships between people are very simply defined as "parent of" or "spouse of", after which all other relationships are computed through a set of rules.
When the program starts, it is pre-loaded with the following family tree:
John = Mary Mike = Dora Adam = Faye | | | --- ---------------------- ------------- | | | | | Dave = Lisa Fred = Emma June | | ----------- --------------- | | | | | Evan Jack Gary Suzy Nita
You will be able to query this family, and to add and delete names, and change their relationships, in a powerful demonstration of Weblog's ability to run pure Prolog programs across the Web, within a page that has been designed independently. Here is the start of the program, "family_0.pl":
% this is the entry point for every weblog application weblog :- consult( family ), family_loop( `When you are ready, let's go!` ).
As ever, weblog/0 is the automatic entry point for the application: in this case, its first job is to call "consult/1" to load in the actual example program, "family.pl", which is a standard part of the WIN-PROLOG release. This is another example of Weblog's powerful ability to modify its own code base at runtime.
Once the example file is loaded, weblog/0 calls "family_loop/1" with some text to display, "When you are ready, let's go!"
The main family tree program generates tables and option lists (see later) of the known persons, parents and spouses which are part of the "family.pl" example, then calls submit/2 to display these, together with the input message text, in an HTML web page, "family_1.htm". On its continuation, "get_atom/2" is used to retrieve an action, which is then passed into "family/1":
% family time - just pick up the required action, and do it
family_loop( Text ) :- table( known_person, Person ), table( known_parent, Parent ), table( known_spouse, Spouse ), option( known_person(X,_), X, Option ), option( relation(X), X, Relate ), submit( 'family_1.htm', [ (text,Text), (person,Person), (parent,Parent), (spouse,Spouse), (option,Option), (relate,Relate) ] ), get_atom( action, Action ), family( Action ).
A Case for Going Loopy
The family/1 predicate comprises a series of clauses each of which handles one of known actions, before returning control to family_loop/1 with suitable text:
% add a person, if not already there family( add_person ) :- get_atom( name_a, Name ), get_atom( gender, Gender ), ( cmp( 0, Name, '' ) -> message( [`please specify a name`], Text ) ; known_person( Name, _ ) -> message( [Name,`already exists`], Text ) ; assert( known_person(Name,Gender) ), message( [Name,`successfully added`], Text ) ), family_loop( Text ). % delete a person, if already there family( delete_person ) :- get_atom( name_d, Name ), ( known_person( Name, _ ) -> retractall( known_person(Name,_) ), retractall( known_parent(Name,_) ), retractall( known_parent(_,Name) ), retractall( known_spouse(Name,_) ), retractall( known_spouse(_,Name) ), message( [Name,`successfully deleted`], Text ) ; message( [Name,`does not exist`], Text ) ), family_loop( Text ). % add a parent, if not already there family( add_parent ) :- get_atom( name_0, Name_0 ), get_atom( name_1, Name_1 ), ( ( cmp( 0, Name_0, '' ) ; cmp( 0, Name_1, '' ) ; cmp( 0, Name_0, Name_1 ) ) -> message( [`please specify two names`], Text ) ; relation( Pred ), Pred( Name_0, Name_1 ) -> message( [Name_0,`is a`,Pred,`of`,Name_1], Text ) ; assert( known_parent(Name_0,Name_1) ), message( [Name_0,`successfully added as parent of`,Name_1], Text ) ), family_loop( Text ). % add a spouse, if not already there family( add_spouse ) :- get_atom( name_0, Name_0 ), get_atom( name_1, Name_1 ), ( ( cmp( 0, Name_0, '' ) ; cmp( 0, Name_1, '' ) ; cmp( 0, Name_0, Name_1 ) ) -> message( [`please specify two names`], Text ) ; relation( Pred ), Pred( Name_0, Name_1 ) -> message( [Name_0,`is a`,Pred,`of`,Name_1], Text ) ; assert( known_spouse(Name_0,Name_1) ), message( [Name_0,`successfully added as spouse of`,Name_1], Text ) ), family_loop( Text ). % enquire about the family tree family( enquire ) :- table( known_person, Person ), table( known_parent, Parent ), table( known_spouse, Spouse ), get_atom( relate, Relate ), get_atom( name_x, Name_0 ), get_atom( name_y, Name_1 ), ( relation( Relate ) -> RELATE = Relate ; RELATE = _ ), ( known_person( Name_0, _ ) -> NAME_0 = Name_0 ; NAME_0 = _ ), ( known_person( Name_1, _ ) -> NAME_1 = Name_1 ; NAME_1 = _ ), message( [NAME_0,`is`,RELATE,`of`,NAME_1], Query ), ( enquire( RELATE, NAME_0, NAME_1 ), submit( 'family_2.htm', [ (query,Query), (person,Person), (parent,Parent), (spouse,Spouse), (relate,RELATE), (name_0,NAME_0), (name_1,NAME_1) ] ), get_atom( action, Action ), Action = done -> family_loop( `Further solutions ignored` ) ; family_loop( `No more solutions` ) ).
A number of utility predicates have been used in this example, to simplify the main body of the code, and to isolate an assortment of nitty-gritty requirements. The first, "get_atom/2", is essentially the same as the built-in Weblog predicate, getarg/2, except that it performs some "normalisation" of the resulting atom. In particular, it forces it to lower case, and retains only alphabetic characters together with the underscore, "_":
% get an argument and convert it into a cleaned-up, lowercase atom get_atom( Name, Atom ) :- getarg( Name, Data ), stratm( Text, Data ), lwrupr( Lows, Text ), strchr( Lows, List ), findall( Char, ( member( Char, List ), member( Char, "abcdefghijklmnopqrstuvwxyz_" ) ), Nice ), atmchr( Atom, Nice ).
The "message/2" predicate takes a list of Prolog terms, and writes them out into a space-separated list, replacing any variables with "???", ready to display as a nicely formatted message:
% make a simple text message from a list message( List, Text ) :- forall( member( Item, List ), ( ( type( Item, 0 ) -> swrite( `???` ) ; swrite( Item ) ), swrite( ` ` ) ) ) ~> Text.
The "table/2" predicate picks up all known entries of a given type, then sorts them into order, before calling "tr/3" to write out the result in the form of an HTML "<table>" body, with alternate rows swapping the class names, "head" and "tail":
% tabulate the given dynamic predicate into rows of a table table( Pred, Text ) :- findall( (Name_0,Name_1), Pred( Name_0, Name_1 ), List ), sort( List, Sort,  ), tr( Sort, head, tail ) ~> Text. % display a table with alternate head and tail classes tr( , _, _ ) :- swrite( `~M~J` ). tr( [(Name_0,Name_1)|More], Head, Tail ) :- swrite( `~M~J` ), swrite( `<tr class="` ), swrite( Head ), swrite( `"><td>` ), swrite( Name_0 ), swrite( `</td><td>` ), swrite( Name_1 ), swrite( `</td></tr>` ), tr( More, Tail, Head ).
The "option/3" predicate does a similar job to table/2, except this time, it write the elements of the sorted list out in the currect HTML syntax for an options list in a "<form>":
% tabulate the given call and variable predicate into options for selection option( Call, Name, Text ) :- findall( Name, Call, List ), sort( List, Sort,  ), ( forall( member( Name, Sort ), ( swrite( `~M~J` ), swrite( `<option value="` ), swrite( Name ), swrite( `">` ), swrite( Name ), swrite( `</option>` ) ) ), swrite( `~M~J` ) ) ~> Text.
Click on the buttons to find out more, or jump directly to any of these links:
|Direct Link||Example Name|
|One Shot Program||"Hello World"|
|Input and Backtracking||"Bonjour Le Monde"|
|Learning and Storage||"Teach Me"|
|Flexible Output||"John Likes Mary"|
|Tables and Forms||"Family Tree"|
|Photoshop and Images||"Lunar Phase"|
|Illustrator and Prolog||"Big Ben Time Check"|
|DreamWeaver and VisiRule 2||"Lawns"|
Copyright © 2019 LPA Ltd
All rights reserved