Skip to content

Commit

Permalink
Add simple, string-based 'add' commands
Browse files Browse the repository at this point in the history
- add a text element
- add a comment
  • Loading branch information
kelko committed Sep 25, 2022
1 parent c5be5f5 commit 7b7eda8
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 2 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ Currently supported:
- `CLEAR-CONTENT`: clears all children from the previously selected elements
- `SET-ATTR`: Sets a given attribute to a specified value
- `SET-TEXT-CONTENT`: removes previous children and replaces it with exactly one given text child
- `ADD-TEXT-CONTENT`: appends a new text child
- `ADD-COMMENT`: appends a new comment child

Planned commands:

- `ADD-TEXT-CONTENT`: appends a new text child
- `ADD-COMMENT`: appends a new comment child
- `ADD-ELEMENT`: appends a new tag/element child
- `REPLACE-WITH`: replace all elements matching a CSS selector with new elements (alias: `MAP`)
- `READ-FROM`: reads a DOM from a different file, mainly in combination with `ADD-ELEMENT` or `REPLACE-WITH` (alias: `SOURCE`)
Expand Down
44 changes: 44 additions & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ pub enum Command<'a> {
/// Remove all children of the currently selected nodes and add a new text as child instead
/// Returns the input as result.
SetTextContent(ValueSource),
/// adds a new text as child
/// Returns the input as result.
AddTextContent(ValueSource),
/// adds a new comment as child
/// Returns the input as result.
AddComment(ValueSource),
}

impl<'a> Command<'a> {
Expand All @@ -80,6 +86,8 @@ impl<'a> Command<'a> {
Self::set_attr(input, attribute, value_source)
}
Command::SetTextContent(value_source) => Self::set_text_content(input, value_source),
Command::AddTextContent(value_source) => Self::add_text_content(input, value_source),
Command::AddComment(value_source) => Self::add_comment(input, value_source),
}
}

Expand Down Expand Up @@ -175,4 +183,40 @@ impl<'a> Command<'a> {

Ok(input.clone())
}

fn add_text_content(
input: &Vec<rctree::Node<HtmlContent>>,
value_source: &ValueSource,
) -> Result<Vec<rctree::Node<HtmlContent>>, CommandError> {
trace!(
"Running ADD-TEXT-CONTENT command with value: {:#?}",
value_source
);

for node in input {
let mut working_copy = rctree::Node::clone(node);
working_copy.append(rctree::Node::new(HtmlContent::Text(value_source.render())));
}

Ok(input.clone())
}

fn add_comment(
input: &Vec<rctree::Node<HtmlContent>>,
value_source: &ValueSource,
) -> Result<Vec<rctree::Node<HtmlContent>>, CommandError> {
trace!(
"Running ADD-COMMENT command with value: {:#?}",
value_source
);

for node in input {
let mut working_copy = rctree::Node::clone(node);
working_copy.append(rctree::Node::new(HtmlContent::Comment(
value_source.render(),
)));
}

Ok(input.clone())
}
}
6 changes: 6 additions & 0 deletions src/parsing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,19 @@ parser! {
= "SET-ATTR{" whitespace()? a:identifier() whitespace()? assign_marker() whitespace()? v:string_value() "}" { Command::SetAttribute(String::from(a), ValueSource::StringValue(String::from(v))) }
rule set_text_content_command() -> Command<'input>
= "SET-TEXT-CONTENT{" whitespace()? (assign_marker() whitespace()?)? v:string_value() "}" { Command::SetTextContent(ValueSource::StringValue(String::from(v))) }
rule add_text_content_command() -> Command<'input>
= "ADD-TEXT-CONTENT{" whitespace()? (assign_marker() whitespace()?)? v:string_value() "}" { Command::AddTextContent(ValueSource::StringValue(String::from(v))) }
rule add_comment_command() -> Command<'input>
= "ADD-COMMENT{" whitespace()? (assign_marker() whitespace()?)? v:string_value() "}" { Command::AddComment(ValueSource::StringValue(String::from(v))) }
pub(super) rule command() -> Command<'input>
= only_command()
/ without_command()
/ clear_attr_command()
/ clear_content_command()
/ set_attr_command()
/ set_text_content_command()
/ add_text_content_command()
/ add_comment_command()
pub rule pipeline() -> Pipeline<'input>
= p:(command() ** " | ") { Pipeline::new(p) }
}
Expand Down
66 changes: 66 additions & 0 deletions src/parsing/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,69 @@ fn parse_single_set_text_content_by_string_with_ascii_arrow() {
)))
);
}

