use quarb_html::HtmlAdapter;
const DOC: &str = r##"<!doctype html>
<html lang="en">
<body>
<header><h1 id="top">Welcome</h1></header>
<main>
<p class="intro">Hello <strong>world</strong>.</p>
<ul>
<li>one</li>
<li>two</li>
</ul>
<a href="#top">back to top</a>
<a href="https://example.com">external</a>
</main>
</body>
</html>"##;
fn nodes(query: &str) -> Vec<String> {
let adapter = HtmlAdapter::parse(DOC);
let mut got: Vec<String> = match quarb::run(query, &adapter).unwrap() {
quarb::QueryResult::Nodes(ns) => ns.into_iter().map(|n| adapter.locator(n)).collect(),
quarb::QueryResult::Values(_) => panic!("expected nodes"),
};
got.sort();
got
}
fn values(query: &str) -> Vec<String> {
let adapter = HtmlAdapter::parse(DOC);
match quarb::run(query, &adapter).unwrap() {
quarb::QueryResult::Values(vs) => vs.iter().map(|v| v.to_string()).collect(),
quarb::QueryResult::Nodes(_) => panic!("expected values"),
}
}
#[test]
fn navigate_by_tag() {
assert_eq!(
nodes("/html/body/main/ul/li"),
vec!["/html/body/main/ul/li[1]", "/html/body/main/ul/li[2]"]
);
assert_eq!(values("//h1::"), vec!["Welcome"]);
assert_eq!(values("//p::"), vec!["Hello world."]);
}
#[test]
fn attributes_as_properties() {
assert_eq!(values("//p::class"), vec!["intro"]);
assert_eq!(values("//html::lang"), vec!["en"]);
assert_eq!(values("//p;;;classes"), vec!["intro"]);
assert_eq!(values("//h1::id"), vec!["top"]);
assert_eq!(values("//h1;;;tag"), vec!["h1"]);
assert_eq!(values("//strong:::traits"), vec!["inline"]);
assert_eq!(values("//h1:::traits"), vec!["block, heading"]);
}
#[test]
fn structural_traits() {
assert_eq!(nodes("//*<heading>"), vec!["/html/body/header/h1"]);
assert_eq!(
nodes("//*<link>"),
vec!["/html/body/main/a[1]", "/html/body/main/a[2]"]
);
assert_eq!(nodes("//p/*<inline>"), vec!["/html/body/main/p/strong"]);
assert_eq!(
nodes("/html/body/*<block>"),
vec!["/html/body/header", "/html/body/main"]
);
}
#[test]
fn predicates_and_pipelines() {
assert_eq!(values("//li:: @| join(\", \")"), vec!["one, two"]);
assert_eq!(values("//a @| count"), vec!["2"]);
assert_eq!(values("//a[::href !~ ~(^#)]::"), vec!["external"]);
assert_eq!(values("//a[::href *= \"example\"]::"), vec!["external"]);
assert_eq!(values("//a[::href =~ /example/]::"), vec!["external"]);
assert_eq!(
values("//a[::href *= \"missing\"]::"),
Vec::<String>::new()
);
}
#[test]
fn resolve_fragment_href() {
assert_eq!(nodes("//a::href~>"), vec!["/html/body/header/h1"]);
assert_eq!(values("//a::href~>::"), vec!["Welcome"]);
assert_eq!(nodes("//h1::href<~"), vec!["/html/body/main/a[1]"]);
assert_eq!(values("//h1::href<~::"), vec!["back to top"]);
}
#[test]
fn numeric_aggregation_over_string_attributes() {
let doc = r##"<!doctype html><html><body>
<user name="Ada"><s rank="3"></s><s rank="1"></s><s rank="2"></s></user>
<user name="Bo"><s rank="5"></s><s rank="5"></s></user>
</body></html>"##;
let adapter = HtmlAdapter::parse(doc);
let vals = |q: &str| match quarb::run(q, &adapter).unwrap() {
quarb::QueryResult::Values(vs) => vs.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
quarb::QueryResult::Nodes(_) => panic!("expected values"),
};
assert_eq!(vals("//s::rank @| sum"), vec!["16"]);
assert_eq!(vals("//s::rank @| avg"), vec!["3.2"]);
assert_eq!(vals("//s::rank @| max"), vec!["5"]);
assert_eq!(vals("//user | .(/s::rank @| avg)"), vec!["2", "5"]);
assert_eq!(vals("//user | .(/s::rank @| avg) @| avg"), vec!["3.5"]);
assert_eq!(vals("//user::name @| max"), vec![""]);
assert_eq!(vals("//user::name @| sort @| last"), vec!["Bo"]);
}
#[test]
fn value_expressions_over_string_attributes() {
let doc = r##"<!doctype html><html><body>
<item price="4" qty="3"></item>
<item price="10" qty="2"></item>
<item price="7" qty="oops"></item>
</body></html>"##;
let adapter = HtmlAdapter::parse(doc);
let vals = |q: &str| match quarb::run(q, &adapter).unwrap() {
quarb::QueryResult::Values(vs) => vs.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
quarb::QueryResult::Nodes(_) => panic!("expected values"),
};
assert_eq!(vals("//item[::price * ::qty > 15]::price"), vec!["10"]);
assert_eq!(vals("//item | ::price * ::qty"), vec!["12", "20", ""]);
assert_eq!(vals("//item | .total(::price * ::qty) @| sum"), vec!["32"]);
assert_eq!(vals("//item | .total(::price * ::qty) @| max"), vec!["20"]);
}
#[test]
fn correlated_join_shares_one_binding() {
let doc = r##"<!doctype html><html><body>
<ul id="users">
<li class="user" data-id="1" data-limit="100">Ada</li>
<li class="user" data-id="2" data-limit="500">Bo</li>
</ul>
<ul id="orders">
<li class="order" data-uid="1" data-amount="200">order-A</li>
<li class="order" data-uid="2" data-amount="300">order-B</li>
</ul>
</body></html>"##;
let adapter = HtmlAdapter::parse(doc);
let run = |q: &str| match quarb::run(q, &adapter).unwrap() {
quarb::QueryResult::Values(vs) => vs.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
quarb::QueryResult::Nodes(_) => panic!("expected values"),
};
let join = concat!(
r#"//li[::class="user"] <=> "#,
r#"//li[::class="order"][::data-uid = $*1::data-id "#,
r#"and ::data-amount > $*1::data-limit]::"#,
);
assert_eq!(run(join), vec!["order-A"]);
let stacked = concat!(
r#"//li[::class="user"] <=> "#,
r#"//li[::class="order"][::data-uid = $*1::data-id]"#,
r#"[::data-amount > $*1::data-limit]::"#,
);
assert_eq!(run(stacked), vec!["order-A"]);
}
#[test]
fn deeply_nested_html_does_not_overflow() {
use quarb::AstAdapter;
let depth = 50_000usize;
let deep = "<div>".repeat(depth);
let adapter = HtmlAdapter::parse(&deep);
let mut stack = vec![adapter.root()];
let mut divs = 0usize;
while let Some(n) = stack.pop() {
if adapter.name(n).as_deref() == Some("div") {
divs += 1;
}
stack.extend(adapter.children(n));
}
assert_eq!(divs, depth);
}
#[test]
fn sibling_reach() {
assert_eq!(
nodes("//p>>*"),
vec![
"/html/body/main/a[1]",
"/html/body/main/a[2]",
"/html/body/main/ul",
]
);
assert_eq!(nodes("//p>>?a"), vec!["/html/body/main/a[1]"]);
assert_eq!(nodes("//p>>!a"), vec!["/html/body/main/a[2]"]);
assert_eq!(nodes("//p>a"), Vec::<String>::new());
assert_eq!(
nodes("//a[::href = \"https://example.com\"]<<*"),
vec![
"/html/body/main/a[1]",
"/html/body/main/p",
"/html/body/main/ul",
]
);
assert_eq!(
nodes("//a[::href = \"https://example.com\"]<<?*"),
vec!["/html/body/main/a[1]"]
);
assert_eq!(
nodes("//a[::href = \"https://example.com\"]<<!*"),
vec!["/html/body/main/p"]
);
}