Package flex2.compiler.mxml.lang

Source Code of flex2.compiler.mxml.lang.TextParser$ParseError

/*
*
*  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 flex2.compiler.mxml.lang;

import flex2.compiler.mxml.reflect.Type;
import flex2.compiler.mxml.reflect.TypeTable;
import flex2.compiler.mxml.rep.BindingExpression;
import flash.css.Descriptor;
import flash.util.StringUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.xerces.util.XMLChar;

/**
* MXML text parser, used to parse attribute values and text
* content. Some utility functionality is also exposed in static
* methods.
*/
public abstract class TextParser
{
    /**
     * valid percentage expressions are: [whitespace] positive-whole-or-decimal-number [whitespace] % [whitespace]
     */
    private static final Pattern percentagePattern = Pattern.compile("\\s*((\\d+)(.(\\d)+)?)\\s*%\\s*");

    /**
     * valid qualified names are series of 1 or more leading-alpha-or-_-followed-by-alphanumerics words, separated by dots
     */
    private static final Pattern qualifiedNamePattern = Pattern.compile("([a-zA-Z_]\\w*)(\\.([a-zA-Z_]\\w*))*");

    /**
     * valid AS RegExps are: / 0-or-more-of-anything / 0-or-more-flag chars. We leave pattern validation to ASC.
     */
    private static final Pattern regExpPattern = Pattern.compile("/.*/[gimsx]*");

    //  error codes
    public final static int Ok = 0;
    public final static int ErrTypeNotEmbeddable = 1;       //  @Embed in a bad spot
    public final static int ErrInvalidTextForType = 2;      //  can't make text work as a serialized instance of type
    public final static int ErrInvalidPercentage = 3;       //  malformed percentage expression
    public final static int ErrTypeNotSerializable = 4;     //  type doesn't have a text representation at all
    public final static int ErrPercentagesNotAllowed = 5;   //  percentage not allowed here
    public final static int ErrTypeNotContextRootable = 6//  @ContextRoot in a bad spot
    public final static int ErrUnrecognizedAtFunction = 7//  @huh?()
    public final static int ErrUndefinedContextRoot = 8;    //  context-root not defined
    public final static int ErrInvalidTwoWayBind  = 9;      //  malformed two-way binding expression
   
    //  processing flags
    public final static int FlagInCDATA = 1;
    public final static int FlagCollapseWhiteSpace = 2;
    public final static int FlagConvertColorNames = 4;
    public final static int FlagAllowPercentages = 8;
    public final static int FlagIgnoreBinding = 16;
    public final static int FlagIgnoreAtFunction = 32;
    public final static int FlagIgnoreArraySyntax = 64;
    public final static int FlagIgnoreAtFunctionEscape = 128;
    public final static int FlagRichTextContent = 256;

    private final TypeTable typeTable;
    private final boolean ignoreTwoWayBinding;
   
    public TextParser(TypeTable typeTable, int compatibilityVersion)
    {
        this.typeTable = typeTable;
        this.ignoreTwoWayBinding = compatibilityVersion == 0 ? false : compatibilityVersion < flex2.compiler.common.MxmlConfiguration.VERSION_4_0;
    }

    public TextParser(TypeTable typeTable)
    {
        this(typeTable, 0);
    }

    /**
     * called when an @ContextRoot has been recognized, in an ok spot. Handler should return a String
     * @param text original @ContextRoot expression, unmodified
     * @return whatever you want parse() to return
     */
    protected abstract String contextRoot(String text);

    /**
     * called when an @Clear has been recognized, in an ok spot. Handler should return a VO
     * @return whatever you want parse() to return
     */
    protected abstract Object clear();
   
    /**
     * called when an @Embed has been recognized, in an ok spot. Handler should return a VO
     * @param text original @Embed expression, unmodified
     * @param type
     * @return whatever you want parse() to return
     */
    protected abstract Object embed(String text, Type type);

    /**
     * called when an @Resource has been recognized, in an ok spot. Handler should return a VO
     * @param text original @Resource expression, unmodified
     * @param type
     * @return whatever you want parse() to return
     */
    protected abstract Object resource(String text, Type type);

    /**
     * called when a binding expression has been parsed. Handler should return a VO
     * @param converted converted binding expression
     * @return whatever you want parse() to return
     */
    protected abstract Object bindingExpression(String converted);
    protected abstract Object bindingExpression(String converted, boolean isTwoWay);

