From ba1b346455a6da788401106912af1768b2a5209c Mon Sep 17 00:00:00 2001 From: Bastien Dejean Date: Mon, 2 Aug 2021 09:56:37 +0200 Subject: [PATCH] Support all the combinators Fixes #182. --- css/toc.css | 2 +- src/document/epub/mod.rs | 251 ++++++++-------- src/document/html/css.rs | 470 ++++++++++++++++++++--------- src/document/html/dom.rs | 578 ++++++++++++++++++++++++++++-------- src/document/html/engine.rs | 193 ++++++------ src/document/html/layout.rs | 9 +- src/document/html/mod.rs | 92 +++--- src/document/html/style.rs | 215 ++++++++++---- src/document/html/xml.rs | 72 +++-- src/document/mod.rs | 6 +- 10 files changed, 1228 insertions(+), 660 deletions(-) diff --git a/css/toc.css b/css/toc.css index 9a11419..df4f4d6 100644 --- a/css/toc.css +++ b/css/toc.css @@ -12,7 +12,7 @@ ul > li { padding-top: 0.5em; } -ul.top > li { +body > ul > li { padding-top: 1em; } diff --git a/src/document/epub/mod.rs b/src/document/epub/mod.rs index cbcc550..2174019 100644 --- a/src/document/epub/mod.rs +++ b/src/document/epub/mod.rs @@ -1,7 +1,6 @@ use std::io::Read; use std::fs::{self, File}; use std::path::{Path, PathBuf}; -use std::borrow::Cow; use std::collections::BTreeSet; use fxhash::FxHashMap; use zip::ZipArchive; @@ -13,12 +12,13 @@ use crate::document::{Document, Location, TextLocation, TocEntry, BoundedText, c use crate::unit::pt_to_px; use crate::geom::{Rectangle, Edge, CycleDir}; use super::pdf::PdfOpener; -use super::html::dom::Node; +use super::html::dom::{XmlTree, NodeRef}; use super::html::engine::{Page, Engine, ResourceFetcher}; use super::html::layout::{StyleData, LoopContext}; use super::html::layout::{RootData, DrawState, DrawCommand, TextCommand, ImageCommand}; use super::html::layout::TextAlign; -use super::html::css::{CssParser, RuleKind}; +use super::html::style::StyleSheet; +use super::html::css::CssParser; use super::html::xml::XmlParser; const VIEWER_STYLESHEET: &str = "css/epub.css"; @@ -37,7 +37,7 @@ impl ResourceFetcher for ZipArchive { pub struct EpubDocument { archive: ZipArchive, - info: Node, + info: XmlTree, parent: PathBuf, engine: Engine, spine: Vec, @@ -64,8 +64,8 @@ impl EpubDocument { let mut text = String::new(); zf.read_to_string(&mut text)?; let root = XmlParser::new(&text).parse(); - root.find("rootfile") - .and_then(|e| e.attr("full-path")) + root.root().find("rootfile") + .and_then(|e| e.attribute("full-path")) .map(String::from) }.ok_or_else(|| format_err!("can't get the OPF path"))?; @@ -83,18 +83,17 @@ impl EpubDocument { let mut spine = Vec::new(); { - let manifest = info.find("manifest") - .ok_or_else(|| format_err!("the manifest is missing"))?; + let manifest = info.root().find("manifest") + .ok_or_else(|| format_err!("the manifest is missing"))?; - let children = info.find("spine") - .and_then(Node::children) - .ok_or_else(|| format_err!("the spine is missing"))?; + let spn = info.root().find("spine") + .ok_or_else(|| format_err!("the spine is missing"))?; - for child in children { - let vertebra_opt = child.attr("idref").and_then(|idref| { + for child in spn.children() { + let vertebra_opt = child.attribute("idref").and_then(|idref| { manifest.find_by_id(idref) }).and_then(|entry| { - entry.attr("href") + entry.attribute("href") }).and_then(|href| { let href = decode_entities(href); let href = percent_decode_str(&href).decode_utf8_lossy(); @@ -187,46 +186,44 @@ impl EpubDocument { rect![0, 0, width as i32, height as i32] } - fn walk_toc(&mut self, node: &Node, toc_dir: &Path, index: &mut usize, cache: &mut UriCache) -> Vec { + fn walk_toc(&mut self, node: NodeRef, toc_dir: &Path, index: &mut usize, cache: &mut UriCache) -> Vec { let mut entries = Vec::new(); // TODO: Take `playOrder` into account? - if let Some(children) = node.children() { - for child in children { - if child.tag_name() == Some("navPoint") { - let title = child.find("navLabel").and_then(|label| { - label.find("text") - }).and_then(|text| { - text.text().map(decode_entities).map(Cow::into_owned) - }).unwrap_or_default(); - - // Example URI: pr03.html#codecomma_and_what_to_do_with_it - let rel_uri = child.find("content").and_then(|content| { - content.attr("src") - .map(|src| percent_decode_str(&decode_entities(src)).decode_utf8_lossy() - .into_owned()) - }).unwrap_or_default(); - - let loc = toc_dir.join(&rel_uri).normalize().to_str() - .map(|uri| Location::Uri(uri.to_string())); - - let current_index = *index; - *index += 1; - - let sub_entries = if child.children().map(|c| c.len() > 2) == Some(true) { - self.walk_toc(child, toc_dir, index, cache) - } else { - Vec::new() - }; - - if let Some(location) = loc { - entries.push(TocEntry { - title, - location, - index: current_index, - children: sub_entries, - }); - } + for child in node.children() { + if child.tag_name() == Some("navPoint") { + let title = child.find("navLabel").and_then(|label| { + label.find("text") + }).map(|text| { + decode_entities(&text.text()).into_owned() + }).unwrap_or_default(); + + // Example URI: pr03.html#codecomma_and_what_to_do_with_it + let rel_uri = child.find("content").and_then(|content| { + content.attribute("src") + .map(|src| percent_decode_str(&decode_entities(src)).decode_utf8_lossy() + .into_owned()) + }).unwrap_or_default(); + + let loc = toc_dir.join(&rel_uri).normalize().to_str() + .map(|uri| Location::Uri(uri.to_string())); + + let current_index = *index; + *index += 1; + + let sub_entries = if child.children().count() > 2 { + self.walk_toc(child, toc_dir, index, cache) + } else { + Vec::new() + }; + + if let Some(location) = loc { + entries.push(TocEntry { + title, + location, + index: current_index, + children: sub_entries, + }); } } } @@ -270,7 +267,7 @@ impl EpubDocument { zf.read_to_string(&mut text).ok()?; } let root = XmlParser::new(&text).parse(); - self.cache_uris(&root, name, start_offset, cache); + self.cache_uris(root.root(), name, start_offset, cache); cache.get(uri).cloned() } else { let page_index = self.page_index(start_offset, index, start_offset)?; @@ -282,15 +279,13 @@ impl EpubDocument { } } - fn cache_uris(&mut self, node: &Node, name: &str, start_offset: usize, cache: &mut UriCache) { - if let Some(id) = node.attr("id") { + fn cache_uris(&mut self, node: NodeRef, name: &str, start_offset: usize, cache: &mut UriCache) { + if let Some(id) = node.attribute("id") { let location = start_offset + node.offset(); cache.insert(format!("{}#{}", name, id), location); } - if let Some(children) = node.children() { - for child in children { - self.cache_uris(child, name, start_offset, cache); - } + for child in node.children() { + self.cache_uris(child, name, start_offset, cache); } } @@ -331,53 +326,52 @@ impl EpubDocument { let mut root = XmlParser::new(&text).parse(); root.wrap_lost_inlines(); - let mut stylesheet = Vec::new(); + let mut stylesheet = StyleSheet::new(); if let Ok(text) = fs::read_to_string(VIEWER_STYLESHEET) { - let (mut css, _) = CssParser::new(&text).parse(RuleKind::Viewer); - stylesheet.append(&mut css); + let mut css = CssParser::new(&text).parse(); + stylesheet.append(&mut css, true); } if let Ok(text) = fs::read_to_string(USER_STYLESHEET) { - let (mut css, _) = CssParser::new(&text).parse(RuleKind::User); - stylesheet.append(&mut css); + let mut css = CssParser::new(&text).parse(); + stylesheet.append(&mut css, true); } if !self.ignore_document_css { - if let Some(head) = root.find("head") { - if let Some(children) = head.children() { - for child in children { - if child.tag_name() == Some("link") && child.attr("rel") == Some("stylesheet") { - if let Some(href) = child.attr("href") { - if let Some(name) = spine_dir.join(href).normalize().to_str() { - let mut text = String::new(); - if let Ok(mut zf) = self.archive.by_name(name) { - zf.read_to_string(&mut text).ok(); - let (mut css, _) = CssParser::new(&text).parse(RuleKind::Document); - stylesheet.append(&mut css); - } + let mut inner_css = StyleSheet::new(); + if let Some(head) = root.root().find("head") { + for child in head.children() { + if child.tag_name() == Some("link") && child.attribute("rel") == Some("stylesheet") { + if let Some(href) = child.attribute("href") { + if let Some(name) = spine_dir.join(href).normalize().to_str() { + let mut text = String::new(); + if let Ok(mut zf) = self.archive.by_name(name) { + zf.read_to_string(&mut text).ok(); + let mut css = CssParser::new(&text).parse(); + inner_css.append(&mut css, false); } } - } else if child.tag_name() == Some("style") && child.attr("type") == Some("text/css") { - if let Some(text) = child.text() { - let (mut css, _) = CssParser::new(text).parse(RuleKind::Document); - stylesheet.append(&mut css); - } } + } else if child.tag_name() == Some("style") && child.attribute("type") == Some("text/css") { + let mut css = CssParser::new(&child.text()).parse(); + inner_css.append(&mut css, false); } } } + + stylesheet.append(&mut inner_css, true); } let mut display_list = Vec::new(); - if let Some(body) = root.find("body").as_mut() { + if let Some(body) = root.root().find("body") { let mut rect = self.engine.rect(); rect.shrink(&self.engine.margin); let language = self.language().or_else(|| { - root.find("html") - .and_then(|html| html.attr("xml:lang")) + root.root().find("html") + .and_then(|html| html.attribute("xml:lang")) .map(String::from) }); @@ -420,23 +414,25 @@ impl EpubDocument { pub fn categories(&self) -> BTreeSet { let mut result = BTreeSet::new(); - if let Some(children) = self.info.find("metadata").and_then(Node::children) { - for child in children { + + if let Some(md) = self.info.root().find("metadata") { + for child in md.children() { if child.tag_qualified_name() == Some("dc:subject") { - if let Some(subject) = child.text().map(|text| decode_entities(text)) { - // Pipe separated list of BISAC categories - if subject.contains(" / ") { - for categ in subject.split('|') { - let start_index = if let Some(index) = categ.find(" - ") { index+3 } else { 0 }; - result.insert(categ[start_index..].trim().replace(" / ", ".")); - } - } else { - result.insert(subject.into_owned()); + let text = child.text(); + let subject = decode_entities(&text); + // Pipe separated list of BISAC categories + if subject.contains(" / ") { + for categ in subject.split('|') { + let start_index = if let Some(index) = categ.find(" - ") { index+3 } else { 0 }; + result.insert(categ[start_index..].trim().replace(" / ", ".")); } + } else { + result.insert(subject.into_owned()); } } } } + result } @@ -510,22 +506,21 @@ impl EpubDocument { } pub fn series(&self) -> Option<(String, String)> { - self.info.find("metadata") - .and_then(Node::children) - .and_then(|children| { + self.info.root().find("metadata") + .and_then(|md| { let mut title = None; let mut index = None; - for child in children { + for child in md.children() { if child.tag_name() == Some("meta") { - if child.attr("name") == Some("calibre:series") { - title = child.attr("content").map(|s| decode_entities(s).into_owned()); - } else if child.attr("name") == Some("calibre:series_index") { - index = child.attr("content").map(|s| decode_entities(s).into_owned()); - } else if child.attr("property") == Some("belongs-to-collection") { - title = child.text().map(|text| decode_entities(text).into_owned()); - } else if child.attr("property") == Some("group-position") { - index = child.text().map(|text| decode_entities(text).into_owned()); + if child.attribute("name") == Some("calibre:series") { + title = child.attribute("content").map(|s| decode_entities(s).into_owned()); + } else if child.attribute("name") == Some("calibre:series_index") { + index = child.attribute("content").map(|s| decode_entities(s).into_owned()); + } else if child.attribute("property") == Some("belongs-to-collection") { + title = Some(decode_entities(&child.text()).into_owned()); + } else if child.attribute("property") == Some("group-position") { + index = Some(decode_entities(&child.text()).into_owned()); } } @@ -539,27 +534,25 @@ impl EpubDocument { } pub fn cover_image(&self) -> Option<&str> { - self.info.find("metadata") - .and_then(Node::children) - .and_then(|children| children.iter().find(|child| { + self.info.root().find("metadata") + .and_then(|md| md.children().find(|child| { child.tag_name() == Some("meta") && - child.attr("name") == Some("cover") + child.attribute("name") == Some("cover") })) - .and_then(|entry| entry.attr("content")) + .and_then(|entry| entry.attribute("content")) .and_then(|cover_id| { - self.info.find("manifest") + self.info.root().find("manifest") .and_then(|entry| entry.find_by_id(cover_id)) - .and_then(|entry| entry.attr("href")) + .and_then(|entry| entry.attribute("href")) }) .or_else(|| { - self.info.find("manifest") - .and_then(Node::children) - .and_then(|children| children.iter().find(|child| { - (child.attr("href").map_or(false, |hr| hr.contains("cover") || hr.contains("Cover")) || + self.info.root().find("manifest") + .and_then(|mf| mf.children().find(|child| { + (child.attribute("href").map_or(false, |hr| hr.contains("cover") || hr.contains("Cover")) || child.id().map_or(false, |id| id.contains("cover"))) && - child.attr("media-type").map_or(false, |mt| mt.starts_with("image/")) + child.attribute("media-type").map_or(false, |mt| mt.starts_with("image/")) })) - .and_then(|entry| entry.attr("href")) + .and_then(|entry| entry.attribute("href")) }) } @@ -615,12 +608,12 @@ impl Document for EpubDocument { } fn toc(&mut self) -> Option> { - let name = self.info.find("spine").and_then(|spine| { - spine.attr("toc") + let name = self.info.root().find("spine").and_then(|spine| { + spine.attribute("toc") }).and_then(|toc_id| { - self.info.find("manifest") + self.info.root().find("manifest") .and_then(|manifest| manifest.find_by_id(toc_id)) - .and_then(|entry| entry.attr("href")) + .and_then(|entry| entry.attribute("href")) }).map(|href| { self.parent.join(href).normalize() .to_string_lossy().into_owned() @@ -637,10 +630,10 @@ impl Document for EpubDocument { } let root = XmlParser::new(&text).parse(); - root.find("navMap").map(|map| { + root.root().find("navMap").map(|map| { let mut cache = FxHashMap::default(); let mut index = 0; - self.walk_toc(&map, &toc_dir, &mut index, &mut cache) + self.walk_toc(map, &toc_dir, &mut index, &mut cache) }) } @@ -867,11 +860,9 @@ impl Document for EpubDocument { } fn metadata(&self, key: &str) -> Option { - self.info.find("metadata") - .and_then(Node::children) - .and_then(|children| children.iter().find(|child| child.tag_qualified_name() == Some(key))) - .and_then(|child| child.children().and_then(|c| c.get(0))) - .and_then(|child| child.text().map(|s| decode_entities(s).into_owned())) + self.info.root().find("metadata") + .and_then(|md| md.children().find(|child| child.tag_qualified_name() == Some(key))) + .map(|child| decode_entities(&child.text()).into_owned()) } fn is_reflowable(&self) -> bool { diff --git a/src/document/html/css.rs b/src/document/html/css.rs index dba71b8..9984466 100644 --- a/src/document/html/css.rs +++ b/src/document/html/css.rs @@ -1,57 +1,100 @@ -use std::cmp::Reverse; use fxhash::FxHashSet; +use super::style::StyleSheet; #[derive(Debug, Clone)] -pub enum Selector { - Simple(SimpleSelector), - ParentChild(SimpleSelector, SimpleSelector), - Siblings(SimpleSelector, SimpleSelector), +pub struct Selector { + pub simple_selectors: Vec, + pub combinators: Vec, } #[derive(Debug, Clone)] pub struct SimpleSelector { pub tag_name: Option, pub classes: FxHashSet, + pub pseudo_classes: Vec, + pub attributes: Vec, pub id: Option, } +#[derive(Debug, Clone)] +pub struct Attribute { + pub name: String, + pub operator: AttributeOperator, +} -impl Default for SimpleSelector { - fn default() -> SimpleSelector { - SimpleSelector { tag_name: None, id: None, classes: FxHashSet::default() } - } +#[derive(Debug, Clone)] +pub enum PseudoClass { + FirstChild, + LastChild, } -pub type Specificity = [usize; 3]; +#[derive(Debug, Clone)] +pub enum AttributeOperator { + // `[attr]` + Exists, + // `[attr=value]` + Matches(String), + // `[attr~=value]` + Contains(String), + // `[attr|=value]` + StartsWith(String), +} -impl SimpleSelector { - // http://www.w3.org/TR/selectors/#specificity - pub fn specificity(&self) -> Specificity { - let a = self.id.iter().count(); - let b = self.classes.len(); - let c = self.tag_name.iter().count(); - [a, b, c] +#[derive(Debug, Copy, Clone)] +pub enum Combinator { + None, + // a > b + Child, + // a b + Descendant, + // a + b + NextSibling, + // a ~ b + SubsequentSibling, +} + +impl Default for Selector { + fn default() -> Self { + Selector { + simple_selectors: Vec::new(), + combinators: Vec::new(), + } } } +impl Default for SimpleSelector { + fn default() -> Self { + SimpleSelector { + tag_name: None, + classes: FxHashSet::default(), + pseudo_classes: Vec::new(), + attributes: Vec::new(), + id: None, + } + } +} + +pub type Specificity = [usize; 3]; + impl Selector { pub fn specificity(&self) -> Specificity { - match self { - Selector::Simple(sel) => sel.specificity(), - Selector::ParentChild(sel1, sel2) | - Selector::Siblings(sel1, sel2) => { - let s1 = sel1.specificity(); - let s2 = sel2.specificity(); - [s1[0] + s2[0], s1[1] + s2[1], s1[2] + s2[2]] - }, + let mut spec = [0usize; 3]; + for sel in &self.simple_selectors { + spec[0] = spec[0].saturating_add(sel.id.iter().count()); + spec[1] = spec[1].saturating_add(sel.classes.len()); + spec[1] = spec[1].saturating_add(sel.pseudo_classes.len()); + spec[1] = spec[1].saturating_add(sel.attributes.len()); + spec[2] = spec[2].saturating_add(sel.tag_name.iter().count()); } + spec } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Declaration { pub name: String, pub value: String, + pub important: bool, } impl Default for Declaration { @@ -59,24 +102,17 @@ impl Default for Declaration { Declaration { name: String::default(), value: String::default(), + important: false, } } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Rule { - pub kind: RuleKind, - pub selectors: Vec, + pub selector: Selector, pub declarations: Vec, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum RuleKind { - Viewer = 0, - User = 1, - Document = 2, -} - #[derive(Debug)] pub struct CssParser<'a> { input: &'a str, @@ -116,81 +152,246 @@ impl<'a> CssParser<'a> { } fn advance_until(&mut self, target: &str) { - if let Some(first) = target.chars().next() { - while !self.eof() { - self.advance(1); - self.advance_while(|&c| c != first); - if self.starts_with(target) { - break; - } + while !self.eof() && !self.starts_with(target) { + self.advance(1); + } + self.advance(target.chars().count()); + } + + fn skip_spaces_and_comments(&mut self) { + loop { + let offset = self.offset; + self.advance_while(|&c| c.is_whitespace()); + if self.starts_with("/*") { + self.advance(2); + self.advance_until("*/"); + } + if offset == self.offset { + break; + } + } + } + + fn skip_ident(&mut self) { + self.advance_while(|&c| c.is_ascii_alphanumeric() || + c == '-' || + c == '_'); + } + + fn skip_block(&mut self) { + let mut balance = 0u8; + + while !self.eof() { + match self.next() { + Some('{') => { + self.advance(1); + balance = balance.saturating_add(1); + }, + Some('}') => { + self.advance(1); + balance = balance.saturating_sub(1); + }, + _ => break, } - self.advance(target.chars().count()); + if balance == 0 { + break; + } + self.advance_while(|&c| c != '{' && c != '}') + } + } + + fn skip_at_rule(&mut self) { + self.advance_while(|&c| c != ';' && c != '{'); + + match self.next() { + Some(';') => self.advance(1), + Some('{') => self.skip_block(), + _ => (), + } + } + + fn attribute_value(&mut self) -> String { + match self.next() { + Some('"') => { + self.advance(1); + let start_offset = self.offset; + self.advance_while(|&c| c != '"'); + let end_offset = self.offset; + self.advance(1); + self.input[start_offset..end_offset].to_string() + }, + _ => { + let offset = self.offset; + self.skip_ident(); + self.input[offset..self.offset].to_string() + }, } } fn parse_selectors(&mut self) -> Vec { + let mut supported = true; let mut selectors = Vec::new(); - let mut simple_selectors = Vec::new(); - let mut sel = SimpleSelector::default(); - let mut combinator = None; - - self.advance_while(|&c| c.is_whitespace()); + let mut s = Selector::default(); + let mut selec = SimpleSelector::default(); + let mut comb = Combinator::None; while !self.eof() { match self.next() { Some('#') => { self.advance(1); let offset = self.offset; - self.advance_while(|&c| c.is_ascii_alphanumeric() || c == '-' || c == '_'); - sel.id = Some(self.input[offset..self.offset].to_string()); - + self.skip_ident(); + selec.id = Some(self.input[offset..self.offset].to_string()); }, Some('.') => { self.advance(1); let offset = self.offset; - self.advance_while(|&c| c.is_ascii_alphanumeric() || c == '-' || c == '_'); - sel.classes.insert(self.input[offset..self.offset].to_string()); + self.skip_ident(); + selec.classes.insert(self.input[offset..self.offset].to_string()); + }, + Some('[') => { + self.advance(1); + self.skip_spaces_and_comments(); + let offset = self.offset; + self.skip_ident(); + let name = self.input[offset..self.offset].to_string(); + self.skip_spaces_and_comments(); + match self.next() { + Some(']') => { + self.advance(1); + selec.attributes.push(Attribute { + name, + operator: AttributeOperator::Exists, + }); + }, + Some('=') => { + self.advance(1); + self.skip_spaces_and_comments(); + let value = self.attribute_value(); + selec.attributes.push(Attribute { + name, + operator: AttributeOperator::Matches(value), + }); + self.skip_spaces_and_comments(); + self.advance(1); + }, + Some('~') => { + self.advance(2); + self.skip_spaces_and_comments(); + let value = self.attribute_value(); + selec.attributes.push(Attribute { + name, + operator: AttributeOperator::Contains(value), + }); + self.skip_spaces_and_comments(); + self.advance(1); + }, + Some('|') => { + self.advance(2); + self.skip_spaces_and_comments(); + let value = self.attribute_value(); + selec.attributes.push(Attribute { + name, + operator: AttributeOperator::StartsWith(value), + }); + self.skip_spaces_and_comments(); + self.advance(1); + }, + _ => { + self.advance(2); + self.skip_spaces_and_comments(); + self.advance(1); + supported = false; + }, + } + + }, + Some(':') => { + self.advance(1); + if self.next() == Some(':') { + supported = false; + self.advance(1); + } + let offset = self.offset; + self.skip_ident(); + match &self.input[offset..self.offset] { + "first-child" => { + selec.pseudo_classes.push(PseudoClass::FirstChild); + }, + "last-child" => { + selec.pseudo_classes.push(PseudoClass::LastChild); + }, + _ => { + supported = false; + }, + } + if self.next() == Some('(') { + self.advance_while(|&c| c != ')'); + self.advance(1); + } + }, + Some('*') => { + self.advance(1); }, _ => { let offset = self.offset; - self.advance_while(|&c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '@'); + self.skip_ident(); if self.offset > offset { - sel.tag_name = Some(self.input[offset..self.offset].to_string()); + selec.tag_name = Some(self.input[offset..self.offset].to_string()); } else { - self.advance_while(|&c| c.is_whitespace()); - simple_selectors.push(sel); - sel = SimpleSelector::default(); - let next = self.next(); - self.advance(1); - if next == Some('>') || next == Some('+') { - combinator = next; - } else if next == Some('{') || next == Some(',') { - match combinator { - Some('>') if simple_selectors.len() == 2 => { - selectors.push(Selector::ParentChild(simple_selectors[0].clone(), - simple_selectors[1].clone())); - }, - Some('+') if simple_selectors.len() == 2 => { - selectors.push(Selector::Siblings(simple_selectors[0].clone(), - simple_selectors[1].clone())); - }, - None if simple_selectors.len() == 1 => { - selectors.push(Selector::Simple(simple_selectors[0].clone())); - }, - _ => (), - } - simple_selectors.clear(); - if next == Some('{') { + let offset = self.offset; + self.skip_spaces_and_comments(); + + s.simple_selectors.push(selec); + s.combinators.push(comb); + selec = SimpleSelector::default(); + + match self.next() { + Some(',') => { + self.advance(1); + self.skip_spaces_and_comments(); + if supported { + selectors.push(s); + } + s = Selector::default(); + comb = Combinator::None; + supported = true; + }, + Some('{') => { + self.advance(1); break; - } + }, + Some('>') => { + self.advance(1); + self.skip_spaces_and_comments(); + comb = Combinator::Child; + }, + Some('+') => { + self.advance(1); + self.skip_spaces_and_comments(); + comb = Combinator::NextSibling; + }, + Some('~') => { + self.advance(1); + self.skip_spaces_and_comments(); + comb = Combinator::SubsequentSibling; + }, + _ => { + if self.offset > offset { + comb = Combinator::Descendant; + } else { + self.advance(1); + } + }, } } - self.advance_while(|&c| c.is_whitespace()); } } } - selectors.sort_by_key(|a| Reverse(a.specificity())); + if supported { + selectors.push(s); + } selectors } @@ -200,24 +401,38 @@ impl<'a> CssParser<'a> { let mut d = Declaration::default(); while !self.eof() { - self.advance_while(|&c| c.is_whitespace()); + self.skip_spaces_and_comments(); match self.next() { Some(':') => { self.advance(1); + self.skip_spaces_and_comments(); let offset = self.offset; + while !self.eof() { - // TODO: Skip !important. - self.advance_while(|&c| c != '"' && c != ';' && c != '}'); - if let Some('"') = self.next() { - self.advance(1); - self.advance_while(|&c| c != '"'); - self.advance(1); - } else { - break; + self.advance_while(|&c| c != '"' && + c != ';' && + c != '}' && + c != '!'); + match self.next() { + Some('"') => { + self.advance(1); + self.advance_while(|&c| c != '"'); + self.advance(1); + }, + Some('!') => { + d.important = true; + break; + }, + _ => break, } } + d.value = self.input[offset..self.offset].trim().to_string(); + if d.important { + self.advance_while(|&c| c != ';' && + c != '}'); + } }, Some(';') => { self.advance(1); @@ -228,18 +443,14 @@ impl<'a> CssParser<'a> { self.advance(1); break; } - Some('/') => { - if self.starts_with("/*") { - self.advance(2); - self.advance_until("*/"); + _ => { + let offset = self.offset; + self.skip_ident(); + if self.offset > offset { + d.name = self.input[offset..self.offset].trim().to_string(); } else { self.advance(1); } - }, - _ => { - let offset = self.offset; - self.advance_while(|&c| c != ':'); - d.name = self.input[offset..self.offset].trim().to_string(); } } } @@ -251,56 +462,31 @@ impl<'a> CssParser<'a> { declarations } - fn parse_rule(&mut self, kind: RuleKind) -> Rule { + fn parse_rules(&mut self, rules: &mut Vec) { let selectors = self.parse_selectors(); let declarations = self.parse_declarations(); - Rule { kind, selectors, declarations } - } - - fn skip_nested_rules(&mut self) { - self.advance_while(|&c| c != '{'); - self.advance(1); - while !self.eof() { - self.parse_rule(RuleKind::Viewer); - self.advance_while(|&c| c.is_whitespace()); - if let Some('}') = self.next() { - self.advance(1); - break; - } + for selector in selectors.into_iter() { + rules.push(Rule { + selector, + declarations: declarations.clone(), + }); } } - pub fn parse(&mut self, kind: RuleKind) -> (Vec, Vec) { + pub fn parse(&mut self) -> StyleSheet { let mut rules = Vec::new(); - let mut at_rules = Vec::new(); + while !self.eof() { - self.advance_while(|&c| c.is_whitespace()); + self.skip_spaces_and_comments(); + match self.next() { None => break, - Some('/') => { - if self.starts_with("/*") { - self.advance(2); - self.advance_until("*/"); - } else { - self.advance(1); - } - }, - Some('@') => { - if self.starts_with("@namespace") || self.starts_with("@charset") { - self.advance_while(|&c| c != ';'); - self.advance(1); - continue; - } else if self.starts_with("@media") { - self.skip_nested_rules(); - continue; - } else { - at_rules.push(self.parse_rule(kind)); - } - }, - _ => rules.push(self.parse_rule(kind)), + Some('@') => self.skip_at_rule(), + _ => self.parse_rules(&mut rules), } } - (rules, at_rules) + + StyleSheet { rules } } } @@ -311,14 +497,14 @@ mod tests { #[test] fn simple_css() { let text = "a, .b { b: c; d: e }"; - let (css, _) = CssParser::new(text).parse(RuleKind::User); + let css = CssParser::new(text).parse(); println!("{:?}", css); } #[test] fn combinators_css() { let text = "a#i.j.k > b { b: c } a + .l { u: v } a { x: y }"; - let (css, _) = CssParser::new(text).parse(RuleKind::User); + let css = CssParser::new(text).parse(); println!("{:?}", css); } } diff --git a/src/document/html/dom.rs b/src/document/html/dom.rs index 18d0a50..191e958 100644 --- a/src/document/html/dom.rs +++ b/src/document/html/dom.rs @@ -1,9 +1,13 @@ +use std::num::NonZeroUsize; use fxhash::{FxHashMap, FxHashSet}; pub type Attributes = FxHashMap; +pub const WRAPPER_TAG_NAME: &str = "anonymous"; #[derive(Debug, Clone)] -pub enum Node { +pub enum NodeData { + Root, + Wrapper(usize), Element(ElementData), Text(TextData), Whitespace(TextData), @@ -15,7 +19,37 @@ pub struct ElementData { pub name: String, pub qualified_name: Option, pub attributes: Attributes, - pub children: Vec, +} + +impl ElementData { + fn is_block(&self) -> bool { + matches!(self.name.as_str(), + "address" | "article" | "aside" | "blockquote" | "body" | "head" | + "details" | "dialog" | "dd" | "div" | "dl" | "dt" | "fieldset" | "figcaption" | + "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "header" | + "hgroup" | "hr" | "html" | "li" | "main" | "nav" | "ol" | "p" | "pre" | "section" | + "table" | "thead" | "colgroup" | "tbody" | "tfoot" | "tr" | "caption" | "td" | "th" | "ul") + } +} + +impl NodeData { + fn text(&self) -> Option<&str> { + match *self { + NodeData::Text(TextData { ref text, .. }) | + NodeData::Whitespace(TextData { ref text, .. }) => Some(text), + _ => None, + } + } + + fn offset(&self) -> usize { + match *self { + NodeData::Text(TextData { offset, .. }) | + NodeData::Whitespace(TextData { offset, .. }) | + NodeData::Element(ElementData { offset, .. }) => offset, + NodeData::Wrapper(offset) => offset, + NodeData::Root => 0, + } + } } #[derive(Debug, Clone)] @@ -24,190 +58,488 @@ pub struct TextData { pub text: String, } -pub fn element(name: &str, offset: usize, attributes: Attributes, children: Vec) -> Node { +pub fn element(name: &str, offset: usize, attributes: Attributes) -> NodeData { let colon = name.find(':'); - Node::Element(ElementData { + NodeData::Element(ElementData { offset, name: name[colon.map(|index| index+1).unwrap_or(0)..].to_string(), qualified_name: colon.map(|_| name.to_string()), attributes, - children }) } -pub fn text(text: &str, offset: usize) -> Node { - Node::Text(TextData { +pub fn text(text: &str, offset: usize) -> NodeData { + NodeData::Text(TextData { offset, text: text.to_string(), }) } -pub fn whitespace(text: &str, offset: usize) -> Node { - Node::Whitespace(TextData { +pub fn whitespace(text: &str, offset: usize) -> NodeData { + NodeData::Whitespace(TextData { offset, text: text.to_string(), }) } -impl Node { - pub fn tag_name(&self) -> Option<&str> { - match *self { - Node::Element(ElementData { ref name, .. }) => Some(name), - _ => None, +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct NodeId(NonZeroUsize); + +impl NodeId { + pub fn from_index(n: usize) -> Self { + NodeId(unsafe { NonZeroUsize::new_unchecked(n + 1) }) + } + + pub fn to_index(self) -> usize { + self.0.get() - 1 + } +} + +#[derive(Debug, Clone)] +pub struct XmlTree { + nodes: Vec, +} + +#[derive(Debug, Clone)] +pub struct Node { + data: NodeData, + parent: Option, + previous_sibling: Option, + next_sibling: Option, + first_child: Option, + last_child: Option, +} + +impl Default for Node { + fn default() -> Self { + Node { + data: NodeData::Root, + parent: None, + previous_sibling: None, + next_sibling: None, + first_child: None, + last_child: None, } } +} - pub fn tag_qualified_name(&self) -> Option<&str> { - match *self { - Node::Element(ElementData { ref qualified_name, .. }) => qualified_name.as_deref(), - _ => None, +#[derive(Debug, Copy, Clone)] +pub struct NodeRef<'a> { + pub id: NodeId, + pub node: &'a Node, + pub tree: &'a XmlTree, +} + +#[derive(Debug)] +pub struct NodeMut<'a> { + pub id: NodeId, + pub tree: &'a mut XmlTree, +} + +impl XmlTree { + pub fn new() -> Self { + XmlTree { + nodes: vec![Node::default()], } } - pub fn children(&self) -> Option<&Vec> { - match *self { - Node::Element(ElementData { ref children, .. }) => Some(children), - _ => None, + fn node(&self, id: NodeId) -> &Node { + unsafe { self.nodes.get_unchecked(id.to_index()) } + } + + fn node_mut(&mut self, id: NodeId) -> &mut Node { + unsafe { self.nodes.get_unchecked_mut(id.to_index()) } + } + + pub fn get(&self, id: NodeId) -> NodeRef { + NodeRef { id, node: self.node(id), tree: self } + } + + pub fn get_mut(&mut self, id: NodeId) -> NodeMut { + NodeMut { id, tree: self } + } + + pub fn root(&self) -> NodeRef { + self.get(NodeId::from_index(0)) + } + + pub fn root_mut(&mut self) -> NodeMut { + self.get_mut(NodeId::from_index(0)) + } + + pub fn wrap_lost_inlines(&mut self) { + let mut ids = Vec::new(); + let mut known_ids = FxHashSet::default(); + + for n in self.root().descendants().filter(|n| n.is_inline()) { + if known_ids.contains(&n.id) { + continue; + } + + let mut first_id = None; + let mut last_id = None; + + for s in n.previous_siblings() { + if s.is_block() { + first_id = Some(s.next_sibling().unwrap().id); + break; + } else { + known_ids.insert(s.id); + } + } + + for s in n.next_siblings() { + if s.is_block() { + last_id = Some(s.previous_sibling().unwrap().id); + break; + } else { + known_ids.insert(s.id); + } + } + + if first_id.is_some() || last_id.is_some() { + let parent = n.parent().unwrap(); + ids.push([parent.id, + first_id.unwrap_or_else(|| parent.node.first_child.unwrap()), + last_id.unwrap_or_else(|| parent.node.last_child.unwrap())]); + } } + + for [parent_id, first_id, last_id] in ids { + let offset = self.node(first_id).data.offset(); + let mut node = self.get_mut(parent_id); + node.wrap_range(first_id, last_id, NodeData::Wrapper(offset)); + } + } +} + +impl<'a> NodeRef<'a> { + pub fn parent(&self) -> Option { + self.node.parent.map(|id| self.tree.get(id)) } - pub fn child(&self, index: usize) -> Option<&Node> { - self.children().and_then(|children| children.get(index)) + pub fn parent_element(&self) -> Option { + self.ancestors().find(|n| n.is_element() && !n.is_wrapper()) } - pub fn attributes(&self) -> Option<&Attributes> { - match *self { - Node::Element(ElementData { ref attributes, .. }) => Some(attributes), - _ => None, + pub fn previous_sibling(&self) -> Option { + self.node.previous_sibling.map(|id| self.tree.get(id)) + } + + pub fn previous_sibling_element(&self) -> Option> { + self.previous_sibling_elements().next() + } + + pub fn next_sibling_element(&self) -> Option> { + self.next_sibling_elements().next() + } + + pub fn next_sibling(&self) -> Option { + self.node.next_sibling.map(|id| self.tree.get(id)) + } + + pub fn first_child(&self) -> Option { + self.node.first_child.map(|id| self.tree.get(id)) + } + + pub fn last_child(&self) -> Option { + self.node.last_child.map(|id| self.tree.get(id)) + } + + pub fn ancestors(&self) -> Ancestors<'a> { + Ancestors { + next: self.parent(), } } - pub fn text(&self) -> Option<&str> { - match *self { - Node::Text(TextData { ref text, .. }) | - Node::Whitespace(TextData { ref text, .. }) => Some(text), - Node::Element(ElementData { ref children, .. }) if children.len() == 1 => { - children.first().and_then(Self::text) - }, - _ => None, + pub fn ancestor_elements(&self) -> impl Iterator> { + self.ancestors().filter(|n| n.is_element() && !n.is_wrapper()) + } + + pub fn previous_siblings(&self) -> PreviousSiblings<'a> { + PreviousSiblings { + next: self.previous_sibling(), } } - pub fn is_block(&self) -> bool { - match *self { - Node::Element(ElementData { ref name, .. }) => { - matches!(name.as_str(), - "address" | "anonymous" | "article" | "aside" | "blockquote" | "body" | "head" | - "details" | "dialog" | "dd" | "div" | "dl" | "dt" | "fieldset" | "figcaption" | - "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "header" | - "hgroup" | "hr" | "html" | "li" | "main" | "nav" | "ol" | "p" | "pre" | "section" | - "table" | "thead" | "colgroup" | "tbody" | "tfoot" | "tr" | "caption" | "td" | "th" | "ul") - }, - _ => false, + pub fn next_siblings(&self) -> NextSiblings<'a> { + NextSiblings { + next: self.next_sibling(), } } - pub fn is_whitespace(&self) -> bool { - matches!(*self, Node::Whitespace(..)) + pub fn previous_sibling_elements(&self) -> impl Iterator> { + self.previous_siblings().filter(|n| n.is_element()) } + pub fn next_sibling_elements(&self) -> impl Iterator> { + self.next_siblings().filter(|n| n.is_element()) + } + + pub fn children(&self) -> Children<'a> { + Children { + next: self.first_child(), + } + } + + pub fn descendants(&self) -> Descendants<'a> { + Descendants { + root_id: self.id, + next: self.first_child(), + } + } + + pub fn has_children(&self) -> bool { + self.node.first_child.is_some() + } pub fn is_element(&self) -> bool { - matches!(*self, Node::Element(..)) + matches!(self.node.data, NodeData::Element { .. } | + NodeData::Wrapper(..) | + NodeData::Root) + } + + pub fn is_inline(&self) -> bool { + match &self.node.data { + NodeData::Element(e) => !e.is_block(), + NodeData::Text(..) => true, + _ => false, + } } - pub fn attr(&self, name: &str) -> Option<&str> { - self.attributes().and_then(|a| a.get(name).map(String::as_str)) + pub fn is_block(&self) -> bool { + match &self.node.data { + NodeData::Element(e) => e.is_block(), + NodeData::Wrapper(..) | NodeData::Root => true, + _ => false, + } } - pub fn classes(&self) -> Option> { - self.attr("class").map(|t| t.split(' ').collect()) + pub fn is_wrapper(&self) -> bool { + matches!(self.node.data, NodeData::Wrapper(..)) } - pub fn id(&self) -> Option<&str> { - self.attr("id") + pub fn data(&self) -> &'a NodeData { + &self.node.data } pub fn offset(&self) -> usize { - match *self { - Node::Text(TextData { offset, .. }) | - Node::Whitespace(TextData { offset, .. }) | - Node::Element(ElementData { offset, .. }) => offset, + self.node.data.offset() + } + + pub fn text(&self) -> String { + self.node.data.text().map(String::from).unwrap_or_else(|| { + self.descendants() + .filter_map(|n| n.node.data.text()) + .fold(String::new(), |mut a, b| { + a.push_str(b); + a + }) + }) + } + + pub fn tag_name(&self) -> Option<&'a str> { + match self.node.data { + NodeData::Element(ElementData { ref name, .. }) => Some(name), + NodeData::Wrapper(..) => Some(WRAPPER_TAG_NAME), + _ => None, } } - pub fn find(&self, tag_name: &str) -> Option<&Node> { - match *self { - Node::Element(ElementData { ref name, ref children, .. }) => { - if tag_name == name { - Some(self) - } else { - for child in children { - let result = child.find(tag_name); - if result.is_some() { - return result; - } - } - None - } - }, + pub fn tag_qualified_name(&self) -> Option<&'a str> { + match self.node.data { + NodeData::Element(ElementData { ref qualified_name, .. }) => qualified_name.as_deref(), _ => None, } } - pub fn find_by_id(&self, value: &str) -> Option<&Node> { - match *self { - Node::Element(ElementData { ref attributes, ref children, .. }) => { - if attributes.get("id").map(|v| v == value).unwrap_or(false) { - Some(self) - } else { - for child in children { - let result = child.find_by_id(value); - if result.is_some() { - return result; - } - } - None - } - }, + pub fn attributes(&self) -> Option<&'a Attributes> { + match self.node.data { + NodeData::Element(ElementData { ref attributes, .. }) => Some(attributes), _ => None, } } - pub fn wrap_lost_inlines(&mut self) { - if let Node::Element(ElementData { ref mut children, .. }) = self { - if children.iter().any(Self::is_block) { - let mut start_index = None; - let mut end_index = None; - let mut i = 0; - while i < children.len() { - let is_block = children[i].is_block(); - let is_whitespace = children[i].is_whitespace(); - if !is_block { - if start_index.is_none() && !is_whitespace { - start_index = Some(i); - } else if start_index.is_some() { - end_index = Some(i); - } - } else { - children[i].wrap_lost_inlines(); - } - if (is_block || i == children.len() - 1) && start_index.is_some() { - let j = start_index.unwrap(); - let k = end_index.unwrap_or(j); - let n = k - j + 1; - let anon = [element("anonymous", children[j].offset(), FxHashMap::default(), Vec::new())]; - let inlines = children.splice(j..=k, anon.iter().cloned()).collect(); - if let Some(Node::Element(ElementData { ref mut children, .. })) = children.get_mut(j) { - *children = inlines; - } - start_index = None; - end_index = None; - i = i - n + 1; - } - i += 1; - } + pub fn attribute(&self, name: &str) -> Option<&'a str> { + self.attributes() + .and_then(|a| a.get(name).map(String::as_str)) + } + + pub fn classes(&self) -> impl Iterator { + self.attribute("class") + .unwrap_or("") + .split_whitespace() + } + + pub fn id(&self) -> Option<&str> { + self.attribute("id") + } + + pub fn find(&self, tag_name: &str) -> Option { + self.descendants() + .find(|n| n.tag_name() == Some(tag_name)) + } + + pub fn find_by_id(&self, id: &str) -> Option { + self.descendants() + .find(|n| n.id() == Some(id)) + } +} + +impl<'a> NodeMut<'a> { + fn node(&mut self) -> &mut Node { + self.tree.node_mut(self.id) + } + + pub fn append(&mut self, data: NodeData) -> NodeId { + let id = NodeId::from_index(self.tree.nodes.len()); + + let node = Node { + data, + parent: Some(self.id), + previous_sibling: self.node().last_child, + next_sibling: None, + first_child: None, + last_child: None, + }; + + self.tree.nodes.push(node); + + if let Some(last_child) = self.node().last_child { + self.tree.node_mut(last_child).next_sibling = Some(id); + } + + self.node().last_child = Some(id); + + if self.node().first_child.is_none() { + self.node().first_child = Some(id); + } + + id + } + + pub fn wrap_range(&mut self, first_id: NodeId, last_id: NodeId, data: NodeData) { + let before = self.tree.node(first_id).previous_sibling; + let after = self.tree.node(last_id).next_sibling; + let id = NodeId::from_index(self.tree.nodes.len()); + + let node = Node { + data, + parent: Some(self.id), + previous_sibling: before, + next_sibling: after, + first_child: Some(first_id), + last_child: Some(last_id), + }; + + self.tree.nodes.push(node); + + if let Some(before_id) = before { + self.tree.node_mut(before_id).next_sibling = Some(id); + } + + if let Some(after_id) = after { + self.tree.node_mut(after_id).previous_sibling = Some(id); + } + + if let Some(first_child_id) = self.node().first_child { + if first_child_id == first_id { + self.node().first_child = Some(id); + } + } + + if let Some(last_child_id) = self.node().last_child { + if last_child_id == last_id { + self.node().last_child = Some(id); } } + + self.tree.node_mut(first_id).previous_sibling = None; + self.tree.node_mut(last_id).next_sibling = None; + self.tree.node_mut(first_id).parent = Some(id); + + let mut node_id = first_id; + while let Some(next_id) = self.tree.node(node_id).next_sibling { + self.tree.node_mut(next_id).parent = Some(id); + node_id = next_id; + } + } +} + +pub struct Ancestors<'a> { + next: Option>, +} + +pub struct NextSiblings<'a> { + next: Option>, +} + +pub struct PreviousSiblings<'a> { + next: Option>, +} + +pub struct Children<'a> { + next: Option>, +} + +pub struct Descendants<'a> { + root_id: NodeId, + next: Option>, +} + +impl<'a> Iterator for Ancestors<'a> { + type Item = NodeRef<'a>; + + fn next(&mut self) -> Option { + let node = self.next.take(); + self.next = node.as_ref().and_then(|node| node.parent()); + node + } +} + +impl<'a> Iterator for PreviousSiblings<'a> { + type Item = NodeRef<'a>; + + fn next(&mut self) -> Option { + let node = self.next.take(); + self.next = node.as_ref().and_then(|node| node.previous_sibling()); + node + } +} + +impl<'a> Iterator for NextSiblings<'a> { + type Item = NodeRef<'a>; + + fn next(&mut self) -> Option { + let node = self.next.take(); + self.next = node.as_ref().and_then(|node| node.next_sibling()); + node + } +} + +impl<'a> Iterator for Children<'a> { + type Item = NodeRef<'a>; + + fn next(&mut self) -> Option { + let node = self.next.take(); + self.next = node.as_ref().and_then(|node| node.next_sibling()); + node + } +} + +impl<'a> Iterator for Descendants<'a> { + type Item = NodeRef<'a>; + + fn next(&mut self) -> Option { + let node = self.next.take(); + if let Some(node) = node { + self.next = node.first_child() + .or_else(|| node.next_sibling()) + .or_else(|| node.ancestors() + .take_while(|n| n.id != self.root_id) + .find(|n| n.node.next_sibling.is_some()) + .and_then(|n| n.next_sibling())); + } + node } } diff --git a/src/document/html/engine.rs b/src/document/html/engine.rs index d278783..548e7b8 100644 --- a/src/document/html/engine.rs +++ b/src/document/html/engine.rs @@ -20,7 +20,7 @@ use super::parse::{parse_width, parse_height, parse_inline_material, parse_font_ use super::parse::{parse_font_weight, parse_font_size, parse_font_features, parse_font_variant}; use super::parse::{parse_letter_spacing, parse_word_spacing}; use super::parse::{parse_line_height, parse_vertical_align, parse_color, parse_list_style_type}; -use super::dom::{Node, ElementData, TextData}; +use super::dom::{NodeRef, NodeData, ElementData, TextData, WRAPPER_TAG_NAME}; use super::layout::{StyleData, InlineMaterial, TextMaterial, ImageMaterial}; use super::layout::{GlueMaterial, PenaltyMaterial, ChildArtifact, SiblingStyle, LoopContext}; use super::layout::{RootData, DrawState, DrawCommand, TextCommand, ImageCommand, FontKind, Fonts}; @@ -28,7 +28,7 @@ use super::layout::{TextAlign, ParagraphElement, TextElement, ImageElement, Disp use super::layout::{WordSpacing, ListStyleType, LineStats}; use super::layout::{hyph_lang, collapse_margins, DEFAULT_HYPH_LANG, HYPHENATION_PATTERNS}; use super::layout::{EM_SPACE_RATIOS, WORD_SPACE_RATIOS, FONT_SPACES}; -use super::style::{Stylesheet, specified_values}; +use super::style::{StyleSheet, specified_values}; const DEFAULT_DPI: u16 = 300; const DEFAULT_WIDTH: u32 = 1404; @@ -139,13 +139,13 @@ impl Engine { rect![0, 0, width as i32, height as i32] } - pub fn build_display_list(&mut self, node: &Node, parent_style: &StyleData, loop_context: &LoopContext, stylesheet: &Stylesheet, root_data: &RootData, resource_fetcher: &mut dyn ResourceFetcher, draw_state: &mut DrawState, display_list: &mut Vec) -> ChildArtifact { + pub fn build_display_list(&mut self, node: NodeRef, parent_style: &StyleData, loop_context: &LoopContext, stylesheet: &StyleSheet, root_data: &RootData, resource_fetcher: &mut dyn ResourceFetcher, draw_state: &mut DrawState, display_list: &mut Vec) -> ChildArtifact { // TODO: border, background, text-transform, tab-size, text-decoration. let mut style = StyleData::default(); let mut rects: Vec> = Vec::new(); rects.push(None); - let props = specified_values(node, loop_context.parent, loop_context.sibling, stylesheet); + let props = specified_values(node, stylesheet); style.display = props.get("display").and_then(|value| parse_display(value)) .unwrap_or(Display::Block); @@ -166,7 +166,7 @@ impl Engine { match node.tag_name() { Some("pre") => style.retain_whitespace = true, - Some("li") | Some("anonymous") => style.list_style_type = parent_style.list_style_type, + Some("li") | Some(WRAPPER_TAG_NAME) => style.list_style_type = parent_style.list_style_type, Some("table") => { let position = draw_state.position; draw_state.column_widths.clear(); @@ -226,7 +226,7 @@ impl Engine { style.text_align = props.get("text-align") .map(String::as_str) - .or_else(|| node.attr("align")) + .or_else(|| node.attribute("align")) .and_then(|value| parse_text_align(value)) .unwrap_or(parent_style.text_align); @@ -246,7 +246,7 @@ impl Engine { } } - if loop_context.parent.is_some() { + if node.parent().is_some() { style.margin = parse_edge(props.get("margin-top").map(String::as_str), props.get("margin-right").map(String::as_str), props.get("margin-bottom").map(String::as_str), @@ -315,20 +315,15 @@ impl Engine { draw_state.position.y += style.padding.top; - let has_blocks = node.children().and_then(|children| { - children.iter().find(|child| !child.is_whitespace()) - .map(Node::is_block) - }); + let has_blocks = node.children().any(|n| n.is_block()); - if has_blocks == Some(true) { + if has_blocks { if node.id().is_some() { display_list.last_mut().unwrap() .push(DrawCommand::Marker(root_data.start_offset + node.offset())); } - if let Some(children) = node.children() { - let mut inner_loop_context = LoopContext { - parent: Some(node), .. Default::default() - }; + if node.has_children() { + let mut inner_loop_context = LoopContext::default(); if node.tag_name() == Some("tr") { inner_loop_context.is_first = loop_context.is_first; @@ -377,12 +372,12 @@ impl Engine { let mut index = 0; // TODO: rowspan, vertical-align - for child in children.iter().filter(|child| child.is_element()) { + for child in node.children().filter(|child| child.is_element()) { if index >= draw_state.column_widths.len() { break; } - let colspan = child.attr("colspan") + let colspan = child.attribute("colspan") .and_then(|v| v.parse().ok()) .unwrap_or(1) .min(draw_state.column_widths.len()-index); @@ -422,7 +417,6 @@ impl Engine { } } - inner_loop_context.sibling = Some(&child); inner_loop_context.sibling_style = artifact.sibling_style; if inner_loop_context.is_last { @@ -437,7 +431,7 @@ impl Engine { style.end_x = end_x; draw_state.position = final_page.1; } else { - let mut iter = children.iter().filter(|child| child.is_element()).peekable(); + let mut iter = node.children().filter(|child| child.is_element()).peekable(); inner_loop_context.is_first = true; let mut index = 0; @@ -446,16 +440,13 @@ impl Engine { inner_loop_context.is_last = true; } - inner_loop_context.parent = Some(node); inner_loop_context.index = index; - if child.tag_name() == Some("anonymous") { - inner_loop_context.parent = loop_context.parent; + if child.is_wrapper() { inner_loop_context.index = loop_context.index; } let artifact = self.build_display_list(child, &style, &inner_loop_context, stylesheet, root_data, resource_fetcher, draw_state, display_list); - inner_loop_context.sibling = Some(&child); inner_loop_context.sibling_style = artifact.sibling_style; inner_loop_context.is_first = false; @@ -484,36 +475,37 @@ impl Engine { } } } else { - if let Some(children) = node.children() { - if children.is_empty() { - if node.id().is_some() { - display_list.last_mut().unwrap() - .push(DrawCommand::Marker(root_data.start_offset + node.offset())); - } - } else { - let mut inlines = Vec::new(); - let mut markers = Vec::new(); - if node.id().is_some() { - markers.push(node.offset()); - } - let mut sibling = None; - for child in children { - self.gather_inline_material(child, Some(node), sibling, stylesheet, &style, &root_data.spine_dir, &mut markers, &mut inlines); - sibling = Some(child); - } - if !inlines.is_empty() { - draw_state.prefix = match style.list_style_type { - None => { - match loop_context.parent.and_then(|parent| parent.tag_name()) { - Some("ul") => format_list_prefix(ListStyleType::Disc, loop_context.index), - Some("ol") => format_list_prefix(ListStyleType::Decimal, loop_context.index), - _ => None, - } + if node.has_children() { + let mut inlines = Vec::new(); + let mut markers = Vec::new(); + if node.id().is_some() { + markers.push(node.offset()); + } + for child in node.children() { + self.gather_inline_material(child, stylesheet, &style, &root_data.spine_dir, &mut markers, &mut inlines); + } + if !inlines.is_empty() { + draw_state.prefix = match style.list_style_type { + None => { + let parent = if node.is_wrapper() { + node.ancestor_elements().nth(1) + } else { + node.parent_element() + }; + match parent.and_then(|parent| parent.tag_name()) { + Some("ul") => format_list_prefix(ListStyleType::Disc, loop_context.index), + Some("ol") => format_list_prefix(ListStyleType::Decimal, loop_context.index), + _ => None, } - Some(kind) => format_list_prefix(kind, loop_context.index), - }; - self.place_paragraphs(&inlines, &style, root_data, &markers, resource_fetcher, draw_state, &mut rects, display_list); - } + }, + Some(kind) => format_list_prefix(kind, loop_context.index), + }; + self.place_paragraphs(&inlines, &style, root_data, &markers, resource_fetcher, draw_state, &mut rects, display_list); + } + } else { + if node.id().is_some() { + display_list.last_mut().unwrap() + .push(DrawCommand::Marker(root_data.start_offset + node.offset())); } } } @@ -547,60 +539,58 @@ impl Engine { } } - fn compute_column_widths(&mut self, node: &Node, parent_style: &StyleData, loop_context: &LoopContext, stylesheet: &Stylesheet, root_data: &RootData, resource_fetcher: &mut dyn ResourceFetcher, draw_state: &mut DrawState) { + fn compute_column_widths(&mut self, node: NodeRef, parent_style: &StyleData, loop_context: &LoopContext, stylesheet: &StyleSheet, root_data: &RootData, resource_fetcher: &mut dyn ResourceFetcher, draw_state: &mut DrawState) { if node.tag_name() == Some("tr") { - if let Some(children) = node.children() { - let mut index = 0; - for child in children.iter().filter(|c| c.is_element()) { - let colspan = child.attr("colspan") - .and_then(|v| v.parse().ok()) - .unwrap_or(1); - let mut display_list = Vec::new(); - display_list.push(Vec::new()); - let artifact = self.build_display_list(child, parent_style, loop_context, stylesheet, root_data, resource_fetcher, draw_state, &mut display_list); - let horiz_padding = artifact.sibling_style.padding.left + - artifact.sibling_style.padding.right; - let min_width = display_list.into_iter() - .flatten() - .filter_map(|dc| { - match dc { - DrawCommand::Text(TextCommand { rect, .. }) => Some(rect.width() as i32 + horiz_padding), - DrawCommand::Image(ImageCommand { rect, .. }) => Some((rect.width() as i32).min(pt_to_px(parent_style.font_size, self.dpi).round().max(1.0) as i32) + horiz_padding), - _ => None, - } - }) - .max().unwrap_or(0); - let max_width = artifact.rects.into_iter() - .filter_map(|v| v.map(|r| r.width() as i32 + horiz_padding)) + let mut index = 0; + for child in node.children().filter(|c| c.is_element()) { + let colspan = child.attribute("colspan") + .and_then(|v| v.parse().ok()) + .unwrap_or(1); + let mut display_list = Vec::new(); + display_list.push(Vec::new()); + let artifact = self.build_display_list(child, parent_style, loop_context, stylesheet, root_data, resource_fetcher, draw_state, &mut display_list); + let horiz_padding = artifact.sibling_style.padding.left + + artifact.sibling_style.padding.right; + let min_width = display_list.into_iter() + .flatten() + .filter_map(|dc| { + match dc { + DrawCommand::Text(TextCommand { rect, .. }) => Some(rect.width() as i32 + horiz_padding), + DrawCommand::Image(ImageCommand { rect, .. }) => Some((rect.width() as i32).min(pt_to_px(parent_style.font_size, self.dpi).round().max(1.0) as i32) + horiz_padding), + _ => None, + } + }) .max().unwrap_or(0); - if colspan == 1 { - if let Some(cw) = draw_state.min_column_widths.get_mut(index) { - *cw = (*cw).max(min_width); - } else { - draw_state.min_column_widths.push(min_width); - } - if let Some(cw) = draw_state.max_column_widths.get_mut(index) { - *cw = (*cw).max(max_width); - } else { - draw_state.max_column_widths.push(max_width); - } + let max_width = artifact.rects.into_iter() + .filter_map(|v| v.map(|r| r.width() as i32 + horiz_padding)) + .max().unwrap_or(0); + if colspan == 1 { + if let Some(cw) = draw_state.min_column_widths.get_mut(index) { + *cw = (*cw).max(min_width); + } else { + draw_state.min_column_widths.push(min_width); + } + if let Some(cw) = draw_state.max_column_widths.get_mut(index) { + *cw = (*cw).max(max_width); + } else { + draw_state.max_column_widths.push(max_width); } - - index += colspan; } + + index += colspan; } - } else if let Some(children) = node.children() { - for child in children.iter().filter(|c| c.is_element()) { + } else { + for child in node.children().filter(|c| c.is_element()) { self.compute_column_widths(child, parent_style, loop_context, stylesheet, root_data, resource_fetcher, draw_state); } } } - fn gather_inline_material(&self, node: &Node, parent: Option<&Node>, sibling: Option<&Node>, stylesheet: &Stylesheet, parent_style: &StyleData, spine_dir: &PathBuf, markers: &mut Vec, inlines: &mut Vec) { - match node { - Node::Element(ElementData { offset, name, attributes, children, .. }) => { + fn gather_inline_material(&self, node: NodeRef, stylesheet: &StyleSheet, parent_style: &StyleData, spine_dir: &PathBuf, markers: &mut Vec, inlines: &mut Vec) { + match node.data() { + NodeData::Element(ElementData { offset, name, attributes, .. }) => { let mut style = StyleData::default(); - let props = specified_values(node, parent, sibling, stylesheet); + let props = specified_values(node, stylesheet); style.font_style = parent_style.font_style; style.line_height = parent_style.line_height; @@ -718,10 +708,8 @@ impl Engine { inlines.append(&mut v); } - let mut sibling = None; - for child in children { - self.gather_inline_material(child, Some(node), sibling, stylesheet, &style, spine_dir, markers, inlines); - sibling = Some(child); + for child in node.children() { + self.gather_inline_material(child, stylesheet, &style, spine_dir, markers, inlines); } if let Some(mut v) = props.get("-plato-insert-after") @@ -729,20 +717,21 @@ impl Engine { inlines.append(&mut v); } }, - Node::Text(TextData { offset, text }) => { + NodeData::Text(TextData { offset, text }) => { inlines.push(InlineMaterial::Text(TextMaterial { offset: *offset, text: decode_entities(&text).into_owned(), style: parent_style.clone(), })); }, - Node::Whitespace(TextData { offset, text }) => { + NodeData::Whitespace(TextData { offset, text }) => { inlines.push(InlineMaterial::Text(TextMaterial { offset: *offset, text: text.to_string(), style: parent_style.clone(), })); }, + _ => (), } } diff --git a/src/document/html/layout.rs b/src/document/html/layout.rs index 2fcd3e4..c6dd4be 100644 --- a/src/document/html/layout.rs +++ b/src/document/html/layout.rs @@ -7,7 +7,6 @@ use kl_hyphenate::{Standard, Language, Load}; use crate::geom::{Point, Rectangle, Edge}; use crate::font::{FontFamily, Font, RenderPlan}; pub use crate::metadata::TextAlign; -use super::dom::Node; use crate::color::BLACK; pub const DEFAULT_HYPH_LANG: &str = "en"; @@ -149,21 +148,17 @@ impl Default for SiblingStyle { } #[derive(Debug, Clone)] -pub struct LoopContext<'a> { +pub struct LoopContext { pub index: usize, - pub parent: Option<&'a Node>, - pub sibling: Option<&'a Node>, pub sibling_style: SiblingStyle, pub is_first: bool, pub is_last: bool, } -impl<'a> Default for LoopContext<'a> { +impl Default for LoopContext { fn default() -> Self { LoopContext { index: 0, - parent: None, - sibling: None, sibling_style: SiblingStyle::default(), is_first: false, is_last: false, diff --git a/src/document/html/mod.rs b/src/document/html/mod.rs index 5cdfe3d..17a10fa 100644 --- a/src/document/html/mod.rs +++ b/src/document/html/mod.rs @@ -16,11 +16,12 @@ use crate::helpers::{Normalize, decode_entities}; use crate::document::{Document, Location, TextLocation, TocEntry, BoundedText}; use crate::unit::pt_to_px; use crate::geom::{Rectangle, Edge, CycleDir}; -use self::dom::Node; +use self::dom::{XmlTree, NodeRef}; use self::layout::{RootData, StyleData, DrawState, LoopContext}; use self::layout::{DrawCommand, TextCommand, ImageCommand, TextAlign}; use self::engine::{Page, Engine, ResourceFetcher}; -use self::css::{CssParser, RuleKind}; +use self::style::StyleSheet; +use self::css::CssParser; use self::xml::XmlParser; const VIEWER_STYLESHEET: &str = "css/html.css"; @@ -30,7 +31,7 @@ type UriCache = FxHashMap; pub struct HtmlDocument { text: String, - content: Node, + content: XmlTree, engine: Engine, pages: Vec, parent: PathBuf, @@ -145,18 +146,16 @@ impl HtmlDocument { let frag_index = uri.find('#')?; let name = &uri[..frag_index]; let content = self.content.clone(); - self.cache_uris(&content, name, cache); + self.cache_uris(content.root(), name, cache); cache.get(uri).cloned() } - fn cache_uris(&mut self, node: &Node, name: &str, cache: &mut UriCache) { - if let Some(id) = node.attr("id") { + fn cache_uris(&mut self, node: NodeRef, name: &str, cache: &mut UriCache) { + if let Some(id) = node.attribute("id") { cache.insert(format!("{}#{}", name, id), node.offset()); } - if let Some(children) = node.children() { - for child in children { - self.cache_uris(child, name, cache); - } + for child in node.children() { + self.cache_uris(child, name, cache); } } @@ -173,43 +172,43 @@ impl HtmlDocument { } fn build_pages(&mut self) -> Vec { - let mut stylesheet = Vec::new(); + let mut stylesheet = StyleSheet::new(); let spine_dir = PathBuf::default(); if let Ok(text) = fs::read_to_string(&self.viewer_stylesheet) { - let (mut css, _) = CssParser::new(&text).parse(RuleKind::Viewer); - stylesheet.append(&mut css); + let mut css = CssParser::new(&text).parse(); + stylesheet.append(&mut css, true); } if let Ok(text) = fs::read_to_string(&self.user_stylesheet) { - let (mut css, _) = CssParser::new(&text).parse(RuleKind::User); - stylesheet.append(&mut css); + let mut css = CssParser::new(&text).parse(); + stylesheet.append(&mut css, true); } if !self.ignore_document_css { - if let Some(head) = self.content.find("head") { - if let Some(children) = head.children() { - for child in children { - if child.tag_name() == Some("link") && child.attr("rel") == Some("stylesheet") { - if let Some(href) = child.attr("href") { - if let Some(name) = spine_dir.join(href).normalize().to_str() { - if let Ok(buf) = self.parent.fetch(name) { - if let Ok(text) = String::from_utf8(buf) { - let (mut css, _) = CssParser::new(&text).parse(RuleKind::Document); - stylesheet.append(&mut css); - } + let mut inner_css = StyleSheet::new(); + + if let Some(head) = self.content.root().find("head") { + for child in head.children() { + if child.tag_name() == Some("link") && child.attribute("rel") == Some("stylesheet") { + if let Some(href) = child.attribute("href") { + if let Some(name) = spine_dir.join(href).normalize().to_str() { + if let Ok(buf) = self.parent.fetch(name) { + if let Ok(text) = String::from_utf8(buf) { + let mut css = CssParser::new(&text).parse(); + inner_css.append(&mut css, false); } } } - } else if child.tag_name() == Some("style") && child.attr("type") == Some("text/css") { - if let Some(text) = child.text() { - let (mut css, _) = CssParser::new(text).parse(RuleKind::Document); - stylesheet.append(&mut css); - } } + } else if child.tag_name() == Some("style") && child.attribute("type") == Some("text/css") { + let mut css = CssParser::new(&child.text()).parse(); + inner_css.append(&mut css, false); } } } + + stylesheet.append(&mut inner_css, true); } let mut pages = Vec::new(); @@ -217,8 +216,9 @@ impl HtmlDocument { let mut rect = self.engine.rect(); rect.shrink(&self.engine.margin); - let language = self.content.find("html") - .and_then(|html| html.attr("xml:lang")) + let language = self.content.root() + .find("html") + .and_then(|html| html.attribute("xml:lang")) .map(String::from); let style = StyleData { @@ -246,12 +246,12 @@ impl HtmlDocument { pages.push(Vec::new()); - self.engine.build_display_list(&self.content, &style, &loop_context, &stylesheet, &root_data, &mut self.parent, &mut draw_state, &mut pages); + self.engine.build_display_list(self.content.root(), &style, &loop_context, &stylesheet, &root_data, &mut self.parent, &mut draw_state, &mut pages); pages.retain(|page| !page.is_empty()); if pages.is_empty() { - pages.push(vec![DrawCommand::Marker(self.content.offset())]); + pages.push(vec![DrawCommand::Marker(self.content.root().offset())]); } pages @@ -266,8 +266,9 @@ impl HtmlDocument { } pub fn language(&self) -> Option { - self.content.find("html") - .and_then(|html| html.attr("xml:lang")) + self.content.root() + .find("html") + .and_then(|html| html.attribute("xml:lang")) .map(String::from) } @@ -416,11 +417,10 @@ impl Document for HtmlDocument { } fn title(&self) -> Option { - self.content.find("head") - .and_then(Node::children) - .and_then(|children| children.iter().find(|child| child.tag_name() == Some("title"))) - .and_then(|child| child.children().and_then(|c| c.get(0))) - .and_then(|child| child.text().map(|s| decode_entities(s).into_owned())) + self.content.root() + .find("head") + .and_then(|head| head.children().find(|child| child.tag_name() == Some("title"))) + .map(|child| decode_entities(&child.text()).into_owned()) } fn author(&self) -> Option { @@ -428,10 +428,10 @@ impl Document for HtmlDocument { } fn metadata(&self, key: &str) -> Option { - self.content.find("head") - .and_then(Node::children) - .and_then(|children| children.iter().find(|child| child.tag_name() == Some("meta") && child.attr("name") == Some(key))) - .and_then(|child| child.attr("content").map(|s| decode_entities(s).into_owned())) + self.content.root() + .find("head") + .and_then(|head| head.children().find(|child| child.tag_name() == Some("meta") && child.attribute("name") == Some(key))) + .and_then(|child| child.attribute("content").map(|s| decode_entities(s).into_owned())) } fn save(&self, path: &str) -> Result<(), Error> { diff --git a/src/document/html/style.rs b/src/document/html/style.rs index a5d1e5a..5143b60 100644 --- a/src/document/html/style.rs +++ b/src/document/html/style.rs @@ -1,51 +1,50 @@ use fxhash::FxHashMap; -use super::dom::Node; -use super::css::{CssParser, Rule, Selector, SimpleSelector, Specificity}; +use super::dom::NodeRef; +use super::css::{CssParser, Rule, Selector, SimpleSelector}; +use super::css::{Combinator, AttributeOperator, PseudoClass}; pub type PropertyMap = FxHashMap; -pub type Stylesheet = [Rule]; -type MatchedRule<'a> = (Specificity, &'a Rule); -#[cfg(test)] -mod tests { - use super::specified_values; - use super::super::css::{CssParser, RuleKind}; - use super::super::xml::XmlParser; +#[derive(Debug, Clone)] +pub struct StyleSheet { + pub rules: Vec +} - #[test] - fn simple_style() { - let xml1 = XmlParser::new("").parse(); - let xml2 = XmlParser::new("").parse(); - let (mut css1, _) = CssParser::new("a { b: 23 }").parse(RuleKind::Viewer); - let (mut css2, _) = CssParser::new(".c.x.y { b: 6 }").parse(RuleKind::Document); - let (mut css3, _) = CssParser::new(".y { b: 2 }").parse(RuleKind::Document); - css1.append(&mut css3); - css1.append(&mut css2); - assert_eq!(specified_values(&xml1, None, None, &css1), [("b".to_string(), "6".to_string()), - ("c".to_string(), "7".to_string())].iter().cloned().collect()); - assert_eq!(specified_values(&xml2, None, None, &css1), [("b".to_string(), "2".to_string())].iter().cloned().collect()); +impl StyleSheet { + pub fn new() -> Self { + StyleSheet { + rules: Vec::new(), + } + } + + pub fn append(&mut self, other: &mut Self, sort: bool) { + if sort { + other.sort(); + } + self.rules.append(&mut other.rules); + } + + pub fn sort(&mut self) { + self.rules.sort_by_cached_key(|rule| rule.selector.specificity()); } } -pub fn specified_values(node: &Node, parent: Option<&Node>, sibling: Option<&Node>, stylesheet: &Stylesheet) -> PropertyMap { +pub fn specified_values(node: NodeRef, stylesheet: &StyleSheet) -> PropertyMap { let mut props = FxHashMap::default(); - let mut rules = matching_rules(node, parent, sibling, stylesheet); + let mut important = Vec::new(); - rules.sort_by(|&(sa, ra), &(sb, rb)| { - if ra.kind == rb.kind { - sa.cmp(&sb) - } else { - ra.kind.cmp(&rb.kind) - } - }); - - for (_, rule) in rules { + for rule in stylesheet.rules.iter() + .filter(|rule| rule.selector.matches(node)) { for declaration in &rule.declarations { - expand_and_insert(&declaration.name, &declaration.value, &mut props); + if declaration.important { + important.push([&declaration.name, &declaration.value]); + } else { + expand_and_insert(&declaration.name, &declaration.value, &mut props); + } } } - let local_declarations = node.attr("style").map(|text| { + let local_declarations = node.attribute("style").map(|text| { CssParser::new(text).parse_declarations() }).unwrap_or_default(); @@ -53,53 +52,113 @@ pub fn specified_values(node: &Node, parent: Option<&Node>, sibling: Option<&Nod expand_and_insert(&declaration.name, &declaration.value, &mut props); } + for [name, value] in important { + expand_and_insert(name, value, &mut props); + } + props } -fn matching_rules<'a>(node: &Node, parent: Option<&Node>, sibling: Option<&Node>, stylesheet: &'a Stylesheet) -> Vec> { - stylesheet.iter().filter_map(|rule| match_rule(node, parent, sibling, rule)).collect() -} +impl Selector { + fn matches(&self, node: NodeRef) -> bool { + let index = self.simple_selectors.len().saturating_sub(1); + self.matches_rec(node, index) + } -fn match_rule<'a>(node: &Node, parent: Option<&Node>, sibling: Option<&Node>, rule: &'a Rule) -> Option> { - rule.selectors.iter().find(|selector| matches(node, parent, sibling, *selector)) - .map(|selector| (selector.specificity(), rule)) -} + fn matches_rec(&self, node: NodeRef, index: usize) -> bool { + let comb = self.combinators[index]; + let selec = &self.simple_selectors[index]; -fn matches(node: &Node, parent: Option<&Node>, sibling: Option<&Node>, selector: &Selector) -> bool { - match selector { - Selector::Simple(sel) => matches_simple_selector(node, sel), - Selector::ParentChild(sel1, sel2) => { - if let Some(p) = parent { - matches_simple_selector(p, sel1) && matches_simple_selector(node, sel2) - } else { + if !selec.matches(node) { + return false; + } + + match comb { + Combinator::Child => { + if let Some(parent) = node.parent_element() { + if self.matches_rec(parent, index - 1) { + return true; + } + } false - } - }, - Selector::Siblings(sel1, sel2) => { - if let Some(s) = sibling { - matches_simple_selector(s, sel1) && matches_simple_selector(node, sel2) - } else { + }, + Combinator::Descendant => { + for anc in node.ancestor_elements() { + if self.matches_rec(anc, index - 1) { + return true; + } + } false - } - }, + }, + Combinator::NextSibling => { + if let Some(nsib) = node.previous_sibling_element() { + if self.matches_rec(nsib, index - 1) { + return true; + } + } + false + }, + Combinator::SubsequentSibling => { + for sib in node.previous_sibling_elements() { + if self.matches_rec(sib, index - 1) { + return true; + } + } + false + }, + Combinator::None => true, + } } } -fn matches_simple_selector(node: &Node, selector: &SimpleSelector) -> bool { - if selector.tag_name.iter().any(|name| node.tag_name() != Some(name)) { - return false; - } +impl SimpleSelector { + fn matches(&self, node: NodeRef) -> bool { + if self.tag_name.iter().any(|name| node.tag_name() != Some(name)) { + return false; + } + + if self.classes.iter().any(|class| node.classes().all(|c| c != class)) { + return false; + } - let node_classes = node.classes().unwrap_or_default(); - if selector.classes.iter().any(|class| !node_classes.contains(&**class)) { - return false; + if self.id.iter().any(|id| node.id() != Some(id)) { + return false; + } + + if self.attributes.iter().any(|attr| node.attribute(&attr.name) + .map(|value| attr.operator.matches(value)) != Some(true)) { + return false; + } + + if self.pseudo_classes.iter().any(|pc| !pc.matches(node)) { + return false; + } + + true } +} - if selector.id.iter().any(|id| node.id() != Some(id)) { - return false; +impl AttributeOperator { + fn matches(&self, value: &str) -> bool { + match self { + AttributeOperator::Exists => true, + AttributeOperator::Matches(v) => v == value, + AttributeOperator::Contains(v) => value.split_whitespace() + .any(|value| value == v), + AttributeOperator::StartsWith(v) => v == value || + (value.starts_with(v) && + value[v.len()..].starts_with('-')), + } } +} - true +impl PseudoClass { + fn matches(&self, node: NodeRef) -> bool { + match self { + PseudoClass::FirstChild => node.previous_sibling_element().is_none(), + PseudoClass::LastChild => node.next_sibling_element().is_none(), + } + } } fn expand_and_insert(name: &str, value: &str, props: &mut PropertyMap) { @@ -151,3 +210,27 @@ fn expand_and_insert(name: &str, value: &str, props: &mut PropertyMap) { } } } + +#[cfg(test)] +mod tests { + use super::specified_values; + use super::super::css::CssParser; + use super::super::style::StyleSheet; + use super::super::xml::XmlParser; + + #[test] + fn simple_style() { + let xml1 = XmlParser::new("").parse(); + let xml2 = XmlParser::new("").parse(); + let mut css = CssParser::new("a { b: 23 }\ + .c.x.y { b: 6; c: 3 }\ + #e { b: 5 }\ + .y { b: 2 }").parse(); + css.sort(); + let n1 = xml1.root().first_child().unwrap(); + let n2 = xml2.root().first_child().unwrap(); + assert_eq!(specified_values(n1, &css), [("b".to_string(), "6".to_string()), + ("c".to_string(), "7".to_string())].iter().cloned().collect()); + assert_eq!(specified_values(n2, &css), [("b".to_string(), "5".to_string())].iter().cloned().collect()); + } +} diff --git a/src/document/html/xml.rs b/src/document/html/xml.rs index 87a9103..0dc24cb 100644 --- a/src/document/html/xml.rs +++ b/src/document/html/xml.rs @@ -1,5 +1,6 @@ use fxhash::FxHashMap; -use super::dom::{Node, Attributes, text, element, whitespace}; +use super::dom::{XmlTree, NodeId, Attributes}; +use super::dom::{text, element, whitespace}; #[derive(Debug)] pub struct XmlParser<'a> { @@ -40,16 +41,10 @@ impl<'a> XmlParser<'a> { } fn advance_until(&mut self, target: &str) { - if let Some(first) = target.chars().next() { - while !self.eof() { - self.advance(1); - self.advance_while(|&c| c != first); - if self.starts_with(target) { - break; - } - } - self.advance(target.chars().count()); + while !self.eof() && !self.starts_with(target) { + self.advance(1); } + self.advance(target.chars().count()); } fn parse_attributes(&mut self) -> Attributes { @@ -76,7 +71,7 @@ impl<'a> XmlParser<'a> { attrs } - fn parse_element(&mut self, nodes: &mut Vec) { + fn parse_element(&mut self, tree: &mut XmlTree, parent_id: NodeId) { let offset = self.offset; self.advance_while(|&c| c != '>' && c != '/' && !c.is_whitespace()); let name = &self.input[offset..self.offset]; @@ -85,20 +80,20 @@ impl<'a> XmlParser<'a> { match self.next() { Some('/') => { self.advance(2); - nodes.push(element(name, offset - 1, attributes, Vec::new())); + tree.get_mut(parent_id) + .append(element(name, offset - 1, attributes)); }, Some('>') => { self.advance(1); - let children = self.parse_nodes(); - nodes.push(element(name, offset - 1, attributes, children)); - } + let id = tree.get_mut(parent_id) + .append(element(name, offset - 1, attributes)); + self.parse_nodes(tree, id); + }, _ => (), } } - fn parse_nodes(&mut self) -> Vec { - let mut nodes = Vec::new(); - + fn parse_nodes(&mut self, tree: &mut XmlTree, parent_id: NodeId) { while !self.eof() { let offset = self.offset; self.advance_while(|&c| c.is_whitespace()); @@ -106,7 +101,8 @@ impl<'a> XmlParser<'a> { match self.next() { Some('<') => { if self.offset > offset { - nodes.push(whitespace(&self.input[offset..self.offset], offset)); + tree.get_mut(parent_id) + .append(whitespace(&self.input[offset..self.offset], offset)); } if self.starts_with(" XmlParser<'a> { } } }, - _ => self.parse_element(&mut nodes), + _ => self.parse_element(tree, parent_id), } }, Some(..) => { self.advance_while(|&c| c != '<'); - nodes.push(text(&self.input[offset..self.offset], offset)); + tree.get_mut(parent_id) + .append(text(&self.input[offset..self.offset], offset)); }, None => break, } } - nodes } - pub fn parse(&mut self) -> Node { - let mut nodes = self.parse_nodes(); - if nodes.len() == 1 { - nodes.remove(0) - } else { - element("root", 0, FxHashMap::default(), nodes) - } + pub fn parse(&mut self) -> XmlTree { + let mut tree = XmlTree::new(); + self.parse_nodes(&mut tree, NodeId::from_index(0)); + tree } } @@ -168,39 +161,42 @@ mod tests { fn test_simple_element() { let text = ""; let xml = XmlParser::new(text).parse(); - assert_eq!(xml.offset(), 0); - assert_eq!(xml.tag_name(), Some("a")); + let n = xml.root().first_child().unwrap(); + assert_eq!(n.offset(), 0); + assert_eq!(n.tag_name(), Some("a")); } #[test] fn test_attributes() { let text = r#""#; let xml = XmlParser::new(text).parse(); - assert_eq!(xml.attr("b"), Some("c")); - assert_eq!(xml.attr("d"), Some("e\"")); + let n = xml.root().first_child().unwrap(); + assert_eq!(n.attribute("b"), Some("c")); + assert_eq!(n.attribute("d"), Some("e\"")); } #[test] fn test_text() { let text = "bcd"; let xml = XmlParser::new(text).parse(); - let child = xml.child(0); + let child = xml.root().first_child().unwrap().children().next(); assert_eq!(child.map(|c| c.offset()), Some(3)); - assert_eq!(child.and_then(|c| c.text()), Some("bcd")); + assert_eq!(child.map(|c| c.text()), Some("bcd".to_string())); } #[test] fn test_inbetween_space() { let text = "x y"; let xml = XmlParser::new(text).parse(); - let child = xml.child(1); - assert_eq!(child.and_then(|c| c.text()), Some(" ")); + let child = xml.root().first_child().unwrap() + .children().nth(1); + assert_eq!(child.map(|c| c.text()), Some(" ".to_string())); } #[test] fn test_central_space() { let text = " "; let xml = XmlParser::new(text).parse(); - assert_eq!(xml.text(), Some(" ")); + assert_eq!(xml.root().text(), " "); } } diff --git a/src/document/mod.rs b/src/document/mod.rs index ca44b35..15f33b4 100644 --- a/src/document/mod.rs +++ b/src/document/mod.rs @@ -257,11 +257,7 @@ pub fn toc_as_html(toc: &[TocEntry], chap_index: usize) -> String { pub fn toc_as_html_aux(toc: &[TocEntry], chap_index: usize, depth: usize, buf: &mut String) { buf.push_str(&"\t".repeat(depth + 2)); - if depth == 0 { - buf.push_str("
    \n"); - } else { - buf.push_str("
      \n"); - } + buf.push_str("
        \n"); for entry in toc { buf.push_str(&"\t".repeat(depth + 3)); match entry.location { -- 2.51.2