diff --git a/src/grammar.ne b/src/grammar.ne index debbc08..75285db 100644 --- a/src/grammar.ne +++ b/src/grammar.ne @@ -4,6 +4,7 @@ const at = index => d => d && d[index]; const pick = indices => d => indices.map(index => d[index]); const text = d => Array.isArray(d) ? d.map(text).join('') : d; + const getCharCode = d => String.fromCharCode(parseInt(text(d[1]), 16)); const transformArg1 = d => ({ [d[0].join('')]: d[2][0] }); const defaultOptional = (value, defaultValue) => value === null ? defaultValue : value; @@ -58,21 +59,35 @@ decimal number -> "-":? (int decimal | int | decimal) {% d => Number(text(d)) %} +hex -> [a-fA-F0-9] {% text %} + angle -> number ("deg" | "rad") {% text %} ident -> ("-":? [_A-Za-z] [_A-Za-z0-9-]:*) {% text %} -# ident -> [^ ]:+ {% text %} color - -> "#" ([a-fA-F0-9]:*) {% text %} + -> "#" (hex:*) {% text %} | ("rgb" | "hsl" | "hsv") ("a":?) "(" ([^)]:+) ")" {% text %} | ([A-Za-z]:+) {% (d, location, reject) => { const name = text(d).toLowerCase(); return cssColorList.indexOf(name) !== -1 ? name : reject; } %} -_ -> [ \t\n\r]:* {% () => null %} -__ -> [ \t\n\r]:+ {% () => null %} +escapeChar + -> "\\" (hex) " " {% getCharCode %} + | "\\" (hex hex) " " {% getCharCode %} + | "\\" (hex hex hex) " " {% getCharCode %} + | "\\" (hex hex hex hex) " " {% getCharCode %} + | "\\" (hex hex hex hex hex) " " {% getCharCode %} + | "\\" (hex hex hex hex hex hex) {% getCharCode %} + | "\\" [^a-fA-F0-9] {% d => text(d[1]) %} + +string + -> "\"" (escapeChar | [^"\\]):* "\"" {% d => text(d[1]) %} + | "'" (escapeChar | [^'\\]):* "'" {% d => text(d[1]) %} + +_ -> [ \t\n\r]:* {% text %} +__ -> [ \t\n\r]:+ {% text %} anyOrder2[a, b] -> $a __ $b {% d => [d[0][0][0], d[2][0][0]] %} @@ -170,17 +185,18 @@ flex return { $merge: { flexGrow, flexShrink, flexBasis } }; } %} +fontFamily + -> string {% at(0) %} + | (ident (_ ident):*) {% text %} + fontFontStyle -> ("normal" | "italic") {% text %} fontFontVariantCss21 -> "normal" {% () => [] %} | "small-caps" {% () => ['small-caps'] %} fontFontWeight -> ("normal" | "bold" | [1-9] "00") {% text %} -fontFontFamily - -> "\"" ("\\" . | [^"]):* "\"" {% d => text(d[1]) %} - | "'" ("\\" . | [^']):* "'" {% d => text(d[1]) %} font -> anyOrderOptional3AllowNull[fontFontStyle, fontFontVariantCss21, fontFontWeight] _ number (_ "/" _ number):? __ - fontFontFamily {% d => { + fontFamily {% d => { const options = { fontStyle: defaultOptional(d[0][0], 'normal'), fontVariant: defaultOptional(d[0][1], []), diff --git a/src/index.js b/src/index.js index 4434420..fd785eb 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ const transforms = [ 'flex', 'flexFlow', 'font', + 'fontFamily', 'fontVariant', 'fontWeight', 'margin', diff --git a/src/index.test.js b/src/index.test.js index c30d6ad..395254d 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -288,6 +288,57 @@ it('omits line height if not specified', () => runTest([ fontVariant: [], })); +it('transforms font without quotes', () => runTest([ + ['font', 'bold italic small-caps 16/18 Helvetica Neue'], +], { + fontFamily: 'Helvetica Neue', + fontSize: 16, + fontWeight: 'bold', + fontStyle: 'italic', + fontVariant: ['small-caps'], + lineHeight: 18, +})); + +it('transforms font-family with double quotes', () => runTest([ + ['font-family', '"Helvetica Neue"'], +], { + fontFamily: 'Helvetica Neue', +})); + +it('transforms font-family with single quotes', () => runTest([ + ['font-family', '\'Helvetica Neue\''], +], { + fontFamily: 'Helvetica Neue', +})); + +it('transforms font-family without quotes', () => runTest([ + ['font-family', 'Helvetica Neue'], +], { + fontFamily: 'Helvetica Neue', +})); + +it('transforms font-family with quotes with otherwise invalid values', () => runTest([ + ['font-family', '"Goudy Bookletter 1911"'], +], { + fontFamily: 'Goudy Bookletter 1911', +})); + +it('transforms font-family with quotes with escaped values', () => runTest([ + ['font-family', '"test\\A test"'], +], { + fontFamily: 'test\ntest', +})); + +it('transforms font-family with quotes with escaped quote', () => runTest([ + ['font-family', '"test\\"test"'], +], { + fontFamily: 'test"test', +})); + +it('does not transform invalid unquoted font-family', () => { + expect(() => transformCss([['font-family', 'Goudy Bookletter 1911']])).toThrow(); +}); + it('allows blacklisting shorthands', () => { const actualStyles = transformCss([['border-radius', '50']], ['borderRadius']); expect(actualStyles).toEqual({ borderRadius: 50 });