    /**
     * called to parse a binding expression.
     * @param text to parse
     * @param line being parsed
     * @return BindingExpression or null if not binding expression
     */
    protected abstract BindingExpression parseBindingExpression(String text, int line);
   
    /**
     * called when a valid percentage string has been parsed. Callback allows subs to do nasty stuff like
     * property-name swapping, etc.
     * @param pct canonicalized percentage string
     * @return whatever you want parse() to return
     */
    protected abstract Object percentage(String pct);

    /**
     * called when an array expression has been parsed. Handler should return a VO
     * @param entries Collection of parsed array entries
     * @param arrayElementType
     * @return whatever you want parse() to return
     */
    protected abstract Object array(Collection<Object> entries, Type arrayElementType);

    /**
     * called when a text value has been parsed for type Function. Handler normally returns the unmodified name
     */
    protected abstract Object functionText(String name);

    /**
     * called when a text value has been parsed for type Class. Handler normally returns the unmodified name
     * if type == Class; a Primitive if type is an instance factory.
     */
    protected abstract Object className(String name, Type type);

    /**
     * called on a parse error
     * @param error one of the constants defined in this interface
     * @param text erroneous text
     * @param type type for which parse was requested
     * @param arrayElementType
     */
    protected abstract void error(int error, String text, Type type, Type arrayElementType);

    /**
     * type-directed text parsing. Search order is:
     * 1. look for a binding expression, if applicable.
     * 2. then look for an embed.
     * 3. then look for a resource.
     * 4. if nothing is found above, attempt to deserialize a value of the specified type[arrayElementType]
     */
    protected Object parse(String text, Type type, Type arrayElementType, int flags)
    {
        if (!inCDATA(flags))
        {
            //  binding?
            if (!ignoreBinding(flags))
            {
                BindingExpression result = parseBindingExpression(text);
                if (result != null)
                {
                    return result;
                }
                else
                {
                    text = cleanupBindingEscapes(text);
                }
            }

            //  @func() ?
            if (!ignoreAtFunction(flags))
            {
                String atFunctionName = getAtFunctionName(text);
                if (atFunctionName != null)
                {
                    return parseAtFunction(atFunctionName, text, type, arrayElementType, flags);
                }
                else
                {
                    // SDK-18397 requires getting \@ and \\@ escapes to work
                    // again after two-way data-binding syntax was added.
                    // The following is called here in case the type is
                    // something other than String, but it then has to avoid
                    // parseString() from removing another slash from \\@.
                    // TODO: Re-work the escaping system in TextParser.
                    text = cleanupAtFunctionEscapes(text);
                    flags = flags | TextParser.FlagIgnoreAtFunctionEscape;
                }
            }
        }

        // If we have [RichTextContent] we treat the CDATA as a String and do
        // not test for any literal syntax
        if (isRichTextContent(flags))
        {
            String parsedString = parseString(text, flags);

            // Singleton coercion
            if (type == typeTable.arrayType)
            {
                return array(Collections.singleton((Object) parsedString), arrayElementType);
            }
            else
            {
                return parsedString;
            }
        }
        else
        {
            //  ordinary value
            return parseValue(text, type, arrayElementType, flags);
        }
    }

