// $Id$ // Author: Yves Lafon // // (c) COPYRIGHT MIT, ERCIM and Keio University, 2012. // Please first read the full copyright statement in file COPYRIGHT.html package org.w3c.css.values; import org.w3c.css.properties.css3.CssBackgroundPosition; import org.w3c.css.util.ApplContext; import org.w3c.css.util.CssVersion; import org.w3c.css.util.InvalidParamException; import java.util.ArrayList; import static org.w3c.css.values.CssOperator.COMMA; import static org.w3c.css.values.CssOperator.SPACE; /** * @author CSS3 Image */ public class CssImage extends CssValue { public static final int type = CssTypes.CSS_IMAGE; public final int getType() { return type; } static final CssIdent to = CssIdent.getIdent("to"); static final CssIdent left = CssIdent.getIdent("left"); static final CssIdent right = CssIdent.getIdent("right"); static final CssIdent top = CssIdent.getIdent("top"); static final CssIdent bottom = CssIdent.getIdent("bottom"); static final CssIdent at = CssIdent.getIdent("at"); static final CssIdent circle = CssIdent.getIdent("circle"); static final CssIdent ellipse = CssIdent.getIdent("ellipse"); static final CssIdent from = CssIdent.getIdent("from"); static final CssIdent[] extent_keywords; static { String _val[] = {"closest-corner", "closest-side", "farthest-corner", "farthest-side"}; extent_keywords = new CssIdent[_val.length]; int i = 0; for (String s : _val) { extent_keywords[i++] = CssIdent.getIdent(s); } } boolean contains_variable = false; public boolean hasCssVariable() { return contains_variable; } public void markCssVariable() { contains_variable = true; } public static boolean isVerticalIdent(CssIdent ident) { return ident.equals(top) || ident.equals(bottom); } public static CssIdent getLinearGradientIdent(CssIdent ident) { if (left.equals(ident)) { return left; } if (right.equals(ident)) { return right; } if (top.equals(ident)) { return top; } if (bottom.equals(ident)) { return bottom; } return null; } public static CssIdent getExtentIdent(CssIdent ident) { for (CssIdent id : extent_keywords) { if (id.equals(ident)) { return id; } } return null; } public static CssIdent getShape(CssIdent ident) { if (circle.equals(ident)) { return circle; } if (ellipse.equals(ident)) { return ellipse; } return null; } String name; CssValue value; private String _cache; /** * Set the value of this function * * @param s the string representation of the frequency. * @param ac For errors and warnings reports. */ public void set(String s, ApplContext ac) { // @@TODO } /** * @param exp * @param ac * @throws InvalidParamException * @spec http://www.w3.org/TR/2012/CR-css3-images-20120417/#image-list-type */ public void setImageList(CssExpression exp, ApplContext ac) throws InvalidParamException { name = "image"; _cache = null; // ImageList defined in CSS3 and onward if (ac.getCssVersion().compareTo(CssVersion.CSS3) < 0) { StringBuilder sb = new StringBuilder(); sb.append(name).append('(').append(exp.toStringFromStart()).append(')'); throw new InvalidParamException("notversion", sb.toString(), ac.getCssVersionString(), ac); } if (exp.hasCssVariable()) { markCssVariable(); } CssValue val; char op; boolean gotcolor = false; ArrayList v = new ArrayList(); CssColor c; while (!exp.end()) { val = exp.getValue(); op = exp.getOperator(); // color is always last if (gotcolor && !hasCssVariable()) { throw new InvalidParamException("value", val.toString(), "image()", ac); } switch (val.getType()) { case CssTypes.CSS_URL: case CssTypes.CSS_STRING: v.add(val); break; case CssTypes.CSS_HASH_IDENT: c = new CssColor(); c.setShortRGBColor(ac, val.getHashIdent().toString()); v.add((val.getRawType() == CssTypes.CSS_HASH_IDENT) ? c : val); gotcolor = true; break; case CssTypes.CSS_COLOR: v.add(val); gotcolor = true; break; case CssTypes.CSS_IDENT: if (CssColorCSS3.currentColor.equals(val.getIdent())) { v.add(val); gotcolor = true; break; } c = new CssColor(); c.setIdentColor(ac, val.getIdent().toString()); v.add((val.getRawType() == CssTypes.CSS_IDENT) ? c : val); gotcolor = true; break; default: if (!hasCssVariable()) { throw new InvalidParamException("value", val.toString(), "image()", ac); } } exp.next(); if ((!exp.end() && op != COMMA) && !hasCssVariable()) { exp.starts(); throw new InvalidParamException("operator", Character.toString(op), ac); } } value = (v.size() == 1) ? v.get(0) : new CssLayerList(v); } /** * @param exp * @param ac * @throws InvalidParamException * @spec http://www.w3.org/TR/2012/CR-css3-images-20120417/#linear-gradient-type */ public void setLinearGradient(CssExpression exp, ApplContext ac) throws InvalidParamException { name = "linear-gradient"; _cache = null; _setLinearGradient(exp, ac); } /** * @param exp * @param ac * @throws InvalidParamException * @spec http://www.w3.org/TR/2012/CR-css3-images-20120417/#linear-gradient-type */ public void setRepeatingLinearGradient(CssExpression exp, ApplContext ac) throws InvalidParamException { name = "repeating-linear-gradient"; _cache = null; _setLinearGradient(exp, ac); } /** * @param exp * @param ac * @throws InvalidParamException * @spec http://www.w3.org/TR/2012/CR-css3-images-20120417/#linear-gradient-type */ private void _setLinearGradient(CssExpression exp, ApplContext ac) throws InvalidParamException { // ImageList defined in CSS3 and onward if (ac.getCssVersion().compareTo(CssVersion.CSS3) < 0) { StringBuilder sb = new StringBuilder(); sb.append(name).append('(').append(exp.toStringFromStart()).append(')'); throw new InvalidParamException("notversion", sb.toString(), ac.getCssVersionString(), ac); } ArrayList v = new ArrayList(); CssValue val = exp.getValue(); char op = exp.getOperator(); if (exp.hasCssVariable()) { markCssVariable(); } switch (val.getType()) { case CssTypes.CSS_NUMBER: // 0 is an acceptable value since CR-css-images-3-20191010 val.getAngle(); case CssTypes.CSS_ANGLE: v.add(val); if (op != COMMA && !hasCssVariable()) { exp.starts(); throw new InvalidParamException("operator", Character.toString(op), ac); } exp.next(); break; case CssTypes.CSS_IDENT: CssIdent ident = val.getIdent(); if (to.equals(ident)) { CssValueList vl = new CssValueList(); vl.add(to); // we must now eat one or two valid idents // this is boringly boring... CssIdent v1 = null; CssIdent v2 = null; if (op != SPACE && !hasCssVariable()) { exp.starts(); throw new InvalidParamException("operator", Character.toString(op), ac); } exp.next(); if (exp.end()) { if (!hasCssVariable()) { throw new InvalidParamException("few-value", name, ac); } else { break; } } val = exp.getValue(); op = exp.getOperator(); boolean isV1Vertical, isV2Vertical; if ((val.getType() != CssTypes.CSS_IDENT) && !hasCssVariable()) { throw new InvalidParamException("value", val.toString(), name, ac); } v1 = getLinearGradientIdent(val.getIdent()); if ((v1 == null) && !hasCssVariable()) { throw new InvalidParamException("value", val.toString(), name, ac); } vl.add((val.getRawType() == CssTypes.CSS_IDENT) ? v1 : val); isV1Vertical = isVerticalIdent(v1); exp.next(); if (exp.end()) { if (hasCssVariable()) { throw new InvalidParamException("few-value", name, ac); } else { break; } } if (op == SPACE) { // the operator is a space, we should have // another val = exp.getValue(); op = exp.getOperator(); if ((val.getType() != CssTypes.CSS_IDENT) && !hasCssVariable()) { throw new InvalidParamException("value", val.toString(), name, ac); } v2 = getLinearGradientIdent(val.getIdent()); if ((v2 == null) && !hasCssVariable()) { throw new InvalidParamException("value", val.toString(), name, ac); } isV2Vertical = isVerticalIdent(v2); if (((isV1Vertical && isV2Vertical) || (!isV1Vertical && !isV2Vertical)) && !hasCssVariable()) { throw new InvalidParamException("value", val.toString(), name, ac); } vl.add((val.getRawType() == CssTypes.CSS_IDENT) ? v2 : val); exp.next(); } v.add(vl); if ((op != COMMA) && !hasCssVariable()) { exp.starts(); throw new InvalidParamException("operator", Character.toString(op), ac); } } if ((top.equals(ident) || bottom.equals(ident) || left.equals(ident) || right.equals(ident)) && !hasCssVariable()) { throw new InvalidParamException( // "linear-gradient-missing-to", "to " + ident, ident, ac); } break; default: // we defer errors to the next step } // now we a list of at least two color stops. ArrayList stops = parseColorStops(exp, ac, true); if ((stops.size() < 2) && !hasCssVariable()) { throw new InvalidParamException("few-value", name, ac); } v.addAll(stops); value = new CssLayerList(v); } /** * @param exp * @param ac * @throws InvalidParamException * @spec https://www.w3.org/TR/2017/WD-css-images-4-20170413/#funcdef-conic-gradient */ public void setConicGradient(CssExpression exp, ApplContext ac) throws InvalidParamException { name = "conic-gradient"; _cache = null; _setConicGradient(exp, ac); } /** * @param exp * @param ac * @throws InvalidParamException * @spec https://www.w3.org/TR/2017/WD-css-images-4-20170413/#funcdef-conic-gradient */ public void setRepeatingConicGradient(CssExpression exp, ApplContext ac) throws InvalidParamException { name = "repeating-conic-gradient"; _cache = null; _setConicGradient(exp, ac); } private void _setConicGradient(CssExpression exp, ApplContext ac) throws InvalidParamException { // ImageList defined in CSS3 and onward if (ac.getCssVersion().compareTo(CssVersion.CSS3) < 0) { StringBuilder sb = new StringBuilder(); sb.append(name).append('(').append(exp.toStringFromStart()).append(')'); throw new InvalidParamException("notversion", sb.toString(), ac.getCssVersionString(), ac); } ArrayList v = new ArrayList(); CssValue val; char op = exp.getOperator(); if (exp.hasCssVariable()) { markCssVariable(); } while (op != COMMA) { val = exp.getValue(); op = exp.getOperator(); switch (val.getType()) { case CssTypes.CSS_IDENT: CssIdent id = val.getIdent(); if (from.equals(id)) { if (exp.getRemainingCount() < 1) { throw new InvalidParamException("few-value", name, ac); } // from must be first if (!v.isEmpty()) { throw new InvalidParamException("value", val, name, ac); } if (op != SPACE) { throw new InvalidParamException("operator", Character.toString(op), ac); } exp.next(); CssValue nextVal = exp.getValue(); op = exp.getOperator(); switch (nextVal.getType()) { case CssTypes.CSS_NUMBER: nextVal.getCheckableValue().checkEqualsZero(ac, name); case CssTypes.CSS_ANGLE: CssValueList vl = new CssValueList(); vl.add(val); vl.add(nextVal); v.add(vl); exp.next(); break; default: throw new InvalidParamException("value", nextVal, name, ac); } break; } else if (at.equals(id)) { if (exp.getRemainingCount() < 1) { throw new InvalidParamException("few-value", name, ac); } CssExpression nex = new CssExpression(); while (!exp.end() && (exp.getOperator() != COMMA)) { exp.next(); nex.addValue(exp.getValue()); op = exp.getOperator(); } CssValueList vl = new CssValueList(); vl.add(val); vl.add(checkPosition(nex, ac)); v.add(vl); exp.next(); break; } default: // parse color stops now, fake a separator op = COMMA; } } // now we a list of at least two color stops. ArrayList stops = parseColorStops(exp, ac, false); if ((stops.size() < 2) && !hasCssVariable()) { throw new InvalidParamException("few-value", name, ac); } v.addAll(stops); value = new CssLayerList(v); } /** * @param exp * @param ac * @throws InvalidParamException * @spec http://www.w3.org/TR/2012/CR-css3-images-20120417/#radial-gradient-type */ public void setRadialGradient(CssExpression exp, ApplContext ac) throws InvalidParamException { name = "radial-gradient"; _cache = null; _setRadialGradient(exp, ac); } /** * @param exp * @param ac * @throws InvalidParamException * @spec http://www.w3.org/TR/2012/CR-css3-images-20120417/#repeating-radial-gradient-type */ public void setRepeatingRadialGradient(CssExpression exp, ApplContext ac) throws InvalidParamException { name = "repeating-radial-gradient"; _cache = null; _setRadialGradient(exp, ac); } /** * @param exp * @param ac * @throws InvalidParamException * @spec http://www.w3.org/TR/2012/CR-css3-images-20120417/#linear-gradient-type */ private void _setRadialGradient(CssExpression exp, ApplContext ac) throws InvalidParamException { // ImageList defined in CSS3 and onward if (ac.getCssVersion().compareTo(CssVersion.CSS3) < 0) { StringBuilder sb = new StringBuilder(); sb.append(name).append('(').append(exp.toStringFromStart()).append(')'); throw new InvalidParamException("notversion", sb.toString(), ac.getCssVersionString(), ac); } ArrayList v = new ArrayList(); CssValue val = exp.getValue(); char op = exp.getOperator(); if (exp.hasCssVariable()) { markCssVariable(); } // check if there is something before the color stops list boolean parse_prolog = false; switch (val.getType()) { case CssTypes.CSS_NUMBER: val.getLength(); case CssTypes.CSS_LENGTH: case CssTypes.CSS_PERCENTAGE: parse_prolog = true; break; case CssTypes.CSS_IDENT: CssIdent id = val.getIdent(); parse_prolog = at.equals(id) || (getShape(id) != null) || (getExtentIdent(id) != null); break; } if (parse_prolog) { CssExpression newexp = new CssExpression(); boolean done = false; while (!done && !exp.end()) { val = exp.getValue(); op = exp.getOperator(); newexp.addValue(val); done = (op == COMMA); exp.next(); } v.add(parseRadialProlog(newexp, ac)); } // now we a list of at least two color stops. ArrayList stops = parseColorStops(exp, ac, true); if ((stops.size() < 2) && !hasCssVariable()) { throw new InvalidParamException("few-value", name, ac); } v.addAll(stops); value = new CssLayerList(v); } private CssValue parseRadialProlog(CssExpression expression, ApplContext ac) throws InvalidParamException { // the fun begins :) CssIdent shape = null; CssValue extend = null; CssValue extend2 = null; CssValue atPosition = null; ArrayList v = new ArrayList(); CssValue val; boolean shapeInMiddle = false; while (!expression.end()) { val = expression.getValue(); switch (val.getType()) { case CssTypes.CSS_PERCENTAGE: if (shapeInMiddle) { throw new InvalidParamException("value", val, name, ac); } CssPercentage p = val.getPercentage(); if (!p.isPositive()) { throw new InvalidParamException("negative-value", val, name, ac); } if (extend == null) { extend = val; break; } if (extend2 == null) { extend2 = val; break; } throw new InvalidParamException("value", val, name, ac); case CssTypes.CSS_NUMBER: case CssTypes.CSS_LENGTH: if (shapeInMiddle) { throw new InvalidParamException("value", val, name, ac); } CssLength l = val.getLength(); if (!l.isPositive()) { throw new InvalidParamException("negative-value", val, name, ac); } if (extend == null) { extend = val; break; } else { if (extend.getType() == CssTypes.CSS_IDENT) { // don't mix ident and length/percentage throw new InvalidParamException("value", val, name, ac); } } if (extend2 == null) { extend2 = val; break; } throw new InvalidParamException("value", val, name, ac); case CssTypes.CSS_IDENT: CssIdent id = val.getIdent(); // final 'at' if (at.equals(id)) { CssExpression exp = new CssExpression(); expression.next(); while (!expression.end()) { exp.addValue(expression.getValue()); expression.next(); } atPosition = checkPosition(exp, ac); break; } if (shape == null) { shape = getShape(id); if (shape != null) { shapeInMiddle = (expression.getCount() != expression.getRemainingCount()); break; } } if (extend == null) { extend = getExtentIdent(id); if (extend != null) { if (shapeInMiddle) { throw new InvalidParamException("value", val, name, ac); } break; } } // unrecognized ident default: throw new InvalidParamException("value", val.toString(), name, ac); } expression.next(); } // extra checks... // circle can have at most one extend and it must not be a percentage if (shape == circle && (extend2 != null || (extend != null && extend.getType() == CssTypes.CSS_PERCENTAGE))) { throw new InvalidParamException("value", expression.toStringFromStart(), name, ac); } // ellipsis must have one extent ident or two (percentage/length) if (shape == ellipse && (extend2 == null && (extend != null && extend.getType() != CssTypes.CSS_IDENT))) { throw new InvalidParamException("value", expression.toStringFromStart(), name, ac); } // if shape is null, it's a circle if (shape == null && extend2 == null && extend != null && extend.getType() == CssTypes.CSS_PERCENTAGE) { throw new InvalidParamException("value", expression.toStringFromStart(), name, ac); } if (shape != null) { v.add(shape); } if (extend != null) { v.add(extend); if (extend2 != null) { v.add(extend2); } } if (atPosition != null) { v.add(at); v.add(atPosition); } return (v.size() == 1) ? v.get(0) : new CssValueList(v); } private final ArrayList parseColorStops(CssExpression expression, ApplContext ac, boolean matchLength) throws InvalidParamException { ArrayList v = new ArrayList(); CssValue val; char op; CssColor stopcol; CssValue stop1, stop2; ArrayList stop; boolean prev_is_hint = false; boolean got_length_angle_percentage; if (expression.hasCssVariable()) { markCssVariable(); // we won't check if type is unknown stop = new ArrayList<>(2); while (!expression.end()) { stop1 = expression.getValue(); op = expression.getOperator(); if (op == SPACE && expression.getRemainingCount() > 1) { expression.next(); stop = new ArrayList<>(2); stop.add(stop1); stop.add(expression.getValue()); op = expression.getOperator(); v.add(new CssValueList(stop)); } else { v.add(stop1); } expression.next(); if (!expression.end() && op != COMMA) { // do nothing as var() can expand to multiple values+separators // expression.starts(); // throw new InvalidParamException("operator", // Character.toString(op), ac); } } return v; } while (!expression.end()) { val = expression.getValue(); op = expression.getOperator(); got_length_angle_percentage = false; switch (val.getType()) { case CssTypes.CSS_NUMBER: val.getCheckableValue().checkEqualsZero(ac, name); case CssTypes.CSS_LENGTH: case CssTypes.CSS_ANGLE: //some tricks to avoid writing the same code twise if (matchLength) { if (val.getType() == CssTypes.CSS_ANGLE) { throw new InvalidParamException("value", val.toString(), "color-stop", ac); } } else { if (val.getType() == CssTypes.CSS_LENGTH) { throw new InvalidParamException("value", val.toString(), "color-stop", ac); } } case CssTypes.CSS_PERCENTAGE: stop1 = val; got_length_angle_percentage = true; // check if we have another one CssValue nextVal = expression.getNextValue(); if (nextVal != null) { switch (nextVal.getType()) { case CssTypes.CSS_NUMBER: nextVal.getCheckableValue().checkEqualsZero(ac, name); case CssTypes.CSS_ANGLE: case CssTypes.CSS_LENGTH: if (matchLength) { if (nextVal.getType() == CssTypes.CSS_ANGLE) { throw new InvalidParamException("value", nextVal.toString(), "color-stop", ac); } } else { if (nextVal.getType() == CssTypes.CSS_LENGTH) { throw new InvalidParamException("value", nextVal.toString(), "color-stop", ac); } } case CssTypes.CSS_PERCENTAGE: if (op != SPACE) { throw new InvalidParamException("operator", Character.toString(op), ac); } // we have our second value... CssValueList vl = new CssValueList(); vl.add(stop1); vl.add(nextVal); stop1 = vl; expression.next(); op = expression.getOperator(); break; default: // do nothing } } break; case CssTypes.CSS_HASH_IDENT: stopcol = new CssColor(); stopcol.setShortRGBColor(ac, val.getHashIdent().toString()); stop1 = (val.getRawType() == CssTypes.CSS_HASH_IDENT) ? stopcol : val; break; case CssTypes.CSS_IDENT: if (CssColorCSS3.currentColor.equals(val.getIdent())) { stop1 = (val.getRawType() == CssTypes.CSS_IDENT) ? CssColorCSS3.currentColor : val; break; } stopcol = new CssColor(); stopcol.setIdentColor(ac, val.getIdent().toString()); stop1 = (val.getRawType() == CssTypes.CSS_IDENT) ? stopcol : val; break; case CssTypes.CSS_COLOR: stop1 = val; break; default: throw new InvalidParamException("value", val.toString(), "color", ac); } if (op == SPACE && expression.getRemainingCount() > 1) { expression.next(); val = expression.getValue(); op = expression.getOperator(); switch (val.getType()) { case CssTypes.CSS_NUMBER: val.getCheckableValue().checkEqualsZero(ac, name); case CssTypes.CSS_ANGLE: case CssTypes.CSS_LENGTH: if (matchLength) { if (val.getType() == CssTypes.CSS_ANGLE) { throw new InvalidParamException("value", val.toString(), "color-stop", ac); } } else { if (val.getType() == CssTypes.CSS_LENGTH) { throw new InvalidParamException("value", val.toString(), "color-stop", ac); } } case CssTypes.CSS_PERCENTAGE: if (got_length_angle_percentage) { throw new InvalidParamException("value", val.toString(), "color-stop", ac); } stop = new ArrayList(2); stop.add(stop1); CssValue nextVal = expression.getNextValue(); if (nextVal != null && op == SPACE) { switch (nextVal.getType()) { case CssTypes.CSS_NUMBER: nextVal.getCheckableValue().checkEqualsZero(ac, name); case CssTypes.CSS_ANGLE: case CssTypes.CSS_LENGTH: if (matchLength) { if (nextVal.getType() == CssTypes.CSS_ANGLE) { throw new InvalidParamException("value", nextVal.toString(), "color-stop", ac); } } else { if (nextVal.getType() == CssTypes.CSS_LENGTH) { throw new InvalidParamException("value", nextVal.toString(), "color-stop", ac); } } case CssTypes.CSS_PERCENTAGE: // we have our second value... if (op != SPACE) { throw new InvalidParamException("operator", Character.toString(op), ac); } CssValueList vl = new CssValueList(); vl.add(val); vl.add(nextVal); stop.add(vl); expression.next(); op = expression.getOperator(); break; default: // do nothing } } else { stop.add(val); } v.add(new CssValueList(stop)); break; case CssTypes.CSS_HASH_IDENT: if (!got_length_angle_percentage) { throw new InvalidParamException("value", val.toString(), "color-stop", ac); } stopcol = new CssColor(); stopcol.setShortRGBColor(ac, val.getHashIdent().toString()); // TODO we rewrite putting color first, should we do that? stop = new ArrayList(2); stop.add((val.getRawType() == CssTypes.CSS_HASH_IDENT) ? stopcol : val); stop.add(stop1); v.add(new CssValueList(stop)); break; case CssTypes.CSS_IDENT: if (!got_length_angle_percentage) { throw new InvalidParamException("value", val.toString(), "color-stop", ac); } if (CssColorCSS3.currentColor.equals(val.getIdent())) { stop2 = (val.getRawType() == CssTypes.CSS_HASH_IDENT) ? CssColorCSS3.currentColor : val; } else { stopcol = new CssColor(); stopcol.setIdentColor(ac, val.getIdent().toString()); stop2 = (val.getRawType() == CssTypes.CSS_IDENT) ? stopcol : val; } // TODO we rewrite putting color first, should we do that? stop = new ArrayList(2); stop.add(stop2); stop.add(stop1); v.add(new CssValueList(stop)); break; case CssTypes.CSS_COLOR: if (!got_length_angle_percentage) { throw new InvalidParamException("value", val.toString(), "color-stop", ac); } stop = new ArrayList(2); // TODO we rewrite putting color first, should we do that? stop.add(val); stop.add(stop1); v.add(new CssValueList(stop)); break; default: throw new InvalidParamException("value", val.toString(), "color-stop", ac); } // we got two values, it is not a linear-color-hint prev_is_hint = false; } else { // we can't have two hints in a row if (prev_is_hint && got_length_angle_percentage) { throw new InvalidParamException("value", stop1, "color-stop", ac); } v.add(stop1); prev_is_hint = got_length_angle_percentage; } expression.next(); if (!expression.end() && op != COMMA) { expression.starts(); throw new InvalidParamException("operator", Character.toString(op), ac); } } return v; } private CssValue checkPosition(CssExpression expression, ApplContext ac) throws InvalidParamException { switch (ac.getCssVersion()) { case CSS3: return CssBackgroundPosition.checkSyntax(ac, expression, name); default: StringBuilder sb = new StringBuilder(); sb.append(name).append('(').append(expression.toStringFromStart()).append(')'); throw new InvalidParamException("notversion", sb.toString(), ac.getCssVersionString(), ac); } } /** * Returns the value */ public Object get() { // @@TODO return null; } /** * Returns the name of the function */ public String getName() { return name; } /** * Returns a string representation of the object. */ public String toString() { if (_cache == null) { StringBuilder sb = new StringBuilder(); sb.append(name).append('(').append(value).append(')'); _cache = sb.toString(); } return _cache; } /** * Compares two values for equality. * * @param other The other value. */ public boolean equals(Object other) { // @@FIXME return (other instanceof CssImage && this.name.equals(((CssImage) other).name) && this.value.equals(((CssImage) other).value)); } }