Weblog Banner

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.

Great Oaks

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

Consulting Room

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!` ).
Consulting Family

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!"

Tabling Points

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),
   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 = _
   ),                                   Utilities
   message( [NAME_0,`is`,RELATE,`of`,NAME_1], Query ),
   (  enquire( RELATE, NAME_0, NAME_1 ),
      submit( 'family_2.htm',
              [ (query,Query),
      get_atom( action, Action ),
      Action = done
   -> family_loop( `Further solutions ignored` )
   ;  family_loop( `No more solutions` )

Utility Predicates

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_" )
   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 ),
   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,
   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 here to run the "Family Tree" example

Click on the buttons to find out more, or jump directly to any of these links:

Weblog Previous Next
Direct LinkExample 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"