    /**
     * Parses text and looks first for a two-way binding expression and then
     * for a one-way binding expression.  If a malformed two-way binding expression
     * is found, i.e. there is anything other than leading and trailing whitespace,
     * or nested bindings, an error is logged and null is returned.
     * @param s the string to be parsed
     * @return BindingExpression or null
     */
    protected BindingExpression parseBindingExpression(String s)
    {       
        int atIdx = -1;
        int openBraceIdx = -1;
       
        // Look for unescaped '@{'.  If found, string will be parsed as a two-way.
        if (!ignoreTwoWayBinding)
        {
            atIdx = StringUtils.findNextUnescaped('@', 0, s);
            if (atIdx >= 0)
            {
                openBraceIdx = StringUtils.findNextUnescaped('{', atIdx + 1, s);
                if (openBraceIdx != atIdx + 1)
                {
                    atIdx = -1;
                }
            }
        }
       
        // Not two-way bind, so look for one-way bind '{' from the start of the string.
        if (atIdx == -1)
        {
            openBraceIdx = StringUtils.findNextUnescaped('{', 0, s);
        }
       
        // Neither one or two-way binding expression.
        if (openBraceIdx == -1)
        {
            return null;
        }

        // this allows for nested binding expressions such as { foo {bar}}
        int closeBraceIdx = StringUtils.findClosingToken('{', '}', s, openBraceIdx);
        if (closeBraceIdx == -1)
        {
            // apparently an open token without the corresponding close token
            // is not considered an error
            return null;
        }

        // Handle two-way binding expressions here, i.e. '@{...}'
        if (atIdx >= 0)
        {
            // no lead, no tail other than whitespace (which technically makes this
            // an expression but one-way allows it and trims it so do the same here)
            if (!s.substring(0, atIdx).trim().equals("") || !s.substring(closeBraceIdx + 1).trim().equals(""))
            {
                error(ErrInvalidTwoWayBind, s, null, null);
                return null;                                                       
            }

            // must have content and no nested binding expressions
            String contents = s.substring(openBraceIdx + 1, closeBraceIdx);
            if (contents.length() == 0 || isBindingExpression(contents))
            {
                error(ErrInvalidTwoWayBind, s, null, null);
                return null;                                                       
            }
          
            //Don't include the braces (or parens since they will just get stripped).
            return (BindingExpression) bindingExpression(contents, true);
        }
       
        StringBuilder buf = new StringBuilder();
       
        //first attach the leading part of the string, all the way up to the opening brace
        //for one-way bind or at-symbol for two-way bind
        String lead = s.substring(0, openBraceIdx);

        //only if there was non-whitespace
        if (!lead.trim().equals(""))
        {
            String text = cleanupBindingEscapes(lead);
            text = cleanupAtFunctionEscapes(text);
            buf.append(StringUtils.formatString(text));
            buf.append(" + ");
        }
       
        //now loop, attaching the piece between braces and the next string if it exists
        while (openBraceIdx != -1)
        {
            //attach this { } (don't include the braces but do use parentheses to group the thing together)
            buf.append("(");
            String contents = s.substring(openBraceIdx + 1, closeBraceIdx);
            if (contents.trim().equals(""))
            {
                //  logWarning("Empty {} in binding expression.");
                contents = "''";
            }
            buf.append(contents);
            buf.append(")");
            //now see if there's a tail part to add
            int lastClose = closeBraceIdx;
            openBraceIdx = StringUtils.findNextUnescaped('{', lastClose, s);
            if (openBraceIdx != -1)
            {
                buf.append(" + ");
                closeBraceIdx = StringUtils.findClosingToken('{', '}', s, openBraceIdx);
                if (closeBraceIdx != -1)
                {
                    String text = cleanupBindingEscapes(s.substring(lastClose + 1, openBraceIdx));
                    text = cleanupAtFunctionEscapes(text);
                    buf.append(StringUtils.formatString(text));
                    buf.append(" + ");
                }
                else
                {
                    buf.append(StringUtils.formatString(s.substring(lastClose + 1)));
                    openBraceIdx = -1; //make sure to finish the loop
                }
            }
            else
            {
                String tail = s.substring(lastClose + 1);
                if (!tail.trim().equals(""))
                {
                    buf.append(" + ");
                    String text = cleanupBindingEscapes(tail);
                    text = cleanupAtFunctionEscapes(text);
                    buf.append(StringUtils.formatString(text));
                }
            }
        }

        return (BindingExpression) bindingExpression(buf.toString());
    }

