Sun Jul  3 08:25:45 UTC 2011  pix@kepibu.org
  * Add support for XMLS-style lists, conflicting with LHTML-style lists
Sun Jul  3 07:55:18 UTC 2011  pix@kepibu.org
  * Minimal support for attribute-starts-with selector
Sun Jun  5 21:44:21 UTC 2011  pix@kepibu.org
  * Update notes file
Tue Apr  5 00:14:51 UTC 2011  pix@kepibu.org
  * depend-on cl-unification-lib to work with stock cl-unification
Wed Feb 10 08:50:16 UTC 2010  pix@kepibu.org
  * Add attribute-equal selector
Wed Feb 10 08:28:34 UTC 2010  pix@kepibu.org
  * Add attribute-present selector
Wed Feb 10 08:27:56 UTC 2010  pix@kepibu.org
  * Serialize returned tags so it's easier to see what was returned
Wed Feb 10 08:26:34 UTC 2010  pix@kepibu.org
  * Formatting.
Wed Feb 10 08:26:25 UTC 2010  pix@kepibu.org
  * Use named-readtables instead of set-dispatch-macro-character
Wed Feb 10 08:20:45 UTC 2010  pix@kepibu.org
  * Return NIL if attribute was not present
Sun Feb  7 09:21:16 UTC 2010  pix@kepibu.org
  * Update notes to reflect updates to cl-unification.
Mon Jan  4 07:11:36 UTC 2010  pix@kepibu.org
  * element-parent now works in lhtml
Mon Jan  4 07:06:50 UTC 2010  pix@kepibu.org
  * Support for asking about ancestors under lhtml
Mon Jan  4 06:58:51 UTC 2010  pix@kepibu.org
  * Don't need &allow-other-key here
Mon Jan  4 06:36:34 UTC 2010  pix@kepibu.org
  * Don't count an+b|b|odd|even as separate items
Mon Jan  4 06:32:27 UTC 2010  pix@kepibu.org
  * :empty selector
Mon Jan  4 06:32:07 UTC 2010  pix@kepibu.org
  * Add *of-type selectors
diff -rN -u old-Oh, Ducks!/notes new-Oh, Ducks!/notes
--- old-Oh, Ducks!/notes	2013-08-10 15:24:27.000000000 +0000
+++ new-Oh, Ducks!/notes	2013-08-10 15:24:27.000000000 +0000
@@ -11,6 +11,7 @@
  + asdf-system-connections
  * closure-html
  * cxml
+ * named-readtables
 [+] Mandatory  [*] Optional
 ** Loading
 Loading "Oh, Ducks!" is just like loading any other ASDF system.
@@ -31,7 +32,13 @@
 To avoid load-order issues resulting in an indeterminate reader on #t,
 you'll probably want to add
 : #.(set-dispatch-macro-character #\# #\T 'unify::|sharp-T-reader|)
+or
+: (unify:enable-template-reader)
+or
+: (named-readtables:in-readtable unify:template-readtable)
 to the top of any file which uses cl-unification's reader templates.
+(The latter two currently only work if you have cl-unification from my
+darcs repo.)
 
 Please feel free to submit patches to closure-html and cl-unification
 to fix this problem.
@@ -44,7 +51,15 @@
 
 For example,
 : :depends-on (:oh-ducks :closure-html :cxml)
-
+** Differentiating between LHTML lists and XMLS lists
+While it would, in theory, be possible to inspect lists and determine if they
+are LHTML or XMLS lists, this is not currently done.  You can, however, choose
+which type you'd like to work with by pushing =:lists-are-xmls= or
+=:lists-are-lhtml= to =*features*= before loading "Oh, Ducks!".
+
+Unfortunately, this means you can only expect to use one list type in a single
+lisp image.  Patches to either automagically detect the list type, or to provide
+layered functions are welcome.
 * Usage
 The combination of oh-ducks and closure-html provides an HTML template
 for use with cl-unification, and has the following syntax:
