Substitute var() in longhand property declarations.

This commit is contained in:
Simon Sapin 2015-09-02 22:56:39 +02:00
parent b2ee828520
commit b4fdb5d803
6 changed files with 254 additions and 157 deletions

View file

@ -24,18 +24,19 @@ pub struct BorrowedValue<'a> {
pub fn parse(input: &mut Parser) -> Result<Value, ()> {
let start = input.position();
let mut references = HashSet::new();
let mut references = Some(HashSet::new());
// FIXME: dont consume a top-level `!` as that would prevent parsing `!important`.
// Maybe using Parser::parse_until_before?
try!(parse_declaration_value(input, &mut references));
Ok(Value {
value: input.slice_from(start).to_owned(),
references: references,
references: references.unwrap(),
})
}
/// https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value
fn parse_declaration_value(input: &mut Parser, references: &mut HashSet<Atom>) -> Result<(), ()> {
pub fn parse_declaration_value(input: &mut Parser, references: &mut Option<HashSet<Atom>>)
-> Result<(), ()> {
if input.is_exhausted() {
// Need at least one token
return Err(())
@ -76,7 +77,7 @@ fn parse_declaration_value(input: &mut Parser, references: &mut HashSet<Atom>) -
/// Like parse_declaration_value,
/// but accept `!` and `;` since they are only invalid at the top level
fn parse_declaration_value_block(input: &mut Parser, references: &mut HashSet<Atom>)
fn parse_declaration_value_block(input: &mut Parser, references: &mut Option<HashSet<Atom>>)
-> Result<(), ()> {
while let Ok(token) = input.next() {
match token {
@ -110,7 +111,7 @@ fn parse_declaration_value_block(input: &mut Parser, references: &mut HashSet<At
}
// If the var function is valid, return Ok((custom_property_name, fallback))
fn parse_var_function<'i, 't>(input: &mut Parser<'i, 't>, references: &mut HashSet<Atom>)
fn parse_var_function<'i, 't>(input: &mut Parser<'i, 't>, references: &mut Option<HashSet<Atom>>)
-> Result<(), ()> {
// https://drafts.csswg.org/css-variables/#typedef-custom-property-name
let name = try!(input.expect_ident());
@ -122,7 +123,9 @@ fn parse_var_function<'i, 't>(input: &mut Parser<'i, 't>, references: &mut HashS
if input.expect_comma().is_ok() {
try!(parse_declaration_value(input, references));
}
references.insert(Atom::from_slice(name));
if let Some(ref mut refs) = *references {
refs.insert(Atom::from_slice(name));
}
Ok(())
}
@ -153,7 +156,8 @@ pub fn cascade<'a>(custom_properties: &mut Option<HashMap<&'a Atom, BorrowedValu
value: &value.value,
references: Some(&value.references),
});
}
},
DeclaredValue::WithVariables { .. } => unreachable!(),
DeclaredValue::Initial => {
map.remove(&name);
}
@ -188,8 +192,8 @@ fn remove_cycles(map: &mut HashMap<&Atom, BorrowedValue>) {
stack: &mut Vec<&'a Atom>,
visited: &mut HashSet<&'a Atom>,
to_remove: &mut HashSet<Atom>) {
let was_not_already_present = visited.insert(name);
if !was_not_already_present {
let already_visited_before = !visited.insert(name);
if already_visited_before {
return
}
if let Some(value) = map.get(name) {
@ -216,6 +220,7 @@ fn remove_cycles(map: &mut HashMap<&Atom, BorrowedValue>) {
}
}
/// Replace `var()` functions for all custom properties.
fn substitute_all(custom_properties: HashMap<&Atom, BorrowedValue>,
inherited: &Option<Arc<HashMap<Atom, String>>>)
-> HashMap<Atom, String> {
@ -230,6 +235,9 @@ fn substitute_all(custom_properties: HashMap<&Atom, BorrowedValue>,
substituted_map
}
/// Replace `var()` functions for one custom property.
/// Also recursively record results for other custom properties referenced by `var()` functions.
/// Return `Err(())` for invalid at computed time.
fn substitute_one(name: &Atom,
value: &BorrowedValue,
custom_properties: &HashMap<&Atom, BorrowedValue>,
@ -252,10 +260,14 @@ fn substitute_one(name: &Atom,
let mut substituted = String::new();
let mut input = Parser::new(&value.value);
let mut start = input.position();
if substitute_block(
custom_properties, inherited, &mut input, &mut start, &mut substituted,
substituted_map, invalid,
).is_ok() {
if substitute_block(&mut input, &mut start, &mut substituted, &mut |name, substituted| {
if let Some(value) = custom_properties.get(name) {
substitute_one(name, value, custom_properties, inherited,
Some(substituted), substituted_map, invalid)
} else {
Err(())
}
}).is_ok() {
substituted.push_str(input.slice_from(start));
substituted
} else {
@ -279,27 +291,33 @@ fn substitute_one(name: &Atom,
Ok(())
}
fn substitute_block(custom_properties: &HashMap<&Atom, BorrowedValue>,
inherited: &Option<Arc<HashMap<Atom, String>>>,
input: &mut Parser,
start: &mut SourcePosition,
substituted: &mut String,
substituted_map: &mut HashMap<Atom, String>,
invalid: &mut HashSet<Atom>)
-> Result<(), ()> {
while let Ok(token) = input.next() {
/// Replace `var()` functions in an arbitrary bit of input.
///
/// The `substitute_one` callback is called for each `var()` function in `input`.
/// If the variable has its initial value,
/// the callback should return `Err(())` and leave `substituted` unchanged.
/// Otherwise, it should push the value of the variable (with its own `var()` functions replaced)
/// to `substituted` and return `Ok(())`.
///
/// Return `Err(())` if `input` is invalid at computed-value time.
fn substitute_block<F>(input: &mut Parser,
start: &mut SourcePosition,
substituted: &mut String,
substitute_one: &mut F)
-> Result<(), ()>
where F: FnMut(&Atom, &mut String) -> Result<(), ()> {
loop {
let input_slice = input.slice_from(*start);
let token = if let Ok(token) = input.next() { token } else { break };
match token {
Token::Function(ref name) if name == "var" => {
substituted.push_str(input.slice_from(*start));
substituted.push_str(input_slice);
try!(input.parse_nested_block(|input| {
let name = input.expect_ident().unwrap();
debug_assert!(name.starts_with("--"));
debug_assert!(name.starts_with("--")); // Ensured by parse_var_function()
let name = Atom::from_slice(&name[2..]);
if let Some(value) = custom_properties.get(&name) {
try!(substitute_one(
&name, value, custom_properties, inherited,
Some(substituted), substituted_map, invalid));
if substitute_one(&name, substituted).is_ok() {
// Skip over the fallback, as `parse_nested_block` would return `Err`
// if we dont consume all of `input`.
// FIXME: Add a specialized method to cssparser to do this with less work.
@ -307,9 +325,7 @@ fn substitute_block(custom_properties: &HashMap<&Atom, BorrowedValue>,
} else {
try!(input.expect_comma());
let mut start = input.position();
try!(substitute_block(
custom_properties, inherited, input,
&mut start, substituted, substituted_map, invalid));
try!(substitute_block(input, &mut start, substituted, substitute_one));
substituted.push_str(input.slice_from(start));
}
Ok(())
@ -321,9 +337,9 @@ fn substitute_block(custom_properties: &HashMap<&Atom, BorrowedValue>,
Token::ParenthesisBlock |
Token::CurlyBracketBlock |
Token::SquareBracketBlock => {
try!(input.parse_nested_block(|input| substitute_block(
custom_properties, inherited, input,
start, substituted, substituted_map, invalid)));
try!(input.parse_nested_block(|input| {
substitute_block(input, start, substituted, substitute_one)
}));
}
_ => {}
@ -337,3 +353,29 @@ fn substitute_block(custom_properties: &HashMap<&Atom, BorrowedValue>,
// ```
Ok(())
}
/// Replace `var()` functions for a non-custom property.
/// Return `Err(())` for invalid at computed time.
pub fn substitute(input: &str, custom_properties: &Option<Arc<HashMap<Atom, String>>>)
-> Result<String, ()> {
let empty_map;
let custom_properties = if let &Some(ref arc) = custom_properties {
&**arc
} else {
empty_map = HashMap::new();
&empty_map
};
let mut substituted = String::new();
let mut input = Parser::new(input);
let mut start = input.position();
try!(substitute_block(&mut input, &mut start, &mut substituted, &mut |name, substituted| {
if let Some(value) = custom_properties.get(name) {
substituted.push_str(value);
Ok(())
} else {
Err(())
}
}));
substituted.push_str(input.slice_from(start));
Ok(substituted)
}