feat(html): Support boolean attributes (#4258)

This commit is contained in:
Alexander Akait 2022-04-06 08:31:27 +03:00 committed by GitHub
parent e91be102d7
commit 8640c8bd43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 127 additions and 41 deletions

View File

@ -11,7 +11,7 @@ pub struct TokenAndSpan {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Attribute {
pub name: JsWord,
pub value: JsWord,
pub value: Option<JsWord>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -98,18 +98,17 @@ where
for attribute in attributes {
start_tag.push(' ');
start_tag.push_str(&attribute.name);
if let Some(value) = &attribute.value {
start_tag.push('=');
let quote = if attribute.value.contains('"') {
'\''
} else {
'"'
};
let quote = if value.contains('"') { '\'' } else { '"' };
start_tag.push(quote);
start_tag.push_str(&attribute.value);
start_tag.push_str(value);
start_tag.push(quote);
}
}
if *self_closing {
start_tag.push('/');
@ -132,8 +131,16 @@ where
for attribute in attributes {
start_tag.push(' ');
start_tag.push_str(&attribute.name);
if let Some(value) = &attribute.value {
start_tag.push('=');
start_tag.push_str(&attribute.value);
let quote = if value.contains('"') { '\'' } else { '"' };
start_tag.push(quote);
start_tag.push_str(value);
start_tag.push(quote);
}
}
start_tag.push('>');

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<label><input type=checkbox checked name=cheese disabled> Cheese</label>
<label><input type=checkbox checked=checked name=cheese disabled=disabled> Cheese</label>
<label><input type='checkbox' checked name=cheese disabled=""> Cheese</label>
<label><input type= checkbox checked name = cheese disabled> Cheese</label>
<label><input type =checkbox checked name = cheese disabled> Cheese</label>
<label><input type = checkbox checked name = cheese disabled> Cheese</label>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
<label><input type="checkbox" checked="checked" name="cheese" disabled="disabled"> Cheese</label>
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
<label><input type="checkbox" checked="checked" name="cheese" disabled="disabled"> Cheese</label>
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
<label><input type="checkbox" checked name="cheese" disabled> Cheese</label>
</body>
</html>

View File

@ -1757,6 +1757,7 @@ where
// Start a new attribute in the current tag token. Set that attribute's name
// to the current input character, and its value to the empty string. Switch
// to the attribute name state.
// We set `None` for `value` to support boolean attributes in AST
Some(c @ '=') => {
self.emit_error(ErrorKind::UnexpectedEqualsSignBeforeAttributeName);
if let Some(ref mut token) = self.cur_token {
@ -1764,13 +1765,13 @@ where
Token::StartTag { attributes, .. } => {
attributes.push(Attribute {
name: c.to_string().into(),
value: "".into(),
value: None,
});
}
Token::EndTag { attributes, .. } => {
attributes.push(Attribute {
name: c.to_string().into(),
value: "".into(),
value: None,
});
}
_ => {}
@ -1781,19 +1782,20 @@ where
// Anything else
// Start a new attribute in the current tag token. Set that attribute name
// and value to the empty string. Reconsume in the attribute name state.
// We set `None` for `value` to support boolean attributes in AST
_ => {
if let Some(ref mut token) = self.cur_token {
match token {
Token::StartTag { attributes, .. } => {
attributes.push(Attribute {
name: "".into(),
value: "".into(),
value: None,
});
}
Token::EndTag { attributes, .. } => {
attributes.push(Attribute {
name: "".into(),
value: "".into(),
value: None,
});
}
_ => {}
@ -1943,6 +1945,7 @@ where
// Anything else
// Start a new attribute in the current tag token. Set that attribute name
// and value to the empty string. Reconsume in the attribute name state.
// We set `None` for `value` to support boolean attributes in AST
_ => {
if let Some(ref mut token) = self.cur_token {
match token {
@ -1950,7 +1953,7 @@ where
| Token::EndTag { attributes, .. } => {
attributes.push(Attribute {
name: "".into(),
value: "".into(),
value: None,
});
}
_ => {}
@ -1970,9 +1973,7 @@ where
// U+000C FORM FEED (FF)
// U+0020 SPACE
// Ignore the character.
Some('\x09' | '\x0a' | '\x0c' | '\x20') => {
self.state = State::BeforeAttributeName;
}
Some('\x09' | '\x0a' | '\x0c' | '\x20') => {}
// U+0022 QUOTATION MARK (")
// Switch to the attribute value (double-quoted) state.
Some('"') => {
@ -2028,10 +2029,16 @@ where
if let Some(attribute) = attributes.last_mut() {
let mut new_value = String::new();
new_value.push_str(&attribute.value);
match &attribute.value {
Some(value) => {
new_value.push_str(value);
}
None => {}
}
new_value.push(REPLACEMENT_CHARACTER);
attribute.value = new_value.into();
attribute.value = Some(new_value.into());
}
}
_ => {}
@ -2054,10 +2061,16 @@ where
if let Some(attribute) = attributes.last_mut() {
let mut new_value = String::new();
new_value.push_str(&attribute.value);
match &attribute.value {
Some(value) => {
new_value.push_str(value);
}
None => {}
}
new_value.push(c);
attribute.value = new_value.into();
attribute.value = Some(new_value.into());
}
}
_ => {}
@ -2095,10 +2108,16 @@ where
if let Some(attribute) = attributes.last_mut() {
let mut new_value = String::new();
new_value.push_str(&attribute.value);
match &attribute.value {
Some(value) => {
new_value.push_str(value);
}
None => {}
}
new_value.push(REPLACEMENT_CHARACTER);
attribute.value = new_value.into();
attribute.value = Some(new_value.into());
}
}
_ => {}
@ -2121,10 +2140,16 @@ where
if let Some(attribute) = attributes.last_mut() {
let mut new_value = String::new();
new_value.push_str(&attribute.value);
match &attribute.value {
Some(value) => {
new_value.push_str(value);
}
None => {}
}
new_value.push(c);
attribute.value = new_value.into();
attribute.value = Some(new_value.into());
}
}
_ => {}
@ -2171,10 +2196,16 @@ where
if let Some(attribute) = attributes.last_mut() {
let mut new_value = String::new();
new_value.push_str(&attribute.value);
match &attribute.value {
Some(value) => {
new_value.push_str(value);
}
None => {}
}
new_value.push(REPLACEMENT_CHARACTER);
attribute.value = new_value.into();
attribute.value = Some(new_value.into());
}
}
_ => {}
@ -2207,10 +2238,16 @@ where
if let Some(attribute) = attributes.last_mut() {
let mut new_value = String::new();
new_value.push_str(&attribute.value);
match &attribute.value {
Some(value) => {
new_value.push_str(value);
}
None => {}
}
new_value.push(c);
attribute.value = new_value.into();
attribute.value = Some(new_value.into());
}
}
_ => {}
@ -4416,10 +4453,16 @@ where
if let Some(attribute) = attributes.last_mut() {
let mut new_value = String::new();
new_value.push_str(&attribute.value);
match &attribute.value {
Some(value) => {
new_value.push_str(value);
}
None => {}
}
new_value.push(c);
attribute.value = new_value.into();
attribute.value = Some(new_value.into());
}
}
_ => {}

View File

@ -56,7 +56,7 @@
5 | <a href=https://www.w3schools.com>This is a link</a>
`----
x StartTag { tag_name: Atom('a' type=inline), self_closing: false, attributes: [Attribute { name: Atom('href' type=inline), value: Atom('https://www.w3schools.com' type=dynamic) }] }
x StartTag { tag_name: Atom('a' type=inline), self_closing: false, attributes: [Attribute { name: Atom('href' type=inline), value: Some(Atom('https://www.w3schools.com' type=dynamic)) }] }
,-[$DIR/tests/fixture/attribute-without-quotes/input.html:5:1]
5 | <a href=https://www.w3schools.com>This is a link</a>
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -415,9 +415,9 @@
8 | <img src="w3schools.jpg" alt="W3Schools.com" width="104" height="142">
`----
x StartTag { tag_name: Atom('img' type=inline), self_closing: false, attributes: [Attribute { name: Atom('src' type=inline), value: Atom('w3schools.jpg' type=dynamic) }, Attribute { name:
| Atom('alt' type=inline), value: Atom('W3Schools.com' type=dynamic) }, Attribute { name: Atom('width' type=inline), value: Atom('104' type=inline) }, Attribute { name: Atom('height' type=inline),
| value: Atom('142' type=inline) }] }
x StartTag { tag_name: Atom('img' type=inline), self_closing: false, attributes: [Attribute { name: Atom('src' type=inline), value: Some(Atom('w3schools.jpg' type=dynamic)) }, Attribute { name:
| Atom('alt' type=inline), value: Some(Atom('W3Schools.com' type=dynamic)) }, Attribute { name: Atom('width' type=inline), value: Some(Atom('104' type=inline)) }, Attribute { name: Atom('height'
| type=inline), value: Some(Atom('142' type=inline)) }] }
,-[$DIR/tests/fixture/images/input.html:8:1]
8 | <img src="w3schools.jpg" alt="W3Schools.com" width="104" height="142">
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -8,7 +8,7 @@
5 | `-> </script>
`----
x StartTag { tag_name: Atom('script' type=inline), self_closing: false, attributes: [Attribute { name: Atom('type' type=static), value: Atom('text/javascript' type=dynamic) }] }
x StartTag { tag_name: Atom('script' type=inline), self_closing: false, attributes: [Attribute { name: Atom('type' type=static), value: Some(Atom('text/javascript' type=dynamic)) }] }
,-[$DIR/tests/fixture/script-cdata/input.html:1:1]
1 | <script type="text/javascript">
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -58,7 +58,7 @@
5 | <h2 title="I'm a header">The title Attribute</h2>
`----
x StartTag { tag_name: Atom('h2' type=inline), self_closing: false, attributes: [Attribute { name: Atom('title' type=inline), value: Atom('I'm a header' type=dynamic) }] }
x StartTag { tag_name: Atom('h2' type=inline), self_closing: false, attributes: [Attribute { name: Atom('title' type=inline), value: Some(Atom('I'm a header' type=dynamic)) }] }
,-[$DIR/tests/fixture/title-attribute/input.html:5:1]
5 | <h2 title="I'm a header">The title Attribute</h2>
: ^^^^^^^^^^^^^^^^^^^^^^^^^
@ -197,7 +197,7 @@
7 | <p title="I'm a tooltip">Mouse over this paragraph, to display the title attribute as a tooltip.</p>
`----
x StartTag { tag_name: Atom('p' type=inline), self_closing: false, attributes: [Attribute { name: Atom('title' type=inline), value: Atom('I'm a tooltip' type=dynamic) }] }
x StartTag { tag_name: Atom('p' type=inline), self_closing: false, attributes: [Attribute { name: Atom('title' type=inline), value: Some(Atom('I'm a tooltip' type=dynamic)) }] }
,-[$DIR/tests/fixture/title-attribute/input.html:7:1]
7 | <p title="I'm a tooltip">Mouse over this paragraph, to display the title attribute as a tooltip.</p>
: ^^^^^^^^^^^^^^^^^^^^^^^^^