1 /* 2 * Copyright (C) 2023 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.internal.vibrator.persistence; 18 19 import static com.android.internal.util.Preconditions.checkArgument; 20 import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE; 21 22 import com.android.modules.utils.TypedXmlPullParser; 23 24 import org.xmlpull.v1.XmlPullParser; 25 import org.xmlpull.v1.XmlPullParserException; 26 27 import java.io.IOException; 28 29 /** 30 * Helper methods for reading elements from a {@link XmlPullParser}. 31 * 32 * @hide 33 */ 34 public final class XmlReader { 35 36 /** 37 * Check parser is currently at {@link XmlPullParser#START_DOCUMENT} and that it has a start tag 38 * with expected root tag name. 39 * 40 * <p>The parser will be pointing to the root start tag found after this method. 41 */ readDocumentStartTag(TypedXmlPullParser parser, String expectedRootTag)42 public static void readDocumentStartTag(TypedXmlPullParser parser, String expectedRootTag) 43 throws XmlParserException, IOException { 44 readDocumentStart(parser); 45 46 String tagName = parser.getName(); 47 XmlValidator.checkParserCondition(expectedRootTag.equals(tagName), 48 "Unexpected root tag found %s, expected %s", tagName, expectedRootTag); 49 } 50 51 /** 52 * Check parser is currently at {@link XmlPullParser#START_DOCUMENT}. 53 * 54 * <p>The parser will be pointing to the first tag in the document. 55 */ readDocumentStart(TypedXmlPullParser parser)56 public static void readDocumentStart(TypedXmlPullParser parser) 57 throws XmlParserException, IOException { 58 try { 59 int type = parser.getEventType(); 60 checkArgument( 61 type == XmlPullParser.START_DOCUMENT, 62 "Unexpected type, expected %d", type); 63 parser.nextTag(); // skips comments, instruction tokens and whitespace only 64 } catch (XmlPullParserException e) { 65 throw XmlParserException.createFromPullParserException("document start tag", e); 66 } 67 } 68 69 /** 70 * Check parser is currently at {@link XmlPullParser#END_TAG} and that has the expected root tag 71 * name, and that the next tag is the {@link XmlPullParser#END_DOCUMENT} tag. 72 * 73 * <p>The parser will be pointing to the end document tag after this method. 74 */ readDocumentEndTag(TypedXmlPullParser parser)75 public static void readDocumentEndTag(TypedXmlPullParser parser) 76 throws XmlParserException, IOException { 77 try { 78 int type = parser.getEventType(); 79 XmlValidator.checkParserCondition(type == XmlPullParser.END_TAG, 80 "Unexpected element at document end, expected end of root tag"); 81 82 type = parser.next(); // skips comments and instruction tokens 83 if (type == XmlPullParser.TEXT && parser.isWhitespace()) { // skip whitespace only 84 type = parser.next(); 85 } 86 87 XmlValidator.checkParserCondition(type == XmlPullParser.END_DOCUMENT, 88 "Unexpected tag found %s, expected document end", parser.getName()); 89 } catch (XmlPullParserException e) { 90 throw XmlParserException.createFromPullParserException("document end tag", e); 91 } 92 } 93 94 /** 95 * Read the next tag and returns true if it's a {@link XmlPullParser#START_TAG} at depth 96 * {@code outerDepth + 1} or false if it's a {@link XmlPullParser#END_TAG} at 97 * {@code outerDepth}. Any other tag will fail this check. 98 * 99 * <p>The parser will be pointing to the next nested start tag when this method returns true, 100 * or to the end tag for given depth if it returns false. 101 * 102 * @return true if start tag found within given depth, false otherwise 103 */ readNextTagWithin(TypedXmlPullParser parser, int outerDepth)104 public static boolean readNextTagWithin(TypedXmlPullParser parser, int outerDepth) 105 throws XmlParserException, IOException { 106 int type; 107 try { 108 type = parser.getEventType(); 109 if (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth) { 110 // Already pointing to the end tag at outerDepth, just return before calling next. 111 return false; 112 } 113 114 type = parser.nextTag(); // skips comments, instruction tokens and whitespace only 115 } catch (XmlPullParserException e) { 116 throw XmlParserException.createFromPullParserException(parser.getName(), e); 117 } 118 119 if (type == XmlPullParser.START_TAG && parser.getDepth() == outerDepth + 1) { 120 return true; 121 } 122 123 // Next tag is not a start tag at outerDepth+1, expect it to be the end tag for outerDepth. 124 XmlValidator.checkParserCondition( 125 type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth, 126 "Unexpected tag found %s, expected end tag at depth %d", 127 parser.getName(), outerDepth); 128 129 return false; 130 } 131 132 /** 133 * Read the next element, ignoring comments and ignorable whitespace, and returns only if it's a 134 * {@link XmlPullParser#TEXT}. Any other tag will fail this check. 135 * 136 * <p>The parser will be pointing to the first next element after skipping comments, 137 * instructions and ignorable whitespace. 138 */ readNextText(TypedXmlPullParser parser, String tagName)139 public static void readNextText(TypedXmlPullParser parser, String tagName) 140 throws XmlParserException, IOException { 141 try { 142 int type = parser.next(); // skips comments, instruction tokens and ignorable whitespace 143 XmlValidator.checkParserCondition(type == XmlPullParser.TEXT, 144 "Unexpected event %s of type %d, expected text event inside tag %s", 145 parser.getName(), type, tagName); 146 } catch (XmlPullParserException e) { 147 throw XmlParserException.createFromPullParserException("text event", e); 148 } 149 } 150 151 /** 152 * Check parser has a {@link XmlPullParser#END_TAG} as the next tag, with no nested tags. 153 * 154 * <p>The parser will be pointing to the end tag after this method. 155 */ readEndTag(TypedXmlPullParser parser)156 public static void readEndTag(TypedXmlPullParser parser) 157 throws XmlParserException, IOException { 158 readEndTag(parser, parser.getName(), parser.getDepth()); 159 } 160 161 /** 162 * Check parser has a {@link XmlPullParser#END_TAG} with same {@code tagDepth} as the next tag, 163 * with no more nested start tags. 164 * 165 * <p>The parser will be pointing to the end tag after this method. 166 */ readEndTag(TypedXmlPullParser parser, String tagName, int tagDepth)167 public static void readEndTag(TypedXmlPullParser parser, String tagName, int tagDepth) 168 throws XmlParserException, IOException { 169 // Read nested tag first, so we can use the parser.getName() in the error message. 170 boolean hasNestedTag = readNextTagWithin(parser, tagDepth); 171 XmlValidator.checkParserCondition(!hasNestedTag, 172 "Unexpected nested tag %s found in tag %s", parser.getName(), tagName); 173 } 174 175 /** 176 * Read attribute from current tag as a non-negative integer, returning default value if 177 * attribute is missing. 178 */ readAttributeIntNonNegative( TypedXmlPullParser parser, String attrName, int defaultValue)179 public static int readAttributeIntNonNegative( 180 TypedXmlPullParser parser, String attrName, int defaultValue) 181 throws XmlParserException { 182 if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) { 183 return defaultValue; 184 } 185 return readAttributeIntNonNegative(parser, attrName); 186 } 187 188 /** Read attribute from current tag as a non-negative integer. */ readAttributeIntNonNegative(TypedXmlPullParser parser, String attrName)189 public static int readAttributeIntNonNegative(TypedXmlPullParser parser, String attrName) 190 throws XmlParserException { 191 String tagName = parser.getName(); 192 int value = readAttributeInt(parser, attrName); 193 194 XmlValidator.checkParserCondition(value >= 0, 195 "Unexpected %s = %d in tag %s, expected %s >= 0", 196 attrName, value, tagName, attrName); 197 return value; 198 } 199 200 /** Read attribute from current tag as an integer within given inclusive range. */ readAttributeIntInRange( TypedXmlPullParser parser, String attrName, int lowerInclusive, int upperInclusive)201 public static int readAttributeIntInRange( 202 TypedXmlPullParser parser, String attrName, int lowerInclusive, int upperInclusive) 203 throws XmlParserException { 204 String tagName = parser.getName(); 205 int value = readAttributeInt(parser, attrName); 206 207 XmlValidator.checkParserCondition( 208 value >= lowerInclusive && value <= upperInclusive, 209 "Unexpected %s = %d in tag %s, expected %s in [%d, %d]", 210 attrName, value, tagName, attrName, lowerInclusive, upperInclusive); 211 return value; 212 } 213 214 /** 215 * Read attribute from current tag as a float within given inclusive range, returning default 216 * value if attribute is missing. 217 */ readAttributeFloatInRange( TypedXmlPullParser parser, String attrName, float lowerInclusive, float upperInclusive, float defaultValue)218 public static float readAttributeFloatInRange( 219 TypedXmlPullParser parser, String attrName, float lowerInclusive, 220 float upperInclusive, float defaultValue) throws XmlParserException { 221 if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) { 222 return defaultValue; 223 } 224 225 return readAttributeFloatInRange(parser, attrName, lowerInclusive, upperInclusive); 226 } 227 228 /** 229 * Read attribute from current tag as a float within given inclusive range. 230 */ readAttributeFloatInRange( TypedXmlPullParser parser, String attrName, float lowerInclusive, float upperInclusive)231 public static float readAttributeFloatInRange( 232 TypedXmlPullParser parser, String attrName, float lowerInclusive, 233 float upperInclusive) throws XmlParserException { 234 String tagName = parser.getName(); 235 float value = readAttributeFloat(parser, attrName); 236 237 XmlValidator.checkParserCondition(value >= lowerInclusive && value <= upperInclusive, 238 "Unexpected %s = %f in tag %s, expected %s in [%f, %f]", attrName, value, tagName, 239 attrName, lowerInclusive, upperInclusive); 240 return value; 241 } 242 243 /** 244 * Read attribute from current tag as a positive float, returning default value if attribute 245 * is missing. 246 */ readAttributePositiveFloat(TypedXmlPullParser parser, String attrName, float defaultValue)247 public static float readAttributePositiveFloat(TypedXmlPullParser parser, String attrName, 248 float defaultValue) throws XmlParserException { 249 if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) { 250 return defaultValue; 251 } 252 253 return readAttributePositiveFloat(parser, attrName); 254 } 255 256 /** 257 * Read attribute from current tag as a positive float. 258 */ readAttributePositiveFloat(TypedXmlPullParser parser, String attrName)259 public static float readAttributePositiveFloat(TypedXmlPullParser parser, String attrName) 260 throws XmlParserException { 261 String tagName = parser.getName(); 262 float value = readAttributeFloat(parser, attrName); 263 264 XmlValidator.checkParserCondition(value > 0, 265 "Unexpected %s = %d in tag %s, expected %s > 0", attrName, value, tagName, 266 attrName); 267 return value; 268 } 269 270 /** 271 * Read attribute from current tag as a positive long. 272 */ readAttributePositiveLong(TypedXmlPullParser parser, String attrName)273 public static long readAttributePositiveLong(TypedXmlPullParser parser, String attrName) 274 throws XmlParserException { 275 String tagName = parser.getName(); 276 long value = readAttributeLong(parser, attrName); 277 278 XmlValidator.checkParserCondition(value > 0, 279 "Unexpected %s = %d in tag %s, expected %s > 0", attrName, value, tagName, 280 attrName); 281 return value; 282 } 283 readAttributeInt(TypedXmlPullParser parser, String attrName)284 private static int readAttributeInt(TypedXmlPullParser parser, String attrName) 285 throws XmlParserException { 286 String tagName = parser.getName(); 287 try { 288 return parser.getAttributeInt(NAMESPACE, attrName); 289 } catch (XmlPullParserException e) { 290 String rawValue = parser.getAttributeValue(NAMESPACE, attrName); 291 throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e); 292 } 293 } 294 readAttributeFloat(TypedXmlPullParser parser, String attrName)295 private static float readAttributeFloat(TypedXmlPullParser parser, String attrName) 296 throws XmlParserException { 297 String tagName = parser.getName(); 298 try { 299 return parser.getAttributeFloat(NAMESPACE, attrName); 300 } catch (XmlPullParserException e) { 301 String rawValue = parser.getAttributeValue(NAMESPACE, attrName); 302 throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e); 303 } 304 } 305 readAttributeLong(TypedXmlPullParser parser, String attrName)306 private static long readAttributeLong(TypedXmlPullParser parser, String attrName) 307 throws XmlParserException { 308 String tagName = parser.getName(); 309 try { 310 return parser.getAttributeLong(NAMESPACE, attrName); 311 } catch (XmlPullParserException e) { 312 String rawValue = parser.getAttributeValue(NAMESPACE, attrName); 313 throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e); 314 } 315 } 316 } 317