Created
February 8, 2025 18:59
-
-
Save inxeoz/9716d832ee97a940016a56bb6fac4142 to your computer and use it in GitHub Desktop.
combineHTMLandCSS.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use kuchiki::traits::*; | |
| use std::collections::HashMap; | |
| /// A simple structure to hold one CSS rule. | |
| struct Rule { | |
| /// One or more selectors (for example, "header nav ul" or "body") | |
| selectors: Vec<String>, | |
| /// A map of CSS property to value for this rule. | |
| declarations: HashMap<String, String>, | |
| } | |
| /// Remove comments (/* … */) from the CSS source. | |
| fn remove_css_comments(css: &str) -> String { | |
| let mut result = String::with_capacity(css.len()); | |
| let mut chars = css.chars().peekable(); | |
| while let Some(ch) = chars.next() { | |
| if ch == '/' && chars.peek() == Some(&'*') { | |
| // Skip until end of comment. | |
| chars.next(); // skip '*' | |
| while let Some(ch) = chars.next() { | |
| if ch == '*' && chars.peek() == Some(&'/') { | |
| chars.next(); // skip '/' | |
| break; | |
| } | |
| } | |
| } else { | |
| result.push(ch); | |
| } | |
| } | |
| result | |
| } | |
| /// A very simple CSS parser that returns a list of rules. | |
| /// This parser assumes the CSS is in the form: | |
| /// selector { property: value; property: value; } | |
| fn parse_css(css: &str) -> Vec<Rule> { | |
| let mut rules = Vec::new(); | |
| let css = remove_css_comments(css); | |
| // Split by "}" to get each block. | |
| for block in css.split('}') { | |
| if let Some((selectors, declarations)) = block.split_once('{') { | |
| // The selectors may be a comma-separated list. | |
| let selectors = selectors | |
| .split(',') | |
| .map(|s| s.trim().to_string()) | |
| .filter(|s| !s.is_empty()) | |
| .collect::<Vec<_>>(); | |
| let mut decl_map = HashMap::new(); | |
| for decl in declarations.split(';') { | |
| let decl = decl.trim(); | |
| if decl.is_empty() { | |
| continue; | |
| } | |
| if let Some((prop, val)) = decl.split_once(':') { | |
| decl_map.insert(prop.trim().to_string(), val.trim().to_string()); | |
| } | |
| } | |
| if !selectors.is_empty() && !decl_map.is_empty() { | |
| rules.push(Rule { | |
| selectors, | |
| declarations: decl_map, | |
| }); | |
| } | |
| } | |
| } | |
| rules | |
| } | |
| /// Parse an inline style attribute (if any) into a HashMap. | |
| fn parse_inline_style(style: Option<&str>) -> HashMap<String, String> { | |
| let mut map = HashMap::new(); | |
| if let Some(style_str) = style { | |
| for decl in style_str.split(';') { | |
| let decl = decl.trim(); | |
| if decl.is_empty() { | |
| continue; | |
| } | |
| if let Some((prop, val)) = decl.split_once(':') { | |
| map.insert(prop.trim().to_string(), val.trim().to_string()); | |
| } | |
| } | |
| } | |
| map | |
| } | |
| /// Main function that reads HTML and CSS, inlines the CSS rules, | |
| /// and prints the resulting HTML. | |
| fn main() { | |
| let html = r##"<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Simple Demo</title> | |
| <link rel="stylesheet" href="style.css"> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>Welcome!</h1> | |
| <nav> | |
| <ul> | |
| <li><a href="#">Home</a></li> | |
| <li><a href="#">About</a></li> | |
| <li><a href="#">Services</a></li> | |
| <li><a href="#">Contact</a></li> | |
| </ul> | |
| </nav> | |
| </header> | |
| <main> | |
| <section id="hero"> | |
| <h2>This is the Hero Section</h2> | |
| <p>A brief description or call to action here.</p> | |
| <button>Learn More</button> | |
| </section> | |
| <section id="content"> | |
| <article> | |
| <h3>Article Title</h3> | |
| <p>Some sample content for the article.</p> | |
| </article> | |
| <article> | |
| <h3>Another Article Title</h3> | |
| <p>More sample content for the article.</p> | |
| </article> | |
| </section> | |
| </main> | |
| <footer> | |
| <p>© 2025 My Demo Site</p> | |
| </footer> | |
| </body> | |
| </html>"##; | |
| // Your CSS code. | |
| let css = r#"/* General Styles */ | |
| body { | |
| font-family: sans-serif; | |
| margin: 0; | |
| padding: 0; | |
| background-color: #f4f4f4; | |
| color: #333; | |
| } | |
| /* Header */ | |
| header { | |
| background-color: #333; | |
| color: #fff; | |
| padding: 10px 0; | |
| text-align: center; | |
| } | |
| header nav ul { | |
| padding: 0; | |
| list-style: none; | |
| } | |
| header nav ul li { | |
| display: inline; | |
| margin: 0 10px; | |
| } | |
| header nav a { | |
| color: #fff; | |
| text-decoration: none; | |
| } | |
| /* Main */ | |
| main { | |
| padding: 20px; | |
| } | |
| #hero { | |
| background-color: #e0e0e0; | |
| padding: 20px; | |
| text-align: center; | |
| margin-bottom: 20px; | |
| } | |
| #hero button { | |
| background-color: #333; | |
| color: #fff; | |
| padding: 10px 20px; | |
| border: none; | |
| cursor: pointer; | |
| } | |
| #content { | |
| display: flex; | |
| justify-content: space-around; /* Distribute articles evenly */ | |
| } | |
| #content article { | |
| width: 45%; /* Adjust as needed */ | |
| background-color: #fff; | |
| padding: 15px; | |
| border: 1px solid #ddd; | |
| } | |
| /* Footer */ | |
| footer { | |
| background-color: #333; | |
| color: #fff; | |
| text-align: center; | |
| padding: 10px 0; | |
| position: fixed; | |
| bottom: 0; | |
| width: 100%; | |
| }"#; | |
| // Parse the CSS into rules. | |
| let rules = parse_css(css); | |
| // Parse the HTML document. | |
| let document = kuchiki::parse_html().one(html); | |
| // Optionally, remove the external CSS <link> tag. | |
| for css_link in document.select("link[rel=stylesheet]").unwrap() { | |
| css_link.as_node().detach(); | |
| } | |
| // For each rule and each selector in that rule, select matching elements | |
| // and merge the rule’s declarations into the element’s inline style. | |
| for rule in &rules { | |
| for selector in &rule.selectors { | |
| if let Ok(mut nodes) = document.select(selector) { | |
| for css_match in nodes { | |
| // Get the element as a mutable NodeRef. | |
| let as_node = css_match.as_node(); | |
| if let Some(element) = as_node.as_element() { | |
| // Borrow the attributes. | |
| let mut attrs = element.attributes.borrow_mut(); | |
| // Parse any existing inline style. | |
| let mut style_map = parse_inline_style(attrs.get("style")); | |
| // Merge/overwrite with the rule's declarations. | |
| for (prop, val) in &rule.declarations { | |
| style_map.insert(prop.clone(), val.clone()); | |
| } | |
| // Reconstruct the inline style string. | |
| let new_style = style_map | |
| .into_iter() | |
| .map(|(prop, val)| format!("{}: {};", prop, val)) | |
| .collect::<Vec<_>>() | |
| .join(" "); | |
| attrs.insert("style", new_style); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Print the modified HTML. | |
| println!("{}", document.to_string()); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment