Skip to content

Commit

Permalink
Get style for nodes (#15)
Browse files Browse the repository at this point in the history
* chore(parser): add fork parser scraper

* chore(scraper): add style parsing scraper

* chore(styles): add parsed styles

* chore(innate): add parse inline css if sheet empty

* chore(audit): add auditor base
  • Loading branch information
j-mendez authored Oct 6, 2023
1 parent d7aaddf commit 000181f
Show file tree
Hide file tree
Showing 14 changed files with 993 additions and 391 deletions.
677 changes: 574 additions & 103 deletions kayle_innate/Cargo.lock

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions kayle_innate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@ wee_alloc = { version = "0.4.5", optional = true }
url = "2.4.0"
lazy_static = "1.4.0"
case_insensitive_string = "0.1.0"
scraper = { version = "0.17.1", features = ["main"], default-features = false }
scraper_forky = { version = "0.17.1", features = ["main"], default-features = false }
getrandom = { version = "0.2", features = ["js"] }
taffy = { version = "0.3.13", optional = true }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
js-sys = "0.3.64"
selectors = "0.25.0"
smallvec = "1.11.1"
selectors = "=0.21.0"
smallvec = "0.6"
ego-tree = "0.6.2"
victor_tree = { version = "0.0.7" }
markup5ever = "0.11.0"

[dependencies.cssparser]
version = "^0.31.0"
version = "^0.25.0"

[dev-dependencies]
wasm-bindgen-test = "0.3.37"
Expand Down
65 changes: 65 additions & 0 deletions kayle_innate/src/engine/audit/auditor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use scraper_forky::ElementRef;
use scraper_forky::Html;
use victor_tree::style::StyleSet;

use super::tree::parse_accessibility_tree;

/// the intro to an audit
pub struct Auditor<'a> {
/// the html document
pub document: &'a Html,
/// the tree to map to nodes
pub tree: std::collections::BTreeMap<&'a str, Vec<ElementRef<'a>>>,
/// styles for the audit
pub author: StyleSet,
// /// the matching context for css selectors
pub match_context: selectors::matching::MatchingContext<'a, scraper_forky::selector::Simple>,
}

impl<'a> Auditor<'a> {
pub fn new(
document: &'a Html,
css_rules: &str,
match_context: selectors::matching::MatchingContext<'a, scraper_forky::selector::Simple>,
) -> Auditor<'a> {
use crate::{console_log, now};
let t = now();
let tree = parse_accessibility_tree(&document);
console_log!("Tree Build Time {:?}", now() - t);
let tt = now();

// TODO: make stylesheet building optional and only on first requirement
let author = {
let mut author = victor_tree::style::StyleSetBuilder::new();
if !css_rules.is_empty() {
author.add_stylesheet(css_rules);
} else {
use markup5ever::local_name;
match tree.get("style") {
Some(styles) => {
for node in styles {
// https://html.spec.whatwg.org/multipage/semantics.html#update-a-style-block
if let Some(type_attr) = node.attr(&local_name!("type")) {
if !type_attr.eq_ignore_ascii_case("text/css") {
continue;
}
author.add_stylesheet(&node.inner_html())
}
}
}
_ => (),
}
}
author.finish()
};

console_log!("StyleSheets Build Time {:?}", now() - tt);

Auditor {
document,
tree,
author,
match_context,
}
}
}
4 changes: 4 additions & 0 deletions kayle_innate/src/engine/audit/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
/// the auditor
pub mod auditor;
/// the node tree
pub mod tree;
/// WCAG audit
pub mod wcag;
74 changes: 74 additions & 0 deletions kayle_innate/src/engine/audit/tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use scraper_forky::ElementRef;
use std::collections::BTreeMap;

/// try to fix all possible issues using a spec against the tree.
pub fn parse_accessibility_tree(
html: &scraper_forky::Html,
// todo: return the nodes with a tuple of the layout node and the element node
) -> std::collections::BTreeMap<&str, Vec<ElementRef<'_>>> {
// use taffy::prelude::*;
// // todo: use optional variable for clips or layout creation
// let mut taffy = Taffy::new();

// let header_node = taffy
// .new_leaf(Style {
// size: Size {
// width: points(800.0),
// height: points(100.0),
// },
// ..Default::default()
// })
// .unwrap();

// let body_node = taffy
// .new_leaf(Style {
// size: Size {
// width: points(800.0),
// height: auto(),
// },
// flex_grow: 1.0,
// ..Default::default()
// })
// .unwrap();

// let root_node = taffy
// .new_with_children(
// Style {
// flex_direction: FlexDirection::Column,
// size: Size {
// width: points(800.0),
// height: points(600.0),
// },
// ..Default::default()
// },
// &[header_node, body_node],
// )
// .unwrap();

