2010-11-01

« »

Edited to add: Upon request I added a demo run of the script below over there. This will hopefully relax the prerequisite knowledge mentioned below.

A Straight Forward Process

I'll walk you through some boilerplate code to introduce you how to get the ball rolling.

Prerequisite to understand the rest is basic understanding of HTML some XSLT, SQL and Scheme. You will see how the combined power of those will result in concise source code.

<xsl:stylesheet
 xmlns:ac="http://www.askemos.org/2000/CoreAPI#"
 xmlns:d="http://www.askemos.org/2005/NameSpaceDSSSL/"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xsql="http://www.askemos.org/2006/XSQL/"
 version="1.0" xml:space="default">

A variable (ab)used as a configuration constant further down. The ~ code's in there are defined in SRFI-19.

<xsl:variable name="date-format">~Y-~m-~dT~H:~M:~S.~N~z</xsl:variable>

Top Level Dispatch

Views

This template will be instantiated when idempotent ("read", e.g., HTTP GET) requests are answered.

<xsl:template match="request[@type=&apos;read&apos;]">
  <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
    <title>Walurne</title>
    <!-- Note to those, who read German: The pun in "Walurne" was intented. --> 
    <meta content="application/xhtml+xml; charset=UTF-8" http-equiv="Content-Type"></meta>
   </head>
   <body>

Elements of the DSSSL namespace are modeled after their XSLT counterparts. With a Scheme expression language instead of XPath.

This example uses SRFI-49 Syntax (akin to Python code) instead of the traditional, full bracketed LISP Syntax. (Which is supposed to be more friendly to the casual reader of LISP code.)

