Author: achiumenti
Date: Tue Jan 13 13:03:03 2009
New Revision: 184
Log:
Added html template capabilities for pages and components
Added:
trunk/main/claw-html/src/parser.lisp
Modified:
trunk/main/claw-html/claw-html.asd
trunk/main/claw-html/src/meta.lisp
trunk/main/claw-html/src/packages.lisp
trunk/main/claw-html/src/tags.lisp
Modified: trunk/main/claw-html/claw-html.asd
==============================================================================
--- trunk/main/claw-html/claw-html.asd (original)
+++ trunk/main/claw-html/claw-html.asd Tue Jan 13 13:03:03 2009
@@ -31,10 +31,11 @@
:name "claw-html"
:author "Andrea Chiumenti"
:description "Common Lisp Active Web HTML generator."
- :depends-on (:closer-mop :local-time :parenscript :cl-ppcre :split-sequence)
+ :depends-on (:closer-mop :local-time :parenscript :cl-ppcre :split-sequence :closure-html)
:components ((:module src
:components ((:file "packages")
(:file "meta" :depends-on ("packages"))
+ (:file "parser" :depends-on ("packages"))
(:file "tags" :depends-on ("packages" "meta"))
(:file "components" :depends-on ("tags" "meta"))
(:file "validators" :depends-on ("components"))
Modified: trunk/main/claw-html/src/meta.lisp
==============================================================================
--- trunk/main/claw-html/src/meta.lisp (original)
+++ trunk/main/claw-html/src/meta.lisp Tue Jan 13 13:03:03 2009
@@ -29,6 +29,9 @@
(in-package :claw-html)
+(defvar *components-templates* (make-hash-table)
+ "Hash table that stores the templates for CLAW components")
+
(defclass metacomponent (standard-class)
()
(:documentation "This is the meta class the must be set for every WCOMPONENT.
Modified: trunk/main/claw-html/src/packages.lisp
==============================================================================
--- trunk/main/claw-html/src/packages.lisp (original)
+++ trunk/main/claw-html/src/packages.lisp Tue Jan 13 13:03:03 2009
@@ -31,7 +31,7 @@
(defpackage :claw-html
- (:use :cl :closer-mop :local-time :parenscript :cl-ppcre :split-sequence)
+ (:use :cl :closer-mop :local-time :parenscript :cl-ppcre :split-sequence :closure-html)
(:shadow :flatten)
(:documentation "A comprehensive web application framework and server for the Common Lisp programming language")
(:export #:*html-4.01-strict*
@@ -43,6 +43,7 @@
#:*rewind-parameter*
#:*validation-errors*
#:*claw-current-page*
+ #:*claw-this-component*
#:error-page
#:render-error-page
@@ -51,6 +52,7 @@
#:build-tagf
#:page
#:page-before-render
+ #:template
#:page-render
#:make-page-renderer
#:page-current-form
@@ -60,7 +62,7 @@
#:page-global-initscripts
#:page-initscripts
#:page-initstyles
- #:page-current-component
+ #:current-component
#:page-body-initscripts
#:htcomponent
#:htcomponent-page
@@ -83,6 +85,8 @@
#:htstring
#:$>
#:$raw>
+ #:htignore
+ #:ignore>
;empty tags definition
#:*empty-tags*
#:area>
@@ -177,6 +181,7 @@
#:u>
#:ul>
#:var>
+ #:parse-claw-template
;; class modifiers
#:page-content
#:generate-id
Added: trunk/main/claw-html/src/parser.lisp
==============================================================================
--- (empty file)
+++ trunk/main/claw-html/src/parser.lisp Tue Jan 13 13:03:03 2009
@@ -0,0 +1,125 @@
+;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
+;;; $Header: src/components.lisp $
+
+;;; Copyright (c) 2008, Andrea Chiumenti. All rights reserved.
+
+;;; Redistribution and use in source and binary forms, with or without
+;;; modification, are permitted provided that the following conditions
+;;; are met:
+
+;;; * Redistributions of source code must retain the above copyright
+;;; notice, this list of conditions and the following disclaimer.
+
+;;; * Redistributions in binary form must reproduce the above
+;;; copyright notice, this list of conditions and the following
+;;; disclaimer in the documentation and/or other materials
+;;; provided with the distribution.
+
+;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
+;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+(in-package :claw-html)
+
+(defclass claw-html-builder (chtml::lhtml-builder)
+ ())
+
+(defun make-claw-html-builder ()
+ (make-instance 'claw-html-builder))
+
+(defmethod hax:start-element ((handler claw-html-builder) name attrs)
+ (let* ((parent (car (chtml::stack handler)))
+ (this (list (find-symbol (format nil "~a>" (string-upcase name)) :claw-html)
+ (flatten (chtml::pt-attributes-to-lhtml attrs)))))
+ (push this (chtml::stack handler))
+ (if parent
+ (push this (cddr parent))
+ (setf (chtml::root handler) this))))
+
+(defmethod hax:end-element ((handler claw-html-builder) name)
+ (let ((current (pop (chtml::stack handler))))
+ (setf (cdr current)
+ (append (cadr current) (reverse (cddr current))))))
+
+;; component parser
+
+(defvar *component-content-template* nil)
+
+(defclass claw-html-component-builder (claw-html-builder)
+ ((component-content-template :initform nil
+ :accessor component-content-template-p)
+ (component-content-ignore :initform nil
+ :accessor component-content-ignore-p)
+ (parsed-content :initform nil
+ :accessor parsed-content)))
+
+(defun make-claw-html-component-builder ()
+ (make-instance 'claw-html-component-builder))
+
+(defmethod hax:start-element :before ((handler claw-html-builder) name attrs)
+ (dolist (attr attrs)
+ (cond
+ ((and (string-equal (hax:attribute-name attr) "CLAWTYPE")
+ (string-equal (hax:attribute-value attr) "$ignore$"))
+ (setf (component-content-ignore-p handler) t))
+ ((and (string-equal (hax:attribute-name attr) "CLAWTYPE")
+ (string-equal (hax:attribute-value attr) "$content$")
+ (null (component-content-ignore-p handler)))
+ (if (component-content-template-p handler)
+ (error "$content$ found multiple times in template")
+ (setf (component-content-template-p handler) t))))))
+
+(defun parse-attributes (attrs)
+ (loop for (key value) on attrs by #'cddr
+ collect key
+ when value collect (parse-attribute-value value)))
+
+(defun parse-attribute-value (value)
+ (multiple-value-bind (result matchesp)
+ (cl-ppcre:regex-replace "(?i)(^\\$lisp>)+([.])*" value "\\2")
+ (if matchesp
+ (read-from-string result)
+ result)))
+
+
+(defmethod hax:end-element ((handler claw-html-component-builder) name)
+ (let ((current (pop (chtml::stack handler))))
+ (let ((attrs (parse-attributes (cadr current))))
+ (cond
+ ((string-equal (getf attrs :clawtype) "$ignore$")
+ (setf (cdr current) nil
+ attrs nil
+ (component-content-ignore-p handler) nil
+ (car current) (find-symbol "IGNORE>" "CLAW-HTML")))
+ ((string-equal (getf attrs :clawtype) "$body$")
+ (setf (cdr current) nil
+ attrs (list (find-symbol "*CLAW-THIS-COMPONENT*" "CLAW-HTML"))
+ (car current) (find-symbol "HTCOMPONENT-BODY" "CLAW-HTML")))
+ ((and (component-content-template-p handler)
+ (string-equal (getf attrs :clawtype) "$content$")
+ (null (parsed-content handler)))
+ (remf attrs :clawtype)
+ (setf (parsed-content handler) (append (list (first current))
+ attrs
+ (reverse (cddr current))))))
+ (unless (component-content-ignore-p handler)
+ (setf (cdr current)
+ (append attrs (reverse (cddr current))))))))
+
+
+(defun parse-claw-template (input)
+ "Parses the input and returns a claw form template (i.e. a CLAW-HTML:TAG instance) and returns a lambda function with no parameters.
+The inpus may be a string a file or a stream.
+"
+ (eval `(lambda () ,(let ((handler (make-claw-html-component-builder)))
+ (chtml:parse input handler)
+ (let ((result (parsed-content handler)))
+ (or (parsed-content handler) result))))))
\ No newline at end of file
Modified: trunk/main/claw-html/src/tags.lisp
==============================================================================
--- trunk/main/claw-html/src/tags.lisp (original)
+++ trunk/main/claw-html/src/tags.lisp Tue Jan 13 13:03:03 2009
@@ -301,6 +301,9 @@
"The CLAW page currently rendering
")
+(defvar *calw-this-component* nil
+ "Variable set when rendering a WCOMPONENT-TEMPLATE so it is accessible inside the template")
+
(defvar *id-table-map* (make-hash-table :test 'equal)
"Holds an hash table of used components/tags id as keys and the number of their occurrences as values.
So if you have a :id \"compId\" given to a previous component, the second
@@ -396,6 +399,7 @@
(id (getf attributes :id))
(static-id (getf attributes :static-id))
(render-condition (getf attributes :render-condition))
+ (claw-type (getf attributes :clawtype))
(real-id (or static-id id))
(instance))
(when static-id
@@ -403,19 +407,27 @@
(setf id nil))
(when render-condition
(remf attributes :render-condition))
- (setf instance (make-instance parent
- :empty emptyp
- :real-id real-id
- :name (string-downcase tag-name)
- :render-condition render-condition
- :attributes attributes
- :body (second fbody)))
- (when real-id
- (if (null static-id)
- (when (and id-table-map id)
- (setf (htcomponent-client-id instance) (generate-id id)))
- (setf (htcomponent-client-id instance) static-id)))
- instance))
+ (if claw-type
+ (let (clawtype-sybol-list (split "([:]){1,2}" (string-upcase claw-type)))
+ (if (second clawtype-sybol-list)
+ (setf claw-type (find-symbol (second clawtype-sybol-list)
+ (first clawtype-sybol-list)))
+ (setf claw-type (find-symbol (first clawtype-sybol-list))))
+ (make-component claw-type attributes (second fbody)))
+ (progn
+ (setf instance (make-instance (or claw-type parent)
+ :empty emptyp
+ :real-id real-id
+ :name (string-downcase tag-name)
+ :render-condition render-condition
+ :attributes attributes
+ :body (second fbody)))
+ (when real-id
+ (if (null static-id)
+ (when (and id-table-map id)
+ (setf (htcomponent-client-id instance) (generate-id id)))
+ (setf (htcomponent-client-id instance) static-id)))
+ instance))))
(defun generate-tagf (tag-name emptyp)
"Internal function that generates an htcomponent creation function from the component class name
@@ -487,7 +499,10 @@
:documentation "Symbol for page charset encoding \(Such as UTF-8)")
(injection-writing-p :initform nil
:accessor page-injection-writing-p
- :documentation "Flag that becomes true when rendering page injections"))
+ :documentation "Flag that becomes true when rendering page injections")
+ (teamplate :initarg :template
+ :accessor template
+ :documentation "A lambda function with no parameters that, when not nil, is used as page-content."))
(:default-initargs :writer t
:external-format-encoding :utf-8
:script-files nil
@@ -501,9 +516,14 @@
:xmloutput nil
:doc-type *html-4.01-transitional*
:request-parameters nil
+ :template nil
:mime-type "text/html")
(:documentation "A page object holds claw components to be rendered") )
+(defmethod page-content ((page page))
+ (when-let (lambda-content (template page))
+ (funcall lambda-content)))
+
(defun make-page-renderer (page-class http-post-parameters http-get-parameters)
"Generates a lambda function from PAGE-RENDER method, that may be used into LISPLET-REGISTER-FUNCTION-LOCATION"
#'(lambda () (with-output-to-string (*standard-output*)
@@ -572,6 +592,10 @@
(:default-initargs :raw nil)
(:documentation "Component needed to render strings"))
+(defclass htignore (htcomponent)
+ ()
+ (:documentation "Ignore all content"))
+
(defmethod initialize-instance :after ((inst tag) &rest keys)
@@ -589,6 +613,11 @@
"Creates a non escaping htstring component"
(make-instance 'htstring :body value :raw t))
+(defun ignore> (&rest ignore)
+ "Creates an ignore content"
+ (declare (ignore ignore))
+ (make-instance 'htignore))
+
(defclass htscript (tag) ()
(:documentation "Creates a component for rendering a <script> tag"))
@@ -1062,6 +1091,11 @@
(htcomponent-render injection page)))
(tag-render-endtag hthead page))))))
+;;;========= HTIGNORE ===================================
+(defmethod htcomponent-rewind((htignore htignore) (page page)))
+(defmethod htcomponent-prerender((htignore htignore) (page page)))
+(defmethod htcomponent-render ((htignore htignore) (page page)))
+
;;;========= HTSTRING ===================================
(defmethod htcomponent-rewind((htstring htstring) (page page)))
@@ -1222,11 +1256,19 @@
(allow-informal-parameters :initarg :allow-informal-parameters
:reader wcomponent-allow-informal-parametersp
:allocation :class
- :documentation "Determines if the component accepts informal parameters"))
+ :documentation "Determines if the component accepts informal parameters")
+ (teamplate :initarg :template
+ :accessor template
+ :documentation "A lambda function with no parameters that, when not nil, is used as page-content. *CLAW-THIS-COMPONENT* is set as a closure, so that may be directly used inside the template."))
(:default-initargs :reserved-parameters nil
:allow-informal-parameters t)
(:documentation "Base class for creationg customized web components. Use this or one of its subclasses to make your own."))
+(defmethod wcomponent-template ((wcomponent wcomponent))
+ (let ((*claw-this-component* wcomponent))
+ (when-let (lambda-content (template page))
+ (funcall lambda-content))))
+
(defmethod wcomponent-created ((wcomponent wcomponent))
nil)
@@ -1270,6 +1312,7 @@
(defun make-component (name parameters content)
"This function instantiates a wcomponent by the passed NAME, separetes parameters into formal(the ones that are the
initarg of a slot, and informal parameters, that have their own slot in common. The CONTENT is the body content."
+ (remf :clawtype parameters)
(unless (or (getf parameters :id)
(getf parameters :static-id))
(setf (getf parameters :id) "claw"))