#![cfg_attr(docsrs, feature(doc_cfg))]
pub use fhp_core as core_types;
pub use fhp_core::tag::Tag;
pub use fhp_tokenizer as tokenizer;
pub use fhp_tree as tree;
pub use fhp_tree::{Document, HtmlError, NodeRef};
pub use fhp_tree::node::NodeId;
pub mod streaming {
pub use fhp_tree::streaming::{EarlyStopParser, ParseStatus, StreamParser, parse_stream};
}
#[cfg(any(feature = "css-selector", feature = "xpath"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "css-selector", feature = "xpath"))))]
pub use fhp_selector::{CompiledSelector, DocumentIndex, Selectable, Selection};
#[cfg(feature = "xpath")]
#[cfg_attr(docsrs, doc(cfg(feature = "xpath")))]
pub mod xpath {
pub use fhp_selector::xpath::ast::XPathResult;
}
#[cfg(feature = "encoding")]
#[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
pub mod encoding {
pub use fhp_encoding::{Encoding, decode, decode_or_detect, detect};
}
#[cfg(feature = "async-tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-tokio")))]
pub mod async_parser {
pub use fhp_tree::async_parser::{AsyncParser, parse_async};
}
pub mod prelude {
pub use fhp_tree::node::NodeId;
pub use fhp_tree::{Document, HtmlError, NodeRef};
#[cfg(any(feature = "css-selector", feature = "xpath"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "css-selector", feature = "xpath"))))]
pub use fhp_selector::{CompiledSelector, Selectable, Selection};
pub use crate::HtmlParser;
}
const DEFAULT_MAX_INPUT_SIZE: usize = 256 * 1024 * 1024;
pub struct ParserBuilder {
max_input_size: usize,
fragment_mode: bool,
}
impl Default for ParserBuilder {
fn default() -> Self {
Self {
max_input_size: DEFAULT_MAX_INPUT_SIZE,
fragment_mode: false,
}
}
}
impl ParserBuilder {
pub fn max_input_size(mut self, size: usize) -> Self {
self.max_input_size = size;
self
}
pub fn fragment_mode(mut self, enabled: bool) -> Self {
self.fragment_mode = enabled;
self
}
pub fn build(self) -> HtmlParser {
HtmlParser {
max_input_size: self.max_input_size,
_fragment_mode: self.fragment_mode,
}
}
}
pub struct HtmlParser {
max_input_size: usize,
_fragment_mode: bool,
}
impl HtmlParser {
pub fn builder() -> ParserBuilder {
ParserBuilder::default()
}
pub fn parse(input: &str) -> Result<Document, HtmlError> {
fhp_tree::parse(input)
}
pub fn parse_owned(input: String) -> Result<Document, HtmlError> {
fhp_tree::parse_owned(input)
}
pub fn parse_bytes(input: &[u8]) -> Result<Document, HtmlError> {
fhp_tree::parse_bytes(input)
}
pub fn parse_str(&self, input: &str) -> Result<Document, HtmlError> {
if input.len() > self.max_input_size {
return Err(HtmlError::InputTooLarge {
size: input.len(),
max: self.max_input_size,
});
}
fhp_tree::parse(input)
}
pub fn parse_str_owned(&self, input: String) -> Result<Document, HtmlError> {
if input.len() > self.max_input_size {
return Err(HtmlError::InputTooLarge {
size: input.len(),
max: self.max_input_size,
});
}
fhp_tree::parse_owned(input)
}
pub fn parse_raw(&self, input: &[u8]) -> Result<Document, HtmlError> {
if input.len() > self.max_input_size {
return Err(HtmlError::InputTooLarge {
size: input.len(),
max: self.max_input_size,
});
}
fhp_tree::parse_bytes(input)
}
}
pub fn parse(input: &str) -> Result<Document, HtmlError> {
HtmlParser::parse(input)
}
pub fn parse_owned(input: String) -> Result<Document, HtmlError> {
HtmlParser::parse_owned(input)
}
pub fn parse_bytes(input: &[u8]) -> Result<Document, HtmlError> {
HtmlParser::parse_bytes(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_convenience() {
let doc = parse("<div><p>Hello</p></div>").unwrap();
assert_eq!(doc.root().text_content(), "Hello");
}
#[test]
fn parse_bytes_convenience() {
let doc = parse_bytes(b"<div><p>Hello</p></div>").unwrap();
assert_eq!(doc.root().text_content(), "Hello");
}
#[test]
fn builder_default() {
let parser = HtmlParser::builder().build();
let doc = parser.parse_str("<p>ok</p>").unwrap();
assert_eq!(doc.root().text_content(), "ok");
}
#[test]
fn builder_max_input_size() {
let parser = HtmlParser::builder().max_input_size(10).build();
let result = parser.parse_str("<p>this is too long</p>");
assert!(result.is_err());
}
#[test]
fn builder_fragment_mode() {
let parser = HtmlParser::builder().fragment_mode(true).build();
let doc = parser.parse_str("<li>item</li>").unwrap();
assert_eq!(doc.root().text_content(), "item");
}
#[test]
fn builder_parse_raw() {
let parser = HtmlParser::builder().build();
let doc = parser.parse_raw(b"<p>bytes</p>").unwrap();
assert_eq!(doc.root().text_content(), "bytes");
}
#[test]
fn builder_parse_raw_too_large() {
let parser = HtmlParser::builder().max_input_size(5).build();
let result = parser.parse_raw(b"<p>too large</p>");
assert!(result.is_err());
}
#[test]
fn static_parse_method() {
let doc = HtmlParser::parse("<b>bold</b>").unwrap();
assert_eq!(doc.root().text_content(), "bold");
}
#[test]
fn static_parse_bytes_method() {
let doc = HtmlParser::parse_bytes(b"<i>italic</i>").unwrap();
assert_eq!(doc.root().text_content(), "italic");
}
#[cfg(feature = "css-selector")]
#[test]
fn selector_reexport() {
let doc = HtmlParser::parse("<div><p>Hello</p></div>").unwrap();
let sel = doc.select("p").unwrap();
assert_eq!(sel.len(), 1);
}
#[test]
fn streaming_reexport() {
let doc = streaming::parse_stream(b"<p>stream</p>".chunks(4)).unwrap();
assert_eq!(doc.root().text_content(), "stream");
}
#[test]
fn node_ref_access() {
let doc = parse("<a href=\"url\">link</a>").unwrap();
let root = doc.root();
let a = root.first_child().unwrap();
assert_eq!(a.tag(), Tag::A);
assert_eq!(a.attr("href"), Some("url"));
}
#[test]
fn prelude_works() {
use crate::prelude::*;
let doc = HtmlParser::parse("<p>prelude</p>").unwrap();
let _root: NodeRef<'_> = doc.root();
}
}