1 /* 2 * Copyright (C) 2021 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 androidx.constraintlayout.core.parser; 17 18 public class CLParser { 19 20 static boolean sDebug = false; 21 22 private String mContent; 23 private boolean mHasComment = false; 24 private int mLineNumber; 25 26 enum TYPE {UNKNOWN, OBJECT, ARRAY, NUMBER, STRING, KEY, TOKEN} 27 28 // @TODO: add description parse(String string)29 public static CLObject parse(String string) throws CLParsingException { 30 return new CLParser(string).parse(); 31 } 32 CLParser(String content)33 public CLParser(String content) { 34 mContent = content; 35 } 36 37 // @TODO: add description parse()38 public CLObject parse() throws CLParsingException { 39 @SuppressWarnings("unused") CLObject root = null; 40 41 char[] content = mContent.toCharArray(); 42 @SuppressWarnings("unused") CLElement currentElement = null; 43 44 final int length = content.length; 45 46 // First, let's find the root element start 47 mLineNumber = 1; 48 49 int startIndex = -1; 50 for (int i = 0; i < length; i++) { 51 char c = content[i]; 52 if (c == '{') { 53 startIndex = i; 54 break; 55 } 56 if (c == '\n') { 57 mLineNumber++; 58 } 59 } 60 if (startIndex == -1) { 61 throw new CLParsingException("invalid json content", null); 62 } 63 64 // We have a root object, let's start 65 root = CLObject.allocate(content); 66 root.setLine(mLineNumber); 67 root.setStart(startIndex); 68 currentElement = root; 69 70 for (int i = startIndex + 1; i < length; i++) { 71 char c = content[i]; 72 if (c == '\n') { 73 mLineNumber++; 74 } 75 if (mHasComment) { 76 if (c == '\n') { 77 mHasComment = false; 78 } else { 79 continue; 80 } 81 } 82 if (false) { 83 System.out.println("Looking at " + i + " : <" + c + ">"); 84 } 85 if (currentElement == null) { 86 break; 87 } 88 if (currentElement.isDone()) { 89 currentElement = getNextJsonElement(i, c, currentElement, content); 90 } else if (currentElement instanceof CLObject) { 91 if (c == '}') { 92 currentElement.setEnd(i - 1); 93 } else { 94 currentElement = getNextJsonElement(i, c, currentElement, content); 95 } 96 } else if (currentElement instanceof CLArray) { 97 if (c == ']') { 98 currentElement.setEnd(i - 1); 99 } else { 100 currentElement = getNextJsonElement(i, c, currentElement, content); 101 } 102 } else if (currentElement instanceof CLString) { 103 char ck = content[(int) currentElement.mStart]; 104 if (ck == c) { 105 currentElement.setStart(currentElement.mStart + 1); 106 currentElement.setEnd(i - 1); 107 } 108 } else { 109 if (currentElement instanceof CLToken) { 110 CLToken token = (CLToken) currentElement; 111 if (!token.validate(c, i)) { 112 throw new CLParsingException("parsing incorrect token " + token.content() 113 + " at line " + mLineNumber, token); 114 } 115 } 116 if (currentElement instanceof CLKey || currentElement instanceof CLString) { 117 char ck = content[(int) currentElement.mStart]; 118 if ((ck == '\'' || ck == '"') && ck == c) { 119 currentElement.setStart(currentElement.mStart + 1); 120 currentElement.setEnd(i - 1); 121 } 122 } 123 if (!currentElement.isDone()) { 124 if (c == '}' || c == ']' || c == ',' || c == ' ' 125 || c == '\t' || c == '\r' || c == '\n' || c == ':') { 126 currentElement.setEnd(i - 1); 127 if (c == '}' || c == ']') { 128 currentElement = currentElement.getContainer(); 129 currentElement.setEnd(i - 1); 130 if (currentElement instanceof CLKey) { 131 currentElement = currentElement.getContainer(); 132 currentElement.setEnd(i - 1); 133 } 134 } 135 } 136 } 137 } 138 139 if (currentElement.isDone() && (!(currentElement instanceof CLKey) 140 || ((CLKey) currentElement).mElements.size() > 0)) { 141 currentElement = currentElement.getContainer(); 142 } 143 } 144 145 // Close all open elements -- 146 // allow us to be more resistant to invalid json, useful during editing. 147 while (currentElement != null && !currentElement.isDone()) { 148 if (currentElement instanceof CLString) { 149 currentElement.setStart((int) currentElement.mStart + 1); 150 } 151 currentElement.setEnd(length - 1); 152 currentElement = currentElement.getContainer(); 153 } 154 155 if (sDebug) { 156 System.out.println("Root: " + root.toJSON()); 157 } 158 159 return root; 160 } 161 getNextJsonElement(int position, char c, CLElement currentElement, char[] content)162 private CLElement getNextJsonElement(int position, char c, CLElement currentElement, 163 char[] content) throws CLParsingException { 164 switch (c) { 165 case ' ': 166 case ':': 167 case ',': 168 case '\t': 169 case '\r': 170 case '\n': { 171 // skip space 172 } 173 break; 174 case '{': { 175 currentElement = createElement(currentElement, 176 position, TYPE.OBJECT, true, content); 177 } 178 break; 179 case '[': { 180 currentElement = createElement(currentElement, 181 position, TYPE.ARRAY, true, content); 182 } 183 break; 184 case ']': 185 case '}': { 186 currentElement.setEnd(position - 1); 187 currentElement = currentElement.getContainer(); 188 currentElement.setEnd(position); 189 } 190 break; 191 case '"': 192 case '\'': { 193 if (currentElement instanceof CLObject) { 194 currentElement = createElement(currentElement, 195 position, TYPE.KEY, true, content); 196 } else { 197 currentElement = createElement(currentElement, 198 position, TYPE.STRING, true, content); 199 } 200 } 201 break; 202 case '/': { 203 if (position + 1 < content.length && content[position + 1] == '/') { 204 mHasComment = true; 205 } 206 } 207 break; 208 case '-': 209 case '+': 210 case '.': 211 case '0': 212 case '1': 213 case '2': 214 case '3': 215 case '4': 216 case '5': 217 case '6': 218 case '7': 219 case '8': 220 case '9': { 221 currentElement = createElement(currentElement, 222 position, TYPE.NUMBER, true, content); 223 } 224 break; 225 default: { 226 if (currentElement instanceof CLContainer 227 && !(currentElement instanceof CLObject)) { 228 currentElement = createElement(currentElement, 229 position, TYPE.TOKEN, true, content); 230 CLToken token = (CLToken) currentElement; 231 if (!token.validate(c, position)) { 232 throw new CLParsingException("incorrect token <" 233 + c + "> at line " + mLineNumber, token); 234 } 235 } else { 236 currentElement = createElement(currentElement, 237 position, TYPE.KEY, true, content); 238 } 239 } 240 } 241 return currentElement; 242 } 243 createElement(CLElement currentElement, int position, TYPE type, boolean applyStart, char[] content)244 private CLElement createElement(CLElement currentElement, int position, 245 TYPE type, boolean applyStart, char[] content) { 246 CLElement newElement = null; 247 if (sDebug) { 248 System.out.println("CREATE " + type + " at " + content[position]); 249 } 250 switch (type) { 251 case OBJECT: { 252 newElement = CLObject.allocate(content); 253 position++; 254 } 255 break; 256 case ARRAY: { 257 newElement = CLArray.allocate(content); 258 position++; 259 } 260 break; 261 case STRING: { 262 newElement = CLString.allocate(content); 263 } 264 break; 265 case NUMBER: { 266 newElement = CLNumber.allocate(content); 267 } 268 break; 269 case KEY: { 270 newElement = CLKey.allocate(content); 271 } 272 break; 273 case TOKEN: { 274 newElement = CLToken.allocate(content); 275 } 276 break; 277 default: 278 break; 279 } 280 if (newElement == null) { 281 return null; 282 } 283 newElement.setLine(mLineNumber); 284 if (applyStart) { 285 newElement.setStart(position); 286 } 287 if (currentElement instanceof CLContainer) { 288 CLContainer container = (CLContainer) currentElement; 289 newElement.setContainer(container); 290 } 291 return newElement; 292 } 293 294 } 295