Parse a stylesheet into rules and declarations with source ranges, walk nested @media rules, and change a declaration or add a rule without reformatting the rest of the file.
Stylesheet.Parse implements CSS Syntax Level 3. Malformed CSS never throws: invalid parts become Invalid nodes. Only the hostile-input size limits throw (CssLimitExceededException). Every node keeps its source range, so you can read the original text (PreludeText, ValueText, Raw) as well as the parsed component values. This example uses the same site.css as Compute CSS styles:
body { font-family: Georgia, serif; color: #222 } .card h2 { font-size: 1.5rem } nav ul { display: flex; gap: 1rem } @media (max-width: 600px) { nav ul { display: block } }
using RecursiveAI.OneHtml.Css; using RecursiveAI.OneHtml.Editing; string css = File.ReadAllText("site.css"); Stylesheet sheet = Stylesheet.Parse(css, "https://library.example/css/site.css"); void Print(CssNode rule, string indent) { if (rule.Kind == CssNodeKind.AtRule) { Console.WriteLine($"{indent}@{rule.Name} {rule.PreludeText.ToString().Trim()}"); foreach (CssNode child in rule.Rules) { Print(child, indent + " "); } return; } Console.WriteLine($"{indent}{rule.PreludeText.ToString().Trim()}"); foreach (CssNode declaration in rule.Declarations) { Console.WriteLine($"{indent} {declaration.Name}: {declaration.ValueText.ToString().Trim()}{(declaration.IsImportant ? " !important" : "")}"); } } foreach (CssNode rule in sheet.Rules) { Print(rule, ""); } // Edit the stylesheet the same lossless way as HTML: only the touched declaration changes CssNode cardHeading = sheet.Rules.First(r => r.PreludeText.ToString().Trim() == ".card h2"); BatchResult result = StylesheetEditing.Apply(sheet, new[] { SheetEdit.SetDeclaration(sheet, cardHeading, "font-size", "1.75rem"), SheetEdit.AppendRule(sheet, ".card h2 a { text-decoration: none }"), }); Console.WriteLine($"Succeeded: {result.Succeeded}"); Console.Write(result.Sheets.Single().NewSheet.ToSourceText());
body font-family: Georgia, serif color: #222 .card h2 font-size: 1.5rem nav ul display: flex gap: 1rem @media (max-width: 600px) nav ul display: block Succeeded: True body { font-family: Georgia, serif; color: #222 } .card h2 { font-size: 1.75rem } nav ul { display: flex; gap: 1rem } @media (max-width: 600px) { nav ul { display: block } } .card h2 a { text-decoration: none }
Notes:
sheet.Ruleslists the top-level rules. A rule’sKindisQualifiedRule(a style rule) orAtRule. For at-rules,Nameis the at-keyword andAtRuleKindsays which one (Media,Supports,Layer,Importand others).RulesandDeclarationsgive the children.ToSourceText()returns the sheet’s text with its original formatting.Serialize()writes the CSSOM serialization instead.StylesheetEditing.Applyedits the way HTML edits do. It splices only the touched declaration or rule, copies the file’s spacing and indentation for new rules, and parses the result again to verify it. The other operations areRemoveDeclaration,InsertRuleBefore,InsertRuleAfter,ReplaceRuleandRemoveRule.- To edit a
<style>element or a linked sheet together with the page,InlineStylesheets.Of(doc)returns the page’s<style>sheets,LinkedStylesheet.Create(url, bytes)wraps a linked one, andnew EditBatch(doc, edits, sheetEdits, linkedSheets).Apply()runs HTML and CSS edits as one transaction. - Inline
style=""attributes are edited withEdit.SetStyleandEdit.RemoveStyleon the element (see Edit HTML without reformatting it).
Parsing details are in CSS syntax, and how stylesheet edits are written and verified is at the end of Editing.