REBOL [
Title: "HTML Dialect"
Short: "HTML Dialect"
Author: ["Henrik Mikael Kristensen"]
Copyright: "2008 - HMK Design"
Filename: %html.r
Version: 0.0.3
Type: 'script
Maturity: 'unstable
Release: 'internal
Created: 13-Apr-2008
Date: 20-Jul-2008
License: {
BSD (www.opensource.org/licenses/bsd-license.php)
Use at your own risk.
}
Purpose: {
HTML dialect for simple generation of webpages with REBOL code.
}
Notes: {
Some problems with bindings in the TRAVERSE word.
Complex type sets in four places in the code.
Ugly binding in HTML-GEN.
}
Future: [
0.0.4 {
TEXT-GEN to convert HTML dialect to pure text.
Better practical use of the error log.
Attributes for form elements.
}
]
History: [
0.0.3 {
Automatic formatting of links via internal site links using AT PAGE.
Page list to manage internal site links
Path! added as cell-type
Simple error logging using LOG-ERROR.
Form rules for simple form generation and filling
Improved generation of content for HEAD such as META and LINK tags.
Sets end-slash for XHTML doctypes
}
0.0.2 {
Better separation of rules.
Renamed HTML-PARSE to HTML-GEN.
Direct support for more encompassing tags.
Tracks the current doctype with DOC-TYPE.
TAGs can be evaluated using the new EVAL-RULE.
END-TAG to complement TAG.
}
0.0.1 "First version"
]
Keywords: []
]
ctx-html: context [
set 'out-buffer ""
out: func [string [word! string! file! url! tag! number! time! date! block!]] [insert tail out-buffer reduce string] ; OK, but complex input
url-vars: []
url-var-string: make string! 1000
close-tag: func [word [word!]] [to-tag join "/" word]
res-tag: none
start-tag: []
end-tags: [] ; Track the historical use of end tags
pages: []
errors: []
current-page: none
refresh-time: 5 ; The number of seconds before a redirect page refreshes.
; ---------- Words used in dialect
head-block: []
end-tag: none ; name conflict with the word used in the dialect
vars: var-block: val: cell-val: tag-vals: form-tags: none
step: 1
form-object: none ; Managing form input via object
; possibly requiring string sanitation here
; form-value: func [word [word!]] [any [all [form-object in form-object word get in form-object word] all [value? word get word] ""]]
form-value: func [word [word!]] [
case [
all [form-object in form-object word] [get in form-object word]
all [value? word] [get word]
all [not value? word] [log-error ["Word" word "does not exist."] ""]
]
]
has-form-value: func [word [word!]] [
case [
all [form-object in form-object word] [true]
all [value? word not none? get word not series? get word] [true]
all [value? word series? get word] [not empty? get word]
all [value? word none? word] [false]
not value? word [log-error ["Word" word "does not exist."] false]
]
]
; ---------- URL Generation Functions
make-url-vars: func [vars /local v word] [
clear url-var-string
parse vars [any [set word any-word! (repend url-var-string [word "="]) set v any-type! (append url-var-string v)]]
]
page-link: func [
name [word!]
/local p
] [
unless p: find pages name [return log-error compose/deep [div error ["Page " (name) " does not exist"]]]
either current-page = name [p/3][make-url-vars clear url-vars compose/deep [tag [a href (append to-string p/2 url-var-string)] [(p/3)] end-tag]]
]
build-url: func [vars /local str w] [str: make string! "" parse vars [any [w: any-word! (repend str [w/1 "="]) any-type! (repend str [w/2 "&"])]] head remove back tail str]
; ---------- Misc Functions
text-gen: func [str] [str] ; pass through for now
log-error: func [str [string! block!]] [append errors text-gen reform str str] ; OK
; ---------- Types
cell-types: [set cell-val [block! | number! | date! | time! | word! | string! | path! | url! | get-word!]] ; Complex. Find common types to shorten this.
doc-types: [
html-2.0-dtd
html-3.2-dtd
html-4.01-strict
html-4.01-transitional
html-4.01-frameset
xhtml-1.0-strict
xhtml-1.0-transitional
xhtml-1.0-frameset
xhtml-1.0-dtd
xhtml-basic-1.0-dtd
xhtml-basic-1.1-dtd
xhtml-mathml-svg-dtd
svg-1.0-dtd
svg-1.1-full-dtd
svg-1.1-basic-dtd
svg-1.1-tiny-dtd
]
doc-type: none
doc-type-words: []
foreach [word tag] doc-types [repend doc-type-words [to-lit-word word '|]]
remove back tail doc-type-words
xhtml?: does [found? find to-string doc-type 'xhtml]
single-tags: [link meta img input frameset hr br]
; ---------- HTML Dialect Table Rules
; Rules for table row generation
table-row-rules: [
; tbody rules here
any [
'row (out
)
any [
['cell (end-tag: 'td) | 'header (end-tag: 'th)] (insert clear start-tag end-tag)
; cell formatting
any [set type [['colspan set val integer! | 'align set val word! | 'width set val integer! opt ['percent (val: join val "%")]] | 'class set val word!] (repend start-tag [type val])]
; cell content
[none! (out build-tag start-tag) | cell-types (out build-tag start-tag html-gen cell-val)] (out close-tag end-tag)
] (out
)
]
]
table-cell-rule: [any [val: any-type! (html-gen compose/deep [td [(val/1)]])]]
table-row-rule: [val: any-type! (out [| ] html-gen val/1 out [ |
])]
; Using the formatted block. Only works with data as blocks of objects right now.
table-format-rules: [
any [
val: object! (parse bind format val/1 table-row-rules) ; block of objects.
| into [(out ) table-cell-rule (out
)] ; block of blocks. ignoring formatting here.
| table-row-rule ; block of single values. ignoring formatting here for later.
]
] ; per row
; Using no specific formatting, but simply produces one table cell per element in the block.
table-block-rules: [
any [
val: object! (out foreach cell next first val/1 [html-gen compose [td (get in val/1 cell)]] out
) ; block of objects
| into [(out ) table-cell-rule (out
)] ; block of blocks
| table-row-rule ; block of single values
]
]
; ---------- HTML Dialect Parse Rules
text-rules: ['html | 'head | 'title | 'body | 'p | 'strong | 'em | 'b | 'i | 'tt | 'pre | 'ul | 'il | 'li | 'sup | 'sub | 'tr | 'th | 'td | 'table | 'a | 'div | 'span] ; OK
data-rules: [[string! | tag! | number!] (out cmd/1) | block! (html-gen cmd/1)] ; OK
eval-rules: [any [val: ['do block! (insert tail eval-res do val/2) | any-type! (insert tail eval-res val/1)]]] ; OK
eval-res: make block! [] ; OK
tag-rules: [
'=== word! (tag-vals: "") opt ['opts block! (tag-vals: cmd/4)] cell-types (html-gen compose/deep [tag [(cmd/2) (tag-vals)] (cell-val) end-tag]) ; OK
| 'tag (clear eval-res) into eval-rules (res-tag: trim build-tag eval-res either find single-tags cmd/2/1 [if xhtml? [append res-tag " /"]][append end-tags cmd/2/1] out res-tag) ; OK
| 'line (html-gen [tag [hr]]) ; OK
| 'end-tag (out close-tag either block? last end-tags [first last end-tags][last end-tags] remove back tail end-tags) ; OK
| ['div | 'span] word! cell-types (html-gen compose/deep [=== (cmd/1) opts [class (cmd/2)] [(cell-val)]]) ; OK
| 'at [
'page word! (html-gen page-link cmd/3)
| set url cell-types cell-types (var-block: make block! []) any [
'vars vars: [block! | object! (vars: third vars/1) | get-word! (vars: get vars/1)] (append var-block vars)
| 'words [set words block! (parse words [any [set w word! (repend var-block [to-set-word w get w])]])]
] (unless empty? var-block [url: rejoin [url "?" build-url third make object! var-block]] html-gen compose/deep [tag [a href (url)] [(cmd/3)] end-tag])
] ; Testing. Complicated.
| 'do block! (html-gen do cmd/2) ; OK
| 'table word! (out build-tag [table class (cmd/2) cellspacing 0 cellpadding 0]) ; OK
any [
['rows [set val word! (parse get val table-block-rules) | into table-row-rules | into table-block-rules]] ; OK
| ['format set format block! 'rows [set val word! (parse get val table-format-rules) | into table-format-rules]] ; OK
] (out )
| text-rules cell-types (html-gen reduce ['=== cmd/1 cell-val]) ; OK
| 'loop integer! block! (loop cmd/2 [html-gen do cmd/3]) ; OK
| 'traverse 2 block! (repeat i length? cmd/2 [html-gen reduce bind cmd/3 cmd/2/:i]) ; Not OK. Bind problem.
]
; ---------- Form Functions and Rules
set-check-indicator: does [append last form-tags either xhtml? [[checked checked]]['checked]] ; OK
set-option-tag: func [block pos] [
case [
all [xhtml? same? block pos] [[=== option opts [selected selected] [(pick values i)]]]
all [same? block pos] [[=== option opts [selected] [(values/:i)]]]
true [[=== option [(values/:i)]]]
]
] ; OK
form-rules: [
[
'form cell-types
opt [[get-word! (vars: get cmd/3) | ['vars [block! (vars: make object! cmd/3) | object! (vars: cmd/3)]]] (form-object: vars)] cell-types
(form-tags: [=== form opts [action (cmd/2) method post] [(cell-val)]]) ; Testing
| 'textarea word! (form-tags: [tag [textarea name (cmd/2)] (form-value cmd/2) end-tag]) ; Testing
| ['field | 'button | 'hidden | 'password] word! (form-tags: [tag [input type (cmd/1) name (cmd/2) value (form-value cmd/2)]]) ; Testing
| 'checkbox word! (form-tags: copy/deep [tag [input type checkbox name (cmd/2)]] if has-form-value cmd/2 [set-check-indicator]) ; OK
| 'radio word! cell-types (form-tags: copy/deep [tag [input type radio name (cmd/2) value (cmd/3)]] if cmd/3 = form-value cmd/2 [set-check-indicator]) ; Testing
| 'select word! (form-tags: compose/deep [=== select opts [name (cmd/2)] []]) [
['values (step: 1) | 'key-values (step: 2) | (step: 1)]
cell-types (for i step length? values: head form-value cell-val step [append form-tags/5 compose/deep bind set-option-tag form-value cell-val at values i 'i])
] ; Testing
| ['submit | 'reset] string! (form-tags: [tag [input type (cmd/1) value (cmd/2)]]) ; Testing
] (html-gen compose/deep form-tags)
]
; ---------- Higher Level Rules
href-types: [word! | url! | string! | path! | refinement!] ; OK
to-head-block: func [data] [append head-block compose/deep [tag [(compose/deep data)]]] ; OK
page-rules: [
; clear out-buffer here?
'page cell-types (clear head-block) any [
val: [
'redirect href-types (to-head-block [meta http-equiv refresh content (rejoin [refresh-time "; url=" val/2])]) ; OK
| 'favicon href-types (to-head-block [link rel "shortcut icon" href (val/2)]) ; OK
| 'charset [string! | word!] (to-head-block [meta http-equiv content-type content (join "text/html; charset=" val/2)]) ; OK
| 'css href-types (to-head-block [link rel stylesheet href (val/2) type "text/css"]) ; OK
| 'script href-types (to-head-block [script src (val/2) type "text/javascript"]) ; OK
; add more types here
]
| 'meta ['name | 'http-equiv] 2 cell-types [to-head-block [meta (val/2) (val/3) content (val/4)]] ; OK
] val: block! (
html-gen compose/deep [
xhtml-1.0-strict ; should be changable
;html-4.01-strict ; activate this line when testing non-XHTML mode
html [head [(head-block) title [(cell-val)]] body [(val/1)]]
]
)
]
site-rules: ['name word! (current-page: cmd/2) | 'pages into [any [word! [url! | word!] string! (repend pages [cmd/2 cmd/3 cmd/4])]]] ; OK
error-rules: ['errors (print errors)] ; Should make this better
word-rules: [doc-type-words (out select doc-types doc-type: cmd/1) | word! (out cmd/1) | get-word! (out get cmd/1)] ; OK
set 'html-gen func [
"Low level HTML dialect"
data [none! object! string! tag! file! url! number! time! date! get-word! word! block!] ; Complex
/local cmd blk header row-blk start-tag ; check these
] compose/deep [
if get-word? data [data: get data] ; problem h
if any [file? data url? data string? data number? data word? data time? data date? data] [out data return true] ; Complex
if none? data [return true]
bind eval-rules 'data
parse data [any [cmd: [(data-rules) | (tag-rules) | (form-rules) | (page-rules) | (site-rules) | (error-rules) | (word-rules)]]]
]
]