Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for parsing multiple link type (rel) attribute values #422

Merged
merged 1 commit into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 40 additions & 25 deletions src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ use crate::url::{
};
use crate::utils::{parse_content_type, retrieve_asset};

#[derive(PartialEq, Eq)]
pub enum LinkType {
Alternate,
DnsPrefetch,
Icon,
Preload,
Stylesheet,
}

struct SrcSetItem<'a> {
path: &'a str,
descriptor: &'a str,
Expand Down Expand Up @@ -141,26 +150,6 @@ pub fn create_metadata_tag(url: &Url) -> String {
)
}

pub fn determine_link_node_type(node: &Handle) -> &str {
let mut link_type: &str = "unknown";

if let Some(link_attr_rel_value) = get_node_attr(node, "rel") {
if is_icon(&link_attr_rel_value) {
link_type = "icon";
} else if link_attr_rel_value.eq_ignore_ascii_case("stylesheet")
|| link_attr_rel_value.eq_ignore_ascii_case("alternate stylesheet")
{
link_type = "stylesheet";
} else if link_attr_rel_value.eq_ignore_ascii_case("preload") {
link_type = "preload";
} else if link_attr_rel_value.eq_ignore_ascii_case("dns-prefetch") {
link_type = "dns-prefetch";
}
}

link_type
}

pub fn embed_srcset(
cache: &mut HashMap<String, Vec<u8>>,
client: &Client,
Expand Down Expand Up @@ -454,6 +443,26 @@ pub fn is_icon(attr_value: &str) -> bool {
ICON_VALUES.contains(&attr_value.to_lowercase().as_str())
}

pub fn parse_link_type(link_attr_rel_value: &str) -> Vec<LinkType> {
let mut types: Vec<LinkType> = vec![];

for link_attr_rel_type in link_attr_rel_value.split_whitespace() {
if link_attr_rel_type.eq_ignore_ascii_case("alternate") {
types.push(LinkType::Alternate);
} else if link_attr_rel_type.eq_ignore_ascii_case("dns-prefetch") {
types.push(LinkType::DnsPrefetch);
} else if link_attr_rel_type.eq_ignore_ascii_case("preload") {
types.push(LinkType::Preload);
} else if link_attr_rel_type.eq_ignore_ascii_case("stylesheet") {
types.push(LinkType::Stylesheet);
} else if is_icon(&link_attr_rel_type) {
types.push(LinkType::Icon);
}
}

types
}

pub fn set_base_url(document: &Handle, desired_base_href: String) -> RcDom {
let mut buf: Vec<u8> = Vec::new();
serialize(
Expand Down Expand Up @@ -665,7 +674,10 @@ pub fn retrieve_and_embed_asset(
s = String::from_utf8_lossy(&data).to_string();
}

if node_name == "link" && determine_link_node_type(node) == "stylesheet" {
if node_name == "link"
&& parse_link_type(&get_node_attr(node, "rel").unwrap_or(String::from("")))
.contains(&LinkType::Stylesheet)
{
// Stylesheet LINK elements require special treatment
let css: String = embed_css(cache, client, &final_url, &s, options);

Expand Down Expand Up @@ -757,9 +769,10 @@ pub fn walk_and_embed_assets(
}
}
"link" => {
let link_type: &str = determine_link_node_type(node);
let link_node_types: Vec<LinkType> =
parse_link_type(&get_node_attr(node, "rel").unwrap_or(String::from("")));

if link_type == "icon" {
if link_node_types.contains(&LinkType::Icon) {
// Find and resolve LINK's href attribute
if let Some(link_attr_href_value) = get_node_attr(node, "href") {
if !options.no_images && !link_attr_href_value.is_empty() {
Expand All @@ -776,7 +789,7 @@ pub fn walk_and_embed_assets(
set_node_attr(node, "href", None);
}
}
} else if link_type == "stylesheet" {
} else if link_node_types.contains(&LinkType::Stylesheet) {
// Resolve LINK's href attribute
if let Some(link_attr_href_value) = get_node_attr(node, "href") {
if options.no_css {
Expand All @@ -797,7 +810,9 @@ pub fn walk_and_embed_assets(
}
}
}
} else if link_type == "preload" || link_type == "dns-prefetch" {
} else if link_node_types.contains(&LinkType::Preload)
|| link_node_types.contains(&LinkType::DnsPrefetch)
{
// Since all resources are embedded as data URLs, preloading and prefetching are not necessary
set_node_attr(node, "rel", None);
} else {
Expand Down
1 change: 1 addition & 0 deletions tests/html/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod get_node_attr;
mod get_node_name;
mod has_favicon;
mod is_icon;
mod parse_link_type;
mod serialize_document;
mod set_node_attr;
mod walk_and_embed_assets;
58 changes: 58 additions & 0 deletions tests/html/parse_link_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗
// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝
// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗
// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║
// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝
// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝

#[cfg(test)]
mod passing {
use monolith::html;

#[test]
fn icon() {
assert!(html::parse_link_type("icon").contains(&html::LinkType::Icon));
}

#[test]
fn shortcut_icon_capitalized() {
assert!(html::parse_link_type("Shortcut Icon").contains(&html::LinkType::Icon));
}

#[test]
fn stylesheet() {
assert!(html::parse_link_type("stylesheet").contains(&html::LinkType::Stylesheet));
}

#[test]
fn preload_stylesheet() {
assert!(html::parse_link_type("preload stylesheet").contains(&html::LinkType::Stylesheet));
}
}

// ███████╗ █████╗ ██╗██╗ ██╗███╗ ██╗ ██████╗
// ██╔════╝██╔══██╗██║██║ ██║████╗ ██║██╔════╝
// █████╗ ███████║██║██║ ██║██╔██╗ ██║██║ ███╗
// ██╔══╝ ██╔══██║██║██║ ██║██║╚██╗██║██║ ██║
// ██║ ██║ ██║██║███████╗██║██║ ╚████║╚██████╔╝
// ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝

#[cfg(test)]
mod failing {
use monolith::html;

#[test]
fn mask_icon() {
assert!(html::parse_link_type("mask-icon").is_empty());
}

#[test]
fn fluid_icon() {
assert!(html::parse_link_type("fluid-icon").is_empty());
}

#[test]
fn empty_string() {
assert!(html::parse_link_type("").is_empty());
}
}
Loading