    /**
     * do type-directed deserialization of typed constant value from text
     * NOTE using equals() not isAssignableTo() for type testing - all tested classes are final as of 8/4/05
     * TODO assertions confirming type finality
     */
    private Object parseValue(String text, Type type, Type arrayElementType, int flags)
    {
        boolean isint = false, isuint = false//  temps

        Object result = null;

        if (type.equals(typeTable.noType) || type.equals(typeTable.objectType))
        {
            result = parseObject(text, arrayElementType, flags);
        }
        else if (type.equals(typeTable.stringType))
        {
            result = parseString(text, flags);
        }
        else if (type.equals(typeTable.numberType) ||
                (isint = type.equals(typeTable.intType)) ||
                (isuint = type.equals(typeTable.uintType)))
        {
            if (text.indexOf('%') >= 0)
            {
                if (allowPercentages(flags))
                {
                    if ((result = parsePercentage(text)) != null)
                    {
                        result = percentage((String)result);
                    }
                    else
                    {
                        result = new ParseError(ErrInvalidPercentage);
                    }
                }
                else
                {
                    result = new ParseError(ErrPercentagesNotAllowed);
                }
            }
            else
            {
                result = isint ? parseInt(text, flags) :
                        isuint ? parseUInt(text, flags) :
                        parseNumber(text, flags);
            }
        }
        else if (type.equals(typeTable.booleanType))
        {
            result = parseBoolean(text);
        }
        else if (type.equals(typeTable.regExpType))
        {
            result = parseRegExp(text);
        }
        else if (type.equals(typeTable.arrayType))
        {
            Collection<Object> c = parseArray(text, arrayElementType, true, flags);
            result = c != null ? array(c, arrayElementType) : null;
        }
        else if (type.equals(typeTable.functionType))
        {
            String f = parseFunction(text);
            result = f != null ? functionText(f) : null;
        }
        else if (acceptsClassRef(type))
        {
            String c = parseClassName(text);
            result = c != null ? className(c, type) : null;
        }
        else
        {
            result = new ParseError(ErrTypeNotSerializable);
        }

        //  handle/return

        if (result == null)
        {
            result = new ParseError(ErrInvalidTextForType);
        }

        if (result instanceof ParseError)
        {
            error(((ParseError)result).errno, text, type, arrayElementType);
            return null;
        }
        else
        {
            return result;
        }
    }

    /**
     *
     */
    private boolean acceptsClassRef(Type type)
    {
        return type.equals(typeTable.classType) || typeTable.getStandardDefs().isInstanceGenerator(type);
    }

    /**
     *
     */
    private Object parseObject(String text, Type arrayElementType, int flags)
    {
        String temp = text.trim();

        Object result;
        if ((result = parseBoolean(temp)) != null)
        {
            return result;
        }
        else if ((result = parseArray(temp, arrayElementType, false, flags)) != null)
        {
            @SuppressWarnings("unchecked")
            Collection<Object> res = (Collection<Object>)result;
            return array(res, arrayElementType);
        }
        else if ((result = parseNumber(temp, flags)) != null)
        {
            return result;
        }
        else
        {
            return text;
        }
    }

    /**
     *
     */
    protected Collection<Object> parseArray(String text, Type elementType, boolean coerceSingleton, int flags)
    {
        String trimmed = text.trim();

        if (ignoreArraySyntax(flags) || !isArray(trimmed))
        {
            if (coerceSingleton)
            {
                Object element = parseValue(text, elementType, typeTable.objectType, flags);
                return element != null ? Collections.singleton(element) : null;
            }
            else
            {
                return null;
            }
        }

        if (isEmptyArray(trimmed))
        {
            return Collections.emptyList();
        }

        Collection<Object> result = new ArrayList<Object>();
    StringBuilder buffer = new StringBuilder();
        char quoteChar = '\'';
        boolean inQuotes = false;

        for (int index = 1, length = trimmed.length(); index < length; index++)
        {
            char c = trimmed.charAt(index);

            switch (c)
            {
            case '[':
                if (inQuotes)
                {
                    buffer.append(c);
                }
                else
                {
                    //  TODO nested arrays?
                }
                break;
            case '"':
            case '\'':
                if (inQuotes)
                {
                    if (quoteChar == c)
                    {
                        inQuotes = false;
                    }
                    else
                    {
                        buffer.append(c);
                    }
                }
                else
                {
                    inQuotes = true;
                    quoteChar = c;
                }
                break;
            case ',':
            case ']':
                if (inQuotes)
                {
                    buffer.append(c);
                }
                else
                {
                    String elementText = buffer.toString().trim();
          buffer = new StringBuilder();

                    //  NOTE clear any special-processing flags, on the interpretation that they only apply to top-level scalars.
                    //  TODO multi-level typed arrays? :)
                    Object element = parseValue(elementText, elementType, typeTable.objectType, 0);
                    if (element != null)
                    {
                        result.add(element);
                    }
                    else
                    {
                        return null;
                    }
                }
                break;
            default:
                buffer.append(c);
            }
        }

        return result;
    }

    /**
     *
     */
    private boolean hasLeadingZeros(String s)
    {
        boolean result = false;
        int n = s.length();
        if (n > 1 && s.charAt(0) == '0' &&
            !(s.startsWith("0x") || s.startsWith("0X") || s.startsWith("0.")))
        {
            result = true;
        }
        return result;
    }

