Browse Source

Macros make everything easy

master
Julio Biason 4 years ago
parent
commit
b87ce65f9c
  1. 250
      html5test/src/main.rs

250
html5test/src/main.rs

@ -1,161 +1,21 @@
use html5ever::parse_document;
use html5ever::tendril::StrTendril;
// use html5ever::tendril::StrTendril;
use html5ever::tendril::TendrilSink;
use markup5ever::interface::Attribute;
// use markup5ever::interface::Attribute;
use markup5ever_rcdom::Handle;
use markup5ever_rcdom::NodeData;
use markup5ever_rcdom::RcDom;
use std::borrow::Borrow;
use std::cell::RefCell;
// use std::cell::RefCell;
use std::default::Default;
// This go_children/walk is stupid, but I shot myself in the foot by adding
// things after the children, link on links.
//
// So, I'm rethinking this, and I'll, basically redo the same thing Tendril is
// doing by building a tree of elements.
//
// So, say, if we have
//
// <p>Text text<a href="link"><span visible>the link</span><span
// invisible>other text</span></a>, <i>for italics</i>, <pre>is for code</pre>
// </p>
//
// That would build
// root --------------------------\
// / | \ Code()
// Text(Text text) | ------ Italic() |
// Link(link) | Text(is for code)
// | Text(for italics)
// Text(the link)
//
// Tree things to do, then:
// 1. Walk the DOM tree and build the text tree.
// 2. Tree elements could return if the DOM three should continue processing or
// ignore incoming children (that would cut the "invisible span" processing,
// for example).
// 3. Build a walker for the new tree, to produce the final text. And, on that,
// we could work on the text wrap, 'cause there are elements that can't be
// wrapped (for example, Links)
/// Nodes in the text tree
#[derive(Debug)]
enum NodeType {
/// The root element; produces nothing, but has the base content.
Root,
/// A text block. Contains the text itself.
Text(String),
/// A line break
LineBreak,
// /// A link to somewhere. Contains the link.
// Link(String),
// /// Italics
// Italic,
// /// Code block
// Code,
// /// A block with an ellipsis at the end
// Ellipsis,
}
#[derive(Debug)]
struct Node {
r#type: NodeType,
children: Vec<Node>,
}
impl Node {
/// Build the root node
fn root() -> Self {
Self {
r#type: NodeType::Root,
children: Vec::new(),
}
}
/// Build a text node
fn text(text: &str) -> Self {
Self {
r#type: NodeType::Text(text.into()),
children: Vec::new(),
}
}
/// Build a linebreak node
fn line_break() -> Self {
Self {
r#type: NodeType::LineBreak,
children: Vec::new(),
}
}
// /// Build a link node
// fn link(href: &str) -> Self {
// Self {
// r#type: NodeType::Link(href.into()),
// children: Vec::new(),
// }
// }
// /// Build a ellipsis node
// fn ellipsis() -> Self {
// Self {
// r#type: NodeType::Ellipsis,
// children: Vec::new(),
// }
// }
/// Add a child node to this node
fn add_child(&mut self, node: Node) {
self.children.push(node);
}
}
// Handle functions can return a three state result:
// 1. Do not process the children of the current Handle
// 2. Process the children and add to the same parent
// 3. Use the new Node as parent for future children.
/// Result of the handling functions
enum HandleResult {
/// Stop processing, don't continue generating nodes
Stop,
/// Follow the children, but don't add any nodes in the current level
Follow,
// /// Produce a new node, but don't attach any children to it
// AddAndStay(Node),
/// Assume a new parent node
AddAndAdopt(Node),
}
/// Handle a simple block of text
fn handle_text(node: &mut Node, contents: &RefCell<StrTendril>) -> HandleResult {
let text = contents.borrow().to_string();
node.add_child(Node::text(&text));
HandleResult::Stop
}
/// Handle an incoming line break
fn handle_line_break() -> HandleResult {
let line_break = Node::line_break();
HandleResult::AddAndAdopt(line_break)
}
/// Process the span content
fn handle_span(attrs: &RefCell<Vec<Attribute>>) -> HandleResult {
let attrs = attrs.borrow();
let classes_attr = attrs
.iter()
.find(|attr| attr.name.local.to_string() == "class");
match classes_attr {
Some(classes) => {
if classes.value.contains("invisible") {
HandleResult::Stop
} else {
HandleResult::Follow
}
}
None => HandleResult::Follow,
/// Simplify the process of keep walking through the results
macro_rules! keep_going {
($source:ident, $target:ident) => {
for child in $source.children.borrow().iter() {
walk(child.borrow(), $target);
}
};
}
// fn handle_anchor(node: &mut Node, attrs: &RefCell<Vec<Attribute>>) -> HandleResult {
@ -180,10 +40,13 @@ fn handle_span(attrs: &RefCell<Vec<Attribute>>) -> HandleResult {
// }
// }
fn walk(input: &Handle, parent: &mut Node) {
// println!(">>> {:?}", input.data);
let element = match input.data {
NodeData::Text { ref contents } => handle_text(parent, contents),
fn walk(input: &Handle, result: &mut String) {
match input.data {
NodeData::Text { ref contents } => {
let text = contents.borrow().to_string();
result.push_str(&text);
keep_going!(input, result);
}
NodeData::Element {
ref name,
ref attrs,
@ -191,46 +54,66 @@ fn walk(input: &Handle, parent: &mut Node) {
} => {
let tag = name.local.to_string();
match tag.as_ref() {
"html" | "head" | "body" => HandleResult::Follow,
"p" => handle_line_break(),
"span" => handle_span(attrs),
// "a" => handle_anchor(parent, attrs),
_ => HandleResult::Stop,
"html" | "head" | "body" => keep_going!(input, result),
"p" => {
keep_going!(input, result);
result.push_str("\n");
}
"span" => {
let attrs = attrs.borrow();
let classes_attr = attrs
.iter()
.find(|attr| attr.name.local.to_string() == "class");
match classes_attr {
Some(classes) => {
if !classes.value.contains("invisible") {
keep_going!(input, result);
}
}
_ => HandleResult::Follow, // if we can't deal with it, just keep going
};
match element {
HandleResult::Stop => {}
HandleResult::Follow => {
for child in input.children.borrow().iter() {
walk(child.borrow(), parent);
None => keep_going!(input, result),
}
}
// HandleResult::AddAndStay(new_node) => {
// parent.add_child(new_node);
// for child in input.children.borrow().iter() {
// walk(child.borrow(), parent);
// }
// }
HandleResult::AddAndAdopt(mut new_node) => {
for child in input.children.borrow().iter() {
walk(child.borrow(), &mut new_node);
"a" => {
let attrs = attrs.borrow();
let rels = attrs
.iter()
.find(|attr| attr.name.local.to_string() == "rel");
let hrefs = attrs
.iter()
.find(|attr| attr.name.local.to_string() == "href");
println!("Rels: {:?}, Hrefs: {:?}", rels, hrefs);
match (rels, hrefs) {
(Some(rel), Some(href)) => {
if !rel.value.to_string().contains("tag") {
result.push_str("[[");
result.push_str(&href.value);
result.push_str("][");
keep_going!(input, result);
result.push_str("]]");
} else {
keep_going!(input, result);
}
}
_ => keep_going!(input, result),
}
}
parent.add_child(new_node);
_ => {}
}
}
_ => {
keep_going!(input, result);
}
};
}
fn build_nodes(text: &str) {
fn build_nodes(source: &str) {
let dom = parse_document(RcDom::default(), Default::default())
.from_utf8()
.read_from(&mut text.as_bytes())
.read_from(&mut source.as_bytes())
.unwrap();
let mut tree = Node::root();
walk(&dom.document, &mut tree);
println!("Tree: {:?}", tree);
let mut result = String::new();
walk(&dom.document, &mut result);
println!("Result: {:?}", result);
}
fn main() {
@ -242,6 +125,11 @@ fn main() {
);
build_nodes(&example_2);
let example_3 = String::from(
r#"<p><a href="mention" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@This is a mention</a> and <a href="tag" class="mention hashtag" rel="tag nofollow noopener noreferrer" target="_blank">#this is a tag</a></p>"#,
);
build_nodes(&example_3);
// let example_1 = String::from(
// r#"<p>Today I finally moved with my contact and calendar management into the terminal with <a href="https://fosstodon.org/tags/vdirsyncer" class="mention hashtag" rel="tag nofollow noopener noreferrer" target="_blank">#<span>vdirsyncer</span></a> <a href="https://fosstodon.org/tags/khal" class="mention hashtag" rel="tag nofollow noopener noreferrer" target="_blank">#<span>khal</span></a> and <a href="https://fosstodon.org/tags/khard" class="mention hashtag" rel="tag nofollow noopener noreferrer" target="_blank">#<span>khard</span></a>.</p><p>Thank you <span class="h-card"><a href="https://fosstodon.org/@hund" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@<span>hund</span></a></span> for your great post: <a href="https://hund.tty1.se/2020/08/12/how-to-sync-and-manage-your-caldav-and-carddav-via-the-terminal.html" rel="nofollow noopener noreferrer" target="_blank"><span class="invisible">https://</span><span class="ellipsis">hund.tty1.se/2020/08/12/how-to</span><span class="invisible">-sync-and-manage-your-caldav-and-carddav-via-the-terminal.html</span></a></p><p><a href="https://fosstodon.org/tags/carddav" class="mention hashtag" rel="tag nofollow noopener noreferrer" target="_blank">#<span>carddav</span></a> <a href="https://fosstodon.org/tags/caldav" class="mention hashtag" rel="tag nofollow noopener noreferrer" target="_blank">#<span>caldav</span></a> <a href="https://fosstodon.org/tags/terminal" class="mention hashtag" rel="tag nofollow noopener noreferrer" target="_blank">#<span>terminal</span></a></p>"#,
// );

Loading…
Cancel
Save