diff --git a/src/css/Properties.js b/src/css/Properties.js index 4764a1b7..a58164d9 100644 --- a/src/css/Properties.js +++ b/src/css/Properties.js @@ -309,11 +309,11 @@ var Properties = { "-ms-flex-wrap" : "nowrap | wrap | wrap-reverse", "float" : "left | right | none | inherit", "float-offset" : 1, - "font" : 1, - "font-family" : 1, + "font" : " | caption | icon | menu | message-box | small-caption | status-bar | inherit", + "font-family" : " | inherit", "font-feature-settings" : " | normal | inherit", "font-kerning" : "auto | normal | none | initial | inherit | unset", - "font-size" : " | | | | inherit", + "font-size" : " | inherit", "font-size-adjust" : " | none | inherit", "font-stretch" : "normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit", "font-style" : "normal | italic | oblique | inherit", @@ -376,7 +376,7 @@ var Properties = { //L "left" : " | inherit", "letter-spacing" : " | normal | inherit", - "line-height" : " | | | normal | inherit", + "line-height" : " | inherit", "line-break" : "auto | loose | normal | strict", "line-stacking" : 1, "line-stacking-ruby" : "exclude-ruby | include-ruby", diff --git a/src/css/ValidationTypes.js b/src/css/ValidationTypes.js index 538cd710..7d528441 100644 --- a/src/css/ValidationTypes.js +++ b/src/css/ValidationTypes.js @@ -214,6 +214,16 @@ var ValidationTypes = { "": function(part){ return (part.type == "function" && /^[A-Z0-9]{4}$/i.test(part)); + }, + + "": function(part){ + var result = this[""](part) || this[""](part) || this[""](part) || this[""](part); + return result; + }, + + "": function(part){ + var result = this[""](part) || this[""](part) || this[""](part) || ValidationTypes.isLiteral(part, "normal"); + return result; } }, @@ -428,6 +438,101 @@ var ValidationTypes = { } return result; + }, + + "": function(expression){ + // identifier: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + var part, + evenness = expression._i % 2, + partResult, + expressionResult = true, + isEscaped = function (text) { + var result = true; + for (var i = 1; i < text.length; i++) { + // 47 is slash, 92 is backslash + if ( /[\x20-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]/.test(text.charAt(i)) && text.charCodeAt(i) != 92 ) { + result = result && text.charCodeAt(i-1) == 92 ; + } + } + return result; + }, + isQuoted = function (text) { + return (text.charAt(0) == "'" && text.charAt(text.length-1) == "'") || (text.charAt(0) == '"' && text.charAt(text.length-1) == '"'); + }; + + while (expression.hasNext()) { + part = expression.next(); + if (expression._i % 2 == evenness) { + // must be a seperator (thus there is a next value) + partResult = part.value == ',' && expression.hasNext(); + } else { + partResult = part.type != 'operator' && ( + part.type == 'identifier' || + (part.type == 'color' && !(/[^A-Za-z]/.test(part.text))) || + (part.type == 'unknown' && isEscaped(part.text)) || + (part.type == 'string' && isQuoted(part.text)) + ) && !( + /^--/.test(part.text) || /^\d/.test(part.text) || /^-\d/.test(part.text) + ); + } + expressionResult = expressionResult && partResult; + } + return expressionResult; + }, + + "": function(expression){ + // font [ [ <‘font-style’> || || <‘font-weight’> || <‘font-stretch’ ]? <‘font-size’> [ / <‘line-height’> ]? <‘font-family’> ] + var + appearances = {style: 0, variant: 0, weight: 0, stretch: 0}, + foundThisIteration, + part, + result; + + // check font-appearance, they are optional but may not be doubled + do { + part = expression.next(); + foundThisIteration = 0; + if (ValidationTypes.isLiteral(part, Properties["font-style"]) && part.value != 'inherit') { + appearances.style++; + foundThisIteration = appearances.style; + } + // + if (ValidationTypes.isLiteral(part, "normal | small-caps")) { + appearances.variant++; + foundThisIteration = appearances.variant; + } + if (ValidationTypes.isLiteral(part, Properties["font-weight"]) && part.value != 'inherit') { + appearances.weight++; + foundThisIteration = appearances.weight; + } + if (ValidationTypes.isLiteral(part, Properties["font-stretch"]) && part.value != 'inherit') { + appearances.stretch++; + foundThisIteration = appearances.stretch; + } + } while (foundThisIteration == 1); + + // validate font-appearance + result = (foundThisIteration <= 1); + // last one was not an appearance + part = expression.previous(); + + // evaluate font-size, the first obligation + result = result && ValidationTypes.isType(expression, ""); + + part = expression.next(); + if (part == '/') { + result = result && ValidationTypes.isType(expression, ""); + } else if (part) { + expression.previous(); + } else { + result = false; + } + + // font-family is the latter obligation + result = result && ValidationTypes.isType(expression, ""); + + return result; + } } }; diff --git a/tests/css/Validation.js b/tests/css/Validation.js index adcae30d..daa54d22 100644 --- a/tests/css/Validation.js +++ b/tests/css/Validation.js @@ -646,7 +646,73 @@ } })); + suite.add(new ValidationTestCase({ + property: "font", + + valid: [ + "italic small-caps 300 1.3em/10% Genova, 'Comic Sans', sans-serif", + "1.3em Shorties, sans-serif", + "12px monospace", + "caption;", + "status-bar", + "inherit;", + ], + + invalid: { + "italic oblique bold 1.3em/10% Genova, 'Comic Sans', sans-serif" : "Expected end of value but found 'oblique'.", + "0.9em Nirwana, 'Comic Sans', sans-serif bold" : "Expected ( | caption | icon | menu | message-box | small-caption | status-bar | inherit) but found '0.9em Nirwana , 'Comic Sans' , sans-serif bold'.", + "'Helvetica Neue', sans-serif 1.2em" : "Expected ( | caption | icon | menu | message-box | small-caption | status-bar | inherit) but found ''Helvetica Neue' , sans-serif 1.2em'.", + "1.3em" : "Expected ( | caption | icon | menu | message-box | small-caption | status-bar | inherit) but found '1.3em'.", + "cursive;" : "Expected ( | caption | icon | menu | message-box | small-caption | status-bar | inherit) but found 'cursive'.", + "'Dormant', sans-serif;" : "Expected ( | caption | icon | menu | message-box | small-caption | status-bar | inherit) but found ''Dormant' , sans-serif'." + } + })); + suite.add(new ValidationTestCase({ + property: "font-family", + + valid: [ + "Futura, sans-serif", + '"New Century Schoolbook", serif', + "'21st Century', fantasy", + "serif", + "sans-serif", + "cursive", + "fantasy", + "monospace", + // solve problem by quoting + "'Red/Black', sans-serif", + '"Lucida\\", Grande", sans-serif', + "'Ahem!}', sans-serif", + '"test@foo", sans-serif', + "'#POUND', sans-serif", + "'Hawaii 5-0', sans-serif", + // solve problem by escaping + "Red\\/Black, sans-serif", +// accepted in the wild but rejected by the unittest +// '\\"Lucida\\", Grande, sans-serif', // Unexpected error: Expected RBRACE at line 1, col 21. + "Ahem\\!, sans-serif", + "test\\@foo, sans-serif", +// rejected both in the wild and by the unittest +// "\\#POUND, sans-serif", // Unexpected error: Expected a hex color but found '#POUND' at line 1, col 20. + "Hawaii\\ 5\\-0, sans-serif", + "yellowgreen" + ], + + invalid: { + "--Futura, sans-serif" : "Expected ( | inherit) but found '--Futura , sans-serif'.", +// errors both in the wild by the unittest +// "47Futura, sans-serif" : "Unexpected token '47Futura' at line 1, col 20.", +// "-7Futura, sans-serif" : "Unexpected token '7Futura' at line 1, col 21.", + "Red/Black, sans-serif" : "Expected ( | inherit) but found 'Red / Black , sans-serif'.", + "'Lucida' Grande, sans-serif" : "Expected ( | inherit) but found ''Lucida' Grande , sans-serif'.", +// errors both in the wild by the unittest +// "Ahem!, sans-serif" : "Expected RBRACE at line 59, col 22. This rule looks for recoverable syntax errors.", +// "test@foo, sans-serif" : "Expected RBRACE at line 60, col 22. This rule looks for recoverable syntax errors.", +// "#POUND, sans-serif" : "Expected a hex color but found '#POUND' at line 1, col 20.", + "Hawaii 5-0, sans-serif" : "Expected ( | inherit) but found 'Hawaii 5 -0 , sans-serif'." + } + })); suite.add(new ValidationTestCase({ property: "min-height",