## modules/graphics/src/main/java/com/sun/javafx/css/parser/CSSParser.java

Print this page

    rev 4839 : RT-2529: Locale specific runtime errors (Turkish)

      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25
      26 package com.sun.javafx.css.parser;
      27
      28 import java.io.BufferedReader;
      29 import java.io.CharArrayReader;
      30 import java.io.IOException;
      31 import java.io.InputStreamReader;
      32 import java.io.Reader;
      33 import java.net.URI;
      34 import java.net.URISyntaxException;
      35 import java.net.URL;
      36 import java.text.MessageFormat;
      37 import java.util.ArrayList;
      38 import java.util.Collections;
      39 import java.util.HashMap;
      40 import java.util.List;

      41 import java.util.Map;
      42 import javafx.geometry.Insets;
      43 import javafx.scene.effect.BlurType;
      44 import javafx.scene.effect.Effect;
      45 import javafx.scene.layout.BackgroundPosition;
      46 import javafx.scene.layout.BackgroundRepeat;
      47 import javafx.scene.layout.BackgroundSize;
      48 import javafx.scene.layout.BorderStrokeStyle;
      49 import javafx.scene.layout.BorderWidths;
      50 import javafx.scene.paint.Color;
      51 import javafx.scene.paint.CycleMethod;
      52 import javafx.scene.paint.Paint;
      53 import javafx.scene.paint.Stop;
      54 import javafx.scene.shape.StrokeLineCap;
      55 import javafx.scene.shape.StrokeLineJoin;
      56 import javafx.scene.shape.StrokeType;
      57 import javafx.scene.text.Font;
      58 import javafx.scene.text.FontPosture;
      59 import javafx.scene.text.FontWeight;
      60 import com.sun.javafx.Utils;
     680     }
     681
     682     // Get the next layer following this term, which may be null
     683     private Term nextLayer(final Term root) {
     684         if (root == null) return null;
     685
     686         Term term = root;
     687         while (term.nextInSeries != null) {
     688             term = term.nextInSeries;
     689         }
     690         return term.nextLayer;
     691     }
     692
     693     ////////////////////////////////////////////////////////////////////////////
     694     //
     695     // Parsing routines
     696     //
     697     ////////////////////////////////////////////////////////////////////////////
     698
     699     ParsedValueImpl valueFor(String property, Term root) throws ParseException {
     700         final String prop = property.toLowerCase();
     701         properties.put(prop, prop);
     702         if (root == null || root.token == null) {
     703             error(root, "Expected value for property \'" + prop + "\'");
     704         }
     705
     706         if (root.token.getType() == CSSLexer.IDENT) {
     707             if ("inherit".equalsIgnoreCase(root.token.getText())) {
     708                 return new ParsedValueImpl<String,String>("inherit", null);
     709             } else if ("null".equalsIgnoreCase(root.token.getText())) {
     710                 return new ParsedValueImpl<String,String>("null", null);
     711             }
     712         }
     713
     714         if ("-fx-background-color".equals(property)) {
     715             return parsePaintLayers(root);
     716         } else if ("-fx-background-image".equals(prop)) {
     717             return parseURILayers(root);
     718         } else if ("-fx-background-insets".equals(prop)) {
     719              return parseInsetsLayers(root);
     720         } else if ("-fx-opaque-insets".equals(prop)) {
     832         case CSSLexer.PERCENTAGE:
     833         case CSSLexer.EMS:
     834         case CSSLexer.EXS:
     835         case CSSLexer.PX:
     836         case CSSLexer.CM:
     837         case CSSLexer.MM:
     838         case CSSLexer.IN:
     839         case CSSLexer.PT:
     840         case CSSLexer.PC:
     841         case CSSLexer.DEG:
     842         case CSSLexer.GRAD:
     843         case CSSLexer.RAD:
     844         case CSSLexer.TURN:
     845             ParsedValueImpl sizeValue = new ParsedValueImpl<Size,Number>(size(token), null);
     846             value = new ParsedValueImpl<ParsedValue<?,Size>, Number>(sizeValue, SizeConverter.getInstance());
     847             break;
     848         case CSSLexer.STRING:
     849         case CSSLexer.IDENT:
     850             boolean isIdent = ttype == CSSLexer.IDENT;
     851             final String str = stripQuotes(token.getText());
     852             final String text = str.toLowerCase();
     853             if ("ladder".equals(text)) {
     854                 value = ladder(root);
     855             } else if ("linear".equals(text)) {
     856                 value = linearGradient(root);
     857             } else if ("radial".equals(text)) {
     858                 value = radialGradient(root);
     859             } else if ("true".equals(text)) {
     860                 // TODO: handling of boolean is really bogus
     861                 value = new ParsedValueImpl<String,Boolean>("true",BooleanConverter.getInstance());
     862             } else if ("false".equals(text)) {
     863                 // TODO: handling of boolean is really bogus
     864                 value = new ParsedValueImpl<String,Boolean>("false",BooleanConverter.getInstance());
     865             } else {
     866                 // if the property value is another property, then it needs to be looked up.
     867                 boolean needsLookup = isIdent && properties.containsKey(str);
     868                 if (needsLookup || ((value = colorValueOfString(str)) == null )) {
     869                     value = new ParsedValueImpl<String,String>(str, null, isIdent || needsLookup);
     870                 }
     871             }
     872             break;
    1387         } else if ("region".regionMatches(true, 0, fcn, 0, 6)) {
    1388             return parseRegion(root);
    1389         } else if ("url".regionMatches(true, 0, fcn, 0, 3)) {
    1390             return parseURI(root);
    1391         } else {
    1392             error(root, "Unexpected function \'" + fcn + "\'");
    1393         }
    1394         return null;
    1395     }
    1396
    1397     private ParsedValueImpl<String,BlurType> blurType(final Term root) throws ParseException {
    1398
    1399         if (root == null) return null;
    1400         if (root.token == null ||
    1401             root.token.getType() != CSSLexer.IDENT ||
    1402             root.token.getText() == null ||
    1403             root.token.getText().isEmpty()) {
    1404             final String msg = "Expected \'gaussian\', \'one-pass-box\', \'two-pass-box\', or \'three-pass-box\'";
    1405             error(root, msg);
    1406         }
    1407         final String blurStr = root.token.getText().toLowerCase();
    1408         BlurType blurType = BlurType.THREE_PASS_BOX;
    1409         if ("gaussian".equals(blurStr)) {
    1410             blurType = BlurType.GAUSSIAN;
    1411         } else if ("one-pass-box".equals(blurStr)) {
    1412             blurType = BlurType.ONE_PASS_BOX;
    1413         } else if ("two-pass-box".equals(blurStr)) {
    1414             blurType = BlurType.TWO_PASS_BOX;
    1415         } else if ("three-pass-box".equals(blurStr)) {
    1416             blurType = BlurType.THREE_PASS_BOX;
    1417         } else {
    1418             final String msg = "Expected \'gaussian\', \'one-pass-box\', \'two-pass-box\', or \'three-pass-box\'";
    1419             error(root, msg);
    1420         }
    1421         return new ParsedValueImpl<String,BlurType>(blurType.name(), new EnumConverter<BlurType>(BlurType.class));
    1422     }
    1423
    1424     // innershadow <blur-type> <color> <radius> <choke> <offset-x> <offset-y>
    1425     private ParsedValueImpl innershadow(final Term root) throws ParseException {
    1426
    1427         // first term in the chain is the function name...
    1511         if ((arg = arg.nextArg) == null) error(prev, "Expected \'<number>\'");
    1512
    1513         ParsedValueImpl<?,Size> offsetYVal = parseSize(arg);
    1514
    1515         ParsedValueImpl[] values = new ParsedValueImpl[] {
    1516             blurVal,
    1517             colorVal,
    1518             radiusVal,
    1519             spreadVal,
    1520             offsetXVal,
    1521             offsetYVal
    1522         };
    1523         return new ParsedValueImpl<ParsedValue[],Effect>(values, EffectConverter.DropShadowConverter.getInstance());
    1524     }
    1525
    1526     // returns null if the Term is null or is not a cycle method.
    1527     private ParsedValueImpl<String, CycleMethod> cycleMethod(final Term root) {
    1528         CycleMethod cycleMethod = null;
    1529         if (root != null && root.token.getType() == CSSLexer.IDENT) {
    1530
    1531             final String text = root.token.getText().toLowerCase();
    1532             if ("repeat".equals(text)) {
    1533                 cycleMethod = CycleMethod.REPEAT;
    1534             } else if ("reflect".equals(text)) {
    1535                 cycleMethod = CycleMethod.REFLECT;
    1536             } else if ("no-cycle".equals(text)) {
    1537                 cycleMethod = CycleMethod.NO_CYCLE;
    1538             }
    1539         }
    1540         if (cycleMethod != null)
    1541             return new ParsedValueImpl<String,CycleMethod>(cycleMethod.name(), new EnumConverter<CycleMethod>(CycleMethod.class));
    1542         else
    1543             return null;
    1544     }
    1545
    1546     // linear <point> TO <point> STOPS <stop>+ cycleMethod?
    1547     private ParsedValueImpl<ParsedValue[],Paint> linearGradient(final Term root) throws ParseException {
    1548
    1549         final String fn = (root.token != null) ? root.token.getText() : null;
    1550         if (fn == null || !"linear".equalsIgnoreCase(fn)) {
    1551             final String msg = "Expected \'linear\'";
    1715
    1716             prev = arg;
    1717             arg = arg.nextArg;
    1718
    1719         } else if("to".equalsIgnoreCase(arg.token.getText())) {
    1720
    1721             prev = arg;
    1722             if ((arg = arg.nextInSeries) == null ||
    1723                 arg.token == null ||
    1724                 arg.token.getType() != CSSLexer.IDENT ||
    1725                 arg.token.getText().isEmpty()) {
    1726                 error (prev, "Expected \'<side-or-corner>\'");
    1727             }
    1728
    1729
    1730             int startX = 0;
    1731             int startY = 0;
    1732             int endX = 0;
    1733             int endY = 0;
    1734
    1735             String sideOrCorner1 = arg.token.getText().toLowerCase();
    1736             // The keywords denote the direction.
    1737             if ("top".equals(sideOrCorner1)) {
    1738                 // going toward the top, then start at the bottom
    1739                 startY = 100;
    1740                 endY = 0;
    1741
    1742             } else if ("bottom".equals(sideOrCorner1)) {
    1743                 // going toward the bottom, then start at the top
    1744                 startY = 0;
    1745                 endY = 100;
    1746
    1747             } else if ("right".equals(sideOrCorner1)) {
    1748                 // going toward the right, then start at the left
    1749                 startX = 0;
    1750                 endX = 100;
    1751
    1752             } else if ("left".equals(sideOrCorner1)) {
    1753                 // going toward the left, then start at the right
    1754                 startX = 100;
    1755                 endX = 0;
    1756
    1757             } else {
    1758                 error(arg, "Invalid \'<side-or-corner>\'");
    1759             }
    1760
    1761             prev = arg;
    1762             if (arg.nextInSeries != null) {
    1763                 arg = arg.nextInSeries;
    1764                 if (arg.token != null &&
    1765                     arg.token.getType() == CSSLexer.IDENT &&
    1766                     !arg.token.getText().isEmpty()) {
    1767
    1768                     String sideOrCorner2 = arg.token.getText().toLowerCase();
    1769
    1770                     // if right or left has already been given,
    1771                     // then either startX or endX will not be zero.
    1772                     if ("right".equals(sideOrCorner2) &&
    1773                         startX == 0 && endX == 0) {
    1774                         // start left, end right
    1775                         startX = 0;
    1776                         endX = 100;
    1777                     } else if ("left".equals(sideOrCorner2) &&
    1778                         startX == 0 && endX == 0) {
    1779                         // start right, end left
    1780                         startX = 100;
    1781                         endX = 0;
    1782
    1783                     // if top or bottom has already been given,
    1784                     // then either startY or endY will not be zero.
    1785                     } else if("top".equals(sideOrCorner2) &&
    1786                         startY == 0 && endY == 0) {
    1787                         // start bottom, end top
    1788                         startY = 100;
    1874
    1875         final String fn = (root.token != null) ? root.token.getText() : null;
    1876         if (fn == null || !"radial".equalsIgnoreCase(fn)) {
    1877             final String msg = "Expected \'radial\'";
    1878             error(root, msg);
    1879         }
    1880
    1881         if (LOGGER.isLoggable(Level.WARNING)) {
    1882             LOGGER.warning(formatDeprecatedMessage(root, "radial gradient"));
    1883         }
    1884
    1885         Term term = root;
    1886         Term prev = root;
    1887
    1888         if ((term = term.nextInSeries) == null) error(root, "Expected \'focus-angle <number>\', \'focus-distance <number>\', \'center (<number>,<number>)\' or \'<size>\'");
    1889         if (term.token == null) error(term, "Expected \'focus-angle <number>\', \'focus-distance <number>\', \'center (<number>,<number>)\' or \'<size>\'");
    1890
    1891
    1892         ParsedValueImpl<?,Size> focusAngle = null;
    1893         if (term.token.getType() == CSSLexer.IDENT) {
    1894             final String keyword = term.token.getText().toLowerCase();
    1895             if ("focus-angle".equals(keyword)) {
    1896
    1897                 prev = term;
    1898                 if ((term = term.nextInSeries) == null) error(prev, "Expected \'<number>\'");
    1899                 if (term.token == null) error(prev, "Expected \'<number>\'");
    1900
    1901                 focusAngle = parseSize(term);
    1902
    1903                 prev = term;
    1904                 if ((term = term.nextInSeries) == null) error(prev, "Expected \'focus-distance <number>\', \'center (<number>,<number>)\' or \'<size>\'");
    1905                 if (term.token == null) error(term,  "Expected \'focus-distance <number>\', \'center (<number>,<number>)\' or \'<size>\'");
    1906             }
    1907         }
    1908
    1909         ParsedValueImpl<?,Size> focusDistance = null;
    1910         if (term.token.getType() == CSSLexer.IDENT) {
    1911             final String keyword = term.token.getText().toLowerCase();
    1912             if ("focus-distance".equals(keyword)) {
    1913
    1914                 prev = term;
    1915                 if ((term = term.nextInSeries) == null) error(prev, "Expected \'<number>\'");
    1916                 if (term.token == null) error(prev, "Expected \'<number>\'");
    1917
    1918                 focusDistance = parseSize(term);
    1919
    1920                 prev = term;
    1921                 if ((term = term.nextInSeries) == null) error(prev, "Expected  \'center (<number>,<number>)\' or \'<size>\'");
    1922                 if (term.token == null) error(term,  "Expected  \'center (<number>,<number>)\' or \'<size>\'");
    1923             }
    1924         }
    1925
    1926         ParsedValueImpl<?,Size>[] centerPoint = null;
    1927         if (term.token.getType() == CSSLexer.IDENT) {
    1928             final String keyword = term.token.getText().toLowerCase();
    1929             if ("center".equals(keyword)) {
    1930
    1931                 prev = term;
    1932                 if ((term = term.nextInSeries) == null) error(prev, "Expected \'(<number>,<number>)\'");
    1933                 if (term.token == null ||
    1934                     term.token.getType() != CSSLexer.LPAREN) error(term, "Expected \'(<number>,<number>)\'");
    1935
    1936                 centerPoint = point(term);
    1937
    1938                 prev = term;
    1939                 if ((term = term.nextInSeries) == null) error(prev, "Expected \'<size>\'");
    1940                 if (term.token == null) error(term,  "Expected \'<size>\'");
    1941             }
    1942         }
    1943
    1944         ParsedValueImpl<?,Size> radius = parseSize(term);
    1945
    1946         prev = term;
    1947         if ((term = term.nextInSeries) == null) error(prev, "Expected \'stops\' keyword");
    1948         if (term.token == null ||
    2531                     left = FIFTY_PERCENT;
    2532                     right = ZERO_PERCENT;
    2533
    2534                     top = ZERO_PERCENT;
    2535                     bottom = ZERO_PERCENT;
    2536
    2537                 } else if( "bottom".equals(v1) ) {
    2538                     left = FIFTY_PERCENT;
    2539                     right = ZERO_PERCENT;
    2540
    2541                     top = ONE_HUNDRED_PERCENT;
    2542                     bottom = ZERO_PERCENT;
    2543                 } else {
    2544                     left = parseSize(termOne);
    2545                     right = ZERO_PERCENT;
    2546                     top = ZERO_PERCENT;
    2547                     bottom = ZERO_PERCENT;
    2548                 }
    2549             } else if( valueOne != null && valueTwo != null && valueThree == null && valueFour == null ) {
    2550                 // 2 values
    2551                 String v1 = valueOne.getText().toLowerCase();
    2552                 String v2 = valueTwo.getText().toLowerCase();
    2553
    2554                 if( ! isPositionKeyWord(v1) ) {
    2555                     left = parseSize(termOne);
    2556                     right = ZERO_PERCENT;
    2557
    2558                     if( "top".equals(v2) ) {
    2559                         top = ZERO_PERCENT;
    2560                         bottom = ZERO_PERCENT;
    2561                     } else if( "bottom".equals(v2) ) {
    2562                         top = ONE_HUNDRED_PERCENT;
    2563                         bottom = ZERO_PERCENT;
    2564                     } else if( "center".equals(v2) ) {
    2565                         top = FIFTY_PERCENT;
    2566                         bottom = ZERO_PERCENT;
    2567                     } else if( !isPositionKeyWord(v2) ) {
    2568                         top = parseSize(termTwo);
    2569                         bottom = ZERO_PERCENT;
    2570                     } else {
    2571                         error(termTwo,"Expected 'top', 'bottom', 'center' or <size>");
    2572                     }
    2595                     left = FIFTY_PERCENT;
    2596                     right = ZERO_PERCENT;
    2597
    2598                     if( v2.equals("top") ) {
    2599                         top = ZERO_PERCENT;
    2600                         bottom = ZERO_PERCENT;
    2601                     } else if( v2.equals("bottom") ) {
    2602                         top = ONE_HUNDRED_PERCENT;
    2603                         bottom = ZERO_PERCENT;
    2604                     } else if( v2.equals("center") ) {
    2605                         top = FIFTY_PERCENT;
    2606                         bottom = ZERO_PERCENT;
    2607                     } else if( ! isPositionKeyWord(v2) ) {
    2608                         top = parseSize(termTwo);
    2609                         bottom = ZERO_PERCENT;
    2610                     } else {
    2611                         error(termTwo,"Expected 'top', 'bottom', 'center' or <size>");
    2612                     }
    2613                 }
    2614             } else if( valueOne != null && valueTwo != null && valueThree != null && valueFour == null ) {
    2615                 String v1 = valueOne.getText().toLowerCase();
    2616                 String v2 = valueTwo.getText().toLowerCase();
    2617                 String v3 = valueThree.getText().toLowerCase();
    2618
    2619                 if( ! isPositionKeyWord(v1) || "center".equals(v1) ) {
    2620                     // 1 is horizontal
    2621                     // means 2 & 3 are vertical
    2622                     if( "center".equals(v1) ) {
    2623                         left = FIFTY_PERCENT;
    2624                     } else {
    2625                         left = parseSize(termOne);
    2626                     }
    2627                     right = ZERO_PERCENT;
    2628
    2629                     if( !isPositionKeyWord(v3) ) {
    2630                         if( "top".equals(v2) ) {
    2631                             top = parseSize(termThree);
    2632                             bottom = ZERO_PERCENT;
    2633                         } else if( "bottom".equals(v2) ) {
    2634                             top = ZERO_PERCENT;
    2635                             bottom = parseSize(termThree);
    2636                         } else {
    2637                             error(termTwo,"Expected 'top' or 'bottom'");
    2673                             left = ONE_HUNDRED_PERCENT;
    2674                             right = ZERO_PERCENT;
    2675                         }
    2676
    2677                         if( ! isPositionKeyWord(v3) ) {
    2678                             if( "top".equals(v2) ) {
    2679                                 top = parseSize(termThree);
    2680                                 bottom = ZERO_PERCENT;
    2681                             } else if( "bottom".equals(v2) ) {
    2682                                 top = ZERO_PERCENT;
    2683                                 bottom = parseSize(termThree);
    2684                             } else {
    2685                                 error(termTwo,"Expected 'top' or 'bottom'");
    2686                             }
    2687                         } else {
    2688                             error(termThree,"Expected <size>");
    2689                         }
    2690                     }
    2691                 }
    2692             } else {
    2693                 String v1 = valueOne.getText().toLowerCase();
    2694                 String v2 = valueTwo.getText().toLowerCase();
    2695                 String v3 = valueThree.getText().toLowerCase();
    2696                 String v4 = valueFour.getText().toLowerCase();
    2697
    2698                 if( (v1.equals("left") || v1.equals("right")) && (v3.equals("top") || v3.equals("bottom") ) && ! isPositionKeyWord(v2) && ! isPositionKeyWord(v4) ) {
    2699                     if( v1.equals("left") ) {
    2700                         left = parseSize(termTwo);
    2701                         right = ZERO_PERCENT;
    2702                     } else {
    2703                         left = ZERO_PERCENT;
    2704                         right = parseSize(termTwo);
    2705                     }
    2706
    2707                     if( v3.equals("top") ) {
    2708                         top = parseSize(termFour);
    2709                         bottom = ZERO_PERCENT;
    2710                     } else {
    2711                         top = ZERO_PERCENT;
    2712                         bottom = parseSize(termFour);
    2713                     }
    2714
    2715                 } else {
    2716                     error(term,"Expected 'left' or 'right' followed by <size> followed by 'top' or 'bottom' followed by <size>");
    2736         return new ParsedValueImpl<ParsedValue<ParsedValue[], BackgroundPosition>[], BackgroundPosition[]>(layers, LayeredBackgroundPositionConverter.getInstance());
    2737     }
    2738
    2739     /*
    2740     http://www.w3.org/TR/css3-background/#the-background-repeat
    2741     <repeat-style> = repeat-x | repeat-y | [repeat | space | round | no-repeat]{1,2}
    2742     */
    2743     private ParsedValueImpl<String, BackgroundRepeat>[] parseRepeatStyle(final Term root)
    2744             throws ParseException {
    2745
    2746         BackgroundRepeat xAxis, yAxis;
    2747         xAxis = yAxis = BackgroundRepeat.NO_REPEAT;
    2748
    2749         Term term = root;
    2750
    2751         if (term.token == null ||
    2752             term.token.getType() != CSSLexer.IDENT ||
    2753             term.token.getText() == null ||
    2754             term.token.getText().isEmpty()) error(term, "Expected \'<repeat-style>\'");
    2755
    2756         String text = term.token.getText().toLowerCase();
    2757         if ("repeat-x".equals(text)) {
    2758             xAxis = BackgroundRepeat.REPEAT;
    2759             yAxis = BackgroundRepeat.NO_REPEAT;
    2760         } else if ("repeat-y".equals(text)) {
    2761             xAxis = BackgroundRepeat.NO_REPEAT;
    2762             yAxis = BackgroundRepeat.REPEAT;
    2763         } else if ("repeat".equals(text)) {
    2764             xAxis = BackgroundRepeat.REPEAT;
    2765             yAxis = BackgroundRepeat.REPEAT;
    2766         } else if ("space".equals(text)) {
    2767             xAxis = BackgroundRepeat.SPACE;
    2768             yAxis = BackgroundRepeat.SPACE;
    2769         } else if ("round".equals(text)) {
    2770             xAxis = BackgroundRepeat.ROUND;
    2771             yAxis = BackgroundRepeat.ROUND;
    2772         } else if ("no-repeat".equals(text)) {
    2773             xAxis = BackgroundRepeat.NO_REPEAT;
    2774             yAxis = BackgroundRepeat.NO_REPEAT;
    2775         } else if ("stretch".equals(text)) {
    2776             xAxis = BackgroundRepeat.NO_REPEAT;
    2777             yAxis = BackgroundRepeat.NO_REPEAT;
    2778         } else {
    2779             error(term, "Expected  \'<repeat-style>\' " + text);
    2780         }
    2781
    2782         if ((term = term.nextInSeries) != null &&
    2783              term.token != null &&
    2784              term.token.getType() == CSSLexer.IDENT &&
    2785              term.token.getText() != null &&
    2786              !term.token.getText().isEmpty()) {
    2787
    2788             text = term.token.getText().toLowerCase();
    2789             if ("repeat-x".equals(text)) {
    2790                 error(term, "Unexpected \'repeat-x\'");
    2791             } else if ("repeat-y".equals(text)) {
    2792                 error(term, "Unexpected \'repeat-y\'");
    2793             } else if ("repeat".equals(text)) {
    2794                 yAxis = BackgroundRepeat.REPEAT;
    2795             } else if ("space".equals(text)) {
    2796                 yAxis = BackgroundRepeat.SPACE;
    2797             } else if ("round".equals(text)) {
    2798                 yAxis = BackgroundRepeat.ROUND;
    2799             } else if ("no-repeat".equals(text)) {
    2800                 yAxis = BackgroundRepeat.NO_REPEAT;
    2801             } else if ("stretch".equals(text)) {
    2802                 yAxis = BackgroundRepeat.NO_REPEAT;
    2803             } else {
    2804                 error(term, "Expected  \'<repeat-style>\'");
    2805             }
    2806         }
    2807
    2808         return new ParsedValueImpl[] {
    2838             term = nextLayer(term);
    2839         }
    2840         return new ParsedValueImpl<ParsedValue<String, BackgroundRepeat>[][], RepeatStruct[]>(layers, RepeatStructConverter.getInstance());
    2841     }
    2842
    2843     /*
    2844     http://www.w3.org/TR/css3-background/#the-background-size
    2845     <bg-size> = [ <length> | <percentage> | auto ]{1,2} | cover | contain
    2846     */
    2847     private ParsedValueImpl<ParsedValue[], BackgroundSize> parseBackgroundSize(final Term root)
    2848         throws ParseException {
    2849
    2850         ParsedValueImpl<?,Size> height = null, width = null;
    2851         boolean cover = false, contain = false;
    2852
    2853         Term term = root;
    2854         if (term.token == null) error(term, "Expected \'<bg-size>\'");
    2855
    2856         if (term.token.getType() == CSSLexer.IDENT) {
    2857             final String text =
    2858                 (term.token.getText() != null) ? term.token.getText().toLowerCase() : null;
    2859
    2860             if ("auto".equals(text)) {
    2861                 // We don't do anything because width / height are already initialized
    2862             } else if ("cover".equals(text)) {
    2863                 cover = true;
    2864             } else if ("contain".equals(text)) {
    2865                 contain = true;
    2866             } else if ("stretch".equals(text)) {
    2867                 width = ONE_HUNDRED_PERCENT;
    2868                 height = ONE_HUNDRED_PERCENT;
    2869             } else {
    2870                 error(term, "Expected \'auto\', \'cover\', \'contain\', or  \'stretch\'");
    2871             }
    2872         } else if (isSize(term.token)) {
    2873             width = parseSize(term);
    2874             height = null;
    2875         } else {
    2876             error(term, "Expected \'<bg-size>\'");
    2877         }
    2878
    2879         if ((term = term.nextInSeries) != null) {
    2880             if (cover || contain) error(term, "Unexpected \'<bg-size>\'");
    2881
    2882             if (term.token.getType() == CSSLexer.IDENT) {
    2883                 final String text =
    2884                     (term.token.getText() != null) ? term.token.getText().toLowerCase() : null;
    2885
    2886                 if ("auto".equals(text)) {
    2887                     height = null;
    2888                 } else if ("cover".equals(text)) {
    2889                     error(term, "Unexpected \'cover\'");
    2890                 } else if ("contain".equals(text)) {
    2891                     error(term, "Unexpected \'contain\'");
    2892                 } else if ("stretch".equals(text)) {
    2893                     height = ONE_HUNDRED_PERCENT;
    2894                 } else {
    2895                     error(term, "Expected \'auto\' or \'stretch\'");
    2896                 }
    2897             } else if (isSize(term.token)) {
    2898                 height = parseSize(term);
    2899             } else {
    2900                 error(term, "Expected \'<bg-size>\'");
    2901             }
    2902
    2903         }
    2904
    2985
    2986         int nLayers = numberOfLayers(root);
    2987         ParsedValueImpl<ParsedValue<ParsedValue[],BorderStrokeStyle>[],BorderStrokeStyle[]>[] layers = new ParsedValueImpl[nLayers];
    2988         int layer = 0;
    2989         Term term = root;
    2990         while (term != null) {
    2991             layers[layer++] = parseBorderStyleSeries(term);
    2992             term = nextLayer(term);
    2993         }
    2994         return new ParsedValueImpl<ParsedValue<ParsedValue<ParsedValue[],BorderStrokeStyle>[],BorderStrokeStyle[]>[], BorderStrokeStyle[][]>(layers, LayeredBorderStyleConverter.getInstance());
    2995     }
    2996
    2997     // Only meant to be used from parseBorderStyle, but might be useful elsewhere
    2998     private String getKeyword(final Term term) {
    2999         if (term != null &&
    3000              term.token != null &&
    3001              term.token.getType() == CSSLexer.IDENT &&
    3002              term.token.getText() != null &&
    3003              !term.token.getText().isEmpty()) {
    3004
    3005             return term.token.getText().toLowerCase();
    3006         }
    3007         return null;
    3008     }
    3009
    3010     //<border-style> [ , <border-style> ]*
    3011     // where <border-style> =
    3012     //      <dash-style> [centered | inside | outside]? [line-join [miter <number> | bevel | round]]? [line-cap [square | butt | round]]?
    3013     // where <dash-style> =
    3014     //      [ none | solid | dotted | dashed ]
    3015     private ParsedValueImpl<ParsedValue[],BorderStrokeStyle> parseBorderStyle(final Term root)
    3016             throws ParseException {
    3017
    3018
    3019         ParsedValue<ParsedValue[],Number[]> dashStyle = null;
    3020         ParsedValue<ParsedValue<?,Size>,Number> dashPhase = null;
    3021         ParsedValue<String,StrokeType> strokeType = null;
    3022         ParsedValue<String,StrokeLineJoin> strokeLineJoin = null;
    3023         ParsedValue<ParsedValue<?,Size>,Number> strokeMiterLimit = null;
    3024         ParsedValue<String,StrokeLineCap> strokeLineCap = null;
    3025
    3125         } else if (ttype == CSSLexer.FUNCTION) {
    3126             segments = segments(root);
    3127         } else {
    3128             error(root, "Expected \'<dash-style>\'");
    3129         }
    3130
    3131         return segments;
    3132     }
    3133
    3134     /*
    3135     <border-style> = none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset
    3136     */
    3137     private ParsedValue<ParsedValue[],Number[]>  borderStyle(Term root)
    3138             throws ParseException {
    3139
    3140         if (root.token == null ||
    3141             root.token.getType() != CSSLexer.IDENT ||
    3142             root.token.getText() == null ||
    3143             root.token.getText().isEmpty()) error(root, "Expected \'<border-style>\'");
    3144
    3145         final String text = root.token.getText().toLowerCase();
    3146
    3147         if ("none".equals(text)) {
    3148             return BorderStyleConverter.NONE;
    3149         } else if ("hidden".equals(text)) {
    3150             // The "hidden" mode doesn't make sense for FX, because it is the
    3151             // same as "none" except for border-collapsed CSS tables
    3152             return BorderStyleConverter.NONE;
    3153         } else if ("dotted".equals(text)) {
    3154             return BorderStyleConverter.DOTTED;
    3155         } else if ("dashed".equals(text)) {
    3156             return BorderStyleConverter.DASHED;
    3157         } else if ("solid".equals(text)) {
    3158             return BorderStyleConverter.SOLID;
    3159         } else if ("double".equals(text)) {
    3160             error(root, "Unsupported <border-style> \'double\'");
    3161         } else if ("groove".equals(text)) {
    3162             error(root, "Unsupported <border-style> \'groove\'");
    3163         } else if ("ridge".equals(text)) {
    3164             error(root, "Unsupported <border-style> \'ridge\'");
    3165         } else if ("inset".equals(text)) {
    3432         }
    3433
    3434         return new ParsedValueImpl<ParsedValue<ParsedValue[],String>[],String[]>(layers, URLConverter.SequenceConverter.getInstance());
    3435     }
    3436
    3437     ////////////////////////////////////////////////////////////////////////////
    3438     //
    3439     // http://www.w3.org/TR/css3-fonts
    3440     //
    3441     ////////////////////////////////////////////////////////////////////////////
    3442
    3443     /* http://www.w3.org/TR/css3-fonts/#font-size-the-font-size-property */
    3444     private ParsedValueImpl<ParsedValue<?,Size>,Number> parseFontSize(final Term root) throws ParseException {
    3445
    3446         if (root == null) return null;
    3447         final Token token = root.token;
    3448         if (token == null || !isSize(token)) error(root, "Expected \'<font-size>\'");
    3449
    3450         Size size = null;
    3451         if (token.getType() == CSSLexer.IDENT) {
    3452             final String ident = token.getText().toLowerCase();
    3453             double value = -1;
    3454             if ("inherit".equals(ident)) {
    3455                 value = 100;
    3456             } else if ("xx-small".equals(ident)) {
    3457                 value = 60;
    3458             } else if ("x-small".equals(ident)) {
    3459                 value = 75;
    3460             } else if ("small".equals(ident)) {
    3461                 value = 80;
    3462             } else if ("medium".equals(ident)) {
    3463                 value = 100;
    3464             } else if ("large".equals(ident)) {
    3465                 value = 120;
    3466             } else if ("x-large".equals(ident)) {
    3467                 value = 150;
    3468             } else if ("xx-large".equals(ident)) {
    3469                 value = 200;
    3470             } else if ("smaller".equals(ident)) {
    3471                 value = 80;
    3472             } else if ("larger".equals(ident)) {
    3480
    3481         // if size is null, then size is not one of the keywords above.
    3482         if (size == null) {
    3483             size = size(token);
    3484         }
    3485
    3486         ParsedValueImpl<?,Size> svalue = new ParsedValueImpl<Size,Size>(size, null);
    3487         return new ParsedValueImpl<ParsedValue<?,Size>,Number>(svalue, FontConverter.FontSizeConverter.getInstance());
    3488     }
    3489
    3490     /* http://www.w3.org/TR/css3-fonts/#font-style-the-font-style-property */
    3491     private ParsedValueImpl<String,FontPosture> parseFontStyle(Term root) throws ParseException {
    3492
    3493         if (root == null) return null;
    3494         final Token token = root.token;
    3495         if (token == null ||
    3496             token.getType() != CSSLexer.IDENT ||
    3497             token.getText() == null ||
    3498             token.getText().isEmpty()) error(root, "Expected \'<font-style>\'");
    3499
    3500         final String ident = token.getText().toLowerCase();
    3501         String posture = FontPosture.REGULAR.name();
    3502
    3503         if ("normal".equals(ident)) {
    3504             posture = FontPosture.REGULAR.name();
    3505         } else if ("italic".equals(ident)) {
    3506             posture = FontPosture.ITALIC.name();
    3507         } else if ("oblique".equals(ident)) {
    3508             posture = FontPosture.ITALIC.name();
    3509         } else if ("inherit".equals(ident)) {
    3510             posture = "inherit";
    3511         } else {
    3512             return null;
    3513         }
    3514
    3515         return new ParsedValueImpl<String,FontPosture>(posture, FontConverter.FontStyleConverter.getInstance());
    3516     }
    3517
    3518     /* http://www.w3.org/TR/css3-fonts/#font-weight-the-font-weight-property */
    3519     private ParsedValueImpl<String, FontWeight> parseFontWeight(Term root) throws ParseException {
    3520
    3521         if (root == null) return null;
    3522         final Token token = root.token;
    3523         if (token == null ||
    3524             token.getText() == null ||
    3525             token.getText().isEmpty()) error(root, "Expected \'<font-weight>\'");
    3526
    3527         final String ident = token.getText().toLowerCase();
    3528         String weight = FontWeight.NORMAL.name();
    3529
    3530         if ("inherit".equals(ident)) {
    3531             weight = FontWeight.NORMAL.name();
    3532         } else if ("normal".equals(ident)) {
    3533             weight = FontWeight.NORMAL.name();
    3534         } else if ("bold".equals(ident)) {
    3535             weight = FontWeight.BOLD.name();
    3536         } else if ("bolder".equals(ident)) {
    3537             weight = FontWeight.BOLD.name();
    3538         } else if ("lighter".equals(ident)) {
    3539             weight = FontWeight.LIGHT.name();
    3540         } else if ("100".equals(ident)) {
    3541             weight = FontWeight.findByWeight(100).name();
    3542         } else if ("200".equals(ident)) {
    3543             weight = FontWeight.findByWeight(200).name();
    3544         } else if ("300".equals(ident)) {
    3545             weight = FontWeight.findByWeight(300).name();
    3546         } else if ("400".equals(ident)) {
    3547             weight = FontWeight.findByWeight(400).name();
    3555             weight = FontWeight.findByWeight(800).name();
    3556         } else if ("900".equals(ident)) {
    3557             weight = FontWeight.findByWeight(900).name();
    3558         } else {
    3559             error(root, "Expected \'<font-weight>\'");
    3560         }
    3561         return new ParsedValueImpl<String,FontWeight>(weight, FontConverter.FontWeightConverter.getInstance());
    3562     }
    3563
    3564     private ParsedValueImpl<String,String>  parseFontFamily(Term root) throws ParseException {
    3565
    3566         if (root == null) return null;
    3567         final Token token = root.token;
    3568         String text = null;
    3569         if (token == null ||
    3570             (token.getType() != CSSLexer.IDENT &&
    3571              token.getType() != CSSLexer.STRING) ||
    3572             (text = token.getText()) == null ||
    3573             text.isEmpty()) error(root, "Expected \'<font-family>\'");
    3574
    3575         final String fam = stripQuotes(text.toLowerCase());
    3576         if ("inherit".equals(fam)) {
    3577             return new ParsedValueImpl<String,String>("inherit", StringConverter.getInstance());
    3578         } else if ("serif".equals(fam) ||
    3579             "sans-serif".equals(fam) ||
    3580             "cursive".equals(fam) ||
    3581             "fantasy".equals(fam) ||
    3582             "monospace".equals(fam)) {
    3583             return new ParsedValueImpl<String,String>(fam, StringConverter.getInstance());
    3584         } else {
    3585             return new ParsedValueImpl<String,String>(token.getText(), StringConverter.getInstance());
    3586         }
    3587     }
    3588
    3589     // (fontStyle || fontVariant || fontWeight)* fontSize (SOLIDUS size)? fontFamily
    3590     private ParsedValueImpl<ParsedValue[],Font> parseFont(Term root) throws ParseException {
    3591
    3592         // Because style, variant, weight, size and family can inherit
    3593         // AND style, variant and weight are optional, parsing this backwards
    3594         // is easier.
    3595         Term next = root.nextInSeries;
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25
      26 package com.sun.javafx.css.parser;
      27
      28 import java.io.BufferedReader;
      29 import java.io.CharArrayReader;
      30 import java.io.IOException;
      31 import java.io.InputStreamReader;
      32 import java.io.Reader;
      33 import java.net.URI;
      34 import java.net.URISyntaxException;
      35 import java.net.URL;
      36 import java.text.MessageFormat;
      37 import java.util.ArrayList;
      38 import java.util.Collections;
      39 import java.util.HashMap;
      40 import java.util.List;
      41 import java.util.Locale;
      42 import java.util.Map;
      43 import javafx.geometry.Insets;
      44 import javafx.scene.effect.BlurType;
      45 import javafx.scene.effect.Effect;
      46 import javafx.scene.layout.BackgroundPosition;
      47 import javafx.scene.layout.BackgroundRepeat;
      48 import javafx.scene.layout.BackgroundSize;
      49 import javafx.scene.layout.BorderStrokeStyle;
      50 import javafx.scene.layout.BorderWidths;
      51 import javafx.scene.paint.Color;
      52 import javafx.scene.paint.CycleMethod;
      53 import javafx.scene.paint.Paint;
      54 import javafx.scene.paint.Stop;
      55 import javafx.scene.shape.StrokeLineCap;
      56 import javafx.scene.shape.StrokeLineJoin;
      57 import javafx.scene.shape.StrokeType;
      58 import javafx.scene.text.Font;
      59 import javafx.scene.text.FontPosture;
      60 import javafx.scene.text.FontWeight;
      61 import com.sun.javafx.Utils;
     681     }
     682
     683     // Get the next layer following this term, which may be null
     684     private Term nextLayer(final Term root) {
     685         if (root == null) return null;
     686
     687         Term term = root;
     688         while (term.nextInSeries != null) {
     689             term = term.nextInSeries;
     690         }
     691         return term.nextLayer;
     692     }
     693
     694     ////////////////////////////////////////////////////////////////////////////
     695     //
     696     // Parsing routines
     697     //
     698     ////////////////////////////////////////////////////////////////////////////
     699
     700     ParsedValueImpl valueFor(String property, Term root) throws ParseException {
     701         final String prop = property.toLowerCase(Locale.ROOT);
     702         properties.put(prop, prop);
     703         if (root == null || root.token == null) {
     704             error(root, "Expected value for property \'" + prop + "\'");
     705         }
     706
     707         if (root.token.getType() == CSSLexer.IDENT) {
     708             if ("inherit".equalsIgnoreCase(root.token.getText())) {
     709                 return new ParsedValueImpl<String,String>("inherit", null);
     710             } else if ("null".equalsIgnoreCase(root.token.getText())) {
     711                 return new ParsedValueImpl<String,String>("null", null);
     712             }
     713         }
     714
     715         if ("-fx-background-color".equals(property)) {
     716             return parsePaintLayers(root);
     717         } else if ("-fx-background-image".equals(prop)) {
     718             return parseURILayers(root);
     719         } else if ("-fx-background-insets".equals(prop)) {
     720              return parseInsetsLayers(root);
     721         } else if ("-fx-opaque-insets".equals(prop)) {
     833         case CSSLexer.PERCENTAGE:
     834         case CSSLexer.EMS:
     835         case CSSLexer.EXS:
     836         case CSSLexer.PX:
     837         case CSSLexer.CM:
     838         case CSSLexer.MM:
     839         case CSSLexer.IN:
     840         case CSSLexer.PT:
     841         case CSSLexer.PC:
     842         case CSSLexer.DEG:
     843         case CSSLexer.GRAD:
     844         case CSSLexer.RAD:
     845         case CSSLexer.TURN:
     846             ParsedValueImpl sizeValue = new ParsedValueImpl<Size,Number>(size(token), null);
     847             value = new ParsedValueImpl<ParsedValue<?,Size>, Number>(sizeValue, SizeConverter.getInstance());
     848             break;
     849         case CSSLexer.STRING:
     850         case CSSLexer.IDENT:
     851             boolean isIdent = ttype == CSSLexer.IDENT;
     852             final String str = stripQuotes(token.getText());
     853             final String text = str.toLowerCase(Locale.ROOT);
     854             if ("ladder".equals(text)) {
     855                 value = ladder(root);
     856             } else if ("linear".equals(text)) {
     857                 value = linearGradient(root);
     858             } else if ("radial".equals(text)) {
     859                 value = radialGradient(root);
     860             } else if ("true".equals(text)) {
     861                 // TODO: handling of boolean is really bogus
     862                 value = new ParsedValueImpl<String,Boolean>("true",BooleanConverter.getInstance());
     863             } else if ("false".equals(text)) {
     864                 // TODO: handling of boolean is really bogus
     865                 value = new ParsedValueImpl<String,Boolean>("false",BooleanConverter.getInstance());
     866             } else {
     867                 // if the property value is another property, then it needs to be looked up.
     868                 boolean needsLookup = isIdent && properties.containsKey(str);
     869                 if (needsLookup || ((value = colorValueOfString(str)) == null )) {
     870                     value = new ParsedValueImpl<String,String>(str, null, isIdent || needsLookup);
     871                 }
     872             }
     873             break;
    1388         } else if ("region".regionMatches(true, 0, fcn, 0, 6)) {
    1389             return parseRegion(root);
    1390         } else if ("url".regionMatches(true, 0, fcn, 0, 3)) {
    1391             return parseURI(root);
    1392         } else {
    1393             error(root, "Unexpected function \'" + fcn + "\'");
    1394         }
    1395         return null;
    1396     }
    1397
    1398     private ParsedValueImpl<String,BlurType> blurType(final Term root) throws ParseException {
    1399
    1400         if (root == null) return null;
    1401         if (root.token == null ||
    1402             root.token.getType() != CSSLexer.IDENT ||
    1403             root.token.getText() == null ||
    1404             root.token.getText().isEmpty()) {
    1405             final String msg = "Expected \'gaussian\', \'one-pass-box\', \'two-pass-box\', or \'three-pass-box\'";
    1406             error(root, msg);
    1407         }
    1408         final String blurStr = root.token.getText().toLowerCase(Locale.ROOT);
    1409         BlurType blurType = BlurType.THREE_PASS_BOX;
    1410         if ("gaussian".equals(blurStr)) {
    1411             blurType = BlurType.GAUSSIAN;
    1412         } else if ("one-pass-box".equals(blurStr)) {
    1413             blurType = BlurType.ONE_PASS_BOX;
    1414         } else if ("two-pass-box".equals(blurStr)) {
    1415             blurType = BlurType.TWO_PASS_BOX;
    1416         } else if ("three-pass-box".equals(blurStr)) {
    1417             blurType = BlurType.THREE_PASS_BOX;
    1418         } else {
    1419             final String msg = "Expected \'gaussian\', \'one-pass-box\', \'two-pass-box\', or \'three-pass-box\'";
    1420             error(root, msg);
    1421         }
    1422         return new ParsedValueImpl<String,BlurType>(blurType.name(), new EnumConverter<BlurType>(BlurType.class));
    1423     }
    1424
    1425     // innershadow <blur-type> <color> <radius> <choke> <offset-x> <offset-y>
    1426     private ParsedValueImpl innershadow(final Term root) throws ParseException {
    1427
    1428         // first term in the chain is the function name...
    1512         if ((arg = arg.nextArg) == null) error(prev, "Expected \'<number>\'");
    1513
    1514         ParsedValueImpl<?,Size> offsetYVal = parseSize(arg);
    1515
    1516         ParsedValueImpl[] values = new ParsedValueImpl[] {
    1517             blurVal,
    1518             colorVal,
    1519             radiusVal,
    1520             spreadVal,
    1521             offsetXVal,
    1522             offsetYVal
    1523         };
    1524         return new ParsedValueImpl<ParsedValue[],Effect>(values, EffectConverter.DropShadowConverter.getInstance());
    1525     }
    1526
    1527     // returns null if the Term is null or is not a cycle method.
    1528     private ParsedValueImpl<String, CycleMethod> cycleMethod(final Term root) {
    1529         CycleMethod cycleMethod = null;
    1530         if (root != null && root.token.getType() == CSSLexer.IDENT) {
    1531
    1532             final String text = root.token.getText().toLowerCase(Locale.ROOT);
    1533             if ("repeat".equals(text)) {
    1534                 cycleMethod = CycleMethod.REPEAT;
    1535             } else if ("reflect".equals(text)) {
    1536                 cycleMethod = CycleMethod.REFLECT;
    1537             } else if ("no-cycle".equals(text)) {
    1538                 cycleMethod = CycleMethod.NO_CYCLE;
    1539             }
    1540         }
    1541         if (cycleMethod != null)
    1542             return new ParsedValueImpl<String,CycleMethod>(cycleMethod.name(), new EnumConverter<CycleMethod>(CycleMethod.class));
    1543         else
    1544             return null;
    1545     }
    1546
    1547     // linear <point> TO <point> STOPS <stop>+ cycleMethod?
    1548     private ParsedValueImpl<ParsedValue[],Paint> linearGradient(final Term root) throws ParseException {
    1549
    1550         final String fn = (root.token != null) ? root.token.getText() : null;
    1551         if (fn == null || !"linear".equalsIgnoreCase(fn)) {
    1552             final String msg = "Expected \'linear\'";
    1716
    1717             prev = arg;
    1718             arg = arg.nextArg;
    1719
    1720         } else if("to".equalsIgnoreCase(arg.token.getText())) {
    1721
    1722             prev = arg;
    1723             if ((arg = arg.nextInSeries) == null ||
    1724                 arg.token == null ||
    1725                 arg.token.getType() != CSSLexer.IDENT ||
    1726                 arg.token.getText().isEmpty()) {
    1727                 error (prev, "Expected \'<side-or-corner>\'");
    1728             }
    1729
    1730
    1731             int startX = 0;
    1732             int startY = 0;
    1733             int endX = 0;
    1734             int endY = 0;
    1735
    1736             String sideOrCorner1 = arg.token.getText().toLowerCase(Locale.ROOT);
    1737             // The keywords denote the direction.
    1738             if ("top".equals(sideOrCorner1)) {
    1739                 // going toward the top, then start at the bottom
    1740                 startY = 100;
    1741                 endY = 0;
    1742
    1743             } else if ("bottom".equals(sideOrCorner1)) {
    1744                 // going toward the bottom, then start at the top
    1745                 startY = 0;
    1746                 endY = 100;
    1747
    1748             } else if ("right".equals(sideOrCorner1)) {
    1749                 // going toward the right, then start at the left
    1750                 startX = 0;
    1751                 endX = 100;
    1752
    1753             } else if ("left".equals(sideOrCorner1)) {
    1754                 // going toward the left, then start at the right
    1755                 startX = 100;
    1756                 endX = 0;
    1757
    1758             } else {
    1759                 error(arg, "Invalid \'<side-or-corner>\'");
    1760             }
    1761
    1762             prev = arg;
    1763             if (arg.nextInSeries != null) {
    1764                 arg = arg.nextInSeries;
    1765                 if (arg.token != null &&
    1766                     arg.token.getType() == CSSLexer.IDENT &&
    1767                     !arg.token.getText().isEmpty()) {
    1768
    1769                     String sideOrCorner2 = arg.token.getText().toLowerCase(Locale.ROOT);
    1770
    1771                     // if right or left has already been given,
    1772                     // then either startX or endX will not be zero.
    1773                     if ("right".equals(sideOrCorner2) &&
    1774                         startX == 0 && endX == 0) {
    1775                         // start left, end right
    1776                         startX = 0;
    1777                         endX = 100;
    1778                     } else if ("left".equals(sideOrCorner2) &&
    1779                         startX == 0 && endX == 0) {
    1780                         // start right, end left
    1781                         startX = 100;
    1782                         endX = 0;
    1783
    1784                     // if top or bottom has already been given,
    1785                     // then either startY or endY will not be zero.
    1786                     } else if("top".equals(sideOrCorner2) &&
    1787                         startY == 0 && endY == 0) {
    1788                         // start bottom, end top
    1789                         startY = 100;
    1875
    1876         final String fn = (root.token != null) ? root.token.getText() : null;
    1877         if (fn == null || !"radial".equalsIgnoreCase(fn)) {
    1878             final String msg = "Expected \'radial\'";
    1879             error(root, msg);
    1880         }
    1881
    1882         if (LOGGER.isLoggable(Level.WARNING)) {
    1883             LOGGER.warning(formatDeprecatedMessage(root, "radial gradient"));
    1884         }
    1885
    1886         Term term = root;
    1887         Term prev = root;
    1888
    1889         if ((term = term.nextInSeries) == null) error(root, "Expected \'focus-angle <number>\', \'focus-distance <number>\', \'center (<number>,<number>)\' or \'<size>\'");
    1890         if (term.token == null) error(term, "Expected \'focus-angle <number>\', \'focus-distance <number>\', \'center (<number>,<number>)\' or \'<size>\'");
    1891
    1892
    1893         ParsedValueImpl<?,Size> focusAngle = null;
    1894         if (term.token.getType() == CSSLexer.IDENT) {
    1895             final String keyword = term.token.getText().toLowerCase(Locale.ROOT);
    1896             if ("focus-angle".equals(keyword)) {
    1897
    1898                 prev = term;
    1899                 if ((term = term.nextInSeries) == null) error(prev, "Expected \'<number>\'");
    1900                 if (term.token == null) error(prev, "Expected \'<number>\'");
    1901
    1902                 focusAngle = parseSize(term);
    1903
    1904                 prev = term;
    1905                 if ((term = term.nextInSeries) == null) error(prev, "Expected \'focus-distance <number>\', \'center (<number>,<number>)\' or \'<size>\'");
    1906                 if (term.token == null) error(term,  "Expected \'focus-distance <number>\', \'center (<number>,<number>)\' or \'<size>\'");
    1907             }
    1908         }
    1909
    1910         ParsedValueImpl<?,Size> focusDistance = null;
    1911         if (term.token.getType() == CSSLexer.IDENT) {
    1912             final String keyword = term.token.getText().toLowerCase(Locale.ROOT);
    1913             if ("focus-distance".equals(keyword)) {
    1914
    1915                 prev = term;
    1916                 if ((term = term.nextInSeries) == null) error(prev, "Expected \'<number>\'");
    1917                 if (term.token == null) error(prev, "Expected \'<number>\'");
    1918
    1919                 focusDistance = parseSize(term);
    1920
    1921                 prev = term;
    1922                 if ((term = term.nextInSeries) == null) error(prev, "Expected  \'center (<number>,<number>)\' or \'<size>\'");
    1923                 if (term.token == null) error(term,  "Expected  \'center (<number>,<number>)\' or \'<size>\'");
    1924             }
    1925         }
    1926
    1927         ParsedValueImpl<?,Size>[] centerPoint = null;
    1928         if (term.token.getType() == CSSLexer.IDENT) {
    1929             final String keyword = term.token.getText().toLowerCase(Locale.ROOT);
    1930             if ("center".equals(keyword)) {
    1931
    1932                 prev = term;
    1933                 if ((term = term.nextInSeries) == null) error(prev, "Expected \'(<number>,<number>)\'");
    1934                 if (term.token == null ||
    1935                     term.token.getType() != CSSLexer.LPAREN) error(term, "Expected \'(<number>,<number>)\'");
    1936
    1937                 centerPoint = point(term);
    1938
    1939                 prev = term;
    1940                 if ((term = term.nextInSeries) == null) error(prev, "Expected \'<size>\'");
    1941                 if (term.token == null) error(term,  "Expected \'<size>\'");
    1942             }
    1943         }
    1944
    1945         ParsedValueImpl<?,Size> radius = parseSize(term);
    1946
    1947         prev = term;
    1948         if ((term = term.nextInSeries) == null) error(prev, "Expected \'stops\' keyword");
    1949         if (term.token == null ||
    2532                     left = FIFTY_PERCENT;
    2533                     right = ZERO_PERCENT;
    2534
    2535                     top = ZERO_PERCENT;
    2536                     bottom = ZERO_PERCENT;
    2537
    2538                 } else if( "bottom".equals(v1) ) {
    2539                     left = FIFTY_PERCENT;
    2540                     right = ZERO_PERCENT;
    2541
    2542                     top = ONE_HUNDRED_PERCENT;
    2543                     bottom = ZERO_PERCENT;
    2544                 } else {
    2545                     left = parseSize(termOne);
    2546                     right = ZERO_PERCENT;
    2547                     top = ZERO_PERCENT;
    2548                     bottom = ZERO_PERCENT;
    2549                 }
    2550             } else if( valueOne != null && valueTwo != null && valueThree == null && valueFour == null ) {
    2551                 // 2 values
    2552                 String v1 = valueOne.getText().toLowerCase(Locale.ROOT);
    2553                 String v2 = valueTwo.getText().toLowerCase(Locale.ROOT);
    2554
    2555                 if( ! isPositionKeyWord(v1) ) {
    2556                     left = parseSize(termOne);
    2557                     right = ZERO_PERCENT;
    2558
    2559                     if( "top".equals(v2) ) {
    2560                         top = ZERO_PERCENT;
    2561                         bottom = ZERO_PERCENT;
    2562                     } else if( "bottom".equals(v2) ) {
    2563                         top = ONE_HUNDRED_PERCENT;
    2564                         bottom = ZERO_PERCENT;
    2565                     } else if( "center".equals(v2) ) {
    2566                         top = FIFTY_PERCENT;
    2567                         bottom = ZERO_PERCENT;
    2568                     } else if( !isPositionKeyWord(v2) ) {
    2569                         top = parseSize(termTwo);
    2570                         bottom = ZERO_PERCENT;
    2571                     } else {
    2572                         error(termTwo,"Expected 'top', 'bottom', 'center' or <size>");
    2573                     }
    2596                     left = FIFTY_PERCENT;
    2597                     right = ZERO_PERCENT;
    2598
    2599                     if( v2.equals("top") ) {
    2600                         top = ZERO_PERCENT;
    2601                         bottom = ZERO_PERCENT;
    2602                     } else if( v2.equals("bottom") ) {
    2603                         top = ONE_HUNDRED_PERCENT;
    2604                         bottom = ZERO_PERCENT;
    2605                     } else if( v2.equals("center") ) {
    2606                         top = FIFTY_PERCENT;
    2607                         bottom = ZERO_PERCENT;
    2608                     } else if( ! isPositionKeyWord(v2) ) {
    2609                         top = parseSize(termTwo);
    2610                         bottom = ZERO_PERCENT;
    2611                     } else {
    2612                         error(termTwo,"Expected 'top', 'bottom', 'center' or <size>");
    2613                     }
    2614                 }
    2615             } else if( valueOne != null && valueTwo != null && valueThree != null && valueFour == null ) {
    2616                 String v1 = valueOne.getText().toLowerCase(Locale.ROOT);
    2617                 String v2 = valueTwo.getText().toLowerCase(Locale.ROOT);
    2618                 String v3 = valueThree.getText().toLowerCase(Locale.ROOT);
    2619
    2620                 if( ! isPositionKeyWord(v1) || "center".equals(v1) ) {
    2621                     // 1 is horizontal
    2622                     // means 2 & 3 are vertical
    2623                     if( "center".equals(v1) ) {
    2624                         left = FIFTY_PERCENT;
    2625                     } else {
    2626                         left = parseSize(termOne);
    2627                     }
    2628                     right = ZERO_PERCENT;
    2629
    2630                     if( !isPositionKeyWord(v3) ) {
    2631                         if( "top".equals(v2) ) {
    2632                             top = parseSize(termThree);
    2633                             bottom = ZERO_PERCENT;
    2634                         } else if( "bottom".equals(v2) ) {
    2635                             top = ZERO_PERCENT;
    2636                             bottom = parseSize(termThree);
    2637                         } else {
    2638                             error(termTwo,"Expected 'top' or 'bottom'");
    2674                             left = ONE_HUNDRED_PERCENT;
    2675                             right = ZERO_PERCENT;
    2676                         }
    2677
    2678                         if( ! isPositionKeyWord(v3) ) {
    2679                             if( "top".equals(v2) ) {
    2680                                 top = parseSize(termThree);
    2681                                 bottom = ZERO_PERCENT;
    2682                             } else if( "bottom".equals(v2) ) {
    2683                                 top = ZERO_PERCENT;
    2684                                 bottom = parseSize(termThree);
    2685                             } else {
    2686                                 error(termTwo,"Expected 'top' or 'bottom'");
    2687                             }
    2688                         } else {
    2689                             error(termThree,"Expected <size>");
    2690                         }
    2691                     }
    2692                 }
    2693             } else {
    2694                 String v1 = valueOne.getText().toLowerCase(Locale.ROOT);
    2695                 String v2 = valueTwo.getText().toLowerCase(Locale.ROOT);
    2696                 String v3 = valueThree.getText().toLowerCase(Locale.ROOT);
    2697                 String v4 = valueFour.getText().toLowerCase(Locale.ROOT);
    2698
    2699                 if( (v1.equals("left") || v1.equals("right")) && (v3.equals("top") || v3.equals("bottom") ) && ! isPositionKeyWord(v2) && ! isPositionKeyWord(v4) ) {
    2700                     if( v1.equals("left") ) {
    2701                         left = parseSize(termTwo);
    2702                         right = ZERO_PERCENT;
    2703                     } else {
    2704                         left = ZERO_PERCENT;
    2705                         right = parseSize(termTwo);
    2706                     }
    2707
    2708                     if( v3.equals("top") ) {
    2709                         top = parseSize(termFour);
    2710                         bottom = ZERO_PERCENT;
    2711                     } else {
    2712                         top = ZERO_PERCENT;
    2713                         bottom = parseSize(termFour);
    2714                     }
    2715
    2716                 } else {
    2717                     error(term,"Expected 'left' or 'right' followed by <size> followed by 'top' or 'bottom' followed by <size>");
    2737         return new ParsedValueImpl<ParsedValue<ParsedValue[], BackgroundPosition>[], BackgroundPosition[]>(layers, LayeredBackgroundPositionConverter.getInstance());
    2738     }
    2739
    2740     /*
    2741     http://www.w3.org/TR/css3-background/#the-background-repeat
    2742     <repeat-style> = repeat-x | repeat-y | [repeat | space | round | no-repeat]{1,2}
    2743     */
    2744     private ParsedValueImpl<String, BackgroundRepeat>[] parseRepeatStyle(final Term root)
    2745             throws ParseException {
    2746
    2747         BackgroundRepeat xAxis, yAxis;
    2748         xAxis = yAxis = BackgroundRepeat.NO_REPEAT;
    2749
    2750         Term term = root;
    2751
    2752         if (term.token == null ||
    2753             term.token.getType() != CSSLexer.IDENT ||
    2754             term.token.getText() == null ||
    2755             term.token.getText().isEmpty()) error(term, "Expected \'<repeat-style>\'");
    2756
    2757         String text = term.token.getText().toLowerCase(Locale.ROOT);
    2758         if ("repeat-x".equals(text)) {
    2759             xAxis = BackgroundRepeat.REPEAT;
    2760             yAxis = BackgroundRepeat.NO_REPEAT;
    2761         } else if ("repeat-y".equals(text)) {
    2762             xAxis = BackgroundRepeat.NO_REPEAT;
    2763             yAxis = BackgroundRepeat.REPEAT;
    2764         } else if ("repeat".equals(text)) {
    2765             xAxis = BackgroundRepeat.REPEAT;
    2766             yAxis = BackgroundRepeat.REPEAT;
    2767         } else if ("space".equals(text)) {
    2768             xAxis = BackgroundRepeat.SPACE;
    2769             yAxis = BackgroundRepeat.SPACE;
    2770         } else if ("round".equals(text)) {
    2771             xAxis = BackgroundRepeat.ROUND;
    2772             yAxis = BackgroundRepeat.ROUND;
    2773         } else if ("no-repeat".equals(text)) {
    2774             xAxis = BackgroundRepeat.NO_REPEAT;
    2775             yAxis = BackgroundRepeat.NO_REPEAT;
    2776         } else if ("stretch".equals(text)) {
    2777             xAxis = BackgroundRepeat.NO_REPEAT;
    2778             yAxis = BackgroundRepeat.NO_REPEAT;
    2779         } else {
    2780             error(term, "Expected  \'<repeat-style>\' " + text);
    2781         }
    2782
    2783         if ((term = term.nextInSeries) != null &&
    2784              term.token != null &&
    2785              term.token.getType() == CSSLexer.IDENT &&
    2786              term.token.getText() != null &&
    2787              !term.token.getText().isEmpty()) {
    2788
    2789             text = term.token.getText().toLowerCase(Locale.ROOT);
    2790             if ("repeat-x".equals(text)) {
    2791                 error(term, "Unexpected \'repeat-x\'");
    2792             } else if ("repeat-y".equals(text)) {
    2793                 error(term, "Unexpected \'repeat-y\'");
    2794             } else if ("repeat".equals(text)) {
    2795                 yAxis = BackgroundRepeat.REPEAT;
    2796             } else if ("space".equals(text)) {
    2797                 yAxis = BackgroundRepeat.SPACE;
    2798             } else if ("round".equals(text)) {
    2799                 yAxis = BackgroundRepeat.ROUND;
    2800             } else if ("no-repeat".equals(text)) {
    2801                 yAxis = BackgroundRepeat.NO_REPEAT;
    2802             } else if ("stretch".equals(text)) {
    2803                 yAxis = BackgroundRepeat.NO_REPEAT;
    2804             } else {
    2805                 error(term, "Expected  \'<repeat-style>\'");
    2806             }
    2807         }
    2808
    2809         return new ParsedValueImpl[] {
    2839             term = nextLayer(term);
    2840         }
    2841         return new ParsedValueImpl<ParsedValue<String, BackgroundRepeat>[][], RepeatStruct[]>(layers, RepeatStructConverter.getInstance());
    2842     }
    2843
    2844     /*
    2845     http://www.w3.org/TR/css3-background/#the-background-size
    2846     <bg-size> = [ <length> | <percentage> | auto ]{1,2} | cover | contain
    2847     */
    2848     private ParsedValueImpl<ParsedValue[], BackgroundSize> parseBackgroundSize(final Term root)
    2849         throws ParseException {
    2850
    2851         ParsedValueImpl<?,Size> height = null, width = null;
    2852         boolean cover = false, contain = false;
    2853
    2854         Term term = root;
    2855         if (term.token == null) error(term, "Expected \'<bg-size>\'");
    2856
    2857         if (term.token.getType() == CSSLexer.IDENT) {
    2858             final String text =
    2859                 (term.token.getText() != null) ? term.token.getText().toLowerCase(Locale.ROOT) : null;
    2860
    2861             if ("auto".equals(text)) {
    2862                 // We don't do anything because width / height are already initialized
    2863             } else if ("cover".equals(text)) {
    2864                 cover = true;
    2865             } else if ("contain".equals(text)) {
    2866                 contain = true;
    2867             } else if ("stretch".equals(text)) {
    2868                 width = ONE_HUNDRED_PERCENT;
    2869                 height = ONE_HUNDRED_PERCENT;
    2870             } else {
    2871                 error(term, "Expected \'auto\', \'cover\', \'contain\', or  \'stretch\'");
    2872             }
    2873         } else if (isSize(term.token)) {
    2874             width = parseSize(term);
    2875             height = null;
    2876         } else {
    2877             error(term, "Expected \'<bg-size>\'");
    2878         }
    2879
    2880         if ((term = term.nextInSeries) != null) {
    2881             if (cover || contain) error(term, "Unexpected \'<bg-size>\'");
    2882
    2883             if (term.token.getType() == CSSLexer.IDENT) {
    2884                 final String text =
    2885                     (term.token.getText() != null) ? term.token.getText().toLowerCase(Locale.ROOT) : null;
    2886
    2887                 if ("auto".equals(text)) {
    2888                     height = null;
    2889                 } else if ("cover".equals(text)) {
    2890                     error(term, "Unexpected \'cover\'");
    2891                 } else if ("contain".equals(text)) {
    2892                     error(term, "Unexpected \'contain\'");
    2893                 } else if ("stretch".equals(text)) {
    2894                     height = ONE_HUNDRED_PERCENT;
    2895                 } else {
    2896                     error(term, "Expected \'auto\' or \'stretch\'");
    2897                 }
    2898             } else if (isSize(term.token)) {
    2899                 height = parseSize(term);
    2900             } else {
    2901                 error(term, "Expected \'<bg-size>\'");
    2902             }
    2903
    2904         }
    2905
    2986
    2987         int nLayers = numberOfLayers(root);
    2988         ParsedValueImpl<ParsedValue<ParsedValue[],BorderStrokeStyle>[],BorderStrokeStyle[]>[] layers = new ParsedValueImpl[nLayers];
    2989         int layer = 0;
    2990         Term term = root;
    2991         while (term != null) {
    2992             layers[layer++] = parseBorderStyleSeries(term);
    2993             term = nextLayer(term);
    2994         }
    2995         return new ParsedValueImpl<ParsedValue<ParsedValue<ParsedValue[],BorderStrokeStyle>[],BorderStrokeStyle[]>[], BorderStrokeStyle[][]>(layers, LayeredBorderStyleConverter.getInstance());
    2996     }
    2997
    2998     // Only meant to be used from parseBorderStyle, but might be useful elsewhere
    2999     private String getKeyword(final Term term) {
    3000         if (term != null &&
    3001              term.token != null &&
    3002              term.token.getType() == CSSLexer.IDENT &&
    3003              term.token.getText() != null &&
    3004              !term.token.getText().isEmpty()) {
    3005
    3006             return term.token.getText().toLowerCase(Locale.ROOT);
    3007         }
    3008         return null;
    3009     }
    3010
    3011     //<border-style> [ , <border-style> ]*
    3012     // where <border-style> =
    3013     //      <dash-style> [centered | inside | outside]? [line-join [miter <number> | bevel | round]]? [line-cap [square | butt | round]]?
    3014     // where <dash-style> =
    3015     //      [ none | solid | dotted | dashed ]
    3016     private ParsedValueImpl<ParsedValue[],BorderStrokeStyle> parseBorderStyle(final Term root)
    3017             throws ParseException {
    3018
    3019
    3020         ParsedValue<ParsedValue[],Number[]> dashStyle = null;
    3021         ParsedValue<ParsedValue<?,Size>,Number> dashPhase = null;
    3022         ParsedValue<String,StrokeType> strokeType = null;
    3023         ParsedValue<String,StrokeLineJoin> strokeLineJoin = null;
    3024         ParsedValue<ParsedValue<?,Size>,Number> strokeMiterLimit = null;
    3025         ParsedValue<String,StrokeLineCap> strokeLineCap = null;
    3026
    3126         } else if (ttype == CSSLexer.FUNCTION) {
    3127             segments = segments(root);
    3128         } else {
    3129             error(root, "Expected \'<dash-style>\'");
    3130         }
    3131
    3132         return segments;
    3133     }
    3134
    3135     /*
    3136     <border-style> = none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset
    3137     */
    3138     private ParsedValue<ParsedValue[],Number[]>  borderStyle(Term root)
    3139             throws ParseException {
    3140
    3141         if (root.token == null ||
    3142             root.token.getType() != CSSLexer.IDENT ||
    3143             root.token.getText() == null ||
    3144             root.token.getText().isEmpty()) error(root, "Expected \'<border-style>\'");
    3145
    3146         final String text = root.token.getText().toLowerCase(Locale.ROOT);
    3147
    3148         if ("none".equals(text)) {
    3149             return BorderStyleConverter.NONE;
    3150         } else if ("hidden".equals(text)) {
    3151             // The "hidden" mode doesn't make sense for FX, because it is the
    3152             // same as "none" except for border-collapsed CSS tables
    3153             return BorderStyleConverter.NONE;
    3154         } else if ("dotted".equals(text)) {
    3155             return BorderStyleConverter.DOTTED;
    3156         } else if ("dashed".equals(text)) {
    3157             return BorderStyleConverter.DASHED;
    3158         } else if ("solid".equals(text)) {
    3159             return BorderStyleConverter.SOLID;
    3160         } else if ("double".equals(text)) {
    3161             error(root, "Unsupported <border-style> \'double\'");
    3162         } else if ("groove".equals(text)) {
    3163             error(root, "Unsupported <border-style> \'groove\'");
    3164         } else if ("ridge".equals(text)) {
    3165             error(root, "Unsupported <border-style> \'ridge\'");
    3166         } else if ("inset".equals(text)) {
    3433         }
    3434
    3435         return new ParsedValueImpl<ParsedValue<ParsedValue[],String>[],String[]>(layers, URLConverter.SequenceConverter.getInstance());
    3436     }
    3437
    3438     ////////////////////////////////////////////////////////////////////////////
    3439     //
    3440     // http://www.w3.org/TR/css3-fonts
    3441     //
    3442     ////////////////////////////////////////////////////////////////////////////
    3443
    3444     /* http://www.w3.org/TR/css3-fonts/#font-size-the-font-size-property */
    3445     private ParsedValueImpl<ParsedValue<?,Size>,Number> parseFontSize(final Term root) throws ParseException {
    3446
    3447         if (root == null) return null;
    3448         final Token token = root.token;
    3449         if (token == null || !isSize(token)) error(root, "Expected \'<font-size>\'");
    3450
    3451         Size size = null;
    3452         if (token.getType() == CSSLexer.IDENT) {
    3453             final String ident = token.getText().toLowerCase(Locale.ROOT);
    3454             double value = -1;
    3455             if ("inherit".equals(ident)) {
    3456                 value = 100;
    3457             } else if ("xx-small".equals(ident)) {
    3458                 value = 60;
    3459             } else if ("x-small".equals(ident)) {
    3460                 value = 75;
    3461             } else if ("small".equals(ident)) {
    3462                 value = 80;
    3463             } else if ("medium".equals(ident)) {
    3464                 value = 100;
    3465             } else if ("large".equals(ident)) {
    3466                 value = 120;
    3467             } else if ("x-large".equals(ident)) {
    3468                 value = 150;
    3469             } else if ("xx-large".equals(ident)) {
    3470                 value = 200;
    3471             } else if ("smaller".equals(ident)) {
    3472                 value = 80;
    3473             } else if ("larger".equals(ident)) {
    3481
    3482         // if size is null, then size is not one of the keywords above.
    3483         if (size == null) {
    3484             size = size(token);
    3485         }
    3486
    3487         ParsedValueImpl<?,Size> svalue = new ParsedValueImpl<Size,Size>(size, null);
    3488         return new ParsedValueImpl<ParsedValue<?,Size>,Number>(svalue, FontConverter.FontSizeConverter.getInstance());
    3489     }
    3490
    3491     /* http://www.w3.org/TR/css3-fonts/#font-style-the-font-style-property */
    3492     private ParsedValueImpl<String,FontPosture> parseFontStyle(Term root) throws ParseException {
    3493
    3494         if (root == null) return null;
    3495         final Token token = root.token;
    3496         if (token == null ||
    3497             token.getType() != CSSLexer.IDENT ||
    3498             token.getText() == null ||
    3499             token.getText().isEmpty()) error(root, "Expected \'<font-style>\'");
    3500
    3501         final String ident = token.getText().toLowerCase(Locale.ROOT);
    3502         String posture = FontPosture.REGULAR.name();
    3503
    3504         if ("normal".equals(ident)) {
    3505             posture = FontPosture.REGULAR.name();
    3506         } else if ("italic".equals(ident)) {
    3507             posture = FontPosture.ITALIC.name();
    3508         } else if ("oblique".equals(ident)) {
    3509             posture = FontPosture.ITALIC.name();
    3510         } else if ("inherit".equals(ident)) {
    3511             posture = "inherit";
    3512         } else {
    3513             return null;
    3514         }
    3515
    3516         return new ParsedValueImpl<String,FontPosture>(posture, FontConverter.FontStyleConverter.getInstance());
    3517     }
    3518
    3519     /* http://www.w3.org/TR/css3-fonts/#font-weight-the-font-weight-property */
    3520     private ParsedValueImpl<String, FontWeight> parseFontWeight(Term root) throws ParseException {
    3521
    3522         if (root == null) return null;
    3523         final Token token = root.token;
    3524         if (token == null ||
    3525             token.getText() == null ||
    3526             token.getText().isEmpty()) error(root, "Expected \'<font-weight>\'");
    3527
    3528         final String ident = token.getText().toLowerCase(Locale.ROOT);
    3529         String weight = FontWeight.NORMAL.name();
    3530
    3531         if ("inherit".equals(ident)) {
    3532             weight = FontWeight.NORMAL.name();
    3533         } else if ("normal".equals(ident)) {
    3534             weight = FontWeight.NORMAL.name();
    3535         } else if ("bold".equals(ident)) {
    3536             weight = FontWeight.BOLD.name();
    3537         } else if ("bolder".equals(ident)) {
    3538             weight = FontWeight.BOLD.name();
    3539         } else if ("lighter".equals(ident)) {
    3540             weight = FontWeight.LIGHT.name();
    3541         } else if ("100".equals(ident)) {
    3542             weight = FontWeight.findByWeight(100).name();
    3543         } else if ("200".equals(ident)) {
    3544             weight = FontWeight.findByWeight(200).name();
    3545         } else if ("300".equals(ident)) {
    3546             weight = FontWeight.findByWeight(300).name();
    3547         } else if ("400".equals(ident)) {
    3548             weight = FontWeight.findByWeight(400).name();
    3556             weight = FontWeight.findByWeight(800).name();
    3557         } else if ("900".equals(ident)) {
    3558             weight = FontWeight.findByWeight(900).name();
    3559         } else {
    3560             error(root, "Expected \'<font-weight>\'");
    3561         }
    3562         return new ParsedValueImpl<String,FontWeight>(weight, FontConverter.FontWeightConverter.getInstance());
    3563     }
    3564
    3565     private ParsedValueImpl<String,String>  parseFontFamily(Term root) throws ParseException {
    3566
    3567         if (root == null) return null;
    3568         final Token token = root.token;
    3569         String text = null;
    3570         if (token == null ||
    3571             (token.getType() != CSSLexer.IDENT &&
    3572              token.getType() != CSSLexer.STRING) ||
    3573             (text = token.getText()) == null ||
    3574             text.isEmpty()) error(root, "Expected \'<font-family>\'");
    3575
    3576         final String fam = stripQuotes(text.toLowerCase(Locale.ROOT));
    3577         if ("inherit".equals(fam)) {
    3578             return new ParsedValueImpl<String,String>("inherit", StringConverter.getInstance());
    3579         } else if ("serif".equals(fam) ||
    3580             "sans-serif".equals(fam) ||
    3581             "cursive".equals(fam) ||
    3582             "fantasy".equals(fam) ||
    3583             "monospace".equals(fam)) {
    3584             return new ParsedValueImpl<String,String>(fam, StringConverter.getInstance());
    3585         } else {
    3586             return new ParsedValueImpl<String,String>(token.getText(), StringConverter.getInstance());
    3587         }
    3588     }
    3589
    3590     // (fontStyle || fontVariant || fontWeight)* fontSize (SOLIDUS size)? fontFamily
    3591     private ParsedValueImpl<ParsedValue[],Font> parseFont(Term root) throws ParseException {
    3592
    3593         // Because style, variant, weight, size and family can inherit
    3594         // AND style, variant and weight are optional, parsing this backwards
    3595         // is easier.
    3596         Term next = root.nextInSeries;