<d:copy-of select="#CONTENT">
guard ; catch exception from not existing database
 exception
  ;; No special handled case, hence fall through to else
  else
   xsl-variable "teaser" ; in that case display the teaser
 xsl-variable (sql-ref '(select name from state)) ; otherwise select view according to current state
    </d:copy-of>
   </body>
  </html>
 </xsl:template>

Mutations

The following template will be instantiated for transactions (requests with type "write", e.g., HTTP POST, PUT).

<xsl:template match="request[@type=&apos;write&apos;]">
  <ac:reply>
   <ac:become><!-- Object Body unchanged -->
    <d:copy-of select="(grove-root (current-node))"></d:copy-of>
   </ac:become>
   <!-- check/translate/match requests -->
   <xsl:call-template name="callaction"></xsl:call-template>
   <!-- Don't pass any output by default. -->
   <ac:output media-type="text/plain" method="xml">Erfolg</ac:output>
  </ac:reply>
 </xsl:template>

A Guarded Dispatch For The Sake Of Safety

Because we know: there is absolutely no way to write a software and add security later on. Too many fruitless hopes have died believing so.

callaction will try to find a template according to current state and requested action. On exception sin is choosen to handle the case.

Note that this translation is the obvious point to evaluate session dependent parameters too.

<xsl:template name="callaction">
  <d:copy-of select="#CONTENT">xsl-variable apply:
  guard
   exception (else "sin")
   sql-ref `(select ok.t from state join ok on state.name = ok.s
             where state.s = 1 and
             ok.a = ,(data (form-field 'action (current-node))))
  </d:copy-of>
 </xsl:template>

Paradise State: everything is possible

How It Looks Alike

Each view is kept in a variable. Here the opener.

<xsl:variable name="teaser">
  <h1>Auswahl Betreff</h1>
  <d:form method="post">
   <input name="name" type="text"></input>
   <input name="action" type="hidden" value="sin"></input>
   <input type="submit" value="ausrufen"></input>
  </d:form>
 </xsl:variable>

And How To Get Out Of It

Since there are no rules we create our own.

If we had to handle session parameters, the translation, here by table ok, would be more complex. We keep the redundancy in the table here from the boilerplate code.

Eventually there a new state setup is entered, which is used by the first template above to choose the next page to display.

<xsl:template name="sin">
  <ac:update>
   create table state (s integer primary key, name text unique);
   create table ok (s text, a text, t text);
   insert into ok values('setup','setup','setup');
   insert into ok values('count','count','count');
   insert into ok values('prepare','deadline','deadline');
   insert into ok values('prepare','window','window');
   insert into ok values('running','running','running');
   create table v (
    id integer primary key autoincrement,
    name text unique,
    s integer default 0
   );
   insert or replace into state(s, name) values(1, "setup");
   create table t (name text);
   insert into t(name) values(
  '<d:copy-of select="sql-quote (data (form-field &apos;name (current-node)))"></d:copy-of>')
  </ac:update>
 </xsl:template>

More States - Now Rule Based

The rest of this stylesheet repeats the structure outlined so far. It's a straight forward, sequential process with the states:

  1. setup
  2. count
  3. prepare
  4. running

shared among all states: more common values

Another variable used as the heading in all views (HTML pages).

(Note that XSL variables are evaluated just once upon first reference. Therefore it's not too much waste to declare variables, which are never used --- though one should put those in local scope if possible without repeating code).

'<xsl:variable name="purpose">
  <h1>
   <xsql:value-of>select name from t</xsql:value-of>
  </h1>
 </xsl:variable>

setup state: evaluate form fields

In setup state we add possible victims to the v table.

Once the close button has been pushed we proceed.

<xsl:variable name="setup">
  <d:copy-of select="xsl-variable &quot;purpose&quot;"></d:copy-of>
  <h2>Möglichkeiten</h2>
  <d:form method="post">
   <p>Hinzufügen: <input name="name" type="text"></input>
<input name="action" type="hidden" value="setup"></input>
<input type="submit" value="+"></input>
<input name="close" type="submit" value="="></input></p>
  </d:form>
  <blockquote>
   <xsql:query row-element="tr" rowset-element="table">
    select name as td from v asc
   </xsql:query>
  </blockquote>
 </xsl:variable>
 <xsl:template name="setup">
  <!-- Do not try to add empty strings as names. -->
  <d:if test="(not (string-null? (data (form-field &apos;name (current-node)))))">
   <ac:update>insert into v(name) values (
    '<d:copy-of select="sql-quote (data (form-field &apos;name (current-node)))"></d:copy-of>'
    )</ac:update>
  </d:if>
  <d:if test="(not (node-list-empty? (form-field &apos;close (current-node))))">
   <ac:update>update state set name='count' where s=1</ac:update>

Let's introduce an advanced feature. The next screen evaluates a parameter n. We create a redirection with n set to 1.

The trick comes from our magic namespace d:Elements in the d-namespace may have attributes from the evaluated namespaces. Attributes from the d-namespace are evaluated as Scheme/DSSSL expressions.

The whole expression boils down to "append ?d=1 to the magic URL".

'  <d:output d:location="read-locator (msg &apos;location-format) (message-location msg) body: &quot;n=1&quot;"></d:output>
  </d:if>
 </xsl:template>

count state: the redir trick

This is the view, which has the n parameter.

<xsl:variable name="count">
  <d:copy-of select="xsl-variable &quot;purpose&quot;"></d:copy-of>
  <h2>Durchzählen</h2>
  <h3>Kandidaten</h3>
  <xsql:query row-element="tr" rowset-element="table">
   select name as td from v asc
  </xsql:query>
  <h3>Vasallen</h3>
  <d:form method="get">Anzahl: <d:input name="n" type="text" d:value="form-field &apos;n (current-node)"></d:input>
<input type="submit" value="="></input>
  </d:form>
  <d:form method="post">
   <input name="action" type="hidden" value="count"></input>
   <d:input name="n" type="hidden" d:value="form-field &apos;n (current-node)"></d:input>
   <p><d:copy-of select="children (form-field &apos;n (current-node))"></d:copy-of>
      Stimmen
      <input name="close" type="submit" value="übermitteln"></input></p>

I resort to Scheme to include n random integers, with n the numeric values of the n-parameter, as hidden html input elements with a value of n.

Just because I did not have a better idea. There are several ways to supply those.

'  <d:copy-of select="#CONTENT">
begin
 define limit
  or
   (string->number (data (form-field 'n (current-node))))
   1
 let loop ((s '()) (n 0))  ; accumulate tan's in s
  if (eqv? n limit)  ; put them in hidden input fields
   map
    lambda (n)
     sxml `(input (@ (type "hidden") (name "tan") (value ,(literal n))))
    s
   let try ((x (random-integer (* 1000 limit)) ))
    if (memv x s)  ; x already there? try again
      try (random-integer (* 1000 limit))
      loop (cons x s) (+ n 1)  ; keep it
      </d:copy-of>
  </d:form>
 </xsl:variable>

Nothing special here. Just create and fill the tan table.

<xsl:template name="count">
  <ac:update>
   create table q (tan integer primary key);
<d:copy-of select="#CONTENT">
node-list-map
 lambda (n)
  literal "insert into q(tan) values('" (sql-quote (data n)) "');\n"
 form-field 'tan (current-node)
   </d:copy-of>
   create table period (start date, end date);
   update state set name='prepare' where s=1
  </ac:update>
 </xsl:template>

prepare state: multiple options

The next step ushers to prepare the wall clock driven stage.

<xsl:variable name="prepare">
  <d:copy-of select="xsl-variable &quot;purpose&quot;"></d:copy-of>
  <h2>TAN Liste</h2>
  <p>Diese TAN-Liste muß in die Lostrommel:</p>
<!-- Führende Nullen wären nett. -->
  <xsql:query row-element="tr" rowset-element="table">
    select tan as td from q
   </xsql:query>
  <h2>Starten</h2>
  <dl>
   <dt>Ab jetzt</dt>
   <dd>
    <d:form method="post">
     <input name="action" type="hidden" value="deadline"></input>
     Frist: <input name="duration" type="text" width=""></input>(sec.)
     <input type="submit" value="!"></input>
    </d:form>
   </dd>
   <dt>Im Zeitraum</dt>
   <dd>
    <d:form method="post">
     <input name="action" type="hidden" value="window">
Von: <input name="start" type="text" width=""></input>
bis: <input name="end" type="text" width=""></input>
<input type="submit" value="!"></input>
    </d:form>
   </dd>
  </dl>
 </xsl:variable>

Eventually we have at least two different templates applicable to the same state.

The first with some SRFI-19 calendar arithmetic. The second one relying on sqlite.

<xsl:template name="deadline">
  <ac:update><d:copy-of select="#CONTENT">
begin
 define duration
   (string->number (data (form-field 'duration (current-node))))
 define start (message-date msg)
 define end
  time-utc->date
   add-duration
    date->time-utc start
    (make-time 'time-duration 0 duration)
   date-zone-offset start
 define fmt (data (xsl-variable "date-format"))
 literal "insert into period(start,end) values('"
  date->string start fmt
  "','"
  date->string end fmt
  "');"
   </d:copy-of>
   update state set name='running' where s=1
  </ac:update>
 </xsl:template>
 <xsl:template name="window">
  <ac:update><d:copy-of select="#CONTENT">begin
 literal "insert into period(start,end) values('"
  sql-quote (data (form-field 'start (current-node)))
  "','"
  sql-quote (data (form-field 'end (current-node)))
  "');"
   </d:copy-of>
   update state set name='running' where s=1
  </ac:update>
 </xsl:template>

running state: the wall clock

During the period set by the wall clock we depend on current time. A good chance to introduce how to code an alternative way. Using xsl:choose we have a match case and an xsl:otherwise.

Well, in this case, one might want to depend on the (current-date) instead of message-date. That's an oversight.

<xsl:variable name="now">
  <d:copy-of select="(date->string (message-date msg) (data (xsl-variable &quot;date-format&quot;)))"/>
 </xsl:variable>
 <xsl:variable name="running">
  <d:copy-of select="xsl-variable &quot;purpose&quot;"></d:copy-of>
  <xsl:choose>
   <d:when test="= 0 (sql-ref
                      `(select count(*) from period where
                      ,(xsl-variable &quot;now&quot;) between start and end))">
    <h2>Abgeschlossen</h2>
    <p>
     <span>Ergebnis </span>
     <d:copy-of select="#CONTENT">
begin
 define satisfacted
  sql-ref "select s from v where s in (select max(s) from v)"
 define objecting
  sql-ref "select count(*) from q"
 literal (/ satisfacted (+ satisfacted objecting))
     </d:copy-of>
     <span> Zustimmung zu </span>
     <xsql:value-of>select name from v where s in (select max(s) from v)</xsql:value-of>
    </p>
<!--
    <xsql:query row-element="tr" rowset-element="table">select name as td, s as td from v order by s asc</xsql:query>
-->
    <d:form method="post">
     <input name="action" type="hidden" value="running"></input>
     <input type="submit" value="Anerkennen"></input>
    </d:form>
   </d:when>
   <xsl:otherwise>
    <p>Von <xsql:value-of>select start from period</xsql:value-of>
bis <xsql:value-of>select end from period</xsql:value-of> entscheiden:</p>
    <d:form method="post">
     <p>
      <span>TAN: </span>
      <input name="tan" type="text"></input>
      <input name="action" type="hidden" value="running"></input>
     </p>
     <ul>
      <xsl:variable name="options">
       <xsql:query rowset-element="">select id, name from v</xsql:query>
      </xsl:variable>
      <d:for-each select="xsl-variable &quot;options&quot;">
       <li>
        <d:input name="id" type="submit" d:value="data ((sxpath &apos;(id)) (current-node))"></d:input>
        <d:copy-of select="data ((sxpath &apos;(name)) (current-node))"></d:copy-of>
       </li>
      </d:for-each>
     </ul>
    </d:form>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:variable>

The same comment as for the corresponding variable running applies here.

During the poll period delete the used TAN and count the casted vote (which means as much as lost voice?); otherwise display the result and confidence.

<xsl:template name="running">
  <xsl:choose>
   <d:when test="= 0 (sql-ref
                      `(select count(*) from period where
                        ,(xsl-variable &quot;now&quot;)
                        between start and end))">
    <!-- END OF Processing so far - we raise an exception to avoid further changes -->
    <ac:update>
     <d:copy-of select="#CONTENT">raise 'nixda</d:copy-of>
    </ac:update>
   </d:when>
   <xsl:otherwise>
    <d:copy-of select="(or
 (= 1 (sql-ref `(select count(tan) from q where tan = ,(data (form-field &apos;tan (current-node))))))
 (error &quot;TAN invalid&quot;))"></d:copy-of>
    <ac:update>
update v set s=s+1 where id=
'<d:copy-of select="sql-quote (data (form-field &apos;id (current-node)))"></d:copy-of>';
delete from q where tan=
'<d:copy-of select="sql-quote (data (form-field &apos;tan (current-node)))"></d:copy-of>'
    </ac:update>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

Puh

So you made your way through the code. Congratulations!

Hopefully you got enough of an idea to adapt it to your needs. If not, feel free to ask/comment.

BTW
The code was tested just before the comments where added.

« »

2015-11
2015-10
2015-08
2015-01
2014-11
2014-09
2014-08
2014-07
2014-06
2014-05
2014-04
2014-03
2014-02
2014-01
2013-12
2013-11
2013-10
2013-09
2013-08
2013-07
2013-06
2013-05
2013-04
2013-02
2013-01
2012-12
2012-11
2012-10
2012-09
2012-08
2012-07
2012-06
2012-05
2012-04
2012-03
2012-02
2012-01
2011-12
2011-11
2011-10
2011-08
2011-07
2011-06
2011-03
2011-02
2011-01
2010-12
2010-11
2010-10
2008-07
2006-09