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 * Check parser has a {@link XmlPullParser#END_TAG} as the next tag, with no nested tags. 134 * 135 * <p>The parser will be pointing to the end tag after this method. 136 */ readEndTag(TypedXmlPullParser parser)137 public static void readEndTag(TypedXmlPullParser parser) 138 throws XmlParserException, IOException { 139 readEndTag(parser, parser.getName(), parser.getDepth()); 140 } 141 142 /** 143 * Check parser has a {@link XmlPullParser#END_TAG} with same {@code tagDepth} as the next tag, 144 * with no more nested start tags. 145 * 146 * <p>The parser will be pointing to the end tag after this method. 147 */ readEndTag(TypedXmlPullParser parser, String tagName, int tagDepth)148 public static void readEndTag(TypedXmlPullParser parser, String tagName, int tagDepth) 149 throws XmlParserException, IOException { 150 // Read nested tag first, so we can use the parser.getName() in the error message. 151 boolean hasNestedTag = readNextTagWithin(parser, tagDepth); 152 XmlValidator.checkParserCondition(!hasNestedTag, 153 "Unexpected nested tag %s found in tag %s", parser.getName(), tagName); 154 } 155 156 /** 157 * Read attribute from current tag as a non-negative integer, returning default value if 158 * attribute is missing. 159 */ readAttributeIntNonNegative( TypedXmlPullParser parser, String attrName, int defaultValue)160 public static int readAttributeIntNonNegative( 161 TypedXmlPullParser parser, String attrName, int defaultValue) 162 throws XmlParserException { 163 if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) { 164 return defaultValue; 165 } 166 return readAttributeIntNonNegative(parser, attrName); 167 } 168 169 /** Read attribute from current tag as a non-negative integer. */ readAttributeIntNonNegative(TypedXmlPullParser parser, String attrName)170 public static int readAttributeIntNonNegative(TypedXmlPullParser parser, String attrName) 171 throws XmlParserException { 172 String tagName = parser.getName(); 173 int value = readAttributeInt(parser, attrName); 174 175 XmlValidator.checkParserCondition(value >= 0, 176 "Unexpected %s = %d in tag %s, expected %s >= 0", 177 attrName, value, tagName, attrName); 178 return value; 179 } 180 181 /** Read attribute from current tag as an integer within given inclusive range. */ readAttributeIntInRange( TypedXmlPullParser parser, String attrName, int lowerInclusive, int upperInclusive)182 public static int readAttributeIntInRange( 183 TypedXmlPullParser parser, String attrName, int lowerInclusive, int upperInclusive) 184 throws XmlParserException { 185 String tagName = parser.getName(); 186 int value = readAttributeInt(parser, attrName); 187 188 XmlValidator.checkParserCondition( 189 value >= lowerInclusive && value <= upperInclusive, 190 "Unexpected %s = %d in tag %s, expected %s in [%d, %d]", 191 attrName, value, tagName, attrName, lowerInclusive, upperInclusive); 192 return value; 193 } 194 195 /** 196 * Read attribute from current tag as a float within given inclusive range, returning default 197 * value if attribute is missing. 198 */ readAttributeFloatInRange( TypedXmlPullParser parser, String attrName, float lowerInclusive, float upperInclusive, float defaultValue)199 public static float readAttributeFloatInRange( 200 TypedXmlPullParser parser, String attrName, float lowerInclusive, 201 float upperInclusive, float defaultValue) throws XmlParserException { 202 if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) { 203 return defaultValue; 204 } 205 String tagName = parser.getName(); 206 float value = readAttributeFloat(parser, attrName); 207 208 XmlValidator.checkParserCondition(value >= lowerInclusive && value <= upperInclusive, 209 "Unexpected %s = %f in tag %s, expected %s in [%f, %f]", 210 attrName, value, tagName, attrName, lowerInclusive, upperInclusive); 211 return value; 212 } 213 readAttributeInt(TypedXmlPullParser parser, String attrName)214 private static int readAttributeInt(TypedXmlPullParser parser, String attrName) 215 throws XmlParserException { 216 String tagName = parser.getName(); 217 try { 218 return parser.getAttributeInt(NAMESPACE, attrName); 219 } catch (XmlPullParserException e) { 220 String rawValue = parser.getAttributeValue(NAMESPACE, attrName); 221 throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e); 222 } 223 } 224 readAttributeFloat(TypedXmlPullParser parser, String attrName)225 private static float readAttributeFloat(TypedXmlPullParser parser, String attrName) 226 throws XmlParserException { 227 String tagName = parser.getName(); 228 try { 229 return parser.getAttributeFloat(NAMESPACE, attrName); 230 } catch (XmlPullParserException e) { 231 String rawValue = parser.getAttributeValue(NAMESPACE, attrName); 232 throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e); 233 } 234 } 235 } 236