Home | History | Annotate | Download | only in resources
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.common.resources;
     18 
     19 import com.android.ide.common.rendering.api.AttrResourceValue;
     20 import com.android.ide.common.rendering.api.DeclareStyleableResourceValue;
     21 import com.android.ide.common.rendering.api.ResourceValue;
     22 import com.android.ide.common.rendering.api.StyleResourceValue;
     23 import com.android.resources.ResourceType;
     24 
     25 import org.xml.sax.Attributes;
     26 import org.xml.sax.SAXException;
     27 import org.xml.sax.helpers.DefaultHandler;
     28 
     29 /**
     30  * SAX handler to parser value resource files.
     31  */
     32 public final class ValueResourceParser extends DefaultHandler {
     33 
     34     // TODO: reuse definitions from somewhere else.
     35     private final static String NODE_RESOURCES = "resources";
     36     private final static String NODE_ITEM = "item";
     37     private final static String ATTR_NAME = "name";
     38     private final static String ATTR_TYPE = "type";
     39     private final static String ATTR_PARENT = "parent";
     40     private final static String ATTR_VALUE = "value";
     41 
     42     private final static String DEFAULT_NS_PREFIX = "android:";
     43     private final static int DEFAULT_NS_PREFIX_LEN = DEFAULT_NS_PREFIX.length();
     44 
     45     public interface IValueResourceRepository {
     46         void addResourceValue(ResourceValue value);
     47     }
     48 
     49     private boolean inResources = false;
     50     private int mDepth = 0;
     51     private ResourceValue mCurrentValue = null;
     52     private StyleResourceValue mCurrentStyle = null;
     53     private DeclareStyleableResourceValue mCurrentDeclareStyleable = null;
     54     private AttrResourceValue mCurrentAttr;
     55     private IValueResourceRepository mRepository;
     56     private final boolean mIsFramework;
     57 
     58     public ValueResourceParser(IValueResourceRepository repository, boolean isFramework) {
     59         mRepository = repository;
     60         mIsFramework = isFramework;
     61     }
     62 
     63     @Override
     64     public void endElement(String uri, String localName, String qName) throws SAXException {
     65         if (mCurrentValue != null) {
     66             mCurrentValue.setValue(trimXmlWhitespaces(mCurrentValue.getValue()));
     67         }
     68 
     69         if (inResources && qName.equals(NODE_RESOURCES)) {
     70             inResources = false;
     71         } else if (mDepth == 2) {
     72             mCurrentValue = null;
     73             mCurrentStyle = null;
     74             mCurrentDeclareStyleable = null;
     75             mCurrentAttr = null;
     76         } else if (mDepth == 3) {
     77             mCurrentValue = null;
     78             if (mCurrentDeclareStyleable != null) {
     79                 mCurrentAttr = null;
     80             }
     81         }
     82 
     83         mDepth--;
     84         super.endElement(uri, localName, qName);
     85     }
     86 
     87     @Override
     88     public void startElement(String uri, String localName, String qName, Attributes attributes)
     89             throws SAXException {
     90         try {
     91             mDepth++;
     92             if (inResources == false && mDepth == 1) {
     93                 if (qName.equals(NODE_RESOURCES)) {
     94                     inResources = true;
     95                 }
     96             } else if (mDepth == 2 && inResources == true) {
     97                 ResourceType type = getType(qName, attributes);
     98 
     99                 if (type != null) {
    100                     // get the resource name
    101                     String name = attributes.getValue(ATTR_NAME);
    102                     if (name != null) {
    103                         switch (type) {
    104                             case STYLE:
    105                                 String parent = attributes.getValue(ATTR_PARENT);
    106                                 mCurrentStyle = new StyleResourceValue(type, name, parent,
    107                                         mIsFramework);
    108                                 mRepository.addResourceValue(mCurrentStyle);
    109                                 break;
    110                             case DECLARE_STYLEABLE:
    111                                 mCurrentDeclareStyleable = new DeclareStyleableResourceValue(
    112                                         type, name, mIsFramework);
    113                                 mRepository.addResourceValue(mCurrentDeclareStyleable);
    114                                 break;
    115                             case ATTR:
    116                                 mCurrentAttr = new AttrResourceValue(type, name, mIsFramework);
    117                                 mRepository.addResourceValue(mCurrentAttr);
    118                                 break;
    119                             default:
    120                                 mCurrentValue = new ResourceValue(type, name, mIsFramework);
    121                                 mRepository.addResourceValue(mCurrentValue);
    122                                 break;
    123                         }
    124                     }
    125                 }
    126             } else if (mDepth == 3) {
    127                 // get the resource name
    128                 String name = attributes.getValue(ATTR_NAME);
    129                 if (name != null) {
    130 
    131                     if (mCurrentStyle != null) {
    132                         // the name can, in some cases, contain a prefix! we remove it.
    133                         if (name.startsWith(DEFAULT_NS_PREFIX)) {
    134                             name = name.substring(DEFAULT_NS_PREFIX_LEN);
    135                         }
    136 
    137                         mCurrentValue = new ResourceValue(null, name, mIsFramework);
    138                         mCurrentStyle.addValue(mCurrentValue);
    139                     } else if (mCurrentDeclareStyleable != null) {
    140                         mCurrentAttr = new AttrResourceValue(ResourceType.ATTR, name, mIsFramework);
    141                         mCurrentDeclareStyleable.addValue(mCurrentAttr);
    142                     } else if (mCurrentAttr != null) {
    143                         // get the enum/flag value
    144                         String value = attributes.getValue(ATTR_VALUE);
    145 
    146                         try {
    147                             // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we
    148                             // use Long.decode instead.
    149                             mCurrentAttr.addValue(name, (int)(long)Long.decode(value));
    150                         } catch (NumberFormatException e) {
    151                             // pass, we'll just ignore this value
    152                         }
    153 
    154                     }
    155                 }
    156             } else if (mDepth == 4 && mCurrentAttr != null) {
    157                 // get the enum/flag name
    158                 String name = attributes.getValue(ATTR_NAME);
    159                 String value = attributes.getValue(ATTR_VALUE);
    160 
    161                 try {
    162                     // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we
    163                     // use Long.decode instead.
    164                     mCurrentAttr.addValue(name, (int)(long)Long.decode(value));
    165                 } catch (NumberFormatException e) {
    166                     // pass, we'll just ignore this value
    167                 }
    168             }
    169         } finally {
    170             super.startElement(uri, localName, qName, attributes);
    171         }
    172     }
    173 
    174     private ResourceType getType(String qName, Attributes attributes) {
    175         String typeValue;
    176 
    177         // if the node is <item>, we get the type from the attribute "type"
    178         if (NODE_ITEM.equals(qName)) {
    179             typeValue = attributes.getValue(ATTR_TYPE);
    180         } else {
    181             // the type is the name of the node.
    182             typeValue = qName;
    183         }
    184 
    185         ResourceType type = ResourceType.getEnum(typeValue);
    186         return type;
    187     }
    188 
    189 
    190     @Override
    191     public void characters(char[] ch, int start, int length) throws SAXException {
    192         if (mCurrentValue != null) {
    193             String value = mCurrentValue.getValue();
    194             if (value == null) {
    195                 mCurrentValue.setValue(new String(ch, start, length));
    196             } else {
    197                 mCurrentValue.setValue(value + new String(ch, start, length));
    198             }
    199         }
    200     }
    201 
    202     public static String trimXmlWhitespaces(String value) {
    203         if (value == null) {
    204             return null;
    205         }
    206 
    207         // look for carriage return and replace all whitespace around it by just 1 space.
    208         int index;
    209 
    210         while ((index = value.indexOf('\n')) != -1) {
    211             // look for whitespace on each side
    212             int left = index - 1;
    213             while (left >= 0) {
    214                 if (Character.isWhitespace(value.charAt(left))) {
    215                     left--;
    216                 } else {
    217                     break;
    218                 }
    219             }
    220 
    221             int right = index + 1;
    222             int count = value.length();
    223             while (right < count) {
    224                 if (Character.isWhitespace(value.charAt(right))) {
    225                     right++;
    226                 } else {
    227                     break;
    228                 }
    229             }
    230 
    231             // remove all between left and right (non inclusive) and replace by a single space.
    232             String leftString = null;
    233             if (left >= 0) {
    234                 leftString = value.substring(0, left + 1);
    235             }
    236             String rightString = null;
    237             if (right < count) {
    238                 rightString = value.substring(right);
    239             }
    240 
    241             if (leftString != null) {
    242                 value = leftString;
    243                 if (rightString != null) {
    244                     value += " " + rightString;
    245                 }
    246             } else {
    247                 value = rightString != null ? rightString : "";
    248             }
    249         }
    250 
    251         // now we un-escape the string
    252         int length = value.length();
    253         char[] buffer = value.toCharArray();
    254 
    255         for (int i = 0 ; i < length ; i++) {
    256             if (buffer[i] == '\\' && i + 1 < length) {
    257                 if (buffer[i+1] == 'u') {
    258                     if (i + 5 < length) {
    259                         // this is unicode char \u1234
    260                         int unicodeChar = Integer.parseInt(new String(buffer, i+2, 4), 16);
    261 
    262                         // put the unicode char at the location of the \
    263                         buffer[i] = (char)unicodeChar;
    264 
    265                         // offset the rest of the buffer since we go from 6 to 1 char
    266                         if (i + 6 < buffer.length) {
    267                             System.arraycopy(buffer, i+6, buffer, i+1, length - i - 6);
    268                         }
    269                         length -= 5;
    270                     }
    271                 } else {
    272                     if (buffer[i+1] == 'n') {
    273                         // replace the 'n' char with \n
    274                         buffer[i+1] = '\n';
    275                     }
    276 
    277                     // offset the buffer to erase the \
    278                     System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
    279                     length--;
    280                 }
    281             } else if (buffer[i] == '"') {
    282                 // if the " was escaped it would have been processed above.
    283                 // offset the buffer to erase the "
    284                 System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
    285                 length--;
    286 
    287                 // unlike when unescaping, we want to process the next char too
    288                 i--;
    289             }
    290         }
    291 
    292         return new String(buffer, 0, length);
    293     }
    294 }
    295