// // Call compute_layout on the root of your tree to run the layout algorithm
// taffy.compute_layout(root_node, Size::MAX_CONTENT).unwrap();
// console_log!("Header Layout {:?}", taffy.layout(header_node).unwrap());
// We can get the x,y, and height, width of the element on proper tree insert

// parse doc will start from html downwards
// accessibility tree for ordered element mappings
let mut accessibility_tree: BTreeMap<&str, Vec<ElementRef<'_>>> =
BTreeMap::from([("title".into(), Default::default())]);

for node in html.tree.nodes() {
match scraper_forky::element_ref::ElementRef::wrap(node) {
Some(element) => {
accessibility_tree
.entry(element.value().name())
.and_modify(|n| n.push(element))
.or_insert(Vec::from([element]));
}
_ => (),
};
}

// console_log!("Getting tree links {:?}", accessibility_tree.get("a"));
// console_log!("Tree {:?}", accessibility_tree);

accessibility_tree
}
10 changes: 4 additions & 6 deletions kayle_innate/src/engine/audit/wcag.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::engine::rules::wcag_rule_map::RULES_A;
use crate::i18n::locales::{get_message, Langs};
use crate::Auditor;
use crate::{console_log, engine::issue::Issue};
use scraper::ElementRef;

/// baseline for all rules
#[derive(Default)]
Expand All @@ -12,21 +12,19 @@ impl WCAG3AA {
/// init the rules
pub fn audit(
// allow tree mutation until threads or setup the tree with initial elements.
tree: &std::collections::BTreeMap<&str, Vec<ElementRef<'_>>>,
_css: cssparser::Parser<'_, '_>,
auditor: &Auditor<'_>,
// todo: get configs like viewport
) -> Vec<Issue> {
let mut issues: Vec<Issue> = Vec::new();

// go through nodes and map to validation rules
for node in tree {
for node in &auditor.tree {
if RULES_A.contains_key(&*node.0) {
let rules = RULES_A.get(&*node.0);
match rules {
Some(rules) => {
for rule in rules {
let (valid, section, selector) =
(rule.validate)(&node.0, &node.1, &_css);
let (valid, section, selector) = (rule.validate)(&node.0, &node.1);

if !valid {
// get locales prior or from document
Expand Down
12 changes: 2 additions & 10 deletions kayle_innate/src/engine/rules/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ pub struct Rule {
/// the type of rule
pub criteria: Criteria,
/// validate a test returns (valid, rule, selectors)
pub validate: fn(
&str,
&Vec<ElementRef<'_>>,
css: &cssparser::Parser<'_, '_>,
) -> (bool, &'static str, Vec<&'static str>),
pub validate: fn(&str, &Vec<ElementRef<'_>>) -> (bool, &'static str, Vec<&'static str>),
/// the principle type
pub principle: Principle,
/// the guideline to follow
Expand All @@ -27,11 +23,7 @@ impl Rule {
criteria: Criteria,
principle: Principle,
guideline: Guideline,
validate: fn(
&str,
&Vec<ElementRef<'_>>,
&cssparser::Parser<'_, '_>,
) -> (bool, &'static str, Vec<&'static str>),
validate: fn(&str, &Vec<ElementRef<'_>>) -> (bool, &'static str, Vec<&'static str>),
) -> Rule {
Rule {
rule_id,
Expand Down
8 changes: 4 additions & 4 deletions kayle_innate/src/engine/rules/wcag_rule_map.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::engine::rules::ids::Techniques;
use crate::engine::rules::rule::Rule;
use crate::engine::rules::wcag_base::{Criteria, Guideline, Principle};
use scraper::Selector;
use scraper_forky::Selector;
use std::collections::BTreeMap;

// todo: validate each element and add a shape that can prevent repitiion
Expand All @@ -11,16 +11,16 @@ lazy_static! {
vec![
// empty titles
("title", Vec::from([
Rule::new(Techniques::H25, Criteria::Error, Principle::Operable, Guideline::Navigable, |_rule, nodes, _css_parser| {
Rule::new(Techniques::H25, Criteria::Error, Principle::Operable, Guideline::Navigable, |_rule, nodes| {
(!nodes.is_empty(), "1.NoTitleEl", Default::default())
}),
Rule::new(Techniques::H25, Criteria::Error, Principle::Understandable, Guideline::Predictable, |_rule, nodes, _css_parser| {
Rule::new(Techniques::H25, Criteria::Error, Principle::Understandable, Guideline::Predictable, |_rule, nodes| {
(nodes.is_empty() || nodes[0].html().is_empty(), "2", Default::default())
}),
])),
// missing label
("form", Vec::from([
Rule::new(Techniques::H32, Criteria::Error, Principle::Operable, Guideline::Predictable, |_rule, nodes, _css_parser| {
Rule::new(Techniques::H32, Criteria::Error, Principle::Operable, Guideline::Predictable, |_rule, nodes| {
// check the first element for now
let mut valid = false;
let selector = unsafe { Selector::parse("button[type=submit]").unwrap_unchecked() };
Expand Down
11 changes: 11 additions & 0 deletions kayle_innate/src/engine/styles/css_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// build matching context
pub fn build_matching_context<'a>(
nth_index_cache: &'a mut selectors::NthIndexCache,
) -> selectors::matching::MatchingContext<'a, scraper_forky::selector::Simple> {
selectors::matching::MatchingContext::new(
selectors::matching::MatchingMode::Normal,
None,
Some(nth_index_cache),
selectors::matching::QuirksMode::NoQuirks,
)
}
12 changes: 6 additions & 6 deletions kayle_innate/src/engine/styles/errors.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use cssparser::{CowRcStr, ParseError};
// use cssparser::{CowRcStr, ParseError};
use selectors::parser::SelectorParseErrorKind;

pub type PropertyParseError<'i> = ParseError<'i, PropertyParseErrorKind<'i>>;
// pub type PropertyParseError<'i> = ParseError<'i, PropertyParseErrorKind<'i>>;

pub enum PropertyParseErrorKind<'i> {
UnknownProperty(CowRcStr<'i>),
UnknownUnit(CowRcStr<'i>),
}
// pub enum PropertyParseErrorKind<'i> {
// UnknownProperty(CowRcStr<'i>),
// UnknownUnit(CowRcStr<'i>),
// }

pub enum RuleParseErrorKind<'i> {
Selector(SelectorParseErrorKind<'i>),
Expand Down
88 changes: 88 additions & 0 deletions kayle_innate/src/engine/styles/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,90 @@
pub mod css_cache;
pub mod errors;
pub mod rules;

use crate::console_log;
use cssparser::{Parser, ParserInput};
use markup5ever::local_name;
use markup5ever::namespace_url;
use markup5ever::ns;
use scraper_forky::selector::Simple;
use scraper_forky::{ElementRef, Html};
use selectors::matching::MatchingContext;
use std::sync::Arc;
use victor_tree::style::cascade::USER_AGENT_STYLESHEET;
use victor_tree::style::declaration_block::DeclarationBlock;
use victor_tree::style::values::{Direction, WritingMode};
use victor_tree::style::{ComputedValues, StyleSet};

/// get the style for an element
pub fn style_for_element<'a>(
author: &StyleSet,
_document: &Html,
node: ElementRef<'a>,
parent_style: Option<&ComputedValues>,
match_context: &mut MatchingContext<'_, Simple>,
) -> Arc<ComputedValues> {
use smallvec::SmallVec;
let style_attr_block;
let mut matching = victor_tree::style::cascade::MatchingDeclarations {
ua: SmallVec::new(),
author: SmallVec::new(),
};

// let mut nth_index_cache = selectors::NthIndexCache::from(Default::default());
// let mut match_context = selectors::matching::MatchingContext::new(
// selectors::matching::MatchingMode::Normal,
// None,
// Some(&mut nth_index_cache),
// selectors::matching::QuirksMode::NoQuirks,
// // selectors::matching::NeedsSelectorFlags::No,
// // selectors::matching::IgnoreNthChildForInvalidation::No,
// );

for &(ref selector, ref block) in &USER_AGENT_STYLESHEET.rules {
if selectors::matching::matches_selector(
selector,
0,
None,
&node,
match_context,
&mut |_, _| {},
) {
matching.ua.push(block)
}
}

// push author style sheet
for &(ref selector, ref block) in &author.rules {
if selectors::matching::matches_selector(
selector,
0,
None,
&node,
match_context,
&mut |_, _| {},
) {
matching.author.push(block)
}
}

if let ns!(html) | ns!(svg) | ns!(mathml) = node.value().name.ns {
if let Some(style_attr) = node.value().attr(&local_name!("style")) {
let mut input = ParserInput::new(style_attr);
let mut parser = Parser::new(&mut input);
style_attr_block = DeclarationBlock::parse(&mut parser);
matching.author.push(&style_attr_block);
}
}

let styles = ComputedValues::new(parent_style, Some(&matching));

console_log!(
"{:?}",
styles
.box_size()
.size_to_physical((WritingMode::SidewaysLr, Direction::Ltr))
);

styles
}
Loading

0 comments on commit 000181f

Please sign in to comment.