    /**
     * We accept 0x and # prefixes.
     */
    private Integer parseInt(String s, int flags)
    {
        if (convertColorNames(flags))
        {
            String c = Descriptor.convertColorName(s);
            if (c != null)
            {
                s = c;
            }
        }

        try
        {
            // Don't parse int's with leading zeros, which are not octal.
            // For example, a MA zip code, 02127.
            if (hasLeadingZeros(s))
            {
                return null;
            }
            else
            {
                return Integer.decode(s);
            }
        }
        catch (NumberFormatException e)
        {
            return null;
        }
    }

    /**
     *
     */
    private Long parseUInt(String s, int flags)
    {
        if (convertColorNames(flags))
        {
            String c = Descriptor.convertColorName(s);
            if (c != null)
            {
                s = c;
            }
        }

        try
        {
            // Don't parse uint's with leading zeros, which are not octal.
            // For example, a MA zip code, 02127.
            if (hasLeadingZeros(s))
            {
                return null;
            }
            else
            {
                Long l = Long.decode(s);
                long val = l.longValue();
                return (val == java.lang.Math.abs(val) && val <= 0xffffffffL) ? l : null;
            }
        }
        catch (NumberFormatException e)
        {
            return null;
        }
    }

    /**
     *
     */
    private Number parseNumber(String s, int flags)
    {
        // Don't parse Number's with leading zeros, which are not octal.
        // For example, a MA zip code, 02127.
        if (hasLeadingZeros(s))
        {
            return null;
        }

        Integer integer = parseInt(s, flags);
        if (integer != null)
        {
            return integer;
        }
        else
        {
            try
            {
                return Double.valueOf(s);
            }
            catch (NumberFormatException e)
            {
                return null;
            }
        }
    }

    /**
     *
     */
    private String parseString(String text, int flags)
    {
        if (collapseWhiteSpace(flags) && !inCDATA(flags))
        {
            text = StringUtils.collapseWhitespace(text, ' ');
        }

        if (!ignoreAtFunctionEscape(flags)
                && (text.length() > 1 && text.charAt(0) == '\\' && "\\@".indexOf(text.charAt(1)) >= 0))
        {
            //  '\' is being used to begin the string with a literal '\' or '@'
            //  NOTE: currently, we only attach special meaning to "@name(...)" when it begins a string.
            text = text.substring(1);
        }

        return text;
    }
   
   /**
    * Static helper to parse simple comma delimited string list. Supports with
    * or without array notation ('[',']').
    */
   public static Collection<String> parseStringList(String text)
   {
       if (text != null)
       {
           String trimmed = text.trim();
          
           if (!isArray(trimmed))
           {
               text = "[" + text + "]";
           }
           else if (isEmptyArray(trimmed))
           {
               return Collections.emptyList();
           }
       }
       else
       {
           text = "";
       }

       Collection<String> result = new ArrayList<String>();
       StringBuilder buffer = new StringBuilder();
       char quoteChar = '\'';
       boolean inQuotes = false;

       for (int index = 1, length = text.length(); index < length; index++)
       {
           char c = text.charAt(index);

           switch (c)
           {
           case '[':
               if (inQuotes)
                   buffer.append(c);
               break;
           case '"':
           case '\'':
               if (inQuotes)
               {
                   if (quoteChar == c)
                       inQuotes = false;
                   else
                       buffer.append(c);
               }
               else
               {
                   inQuotes = true;
                   quoteChar = c;
               }
               break;
           case ',':
           case ']':
               if (inQuotes)
               {
                   buffer.append(c);
               }
               else
               {
                   String elementText = buffer.toString().trim();
                   buffer = new StringBuilder();
                   result.add(elementText);
               }
               break;
           default:
               buffer.append(c);
           }
       }

       return result;
   }

    /**
     *
     */
    private static Boolean parseBoolean(String text)
    {
        // If we get false, make sure its because the user specified 'false'
        Boolean b = Boolean.valueOf(StringUtils.collapseWhitespace(text, ' '));
        return b.booleanValue() || text.equalsIgnoreCase("false") ? b : null;
    }

    /**
     *
     */
    private static String parseRegExp(String text)
    {
        Matcher m = regExpPattern.matcher(text);
        return m.matches() ? m.group(0) : null;
    }

