1 /* 2 * Copyright (C) 2015 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.bridge.impl; 18 19 import org.xmlpull.v1.XmlPullParser; 20 import org.xmlpull.v1.XmlPullParserException; 21 22 import android.annotation.Nullable; 23 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.io.Reader; 27 import java.util.ArrayList; 28 import java.util.Collections; 29 import java.util.List; 30 31 /** 32 * A wrapper around XmlPullParser that can peek forward to inspect if the file is a data-binding 33 * layout and some parts need to be stripped. 34 */ 35 public class LayoutParserWrapper implements XmlPullParser { 36 37 // Data binding constants. 38 private static final String TAG_LAYOUT = "layout"; 39 private static final String TAG_DATA = "data"; 40 private static final String DEFAULT = "default="; 41 42 private final XmlPullParser mDelegate; 43 44 // Storage for peeked values. 45 private boolean mPeeked; 46 private int mEventType; 47 private int mDepth; 48 private int mNext; 49 private List<Attribute> mAttributes; 50 private String mText; 51 private String mName; 52 53 // Used to end the document before the actual parser ends. 54 private int mFinalDepth = -1; 55 private boolean mEndNow; 56 LayoutParserWrapper(XmlPullParser delegate)57 public LayoutParserWrapper(XmlPullParser delegate) { 58 mDelegate = delegate; 59 } 60 peekTillLayoutStart()61 public LayoutParserWrapper peekTillLayoutStart() throws IOException, XmlPullParserException { 62 final int STATE_LAYOUT_NOT_STARTED = 0; // <layout> tag not encountered yet. 63 final int STATE_ROOT_NOT_STARTED = 1; // the main view root not found yet. 64 final int STATE_INSIDE_DATA = 2; // START_TAG for <data> found, but not END_TAG. 65 66 int state = STATE_LAYOUT_NOT_STARTED; 67 int dataDepth = -1; // depth of the <data> tag. Should be two. 68 while (true) { 69 int peekNext = peekNext(); 70 switch (peekNext) { 71 case START_TAG: 72 if (state == STATE_LAYOUT_NOT_STARTED) { 73 if (mName.equals(TAG_LAYOUT)) { 74 state = STATE_ROOT_NOT_STARTED; 75 } else { 76 return this; // no layout tag in the file. 77 } 78 } else if (state == STATE_ROOT_NOT_STARTED) { 79 if (mName.equals(TAG_DATA)) { 80 state = STATE_INSIDE_DATA; 81 dataDepth = mDepth; 82 } else { 83 mFinalDepth = mDepth; 84 return this; 85 } 86 } 87 break; 88 case END_TAG: 89 if (state == STATE_INSIDE_DATA) { 90 if (mDepth <= dataDepth) { 91 state = STATE_ROOT_NOT_STARTED; 92 } 93 } 94 break; 95 case END_DOCUMENT: 96 // No layout start found. 97 return this; 98 } 99 // consume the peeked tag. 100 next(); 101 } 102 } 103 peekNext()104 private int peekNext() throws IOException, XmlPullParserException { 105 if (mPeeked) { 106 return mNext; 107 } 108 mEventType = mDelegate.getEventType(); 109 mNext = mDelegate.next(); 110 if (mEventType == START_TAG) { 111 int count = mDelegate.getAttributeCount(); 112 mAttributes = count > 0 ? new ArrayList<Attribute>(count) : 113 Collections.<Attribute>emptyList(); 114 for (int i = 0; i < count; i++) { 115 mAttributes.add(new Attribute(mDelegate.getAttributeNamespace(i), 116 mDelegate.getAttributeName(i), mDelegate.getAttributeValue(i))); 117 } 118 } 119 mDepth = mDelegate.getDepth(); 120 mText = mDelegate.getText(); 121 mName = mDelegate.getName(); 122 mPeeked = true; 123 return mNext; 124 } 125 reset()126 private void reset() { 127 mAttributes = null; 128 mText = null; 129 mName = null; 130 mPeeked = false; 131 } 132 133 @Override next()134 public int next() throws XmlPullParserException, IOException { 135 int returnValue; 136 int depth; 137 if (mPeeked) { 138 returnValue = mNext; 139 depth = mDepth; 140 reset(); 141 } else if (mEndNow) { 142 return END_DOCUMENT; 143 } else { 144 returnValue = mDelegate.next(); 145 depth = getDepth(); 146 } 147 if (returnValue == END_TAG && depth <= mFinalDepth) { 148 mEndNow = true; 149 } 150 return returnValue; 151 } 152 153 @Override getEventType()154 public int getEventType() throws XmlPullParserException { 155 return mPeeked ? mEventType : mDelegate.getEventType(); 156 } 157 158 @Override getDepth()159 public int getDepth() { 160 return mPeeked ? mDepth : mDelegate.getDepth(); 161 } 162 163 @Override getName()164 public String getName() { 165 return mPeeked ? mName : mDelegate.getName(); 166 } 167 168 @Override getText()169 public String getText() { 170 return mPeeked ? mText : mDelegate.getText(); 171 } 172 173 @Override getAttributeValue(@ullable String namespace, String name)174 public String getAttributeValue(@Nullable String namespace, String name) { 175 String returnValue = null; 176 if (mPeeked) { 177 if (mAttributes == null) { 178 if (mEventType != START_TAG) { 179 throw new IndexOutOfBoundsException("getAttributeValue() called when not at START_TAG."); 180 } else { 181 return null; 182 } 183 } else { 184 for (Attribute attribute : mAttributes) { 185 //noinspection StringEquality for nullness check. 186 if (attribute.name.equals(name) && (attribute.namespace == namespace || 187 attribute.namespace != null && attribute.namespace.equals(namespace))) { 188 returnValue = attribute.value; 189 break; 190 } 191 } 192 } 193 } else { 194 returnValue = mDelegate.getAttributeValue(namespace, name); 195 } 196 // Check if the value is bound via data-binding, if yes get the default value. 197 if (returnValue != null && mFinalDepth >= 0 && returnValue.startsWith("@{")) { 198 // TODO: Improve the detection of default keyword. 199 int i = returnValue.lastIndexOf(DEFAULT); 200 return i > 0 ? returnValue.substring(i + DEFAULT.length(), returnValue.length() - 1) 201 : null; 202 } 203 return returnValue; 204 } 205 206 private static class Attribute { 207 @Nullable 208 public final String namespace; 209 public final String name; 210 public final String value; 211 Attribute(@ullable String namespace, String name, String value)212 public Attribute(@Nullable String namespace, String name, String value) { 213 this.namespace = namespace; 214 this.name = name; 215 this.value = value; 216 } 217 } 218 219 // Not affected by peeking. 220 221 @Override setFeature(String s, boolean b)222 public void setFeature(String s, boolean b) throws XmlPullParserException { 223 mDelegate.setFeature(s, b); 224 } 225 226 @Override setProperty(String s, Object o)227 public void setProperty(String s, Object o) throws XmlPullParserException { 228 mDelegate.setProperty(s, o); 229 } 230 231 @Override setInput(InputStream inputStream, String s)232 public void setInput(InputStream inputStream, String s) throws XmlPullParserException { 233 mDelegate.setInput(inputStream, s); 234 } 235 236 @Override setInput(Reader reader)237 public void setInput(Reader reader) throws XmlPullParserException { 238 mDelegate.setInput(reader); 239 } 240 241 @Override getInputEncoding()242 public String getInputEncoding() { 243 return mDelegate.getInputEncoding(); 244 } 245 246 @Override getNamespace(String s)247 public String getNamespace(String s) { 248 return mDelegate.getNamespace(s); 249 } 250 251 @Override getPositionDescription()252 public String getPositionDescription() { 253 return mDelegate.getPositionDescription(); 254 } 255 256 @Override getLineNumber()257 public int getLineNumber() { 258 return mDelegate.getLineNumber(); 259 } 260 261 @Override getNamespace()262 public String getNamespace() { 263 return mDelegate.getNamespace(); 264 } 265 266 @Override getColumnNumber()267 public int getColumnNumber() { 268 return mDelegate.getColumnNumber(); 269 } 270 271 // -- We don't care much about the methods that follow. 272 273 @Override require(int i, String s, String s1)274 public void require(int i, String s, String s1) throws XmlPullParserException, IOException { 275 throw new UnsupportedOperationException("Only few parser methods are supported."); 276 } 277 278 @Override getFeature(String s)279 public boolean getFeature(String s) { 280 throw new UnsupportedOperationException("Only few parser methods are supported."); 281 } 282 283 @Override defineEntityReplacementText(String s, String s1)284 public void defineEntityReplacementText(String s, String s1) throws XmlPullParserException { 285 throw new UnsupportedOperationException("Only few parser methods are supported."); 286 } 287 288 @Override getProperty(String s)289 public Object getProperty(String s) { 290 throw new UnsupportedOperationException("Only few parser methods are supported."); 291 } 292 293 @Override nextToken()294 public int nextToken() throws XmlPullParserException, IOException { 295 throw new UnsupportedOperationException("Only few parser methods are supported."); 296 } 297 298 @Override getNamespaceCount(int i)299 public int getNamespaceCount(int i) throws XmlPullParserException { 300 throw new UnsupportedOperationException("Only few parser methods are supported."); 301 } 302 303 @Override getNamespacePrefix(int i)304 public String getNamespacePrefix(int i) throws XmlPullParserException { 305 throw new UnsupportedOperationException("Only few parser methods are supported."); 306 } 307 308 @Override getNamespaceUri(int i)309 public String getNamespaceUri(int i) throws XmlPullParserException { 310 throw new UnsupportedOperationException("Only few parser methods are supported."); 311 } 312 313 @Override isWhitespace()314 public boolean isWhitespace() throws XmlPullParserException { 315 throw new UnsupportedOperationException("Only few parser methods are supported."); 316 } 317 318 @Override getTextCharacters(int[] ints)319 public char[] getTextCharacters(int[] ints) { 320 throw new UnsupportedOperationException("Only few parser methods are supported."); 321 } 322 323 @Override getPrefix()324 public String getPrefix() { 325 throw new UnsupportedOperationException("Only few parser methods are supported."); 326 } 327 328 @Override isEmptyElementTag()329 public boolean isEmptyElementTag() throws XmlPullParserException { 330 throw new UnsupportedOperationException("Only few parser methods are supported."); 331 } 332 333 @Override getAttributeCount()334 public int getAttributeCount() { 335 throw new UnsupportedOperationException("Only few parser methods are supported."); 336 } 337 338 @Override getAttributeNamespace(int i)339 public String getAttributeNamespace(int i) { 340 throw new UnsupportedOperationException("Only few parser methods are supported."); 341 } 342 343 @Override getAttributeName(int i)344 public String getAttributeName(int i) { 345 throw new UnsupportedOperationException("Only few parser methods are supported."); 346 } 347 348 @Override getAttributePrefix(int i)349 public String getAttributePrefix(int i) { 350 throw new UnsupportedOperationException("Only few parser methods are supported."); 351 } 352 353 @Override getAttributeType(int i)354 public String getAttributeType(int i) { 355 throw new UnsupportedOperationException("Only few parser methods are supported."); 356 } 357 358 @Override isAttributeDefault(int i)359 public boolean isAttributeDefault(int i) { 360 throw new UnsupportedOperationException("Only few parser methods are supported."); 361 } 362 363 @Override getAttributeValue(int i)364 public String getAttributeValue(int i) { 365 throw new UnsupportedOperationException("Only few parser methods are supported."); 366 } 367 368 @Override nextText()369 public String nextText() throws XmlPullParserException, IOException { 370 throw new UnsupportedOperationException("Only few parser methods are supported."); 371 } 372 373 @Override nextTag()374 public int nextTag() throws XmlPullParserException, IOException { 375 throw new UnsupportedOperationException("Only few parser methods are supported."); 376 } 377 } 378