diff --git a/src/language/CSSUtils.js b/src/language/CSSUtils.js index 70d77872909..0b702e7d67e 100644 --- a/src/language/CSSUtils.js +++ b/src/language/CSSUtils.js @@ -73,7 +73,9 @@ define(function (require, exports, module) { var selectorStartChar = -1, selectorStartLine = -1; var selectorGroupStartLine = -1, selectorGroupStartChar = -1; var declListStartLine = -1, declListStartChar = -1; - + var escapePattern = new RegExp("\\\\[^\\\\]+", "g"); + var validationPattern = new RegExp("\\\\([a-f0-9]{6}|[a-f0-9]{4}(\\s|\\\\|$)|[a-f0-9]{2}(\\s|\\\\|$)|.)", "i"); + // implement _firstToken()/_nextToken() methods to // provide a single stream of tokens @@ -183,7 +185,24 @@ define(function (require, exports, module) { break; } } - + + // Unicode character replacement as defined in http://www.w3.org/TR/CSS21/syndata.html#characters + if (/\\/.test(currentSelector)) { + // Double replace in case of pattern overlapping (regex improvement?) + currentSelector = currentSelector.replace(escapePattern, function (escapedToken) { + return escapedToken.replace(validationPattern, function (unicodeChar) { + unicodeChar = unicodeChar.substr(1); + if (unicodeChar.length === 1) { + return unicodeChar; + } else { + if (parseInt(unicodeChar, 16) < 0x10FFFF) { + return String.fromCharCode(parseInt(unicodeChar, 16)); + } else { return String.fromCharCode(0xFFFD); } + } + }); + }); + } + currentSelector = currentSelector.trim(); if (currentSelector !== "") { selectors.push({selector: currentSelector, diff --git a/test/spec/CSSUtils-test-files/escaped-identifiers.css b/test/spec/CSSUtils-test-files/escaped-identifiers.css new file mode 100644 index 00000000000..c62220bc86b --- /dev/null +++ b/test/spec/CSSUtils-test-files/escaped-identifiers.css @@ -0,0 +1,61 @@ +/* Test ".si\mple" (simple) */ +.si\mple {} + +/* Test ".not\\so\|simple\?" (not\so|simple?) */ + .not\\so\|simple\? {} + +/* Test ".\74 wodigi\74 s" (.twodigits)*/ +.\74 wodigi\74 s {} + +/* Test ".fourdigi\0074 s" (.fourdigits)*/ +.fourdigi\0074 s {} + +/* Test ".sixdigi\000074s" (.sixdigits) */ + .sixdigi\000074s {} + +/* Test ".two-digit-endspac\65 " (.two-digit-endspace) */ +.two-digit-endspac\65 {} + +/* Test ".four-digit-endspac\0065 " (.four-digit-endspace) */ +.four-digit-endspac\0065 {} + +/* Test ".six-digit-endspace\000065" (.six-digit-endspace) */ +.six-digit-endspac\000065 {} + +/* Test ".mi\78 in\002D it\2D a\00006C\006C" (.mixin-it-all) */ +.mi\78 in\002D it\2D a\00006C\006C{} + +/* Test ".\74 wo-wi\74out-space" (.two-wi74out-space) */ +.\74 wo-wi\74out-space {} + +/* Test ".four-n\0085-space" (.four-n0085-space) */ +.four-n\0085-space {} + +/* Test "" Out of range unicode char, uses replace instead */ +.\110000\0075\74\cc6699frange {} + + .escape\|random\|char { + color: red; +} + +.mixin\!tUp { + font-weight: bold; +} + +.\34 04 { + background: red; +} + +.\34 04 strong { + color: #ff00ff; + font-weight: bold; +} + +.trailingTest\+ { + color: red; +} + +/* This hideous test of hideousness checks for the selector "blockquote" with various permutations of hex escapes */ +\62\6c\6f \63 \6B \0071 \000075o\74 e { + color: silver; +} \ No newline at end of file diff --git a/test/spec/CSSUtils-test.js b/test/spec/CSSUtils-test.js index d45292d21c8..bd9b69c41ed 100644 --- a/test/spec/CSSUtils-test.js +++ b/test/spec/CSSUtils-test.js @@ -38,7 +38,8 @@ define(function (require, exports, module) { universalCssFileEntry = new NativeFileSystem.FileEntry(testPath + "/universal.css"), groupsFileEntry = new NativeFileSystem.FileEntry(testPath + "/groups.css"), offsetsCssFileEntry = new NativeFileSystem.FileEntry(testPath + "/offsets.css"), - bootstrapCssFileEntry = new NativeFileSystem.FileEntry(testPath + "/bootstrap.css"); + bootstrapCssFileEntry = new NativeFileSystem.FileEntry(testPath + "/bootstrap.css"), + escapesCssFileEntry = new NativeFileSystem.FileEntry(testPath + "/escaped-identifiers.css"); /** @@ -251,6 +252,80 @@ define(function (require, exports, module) { }); }); + + describe("escapes", function() { + + beforeEach(function () { + init(this, escapesCssFileEntry); + }); + + it("should remove simple backslashes for simple characters", function() { + var selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[0].selector).toEqual(".simple"); + }); + + it("should remove simple backslashes with escaped characters", function() { + var selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[1].selector).toEqual(".not\\so|simple?"); + }); + + it("should parse '\\XX ' as a single character", function() { + var selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[2].selector).toEqual(".twodigits"); + }); + + it("should parse '\\XXXX ' as a single character", function() { + var selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[3].selector).toEqual(".fourdigits"); + }); + + it("should parse '\\XXXXXX' as a single character", function() { + var selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[4].selector).toEqual(".sixdigits"); + }); + + it("should not trim end spaces", function() { + var selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[5].selector).toEqual(".two-digit-endspace"); + + selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[6].selector).toEqual(".four-digit-endspace"); + + selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[7].selector).toEqual(".six-digit-endspace"); + }); + + it("should detect all combinations", function() { + var selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[8].selector).toEqual(".mixin-it-all"); + }); + + it("should parse '\\AX' as AX", function() { + var selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[9].selector).toEqual(".two-wi74out-space"); + }); + + it("should parse '\\AXXX' as AXXX", function() { + var selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[10].selector).toEqual(".four-n0085-space"); + }); + + it("should replace out of range characters with �", function() { + var selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[11].selector).toEqual(".�ut�frange"); + }); + + it("should parse everything less does", function() { + var selectors = CSSUtils.extractAllSelectors(this.fileCssContent); + expect(selectors[12].selector).toEqual(".escape|random|char"); + expect(selectors[13].selector).toEqual(".mixin!tUp"); + expect(selectors[14].selector).toEqual(".404"); + expect(selectors[15].selector).toEqual(".404 strong"); + expect(selectors[16].selector).toEqual(".trailingTest+"); + expect(selectors[17].selector).toEqual("blockquote"); + }); + }); + }); // describe("CSSUtils")