Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions tests/test_tinycss2.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,3 +703,37 @@ def test_escape_in_function_name():
function, = parse_component_value_list('\\dddf()')
assert function.type == 'function'
assert function.name == '\udddf'


def test_serialize_dimension_scientific_notation():
# A dimension whose unit begins with 'e'/'E' followed by an optional sign
# and a digit must not be serialized in a way that merges back into a
# scientific-notation number-token, and the unit's case must be preserved.
# e.g. the dimension "1" + unit "e3" must not serialize to "1e3" (== 1000),
# and the dimension "1" + unit "E" must not become unit "e".
cases = [
(r'1\65 ', 1, 'e'), # unit 'e'
(r'1\45 ', 1, 'E'), # unit 'E': case must survive round-trip
(r'1\65 3', 1, 'e3'), # must not merge into number 1e3 == 1000
(r'1\45 3', 1, 'E3'),
(r'1\65 05', 1, 'e05'), # must not merge into number 1e05 == 100000
(r'1\65 -3', 1, 'e-3'), # must not merge into number 1e-3 == 0.001
(r'1\45 -3', 1, 'E-3'),
]
for source, int_value, unit in cases:
token, = parse_component_value_list(source)
assert token.type == 'dimension'
assert token.int_value == int_value
assert token.unit == unit
reparsed, = parse_component_value_list(token.serialize())
assert reparsed.type == 'dimension', (
f'{source!r} serialized to {token.serialize()!r} which re-parsed '
f'as a {reparsed.type}-token')
assert reparsed.value == token.value
assert reparsed.int_value == int_value
assert reparsed.unit == unit

# Units that cannot form scientific notation stay unescaped (no over-escape).
assert parse_component_value_list('1em')[0].serialize() == '1em'
assert parse_component_value_list('1ex')[0].serialize() == '1ex'
assert parse_component_value_list('5EX')[0].serialize() == '5EX'
14 changes: 11 additions & 3 deletions tinycss2/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,10 +566,18 @@ def __init__(self, line, column, value, int_value, representation, unit):

def _serialize_to(self, write):
write(self.representation)
# Disambiguate with scientific notation
# Disambiguate with scientific notation.
# A unit starting with 'e' or 'E' followed by an optional sign and a
# digit would otherwise merge with the number into a single
# scientific-notation number-token when re-parsed
# (e.g. '1' + 'e3' -> '1e3', or '1' + 'E-3' -> '1E-3').
# Escaping the leading letter prevents this; the escape must use the
# letter's own code point so that its case is preserved.
unit = self.unit
if unit in ('e', 'E') or unit.startswith(('e-', 'E-')):
write('\\65 ')
first = unit[0]
if first in ('e', 'E') and (
len(unit) == 1 or unit[1] in '+-0123456789'):
write('\\%X ' % ord(first))
write(serialize_name(unit[1:]))
else:
write(serialize_identifier(unit))
Expand Down
Loading