diff --git a/src/css/Properties.js b/src/css/Properties.js index 6d781d23..6cad177f 100644 --- a/src/css/Properties.js +++ b/src/css/Properties.js @@ -90,7 +90,7 @@ var Properties = { "background-color" : " | inherit", "background-image" : { multi: "", comma: true }, "background-origin" : { multi: "", comma: true }, - "background-position" : { multi: "", comma: true }, + "background-position" : " | inherit", "background-repeat" : { multi: "", comma: true }, "background-size" : { multi: "", comma: true }, "baseline-shift" : "baseline | sub | super | | ", @@ -104,8 +104,8 @@ var Properties = { "border" : " || || ", "border-bottom" : " || || ", "border-bottom-color" : " | inherit", - "border-bottom-left-radius" : "", - "border-bottom-right-radius" : "", + "border-bottom-left-radius" : " | inherit", + "border-bottom-right-radius" : " | inherit", "border-bottom-style" : "", "border-bottom-width" : "", "border-collapse" : "collapse | separate | inherit", @@ -198,8 +198,8 @@ var Properties = { "border-style" : { multi: "", max: 4 }, "border-top" : " || || ", "border-top-color" : " | inherit", - "border-top-left-radius" : "", - "border-top-right-radius" : "", + "border-top-left-radius" : " | inherit", + "border-top-right-radius" : " | inherit", "border-top-style" : "", "border-top-width" : "", "border-width" : { multi: "", max: 4 }, @@ -285,7 +285,9 @@ var Properties = { "empty-cells" : "show | hide | inherit", //F - "fill" : " | inherit", + "fill" : " | inherit", + "fill-opacity" : " | inherit", + "fill-rule" : "nonzero | evenodd | inherit", "filter" : " | none", "fit" : "fill | hidden | meet | slice", "fit-position" : 1, @@ -420,8 +422,8 @@ var Properties = { //O "object-fit" : "fill | contain | cover | none | scale-down", - "object-position" : "", - "opacity" : " | inherit", + "object-position" : " | inherit", + "opacity" : " | inherit", "order" : "", "-webkit-order" : "", "orphans" : " | inherit", @@ -489,6 +491,14 @@ var Properties = { "src" : 1, "stress" : 1, "string-set" : 1, + "stroke" : " | inherit", + "stroke-dasharray" : "none | | inherit", + "stroke-dashoffset" : " | | inherit", + "stroke-linecap" : "butt | round | square | inherit", + "stroke-linejoin" : "miter | round | bevel | inherit", + "stroke-miterlimit" : " | inherit", + "stroke-opacity" : " | inherit", + "stroke-width" : " | | inherit", "table-layout" : "auto | fixed | inherit", "tab-size" : " | ", diff --git a/src/css/PropertyValueIterator.js b/src/css/PropertyValueIterator.js index 01cbdb9b..45439605 100644 --- a/src/css/PropertyValueIterator.js +++ b/src/css/PropertyValueIterator.js @@ -122,3 +122,11 @@ PropertyValueIterator.prototype.restore = function(){ } }; +/** + * Drops the last saved bookmark. + * @return {void} + * @method drop + */ +PropertyValueIterator.prototype.drop = function() { + this._marks.pop(); +}; diff --git a/src/css/Validation.js b/src/css/Validation.js index 038f650c..23bd4ce2 100644 --- a/src/css/Validation.js +++ b/src/css/Validation.js @@ -53,7 +53,7 @@ var Validation = { part = expression.peek(); throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col); } else { - throw new ValidationError("Expected (" + types + ") but found '" + value + "'.", value.line, value.col); + throw new ValidationError("Expected (" + ValidationTypes.describe(types) + ") but found '" + value + "'.", value.line, value.col); } } else if (expression.hasNext()) { part = expression.next(); diff --git a/src/css/ValidationTypes.js b/src/css/ValidationTypes.js index dbb5725b..3a161662 100644 --- a/src/css/ValidationTypes.js +++ b/src/css/ValidationTypes.js @@ -1,6 +1,249 @@ //This file will likely change a lot! Very experimental! -/*global ValidationError */ -var ValidationTypes = { +var ValidationTypes; + +/** + * This class implements a combinator library for matcher functions. + * The combinators are described at: + * https://developer.mozilla.org/en-US/docs/Web/CSS/Value_definition_syntax#Component_value_combinators + */ +var Matcher = function(matchFunc, toString) { + this.match = function(expression) { + // Save/restore marks to ensure that failed matches always restore + // the original location in the expression. + var result; + expression.mark(); + result = matchFunc(expression); + if (result) { + expression.drop(); + } else { + expression.restore(); + } + return result; + }; + this.toString = typeof toString === "function" ? toString : function() { + return toString; + }; +}; + +/** Precedence table of combinators. */ +Matcher.prec = { + MOD: 5, + SEQ: 4, + ANDAND: 3, + OROR: 2, + ALT: 1 +}; + +/** + * Convert a string to a matcher (parsing simple alternations), + * or do nothing if the argument is already a matcher. + */ +Matcher.cast = function(m) { + if (m instanceof Matcher) { + return m; + } + if (/ \| /.test(m)) { + return Matcher.alt.apply(Matcher, m.split(" | ")); + } + return Matcher.fromType(m); +}; + +/** + * Create a matcher for a single type. + */ +Matcher.fromType = function(type) { + return new Matcher(function(expression) { + return expression.hasNext() && ValidationTypes.isType(expression, type); + }, type); +}; + +/** + * Create a matcher for one or more juxtaposed words, which all must + * occur, in the given order. + */ +Matcher.seq = function() { + var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); + if (ms.length === 1) { return ms[0]; } + return new Matcher(function(expression) { + var i, result = true; + for (i = 0; result && i < ms.length; i++) { + result = ms[i].match(expression); + } + return result; + }, function(prec) { + var p = Matcher.prec.SEQ; + var s = ms.map(function(m) { return m.toString(p); }).join(" "); + if (prec > p) { s = "[ " + s + " ]"; } + return s; + }); +}; + +/** + * Create a matcher for one or more alternatives, where exactly one + * must occur. + */ +Matcher.alt = function() { + var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); + if (ms.length === 1) { return ms[0]; } + return new Matcher(function(expression) { + var i, result = false; + for (i = 0; !result && i < ms.length; i++) { + result = ms[i].match(expression); + } + return result; + }, function(prec) { + var p = Matcher.prec.ALT; + var s = ms.map(function(m) { return m.toString(p); }).join(" | "); + if (prec > p) { s = "[ " + s + " ]"; } + return s; + }); +}; + +/** + * Create a matcher for two or more options. This implements the + * double bar (||) and double ampersand (&&) operators, as well as + * variants of && where some of the alternatives are optional. + * This will backtrack through even successful matches to try to + * maximize the number of items matched. + */ +Matcher.many = function(required) { + var ms = Array.prototype.slice.call(arguments, 1).reduce(function(acc, v) { + if (v.expand) { + // Insert all of the options for the given complex rule as + // individual options. + acc.push.apply(acc, ValidationTypes.complex[v.expand].options); + } else { + acc.push(Matcher.cast(v)); + } + return acc; + }, []); + if (required === true) { required = ms.map(function() { return true; }); } + var result = new Matcher(function(expression) { + var seen = [], max = 0, pass = 0; + var success = function(matchCount) { + if (pass === 0) { + max = Math.max(matchCount, max); + return matchCount === ms.length; + } else { + return matchCount === max; + } + }; + var tryMatch = function(matchCount) { + for (var i = 0; i < ms.length; i++) { + if (seen[i]) { continue; } + expression.mark(); + if (ms[i].match(expression)) { + seen[i] = true; + // Increase matchCount iff this was a required element + // (or if all the elements are optional) + if (tryMatch(matchCount + ((required === false || required[i]) ? 1 : 0))) { + expression.drop(); + return true; + } + // Backtrack: try *not* matching using this rule, and + // let's see if it leads to a better overall match. + expression.restore(); + seen[i] = false; + } else { + expression.drop(); + } + } + return success(matchCount); + }; + if (!tryMatch(0)) { + // Couldn't get a complete match, retrace our steps to make the + // match with the maximum # of required elements. + pass++; + tryMatch(0); + } + + if (required === false) { + return (max > 0); + } + // Use finer-grained specification of which matchers are required. + for (var i = 0; i < ms.length; i++) { + if (required[i] && !seen[i]) { + return false; + } + } + return true; + }, function(prec) { + var p = (required === false) ? Matcher.prec.OROR : Matcher.prec.ANDAND; + var s = ms.map(function(m, i) { + if (required !== false && !required[i]) { + return m.toString(Matcher.prec.MOD) + "?"; + } + return m.toString(p); + }).join(required === false ? " || " : " && "); + if (prec > p) { s = "[ " + s + " ]"; } + return s; + }); + result.options = ms; + return result; +}; + +/** + * Create a matcher for two or more options, where all options are + * mandatory but they may appear in any order. + */ +Matcher.andand = function() { + var args = Array.prototype.slice.call(arguments); + args.unshift(true); + return Matcher.many.apply(Matcher, args); +}; + +/** + * Create a matcher for two or more options, where options are + * optional and may appear in any order, but at least one must be + * present. + */ +Matcher.oror = function() { + var args = Array.prototype.slice.call(arguments); + args.unshift(false); + return Matcher.many.apply(Matcher, args); +}; + +/** Instance methods on Matchers. */ +Matcher.prototype = { + constructor: Matcher, + // These are expected to be overridden in every instance. + match: function(expression) { throw new Error("unimplemented"); }, + toString: function() { throw new Error("unimplemented"); }, + // This returns a standalone function to do the matching. + func: function() { return this.match.bind(this); }, + // Basic combinators + then: function(m) { return Matcher.seq(this, m); }, + or: function(m) { return Matcher.alt(this, m); }, + andand: function(m) { return Matcher.many(true, this, m); }, + oror: function(m) { return Matcher.many(false, this, m); }, + // Component value multipliers + star: function() { return this.braces(0, Infinity, "*"); }, + plus: function() { return this.braces(1, Infinity, "+"); }, + question: function() { return this.braces(0, 1, "?"); }, + hash: function() { + return this.braces(1, Infinity, "#", Matcher.cast(",")); + }, + braces: function(min, max, marker, optSep) { + var m1 = this, m2 = optSep ? optSep.then(this) : this; + if (!marker) { + marker = "{" + min + "," + max + "}"; + } + return new Matcher(function(expression) { + var result = true, i; + for (i = 0; i < max; i++) { + if (i > 0 && optSep) { + result = m2.match(expression); + } else { + result = m1.match(expression); + } + if (!result) { break; } + } + return (i >= min); + }, function() { return m1.toString(Matcher.prec.MOD) + marker; }); + } +}; + +ValidationTypes = { isLiteral: function (part, literals) { var text = part.text.toString().toLowerCase(), @@ -8,7 +251,10 @@ var ValidationTypes = { i, len, found = false; for (i=0,len=args.length; i < len && !found; i++){ - if (text === args[i].toLowerCase()){ + if (args[i].slice(-2) === "()"){ + found = (part.type === "function" && + part.name === args[i].slice(0, -2)); + } else if (text === args[i].toLowerCase()){ found = true; } } @@ -24,6 +270,13 @@ var ValidationTypes = { return !!this.complex[type]; }, + describe: function(type) { + if (this.complex[type] instanceof Matcher) { + return this.complex[type].toString(0); + } + return type; + }, + /** * Determines if the next part(s) of the given expression * are any of the given types. @@ -72,6 +325,8 @@ var ValidationTypes = { if (result) { expression.next(); } + } else if (this.complex[type] instanceof Matcher) { + result = this.complex[type].match(expression); } else { result = this.complex[type](expression); } @@ -92,7 +347,7 @@ var ValidationTypes = { }, "": function(part){ - return part.type === "function" && part.name === "attr"; + return ValidationTypes.isLiteral(part, "attr()"); }, "": function(part){ @@ -108,7 +363,7 @@ var ValidationTypes = { }, "": function(part){ - return part.type === "function" && part.name === "content"; + return ValidationTypes.isLiteral(part, "content()"); }, "": function(part){ @@ -132,10 +387,39 @@ var ValidationTypes = { return part.type === "color" || part == "transparent" || part == "currentColor"; }, + // The SVG spec doesn't include "currentColor" or "transparent" as a color. + "": function(part) { + return part.type === "color"; + }, + + "": function(part){ + /* ex.: + https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/local + icc-color(acmecmyk, 0.11, 0.48, 0.83, 0.00) + cielab(62.253188, 23.950124, 48.410653) + cielch(62.253188, 54.011108, 63.677091) + icc-color(FooColors, Sandy23C) + http://www.w3.org/TR/2009/WD-SVGColor12-20091001/#iccnamedcolor + ~"icc-color(" name (comma-wsp number)+ ")" + ~"icc-named-color(" name comma-wsp namedColor ")" + ~"cielab(" lightness comma-wsp a-value comma-wsp b-value ")" + ~"cielchab(" lightness comma-wsp chroma comma-wsp hue ")" + */ + return ValidationTypes.isLiteral(part, "cielab() | cielch() | cielchab() | icc-color() | icc-named-color()"); + }, + "": function(part){ return part.type === "number" || this[""](part); }, + "": function(part){ + return this[""](part) && part.value >= 1; + }, + + "": function(part){ + return this[""](part) && part.value >= 0 && part.value <= 1; + }, + "": function(part){ return part.type === "integer"; }, @@ -186,7 +470,7 @@ var ValidationTypes = { }, "": function(part){ - return part.type === "function" && (part.name === "rect" || part.name === "inset-rect"); + return ValidationTypes.isLiteral(part, "rect() | inset-rect()"); }, "": function(part){ @@ -194,9 +478,7 @@ var ValidationTypes = { // circle() = circle( []? [at ]? ) // ellipse() = ellipse( [{2}]? [at ]? ) // polygon() = polygon( [,]? [ ]# ) - return part.type === "function" && ( - part.name === "inset" || part.name === "circle" || part.name === "ellipse" || part.name === "polygon" - ); + return ValidationTypes.isLiteral(part, "inset() | circle() | ellipse() | polygon()"); }, "": function(part) { @@ -240,292 +522,106 @@ var ValidationTypes = { }, "": function(part){ - return part.type === "function" && ( - part.name === 'blur' || - part.name === 'brightness' || - part.name === 'contrast' || - part.name === 'custom' || // Not actually in formal spec. - part.name === 'drop-shadow' || - part.name === 'grayscale' || - part.name === 'hue-rotate' || - part.name === 'invert' || - part.name === 'opacity' || - part.name === 'saturate' || - part.name === 'sepia'); + // custom() isn't actually in the spec + return ValidationTypes.isLiteral( + part, "blur() | brightness() | contrast() | custom() | " + + "drop-shadow() | grayscale() | hue-rotate() | invert() | " + + "opacity() | saturate() | sepia()"); } }, complex: { - "": function(expression){ - var result = false, - numeric = " | ", - xDir = "left | right", - yDir = "top | bottom", - count = 0; - - while (expression.peek(count) && expression.peek(count).text !== ",") { - count++; - } - -/* - = [ - [ left | center | right | top | bottom | | ] -| - [ left | center | right | | ] - [ top | center | bottom | | ] -| - [ center | [ left | right ] [ | ]? ] && - [ center | [ top | bottom ] [ | ]? ] -] -*/ - - if (count < 3) { - if (ValidationTypes.isAny(expression, xDir + " | center | " + numeric)) { - result = true; - ValidationTypes.isAny(expression, yDir + " | center | " + numeric); - } else if (ValidationTypes.isAny(expression, yDir)) { - result = true; - ValidationTypes.isAny(expression, xDir + " | center"); - } - } else { - if (ValidationTypes.isAny(expression, xDir)) { - if (ValidationTypes.isAny(expression, yDir)) { - result = true; - ValidationTypes.isAny(expression, numeric); - } else if (ValidationTypes.isAny(expression, numeric)) { - if (ValidationTypes.isAny(expression, yDir)) { - result = true; - ValidationTypes.isAny(expression, numeric); - } else if (ValidationTypes.isAny(expression, "center")) { - result = true; - } - } - } else if (ValidationTypes.isAny(expression, yDir)) { - if (ValidationTypes.isAny(expression, xDir)) { - result = true; - ValidationTypes.isAny(expression, numeric); - } else if (ValidationTypes.isAny(expression, numeric)) { - if (ValidationTypes.isAny(expression, xDir)) { - result = true; - ValidationTypes.isAny(expression, numeric); - } else if (ValidationTypes.isAny(expression, "center")) { - result = true; - } - } - } else if (ValidationTypes.isAny(expression, "center")) { - if (ValidationTypes.isAny(expression, xDir + " | " + yDir)) { - result = true; - ValidationTypes.isAny(expression, numeric); - } - } - } - - return result; - }, - - "": function(expression){ - // = [ | | auto ]{1,2} | cover | contain - var result = false, - numeric = " | | auto"; - - if (ValidationTypes.isAny(expression, "cover | contain")) { - result = true; - } else if (ValidationTypes.isAny(expression, numeric)) { - result = true; - ValidationTypes.isAny(expression, numeric); - } - - return result; - }, - - "": function(expression){ - return ValidationTypes.isAny(expression, ""); - }, - - "": function(expression) { - // || - var result = false; - - if (ValidationTypes.isType(expression, "")) { - result = true; - if (expression.hasNext()) { - result = ValidationTypes.isType(expression, ""); - } - } else if (ValidationTypes.isType(expression, "")) { - result = true; - if (expression.hasNext()) { - result = ValidationTypes.isType(expression, ""); - } - } - - return result && !expression.hasNext(); - - }, - - "": function(expression){ - var result, part, i; - for (i = 0, result = true; result && expression.hasNext(); i++) { - result = ValidationTypes.isAny(expression, " | "); - } - - if (i > 1 && !result) { - // More precise error message if we fail after the first - // parsed . - part = expression.peek(); - throw new ValidationError("Expected ( | ) but found '" + part.text + "'.", part.line, part.col); - } - - return result; - - }, - - "": function(expression){ - //repeat-x | repeat-y | [repeat | space | round | no-repeat]{1,2} - var result = false, - values = "repeat | space | round | no-repeat", - part; - - if (expression.hasNext()){ - part = expression.next(); - - if (ValidationTypes.isLiteral(part, "repeat-x | repeat-y")) { - result = true; - } else if (ValidationTypes.isLiteral(part, values)) { - result = true; - - if (expression.hasNext() && ValidationTypes.isLiteral(expression.peek(), values)) { - expression.next(); - } - } - } - - return result; - - }, - - "": function(expression) { - //inset? && [ {2,4} && ? ] - var result = false, - count = 0, - inset = false, - color = false; - - if (expression.hasNext()) { - - if (ValidationTypes.isAny(expression, "inset")){ - inset = true; - } - - if (ValidationTypes.isAny(expression, "")) { - color = true; - } - - while (ValidationTypes.isAny(expression, "") && count < 4) { - count++; - } - - - if (expression.hasNext()) { - if (!color) { - ValidationTypes.isAny(expression, ""); - } - - if (!inset) { - ValidationTypes.isAny(expression, "inset"); - } - - } - - result = (count >= 2 && count <= 4); - - } - - return result; - }, - - "": function(expression) { - //[ | ] [ | ]? - var result = false, - simple = " | | inherit"; - - if (ValidationTypes.isAny(expression, simple)){ - result = true; - ValidationTypes.isAny(expression, simple); - } - - return result; - }, - - "": function(expression) { - // http://www.w3.org/TR/2014/WD-css-flexbox-1-20140325/#flex-property - // none | [ ? || ] - // Valid syntaxes, according to https://developer.mozilla.org/en-US/docs/Web/CSS/flex#Syntax - // * none - // * - // * - // * - // * - // * - // * inherit - var part, - result = false; - if (ValidationTypes.isAny(expression, "none | inherit")) { - result = true; - } else { - if (ValidationTypes.isType(expression, "")) { - if (expression.peek()) { - if (ValidationTypes.isType(expression, "")) { - if (expression.peek()) { - result = ValidationTypes.isType(expression, ""); - } else { - result = true; - } - } else if (ValidationTypes.isType(expression, "")) { - result = expression.peek() === null; - } - } else { - result = true; - } - } else if (ValidationTypes.isType(expression, "")) { - result = true; - } - } - - if (!result) { - // Generate a more verbose error than "Expected ..." - part = expression.peek(); - throw new ValidationError("Expected (none | [ ? || ]) but found '" + expression.value.text + "'.", part.line, part.col); - } - - return result; - }, - - "": function(expression) { - // none | [ underline || overline || line-through || blink ] | inherit - var part, - result, - someOf = "[ underline || overline || line-through || blink ]", - identifiers = {}, - found; - - do { - part = expression.next(); - found = 0; - if (someOf.indexOf(part) > -1) { - if (!identifiers[part]) { - identifiers[part] = 0; - } - identifiers[part]++; - found = identifiers[part]; - } - } while (found === 1 && expression.hasNext()); - - result = found === 1 && !expression.hasNext(); - if (found === 0 && JSON.stringify(identifiers) === '{}') { - expression.previous(); - } - return result; - } + "": Matcher.cast("").hash(), + + "": + // = [ | | auto ]{1,2} | cover | contain + Matcher.alt("cover", "contain", Matcher.cast(" | | auto").braces(1,2)), + + "": Matcher.cast(""), + + "": + // || + Matcher.cast("").oror(""), + + "": + // "list of comma and/or white space separated s and + // s". We use to enforce the + // nonnegative constraint. + Matcher.cast("") + .braces(1, Infinity, "#", Matcher.cast(",").question()), + + "": + // [ | ]+ + Matcher.cast(" | ").plus(), + + "": + // none | currentColor | []? | + // [ none | currentColor | []? ]? + + // Note that here is "as defined in the SVG spec", which + // is more restrictive that the defined in the CSS spec. + Matcher.alt("", + Matcher.seq("", Matcher.cast("").question())), + // Helper definition for above. + "": + Matcher.alt("none", "currentColor", + Matcher.seq("", + Matcher.cast("").question())), + + "": + // = [ + // [ left | center | right | top | bottom | | ] + // | + // [ left | center | right | | ] + // [ top | center | bottom | | ] + // | + // [ center | [ left | right ] [ | ]? ] && + // [ center | [ top | bottom ] [ | ]? ] + //] + Matcher.alt( + // Because `alt` combinator is ordered, we need to test these + // in order from longest possible match to shortest. + Matcher.andand( + Matcher.cast("center").or( + Matcher.seq("left | right", + Matcher.cast(" | ").question())), + Matcher.cast("center").or( + Matcher.seq("top | bottom", + Matcher.cast(" | ").question()))), + Matcher.seq("left | center | right | | ", + "top | center | bottom | | "), + "left | center | right | top | bottom | | " + ), + + "": + //repeat-x | repeat-y | [repeat | space | round | no-repeat]{1,2} + Matcher.alt("repeat-x | repeat-y", Matcher.cast("repeat | space | round | no-repeat").braces(1,2)), + + "": + //inset? && [ {2,4} && ? ] + Matcher.many([true /* length is required */], + Matcher.cast("").braces(2,4), "inset", ""), + + "": + //[ | ] [ | ]? + Matcher.cast(" | ").braces(1,2), + + "": + // http://www.w3.org/TR/2014/WD-css-flexbox-1-20140325/#flex-property + // none | [ ? || ] + // Valid syntaxes, according to https://developer.mozilla.org/en-US/docs/Web/CSS/flex#Syntax + // * none + // * + // * + // * + // * + // * + // * inherit + Matcher.alt("none", "inherit", Matcher.cast("").then(Matcher.cast("").question()).oror("")), + + "": + // none | [ underline || overline || line-through || blink ] | inherit + Matcher.oror("underline", "overline", "line-through", "blink") } }; diff --git a/tests/css/Validation.js b/tests/css/Validation.js index 8d2d976c..8b2d41e5 100644 --- a/tests/css/Validation.js +++ b/tests/css/Validation.js @@ -197,6 +197,8 @@ "right top 5%", "top 3em center", "center top 3em", + "right 3% center", + "center right 3%", "top 3em right 10%", "top, bottom", "left 10px, left 10px", @@ -204,10 +206,10 @@ ], invalid: { - "foo" : "Expected () but found 'foo'.", + "foo" : "Expected ( | inherit) but found 'foo'.", "10% left" : "Expected end of value but found 'left'.", - "left center right" : "Expected end of value but found 'center'.", - "center 3em right 10%": "Expected end of value but found '3em'.", + "left center right" : "Expected end of value but found 'right'.", + "center 3em right 10%": "Expected end of value but found 'right'.", } })); @@ -372,7 +374,7 @@ ], invalid: { - "foo" : "Expected () but found 'foo'.", + "foo" : "Expected ( | inherit) but found 'foo'.", "5px 5px 7px" : "Expected end of value but found '7px'.", } })); @@ -388,7 +390,7 @@ ], invalid: { - "foo" : "Expected () but found 'foo'.", + "foo" : "Expected ( | inherit) but found 'foo'.", "5px 5px 7px" : "Expected end of value but found '7px'.", } })); @@ -457,7 +459,7 @@ ], invalid: { - "foo" : "Expected () but found 'foo'.", + "foo" : "Expected ( | inherit) but found 'foo'.", "5px 5px 7px" : "Expected end of value but found '7px'.", } })); @@ -472,7 +474,7 @@ ], invalid: { - "foo" : "Expected () but found 'foo'.", + "foo" : "Expected ( | inherit) but found 'foo'.", "5px 5px 7px" : "Expected end of value but found '7px'.", } })); @@ -727,6 +729,46 @@ } })); + // test + suite.add(new ValidationTestCase({ + property: "fill", + + valid: [ + "url('myGradient')", + "url('myGradient') darkred", + "url('myGradient') darkred icc-color(myCmykDarkRed)", + "currentColor", + "darkred icc-color(myCmykDarkRed)", + "none", + "inherit" + ], + + invalid: { + "url('myGradient') inherit" : "Expected end of value but found 'inherit'.", + "url('myGradient') icc-color(myCmykDarkRed)" : "Expected end of value but found 'icc-color(myCmykDarkRed)'.", + "currentColor icc-color(myCmykDarkRed)" : "Expected end of value but found 'icc-color(myCmykDarkRed)'.", + "icc-color(myCmykDarkRed) darkred" : "Expected ( | inherit) but found 'icc-color(myCmykDarkRed) darkred'.", + "icc-color(myCmykDarkRed)" : "Expected ( | inherit) but found 'icc-color(myCmykDarkRed)'.", + "icc-color(myCmykDarkRed) inherit" : "Expected ( | inherit) but found 'icc-color(myCmykDarkRed) inherit'.", + "inherit icc-color(myCmykDarkRed)" : "Expected end of value but found 'icc-color(myCmykDarkRed)'.", + "none inherit" : "Expected end of value but found 'inherit'." + } + })); + + suite.add(new ValidationTestCase({ + property: "fill-rule", + + valid: [ + "nonzero", + "evenodd", + "inherit" + ], + + invalid: { + "foo" : "Expected (nonzero | evenodd | inherit) but found 'foo'." + } + })); + suite.add(new ValidationTestCase({ property: "filter", @@ -743,7 +785,7 @@ invalid: { "circle(50% at 0 0)" : "Expected ( | none) but found 'circle(50% at 0 0)'.", "foo" : "Expected ( | none) but found 'foo'.", - "blur(30px 30px) none" : "Expected ( | ) but found 'none'." + "blur(30px 30px) none" : "Expected end of value but found 'none'." } })); @@ -764,7 +806,7 @@ ], invalid: { - "foo": "Expected (none | [ ? || ]) but found 'foo'." + "foo": "Expected (none | inherit | ? || ) but found 'foo'." } })); }); @@ -965,9 +1007,9 @@ invalid: { "none underline" : "Expected end of value but found 'underline'.", - "line-through none" : "Expected (none | | inherit) but found 'line-through none'.", + "line-through none" : "Expected end of value but found 'none'.", "inherit blink" : "Expected end of value but found 'blink'.", - "overline inherit" : "Expected (none | | inherit) but found 'overline inherit'.", + "overline inherit" : "Expected end of value but found 'inherit'.", "foo" : "Expected (none | | inherit) but found 'foo'." } })); @@ -1026,10 +1068,10 @@ ], invalid: { - "foo" : "Expected () but found 'foo'.", + "foo" : "Expected ( | inherit) but found 'foo'.", "10% left" : "Expected end of value but found 'left'.", - "left center right" : "Expected end of value but found 'center'.", - "center 3em right 10%": "Expected end of value but found '3em'.", + "left center right" : "Expected end of value but found 'right'.", + "center 3em right 10%": "Expected end of value but found 'right'.", "top, bottom" : "Expected end of value but found ','." } })); @@ -1038,11 +1080,15 @@ property: "opacity", valid: [ + "0", + "0.5", "1" ], invalid: { - "foo" : "Expected ( | inherit) but found 'foo'." + "-0.75" : "Expected ( | inherit) but found '-0.75'.", + "12" : "Expected ( | inherit) but found '12'.", + "foo" : "Expected ( | inherit) but found 'foo'." } })); @@ -1068,6 +1114,78 @@ } })); + suite.add(new ValidationTestCase({ + property: "stroke-dasharray", + + valid: [ + "0", + "4", + "20px", + "20px 40px 30px", + "20px, 40px, 30px", + "calc(1px + 2px) calc(3px + 1em)", + "none", + "inherit" + ], + + invalid: { + "-20px" : "Expected (none | | inherit) but found '-20px'.", + "20px," : "Expected end of value but found ','.", + "20px, -20px": "Expected end of value but found ','.", + "auto" : "Expected (none | | inherit) but found 'auto'." + } + })); + + suite.add(new ValidationTestCase({ + property: "stroke-linecap", + + valid: [ + "butt", + "round", + "square", + "inherit" + ], + + invalid: { + "auto" : "Expected (butt | round | square | inherit) but found 'auto'.", + "none" : "Expected (butt | round | square | inherit) but found 'none'." + } + })); + + suite.add(new ValidationTestCase({ + property: "stroke-linejoin", + + valid: [ + "miter", + "round", + "bevel", + "inherit" + ], + + invalid: { + "auto" : "Expected (miter | round | bevel | inherit) but found 'auto'.", + "none" : "Expected (miter | round | bevel | inherit) but found 'none'." + } + })); + + suite.add(new ValidationTestCase({ + property: "stroke-miterlimit", + + valid: [ + "1", + "1.4", + "20", + "10", + "inherit" + ], + + invalid: { + "-10" : "Expected ( | inherit) but found '-10'.", + "0.5" : "Expected ( | inherit) but found '0.5'.", + "foo" : "Expected ( | inherit) but found 'foo'." + } + })); + suite.add(new ValidationTestCase({ property: "-ms-touch-action",