@@ -71,7 +86,7 @@
 (match (#T(html (:model dom)
                 ("i" . #t(list ?j ?i))
                 ("span>i" . ?span))
-          "<div>I do <i>not</i> like cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
+        "<div>I do <i>not</i> like cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
   (values i span)) =>
   #<ELEMENT i "not">,
   (#<ELEMENT i "cheese">)
@@ -129,34 +144,28 @@
 I also recommend submitting a patch.  Other people might want to use
 that selector, too!
 * To Do
-** working lhtml/xmls support [1/2]
+** working lhtml/xmls support [2/2]
  * [X] non-descendant cases (class, id, etc.)
- * [ ] selectors involving descendants
+ * [X] selectors involving descendants
+   CAUTION: Won't produce sane results if the document tree is
+            modified or you use nested (match)es.
 ** write documentation
 ** improve selector support
-*** positional selectors [9/19]
- * [X] :nth-child(an+b)
- * [X] :nth-child(b)
- * [X] :nth-child(odd|even)
- * [X] :nth-last-child(an+b)
- * [X] :nth-last-child(b)
- * [X] :nth-last-child(odd|even)
+*** positional selectors [11/11]
+ * [X] :nth-child
+ * [X] :nth-last-child
  * [X] :first-child
  * [X] :last-child
- * [ ] :nth-of-type(an+b)
- * [ ] :nth-of-type(b)
- * [ ] :nth-of-type(odd|even)
- * [ ] :nth-last-of-type(an+b)
- * [ ] :nth-last-of-type(b)
- * [ ] :nth-last-of-type(odd|even)
- * [ ] :first-of-type
- * [ ] :last-of-type
+ * [X] :nth-of-type
+ * [X] :nth-last-of-type
+ * [X] :first-of-type
+ * [X] :last-of-type
  * [X] :only-child
- * [ ] :only-of-type
- * [ ] :empty
-*** attribute selectors [0/7]
- * [ ] attribute-present  [att]
- * [ ] attribute-equal    [att=val]
+ * [X] :only-of-type
+ * [X] :empty
+*** attribute selectors [2/7]
+ * [X] attribute-present  [att]
+ * [X] attribute-equal    [att=val]
  * [ ] attribute-member   [att~=val]
  * [ ] attribute-lang     [att|=val]
  * [ ] attribute-begins   [att^=val]
@@ -166,6 +175,7 @@
 *** any others?
 ** namespace support(?)
 ** Submit patch to cl-unification to add (enable/disable-template-reader) functions
+Submitted.  Was it ever accepted?  Man, I don't remember.
 ** Submit patch to closure-html to add (enable/disable-reader) functions
 ** non-css templates (e.g., for matching on text of element)?
 Maybe special-case string/regexp-templates, so for example
@@ -176,3 +186,12 @@
 might cause some difficulty, however--we should get a list of matched elements
 for the div selector, but the regexp variable (?o) can only match once (without
 some wacky environment merging, anyway).
+** Element structure templates
+For instance, sometimes it'd be nice to stuff the value of an attribute into a
+variable, like so:
+:  (match #t(attr ("href" ?href) ("name" ?name)) "<a href='url' name='link'></a>"
+:     (values href name)) =>
+:    "url", "link"
+While it's certainly easy enough to do that using, say, XMLS-style lists, a
+general object-model-agnostic method would seem to be preferrable.
+** Layered functions so LHTML vs. XMLS support can be switched at runtime
diff -rN -u old-Oh, Ducks!/oh-ducks.asd new-Oh, Ducks!/oh-ducks.asd
--- old-Oh, Ducks!/oh-ducks.asd	2013-08-10 15:24:27.000000000 +0000
+++ new-Oh, Ducks!/oh-ducks.asd	2013-08-10 15:24:27.000000000 +0000
@@ -17,7 +17,7 @@
   :maintainer "pinterface <pix@kepibu.org>"
   :author "pinterface <pix@kepibu.org>"
   :licence "BSD-style"
-  :depends-on (:cl-unification :cl-ppcre :split-sequence :alexandria)
+  :depends-on (:cl-unification-lib :cl-unification :cl-ppcre :split-sequence :alexandria)
   :serial t
   :components ((:file "package")
                (:file "regexp-template")
@@ -32,11 +32,17 @@
   :requires (:oh-ducks :closure-html)
   :components ((:file "chtml")
                (:module "traversal"
-                :components ((:file "lhtml")
+                :components (#-lists-are-xmls (:file "lhtml")
                              (:file "pt")))))
 
 (defsystem-connection ducks+cxml
   :requires (:oh-ducks :cxml)
   :components ((:file "cxml")
                (:module "traversal"
-                :components ((:file "dom")))))
+                :components ((:file "dom")
+                             #-lists-are-lhtml (:file "xmls")))))
+
+;; In case you're wondering, we check the inverse of the :lists-are-* keywords
+;; so, in the event you only load cxml (or chtml), and don't specify which
+;; format lists are expected to take, you get the appropriate list operation by
+;; default.
diff -rN -u old-Oh, Ducks!/selectors.lisp new-Oh, Ducks!/selectors.lisp
--- old-Oh, Ducks!/selectors.lisp	2013-08-10 15:24:27.000000000 +0000
+++ new-Oh, Ducks!/selectors.lisp	2013-08-10 15:24:27.000000000 +0000
@@ -38,6 +38,15 @@
 (defclass class-selector (simple-selector) ())
 (defclass nth-child-selector (simple-selector) ())
 (defclass nth-last-child-selector (nth-child-selector) ())
+(defclass nth-of-type-selector (nth-child-selector) ())
+(defclass nth-last-of-type-selector (nth-of-type-selector) ())
+(defclass empty-selector (simple-selector) ())
+
+(defclass attribute-selector (simple-selector)
+  ((val :reader attribute-value :initarg :value)))
+(defclass attribute-present-selector (attribute-selector) ())
+(defclass attribute-equal-selector (attribute-selector) ())
+(defclass attribute-starts-with-selector (attribute-selector) ())
 
 (defmethod initialize-instance :after ((selector nth-child-selector)
                                        &key (asign "+") a
@@ -100,6 +109,16 @@
     (#T(regexp$ (\s+)         ())
      (list (make-instance 'descendant-combinator :matcher (or (parse-selector &rest) %implicit-element-selector))))
     ;; simple selectors
+    ;; attribute selectors
+    (#T(regexp$ ("[" $name "]") (?attribute))
+     (cons (make-instance 'attribute-present-selector :arg attribute)
+           (parse-selector &rest)))
+    (#T(regexp$ ("[" $name "=" $name "]") (?attribute ?value))
+     (cons (make-instance 'attribute-equal-selector :arg attribute :value value)
+           (parse-selector &rest)))
+    (#T(regexp$ ("[" $name "^=" $name "]") (?attribute ?value))
+     (cons (make-instance 'attribute-starts-with-selector :arg attribute :value value)
+           (parse-selector &rest)))
     ;; cyclic (An+B, n+B)
     (#T(regexp$ (":nth-child(" \s* an+b \s* ")")
                 (?asign ?a ?bsign ?b))
@@ -113,6 +132,18 @@
                           :asign asign :a a
                           :bsign bsign :b b)
            (parse-selector &rest)))
+    (#T(regexp$ (":nth-of-type(" \s* an+b \s* ")")
+                (?asign ?a ?bsign ?b))
+     (cons (make-instance 'nth-of-type-selector
+                          :asign asign :a a
+                          :bsign bsign :b b)
+           (parse-selector &rest)))
+    (#T(regexp$ (":nth-last-of-type(" \s* an+b \s* ")")
+                (?asign ?a ?bsign ?b))
+     (cons (make-instance 'nth-last-of-type-selector
+                          :asign asign :a a
+                          :bsign bsign :b b)
+           (parse-selector &rest)))
     ;; absolute (B)
     (#T(regexp$ (":nth-child(" \s* b \s* ")")
                 (?b))
@@ -122,6 +153,14 @@
                 (?b))
      (cons (make-instance 'nth-last-child-selector :a 0 :b b)
            (parse-selector &rest)))
+    (#T(regexp$ (":nth-of-type(" \s* b \s* ")")
+                (?b))
+     (cons (make-instance 'nth-of-type-selector :a 0 :b b)
+           (parse-selector &rest)))
+    (#T(regexp$ (":nth-last-of-type(" \s* b \s* ")")
+                (?b))
+     (cons (make-instance 'nth-last-of-type-selector :a 0 :b b)
+           (parse-selector &rest)))
     ;; named (odd, even)
     (#T(regexp$ (":nth-child(" \s* odd/even \s* ")")
                 (?which))
@@ -131,6 +170,14 @@
                 (?which))
      (cons (make-instance 'nth-last-child-selector :namedp t :b which)
            (parse-selector &rest)))
+    (#T(regexp$ (":nth-of-type(" \s* odd/even \s* ")")
+                (?which))
+     (cons (make-instance 'nth-of-type-selector :namedp t :b which)
+           (parse-selector &rest)))
+    (#T(regexp$ (":nth-last-of-type(" \s* odd/even \s* ")")
+                (?which))
+     (cons (make-instance 'nth-last-of-type-selector :namedp t :b which)
+           (parse-selector &rest)))
     ;; Everybody else
     (#T(regexp$ (":first-child") ())
      (cons (make-instance 'nth-child-selector :a 0 :b 1)
@@ -142,6 +189,18 @@
       (list* (make-instance 'nth-child-selector      :a 0 :b 1)
              (make-instance 'nth-last-child-selector :a 0 :b 1)
              (parse-selector &rest)))
+    (#T(regexp$ (":first-of-type") ())
+     (cons (make-instance 'nth-of-type-selector :a 0 :b 1)
+           (parse-selector &rest)))
+    (#T(regexp$ (":last-of-type")  ())
+     (cons (make-instance 'nth-last-of-type-selector :a 0 :b 1)
+           (parse-selector &rest)))
+    (#T(regexp$ (":only-of-type")  ())
+      (list* (make-instance 'nth-of-type-selector      :a 0 :b 1)
+             (make-instance 'nth-last-of-type-selector :a 0 :b 1)
+             (parse-selector &rest)))
+    (#T(regexp$ (":empty")       ())
+      (cons (make-instance 'empty-selector)           (parse-selector &rest)))
     (#T(regexp$ (#\# $name)      (?id))
      (cons (make-instance 'id-selector :arg id)       (parse-selector &rest)))
     (#T(regexp$ (#\. $name)      (?class))
@@ -191,6 +250,24 @@
               (parent (element-parent element)))
     (an+b? (car arg) (cdr arg) element (reverse (element-children parent)))))
 
+(defmethod subject-p ((selector nth-of-type-selector) element)
+  (when-let* ((arg (selector-arg selector))
+              (parent (element-parent element)))
+    (an+b? (car arg) (cdr arg) element
+           (remove-if-not (rcurry #'element-type-equal (element-type element))
+                          (element-children parent)))))
+
+(defmethod subject-p ((selector nth-last-of-type-selector) element)
+  (when-let* ((arg (selector-arg selector))
+              (parent (element-parent element)))
+    (an+b? (car arg) (cdr arg) element
+           (reverse
+            (remove-if-not (rcurry #'element-type-equal (element-type element))
+                           (element-children parent))))))
+
+(defmethod subject-p ((selector empty-selector) element)
+  (= 0 (length (element-children element))))
+
 (defmethod subject-p ((selector class-selector) element)
   (member (selector-arg selector)
           (element-classes element)
@@ -200,6 +277,17 @@
   (declare (ignore element selector))
   t)
 
+(defmethod subject-p ((selector attribute-present-selector) element)
+  (element-attribute (selector-arg selector) element))
+
+(defmethod subject-p ((selector attribute-equal-selector) element)
+  (when-let* ((val (element-attribute (selector-arg selector) element)))
+    (string= val (attribute-value selector))))
+
+(defmethod subject-p ((selector attribute-starts-with-selector) element)
+  (when-let* ((val (element-attribute (selector-arg selector) element)))
+    (alexandria:starts-with-subseq (string-downcase (attribute-value selector)) (string-downcase val))))
+
 (defmethod subject-p ((selector %implicit-element-selector) element)
   (eq element *implicit-element*))
 
diff -rN -u old-Oh, Ducks!/tests.lisp new-Oh, Ducks!/tests.lisp
--- old-Oh, Ducks!/tests.lisp	2013-08-10 15:24:27.000000000 +0000
+++ new-Oh, Ducks!/tests.lisp	2013-08-10 15:24:27.000000000 +0000
@@ -1,14 +1,13 @@
 (in-package #:oh-ducks)
+(named-readtables:in-readtable template-readtable)
 ;; FIXME: the switch to chtml:pt nodes means our #'equalp no longer
 ;; works.
 
-#.(set-dispatch-macro-character #\# #\T 'unify::|sharp-T-reader|)
-
 #+(or) (setq *default-parser* 'pt)
 
 (equalp '(:div ((:id "id")) "I " (:i () "like") " cheese.")
         (match (#T(html (:model lhtml) ("#id" . ?div))
-                  "<div id=\"id\">I <i>like</i> cheese.</div>")
+                "<div id=\"id\">I <i>like</i> cheese.</div>")
           ;; FIXME: learn to distinguish between when there should only be one
           ;;        result and when there should be many?
           (car div)))
@@ -23,88 +22,146 @@
           (values divs pig)))
 
 (equalp '((:i () "not") (:i () "cheese"))
-        (match (#T(html ("div" ("i" . ?i)))
-                  "<div>I do <i>not</i> like cheese.</div><div>I like <i>cheese</i>.</div>")
+        (match (#T(html (:model lhtml)
+                        ("div" ("i" . ?i)))
+                "<div>I do <i>not</i> like cheese.</div><div>I like <i>cheese</i>.</div>")
           i))
 
 (equalp '((:i () "not"))
-        (match (#T(html (:model dom)
+        (match (#T(html (:model lhtml)
                         ("div>i" . ?i))
-                  "<div>I do <i>not</i> like cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
+                "<div>I do <i>not</i> like cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
           i))
 
 (equalp '((:i () "not"))
-        (match (#T(html (:model dom)
+        (match (#T(html (:model lhtml)
                         ("div" (">i" . ?i)
                                ;("i" . #t(list ?j ?i))
                                ("span>i" . ?span)))
-                  "<div>I do <i>not</i> like cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
+                "<div>I do <i>not</i> like cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
           (values i span)))
 
-(match (#T(html (:model dom)
-                ("i" . #t(list ?j ?i))
-                ("span>i" . ?span))
-          "<div>I do <i>not</i> like cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
-  (values i span))
+(defun make-dom-document (child-node)
+  (make-instance 'rune-dom::document :children (rune-dom::make-node-list (list child-node))))
 
-(match (#T(html (:model dom)
-                ("div:first-child" . ?div)
-                ("i:nth-child(1)" . ?i))
-          "<div>I do <i>not</i> <i>like</i> cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
-  (values div i))
+(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))))
 
-(match (#T(html (:model dom)
-                ("div:nth-last-child(1)" . ?div)
-		("div:last-child" . ?d2))
-          "<div>I do <i>not</i> <i>like</i> cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
-  (values div d2))
+(equal '("<i>cheese</i>" "<i>cheese</i>")
+       (serialize-values
+        (match (#T(html (:model dom)
+                        ("i" . #t(list ?j ?i))
+                        ("span>i" . ?span))
+                  "<div>I do <i>not</i> like cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
+          (values i span))))
 
-(match (#T(html (:model dom)
-                ("i:only-child" . ?i))
-          "<div>I do <i>not</i> <i>like</i> cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
-  (values i))
+(serialize-values
+ (match (#T(html (:model dom)
+                 ("div:first-child" . ?div)
+                 ("i:nth-child(1)" . ?i))
+         "<div>I do <i>not</i> <i>like</i> cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
+   (values div i)))
+
+(serialize-values
+ (match (#T(html (:model dom)
+                 ("div:nth-last-child(1)" . ?div)
+                 ("div:last-child" . ?d2))
+         "<div>I do <i>not</i> <i>like</i> cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
+   (values div d2)))
+
+(serialize-values
+ (match (#t(html (:model dom)
+                 (":nth-last-of-type(2)" . ?first)
+                 (":nth-of-type(2)" . ?last))
+         "<div><span>1</span><i>i</i><span>2</span><i>i</i></div>")
+   (values first last)))
 
-;; throws 'unification-failure
 (match (#T(html (:model dom)
                 ("q" . ?div))
-          "<div>I do <i>not</i> <i>like</i> cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
+        "<div>I do <i>not</i> <i>like</i> cheese.</div><div><span>I like <i>cheese</i>.</span></div>")
   (values div))
 
-(match (#T(html (:model dom)
-                ("b + i" . ?i))
-        "<div>I <b>really</b> <i>like</i> cheese.  Do you not <i>dislike</i> cheese?</div>")
-  (values i))
-
-(match (#T(html (:model dom)
-                ("b ~ i" . ?i))
-        "<div>I <i>really</i> <b>like</b> cheese.  Do you not <i>dislike</i> cheese?</div>")
-  (values i))
+;; throws 'unification-failure
+(serialize-values
+ (match (#T(html (:model dom)
+                 ("i:only-child" . ?i)
+                 ("i:only-of-type" . ?i-type))
+         "<div>I do <i>not</i> <i>like</i> cheese.</div><div><span><i>I</i> like <i>cheese</i>.</span></div>")
+   (values i i-type)))
+
+(serialize-values
+ (match (#T(html (:model dom)
+                 ("b + i" . ?i))
+         "<div>I <b>really</b> <i>like</i> cheese.  Do you not <i>dislike</i> cheese?</div>")
+   (values i)))
+
+(serialize-values
+ (match (#T(html (:model dom)
+                 ("b ~ i" . ?i))
+         "<div>I <i>really</i> <b>like</b> cheese.  Do you <i>not</i> <i>dislike</i> cheese?</div>")
+   (values i)))
+
+(serialize-values
+ (match (#T(html (:model pt)
+                 ("body :empty" . ?empty))
+         "<div><p><br></p><p>testing<i>i</i>testing</p></div>")
+   (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".
-(match (#T(html (:model dom)
-                ("q" . ?q))
-        "<div><i>ham</i> foo <q>bar <i>baz</i></q> quuz <i>spam</i></div>")
-  (match (#t(html ("> i" . ?i))
-            (first q))
-    i))
+(serialize-values
+ (match (#T(html (:model dom)
+                 ("q" . ?q))
+         "<div><i>ham</i> foo <q>bar <i>baz</i></q> quuz <i>spam</i></div>")
+   (match (#t(html ("> i" . ?i))
+           (first q))
+     i)))
 
 ;; siblings will also match, thanks to a bit of ugly code
-(match (#T(html (:model dom)
-                ("q" . ?q))
-        "<div><i>ham</i> foo <q>bar <i>baz</i></q> quuz <i>spam</i><q></q><i>not match</i></div>")
-  (match (#t(html ("+ i" . ?i))
-            (first q))
-    i))
-
-(match (#T(html (:model dom)
-                ("q" . ?q))
-        "<div> foo <q>outer q <i>baz <q>inner q</q></i></q> quuz</div>")
-  (match (#t(html ("q" . ?i))
-            (first q))
-    i))
-
+(serialize-values
+ (match (#T(html (:model dom)
+                 ("q" . ?q))
+         "<div><i>ham</i> foo <q>bar <i>baz</i></q> quuz <i>spam</i><q></q><i>not match</i></div>")
+   (match (#t(html ("+ i" . ?i))
+           (first q))
+     i)))
+
+(serialize-values
+ (match (#T(html (:model dom)
+                 ("q" . ?q))
+         "<div> foo <q>outer q <i>baz <q>inner q</q></i></q> quuz</div>")
+   (match (#t(html ("q" . ?i))
+           (first q))
+     i)))
+
+(serialize-values
+ (match (#T(html (:model dom)
+                 ("[id]" . ?ids))
+         "<div><i id=''>blank id</i>foo<b>no id</b>bar<i id='id'>id id</i></div>")
+   ids))
+
+(serialize-values
+ (match (#T(html (:model dom)
+                 ("[id=foo]" . ?id))
+         "<div><i id='bar'>bar id</i><i>no id</i><i id='foo'>foo id</i></div>")
+   id))
 
 #+LATER?
 (match (#t(html ("div::content" . #t(regexp+ "^f(o+)" (?o))))
diff -rN -u old-Oh, Ducks!/traversal/dom.lisp new-Oh, Ducks!/traversal/dom.lisp
--- old-Oh, Ducks!/traversal/dom.lisp	2013-08-10 15:24:27.000000000 +0000
+++ new-Oh, Ducks!/traversal/dom.lisp	2013-08-10 15:24:27.000000000 +0000
@@ -7,10 +7,9 @@
 (defmethod unify:unify ((template oh-ducks::css-selector-template)
                         (document dom:document)
                         &optional (env (unify:make-empty-environment))
-                        &key &allow-other-keys)
+                        &key)
   (unify:unify template (dom:document-element document) env))
 
-
 ;;; general accessors
 
 (defmethod element-children ((element dom:element))
@@ -24,7 +23,8 @@
 (defmethod element-attribute ((attribute symbol) (element dom:element))
   (element-attribute (string-downcase (symbol-name attribute)) element))
 (defmethod element-attribute ((attribute string) (element dom:element))
-  (dom:get-attribute element attribute))
+  (when-let* ((attribute-node (dom:get-attribute-node element attribute)))
+    (dom:value attribute-node)))
 
 (defmethod element-type ((element dom:element))
   (dom:tag-name element))
diff -rN -u old-Oh, Ducks!/traversal/lhtml.lisp new-Oh, Ducks!/traversal/lhtml.lisp
--- old-Oh, Ducks!/traversal/lhtml.lisp	2013-08-10 15:24:27.000000000 +0000
+++ new-Oh, Ducks!/traversal/lhtml.lisp	2013-08-10 15:24:27.000000000 +0000
@@ -3,18 +3,44 @@
 ;;;          structurally compatible).  Sorry, but that's the way it goes.
 (in-package #:oh-ducks.traversal)
 
+(defvar *lhtml-family-tree* nil)
+
+(defun in-hash (key hash)
+  (multiple-value-bind (val present-p) (gethash key hash)
+    (declare (ignore val))
+    present-p))
+
+(defun %mark-parents (parent children)
+  (dolist (item children)
+    (setf (gethash item *lhtml-family-tree*) parent)
+    (%mark-parents item (element-children item))))
+
+;; WARNING: This won't produce sane results for nested (match)es, because we
+;;          have no way to bind in a large enough scope.
+(defmethod unify:unify ((template oh-ducks::css-selector-template)
+                        (element list)
+                        &optional (env (unify:make-empty-environment))
+                        &key)
+  (if (and *lhtml-family-tree*
+	   (in-hash element *lhtml-family-tree*))
+      (call-next-method)
+      (let ((*lhtml-family-tree* (make-hash-table :test 'eq)))
+        (%mark-parents nil (list element))
+        (%mark-parents element (element-children element))
+        (call-next-method))))
+
 ;;; general accessors
 
 (defmethod element-children ((element list))
   (remove-if-not (lambda (x) (and (listp x) (keywordp (car x))))
                  (cddr element)))
 
-;; FIXME: bleh... may not even be worth trying to support this
 (defmethod element-parent ((element list))
-  (error "cannot get parent"))
-
-(defmethod element-ancestors ((element list))
-  (error "cannot get ancestors"))
+  (multiple-value-bind (parent present?)
+      (gethash element *lhtml-family-tree*)
+    (if present?
+        parent
+        (error "unable to determine parent"))))
 
 (defmethod element-attribute ((attribute symbol) (element list))
   (cadr (assoc attribute (cadr element))))
diff -rN -u old-Oh, Ducks!/traversal/xmls.lisp new-Oh, Ducks!/traversal/xmls.lisp
--- old-Oh, Ducks!/traversal/xmls.lisp	1970-01-01 00:00:00.000000000 +0000
+++ new-Oh, Ducks!/traversal/xmls.lisp	2013-08-10 15:24:27.000000000 +0000
@@ -0,0 +1,58 @@
+;;; WARNING: This conflicts with lhtml.
+(in-package #:oh-ducks.traversal)
+
+(defvar *xmls-family-tree* nil)
+
+(defun in-hash (key hash)
+  (multiple-value-bind (val present-p) (gethash key hash)
+    (declare (ignore val))
+    present-p))
+
+(defun %mark-parents (parent children)
+  (dolist (item children)
+    (setf (gethash item *xmls-family-tree*) parent)
+    (%mark-parents item (element-children item))))
+
+;; WARNING: This won't produce sane results for nested (match)es, because we
+;;          have no way to bind in a large enough scope.
+(defmethod unify:unify ((template oh-ducks::css-selector-template)
+                        (element list)
+                        &optional (env (unify:make-empty-environment))
+                        &key)
+  (if (and *xmls-family-tree*
+	   (in-hash element *xmls-family-tree*))
+      (call-next-method)
+      (let ((*xmls-family-tree* (make-hash-table :test 'eq)))
+        (%mark-parents nil (list element))
+        (%mark-parents element (element-children element))
+        (call-next-method))))
+
+(defmethod unify:unify ((document list) (template oh-ducks::css-selector-template)
+                        &optional (env (unify:make-empty-environment))
+                        &key)
+  (unify:unify template document env))
+
+;;; general accessors
+
+(defmethod element-children ((element list))
+  (remove-if-not (lambda (x) (and (listp x) (stringp (car x))))
+                 (cddr element)))
+
+(defmethod element-parent ((element list))
+  (multiple-value-bind (parent present?)
+      (gethash element *xmls-family-tree*)
+    (if present?
+        parent
+        (error "unable to determine parent"))))
+
+#+(or)
+(defmethod element-attribute ((attribute symbol) (element list))
+  (cadr (assoc attribute (cadr element))))
+(defmethod element-attribute ((attribute string) (element list))
+  (cadr (assoc attribute (cadr element) :test #'string=)))
+
+(defmethod element-type ((element list))
+  (car element))
+
+(defmethod element-content ((element list))
+  (cddr element))
diff -rN -u old-Oh, Ducks!/unify.lisp new-Oh, Ducks!/unify.lisp
--- old-Oh, Ducks!/unify.lisp	2013-08-10 15:24:27.000000000 +0000
+++ new-Oh, Ducks!/unify.lisp	2013-08-10 15:24:27.000000000 +0000
@@ -37,7 +37,7 @@
 ;;; descendant combinator to be sure, but the general one (#\Space) doesn't
 ;;; exactly show up all that well.  Somebody might assume " b" was the same as
 ;;; "b" and get confused.
-                             ((ignore-errors (element-parent document)) ; bleh.  element-parent breaks lhtml nodes
+                             ((element-parent document)
                               (subjects-in-list css-specifier (element-children document)))
                              ;; root element includes itself
                              (t (subjects-of css-specifier document)))))