#[test]
fn parse_single_add_text_content_by_string() {
let parsed = super::grammar::command("ADD-TEXT-CONTENT{'some text'}");
assert_eq!(
parsed,
Ok(Command::AddTextContent(ValueSource::StringValue(
String::from("some text")
)))
);
}

#[test]
fn parse_single_add_text_content_by_string_with_arrow() {
let parsed = super::grammar::command("ADD-TEXT-CONTENT{ ↤ 'some text'}");
assert_eq!(
parsed,
Ok(Command::AddTextContent(ValueSource::StringValue(
String::from("some text")
)))
);
}

#[test]
fn parse_single_add_text_content_by_string_with_ascii_arrow() {
let parsed = super::grammar::command("ADD-TEXT-CONTENT{ <- 'some text'}");
assert_eq!(
parsed,
Ok(Command::AddTextContent(ValueSource::StringValue(
String::from("some text")
)))
);
}

#[test]
fn parse_single_add_comment_by_string() {
let parsed = super::grammar::command("ADD-COMMENT{'some text'}");
assert_eq!(
parsed,
Ok(Command::AddComment(ValueSource::StringValue(String::from(
"some text"
))))
);
}

#[test]
fn parse_single_add_comment_by_string_with_arrow() {
let parsed = super::grammar::command("ADD-COMMENT{ ↤ 'some text'}");
assert_eq!(
parsed,
Ok(Command::AddComment(ValueSource::StringValue(String::from(
"some text"
))))
);
}

#[test]
fn parse_single_add_comment_by_string_with_ascii_arrow() {
let parsed = super::grammar::command("ADD-COMMENT{ <- 'some text'}");
assert_eq!(
parsed,
Ok(Command::AddComment(ValueSource::StringValue(String::from(
"some text"
))))
);
}
156 changes: 156 additions & 0 deletions src/pipeline/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,159 @@ fn run_on_single_set_text_content_from_string_for_tag_with_multiple_children() {
String::from(r#"<div class="bar" data-test="foo">Other Content</div>"#)
);
}