    /**
     * NOTE returns canonicalized percentage expression in a String - not the percentage itself
     */
    private static String parsePercentage(String text)
    {
        Matcher m = percentagePattern.matcher(text);
        return m.matches() ? m.group(1) + '%' : null;
    }

    /**
     * TODO there was a TODO in 1.5 about parsing source code... ?
     */
    private static String parseFunction(String text)
    {
        return text.trim();
    }

    /**
     *
     */
    public static String parseClassName(String text)
    {
        String name = text.trim();
        return isQualifiedName(name) ? name : null;
    }

    /**
     *
     */
    private static class ParseError
    {
        final int errno;
        ParseError(final int errno) { this.errno = errno; }
    }
   
   

    /**
     * flag extraction
     */
    private static final boolean inCDATA(int flags) { return (flags & FlagInCDATA) == FlagInCDATA; }
    private static final boolean collapseWhiteSpace(int flags) { return (flags & FlagCollapseWhiteSpace) == FlagCollapseWhiteSpace; }
    private static final boolean convertColorNames(int flags) { return (flags & FlagConvertColorNames) == FlagConvertColorNames; }
    private static final boolean allowPercentages(int flags) { return (flags & FlagAllowPercentages) == FlagAllowPercentages; }
    private static final boolean ignoreBinding(int flags) { return (flags & FlagIgnoreBinding) == FlagIgnoreBinding; }
    private static final boolean ignoreAtFunction(int flags) { return (flags & FlagIgnoreAtFunction) == FlagIgnoreAtFunction; }
    private static final boolean ignoreArraySyntax(int flags) { return (flags & FlagIgnoreArraySyntax) == FlagIgnoreArraySyntax; }
    private static final boolean ignoreAtFunctionEscape(int flags) { return (flags & FlagIgnoreAtFunctionEscape) == FlagIgnoreAtFunctionEscape; }
    private static final boolean isRichTextContent(int flags) { return (flags & FlagRichTextContent) == FlagRichTextContent; }

    /**
     * TODO make private
     * Get rid of backslashes that were escaping curly braces
     * @param toClean
     * @return the cleaned string
     */
    public static String cleanupBindingEscapes(String toClean)
    {
        toClean = StringUtils.cleanupEscapedChar('{', toClean);
        toClean = StringUtils.cleanupEscapedChar('}', toClean);
        return toClean;
    }

    /**
     * TODO make private
     * Get rid of backslashes that were escaping at-functions
     * @param toClean
     * @return the cleaned string
    */
    public static String cleanupAtFunctionEscapes(String toClean)
    {
        toClean = StringUtils.cleanupEscapedChar('@', toClean);
        return toClean;
    }

    /**
     * TODO make private
     * replace backslashes for curly braces with &#7d; &#7b;
     * @param toClean
     * @return the cleaned string
     */
    public static String replaceBindingEscapesForE4X(String toClean)
    {
        toClean = StringUtils.cleanupEscapedCharForXML('{', toClean);
        toClean = StringUtils.cleanupEscapedCharForXML('}', toClean);
        toClean = StringUtils.cleanupEscapedCharForXML('@', toClean);
        return toClean;
    }
   
    /**
     * Determine if string s contains matching unescaped '{' and '} characters.
     * This could represent either a one-way or two-way ('@{' and '}') expression.
     * @param s
     * @return true if s is either a one-way or two-way data binding expression
     */
    public static boolean isBindingExpression(String s)
    {
        int openBraceIdx = StringUtils.findNthUnescaped('{', 1, s);
        if (openBraceIdx == -1)
        {
            return false;
        }

        int closeBraceIdx = StringUtils.findClosingToken('{', '}', s, openBraceIdx);
        if (closeBraceIdx == -1)
        {
            return false;
        }

        return true;
    }

    /**
     * Returns true if s contains unquoted '@{ }' sequence but it still may
     * not be a legal two-way bind expression if there are nested or multiple binds,
     * or leading and trailing text around the sequence.
     * @param s
     * @return boolean
     */
    public static boolean isTwoWayBindingExpression(String s)
    {
        int openBraceIdx = StringUtils.findNthUnescaped('{', 1, s);
        if (openBraceIdx == -1 || openBraceIdx == 0)
        {
            return false;
        }

        int atIdx = StringUtils.findNthUnescaped('@', 1, s);
        if (atIdx != openBraceIdx - 1)
        {
            return false;
        }

        int closeBraceIdx = StringUtils.findClosingToken('{', '}', s, openBraceIdx);
        if (closeBraceIdx == -1)
        {
            return false;
        }

        return true;
    }

