| /* |
| * Copyright 2009 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.resources.css; |
| |
| import com.google.gwt.resources.css.ast.Context; |
| import com.google.gwt.resources.css.ast.CssCompilerException; |
| import com.google.gwt.resources.css.ast.CssModVisitor; |
| import com.google.gwt.resources.css.ast.CssNoFlip; |
| import com.google.gwt.resources.css.ast.CssProperty; |
| import com.google.gwt.resources.css.ast.CssProperty.IdentValue; |
| import com.google.gwt.resources.css.ast.CssProperty.NumberValue; |
| import com.google.gwt.resources.css.ast.CssProperty.Value; |
| import com.google.gwt.resources.css.ast.CssRule; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Locale; |
| |
| /** |
| * Applies RTL transforms to a stylesheet. |
| */ |
| public class RtlVisitor extends CssModVisitor { |
| /** |
| * Records if we're currently visiting a CssRule whose only selector is |
| * "body". |
| */ |
| private boolean inBodyRule; |
| |
| @Override |
| public void endVisit(CssProperty x, Context ctx) { |
| String name = x.getName(); |
| |
| if (name.equalsIgnoreCase("left")) { |
| x.setName("right"); |
| } else if (name.equalsIgnoreCase("right")) { |
| x.setName("left"); |
| } else if (name.endsWith("-left")) { |
| int len = name.length(); |
| x.setName(name.substring(0, len - 4) + "right"); |
| } else if (name.endsWith("-right")) { |
| int len = name.length(); |
| x.setName(name.substring(0, len - 5) + "left"); |
| } else if (name.contains("-right-")) { |
| x.setName(name.replace("-right-", "-left-")); |
| } else if (name.contains("-left-")) { |
| x.setName(name.replace("-left-", "-right-")); |
| } else { |
| List<Value> values = new ArrayList<Value>(x.getValues().getValues()); |
| invokePropertyHandler(x.getName(), values); |
| x.setValue(new CssProperty.ListValue(values)); |
| } |
| } |
| |
| @Override |
| public boolean visit(CssNoFlip x, Context ctx) { |
| return false; |
| } |
| |
| @Override |
| public boolean visit(CssRule x, Context ctx) { |
| inBodyRule = x.getSelectors().size() == 1 |
| && x.getSelectors().get(0).getSelector().equals("body"); |
| return true; |
| } |
| |
| void propertyHandlerBackground(List<Value> values) { |
| /* |
| * The first numeric value will be treated as the left position only if we |
| * havn't seen any value that could potentially be the left value. |
| */ |
| boolean seenLeft = false; |
| |
| for (ListIterator<Value> it = values.listIterator(); it.hasNext();) { |
| Value v = it.next(); |
| Value maybeFlipped = flipLeftRightIdentValue(v); |
| NumberValue nv = v.isNumberValue(); |
| if (v != maybeFlipped) { |
| it.set(maybeFlipped); |
| seenLeft = true; |
| |
| } else if (isIdent(v, "center")) { |
| seenLeft = true; |
| |
| } else if (!seenLeft && (nv != null)) { |
| seenLeft = true; |
| if ("%".equals(nv.getUnits())) { |
| float position = 100f - nv.getValue(); |
| it.set(new NumberValue(position, "%")); |
| break; |
| } |
| } |
| } |
| } |
| |
| void propertyHandlerBackgroundPosition(List<Value> values) { |
| propertyHandlerBackground(values); |
| } |
| |
| Value propertyHandlerBackgroundPositionX(Value v) { |
| ArrayList<Value> list = new ArrayList<Value>(1); |
| list.add(v); |
| propertyHandlerBackground(list); |
| return list.get(0); |
| } |
| |
| /** |
| * Note there should be no propertyHandlerBorder(). The CSS spec states that |
| * the border property must set all values at once. |
| */ |
| void propertyHandlerBorderColor(List<Value> values) { |
| swapFour(values); |
| } |
| |
| void propertyHandlerBorderStyle(List<Value> values) { |
| swapFour(values); |
| } |
| |
| void propertyHandlerBorderWidth(List<Value> values) { |
| swapFour(values); |
| } |
| |
| Value propertyHandlerClear(Value v) { |
| return propertyHandlerFloat(v); |
| } |
| |
| Value propertyHandlerCursor(Value v) { |
| IdentValue identValue = v.isIdentValue(); |
| if (identValue == null) { |
| return v; |
| } |
| |
| String ident = identValue.getIdent().toLowerCase(Locale.ROOT); |
| if (!ident.endsWith("-resize")) { |
| return v; |
| } |
| |
| StringBuffer newIdent = new StringBuffer(); |
| |
| if (ident.length() == 9) { |
| if (ident.charAt(0) == 'n') { |
| newIdent.append('n'); |
| ident = ident.substring(1); |
| } else if (ident.charAt(0) == 's') { |
| newIdent.append('s'); |
| ident = ident.substring(1); |
| } else { |
| return v; |
| } |
| } |
| |
| if (ident.length() == 8) { |
| if (ident.charAt(0) == 'e') { |
| newIdent.append("w-resize"); |
| } else if (ident.charAt(0) == 'w') { |
| newIdent.append("e-resize"); |
| } else { |
| return v; |
| } |
| return new IdentValue(newIdent.toString()); |
| } else { |
| return v; |
| } |
| } |
| |
| Value propertyHandlerDirection(Value v) { |
| if (inBodyRule) { |
| if (isIdent(v, "ltr")) { |
| return new IdentValue("rtl"); |
| } else if (isIdent(v, "rtl")) { |
| return new IdentValue("ltr"); |
| } |
| } |
| return v; |
| } |
| |
| Value propertyHandlerFloat(Value v) { |
| return flipLeftRightIdentValue(v); |
| } |
| |
| void propertyHandlerMargin(List<Value> values) { |
| swapFour(values); |
| } |
| |
| void propertyHandlerPadding(List<Value> values) { |
| swapFour(values); |
| } |
| |
| Value propertyHandlerPageBreakAfter(Value v) { |
| return flipLeftRightIdentValue(v); |
| } |
| |
| Value propertyHandlerPageBreakBefore(Value v) { |
| return flipLeftRightIdentValue(v); |
| } |
| |
| Value propertyHandlerTextAlign(Value v) { |
| return flipLeftRightIdentValue(v); |
| } |
| |
| private Value flipLeftRightIdentValue(Value v) { |
| if (isIdent(v, "right")) { |
| return new IdentValue("left"); |
| |
| } else if (isIdent(v, "left")) { |
| return new IdentValue("right"); |
| } |
| return v; |
| } |
| |
| /** |
| * Reflectively invokes a propertyHandler method for the named property. |
| * Dashed names are transformed into camel-case names; only letters following |
| * a dash will be capitalized when looking for a method to prevent |
| * <code>fooBar<code> and <code>foo-bar</code> from colliding. |
| */ |
| private void invokePropertyHandler(String name, List<Value> values) { |
| // See if we have a property-handler function |
| try { |
| String[] parts = name.toLowerCase(Locale.ROOT).split("-"); |
| StringBuffer methodName = new StringBuffer("propertyHandler"); |
| for (String part : parts) { |
| if (part.length() > 0) { |
| // A leading hyphen, or something like foo--bar, which is weird |
| methodName.append(Character.toUpperCase(part.charAt(0))); |
| methodName.append(part, 1, part.length()); |
| } |
| } |
| |
| try { |
| // Single-arg for simplicity |
| Method m = getClass().getDeclaredMethod(methodName.toString(), |
| Value.class); |
| assert Value.class.isAssignableFrom(m.getReturnType()); |
| Value newValue = (Value) m.invoke(this, values.get(0)); |
| values.set(0, newValue); |
| } catch (NoSuchMethodException e) { |
| // OK |
| } |
| |
| try { |
| // Or the whole List for completeness |
| Method m = getClass().getDeclaredMethod(methodName.toString(), |
| List.class); |
| m.invoke(this, values); |
| } catch (NoSuchMethodException e) { |
| // OK |
| } |
| |
| } catch (SecurityException e) { |
| throw new CssCompilerException( |
| "Unable to invoke property handler function for " + name, e); |
| } catch (IllegalArgumentException e) { |
| throw new CssCompilerException( |
| "Unable to invoke property handler function for " + name, e); |
| } catch (IllegalAccessException e) { |
| throw new CssCompilerException( |
| "Unable to invoke property handler function for " + name, e); |
| } catch (InvocationTargetException e) { |
| throw new CssCompilerException( |
| "Unable to invoke property handler function for " + name, e); |
| } |
| } |
| |
| private boolean isIdent(Value value, String query) { |
| IdentValue v = value.isIdentValue(); |
| return v != null && v.getIdent().equalsIgnoreCase(query); |
| } |
| |
| /** |
| * Swaps the second and fourth values in a list of four values. |
| */ |
| private void swapFour(List<Value> values) { |
| if (values.size() == 4) { |
| Collections.swap(values, 1, 3); |
| } |
| } |
| } |