noah wrote:Lutz, thank you for your reply. You write that you're sure it can be done.
Of course it can be done -- Lutz is always right. :-)
Given a stylesheet 'person-style.xsl':
Code: Select all
<html xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xsl:version="1.0">
<body>
<table>
<xsl:for-each select="/persons/person">
<tr>
<td><xsl:value-of select="first-name"/></td>
<td><xsl:value-of select="last-name"/></td>
<td><xsl:value-of select="phone"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
and a database 'person-db.xml':
Code: Select all
<?xml version="1.0" encoding="UTF-8"?>
<persons>
<person>
<first-name>John</first-name>
<last-name>Smith</last-name>
<phone>111.222.3333</phone>
</person>
<person>
<first-name>Bob</first-name>
<last-name>Hope</last-name>
<phone>555.111.2222</phone>
</person>
</persons>
and some handy functions:
Code: Select all
(context 'xsl)
(define *db* '())
(define *local-db* '())
;; The evaluator 'eval-sxsl' will evaluate any list with a head which
;; is a symbol contained in '*xsl-procs*'.
(define *xsl-procs* '(xsl:value-of xsl:for-each))
(define-macro (@ proc) (eval (cons (symbol (proc 0) xsl) (1 proc))))
(define (value-of text-element)
(and text-element
(let ((text-content (text-element 1)))
(and (string? text-content) text-content))))
(define (xsl:select xpath-str)
(xsl:select* (parse xpath-str "/")
(if (empty? *local-db*) *db* *local-db*)))
(define (xsl:select* xpath db)
(let ((x0 (xpath 0)))
(cond ((or (empty? xpath) (empty? db)) nil)
((= x0 "") (xsl:select* (1 xpath) db))
((= (symbol x0 MAIN) (db 0 0))
(if (= (length xpath) 1)
(db 0)
(xsl:select* (1 xpath) (1 (db 0)))))
(true (xsl:select* xpath (1 db))))))
(define (xsl-proc-symbol? maybe-symbol)
(and (symbol? maybe-symbol)
(member maybe-symbol xsl:*xsl-procs*)))
(define (eval-sxsl sxsl)
(cond ((atom? sxsl) sxsl)
((list? sxsl)
(if (xsl-proc-symbol? (first sxsl))
(eval sxsl)
(map eval-sxsl sxsl)))
;; I think the following branch is impossible,
;; but I am not sure.
(true 'IMPROBABLE-CASE-REACHED)))
(define-macro (for-each selector)
(letn ((xpath (parse (selector 1 1) "/"))
(last-xpath-symbol (symbol (pop xpath -1) MAIN))
(xpath-str (join xpath "/"))
(new-selector (list (selector 0)
(list (selector 1 0)
xpath-str)))
(sxml-data (filter (fn (element)
(and (list? element)
(= (element 0) last-xpath-symbol)))
(eval new-selector)))
(sxsl-body (args)))
(apply append
(map (fn (*local-db*)
(map xsl:eval-sxsl sxsl-body))
sxml-data))))
(context MAIN)
(define (contextualize-symbols expr)
(cond ((symbol? expr)
(letn ((contextual-symbol (symbol expr))
(parsed-symbol (parse (name contextual-symbol) ":")))
(if (> (length parsed-symbol) 1)
(symbol (parsed-symbol -1)
(symbol (parsed-symbol 0)))
contextual-symbol)))
((list? expr) (map contextualize-symbols expr))
(true expr)))
and some driver code:
Code: Select all
(xml-type-tags nil nil nil nil) ; setup SXMLprocessing
;; Read in XML (database) file.
(set 'xsl:*db* (xml-parse (read-file "person-db.xml") 31))
;; Read in XSL (stylesheet) file.
(set 'SXSL (contextualize-symbols
(xml-parse (read-file "person-style.xsl") 31)))
just crank up some music and let'er rip. Yeeehaaawww!!
Code: Select all
> xsl:*db*
((persons (person (first-name "John") (last-name "Smith") (phone
"111.222.3333"))
(person (first-name "Bob") (last-name "Hope") (phone "555.111.2222"))))
> SXSL
((html (@ (xmlns:xsl "http://www.w3.org/1999/XSL/Transform") (xsl:version
"1.0"))
(body (table (xsl:for-each (@ (select "/persons/person")) (tr (
td
(xsl:value-of (@ (select "first-name"))))
(td (xsl:value-of (@ (select "last-name"))))
(td (xsl:value-of (@ (select "phone"))))))))))
> (xsl:eval-sxsl SXSL)
((html (@ (xmlns:xsl "http://www.w3.org/1999/XSL/Transform") (xsl:version
"1.0"))
(body (table ((tr (td "John") (td "Smith") (td "111.222.3333"))
(tr (td "Bob") (td "Hope") (td "555.111.2222")))))))
Now, use Lutz's 'expr2xml' function to recover XHTML from the SXML. (Actually, you'll have to alter it a bit to handle the element attributes in the '@'.)
Obviously, the supporting functions I wrote into the 'xsl' context (above) don't entirely comply with the standard of XPath and XSLT (ha, ha, ha) -- that's OK 'cause I don't know XPath, etc. anyway! :-) However, the point is clear that it's very possible to "roll your own" XSLT-like processing in newLISP. And it's doesn't take a lot of code.
(By the way, does anyone know why the function 'contextualize-symbols' is needed? There is a very subtle thing going on with how 'xml-parse' creates symbols from element tag names in the XML source, when the tag names have namespace qualifiers. Unfortunately when 'xml-parse' reads a tag like '<xsl:for-each ...>' it will not create or reference a symbol like 'for-each' in the 'xsl' context, as expected. Instead, a symbol 'xsl:for-each' is referenced out of 'MAIN'. 'contextualize-symbols' is a post-processing kludge which rectifies the situation -- it will read an expression and fix the dorked-up symbols it finds in it. I'm going to sleep now.)
(λx. x x) (λx. x x)