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.layoutlib.utils; 18 19 import org.xml.sax.Attributes; 20 import org.xml.sax.SAXException; 21 import org.xml.sax.helpers.DefaultHandler; 22 23 /** 24 * SAX handler to parser value resource files. 25 */ 26 public final class ValueResourceParser extends DefaultHandler { 27 28 // TODO: reuse definitions from somewhere else. 29 private final static String NODE_RESOURCES = "resources"; 30 private final static String NODE_ITEM = "item"; 31 private final static String ATTR_NAME = "name"; 32 private final static String ATTR_TYPE = "type"; 33 private final static String ATTR_PARENT = "parent"; 34 35 // Resource type definition 36 private final static String RES_STYLE = "style"; 37 private final static String RES_ATTR = "attr"; 38 39 private final static String DEFAULT_NS_PREFIX = "android:"; 40 private final static int DEFAULT_NS_PREFIX_LEN = DEFAULT_NS_PREFIX.length(); 41 42 public interface IValueResourceRepository { addResourceValue(String resType, ResourceValue value)43 void addResourceValue(String resType, ResourceValue value); 44 } 45 46 private boolean inResources = false; 47 private int mDepth = 0; 48 private StyleResourceValue mCurrentStyle = null; 49 private ResourceValue mCurrentValue = null; 50 private IValueResourceRepository mRepository; 51 private final boolean mIsFramework; 52 ValueResourceParser(IValueResourceRepository repository, boolean isFramework)53 public ValueResourceParser(IValueResourceRepository repository, boolean isFramework) { 54 mRepository = repository; 55 mIsFramework = isFramework; 56 } 57 58 @Override endElement(String uri, String localName, String qName)59 public void endElement(String uri, String localName, String qName) throws SAXException { 60 if (mCurrentValue != null) { 61 mCurrentValue.setValue(trimXmlWhitespaces(mCurrentValue.getValue())); 62 } 63 64 if (inResources && qName.equals(NODE_RESOURCES)) { 65 inResources = false; 66 } else if (mDepth == 2) { 67 mCurrentValue = null; 68 mCurrentStyle = null; 69 } else if (mDepth == 3) { 70 mCurrentValue = null; 71 } 72 73 mDepth--; 74 super.endElement(uri, localName, qName); 75 } 76 77 @Override startElement(String uri, String localName, String qName, Attributes attributes)78 public void startElement(String uri, String localName, String qName, Attributes attributes) 79 throws SAXException { 80 try { 81 mDepth++; 82 if (inResources == false && mDepth == 1) { 83 if (qName.equals(NODE_RESOURCES)) { 84 inResources = true; 85 } 86 } else if (mDepth == 2 && inResources == true) { 87 String type; 88 89 // if the node is <item>, we get the type from the attribute "type" 90 if (NODE_ITEM.equals(qName)) { 91 type = attributes.getValue(ATTR_TYPE); 92 } else { 93 // the type is the name of the node. 94 type = qName; 95 } 96 97 if (type != null) { 98 if (RES_ATTR.equals(type) == false) { 99 // get the resource name 100 String name = attributes.getValue(ATTR_NAME); 101 if (name != null) { 102 if (RES_STYLE.equals(type)) { 103 String parent = attributes.getValue(ATTR_PARENT); 104 mCurrentStyle = new StyleResourceValue(type, name, parent, mIsFramework); 105 mRepository.addResourceValue(type, mCurrentStyle); 106 } else { 107 mCurrentValue = new ResourceValue(type, name, mIsFramework); 108 mRepository.addResourceValue(type, mCurrentValue); 109 } 110 } 111 } 112 } 113 } else if (mDepth == 3 && mCurrentStyle != null) { 114 // get the resource name 115 String name = attributes.getValue(ATTR_NAME); 116 if (name != null) { 117 // the name can, in some cases, contain a prefix! we remove it. 118 if (name.startsWith(DEFAULT_NS_PREFIX)) { 119 name = name.substring(DEFAULT_NS_PREFIX_LEN); 120 } 121 122 mCurrentValue = new ResourceValue(null, name, mIsFramework); 123 mCurrentStyle.addItem(mCurrentValue); 124 } 125 } 126 } finally { 127 super.startElement(uri, localName, qName, attributes); 128 } 129 } 130 131 @Override characters(char[] ch, int start, int length)132 public void characters(char[] ch, int start, int length) throws SAXException { 133 if (mCurrentValue != null) { 134 String value = mCurrentValue.getValue(); 135 if (value == null) { 136 mCurrentValue.setValue(new String(ch, start, length)); 137 } else { 138 mCurrentValue.setValue(value + new String(ch, start, length)); 139 } 140 } 141 } 142 trimXmlWhitespaces(String value)143 public static String trimXmlWhitespaces(String value) { 144 if (value == null) { 145 return null; 146 } 147 148 // look for carriage return and replace all whitespace around it by just 1 space. 149 int index; 150 151 while ((index = value.indexOf('\n')) != -1) { 152 // look for whitespace on each side 153 int left = index - 1; 154 while (left >= 0) { 155 if (Character.isWhitespace(value.charAt(left))) { 156 left--; 157 } else { 158 break; 159 } 160 } 161 162 int right = index + 1; 163 int count = value.length(); 164 while (right < count) { 165 if (Character.isWhitespace(value.charAt(right))) { 166 right++; 167 } else { 168 break; 169 } 170 } 171 172 // remove all between left and right (non inclusive) and replace by a single space. 173 String leftString = null; 174 if (left >= 0) { 175 leftString = value.substring(0, left + 1); 176 } 177 String rightString = null; 178 if (right < count) { 179 rightString = value.substring(right); 180 } 181 182 if (leftString != null) { 183 value = leftString; 184 if (rightString != null) { 185 value += " " + rightString; 186 } 187 } else { 188 value = rightString != null ? rightString : ""; 189 } 190 } 191 192 // now we un-escape the string 193 int length = value.length(); 194 char[] buffer = value.toCharArray(); 195 196 for (int i = 0 ; i < length ; i++) { 197 if (buffer[i] == '\\' && i + 1 < length) { 198 if (buffer[i+1] == 'u') { 199 if (i + 5 < length) { 200 // this is unicode char \u1234 201 int unicodeChar = Integer.parseInt(new String(buffer, i+2, 4), 16); 202 203 // put the unicode char at the location of the \ 204 buffer[i] = (char)unicodeChar; 205 206 // offset the rest of the buffer since we go from 6 to 1 char 207 if (i + 6 < buffer.length) { 208 System.arraycopy(buffer, i+6, buffer, i+1, length - i - 6); 209 } 210 length -= 5; 211 } 212 } else { 213 if (buffer[i+1] == 'n') { 214 // replace the 'n' char with \n 215 buffer[i+1] = '\n'; 216 } 217 218 // offset the buffer to erase the \ 219 System.arraycopy(buffer, i+1, buffer, i, length - i - 1); 220 length--; 221 } 222 } 223 } 224 225 return new String(buffer, 0, length); 226 } 227 } 228