Skip to content

Commit 411deb9

Browse files
author
spicychickensauce
committed
Implement nth_child
1 parent 50c0727 commit 411deb9

4 files changed

Lines changed: 69 additions & 0 deletions

File tree

c_src/lazy_html.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,36 @@ ExLazyHTML parent_node(ErlNifEnv *env, ExLazyHTML ex_lazy_html) {
734734
}
735735
FINE_NIF(parent_node, ERL_NIF_DIRTY_JOB_CPU_BOUND);
736736

737+
std::vector<int64_t> nth_child(ErlNifEnv *env, ExLazyHTML ex_lazy_html) {
738+
auto values = std::vector<int64_t>();
739+
for (auto node : ex_lazy_html.resource->nodes) {
740+
if (node->type != LXB_DOM_NODE_TYPE_ELEMENT) {
741+
continue;
742+
}
743+
744+
auto parent = node->parent;
745+
if (parent == NULL) {
746+
// We're at the root, nth_child is 1
747+
values.push_back(1);
748+
} else {
749+
int64_t i = 1;
750+
for (auto child = lxb_dom_node_first_child(parent); child != NULL;
751+
child = lxb_dom_node_next(child)) {
752+
if (child == node) {
753+
break;
754+
}
755+
if (child->type == LXB_DOM_NODE_TYPE_ELEMENT) {
756+
i++;
757+
}
758+
}
759+
values.push_back(i);
760+
}
761+
}
762+
763+
return values;
764+
}
765+
FINE_NIF(nth_child, ERL_NIF_DIRTY_JOB_CPU_BOUND);
766+
737767
std::string text(ErlNifEnv *env, ExLazyHTML ex_lazy_html) {
738768
auto document = ex_lazy_html.resource->document_ref->document;
739769

lib/lazy_html.ex

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,23 @@ defmodule LazyHTML do
387387
LazyHTML.NIF.parent_node(lazy_html)
388388
end
389389

390+
@doc """
391+
Returns the positions of the selcted nodes among their siblings.
392+
Note that the numbering is 1 based and doesn't include text nodes,
393+
as it matches the `nth-child` CSS selector.
394+
395+
## Examples
396+
397+
iex> lazy_html = LazyHTML.from_fragment(~S|<div><span>1</span><span>2</span></div>|)
398+
iex> spans = LazyHTML.query(lazy_html, "span")
399+
iex> LazyHTML.nth_child(spans)
400+
[1, 2]
401+
"""
402+
@spec nth_child(t()) :: list(integer())
403+
def nth_child(lazy_html) do
404+
LazyHTML.NIF.nth_child(lazy_html)
405+
end
406+
390407
@doc """
391408
Returns the text content of all nodes in `lazy_html`.
392409

lib/lazy_html/nif.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ defmodule LazyHTML.NIF do
2222
def query_by_id(_lazy_html, _id), do: err!()
2323
def child_nodes(_lazy_html), do: err!()
2424
def parent_node(_lazy_html), do: err!()
25+
def nth_child(_lazy_html), do: err!()
2526
def text(_lazy_html), do: err!()
2627
def attribute(_lazy_html, _name), do: err!()
2728
def attributes(_lazy_html), do: err!()

test/lazy_html_test.exs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,27 @@ defmodule LazyHTMLTest do
341341
end
342342
end
343343

344+
describe "nth_child/1" do
345+
test "nth_child gives position" do
346+
lazy_html =
347+
LazyHTML.from_fragment("""
348+
<div>
349+
Text isn't counted.
350+
<span>1</span>
351+
<!-- neither are comments -->
352+
<span>2</span>
353+
</div>
354+
""")
355+
assert LazyHTML.nth_child(lazy_html) == [1]
356+
assert lazy_html["div"] |> LazyHTML.nth_child() == [1]
357+
assert lazy_html["span"] |> LazyHTML.nth_child() == [1, 2]
358+
359+
# Verify numbering matches css selector
360+
assert lazy_html["span:nth-child(1)"] |> LazyHTML.text == "1"
361+
assert lazy_html["span:nth-child(2)"] |> LazyHTML.text == "2"
362+
end
363+
end
364+
344365
describe "query_by_id/2" do
345366
test "raises when an empty id is given" do
346367
assert_raise ArgumentError, ~r/id cannot be empty/, fn ->

0 commit comments

Comments
 (0)