| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library csslib.parser; | 5 library csslib.parser; |
| 6 | 6 |
| 7 import 'dart:math' as math; | 7 import 'dart:math' as math; |
| 8 | 8 |
| 9 import 'package:source_maps/span.dart' show SourceFile, Span, FileSpan; | 9 import 'package:source_maps/span.dart' show SourceFile, Span, FileSpan; |
| 10 | 10 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 | 23 |
| 24 /** Used for parser lookup ahead (used for nested selectors Less support). */ | 24 /** Used for parser lookup ahead (used for nested selectors Less support). */ |
| 25 class ParserState extends TokenizerState { | 25 class ParserState extends TokenizerState { |
| 26 final Token peekToken; | 26 final Token peekToken; |
| 27 final Token previousToken; | 27 final Token previousToken; |
| 28 | 28 |
| 29 ParserState(this.peekToken, this.previousToken, Tokenizer tokenizer) | 29 ParserState(this.peekToken, this.previousToken, Tokenizer tokenizer) |
| 30 : super(tokenizer); | 30 : super(tokenizer); |
| 31 } | 31 } |
| 32 | 32 |
| 33 // TODO(jmesserly): this should not be global |
| 33 void _createMessages({List<Message> errors, List<String> options}) { | 34 void _createMessages({List<Message> errors, List<String> options}) { |
| 34 if (errors == null) errors = []; | 35 if (errors == null) errors = []; |
| 35 | 36 |
| 36 if (options == null) { | 37 if (options == null) { |
| 37 options = ['--no-colors', 'memory']; | 38 options = ['--no-colors', 'memory']; |
| 38 } | 39 } |
| 39 var opt = PreprocessorOptions.parse(options); | 40 var opt = PreprocessorOptions.parse(options); |
| 40 messages = new Messages(options: opt, printHandler: errors.add); | 41 messages = new Messages(options: opt, printHandler: errors.add); |
| 41 } | 42 } |
| 42 | 43 |
| 43 /** CSS checked mode enabled. */ | 44 /** CSS checked mode enabled. */ |
| 44 bool get isChecked => messages.options.checked; | 45 bool get isChecked => messages.options.checked; |
| 45 | 46 |
| 46 // TODO(terry): Remove nested name parameter. | 47 // TODO(terry): Remove nested name parameter. |
| 47 /** Parse and analyze the CSS file. */ | 48 /** Parse and analyze the CSS file. */ |
| 48 StyleSheet compile(var input, {List<Message> errors, List<String> options, | 49 StyleSheet compile(input, {List<Message> errors, List<String> options, |
| 49 bool nested: true, | 50 bool nested: true, |
| 50 bool polyfill: false, | 51 bool polyfill: false, |
| 51 List<StyleSheet> includes: null}) { | 52 List<StyleSheet> includes: null}) { |
| 52 | 53 |
| 53 if (includes == null) { | 54 if (includes == null) { |
| 54 includes = []; | 55 includes = []; |
| 55 } | 56 } |
| 56 | 57 |
| 57 var source = _inputAsString(input); | 58 var source = _inputAsString(input); |
| 58 | 59 |
| (...skipping 19 matching lines...) Expand all Loading... |
| 78 | 79 |
| 79 _createMessages(errors: errors, options: options); | 80 _createMessages(errors: errors, options: options); |
| 80 new Analyzer(styleSheets, messages).run(); | 81 new Analyzer(styleSheets, messages).run(); |
| 81 } | 82 } |
| 82 | 83 |
| 83 /** | 84 /** |
| 84 * Parse the [input] CSS stylesheet into a tree. The [input] can be a [String], | 85 * Parse the [input] CSS stylesheet into a tree. The [input] can be a [String], |
| 85 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional | 86 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional |
| 86 * [errors] list will contain each error/warning as a [Message]. | 87 * [errors] list will contain each error/warning as a [Message]. |
| 87 */ | 88 */ |
| 88 StyleSheet parse(var input, {List<Message> errors, List<String> options}) { | 89 StyleSheet parse(input, {List<Message> errors, List<String> options}) { |
| 89 var source = _inputAsString(input); | 90 var source = _inputAsString(input); |
| 90 | 91 |
| 91 _createMessages(errors: errors, options: options); | 92 _createMessages(errors: errors, options: options); |
| 92 | 93 |
| 93 var file = new SourceFile.text(null, source); | 94 var file = new SourceFile.text(null, source); |
| 94 | |
| 95 return new _Parser(file, source).parse(); | 95 return new _Parser(file, source).parse(); |
| 96 } | 96 } |
| 97 | 97 |
| 98 /** | 98 /** |
| 99 * Parse the [input] CSS selector into a tree. The [input] can be a [String], | 99 * Parse the [input] CSS selector into a tree. The [input] can be a [String], |
| 100 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional | 100 * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional |
| 101 * [errors] list will contain each error/warning as a [Message]. | 101 * [errors] list will contain each error/warning as a [Message]. |
| 102 */ | 102 */ |
| 103 StyleSheet selector(var input, {List<Message> errors}) { | 103 // TODO(jmesserly): should rename "parseSelector" and return Selector |
| 104 StyleSheet selector(input, {List<Message> errors}) { |
| 104 var source = _inputAsString(input); | 105 var source = _inputAsString(input); |
| 105 | 106 |
| 106 _createMessages(errors: errors); | 107 _createMessages(errors: errors); |
| 107 | 108 |
| 108 var file = new SourceFile.text(null, source); | 109 var file = new SourceFile.text(null, source); |
| 109 | 110 return (new _Parser(file, source) |
| 110 return new _Parser(file, source).parseSelector(); | 111 ..tokenizer.inSelector = true) |
| 112 .parseSelector(); |
| 111 } | 113 } |
| 112 | 114 |
| 113 String _inputAsString(var input) { | 115 SelectorGroup parseSelectorGroup(input, {List<Message> errors}) { |
| 116 var source = _inputAsString(input); |
| 117 |
| 118 _createMessages(errors: errors); |
| 119 |
| 120 var file = new SourceFile.text(null, source); |
| 121 return (new _Parser(file, source) |
| 122 // TODO(jmesserly): this fix should be applied to the parser. It's tricky |
| 123 // because by the time the flag is set one token has already been fetched. |
| 124 ..tokenizer.inSelector = true) |
| 125 .processSelectorGroup(); |
| 126 } |
| 127 |
| 128 String _inputAsString(input) { |
| 114 String source; | 129 String source; |
| 115 | 130 |
| 116 if (input is String) { | 131 if (input is String) { |
| 117 source = input; | 132 source = input; |
| 118 } else if (input is List<int>) { | 133 } else if (input is List<int>) { |
| 119 // TODO(terry): The parse function needs an "encoding" argument and will | 134 // TODO(terry): The parse function needs an "encoding" argument and will |
| 120 // default to whatever encoding CSS defaults to. | 135 // default to whatever encoding CSS defaults to. |
| 121 // | 136 // |
| 122 // Here's some info about CSS encodings: | 137 // Here's some info about CSS encodings: |
| 123 // http://www.w3.org/International/questions/qa-css-charset.en.php | 138 // http://www.w3.org/International/questions/qa-css-charset.en.php |
| (...skipping 16 matching lines...) Expand all Loading... |
| 140 | 155 |
| 141 return source; | 156 return source; |
| 142 } | 157 } |
| 143 | 158 |
| 144 // TODO(terry): Consider removing this class when all usages can be eliminated | 159 // TODO(terry): Consider removing this class when all usages can be eliminated |
| 145 // or replaced with compile API. | 160 // or replaced with compile API. |
| 146 /** Public parsing interface for csslib. */ | 161 /** Public parsing interface for csslib. */ |
| 147 class Parser { | 162 class Parser { |
| 148 final _Parser _parser; | 163 final _Parser _parser; |
| 149 | 164 |
| 165 // TODO(jmesserly): having file and text is redundant. |
| 150 Parser(SourceFile file, String text, {int start: 0, String baseUrl}) : | 166 Parser(SourceFile file, String text, {int start: 0, String baseUrl}) : |
| 151 _parser = new _Parser(file, text, start: start, baseUrl: baseUrl); | 167 _parser = new _Parser(file, text, start: start, baseUrl: baseUrl); |
| 152 | 168 |
| 153 StyleSheet parse() => _parser.parse(); | 169 StyleSheet parse() => _parser.parse(); |
| 154 } | 170 } |
| 155 | 171 |
| 156 /** A simple recursive descent parser for CSS. */ | 172 /** A simple recursive descent parser for CSS. */ |
| 157 class _Parser { | 173 class _Parser { |
| 158 final Tokenizer tokenizer; | 174 final Tokenizer tokenizer; |
| 159 | 175 |
| (...skipping 1007 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1167 if (decls.length > 0) { | 1183 if (decls.length > 0) { |
| 1168 groups.add(new DeclarationGroup(decls, _makeSpan(start))); | 1184 groups.add(new DeclarationGroup(decls, _makeSpan(start))); |
| 1169 } | 1185 } |
| 1170 | 1186 |
| 1171 return groups; | 1187 return groups; |
| 1172 } | 1188 } |
| 1173 | 1189 |
| 1174 SelectorGroup processSelectorGroup() { | 1190 SelectorGroup processSelectorGroup() { |
| 1175 List<Selector> selectors = []; | 1191 List<Selector> selectors = []; |
| 1176 int start = _peekToken.start; | 1192 int start = _peekToken.start; |
| 1193 |
| 1177 do { | 1194 do { |
| 1178 Selector selector = processSelector(); | 1195 Selector selector = processSelector(); |
| 1179 if (selector != null) { | 1196 if (selector != null) { |
| 1180 selectors.add(selector); | 1197 selectors.add(selector); |
| 1181 } | 1198 } |
| 1182 } while (_maybeEat(TokenKind.COMMA)); | 1199 } while (_maybeEat(TokenKind.COMMA)); |
| 1183 | 1200 |
| 1184 if (selectors.length > 0) { | 1201 if (selectors.length > 0) { |
| 1185 return new SelectorGroup(selectors, _makeSpan(start)); | 1202 return new SelectorGroup(selectors, _makeSpan(start)); |
| 1186 } | 1203 } |
| (...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1406 // : or :: and making this a normal selector. For now, | 1423 // : or :: and making this a normal selector. For now, |
| 1407 // create an empty pseudoName. | 1424 // create an empty pseudoName. |
| 1408 var pseudoName; | 1425 var pseudoName; |
| 1409 if (_peekIdentifier()) { | 1426 if (_peekIdentifier()) { |
| 1410 pseudoName = identifier(); | 1427 pseudoName = identifier(); |
| 1411 } else { | 1428 } else { |
| 1412 return null; | 1429 return null; |
| 1413 } | 1430 } |
| 1414 | 1431 |
| 1415 // Functional pseudo? | 1432 // Functional pseudo? |
| 1416 if (_maybeEat(TokenKind.LPAREN)) { | 1433 |
| 1434 if (_peekToken.kind == TokenKind.LPAREN) { |
| 1435 |
| 1417 if (!pseudoElement && pseudoName.name.toLowerCase() == 'not') { | 1436 if (!pseudoElement && pseudoName.name.toLowerCase() == 'not') { |
| 1437 _eat(TokenKind.LPAREN); |
| 1438 |
| 1418 // Negation : ':NOT(' S* negation_arg S* ')' | 1439 // Negation : ':NOT(' S* negation_arg S* ')' |
| 1419 var negArg = simpleSelector(); | 1440 var negArg = simpleSelector(); |
| 1420 | 1441 |
| 1421 _eat(TokenKind.RPAREN); | 1442 _eat(TokenKind.RPAREN); |
| 1422 return new NegationSelector(negArg, _makeSpan(start)); | 1443 return new NegationSelector(negArg, _makeSpan(start)); |
| 1423 } else { | 1444 } else { |
| 1445 // Special parsing for expressions in pseudo functions. Minus is used |
| 1446 // as operator not identifier. |
| 1447 // TODO(jmesserly): we need to flip this before we eat the "(" as the |
| 1448 // next token will be fetched when we do that. I think we should try to |
| 1449 // refactor so we don't need this boolean; it seems fragile. |
| 1450 tokenizer.inSelectorExpression = true; |
| 1451 _eat(TokenKind.LPAREN); |
| 1452 |
| 1424 // Handle function expression. | 1453 // Handle function expression. |
| 1425 var span = _makeSpan(start); | 1454 var span = _makeSpan(start); |
| 1426 var expr = processSelectorExpression(); | 1455 var expr = processSelectorExpression(); |
| 1427 | 1456 |
| 1457 tokenizer.inSelectorExpression = false; |
| 1458 |
| 1428 // Used during selector look-a-head if not a SelectorExpression is | 1459 // Used during selector look-a-head if not a SelectorExpression is |
| 1429 // bad. | 1460 // bad. |
| 1430 if (expr is! SelectorExpression) { | 1461 if (expr is! SelectorExpression) { |
| 1431 _errorExpected("CSS expression"); | 1462 _errorExpected("CSS expression"); |
| 1432 return null; | 1463 return null; |
| 1433 } | 1464 } |
| 1434 | 1465 |
| 1435 _eat(TokenKind.RPAREN); | 1466 _eat(TokenKind.RPAREN); |
| 1436 return (pseudoElement) ? | 1467 return (pseudoElement) ? |
| 1437 new PseudoElementFunctionSelector(pseudoName, expr, span) : | 1468 new PseudoElementFunctionSelector(pseudoName, expr, span) : |
| (...skipping 18 matching lines...) Expand all Loading... |
| 1456 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ | 1487 * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ |
| 1457 * | 1488 * |
| 1458 * num [0-9]+|[0-9]*\.[0-9]+ | 1489 * num [0-9]+|[0-9]*\.[0-9]+ |
| 1459 * PLUS '+' | 1490 * PLUS '+' |
| 1460 * DIMENSION {num}{ident} | 1491 * DIMENSION {num}{ident} |
| 1461 * NUMBER {num} | 1492 * NUMBER {num} |
| 1462 */ | 1493 */ |
| 1463 processSelectorExpression() { | 1494 processSelectorExpression() { |
| 1464 var start = _peekToken.start; | 1495 var start = _peekToken.start; |
| 1465 | 1496 |
| 1466 var expression = new SelectorExpression(_makeSpan(start)); | 1497 var expressions = []; |
| 1467 | 1498 |
| 1468 Token termToken; | 1499 Token termToken; |
| 1469 var value; | 1500 var value; |
| 1470 | 1501 |
| 1471 // Special parsing for expressions in pseudo functions. Minus is used as | |
| 1472 // operator not identifier. | |
| 1473 tokenizer.selectorExpression = true; | |
| 1474 | |
| 1475 var keepParsing = true; | 1502 var keepParsing = true; |
| 1476 while (keepParsing) { | 1503 while (keepParsing) { |
| 1477 switch (_peek()) { | 1504 switch (_peek()) { |
| 1478 case TokenKind.PLUS: | 1505 case TokenKind.PLUS: |
| 1479 start = _peekToken.start; | 1506 start = _peekToken.start; |
| 1480 termToken = _next(); | 1507 termToken = _next(); |
| 1481 expression.add(new OperatorPlus(_makeSpan(start))); | 1508 expressions.add(new OperatorPlus(_makeSpan(start))); |
| 1482 break; | 1509 break; |
| 1483 case TokenKind.MINUS: | 1510 case TokenKind.MINUS: |
| 1484 start = _peekToken.start; | 1511 start = _peekToken.start; |
| 1485 termToken = _next(); | 1512 termToken = _next(); |
| 1486 expression.add(new OperatorMinus(_makeSpan(start))); | 1513 expressions.add(new OperatorMinus(_makeSpan(start))); |
| 1487 break; | 1514 break; |
| 1488 case TokenKind.INTEGER: | 1515 case TokenKind.INTEGER: |
| 1489 termToken = _next(); | 1516 termToken = _next(); |
| 1490 value = int.parse(termToken.text); | 1517 value = int.parse(termToken.text); |
| 1491 break; | 1518 break; |
| 1492 case TokenKind.DOUBLE: | 1519 case TokenKind.DOUBLE: |
| 1493 termToken = _next(); | 1520 termToken = _next(); |
| 1494 value = double.parse(termToken.text); | 1521 value = double.parse(termToken.text); |
| 1495 break; | 1522 break; |
| 1496 case TokenKind.SINGLE_QUOTE: | 1523 case TokenKind.SINGLE_QUOTE: |
| (...skipping 13 matching lines...) Expand all Loading... |
| 1510 | 1537 |
| 1511 if (keepParsing && value != null) { | 1538 if (keepParsing && value != null) { |
| 1512 var unitTerm; | 1539 var unitTerm; |
| 1513 // Don't process the dimension if MINUS or PLUS is next. | 1540 // Don't process the dimension if MINUS or PLUS is next. |
| 1514 if (_peek() != TokenKind.MINUS && _peek() != TokenKind.PLUS) { | 1541 if (_peek() != TokenKind.MINUS && _peek() != TokenKind.PLUS) { |
| 1515 unitTerm = processDimension(termToken, value, _makeSpan(start)); | 1542 unitTerm = processDimension(termToken, value, _makeSpan(start)); |
| 1516 } | 1543 } |
| 1517 if (unitTerm == null) { | 1544 if (unitTerm == null) { |
| 1518 unitTerm = new LiteralTerm(value, value.name, _makeSpan(start)); | 1545 unitTerm = new LiteralTerm(value, value.name, _makeSpan(start)); |
| 1519 } | 1546 } |
| 1520 expression.add(unitTerm); | 1547 expressions.add(unitTerm); |
| 1521 | 1548 |
| 1522 value = null; | 1549 value = null; |
| 1523 } | 1550 } |
| 1524 } | 1551 } |
| 1525 | 1552 |
| 1526 tokenizer.selectorExpression = false; | 1553 return new SelectorExpression(expressions, _makeSpan(start)); |
| 1527 | |
| 1528 return expression; | |
| 1529 } | 1554 } |
| 1530 | 1555 |
| 1531 // Attribute grammar: | 1556 // Attribute grammar: |
| 1532 // | 1557 // |
| 1533 // attributes : | 1558 // attributes : |
| 1534 // '[' S* IDENT S* [ ATTRIB_MATCHES S* [ IDENT | STRING ] S* ]? ']' | 1559 // '[' S* IDENT S* [ ATTRIB_MATCHES S* [ IDENT | STRING ] S* ]? ']' |
| 1535 // | 1560 // |
| 1536 // ATTRIB_MATCHES : | 1561 // ATTRIB_MATCHES : |
| 1537 // [ '=' | INCLUDES | DASHMATCH | PREFIXMATCH | SUFFIXMATCH | SUBSTRMATCH ] | 1562 // [ '=' | INCLUDES | DASHMATCH | PREFIXMATCH | SUFFIXMATCH | SUBSTRMATCH ] |
| 1538 // | 1563 // |
| (...skipping 797 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2336 } | 2361 } |
| 2337 | 2362 |
| 2338 return term; | 2363 return term; |
| 2339 } | 2364 } |
| 2340 | 2365 |
| 2341 String processQuotedString([bool urlString = false]) { | 2366 String processQuotedString([bool urlString = false]) { |
| 2342 var start = _peekToken.start; | 2367 var start = _peekToken.start; |
| 2343 | 2368 |
| 2344 // URI term sucks up everything inside of quotes(' or ") or between parens | 2369 // URI term sucks up everything inside of quotes(' or ") or between parens |
| 2345 var stopToken = urlString ? TokenKind.RPAREN : -1; | 2370 var stopToken = urlString ? TokenKind.RPAREN : -1; |
| 2371 |
| 2372 // Note: disable skipping whitespace tokens inside a string. |
| 2373 // TODO(jmesserly): the layering here feels wrong. |
| 2374 var skipWhitespace = tokenizer._skipWhitespace; |
| 2375 tokenizer._skipWhitespace = false; |
| 2376 |
| 2346 switch (_peek()) { | 2377 switch (_peek()) { |
| 2347 case TokenKind.SINGLE_QUOTE: | 2378 case TokenKind.SINGLE_QUOTE: |
| 2348 stopToken = TokenKind.SINGLE_QUOTE; | 2379 stopToken = TokenKind.SINGLE_QUOTE; |
| 2349 start = _peekToken.start + 1; // Skip the quote might have whitespace. | 2380 start = _peekToken.start + 1; // Skip the quote might have whitespace. |
| 2350 _next(); // Skip the SINGLE_QUOTE. | 2381 _next(); // Skip the SINGLE_QUOTE. |
| 2351 break; | 2382 break; |
| 2352 case TokenKind.DOUBLE_QUOTE: | 2383 case TokenKind.DOUBLE_QUOTE: |
| 2353 stopToken = TokenKind.DOUBLE_QUOTE; | 2384 stopToken = TokenKind.DOUBLE_QUOTE; |
| 2354 start = _peekToken.start + 1; // Skip the quote might have whitespace. | 2385 start = _peekToken.start + 1; // Skip the quote might have whitespace. |
| 2355 _next(); // Skip the DOUBLE_QUOTE. | 2386 _next(); // Skip the DOUBLE_QUOTE. |
| 2356 break; | 2387 break; |
| 2357 default: | 2388 default: |
| 2358 if (urlString) { | 2389 if (urlString) { |
| 2359 if (_peek() == TokenKind.LPAREN) { | 2390 if (_peek() == TokenKind.LPAREN) { |
| 2360 _next(); // Skip the LPAREN. | 2391 _next(); // Skip the LPAREN. |
| 2361 start = _peekToken.start; | 2392 start = _peekToken.start; |
| 2362 } | 2393 } |
| 2363 stopToken = TokenKind.RPAREN; | 2394 stopToken = TokenKind.RPAREN; |
| 2364 } else { | 2395 } else { |
| 2365 _error('unexpected string', _makeSpan(start)); | 2396 _error('unexpected string', _makeSpan(start)); |
| 2366 } | 2397 } |
| 2367 break; | 2398 break; |
| 2368 } | 2399 } |
| 2369 | 2400 |
| 2370 // Gobble up everything until we hit our stop token. | 2401 // Gobble up everything until we hit our stop token. |
| 2371 var runningStart = _peekToken.start; | 2402 var runningStart = _peekToken.start; |
| 2403 |
| 2404 var stringValue = new StringBuffer(); |
| 2372 while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) { | 2405 while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) { |
| 2373 var tok = _next(); | 2406 stringValue.write(_next().text); |
| 2374 } | 2407 } |
| 2375 | 2408 |
| 2409 tokenizer._skipWhitespace = skipWhitespace; |
| 2410 |
| 2376 // All characters between quotes is the string. | 2411 // All characters between quotes is the string. |
| 2377 var end = _peekToken.end; | |
| 2378 var stringValue = (_peekToken.span as FileSpan).file.getText(start, | |
| 2379 end - 1); | |
| 2380 | |
| 2381 if (stopToken != TokenKind.RPAREN) { | 2412 if (stopToken != TokenKind.RPAREN) { |
| 2382 _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE; | 2413 _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE; |
| 2383 } | 2414 } |
| 2384 | 2415 |
| 2385 return stringValue; | 2416 return stringValue.toString(); |
| 2386 } | 2417 } |
| 2387 | 2418 |
| 2388 // TODO(terry): Should probably understand IE's non-standard filter syntax to | 2419 // TODO(terry): Should probably understand IE's non-standard filter syntax to |
| 2389 // fully support calc, var(), etc. | 2420 // fully support calc, var(), etc. |
| 2390 /** | 2421 /** |
| 2391 * IE's filter property breaks CSS value parsing. IE's format can be: | 2422 * IE's filter property breaks CSS value parsing. IE's format can be: |
| 2392 * | 2423 * |
| 2393 * filter: progid:DXImageTransform.MS.gradient(Type=0, Color='#9d8b83'); | 2424 * filter: progid:DXImageTransform.MS.gradient(Type=0, Color='#9d8b83'); |
| 2394 * | 2425 * |
| 2395 * We'll just parse everything after the 'progid:' look for the left paren | 2426 * We'll just parse everything after the 'progid:' look for the left paren |
| (...skipping 268 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2664 | 2695 |
| 2665 if (replace != null && result == null) { | 2696 if (replace != null && result == null) { |
| 2666 result = new StringBuffer(text.substring(0, i)); | 2697 result = new StringBuffer(text.substring(0, i)); |
| 2667 } | 2698 } |
| 2668 | 2699 |
| 2669 if (result != null) result.write(replace != null ? replace : text[i]); | 2700 if (result != null) result.write(replace != null ? replace : text[i]); |
| 2670 } | 2701 } |
| 2671 | 2702 |
| 2672 return result == null ? text : result.toString(); | 2703 return result == null ? text : result.toString(); |
| 2673 } | 2704 } |
| OLD | NEW |