package net.jangaroo.jooc.mxml;
import net.jangaroo.utils.AS3Type;
import net.jangaroo.utils.CompilerUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Some useful utility functions for MXML handling.
*/
public class MxmlUtils {
public static final String MXML_NAMESPACE_URI = "http://ns.adobe.com/mxml/2009";
public static final String EXML_UNTYPED_NAMESPACE = "exml:untyped";
public static final String MXML_DECLARATIONS = "Declarations";
public static final String MXML_SCRIPT = "Script";
public static final String MXML_METADATA = "Metadata";
public static final String MXML_ID_ATTRIBUTE = "id";
public static final String MXML_DEFAULT_PROPERTY_ANNOTATION = "DefaultProperty";
public static final String EXML_MIXINS_PROPERTY_NAME = "__mixins__";
public static final String EVENT_DISPATCHER_INTERFACE = "ext.mixin.IObservable";
public static final String ADD_EVENT_LISTENER_METHOD_NAME = "addEventListener";
private static final Pattern IS_BINDING_EXPRESSION_PATTERN = Pattern.compile("(^|[^\\\\])\\{([^}]*[^\\\\])\\}");
private static final Pattern BINDING_EXPRESSION_START_OR_END_PATTERN = Pattern.compile("[{}]");
private static final Pattern MXML_COMMENT = Pattern.compile("<!--(-?)([^-]*(?:-[^-]+)*)-->", Pattern.DOTALL);
public static final String CONFIG = "config";
public static boolean isMxmlNamespace(String uri) {
return MXML_NAMESPACE_URI.equals(uri);
}
public static String createBindingExpression(String code) {
return String.format("{%s}", code);
}
public static boolean isBindingExpression(String attributeValue) {
return IS_BINDING_EXPRESSION_PATTERN.matcher(attributeValue).find();
}
public static String getBindingExpression(String attributeValue) {
Matcher matcher = BINDING_EXPRESSION_START_OR_END_PATTERN.matcher(attributeValue);
StringBuilder bindingExpression = new StringBuilder();
// since we have to quote literals, we cannot use matcher.appendReplacement() / appendTail() :-(
int startPos = 0;
int curlyNesting = 0;
while (matcher.find()) {
int curlyPos = matcher.start();
if (curlyPos == 0 || attributeValue.charAt(curlyPos - 1) != '\\') { // skip escaped curly braces
String curly = matcher.group();
if ("{".equals(curly)) {
if (curlyNesting == 0) {
// add the previous term as a literal:
startPos = addTerm(bindingExpression, attributeValue, startPos, curlyPos, true);
}
++curlyNesting;
} else { assert "}".equals(curly);
if (curlyNesting > 0) { // interpret additional closing curly braces as literal
--curlyNesting;
if (curlyNesting == 0) {
// add the previous term as an expression:
startPos = addTerm(bindingExpression, attributeValue, startPos, curlyPos, false);
}
}
}
}
}
if (startPos < attributeValue.length()) {
// interprete unclosed curly bracket as literal:
if (curlyNesting > 0) {
--startPos;
}
// add the remains as a literal:
addTerm(bindingExpression, attributeValue, startPos, attributeValue.length(), true);
}
return bindingExpression.toString();
}
private static int addTerm(StringBuilder bindingExpression, String attributeValue, int startPos, int endPos, boolean quote) {
if (startPos < endPos) {
if (bindingExpression.length() > 0) {
bindingExpression.append(" + ");
}
String term = attributeValue.substring(startPos, endPos);
bindingExpression.append(quote ? CompilerUtils.quote(term) : term);
}
return endPos + 1;
}
public static Object getAttributeValue(String attributeValue, String type) {
if (!MxmlUtils.isBindingExpression(attributeValue)) {
AS3Type as3Type = type == null ? AS3Type.ANY : AS3Type.typeByName(type);
if (AS3Type.ANY.equals(as3Type)) {
as3Type = CompilerUtils.guessType(attributeValue);
}
if (as3Type != null) {
attributeValue = attributeValue.trim();
switch (as3Type) {
case BOOLEAN:
return Boolean.parseBoolean(attributeValue);
case NUMBER:
return Double.parseDouble(attributeValue);
case UINT:
case INT:
return Long.parseLong(attributeValue);
}
}
}
// code expression, Object or specific type. We don't care (for now).
return attributeValue;
}
/**
* Return a stringified representation of an object value.
* This method can handle <code>Number</code>, <code>Boolean</code>,
* <code>String</code> and strings containing a binding expression (curly braces).
*
* @param value The value to be serialized.
* @return a stringified representation of the object value
*/
@Nonnull
public static String valueToString(@Nullable Object value) {
if (value == null) {
return "null";
}
if (value instanceof Number
|| value instanceof Boolean) {
return value.toString();
} else if (MxmlUtils.isBindingExpression(value.toString())) {
return MxmlUtils.getBindingExpression(value.toString());
}
return CompilerUtils.quote(value.toString().replaceAll("\\\\\\{", "{"));
}
public static String capitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
return name.substring(0,1).toUpperCase() + name.substring(1);
}
public static String toASDoc(String xmlWhitespace) {
// convert MXML comments to ASdoc comments
Matcher matcher = MXML_COMMENT.matcher(xmlWhitespace);
StringBuffer sb = new StringBuffer();
while(matcher.find()) {
String prefix = "-".equals(matcher.group(1)) ? "/**" : "/*";
String content = Matcher.quoteReplacement(matcher.group(2));
matcher.appendReplacement(sb, prefix + content + "*/");
}
return matcher.appendTail(sb).toString();
}
}