Skip to content

Commit

Permalink
add support for parsing multiple link type (rel) attribute values
Browse files Browse the repository at this point in the history
  • Loading branch information
snshn committed Dec 2, 2024
1 parent 2a8d5d7 commit 4a5f85b
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 25 deletions.
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());
}
}

0 comments on commit 4a5f85b

Please sign in to comment.