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 package com.android.layoutlib.bridge.android; 17 18 import com.android.ide.common.rendering.api.ILayoutPullParser; 19 import com.android.ide.common.rendering.api.ResourceNamespace; 20 import com.android.ide.common.rendering.api.ResourceValue; 21 import com.android.layoutlib.bridge.impl.ParserFactory; 22 23 import org.xmlpull.v1.XmlPullParser; 24 import org.xmlpull.v1.XmlPullParserException; 25 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.content.res.XmlResourceParser; 29 import android.util.AttributeSet; 30 import android.util.BridgeXmlPullAttributes; 31 import android.util.ResolvingAttributeSet; 32 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.Reader; 36 37 /** 38 * {@link BridgeXmlBlockParser} reimplements most of android.xml.XmlBlock.Parser. 39 * It delegates to both an instance of {@link XmlPullParser} and an instance of 40 * XmlPullAttributes (for the {@link AttributeSet} part). 41 */ 42 public class BridgeXmlBlockParser implements XmlResourceParser, ResolvingAttributeSet { 43 @NonNull private final XmlPullParser mParser; 44 @NonNull private final ResolvingAttributeSet mAttrib; 45 @Nullable private final BridgeContext mContext; 46 @NonNull private final ResourceNamespace mFileResourceNamespace; 47 48 private boolean mStarted = false; 49 private int mEventType = START_DOCUMENT; 50 51 private boolean mPopped = true; // default to true in case it's not pushed. 52 53 /** 54 * Builds a {@link BridgeXmlBlockParser}. 55 * @param parser XmlPullParser to get the content from. 56 * @param context the Context. 57 * @param fileNamespace namespace of the file being parsed. 58 */ BridgeXmlBlockParser( @onNull XmlPullParser parser, @Nullable BridgeContext context, @NonNull ResourceNamespace fileNamespace)59 public BridgeXmlBlockParser( 60 @NonNull XmlPullParser parser, 61 @Nullable BridgeContext context, 62 @NonNull ResourceNamespace fileNamespace) { 63 if (ParserFactory.LOG_PARSER) { 64 System.out.println("CRTE " + parser.toString()); 65 } 66 67 mParser = parser; 68 mContext = context; 69 mFileResourceNamespace = fileNamespace; 70 71 if (mContext != null) { 72 mAttrib = new BridgeXmlPullAttributes(parser, context, mFileResourceNamespace); 73 mContext.pushParser(this); 74 mPopped = false; 75 } 76 else { 77 mAttrib = new NopAttributeSet(); 78 } 79 } 80 getParser()81 public XmlPullParser getParser() { 82 return mParser; 83 } 84 85 @NonNull getFileResourceNamespace()86 public ResourceNamespace getFileResourceNamespace() { 87 return mFileResourceNamespace; 88 } 89 getViewCookie()90 public Object getViewCookie() { 91 if (mParser instanceof ILayoutPullParser) { 92 return ((ILayoutPullParser)mParser).getViewCookie(); 93 } 94 95 return null; 96 } 97 ensurePopped()98 public void ensurePopped() { 99 if (mContext != null && !mPopped) { 100 mContext.popParser(); 101 mPopped = true; 102 } 103 } 104 105 // ------- XmlResourceParser implementation 106 107 @Override setFeature(String name, boolean state)108 public void setFeature(String name, boolean state) 109 throws XmlPullParserException { 110 if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) { 111 return; 112 } 113 if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) { 114 return; 115 } 116 throw new XmlPullParserException("Unsupported feature: " + name); 117 } 118 119 @Override getFeature(String name)120 public boolean getFeature(String name) { 121 return FEATURE_PROCESS_NAMESPACES.equals(name) || 122 FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name); 123 } 124 125 @Override setProperty(String name, Object value)126 public void setProperty(String name, Object value) throws XmlPullParserException { 127 throw new XmlPullParserException("setProperty() not supported"); 128 } 129 130 @Override getProperty(String name)131 public Object getProperty(String name) { 132 return null; 133 } 134 135 @Override setInput(Reader in)136 public void setInput(Reader in) throws XmlPullParserException { 137 mParser.setInput(in); 138 } 139 140 @Override setInput(InputStream inputStream, String inputEncoding)141 public void setInput(InputStream inputStream, String inputEncoding) 142 throws XmlPullParserException { 143 mParser.setInput(inputStream, inputEncoding); 144 } 145 146 @Override defineEntityReplacementText(String entityName, String replacementText)147 public void defineEntityReplacementText(String entityName, 148 String replacementText) throws XmlPullParserException { 149 throw new XmlPullParserException( 150 "defineEntityReplacementText() not supported"); 151 } 152 153 @Override getNamespacePrefix(int pos)154 public String getNamespacePrefix(int pos) throws XmlPullParserException { 155 throw new XmlPullParserException("getNamespacePrefix() not supported"); 156 } 157 158 @Override getInputEncoding()159 public String getInputEncoding() { 160 return null; 161 } 162 163 @Override getNamespace(String prefix)164 public String getNamespace(String prefix) { 165 return mParser.getNamespace(prefix); 166 } 167 168 @Override getNamespaceCount(int depth)169 public int getNamespaceCount(int depth) throws XmlPullParserException { 170 throw new XmlPullParserException("getNamespaceCount() not supported"); 171 } 172 173 @Override getPositionDescription()174 public String getPositionDescription() { 175 return "Binary XML file line #" + getLineNumber(); 176 } 177 178 @Override getNamespaceUri(int pos)179 public String getNamespaceUri(int pos) throws XmlPullParserException { 180 throw new XmlPullParserException("getNamespaceUri() not supported"); 181 } 182 183 @Override getColumnNumber()184 public int getColumnNumber() { 185 return -1; 186 } 187 188 @Override getDepth()189 public int getDepth() { 190 return mParser.getDepth(); 191 } 192 193 @Override getText()194 public String getText() { 195 return mParser.getText(); 196 } 197 198 @Override getLineNumber()199 public int getLineNumber() { 200 return mParser.getLineNumber(); 201 } 202 203 @Override getEventType()204 public int getEventType() { 205 return mEventType; 206 } 207 208 @Override isWhitespace()209 public boolean isWhitespace() throws XmlPullParserException { 210 // Original comment: whitespace was stripped by aapt. 211 return mParser.isWhitespace(); 212 } 213 214 @Override getPrefix()215 public String getPrefix() { 216 throw new RuntimeException("getPrefix not supported"); 217 } 218 219 @Override getTextCharacters(int[] holderForStartAndLength)220 public char[] getTextCharacters(int[] holderForStartAndLength) { 221 String txt = getText(); 222 char[] chars = null; 223 if (txt != null) { 224 holderForStartAndLength[0] = 0; 225 holderForStartAndLength[1] = txt.length(); 226 chars = new char[txt.length()]; 227 txt.getChars(0, txt.length(), chars, 0); 228 } 229 return chars; 230 } 231 232 @Override getNamespace()233 public String getNamespace() { 234 return mParser.getNamespace(); 235 } 236 237 @Override getName()238 public String getName() { 239 return mParser.getName(); 240 } 241 242 @Override getAttributeNamespace(int index)243 public String getAttributeNamespace(int index) { 244 return mParser.getAttributeNamespace(index); 245 } 246 247 @Override getAttributeName(int index)248 public String getAttributeName(int index) { 249 return mParser.getAttributeName(index); 250 } 251 252 @Override getAttributePrefix(int index)253 public String getAttributePrefix(int index) { 254 throw new RuntimeException("getAttributePrefix not supported"); 255 } 256 257 @Override isEmptyElementTag()258 public boolean isEmptyElementTag() { 259 // XXX Need to detect this. 260 return false; 261 } 262 263 @Override getAttributeCount()264 public int getAttributeCount() { 265 return mParser.getAttributeCount(); 266 } 267 268 @Override getAttributeValue(int index)269 public String getAttributeValue(int index) { 270 return mParser.getAttributeValue(index); 271 } 272 273 @Override getAttributeType(int index)274 public String getAttributeType(int index) { 275 return "CDATA"; 276 } 277 278 @Override isAttributeDefault(int index)279 public boolean isAttributeDefault(int index) { 280 return false; 281 } 282 283 @Override nextToken()284 public int nextToken() throws XmlPullParserException, IOException { 285 return next(); 286 } 287 288 @Override getAttributeValue(String namespace, String name)289 public String getAttributeValue(String namespace, String name) { 290 return mParser.getAttributeValue(namespace, name); 291 } 292 293 @Override next()294 public int next() throws XmlPullParserException, IOException { 295 if (!mStarted) { 296 mStarted = true; 297 298 if (ParserFactory.LOG_PARSER) { 299 System.out.println("STRT " + mParser.toString()); 300 } 301 302 return START_DOCUMENT; 303 } 304 305 int ev = mParser.next(); 306 307 // AAPT treats resources so that XmlBlock$Parser never has TEXT events that are 308 // whitespace only. Ignore those events here as resources from Studio have not gone 309 // through AAPT compilation. 310 while (ev == TEXT && mParser.isWhitespace()) { 311 ev = mParser.next(); 312 } 313 314 if (ParserFactory.LOG_PARSER) { 315 System.out.println("NEXT " + mParser.toString() + " " + 316 eventTypeToString(mEventType) + " -> " + eventTypeToString(ev)); 317 } 318 319 if (ev == END_TAG && mParser.getDepth() == 1) { 320 // done with parser remove it from the context stack. 321 ensurePopped(); 322 } 323 324 mEventType = ev; 325 return ev; 326 } 327 eventTypeToString(int eventType)328 private static String eventTypeToString(int eventType) { 329 switch (eventType) { 330 case START_DOCUMENT: 331 return "START_DOC"; 332 case END_DOCUMENT: 333 return "END_DOC"; 334 case START_TAG: 335 return "START_TAG"; 336 case END_TAG: 337 return "END_TAG"; 338 case TEXT: 339 return "TEXT"; 340 case CDSECT: 341 return "CDSECT"; 342 case ENTITY_REF: 343 return "ENTITY_REF"; 344 case IGNORABLE_WHITESPACE: 345 return "IGNORABLE_WHITESPACE"; 346 case PROCESSING_INSTRUCTION: 347 return "PROCESSING_INSTRUCTION"; 348 case COMMENT: 349 return "COMMENT"; 350 case DOCDECL: 351 return "DOCDECL"; 352 } 353 354 return "????"; 355 } 356 357 @Override require(int type, String namespace, String name)358 public void require(int type, String namespace, String name) 359 throws XmlPullParserException { 360 if (type != getEventType() 361 || (namespace != null && !namespace.equals(getNamespace())) 362 || (name != null && !name.equals(getName()))) 363 throw new XmlPullParserException("expected " + TYPES[type] 364 + getPositionDescription()); 365 } 366 367 @Override nextText()368 public String nextText() throws XmlPullParserException, IOException { 369 if (getEventType() != START_TAG) { 370 throw new XmlPullParserException(getPositionDescription() 371 + ": parser must be on START_TAG to read next text", this, 372 null); 373 } 374 int eventType = next(); 375 if (eventType == TEXT) { 376 String result = getText(); 377 eventType = next(); 378 if (eventType != END_TAG) { 379 throw new XmlPullParserException( 380 getPositionDescription() 381 + ": event TEXT it must be immediately followed by END_TAG", 382 this, null); 383 } 384 return result; 385 } else if (eventType == END_TAG) { 386 return ""; 387 } else { 388 throw new XmlPullParserException(getPositionDescription() 389 + ": parser must be on START_TAG or TEXT to read text", 390 this, null); 391 } 392 } 393 394 @Override nextTag()395 public int nextTag() throws XmlPullParserException, IOException { 396 int eventType = next(); 397 if (eventType == TEXT && isWhitespace()) { // skip whitespace 398 eventType = next(); 399 } 400 if (eventType != START_TAG && eventType != END_TAG) { 401 throw new XmlPullParserException(getPositionDescription() 402 + ": expected start or end tag", this, null); 403 } 404 return eventType; 405 } 406 407 // AttributeSet implementation 408 409 410 @Override close()411 public void close() { 412 // pass 413 } 414 415 @Override getAttributeBooleanValue(int index, boolean defaultValue)416 public boolean getAttributeBooleanValue(int index, boolean defaultValue) { 417 return mAttrib.getAttributeBooleanValue(index, defaultValue); 418 } 419 420 @Override getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue)421 public boolean getAttributeBooleanValue(String namespace, String attribute, 422 boolean defaultValue) { 423 return mAttrib.getAttributeBooleanValue(namespace, attribute, defaultValue); 424 } 425 426 @Override getAttributeFloatValue(int index, float defaultValue)427 public float getAttributeFloatValue(int index, float defaultValue) { 428 return mAttrib.getAttributeFloatValue(index, defaultValue); 429 } 430 431 @Override getAttributeFloatValue(String namespace, String attribute, float defaultValue)432 public float getAttributeFloatValue(String namespace, String attribute, float defaultValue) { 433 return mAttrib.getAttributeFloatValue(namespace, attribute, defaultValue); 434 } 435 436 @Override getAttributeIntValue(int index, int defaultValue)437 public int getAttributeIntValue(int index, int defaultValue) { 438 return mAttrib.getAttributeIntValue(index, defaultValue); 439 } 440 441 @Override getAttributeIntValue(String namespace, String attribute, int defaultValue)442 public int getAttributeIntValue(String namespace, String attribute, int defaultValue) { 443 return mAttrib.getAttributeIntValue(namespace, attribute, defaultValue); 444 } 445 446 @Override getAttributeListValue(int index, String[] options, int defaultValue)447 public int getAttributeListValue(int index, String[] options, int defaultValue) { 448 return mAttrib.getAttributeListValue(index, options, defaultValue); 449 } 450 451 @Override getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue)452 public int getAttributeListValue(String namespace, String attribute, 453 String[] options, int defaultValue) { 454 return mAttrib.getAttributeListValue(namespace, attribute, options, defaultValue); 455 } 456 457 @Override getAttributeNameResource(int index)458 public int getAttributeNameResource(int index) { 459 return mAttrib.getAttributeNameResource(index); 460 } 461 462 @Override getAttributeResourceValue(int index, int defaultValue)463 public int getAttributeResourceValue(int index, int defaultValue) { 464 return mAttrib.getAttributeResourceValue(index, defaultValue); 465 } 466 467 @Override getAttributeResourceValue(String namespace, String attribute, int defaultValue)468 public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) { 469 return mAttrib.getAttributeResourceValue(namespace, attribute, defaultValue); 470 } 471 472 @Override getAttributeUnsignedIntValue(int index, int defaultValue)473 public int getAttributeUnsignedIntValue(int index, int defaultValue) { 474 return mAttrib.getAttributeUnsignedIntValue(index, defaultValue); 475 } 476 477 @Override getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue)478 public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) { 479 return mAttrib.getAttributeUnsignedIntValue(namespace, attribute, defaultValue); 480 } 481 482 @Override getClassAttribute()483 public String getClassAttribute() { 484 return mAttrib.getClassAttribute(); 485 } 486 487 @Override getIdAttribute()488 public String getIdAttribute() { 489 return mAttrib.getIdAttribute(); 490 } 491 492 @Override getIdAttributeResourceValue(int defaultValue)493 public int getIdAttributeResourceValue(int defaultValue) { 494 return mAttrib.getIdAttributeResourceValue(defaultValue); 495 } 496 497 @Override getStyleAttribute()498 public int getStyleAttribute() { 499 return mAttrib.getStyleAttribute(); 500 } 501 502 @Override 503 @Nullable getResolvedAttributeValue(@ullable String namespace, @NonNull String name)504 public ResourceValue getResolvedAttributeValue(@Nullable String namespace, 505 @NonNull String name) { 506 return mAttrib.getResolvedAttributeValue(namespace, name); 507 } 508 } 509