Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Counterparts of child and descendant for first element in the chain #40

Open
vspinu opened this issue Sep 23, 2016 · 5 comments · May be fixed by #90
Open

Counterparts of child and descendant for first element in the chain #40

vspinu opened this issue Sep 23, 2016 · 5 comments · May be fixed by #90
Labels
category: ergonomics Quality of Life improvements type: feature request Feature requests

Comments

@vspinu
Copy link

vspinu commented Sep 23, 2016

I have asked for this in #23 but I am afraid has-child doesn't cut it, so I am re-iterating it as a separate issue.

I would like to be able to get the first element in a chain of descendants. So if elements are td > tr > input the following would give me the td element:

(hs/parent 
   (hs/tag :td)
   (hs/tag :tr)
   (hs/tag :input))

Similarly hs/ancestor should be the counterpart of hs/descendant.

In this regard the api for same level siblings is complete as given by follow <> precede and folow-adjacent <> precede-adjacent.

I know about has-child, but the above simple snippet becomes:

(hs/and
  (hs/tag :td)
  (hs/and
    (hs/tag :tr)
    (hs/has-child 
      (hs/tag :input))))

which even in this simple case looks pretty daunting.

@davidsantiago
Copy link
Collaborator

I'm having a little trouble understanding your issue. But, the counterpart of descendant is has-descendant. The counterpart of child is has-child. All of the selectors are named to be relative to the node you're trying to select. I don't see why (has-child (tag :tr) (tag :input)) doesn't do what you want in that last example.

(I believe you're having an issue, of course, I'm just trying to explain why I'm not understanding what you're asking).

@vspinu
Copy link
Author

vspinu commented Sep 28, 2016

I don't see why (has-child (tag :tr) (tag :input)) doesn't do what you want in that last example.

My last example had one has-child missing.

I guess my main issue is that you need to include (hs/and (hs/has-child for every level of hierarchy. Compare:

(hs/and
  (hs/tag :td)
  (hs/has-child
    (hs/and
      (hs/tag :tr)
      (hs/has-child 
        (hs/tag :input)))))

vs

(hs/parent
  (hs/tag :td) 
  (hs/tag :tr)
  (hs/tag :input))

I don't see has-child as natural counterpart of child because it accepts one selector but child accepts many. child is like a convenient threading macro but has-child is not. A currently missing function has-parent would be a natural counterpart of has-child.

@port19x
Copy link
Contributor

port19x commented Apr 11, 2023

has-child is indeed unergonomic and no fun to use!

@port19x port19x added type: feature request Feature requests priority 3: low category: ergonomics Quality of Life improvements labels Apr 11, 2023
@port19x port19x mentioned this issue Apr 11, 2023
@Mertzenich
Copy link

Mertzenich commented Mar 9, 2024

If I have time I hope to implement functional variants of parent and ancestor (I'll submit a PR if I end up getting around to it). In the meantime, here is a macro I whipped up to produce the has-child/has-descendent chains described above by @vspinu. It takes a function you want to utilize, such as has-child, followed by your selectors:

(defmacro has-*
  [sel & selectors]
  (if (empty? selectors)
    '(hickory.select/and)
    (let [rev (reverse selectors)]
      (loop [selectors (rest rev)
             output (list 'hickory.select/and (first rev))]
        (cond
          (empty? selectors) output
          (= (count selectors) 1) (list 'hickory.select/and (first selectors) (list sel output))
          :else (recur (rest selectors)
                       (list 'hickory.select/and (first selectors)
                             (list sel output))))))))

Example usage:

(macroexpand '(has-* hickory.select/has-child))
;; => (hickory.select/and)
;; Should match anything.
(macroexpand '(has-* hickory.select/has-child (sel/tag :td)))
;; => (hickory.select/and (sel/tag :td))
(macroexpand '(has-* hickory.select/has-child (sel/tag :td) (sel/tag :tr)))
;; => (hickory.select/and
;;     (sel/tag :td)
;;     (hickory.select/has-child (hickory.select/and (sel/tag :tr))))
(macroexpand '(has-* hickory.select/has-child (hs/tag :td) (hs/tag :tr) (hs/tag :input)))
;; => (hickory.select/and
;;     (sel/tag :td)
;;     (hickory.select/has-child
;;      (hickory.select/and
;;       (sel/tag :tr)
;;       (hickory.select/has-child (hickory.select/and (sel/tag :input))))))

@Mertzenich
Copy link

Mertzenich commented Mar 9, 2024

I believe I have a functional solution, I hope to test it sometime this weekend or early next week. It works in a similar way as the examples above, except this time we recursively combine the functions. I also need to dig into the code base a bit more since I worry that my solution may be reinventing the wheel a bit. I'll post more details as soon as I can.

@Mertzenich Mertzenich linked a pull request Mar 23, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
category: ergonomics Quality of Life improvements type: feature request Feature requests
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants