blob: ca1383978b4b3ac263d3b9fda7ed6a5438169965 [file] [log] [blame]
/*
* 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.core.shared.impl.StringCase;
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;
/**
* 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 = StringCase.toLower(identValue.getIdent());
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 = StringCase.toLower(name).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);
}
}
}