Uses the html5ever to parse html and convert it to a sxd_document::Package to use with sxd_xpath.
parse_html and parse_html_fragment intentionally return only the parsed
sxd_document::Package. If callers need to inspect html5ever parse errors,
use parse_html_with_errors or parse_html_fragment_with_errors:
let (package, errors) = sxd_html::parse_html_with_errors(contents);
for error in &errors {
eprintln!("line {}: {}", error.line(), error.message());
}use sxd_xpath::{nodeset::Node, Context, Error, Factory, Value};
fn main() -> anyhow::Result<()> {
let contents = r#"
<!doctype html>
<html lang="en">
<body>
<article>
<h1>
<a href="/rust-lang/rust"><span>rust-lang</span> /rust</a>
</h1>
</article>
<article>
<h1>
<a href="/tokio-rs/tokio"><span>tokio-rs</span> /tokio</a>
</h1>
</article>
<article>
<h1>
<a href="/hyperium/hyper"><span>hyperium</span> /hyper</a>
</h1>
</article>
</body>
</html>
"#;
let package = sxd_html::parse_html(contents);
let document = package.as_document();
let mut trending_repos: Vec<String> = Default::default();
let repo_as = match evaluate_xpath_node(document.root(), "//article/h1/a")? {
Value::Nodeset(set) => set,
_ => panic!("Expected node set"),
}
.document_order();
for repo_a in repo_as {
let user = evaluate_xpath_node(repo_a, "./span/text()")?.into_string();
let name = repo_a
.children()
.last()
.unwrap()
.text()
.map(|t| t.text())
.unwrap_or_default();
trending_repos.push(format!("{}{}", user.trim(), name.trim()));
}
println!("Trending Repos :");
for name in trending_repos {
println!("\t{name}");
}
Ok(())
}
fn evaluate_xpath_node<'d>(node: impl Into<Node<'d>>, expr: &str) -> Result<Value<'d>, Error> {
let factory = Factory::new();
let expression = factory.build(expr)?;
let expression = expression.ok_or(Error::NoXPath)?;
let context = Context::new();
expression
.evaluate(&context, node.into())
.map_err(Into::into)
}Output:
Trending Repos :
rust-lang/rust
tokio-rs/tokio
hyperium/hyper
This library uses html5ever to parse html. So you need to follow the constraints of html5ever.
For example, you need to be careful about how to handle <table> tags.
<table>
<tr>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
</tr>
</table>will be interpreted as
<table>
<tbody>
<tr>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
</tr>
</tbody>
</table>So if you run the query //table/tr/td, you will not get the td hidden by tbody.
You need to run the query //table/tbody/tr/td.
HTML elements are stored without the XHTML namespace so existing unprefixed XPath queries such as
//article/h1/a keep working. Embedded SVG and MathML elements keep their HTML5 namespaces. Register
the namespace prefix on the XPath context before querying those elements:
let mut context = Context::new();
context.set_namespace("svg", "http://www.w3.org/2000/svg");Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.