blob: 51aed135a39e1afbdb991d859dc389f036bbacc6 [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
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
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080017#include "ResourceParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070018#include "ResourceTable.h"
19#include "ResourceUtils.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080020#include "ResourceValues.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070021#include "ValueVisitor.h"
Adam Lesinski7ff3ee12015-12-14 16:08:50 -080022#include "util/ImmutableMap.h"
Adam Lesinski9e10ac72015-10-16 14:37:48 -070023#include "util/Util.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080024#include "xml/XmlPullParser.h"
Adam Lesinski9e10ac72015-10-16 14:37:48 -070025
Adam Lesinski7ff3ee12015-12-14 16:08:50 -080026#include <functional>
Adam Lesinski769de982015-04-10 19:43:55 -070027#include <sstream>
28
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080029namespace aapt {
30
Adam Lesinskicacb28f2016-10-19 12:18:14 -070031constexpr const char* sXliffNamespaceUri =
32 "urn:oasis:names:tc:xliff:document:1.2";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080033
Adam Lesinski27afb9e2015-11-06 18:25:04 -080034/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -070035 * Returns true if the element is <skip> or <eat-comment> and can be safely
36 * ignored.
Adam Lesinski27afb9e2015-11-06 18:25:04 -080037 */
Adam Lesinskicacb28f2016-10-19 12:18:14 -070038static bool shouldIgnoreElement(const StringPiece& ns,
39 const StringPiece& name) {
40 return ns.empty() && (name == "skip" || name == "eat-comment");
Adam Lesinski27afb9e2015-11-06 18:25:04 -080041}
42
Adam Lesinskid0f116b2016-07-08 15:00:32 -070043static uint32_t parseFormatType(const StringPiece& piece) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070044 if (piece == "reference")
45 return android::ResTable_map::TYPE_REFERENCE;
46 else if (piece == "string")
47 return android::ResTable_map::TYPE_STRING;
48 else if (piece == "integer")
49 return android::ResTable_map::TYPE_INTEGER;
50 else if (piece == "boolean")
51 return android::ResTable_map::TYPE_BOOLEAN;
52 else if (piece == "color")
53 return android::ResTable_map::TYPE_COLOR;
54 else if (piece == "float")
55 return android::ResTable_map::TYPE_FLOAT;
56 else if (piece == "dimension")
57 return android::ResTable_map::TYPE_DIMENSION;
58 else if (piece == "fraction")
59 return android::ResTable_map::TYPE_FRACTION;
60 else if (piece == "enum")
61 return android::ResTable_map::TYPE_ENUM;
62 else if (piece == "flags")
63 return android::ResTable_map::TYPE_FLAGS;
64 return 0;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -080065}
66
Adam Lesinskid0f116b2016-07-08 15:00:32 -070067static uint32_t parseFormatAttribute(const StringPiece& str) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070068 uint32_t mask = 0;
69 for (StringPiece part : util::tokenize(str, '|')) {
70 StringPiece trimmedPart = util::trimWhitespace(part);
71 uint32_t type = parseFormatType(trimmedPart);
72 if (type == 0) {
73 return 0;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -080074 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -070075 mask |= type;
76 }
77 return mask;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -080078}
79
Adam Lesinski7ff3ee12015-12-14 16:08:50 -080080/**
81 * A parsed resource ready to be added to the ResourceTable.
82 */
83struct ParsedResource {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070084 ResourceName name;
85 ConfigDescription config;
86 std::string product;
87 Source source;
88 ResourceId id;
89 Maybe<SymbolState> symbolState;
90 std::string comment;
91 std::unique_ptr<Value> value;
92 std::list<ParsedResource> childResources;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -080093};
94
95// Recursively adds resources to the ResourceTable.
Adam Lesinskicacb28f2016-10-19 12:18:14 -070096static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag,
97 ParsedResource* res) {
98 StringPiece trimmedComment = util::trimWhitespace(res->comment);
99 if (trimmedComment.size() != res->comment.size()) {
100 // Only if there was a change do we re-assign.
101 res->comment = trimmedComment.toString();
102 }
Adam Lesinski7656554f2016-03-10 21:55:04 -0800103
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700104 if (res->symbolState) {
105 Symbol symbol;
106 symbol.state = res->symbolState.value();
107 symbol.source = res->source;
108 symbol.comment = res->comment;
109 if (!table->setSymbolState(res->name, res->id, symbol, diag)) {
110 return false;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800111 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700112 }
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800113
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700114 if (res->value) {
115 // Attach the comment, source and config to the value.
116 res->value->setComment(std::move(res->comment));
117 res->value->setSource(std::move(res->source));
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800118
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700119 if (!table->addResource(res->name, res->id, res->config, res->product,
120 std::move(res->value), diag)) {
121 return false;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800122 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700123 }
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800124
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700125 bool error = false;
126 for (ParsedResource& child : res->childResources) {
127 error |= !addResourcesToTable(table, diag, &child);
128 }
129 return !error;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800130}
131
132// Convenient aliases for more readable function calls.
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700133enum { kAllowRawString = true, kNoRawString = false };
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800134
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700135ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table,
136 const Source& source,
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700137 const ConfigDescription& config,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700138 const ResourceParserOptions& options)
139 : mDiag(diag),
140 mTable(table),
141 mSource(source),
142 mConfig(config),
143 mOptions(options) {}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800144
145/**
146 * Build a string from XML that converts nested elements into Span objects.
147 */
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700148bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser,
149 std::string* outRawString,
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800150 StyleString* outStyleString) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700151 std::vector<Span> spanStack;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800152
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700153 bool error = false;
154 outRawString->clear();
155 outStyleString->spans.clear();
156 util::StringBuilder builder;
157 size_t depth = 1;
158 while (xml::XmlPullParser::isGoodEvent(parser->next())) {
159 const xml::XmlPullParser::Event event = parser->getEvent();
160 if (event == xml::XmlPullParser::Event::kEndElement) {
161 if (!parser->getElementNamespace().empty()) {
162 // We already warned and skipped the start element, so just skip here
163 // too
164 continue;
165 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700166
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700167 depth--;
168 if (depth == 0) {
169 break;
170 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800171
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700172 spanStack.back().lastChar = builder.utf16Len() - 1;
173 outStyleString->spans.push_back(spanStack.back());
174 spanStack.pop_back();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800175
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700176 } else if (event == xml::XmlPullParser::Event::kText) {
177 outRawString->append(parser->getText());
178 builder.append(parser->getText());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800179
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700180 } else if (event == xml::XmlPullParser::Event::kStartElement) {
181 if (!parser->getElementNamespace().empty()) {
182 if (parser->getElementNamespace() != sXliffNamespaceUri) {
183 // Only warn if this isn't an xliff namespace.
184 mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
185 << "skipping element '" << parser->getElementName()
186 << "' with unknown namespace '"
187 << parser->getElementNamespace() << "'");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800188 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700189 continue;
190 }
191 depth++;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800192
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700193 // Build a span object out of the nested element.
194 std::string spanName = parser->getElementName();
195 const auto endAttrIter = parser->endAttributes();
196 for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter;
197 ++attrIter) {
198 spanName += ";";
199 spanName += attrIter->name;
200 spanName += "=";
201 spanName += attrIter->value;
202 }
203
204 if (builder.utf16Len() > std::numeric_limits<uint32_t>::max()) {
205 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
206 << "style string '" << builder.str() << "' is too long");
207 error = true;
208 } else {
209 spanStack.push_back(
210 Span{spanName, static_cast<uint32_t>(builder.utf16Len())});
211 }
212
213 } else if (event == xml::XmlPullParser::Event::kComment) {
214 // Skip
215 } else {
216 assert(false);
217 }
218 }
219 assert(spanStack.empty() && "spans haven't been fully processed");
220
221 outStyleString->str = builder.str();
222 return !error;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800223}
224
Adam Lesinski467f1712015-11-16 17:35:44 -0800225bool ResourceParser::parse(xml::XmlPullParser* parser) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700226 bool error = false;
227 const size_t depth = parser->getDepth();
228 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
229 if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
230 // Skip comments and text.
231 continue;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800232 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700233
234 if (!parser->getElementNamespace().empty() ||
235 parser->getElementName() != "resources") {
236 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
237 << "root element must be <resources>");
238 return false;
239 }
240
241 error |= !parseResources(parser);
242 break;
243 };
244
245 if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) {
246 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
247 << "xml parser error: " << parser->getLastError());
248 return false;
249 }
250 return !error;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800251}
252
Adam Lesinski467f1712015-11-16 17:35:44 -0800253bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700254 std::set<ResourceName> strippedResources;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700255
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700256 bool error = false;
257 std::string comment;
258 const size_t depth = parser->getDepth();
259 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
260 const xml::XmlPullParser::Event event = parser->getEvent();
261 if (event == xml::XmlPullParser::Event::kComment) {
262 comment = parser->getComment();
263 continue;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800264 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700265
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700266 if (event == xml::XmlPullParser::Event::kText) {
267 if (!util::trimWhitespace(parser->getText()).empty()) {
268 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
269 << "plain text not allowed here");
270 error = true;
271 }
272 continue;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700273 }
274
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700275 assert(event == xml::XmlPullParser::Event::kStartElement);
276
277 if (!parser->getElementNamespace().empty()) {
278 // Skip unknown namespace.
279 continue;
280 }
281
282 std::string elementName = parser->getElementName();
283 if (elementName == "skip" || elementName == "eat-comment") {
284 comment = "";
285 continue;
286 }
287
288 ParsedResource parsedResource;
289 parsedResource.config = mConfig;
290 parsedResource.source = mSource.withLine(parser->getLineNumber());
291 parsedResource.comment = std::move(comment);
292
293 // Extract the product name if it exists.
294 if (Maybe<StringPiece> maybeProduct =
295 xml::findNonEmptyAttribute(parser, "product")) {
296 parsedResource.product = maybeProduct.value().toString();
297 }
298
299 // Parse the resource regardless of product.
300 if (!parseResource(parser, &parsedResource)) {
301 error = true;
302 continue;
303 }
304
305 if (!addResourcesToTable(mTable, mDiag, &parsedResource)) {
306 error = true;
307 }
308 }
309
310 // Check that we included at least one variant of each stripped resource.
311 for (const ResourceName& strippedResource : strippedResources) {
312 if (!mTable->findResource(strippedResource)) {
313 // Failed to find the resource.
314 mDiag->error(DiagMessage(mSource)
315 << "resource '" << strippedResource
316 << "' "
317 "was filtered out but no product variant remains");
318 error = true;
319 }
320 }
321
322 return !error;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800323}
324
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700325bool ResourceParser::parseResource(xml::XmlPullParser* parser,
326 ParsedResource* outResource) {
327 struct ItemTypeFormat {
328 ResourceType type;
329 uint32_t format;
330 };
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800331
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700332 using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*,
333 ParsedResource*)>;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800334
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700335 static const auto elToItemMap =
336 ImmutableMap<std::string, ItemTypeFormat>::createPreSorted({
337 {"bool", {ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN}},
338 {"color", {ResourceType::kColor, android::ResTable_map::TYPE_COLOR}},
339 {"dimen",
340 {ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT |
341 android::ResTable_map::TYPE_FRACTION |
342 android::ResTable_map::TYPE_DIMENSION}},
343 {"drawable",
344 {ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR}},
345 {"fraction",
346 {ResourceType::kFraction,
347 android::ResTable_map::TYPE_FLOAT |
348 android::ResTable_map::TYPE_FRACTION |
349 android::ResTable_map::TYPE_DIMENSION}},
350 {"integer",
351 {ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER}},
352 {"string",
353 {ResourceType::kString, android::ResTable_map::TYPE_STRING}},
354 });
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800355
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700356 static const auto elToBagMap =
357 ImmutableMap<std::string, BagParseFunc>::createPreSorted({
358 {"add-resource", std::mem_fn(&ResourceParser::parseAddResource)},
359 {"array", std::mem_fn(&ResourceParser::parseArray)},
360 {"attr", std::mem_fn(&ResourceParser::parseAttr)},
361 {"declare-styleable",
362 std::mem_fn(&ResourceParser::parseDeclareStyleable)},
363 {"integer-array", std::mem_fn(&ResourceParser::parseIntegerArray)},
364 {"java-symbol", std::mem_fn(&ResourceParser::parseSymbol)},
365 {"plurals", std::mem_fn(&ResourceParser::parsePlural)},
366 {"public", std::mem_fn(&ResourceParser::parsePublic)},
367 {"public-group", std::mem_fn(&ResourceParser::parsePublicGroup)},
368 {"string-array", std::mem_fn(&ResourceParser::parseStringArray)},
369 {"style", std::mem_fn(&ResourceParser::parseStyle)},
370 {"symbol", std::mem_fn(&ResourceParser::parseSymbol)},
371 });
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800372
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700373 std::string resourceType = parser->getElementName();
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800374
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700375 // The value format accepted for this resource.
376 uint32_t resourceFormat = 0u;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800377
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700378 if (resourceType == "item") {
379 // Items have their type encoded in the type attribute.
380 if (Maybe<StringPiece> maybeType =
381 xml::findNonEmptyAttribute(parser, "type")) {
382 resourceType = maybeType.value().toString();
383 } else {
384 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
385 << "<item> must have a 'type' attribute");
386 return false;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800387 }
388
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700389 if (Maybe<StringPiece> maybeFormat =
390 xml::findNonEmptyAttribute(parser, "format")) {
391 // An explicit format for this resource was specified. The resource will
392 // retain
393 // its type in its name, but the accepted value for this type is
394 // overridden.
395 resourceFormat = parseFormatType(maybeFormat.value());
396 if (!resourceFormat) {
397 mDiag->error(DiagMessage(outResource->source)
398 << "'" << maybeFormat.value() << "' is an invalid format");
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800399 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700400 }
401 }
402 }
403
404 // Get the name of the resource. This will be checked later, because not all
405 // XML elements require a name.
406 Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
407
408 if (resourceType == "id") {
409 if (!maybeName) {
410 mDiag->error(DiagMessage(outResource->source)
411 << "<" << parser->getElementName()
412 << "> missing 'name' attribute");
413 return false;
414 }
415
416 outResource->name.type = ResourceType::kId;
417 outResource->name.entry = maybeName.value().toString();
418 outResource->value = util::make_unique<Id>();
419 return true;
420 }
421
422 const auto itemIter = elToItemMap.find(resourceType);
423 if (itemIter != elToItemMap.end()) {
424 // This is an item, record its type and format and start parsing.
425
426 if (!maybeName) {
427 mDiag->error(DiagMessage(outResource->source)
428 << "<" << parser->getElementName()
429 << "> missing 'name' attribute");
430 return false;
431 }
432
433 outResource->name.type = itemIter->second.type;
434 outResource->name.entry = maybeName.value().toString();
435
436 // Only use the implicit format for this type if it wasn't overridden.
437 if (!resourceFormat) {
438 resourceFormat = itemIter->second.format;
439 }
440
441 if (!parseItem(parser, outResource, resourceFormat)) {
442 return false;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800443 }
444 return true;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700445 }
446
447 // This might be a bag or something.
448 const auto bagIter = elToBagMap.find(resourceType);
449 if (bagIter != elToBagMap.end()) {
450 // Ensure we have a name (unless this is a <public-group>).
451 if (resourceType != "public-group") {
452 if (!maybeName) {
453 mDiag->error(DiagMessage(outResource->source)
454 << "<" << parser->getElementName()
455 << "> missing 'name' attribute");
456 return false;
457 }
458
459 outResource->name.entry = maybeName.value().toString();
460 }
461
462 // Call the associated parse method. The type will be filled in by the
463 // parse func.
464 if (!bagIter->second(this, parser, outResource)) {
465 return false;
466 }
467 return true;
468 }
469
470 // Try parsing the elementName (or type) as a resource. These shall only be
471 // resources like 'layout' or 'xml' and they can only be references.
472 const ResourceType* parsedType = parseResourceType(resourceType);
473 if (parsedType) {
474 if (!maybeName) {
475 mDiag->error(DiagMessage(outResource->source)
476 << "<" << parser->getElementName()
477 << "> missing 'name' attribute");
478 return false;
479 }
480
481 outResource->name.type = *parsedType;
482 outResource->name.entry = maybeName.value().toString();
483 outResource->value =
484 parseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
485 if (!outResource->value) {
486 mDiag->error(DiagMessage(outResource->source)
487 << "invalid value for type '" << *parsedType
488 << "'. Expected a reference");
489 return false;
490 }
491 return true;
492 }
493
494 mDiag->warn(DiagMessage(outResource->source)
495 << "unknown resource type '" << parser->getElementName() << "'");
496 return false;
497}
498
499bool ResourceParser::parseItem(xml::XmlPullParser* parser,
500 ParsedResource* outResource,
501 const uint32_t format) {
502 if (format == android::ResTable_map::TYPE_STRING) {
503 return parseString(parser, outResource);
504 }
505
506 outResource->value = parseXml(parser, format, kNoRawString);
507 if (!outResource->value) {
508 mDiag->error(DiagMessage(outResource->source) << "invalid "
509 << outResource->name.type);
510 return false;
511 }
512 return true;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -0800513}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800514
515/**
516 * Reads the entire XML subtree and attempts to parse it as some Item,
517 * with typeMask denoting which items it can be. If allowRawValue is
518 * true, a RawString is returned if the XML couldn't be parsed as
519 * an Item. If allowRawValue is false, nullptr is returned in this
520 * case.
521 */
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700522std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser,
523 const uint32_t typeMask,
Adam Lesinskie78fd612015-10-22 12:48:43 -0700524 const bool allowRawValue) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700525 const size_t beginXmlLine = parser->getLineNumber();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800526
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700527 std::string rawValue;
528 StyleString styleString;
529 if (!flattenXmlSubtree(parser, &rawValue, &styleString)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800530 return {};
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700531 }
532
533 if (!styleString.spans.empty()) {
534 // This can only be a StyledString.
535 return util::make_unique<StyledString>(mTable->stringPool.makeRef(
536 styleString, StringPool::Context{1, mConfig}));
537 }
538
539 auto onCreateReference = [&](const ResourceName& name) {
540 // name.package can be empty here, as it will assume the package name of the
541 // table.
542 std::unique_ptr<Id> id = util::make_unique<Id>();
543 id->setSource(mSource.withLine(beginXmlLine));
544 mTable->addResource(name, {}, {}, std::move(id), mDiag);
545 };
546
547 // Process the raw value.
548 std::unique_ptr<Item> processedItem = ResourceUtils::tryParseItemForAttribute(
549 rawValue, typeMask, onCreateReference);
550 if (processedItem) {
551 // Fix up the reference.
552 if (Reference* ref = valueCast<Reference>(processedItem.get())) {
553 transformReferenceFromNamespace(parser, "", ref);
554 }
555 return processedItem;
556 }
557
558 // Try making a regular string.
559 if (typeMask & android::ResTable_map::TYPE_STRING) {
560 // Use the trimmed, escaped string.
561 return util::make_unique<String>(mTable->stringPool.makeRef(
562 styleString.str, StringPool::Context{1, mConfig}));
563 }
564
565 if (allowRawValue) {
566 // We can't parse this so return a RawString if we are allowed.
567 return util::make_unique<RawString>(
568 mTable->stringPool.makeRef(rawValue, StringPool::Context{1, mConfig}));
569 }
570 return {};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800571}
572
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700573bool ResourceParser::parseString(xml::XmlPullParser* parser,
574 ParsedResource* outResource) {
575 bool formatted = true;
576 if (Maybe<StringPiece> formattedAttr =
577 xml::findAttribute(parser, "formatted")) {
578 Maybe<bool> maybeFormatted =
579 ResourceUtils::parseBool(formattedAttr.value());
580 if (!maybeFormatted) {
581 mDiag->error(DiagMessage(outResource->source)
582 << "invalid value for 'formatted'. Must be a boolean");
583 return false;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800584 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700585 formatted = maybeFormatted.value();
586 }
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800587
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700588 bool translateable = mOptions.translatable;
589 if (Maybe<StringPiece> translateableAttr =
590 xml::findAttribute(parser, "translatable")) {
591 Maybe<bool> maybeTranslateable =
592 ResourceUtils::parseBool(translateableAttr.value());
593 if (!maybeTranslateable) {
594 mDiag->error(DiagMessage(outResource->source)
595 << "invalid value for 'translatable'. Must be a boolean");
596 return false;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800597 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700598 translateable = maybeTranslateable.value();
599 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800600
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700601 outResource->value =
602 parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
603 if (!outResource->value) {
604 mDiag->error(DiagMessage(outResource->source) << "not a valid string");
605 return false;
606 }
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800607
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700608 if (String* stringValue = valueCast<String>(outResource->value.get())) {
609 stringValue->setTranslateable(translateable);
Adam Lesinski393b5f02015-12-17 13:03:11 -0800610
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700611 if (formatted && translateable) {
612 if (!util::verifyJavaStringFormat(*stringValue->value)) {
613 DiagMessage msg(outResource->source);
614 msg << "multiple substitutions specified in non-positional format; "
615 "did you mean to add the formatted=\"false\" attribute?";
616 if (mOptions.errorOnPositionalArguments) {
617 mDiag->error(msg);
618 return false;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800619 }
Adam Lesinski393b5f02015-12-17 13:03:11 -0800620
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700621 mDiag->warn(msg);
622 }
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800623 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700624
625 } else if (StyledString* stringValue =
626 valueCast<StyledString>(outResource->value.get())) {
627 stringValue->setTranslateable(translateable);
628 }
629 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800630}
631
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700632bool ResourceParser::parsePublic(xml::XmlPullParser* parser,
633 ParsedResource* outResource) {
634 Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
635 if (!maybeType) {
636 mDiag->error(DiagMessage(outResource->source)
637 << "<public> must have a 'type' attribute");
638 return false;
639 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800640
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700641 const ResourceType* parsedType = parseResourceType(maybeType.value());
642 if (!parsedType) {
643 mDiag->error(DiagMessage(outResource->source) << "invalid resource type '"
644 << maybeType.value()
645 << "' in <public>");
646 return false;
647 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800648
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700649 outResource->name.type = *parsedType;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800650
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700651 if (Maybe<StringPiece> maybeIdStr =
652 xml::findNonEmptyAttribute(parser, "id")) {
653 Maybe<ResourceId> maybeId =
654 ResourceUtils::parseResourceId(maybeIdStr.value());
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700655 if (!maybeId) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700656 mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '"
657 << maybeId.value()
658 << "' in <public>");
659 return false;
Adam Lesinski27afb9e2015-11-06 18:25:04 -0800660 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700661 outResource->id = maybeId.value();
662 }
Adam Lesinski27afb9e2015-11-06 18:25:04 -0800663
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700664 if (*parsedType == ResourceType::kId) {
665 // An ID marked as public is also the definition of an ID.
666 outResource->value = util::make_unique<Id>();
667 }
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700668
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700669 outResource->symbolState = SymbolState::kPublic;
670 return true;
Adam Lesinski27afb9e2015-11-06 18:25:04 -0800671}
672
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700673bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser,
674 ParsedResource* outResource) {
675 Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
676 if (!maybeType) {
677 mDiag->error(DiagMessage(outResource->source)
678 << "<public-group> must have a 'type' attribute");
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800679 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700680 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800681
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700682 const ResourceType* parsedType = parseResourceType(maybeType.value());
683 if (!parsedType) {
684 mDiag->error(DiagMessage(outResource->source) << "invalid resource type '"
685 << maybeType.value()
686 << "' in <public-group>");
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800687 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700688 }
689
690 Maybe<StringPiece> maybeIdStr =
691 xml::findNonEmptyAttribute(parser, "first-id");
692 if (!maybeIdStr) {
693 mDiag->error(DiagMessage(outResource->source)
694 << "<public-group> must have a 'first-id' attribute");
695 return false;
696 }
697
698 Maybe<ResourceId> maybeId =
699 ResourceUtils::parseResourceId(maybeIdStr.value());
700 if (!maybeId) {
701 mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '"
702 << maybeIdStr.value()
703 << "' in <public-group>");
704 return false;
705 }
706
707 ResourceId nextId = maybeId.value();
708
709 std::string comment;
710 bool error = false;
711 const size_t depth = parser->getDepth();
712 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
713 if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
714 comment = util::trimWhitespace(parser->getComment()).toString();
715 continue;
716 } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
717 // Skip text.
718 continue;
719 }
720
721 const Source itemSource = mSource.withLine(parser->getLineNumber());
722 const std::string& elementNamespace = parser->getElementNamespace();
723 const std::string& elementName = parser->getElementName();
724 if (elementNamespace.empty() && elementName == "public") {
725 Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
726 if (!maybeName) {
727 mDiag->error(DiagMessage(itemSource)
728 << "<public> must have a 'name' attribute");
729 error = true;
730 continue;
731 }
732
733 if (xml::findNonEmptyAttribute(parser, "id")) {
734 mDiag->error(DiagMessage(itemSource)
735 << "'id' is ignored within <public-group>");
736 error = true;
737 continue;
738 }
739
740 if (xml::findNonEmptyAttribute(parser, "type")) {
741 mDiag->error(DiagMessage(itemSource)
742 << "'type' is ignored within <public-group>");
743 error = true;
744 continue;
745 }
746
747 ParsedResource childResource;
748 childResource.name.type = *parsedType;
749 childResource.name.entry = maybeName.value().toString();
750 childResource.id = nextId;
751 childResource.comment = std::move(comment);
752 childResource.source = itemSource;
753 childResource.symbolState = SymbolState::kPublic;
754 outResource->childResources.push_back(std::move(childResource));
755
756 nextId.id += 1;
757
758 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
759 mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
760 error = true;
761 }
762 }
763 return !error;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800764}
765
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700766bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser,
767 ParsedResource* outResource) {
768 Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
769 if (!maybeType) {
770 mDiag->error(DiagMessage(outResource->source)
771 << "<" << parser->getElementName()
772 << "> must have a 'type' attribute");
773 return false;
774 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800775
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700776 const ResourceType* parsedType = parseResourceType(maybeType.value());
777 if (!parsedType) {
778 mDiag->error(DiagMessage(outResource->source)
779 << "invalid resource type '" << maybeType.value() << "' in <"
780 << parser->getElementName() << ">");
781 return false;
782 }
783
784 outResource->name.type = *parsedType;
785 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800786}
787
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700788bool ResourceParser::parseSymbol(xml::XmlPullParser* parser,
789 ParsedResource* outResource) {
790 if (parseSymbolImpl(parser, outResource)) {
791 outResource->symbolState = SymbolState::kPrivate;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700792 return true;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700793 }
794 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800795}
796
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700797bool ResourceParser::parseAddResource(xml::XmlPullParser* parser,
798 ParsedResource* outResource) {
799 if (parseSymbolImpl(parser, outResource)) {
800 outResource->symbolState = SymbolState::kUndefined;
801 return true;
802 }
803 return false;
804}
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700805
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700806bool ResourceParser::parseAttr(xml::XmlPullParser* parser,
807 ParsedResource* outResource) {
808 return parseAttrImpl(parser, outResource, false);
809}
810
811bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser,
812 ParsedResource* outResource, bool weak) {
813 outResource->name.type = ResourceType::kAttr;
814
815 // Attributes only end up in default configuration.
816 if (outResource->config != ConfigDescription::defaultConfig()) {
817 mDiag->warn(DiagMessage(outResource->source)
818 << "ignoring configuration '" << outResource->config
819 << "' for attribute " << outResource->name);
820 outResource->config = ConfigDescription::defaultConfig();
821 }
822
823 uint32_t typeMask = 0;
824
825 Maybe<StringPiece> maybeFormat = xml::findAttribute(parser, "format");
826 if (maybeFormat) {
827 typeMask = parseFormatAttribute(maybeFormat.value());
828 if (typeMask == 0) {
829 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
830 << "invalid attribute format '" << maybeFormat.value()
831 << "'");
832 return false;
833 }
834 }
835
836 Maybe<int32_t> maybeMin, maybeMax;
837
838 if (Maybe<StringPiece> maybeMinStr = xml::findAttribute(parser, "min")) {
839 StringPiece minStr = util::trimWhitespace(maybeMinStr.value());
840 if (!minStr.empty()) {
841 std::u16string minStr16 = util::utf8ToUtf16(minStr);
842 android::Res_value value;
843 if (android::ResTable::stringToInt(minStr16.data(), minStr16.size(),
844 &value)) {
845 maybeMin = static_cast<int32_t>(value.data);
846 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800847 }
848
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700849 if (!maybeMin) {
850 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
851 << "invalid 'min' value '" << minStr << "'");
852 return false;
853 }
854 }
855
856 if (Maybe<StringPiece> maybeMaxStr = xml::findAttribute(parser, "max")) {
857 StringPiece maxStr = util::trimWhitespace(maybeMaxStr.value());
858 if (!maxStr.empty()) {
859 std::u16string maxStr16 = util::utf8ToUtf16(maxStr);
860 android::Res_value value;
861 if (android::ResTable::stringToInt(maxStr16.data(), maxStr16.size(),
862 &value)) {
863 maybeMax = static_cast<int32_t>(value.data);
864 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800865 }
866
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700867 if (!maybeMax) {
868 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
869 << "invalid 'max' value '" << maxStr << "'");
870 return false;
871 }
872 }
873
874 if ((maybeMin || maybeMax) &&
875 (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) {
876 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
877 << "'min' and 'max' can only be used when format='integer'");
878 return false;
879 }
880
881 struct SymbolComparator {
882 bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
883 return a.symbol.name.value() < b.symbol.name.value();
884 }
885 };
886
887 std::set<Attribute::Symbol, SymbolComparator> items;
888
889 std::string comment;
890 bool error = false;
891 const size_t depth = parser->getDepth();
892 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
893 if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
894 comment = util::trimWhitespace(parser->getComment()).toString();
895 continue;
896 } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
897 // Skip text.
898 continue;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800899 }
900
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700901 const Source itemSource = mSource.withLine(parser->getLineNumber());
902 const std::string& elementNamespace = parser->getElementNamespace();
903 const std::string& elementName = parser->getElementName();
904 if (elementNamespace.empty() &&
905 (elementName == "flag" || elementName == "enum")) {
906 if (elementName == "enum") {
907 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
908 mDiag->error(DiagMessage(itemSource)
909 << "can not define an <enum>; already defined a <flag>");
910 error = true;
911 continue;
912 }
913 typeMask |= android::ResTable_map::TYPE_ENUM;
914
915 } else if (elementName == "flag") {
916 if (typeMask & android::ResTable_map::TYPE_ENUM) {
917 mDiag->error(DiagMessage(itemSource)
918 << "can not define a <flag>; already defined an <enum>");
919 error = true;
920 continue;
921 }
922 typeMask |= android::ResTable_map::TYPE_FLAGS;
923 }
924
925 if (Maybe<Attribute::Symbol> s =
926 parseEnumOrFlagItem(parser, elementName)) {
927 Attribute::Symbol& symbol = s.value();
928 ParsedResource childResource;
929 childResource.name = symbol.symbol.name.value();
930 childResource.source = itemSource;
931 childResource.value = util::make_unique<Id>();
932 outResource->childResources.push_back(std::move(childResource));
933
934 symbol.symbol.setComment(std::move(comment));
935 symbol.symbol.setSource(itemSource);
936
937 auto insertResult = items.insert(std::move(symbol));
938 if (!insertResult.second) {
939 const Attribute::Symbol& existingSymbol = *insertResult.first;
940 mDiag->error(DiagMessage(itemSource)
941 << "duplicate symbol '"
942 << existingSymbol.symbol.name.value().entry << "'");
943
944 mDiag->note(DiagMessage(existingSymbol.symbol.getSource())
945 << "first defined here");
946 error = true;
947 }
948 } else {
949 error = true;
950 }
951 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
952 mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
953 error = true;
954 }
955
956 comment = {};
957 }
958
959 if (error) {
960 return false;
961 }
962
963 std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
964 attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
965 attr->typeMask =
966 typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
967 if (maybeMin) {
968 attr->minInt = maybeMin.value();
969 }
970
971 if (maybeMax) {
972 attr->maxInt = maybeMax.value();
973 }
974 outResource->value = std::move(attr);
975 return true;
976}
977
978Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(
979 xml::XmlPullParser* parser, const StringPiece& tag) {
980 const Source source = mSource.withLine(parser->getLineNumber());
981
982 Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
983 if (!maybeName) {
984 mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <"
985 << tag << ">");
986 return {};
987 }
988
989 Maybe<StringPiece> maybeValue = xml::findNonEmptyAttribute(parser, "value");
990 if (!maybeValue) {
991 mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <"
992 << tag << ">");
993 return {};
994 }
995
996 std::u16string value16 = util::utf8ToUtf16(maybeValue.value());
997 android::Res_value val;
998 if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) {
999 mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value()
1000 << "' for <" << tag
1001 << ">; must be an integer");
1002 return {};
1003 }
1004
1005 return Attribute::Symbol{
1006 Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())),
1007 val.data};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001008}
1009
Adam Lesinski467f1712015-11-16 17:35:44 -08001010bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001011 const Source source = mSource.withLine(parser->getLineNumber());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001012
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001013 Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
1014 if (!maybeName) {
1015 mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
1016 return false;
1017 }
1018
1019 Maybe<Reference> maybeKey =
1020 ResourceUtils::parseXmlAttributeName(maybeName.value());
1021 if (!maybeKey) {
1022 mDiag->error(DiagMessage(source) << "invalid attribute name '"
1023 << maybeName.value() << "'");
1024 return false;
1025 }
1026
1027 transformReferenceFromNamespace(parser, "", &maybeKey.value());
1028 maybeKey.value().setSource(source);
1029
1030 std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
1031 if (!value) {
1032 mDiag->error(DiagMessage(source) << "could not parse style item");
1033 return false;
1034 }
1035
1036 style->entries.push_back(
1037 Style::Entry{std::move(maybeKey.value()), std::move(value)});
1038 return true;
1039}
1040
1041bool ResourceParser::parseStyle(xml::XmlPullParser* parser,
1042 ParsedResource* outResource) {
1043 outResource->name.type = ResourceType::kStyle;
1044
1045 std::unique_ptr<Style> style = util::make_unique<Style>();
1046
1047 Maybe<StringPiece> maybeParent = xml::findAttribute(parser, "parent");
1048 if (maybeParent) {
1049 // If the parent is empty, we don't have a parent, but we also don't infer
1050 // either.
1051 if (!maybeParent.value().empty()) {
1052 std::string errStr;
1053 style->parent = ResourceUtils::parseStyleParentReference(
1054 maybeParent.value(), &errStr);
1055 if (!style->parent) {
1056 mDiag->error(DiagMessage(outResource->source) << errStr);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001057 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001058 }
1059
1060 // Transform the namespace prefix to the actual package name, and mark the
1061 // reference as
1062 // private if appropriate.
1063 transformReferenceFromNamespace(parser, "", &style->parent.value());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001064 }
1065
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001066 } else {
1067 // No parent was specified, so try inferring it from the style name.
1068 std::string styleName = outResource->name.entry;
1069 size_t pos = styleName.find_last_of(u'.');
1070 if (pos != std::string::npos) {
1071 style->parentInferred = true;
1072 style->parent = Reference(
1073 ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos)));
1074 }
1075 }
1076
1077 bool error = false;
1078 const size_t depth = parser->getDepth();
1079 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1080 if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1081 // Skip text and comments.
1082 continue;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001083 }
1084
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001085 const std::string& elementNamespace = parser->getElementNamespace();
1086 const std::string& elementName = parser->getElementName();
1087 if (elementNamespace == "" && elementName == "item") {
1088 error |= !parseStyleItem(parser, style.get());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001089
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001090 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1091 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
1092 << ":" << elementName << ">");
1093 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001094 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001095 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001096
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001097 if (error) {
1098 return false;
1099 }
1100
1101 outResource->value = std::move(style);
1102 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001103}
1104
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001105bool ResourceParser::parseArray(xml::XmlPullParser* parser,
1106 ParsedResource* outResource) {
1107 return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001108}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001109
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001110bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser,
1111 ParsedResource* outResource) {
1112 return parseArrayImpl(parser, outResource,
1113 android::ResTable_map::TYPE_INTEGER);
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001114}
1115
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001116bool ResourceParser::parseStringArray(xml::XmlPullParser* parser,
1117 ParsedResource* outResource) {
1118 return parseArrayImpl(parser, outResource,
1119 android::ResTable_map::TYPE_STRING);
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001120}
1121
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001122bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser,
1123 ParsedResource* outResource,
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001124 const uint32_t typeMask) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001125 outResource->name.type = ResourceType::kArray;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001126
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001127 std::unique_ptr<Array> array = util::make_unique<Array>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001128
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001129 bool translateable = mOptions.translatable;
1130 if (Maybe<StringPiece> translateableAttr =
1131 xml::findAttribute(parser, "translatable")) {
1132 Maybe<bool> maybeTranslateable =
1133 ResourceUtils::parseBool(translateableAttr.value());
1134 if (!maybeTranslateable) {
1135 mDiag->error(DiagMessage(outResource->source)
1136 << "invalid value for 'translatable'. Must be a boolean");
1137 return false;
Adam Lesinski458b8772016-04-25 14:20:21 -07001138 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001139 translateable = maybeTranslateable.value();
1140 }
1141 array->setTranslateable(translateable);
Adam Lesinski458b8772016-04-25 14:20:21 -07001142
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001143 bool error = false;
1144 const size_t depth = parser->getDepth();
1145 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1146 if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1147 // Skip text and comments.
1148 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001149 }
1150
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001151 const Source itemSource = mSource.withLine(parser->getLineNumber());
1152 const std::string& elementNamespace = parser->getElementNamespace();
1153 const std::string& elementName = parser->getElementName();
1154 if (elementNamespace.empty() && elementName == "item") {
1155 std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
1156 if (!item) {
1157 mDiag->error(DiagMessage(itemSource) << "could not parse array item");
1158 error = true;
1159 continue;
1160 }
1161 item->setSource(itemSource);
1162 array->items.emplace_back(std::move(item));
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001163
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001164 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1165 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
1166 << "unknown tag <" << elementNamespace << ":" << elementName
1167 << ">");
1168 error = true;
1169 }
1170 }
1171
1172 if (error) {
1173 return false;
1174 }
1175
1176 outResource->value = std::move(array);
1177 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001178}
1179
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001180bool ResourceParser::parsePlural(xml::XmlPullParser* parser,
1181 ParsedResource* outResource) {
1182 outResource->name.type = ResourceType::kPlurals;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001183
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001184 std::unique_ptr<Plural> plural = util::make_unique<Plural>();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001185
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001186 bool error = false;
1187 const size_t depth = parser->getDepth();
1188 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1189 if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1190 // Skip text and comments.
1191 continue;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001192 }
1193
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001194 const Source itemSource = mSource.withLine(parser->getLineNumber());
1195 const std::string& elementNamespace = parser->getElementNamespace();
1196 const std::string& elementName = parser->getElementName();
1197 if (elementNamespace.empty() && elementName == "item") {
1198 Maybe<StringPiece> maybeQuantity =
1199 xml::findNonEmptyAttribute(parser, "quantity");
1200 if (!maybeQuantity) {
1201 mDiag->error(DiagMessage(itemSource)
1202 << "<item> in <plurals> requires attribute "
1203 << "'quantity'");
1204 error = true;
1205 continue;
1206 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001207
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001208 StringPiece trimmedQuantity = util::trimWhitespace(maybeQuantity.value());
1209 size_t index = 0;
1210 if (trimmedQuantity == "zero") {
1211 index = Plural::Zero;
1212 } else if (trimmedQuantity == "one") {
1213 index = Plural::One;
1214 } else if (trimmedQuantity == "two") {
1215 index = Plural::Two;
1216 } else if (trimmedQuantity == "few") {
1217 index = Plural::Few;
1218 } else if (trimmedQuantity == "many") {
1219 index = Plural::Many;
1220 } else if (trimmedQuantity == "other") {
1221 index = Plural::Other;
1222 } else {
1223 mDiag->error(DiagMessage(itemSource)
1224 << "<item> in <plural> has invalid value '"
1225 << trimmedQuantity << "' for attribute 'quantity'");
1226 error = true;
1227 continue;
1228 }
1229
1230 if (plural->values[index]) {
1231 mDiag->error(DiagMessage(itemSource) << "duplicate quantity '"
1232 << trimmedQuantity << "'");
1233 error = true;
1234 continue;
1235 }
1236
1237 if (!(plural->values[index] = parseXml(
1238 parser, android::ResTable_map::TYPE_STRING, kNoRawString))) {
1239 error = true;
1240 }
1241 plural->values[index]->setSource(itemSource);
1242
1243 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1244 mDiag->error(DiagMessage(itemSource)
1245 << "unknown tag <" << elementNamespace << ":" << elementName
1246 << ">");
1247 error = true;
1248 }
1249 }
1250
1251 if (error) {
1252 return false;
1253 }
1254
1255 outResource->value = std::move(plural);
1256 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001257}
1258
Adam Lesinski52364f72016-01-11 13:10:24 -08001259bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser,
1260 ParsedResource* outResource) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001261 outResource->name.type = ResourceType::kStyleable;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001262
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001263 // Declare-styleable is kPrivate by default, because it technically only
1264 // exists in R.java.
1265 outResource->symbolState = SymbolState::kPublic;
Adam Lesinski9f222042015-11-04 13:51:45 -08001266
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001267 // Declare-styleable only ends up in default config;
1268 if (outResource->config != ConfigDescription::defaultConfig()) {
1269 mDiag->warn(DiagMessage(outResource->source)
1270 << "ignoring configuration '" << outResource->config
1271 << "' for styleable " << outResource->name.entry);
1272 outResource->config = ConfigDescription::defaultConfig();
1273 }
1274
1275 std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
1276
1277 std::string comment;
1278 bool error = false;
1279 const size_t depth = parser->getDepth();
1280 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1281 if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
1282 comment = util::trimWhitespace(parser->getComment()).toString();
1283 continue;
1284 } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1285 // Ignore text.
1286 continue;
Adam Lesinski52364f72016-01-11 13:10:24 -08001287 }
1288
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001289 const Source itemSource = mSource.withLine(parser->getLineNumber());
1290 const std::string& elementNamespace = parser->getElementNamespace();
1291 const std::string& elementName = parser->getElementName();
1292 if (elementNamespace.empty() && elementName == "attr") {
1293 Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
1294 if (!maybeName) {
1295 mDiag->error(DiagMessage(itemSource)
1296 << "<attr> tag must have a 'name' attribute");
1297 error = true;
1298 continue;
1299 }
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001300
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001301 // If this is a declaration, the package name may be in the name. Separate
1302 // these out.
1303 // Eg. <attr name="android:text" />
1304 Maybe<Reference> maybeRef =
1305 ResourceUtils::parseXmlAttributeName(maybeName.value());
1306 if (!maybeRef) {
1307 mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
1308 << maybeName.value() << "'");
1309 error = true;
1310 continue;
1311 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001312
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001313 Reference& childRef = maybeRef.value();
1314 xml::transformReferenceFromNamespace(parser, "", &childRef);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001315
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001316 // Create the ParsedResource that will add the attribute to the table.
1317 ParsedResource childResource;
1318 childResource.name = childRef.name.value();
1319 childResource.source = itemSource;
1320 childResource.comment = std::move(comment);
Adam Lesinski467f1712015-11-16 17:35:44 -08001321
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001322 if (!parseAttrImpl(parser, &childResource, true)) {
1323 error = true;
1324 continue;
1325 }
Adam Lesinski467f1712015-11-16 17:35:44 -08001326
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001327 // Create the reference to this attribute.
1328 childRef.setComment(childResource.comment);
1329 childRef.setSource(itemSource);
1330 styleable->entries.push_back(std::move(childRef));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001331
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001332 outResource->childResources.push_back(std::move(childResource));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001333
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001334 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1335 mDiag->error(DiagMessage(itemSource)
1336 << "unknown tag <" << elementNamespace << ":" << elementName
1337 << ">");
1338 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001339 }
1340
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001341 comment = {};
1342 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001343
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001344 if (error) {
1345 return false;
1346 }
1347
1348 outResource->value = std::move(styleable);
1349 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001350}
1351
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001352} // namespace aapt