FileDocCategorySizeDatePackage
ValueResourceParser.javaAPI DocAndroid 1.5 API8501Wed May 06 22:41:10 BST 2009com.android.layoutlib.utils

ValueResourceParser.java

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * 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.android.layoutlib.utils;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * SAX handler to parser value resource files.
 */
public final class ValueResourceParser extends DefaultHandler {

    // TODO: reuse definitions from somewhere else.
    private final static String NODE_RESOURCES = "resources";
    private final static String NODE_ITEM = "item";
    private final static String ATTR_NAME = "name";
    private final static String ATTR_TYPE = "type";
    private final static String ATTR_PARENT = "parent";
    
    // Resource type definition
    private final static String RES_STYLE = "style";
    private final static String RES_ATTR = "attr";
    
    private final static String DEFAULT_NS_PREFIX = "android:";
    private final static int DEFAULT_NS_PREFIX_LEN = DEFAULT_NS_PREFIX.length();
    
    public interface IValueResourceRepository {
        void addResourceValue(String resType, ResourceValue value);
    }
    
    private boolean inResources = false;
    private int mDepth = 0;
    private StyleResourceValue mCurrentStyle = null;
    private ResourceValue mCurrentValue = null;
    private IValueResourceRepository mRepository;
    private final boolean mIsFramework;
    
    public ValueResourceParser(IValueResourceRepository repository, boolean isFramework) {
        mRepository = repository;
        mIsFramework = isFramework;
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if (mCurrentValue != null) {
            mCurrentValue.setValue(trimXmlWhitespaces(mCurrentValue.getValue()));
        }
        
        if (inResources && qName.equals(NODE_RESOURCES)) {
            inResources = false;
        } else if (mDepth == 2) {
            mCurrentValue = null;
            mCurrentStyle = null;
        } else if (mDepth == 3) {
            mCurrentValue = null;
        }
        
        mDepth--;
        super.endElement(uri, localName, qName);
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes)
            throws SAXException {
        try {
            mDepth++;
            if (inResources == false && mDepth == 1) {
                if (qName.equals(NODE_RESOURCES)) {
                    inResources = true;
                }
            } else if (mDepth == 2 && inResources == true) {
                String type;
                
                // if the node is <item>, we get the type from the attribute "type"
                if (NODE_ITEM.equals(qName)) {
                    type = attributes.getValue(ATTR_TYPE);
                } else {
                    // the type is the name of the node.
                    type = qName;
                }

                if (type != null) {
                    if (RES_ATTR.equals(type) == false) {
                        // get the resource name
                        String name = attributes.getValue(ATTR_NAME);
                        if (name != null) {
                            if (RES_STYLE.equals(type)) {
                                String parent = attributes.getValue(ATTR_PARENT);
                                mCurrentStyle = new StyleResourceValue(type, name, parent, mIsFramework);
                                mRepository.addResourceValue(type, mCurrentStyle);
                            } else {
                                mCurrentValue = new ResourceValue(type, name, mIsFramework);
                                mRepository.addResourceValue(type, mCurrentValue);
                            }
                        }
                    }
                }
            } else if (mDepth == 3 && mCurrentStyle != null) {
                // get the resource name
                String name = attributes.getValue(ATTR_NAME);
                if (name != null) {
                    // the name can, in some cases, contain a prefix! we remove it.
                    if (name.startsWith(DEFAULT_NS_PREFIX)) {
                        name = name.substring(DEFAULT_NS_PREFIX_LEN);
                    }
    
                    mCurrentValue = new ResourceValue(null, name, mIsFramework);
                    mCurrentStyle.addItem(mCurrentValue);
                }
            }
        } finally {
            super.startElement(uri, localName, qName, attributes);
        }
    }
    
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        if (mCurrentValue != null) {
            String value = mCurrentValue.getValue();
            if (value == null) {
                mCurrentValue.setValue(new String(ch, start, length));
            } else {
                mCurrentValue.setValue(value + new String(ch, start, length));
            }
        }
    }
    
    public static String trimXmlWhitespaces(String value) {
        if (value == null) {
            return null;
        }

        // look for carriage return and replace all whitespace around it by just 1 space.
        int index;
        
        while ((index = value.indexOf('\n')) != -1) {
            // look for whitespace on each side
            int left = index - 1;
            while (left >= 0) {
                if (Character.isWhitespace(value.charAt(left))) {
                    left--;
                } else {
                    break;
                }
            }
            
            int right = index + 1;
            int count = value.length();
            while (right < count) {
                if (Character.isWhitespace(value.charAt(right))) {
                    right++;
                } else {
                    break;
                }
            }
            
            // remove all between left and right (non inclusive) and replace by a single space.
            String leftString = null;
            if (left >= 0) {
                leftString = value.substring(0, left + 1);
            }
            String rightString = null;
            if (right < count) {
                rightString = value.substring(right);
            }
            
            if (leftString != null) {
                value = leftString;
                if (rightString != null) {
                    value += " " + rightString;
                }
            } else {
                value = rightString != null ? rightString : "";
            }
        }
        
        // now we un-escape the string
        int length = value.length();
        char[] buffer = value.toCharArray();
        
        for (int i = 0 ; i < length ; i++) {
            if (buffer[i] == '\\' && i + 1 < length) {
                if (buffer[i+1] == 'u') {
                    if (i + 5 < length) {
                        // this is unicode char \u1234
                        int unicodeChar = Integer.parseInt(new String(buffer, i+2, 4), 16);
                        
                        // put the unicode char at the location of the \
                        buffer[i] = (char)unicodeChar;
    
                        // offset the rest of the buffer since we go from 6 to 1 char
                        if (i + 6 < buffer.length) {
                            System.arraycopy(buffer, i+6, buffer, i+1, length - i - 6);
                        }
                        length -= 5;
                    }
                } else {
                    if (buffer[i+1] == 'n') {
                        // replace the 'n' char with \n
                        buffer[i+1] = '\n';
                    }
                    
                    // offset the buffer to erase the \
                    System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
                    length--;
                }
            }
        }
        
        return new String(buffer, 0, length);
    }
}