(in-package #:oh-ducks)
(named-readtables:in-readtable template-readtable)
;; FIXME: the switch to chtml:pt nodes means our #'equalp no longer
;; works.
#+(or) (setq *default-parser* 'pt)
(equalp '(:div ((:id "id")) "I " (:i () "like") " cheese.")
(match (#T(html (:model lhtml) ("#id" . ?div))
"
I like cheese.
")
;; FIXME: learn to distinguish between when there should only be one
;; result and when there should be many?
(car div)))
(equalp '((:div ((:class "red fish")) "one fish")
(:div ((:class "blue fish")) "two fish"))
(match (#T(html (:model lhtml)
(".fish" . ?divs)
(".pig" . ?pig))
"bricklayer
one fish
two fish
")
;; pig doesn't affect the equalp...but does show separate things are separate
(values divs pig)))
(equalp '((:i () "not") (:i () "cheese"))
(match (#T(html (:model lhtml)
("div" ("i" . ?i)))
"I do not like cheese.
I like cheese.
")
i))
(equalp '((:i () "not"))
(match (#T(html (:model lhtml)
("div>i" . ?i))
"I do not like cheese.
I like cheese.
")
i))
(equalp '((:i () "not"))
(match (#T(html (:model lhtml)
("div" (">i" . ?i)
;("i" . #t(list ?j ?i))
("span>i" . ?span)))
"I do not like cheese.
I like cheese.
")
(values i span)))
(defun make-dom-document (child-node)
(make-instance 'rune-dom::document :children (rune-dom::make-node-list (list child-node))))
(defun serialize (object)
(let ((document
(etypecase object
(rune-dom::document object)
(rune-dom::element (make-dom-document object))
(chtml:pt object)
(list object))))
(etypecase document
(rune-dom::document
(dom:map-document (cxml:make-string-sink :omit-xml-declaration-p t)
document))
(chtml:pt
(chtml:serialize-pt document (chtml:make-string-sink)))
(list (mapcar #'serialize document)))))
(defmacro serialize-values (form)
`(let ((values (multiple-value-list ,form)))
(values-list (mapcar #'serialize values))))
(equal '("cheese" "cheese")
(serialize-values
(match (#T(html (:model dom)
("i" . #t(list ?j ?i))
("span>i" . ?span))
"I do not like cheese.
I like cheese.
")
(values i span))))
(serialize-values
(match (#T(html (:model dom)
("div:first-child" . ?div)
("i:nth-child(1)" . ?i))
"I do not like cheese.
I like cheese.
")
(values div i)))
(serialize-values
(match (#T(html (:model dom)
("div:nth-last-child(1)" . ?div)
("div:last-child" . ?d2))
"I do not like cheese.
I like cheese.
")
(values div d2)))
(serialize-values
(match (#t(html (:model dom)
(":nth-last-of-type(2)" . ?first)
(":nth-of-type(2)" . ?last))
"1i2i
")
(values first last)))
(match (#T(html (:model dom)
("q" . ?div))
"I do not like cheese.
I like cheese.
")
(values div))
;; throws 'unification-failure
(serialize-values
(match (#T(html (:model dom)
("i:only-child" . ?i)
("i:only-of-type" . ?i-type))
"I do not like cheese.
I like cheese.
")
(values i i-type)))
(serialize-values
(match (#T(html (:model dom)
("b + i" . ?i))
"I really like cheese. Do you not dislike cheese?
")
(values i)))
(serialize-values
(match (#T(html (:model dom)
("b ~ i" . ?i))
"I really like cheese. Do you not dislike cheese?
")
(values i)))
(serialize-values
(match (#T(html (:model pt)
("body :empty" . ?empty))
"")
(values empty)))
;; Sometimes, you want to match a thing inside a thing, in which case
;; combinators should implicitly assume an unspecified right side means
;; "whatever element I gave you".
(serialize-values
(match (#T(html (:model dom)
("q" . ?q))
"ham foo bar baz
quuz spam
")
(match (#t(html ("> i" . ?i))
(first q))
i)))
;; siblings will also match, thanks to a bit of ugly code
(serialize-values
(match (#T(html (:model dom)
("q" . ?q))
"ham foo bar baz
quuz spamnot match
")
(match (#t(html ("+ i" . ?i))
(first q))
i)))
(serialize-values
(match (#T(html (:model dom)
("q" . ?q))
" foo outer q baz inner q
quuz
")
(match (#t(html ("q" . ?i))
(first q))
i)))
(serialize-values
(match (#T(html (:model dom)
("[id]" . ?ids))
"blank idfoono idbarid id
")
ids))
(serialize-values
(match (#T(html (:model dom)
("[id=foo]" . ?id))
"bar idno idfoo id
")
id))
#+LATER?
(match (#t(html ("div::content" . #t(regexp+ "^f(o+)" (?o))))
"barbaz
fooooooobar
")
(values o &rest))