#[test]
fn run_on_single_add_text_content_from_string_for_tag() {
let pipeline = Pipeline::new(vec![Command::AddTextContent(ValueSource::StringValue(
String::from("Other Content"),
))]);

let dom = tl::parse(
r#"<div data-test="foo" class="bar">Some Content</div>"#,
tl::ParserOptions::default(),
)
.unwrap();
let starting_elements = HtmlContent::import(dom).unwrap();

let mut result = pipeline
.run_on(vec![rctree::Node::clone(&starting_elements)])
.unwrap();

assert_eq!(result.len(), 1);
let first_result = result.pop().unwrap();
assert_eq!(
first_result.outer_html(),
String::from(r#"<div class="bar" data-test="foo">Some ContentOther Content</div>"#)
);
}

#[test]
fn run_on_single_add_text_content_from_string_for_empty_tag() {
let pipeline = Pipeline::new(vec![Command::AddTextContent(ValueSource::StringValue(
String::from("Other Content"),
))]);

let dom = tl::parse(
r#"<div data-test="foo" class="bar"></div>"#,
tl::ParserOptions::default(),
)
.unwrap();
let starting_elements = HtmlContent::import(dom).unwrap();

let mut result = pipeline
.run_on(vec![rctree::Node::clone(&starting_elements)])
.unwrap();

assert_eq!(result.len(), 1);
let first_result = result.pop().unwrap();
assert_eq!(
first_result.outer_html(),
String::from(r#"<div class="bar" data-test="foo">Other Content</div>"#)
);
}

#[test]
fn run_on_single_add_text_content_from_string_for_tag_with_multiple_children() {
let pipeline = Pipeline::new(vec![Command::AddTextContent(ValueSource::StringValue(
String::from("Other Content"),
))]);

let dom = tl::parse(
r#"<div data-test="foo" class="bar">Some <em>special</em> Content. <!-- rightly so --></div>"#,
tl::ParserOptions::default(),
)
.unwrap();
let starting_elements = HtmlContent::import(dom).unwrap();

let mut result = pipeline
.run_on(vec![rctree::Node::clone(&starting_elements)])
.unwrap();

assert_eq!(result.len(), 1);
let first_result = result.pop().unwrap();
assert_eq!(
first_result.outer_html(),
String::from(
r#"<div class="bar" data-test="foo">Some <em>special</em> Content. <!-- rightly so -->Other Content</div>"#
)
);
}

#[test]
fn run_on_single_add_comment_from_string_for_tag() {
let pipeline = Pipeline::new(vec![Command::AddComment(ValueSource::StringValue(
String::from("Other Content"),
))]);

let dom = tl::parse(
r#"<div data-test="foo" class="bar">Some Content</div>"#,
tl::ParserOptions::default(),
)
.unwrap();
let starting_elements = HtmlContent::import(dom).unwrap();

let mut result = pipeline
.run_on(vec![rctree::Node::clone(&starting_elements)])
.unwrap();

assert_eq!(result.len(), 1);
let first_result = result.pop().unwrap();
assert_eq!(
first_result.outer_html(),
String::from(
r#"<div class="bar" data-test="foo">Some Content<!-- Other Content --></div>"#
)
);
}

#[test]
fn run_on_single_add_comment_from_string_for_empty_tag() {
let pipeline = Pipeline::new(vec![Command::AddComment(ValueSource::StringValue(
String::from("Other Content"),
))]);

let dom = tl::parse(
r#"<div data-test="foo" class="bar"></div>"#,
tl::ParserOptions::default(),
)
.unwrap();
let starting_elements = HtmlContent::import(dom).unwrap();

let mut result = pipeline
.run_on(vec![rctree::Node::clone(&starting_elements)])
.unwrap();

assert_eq!(result.len(), 1);
let first_result = result.pop().unwrap();
assert_eq!(
first_result.outer_html(),
String::from(r#"<div class="bar" data-test="foo"><!-- Other Content --></div>"#)
);
}

#[test]
fn run_on_single_add_comment_from_string_for_tag_with_multiple_children() {
let pipeline = Pipeline::new(vec![Command::AddComment(ValueSource::StringValue(
String::from("Other Content"),
))]);

let dom = tl::parse(
r#"<div data-test="foo" class="bar"><!-- rightly so -->Some <em>special</em> Content.</div>"#,
tl::ParserOptions::default(),
)
.unwrap();
let starting_elements = HtmlContent::import(dom).unwrap();

let mut result = pipeline
.run_on(vec![rctree::Node::clone(&starting_elements)])
.unwrap();

assert_eq!(result.len(), 1);
let first_result = result.pop().unwrap();
assert_eq!(
first_result.outer_html(),
String::from(
r#"<div class="bar" data-test="foo"><!-- rightly so -->Some <em>special</em> Content.<!-- Other Content --></div>"#
)
);
}
60 changes: 60 additions & 0 deletions tests/add_comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use html_streaming_editor::*;

const HTML_INPUT: &str = r#"<html>
<head></head>
<body>
<h1>Title</h1>
<p id="first-para">Some first text</p>
<p id="second-para">Some more text, even with an <img src=""></p>
<p id="third-para">Third text of <abbr>HTML</abbr>, but no <abbr>CSS</abbr></p>
<ul id="list">
<li id="item-1">1</li>
<li id="item-2">2</li>
<li id="item-3">3</li>
</ul>
</body>
</html>"#;

#[test]
fn add_to_first_p_content() -> Result<(), StreamingEditorError> {
let command = "ONLY{#first-para} | ADD-COMMENT{'followed by a comment'}";

let mut input = Box::new(HTML_INPUT.as_bytes());
let mut output = Vec::new();
let hse = HtmlStreamingEditor::new(&mut input, &mut output);

let _ = hse.run(command)?;
let result_string = String::from_utf8(output).unwrap();

assert_eq!(
result_string,
String::from(r#"<p id="first-para">Some first text<!-- followed by a comment --></p>"#)
);

Ok(())
}

#[test]
fn add_to_ul() -> Result<(), StreamingEditorError> {
let command = "ONLY{ul} | ADD-COMMENT{'Foo'}";

let mut input = Box::new(HTML_INPUT.as_bytes());
let mut output = Vec::new();
let hse = HtmlStreamingEditor::new(&mut input, &mut output);

let _ = hse.run(command)?;
let result_string = String::from_utf8(output).unwrap();

assert_eq!(
result_string,
String::from(
r#"<ul id="list">
<li id="item-1">1</li>
<li id="item-2">2</li>
<li id="item-3">3</li>
<!-- Foo --></ul>"#
)
);

Ok(())
}
Loading

0 comments on commit 7b7eda8

Please sign in to comment.