    /**
     *
     */
    //TODO ideally private ...
    public static String getAtFunctionName(String value)
    {
        value = value.trim();

        if (value.length() > 1 && value.charAt(0) == '@')
        {
            int openParen = value.indexOf('(');

            // A function must have an open paren and a close paren after the open paren.
            if (openParen > 1 && value.indexOf(')') > openParen)
            {
                return value.substring(1, openParen);
            }
        }

        return null;
    }

    /**
     *
     */
    private Object parseAtFunction(String functionName, String text, Type type, Type arrayElementType, int flags)
    {
        Object result = null;

        if ("Embed".equals(functionName))
        {
            // @Embed requires that lvalue accept String or Class
            if (typeTable.stringType.isAssignableTo(type) || acceptsClassRef(type))
            {
                result = embed(text, type);
            }
            else
            {
                error(ErrTypeNotEmbeddable, text, type, arrayElementType);
            }
        }
        else if ("ContextRoot".equals(functionName))
        {
            // @ContextRoot requires a String lvalue
            if (typeTable.stringType.isAssignableTo(type))
            {
                result = contextRoot(text);
            }
            else
            {
                error(ErrTypeNotContextRootable, text, type, arrayElementType);
            }
        }
        else if ("Resource".equals(functionName))
        {
            result = resource(text, type);
        }
        else if ("Clear".equals(functionName))
        {
            result = clear();
        }
        else
        {
            error(ErrUnrecognizedAtFunction, text, type, arrayElementType);
        }

        return result;
    }

    /**
     *
     */
    protected static boolean isArray(String text)
    {
        assert text.equals(text.trim());
        boolean result = true;

        if ((text.length() < 2) ||
            (text.charAt(0) != '[') ||
            (text.charAt(text.length() - 1) != ']'))
        {
            result = false;
        }

        return result;
    }

    private static boolean isEmptyArray(String text)
    {
        assert text.equals(text.trim());
        boolean result = false;

        if (isArray(text) && text.substring(1, text.length() - 1).trim().length() == 0)
        {
            result = true;
        }

        return result;
    }

    /**
     * test if this is a valid identifier, and is not an actionscript keyword.
     */
    public static boolean isValidIdentifier(String id)
    {
        if (id.length() == 0 || !isIdentifierFirstChar(id.charAt(0)))
        {
            return false;
        }

        for (int i=1; i < id.length(); i++)
        {
            if (!isIdentifierChar(id.charAt(i)))
            {
                return false;
            }
        }

        if (StandardDefs.isReservedWord(id))
        {
            return false;
        }

        return true;
    }
   
    /**
     * test if this is a valid state identifier, essentially must be a valid
     * XML attribute name *without* : or . character within.
     */
    public static boolean isValidStateIdentifier(String id)
    {
        if (id != null)
        {
            for (int i=0; i < id.length(); i++)
            {
                if (!isIdentifierChar(id.charAt(i)))
                {
                    return false;
                }
            }
       
            if (!XMLChar.isValidName(id))
            {
                return false;
            }

            return true;
        }
        return false;
    }
   
    /**
     * Used to detect scoped attributes.
     */
    public static boolean isScopedName(String name)
    {
        return name.indexOf('.') != -1;
    }
 
    /**
     * Helper used to decompose a scoped name.
     */
    public static String[] analyzeScopedName(String name)
    {
        String[] results = name.split("\\.");                          
        return (results.length != 2) ? null : results;
    }

    /**
     *
     */
    private static boolean isIdentifierFirstChar(char ch)
    {
        return Character.isJavaIdentifierStart(ch);
    }

    /**
     *
     */
    private static boolean isIdentifierChar(int ch)
    {
        return ch != -1 && Character.isJavaIdentifierPart((char)ch);
    }

    /**
     *
     */
    private static boolean isQualifiedName(String text)
    {
        return qualifiedNamePattern.matcher(text).matches() && !StandardDefs.isReservedWord(text);
    }
}
TOP

Related Classes of flex2.compiler.mxml.lang.TextParser$ParseError

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.