blob: 2b9d06737141139a34677ae12fa6efb9050f9e80 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.royale.compiler.internal.driver.js.royale;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.css.ICSSDocument;
import org.apache.royale.compiler.css.ICSSMediaQueryCondition;
import org.apache.royale.compiler.css.ICSSProperty;
import org.apache.royale.compiler.css.ICSSPropertyValue;
import org.apache.royale.compiler.css.ICSSRule;
import org.apache.royale.compiler.css.ICSSSelector;
import org.apache.royale.compiler.css.ICSSSelectorCondition;
import org.apache.royale.compiler.internal.codegen.js.goog.JSGoogEmitterTokens;
import org.apache.royale.compiler.internal.css.*;
import org.apache.royale.compiler.internal.css.codegen.CSSCompilationSession;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
public class JSCSSCompilationSession extends CSSCompilationSession
{
private List<String> otherCSSFunctions = Arrays.asList(
"-moz-linear-gradient",
"-webkit-linear-gradient",
"linear-gradient",
"progid:DXImageTransform.Microsoft.gradient",
"translateX",
"translateY",
"translate",
"blur",
"brightness",
"contrast",
"drop-shadow",
"hue-rotate",
"invert",
"saturate",
"sepia"
);
// this two conflicts with other css functions with the same name - comment for now
// "grayscale",
// "opacity",
private ArrayList<String> requires;
public String getEncodedCSS()
{
final ICSSDocument css = synthesisNormalizedCSS(false);
StringBuilder sb = new StringBuilder();
requires = new ArrayList<String>();
encodeCSS(css, sb);
if (sb.length() == 0)
return null;
sb.append("];\n");
for (String r : requires)
{
sb.append(JSGoogEmitterTokens.GOOG_REQUIRE.getToken() + "('" + formatQualifiedName(r) + "');\n");
}
return sb.toString();
}
public String emitCSS()
{
final ICSSDocument css = synthesisNormalizedCSS(false);
StringBuilder sb = new StringBuilder();
sb.append("/* Generated by Apache Royale Compiler */\n");
walkCSS(css, sb);
return sb.toString();
}
/**
* used to minify the CSS for release mode
*/
public static String minifyCSSString(String cssString)
{
//Remove empty selectors
cssString = cssString.replaceAll("\\S+\\s?\\{[\\s\\n]+}", "");
// Remove comments
cssString = cssString.replaceAll("/\\*[\\d\\D]*?\\*/", "");
cssString = cssString.replace(";}", "}");
cssString = cssString.replaceAll("[\\n\\r]+\\s*", "");
cssString = cssString.replaceAll("\\s+", " ");
cssString = cssString.replaceAll("\\s?([:,;{}])\\s?", "$1");
cssString = cssString.replaceAll("([\\s:]0)(px|pt|em)", "$1");
return cssString;
}
private String fontFaceToString(CSSFontFace fontFace)
{
final StringBuilder result = new StringBuilder();
result.append("@font-face {\n");
result.append(" ");
result.append("font-family: ");
result.append(fontFace.getFontFamily() + ";\n");
result.append(" ");
result.append("font-style: ");
result.append(fontFace.getFontStyle() + ";\n");
result.append(" ");
result.append("font-weight: ");
result.append(fontFace.getFontStyle() + ";\n");
result.append(" ");
ArrayList<ICSSPropertyValue> sources = fontFace.getSources();
for (ICSSPropertyValue src : sources)
{
result.append("src: ");
result.append(src.toString() + ";\n");
}
result.append("}\n");
return result.toString();
}
private String cssRuleToString(ICSSRule rule)
{
final StringBuilder result = new StringBuilder();
ImmutableList<ICSSMediaQueryCondition> mqList = rule.getMediaQueryConditions();
boolean hasMediaQuery = !mqList.isEmpty();
if (hasMediaQuery)
{
result.append("@media ");
result.append(Joiner.on(" and ").join(rule.getMediaQueryConditions()));
result.append(" {\n");
result.append(" ");
}
ImmutableList<ICSSSelector> selectors = rule.getSelectorGroup();
boolean firstOne = true;
for (ICSSSelector selector : selectors)
{
String s = selector.toString();
// add "." to type selectors that don't map cleanly
// to CSS type selectors to convert them to class
// selectors.
if (!s.startsWith(".") && !s.startsWith("*") && !s.startsWith("#") && !s.startsWith("::"))
{
String condition = null;
int colon = s.indexOf(":");
if (colon != -1)
{
condition = s.substring(colon);
s = s.substring(0, colon);
}
else
{
int brace = s.indexOf("[");
if (brace != -1)
{
condition = s.substring(brace);
s = s.substring(0, brace);
}
else
{
int child = s.indexOf(">");
if (child != -1)
{
condition = s.substring(child);
s = s.substring(0, child);
}
else
{
int preceded = s.indexOf("+");
if (preceded != -1)
{
condition = s.substring(preceded);
s = s.substring(0, preceded);
}
}
}
}
if (!htmlElementNames.contains(s.toLowerCase()))
{
if (s.indexOf(" ") > 0)
{
String parts[] = s.split(" ");
int n = parts.length;
s = "";
for (int i = 0; i < n; i++)
{
if (i != 0)
s += " ";
String part = parts[i];
if (!part.startsWith(".") && !part.startsWith("*") && !part.startsWith("#") && !part.startsWith("::"))
{
int pipe = part.indexOf("|");
if (pipe != -1)
part = part.substring(pipe + 1);
part = "." + part;
}
s += part;
}
}
else
{
int pipe = s.indexOf("|");
if (pipe != -1)
s = s.substring(pipe + 1);
s = "." + s;
}
}
if (condition != null)
s = s + condition;
}
if (!firstOne)
result.append(",\n");
result.append(s);
}
result.append(" {\n");
ArrayList<String> listOfProps = new ArrayList<String>();
for (final ICSSProperty prop : rule.getProperties())
{
String propString = ((CSSProperty)prop).toCSSString();
// skip class references since the won't work in CSS
if (propString.contains("ClassReference"))
continue;
listOfProps.add(propString);
}
Collections.sort(listOfProps);
for (final String propString : listOfProps)
{
if (!hasMediaQuery)
result.append(" ");
result.append(" ").append(propString).append("\n");
}
if (hasMediaQuery)
result.append(" }\n");
result.append("}\n");
return result.toString();
}
private void walkCSS(ICSSDocument css, StringBuilder sb)
{
for (CSSFontFace fontFace : fontFaces)
{
sb.append(fontFaceToString(fontFace));
}
if (fontFaces.size() > 0)
sb.append("\n\n");
ImmutableList<ICSSRule> rules = css.getRules();
for (ICSSRule rule : rules)
{
String s = cssRuleToString(rule);
if (s.startsWith("@media -royale-swf"))
continue;
sb.append(s);
sb.append("\n\n");
}
}
private void encodeCSS(ICSSDocument css, StringBuilder sb)
{
ImmutableList<ICSSRule> rules = css.getRules();
boolean skipcomma = true;
for (ICSSRule rule : rules)
{
String s = encodeRule(rule);
if (s != null)
{
if (skipcomma)
skipcomma = false;
else
sb.append(",\n");
sb.append(s);
}
}
}
List<String> htmlElementNames = Arrays.asList(
"a",
"aside",
"b",
"br",
"body",
"button",
"caption",
"code",
"col",
"colgroup",
"dialog",
"div",
"em",
"embed",
"font",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"header",
"hr",
"html",
"i",
"img",
"input",
"label",
"li",
"main",
/*"menu", not cross-browser, and we want to use it in Royale */
"nav",
"ol",
"option",
"p",
"pre",
"s",
"select",
"small",
"span",
"strong",
"table",
"tbody",
"td",
"textarea",
"tfoot",
"th",
"thead",
"tr",
"u",
"ul"
);
private String escapeDoubleQuotes(String s)
{
s = replaceUnicodeEncoded(s);
if (s.contains("\""))
s = s.replace("\"", "\\\"");
return s;
}
private String replaceUnicodeEncoded(String s)
{
//almost all escape sequences are converted to actual character sequences, except for some that don't have
//a good result for Character.toChars (e.g. zero width space or non-breaking-space)
//convert hex char values to unicode
if (s.matches("\\\\[0-9a-fA-F]{1,4}")) {
s = s.replaceAll("\\\\([0-9a-fA-F]{1,4})\\s?","\\\\u$1");
}
return s;
}
private String encodeRule(ICSSRule rule)
{
final StringBuilder result = new StringBuilder();
ImmutableList<ICSSMediaQueryCondition> mqlist = rule.getMediaQueryConditions();
int n = mqlist.size();
if (n > 0)
{
if (mqlist.get(0).toString().equals("-royale-swf"))
return null;
result.append(n);
for (ICSSMediaQueryCondition mqcond : mqlist)
{
result.append(",\n");
result.append("\"" + mqcond.toString() + "\"");
}
}
else
result.append(n);
result.append(",\n");
ImmutableList<ICSSSelector> slist = rule.getSelectorGroup();
result.append(slist.size());
for (ICSSSelector sel : slist)
{
result.append(",\n");
String selName = this.resolvedSelectors.get(sel);
if (selName == null || selName.equals("null"))
result.append("\"" + sel.toString() + "\"");
else
{
selName = formatQualifiedName(selName);
ImmutableList<ICSSSelectorCondition> conds = sel.getConditions();
for (ICSSSelectorCondition cond : conds)
{
String condString = escapeDoubleQuotes(cond.toString());
selName += condString;
}
result.append("\"" + selName + "\"");
}
}
result.append(",\n");
result.append("function() {");
ImmutableList<ICSSProperty> plist = rule.getProperties();
ArrayList<String> listOfProps = new ArrayList<String>();
for (final ICSSProperty prop : rule.getProperties())
{
StringBuilder line = new StringBuilder();
line.append("this[\"" + prop.getName() + "\"] = ");
ICSSPropertyValue value = prop.getValue();
if (value instanceof CSSArrayPropertyValue)
{
ImmutableList<? extends ICSSPropertyValue> values = ((CSSArrayPropertyValue)value).getElements();
line.append("[");
boolean firstone = true;
for (ICSSPropertyValue val : values)
{
if (firstone)
firstone = false;
else
line.append(", ");
if (val instanceof CSSStringPropertyValue)
{
line.append("\"" + escapeDoubleQuotes(((CSSStringPropertyValue)val).getValue()) + "\"");
}
else if (val instanceof CSSColorPropertyValue)
{
line.append(new Integer(((CSSColorPropertyValue)val).getColorAsInt()));
}
else if (val instanceof CSSRgbColorPropertyValue)
{
line.append(new Integer(((CSSRgbColorPropertyValue)val).getColorAsInt()));
}
else if (value instanceof CSSRgbaColorPropertyValue)
{
//todo: handle alpha in the RGBA ?
line.append(new Long(((CSSRgbaColorPropertyValue)value).getColorAsLong()));
}
else if (val instanceof CSSKeywordPropertyValue)
{
CSSKeywordPropertyValue keywordValue = (CSSKeywordPropertyValue)val;
String keywordString = keywordValue.getKeyword();
if (IASLanguageConstants.TRUE.equals(keywordString))
line.append("true");
else if (IASLanguageConstants.FALSE.equals(keywordString))
line.append("false");
else
line.append("\"" + ((CSSKeywordPropertyValue)val).getKeyword() + "\"");
}
else if (val instanceof CSSNumberPropertyValue)
{
line.append(new Double(((CSSNumberPropertyValue)val).getNumber().doubleValue()));
}
else if (val instanceof CSSURLAndFormatPropertyValue)
{
line.append("\"" + escapeDoubleQuotes(((CSSURLAndFormatPropertyValue)val).toString()) + "\"");
}
else if (val instanceof CSSMultiValuePropertyValue)
{
line.append("\"" + ((CSSMultiValuePropertyValue)val).toString() + "\"");
}
else
{
line.append("unexpected value type: " + val.toString());
}
}
line.append("]");
}
else if (value instanceof CSSMultiValuePropertyValue)
{
ImmutableList<? extends ICSSPropertyValue> values = ((CSSMultiValuePropertyValue)value).getElements();
line.append("[");
boolean firstone = true;
for (ICSSPropertyValue val : values)
{
if (firstone)
firstone = false;
else
line.append(", ");
if (val instanceof CSSStringPropertyValue)
{
line.append("\"" + escapeDoubleQuotes(((CSSStringPropertyValue)val).getValue()) + "\"");
}
else if (val instanceof CSSColorPropertyValue)
{
line.append(new Integer(((CSSColorPropertyValue)val).getColorAsInt()));
}
else if (val instanceof CSSRgbColorPropertyValue)
{
line.append(new Integer(((CSSRgbColorPropertyValue)val).getColorAsInt()));
}
else if (val instanceof CSSRgbaColorPropertyValue)
{
//todo: handle alpha in the RGBA ?
line.append(new Long(((CSSRgbaColorPropertyValue)val).getColorAsLong()));
}
else if (val instanceof CSSKeywordPropertyValue)
{
CSSKeywordPropertyValue keywordValue = (CSSKeywordPropertyValue)val;
String keywordString = keywordValue.getKeyword();
if (IASLanguageConstants.TRUE.equals(keywordString))
line.append("true");
else if (IASLanguageConstants.FALSE.equals(keywordString))
line.append("false");
else
line.append("\"" + ((CSSKeywordPropertyValue)val).getKeyword() + "\"");
}
else if (val instanceof CSSNumberPropertyValue)
{
line.append(new Double(((CSSNumberPropertyValue)val).getNumber().doubleValue()));
}
else if (val instanceof CSSURLAndFormatPropertyValue)
{
line.append("\"" + escapeDoubleQuotes(((CSSURLAndFormatPropertyValue)val).toString()) + "\"");
}
else if (val instanceof CSSMultiValuePropertyValue)
{
line.append("\"" + ((CSSMultiValuePropertyValue)val).toString() + "\"");
}
else
{
line.append("unexpected value type: " + val.toString());
}
}
line.append("]");
}
else if (value instanceof CSSStringPropertyValue)
{
line.append("\"" + replaceUnicodeEncoded(((CSSStringPropertyValue)value).getValue()) + "\"");
}
else if (value instanceof CSSColorPropertyValue)
{
line.append(new Integer(((CSSColorPropertyValue)value).getColorAsInt()));
}
else if (value instanceof CSSRgbColorPropertyValue)
{
line.append(new Integer(((CSSRgbColorPropertyValue)value).getColorAsInt()));
}
else if (value instanceof CSSRgbaColorPropertyValue)
{
//todo: handle alpha in the RGBA ?
line.append(new Long(((CSSRgbaColorPropertyValue)value).getColorAsLong()));
}
else if (value instanceof CSSKeywordPropertyValue)
{
CSSKeywordPropertyValue keywordValue = (CSSKeywordPropertyValue)value;
String keywordString = keywordValue.getKeyword();
if (IASLanguageConstants.TRUE.equals(keywordString))
line.append("true");
else if (IASLanguageConstants.FALSE.equals(keywordString))
line.append("false");
else
line.append("\"" + ((CSSKeywordPropertyValue)value).getKeyword() + "\"");
}
else if (value instanceof CSSNumberPropertyValue)
{
line.append(new Double(((CSSNumberPropertyValue)value).getNumber().doubleValue()));
}
else if (value instanceof CSSFunctionCallPropertyValue)
{
final CSSFunctionCallPropertyValue functionCall = (CSSFunctionCallPropertyValue)value;
if ("ClassReference".equals(functionCall.name))
{
final String className = CSSFunctionCallPropertyValue.getSingleArgumentFromRaw(functionCall.rawArguments);
if ("null".equals(className))
{
// ClassReference(null) resets the property's class reference.
line.append("null");
}
else
{
line.append(formatQualifiedName(className));
requires.add(className);
}
}
else if ("url".equals(functionCall.name))
{
final String urlString = CSSFunctionCallPropertyValue.getSingleArgumentFromRaw(functionCall.rawArguments);
line.append("\"" + urlString + "\"");
}
else if ("PropertyReference".equals(functionCall.name))
{
// TODO: implement me
}
else if ("calc".equals(functionCall.name))
{
// TODO: implement me
line.append("null");
}
else if ("Embed".equals(functionCall.name))
{
// TODO: implement me
/*
final ICompilerProblem e = new CSSCodeGenProblem(
new IllegalStateException("Unable to find compilation unit for " + functionCall));
problems.add(e);
*/
}
else if (otherCSSFunctions.contains(functionCall.name))
{
// ignore for now?
line.append("null");
}
else
{
assert false : "CSS parser bug: unexpected function call property value: " + functionCall;
throw new IllegalStateException("Unexpected function call property value: " + functionCall);
}
}
listOfProps.add(line.toString());
}
Collections.sort(listOfProps);
boolean firstProp = true;
for (final String line2 : listOfProps)
{
if (!firstProp)
result.append(";\n");
firstProp = false;
result.append(line2);
}
result.append("}");
return result.toString();
}
@Override
protected boolean keepRule(ICSSRule newRule)
{
if (super.keepRule(newRule))
return true;
// might need to loop over all selectors in selector group
if (newRule.getSelectorGroup().size() > 0)
{
ICSSSelector selector = newRule.getSelectorGroup().get(0);
String elementName = selector.getElementName();
if (elementName != null)
{
if (htmlElementNames.contains(elementName))
return true;
}
else
{
return true;
}
}
return false;
}
private String formatQualifiedName(String name)
{
/*
if (name.contains("goog.") || name.startsWith("Vector."))
return name;
if (name.startsWith("."))
{
return "." + name.substring(1).replaceAll("\\.", "_");
}
name = name.replaceAll("\\.", "_");
*/
return name;
}
}