blob: 7d50e1d388164360d2a9987a28526a1c8884bb8b [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(
Adam Lesinskib54ef102016-10-21 13:38:42 -0700536 styleString,
537 StringPool::Context(StringPool::Context::kStylePriority, mConfig)));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700538 }
539
540 auto onCreateReference = [&](const ResourceName& name) {
541 // name.package can be empty here, as it will assume the package name of the
542 // table.
543 std::unique_ptr<Id> id = util::make_unique<Id>();
544 id->setSource(mSource.withLine(beginXmlLine));
545 mTable->addResource(name, {}, {}, std::move(id), mDiag);
546 };
547
548 // Process the raw value.
549 std::unique_ptr<Item> processedItem = ResourceUtils::tryParseItemForAttribute(
550 rawValue, typeMask, onCreateReference);
551 if (processedItem) {
552 // Fix up the reference.
553 if (Reference* ref = valueCast<Reference>(processedItem.get())) {
554 transformReferenceFromNamespace(parser, "", ref);
555 }
556 return processedItem;
557 }
558
559 // Try making a regular string.
560 if (typeMask & android::ResTable_map::TYPE_STRING) {
561 // Use the trimmed, escaped string.
562 return util::make_unique<String>(mTable->stringPool.makeRef(
Adam Lesinskib54ef102016-10-21 13:38:42 -0700563 styleString.str, StringPool::Context(mConfig)));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700564 }
565
566 if (allowRawValue) {
567 // We can't parse this so return a RawString if we are allowed.
568 return util::make_unique<RawString>(
Adam Lesinskib54ef102016-10-21 13:38:42 -0700569 mTable->stringPool.makeRef(rawValue, StringPool::Context(mConfig)));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700570 }
571 return {};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800572}
573
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700574bool ResourceParser::parseString(xml::XmlPullParser* parser,
575 ParsedResource* outResource) {
576 bool formatted = true;
577 if (Maybe<StringPiece> formattedAttr =
578 xml::findAttribute(parser, "formatted")) {
579 Maybe<bool> maybeFormatted =
580 ResourceUtils::parseBool(formattedAttr.value());
581 if (!maybeFormatted) {
582 mDiag->error(DiagMessage(outResource->source)
583 << "invalid value for 'formatted'. Must be a boolean");
584 return false;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800585 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700586 formatted = maybeFormatted.value();
587 }
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800588
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700589 bool translateable = mOptions.translatable;
590 if (Maybe<StringPiece> translateableAttr =
591 xml::findAttribute(parser, "translatable")) {
592 Maybe<bool> maybeTranslateable =
593 ResourceUtils::parseBool(translateableAttr.value());
594 if (!maybeTranslateable) {
595 mDiag->error(DiagMessage(outResource->source)
596 << "invalid value for 'translatable'. Must be a boolean");
597 return false;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800598 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700599 translateable = maybeTranslateable.value();
600 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800601
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700602 outResource->value =
603 parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
604 if (!outResource->value) {
605 mDiag->error(DiagMessage(outResource->source) << "not a valid string");
606 return false;
607 }
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800608
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700609 if (String* stringValue = valueCast<String>(outResource->value.get())) {
610 stringValue->setTranslateable(translateable);
Adam Lesinski393b5f02015-12-17 13:03:11 -0800611
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700612 if (formatted && translateable) {
613 if (!util::verifyJavaStringFormat(*stringValue->value)) {
614 DiagMessage msg(outResource->source);
615 msg << "multiple substitutions specified in non-positional format; "
616 "did you mean to add the formatted=\"false\" attribute?";
617 if (mOptions.errorOnPositionalArguments) {
618 mDiag->error(msg);
619 return false;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800620 }
Adam Lesinski393b5f02015-12-17 13:03:11 -0800621
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700622 mDiag->warn(msg);
623 }
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800624 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700625
626 } else if (StyledString* stringValue =
627 valueCast<StyledString>(outResource->value.get())) {
628 stringValue->setTranslateable(translateable);
629 }
630 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800631}
632
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700633bool ResourceParser::parsePublic(xml::XmlPullParser* parser,
634 ParsedResource* outResource) {
635 Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
636 if (!maybeType) {
637 mDiag->error(DiagMessage(outResource->source)
638 << "<public> must have a 'type' attribute");
639 return false;
640 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800641
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700642 const ResourceType* parsedType = parseResourceType(maybeType.value());
643 if (!parsedType) {
644 mDiag->error(DiagMessage(outResource->source) << "invalid resource type '"
645 << maybeType.value()
646 << "' in <public>");
647 return false;
648 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800649
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700650 outResource->name.type = *parsedType;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800651
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700652 if (Maybe<StringPiece> maybeIdStr =
653 xml::findNonEmptyAttribute(parser, "id")) {
654 Maybe<ResourceId> maybeId =
655 ResourceUtils::parseResourceId(maybeIdStr.value());
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700656 if (!maybeId) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700657 mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '"
658 << maybeId.value()
659 << "' in <public>");
660 return false;
Adam Lesinski27afb9e2015-11-06 18:25:04 -0800661 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700662 outResource->id = maybeId.value();
663 }
Adam Lesinski27afb9e2015-11-06 18:25:04 -0800664
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700665 if (*parsedType == ResourceType::kId) {
666 // An ID marked as public is also the definition of an ID.
667 outResource->value = util::make_unique<Id>();
668 }
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700669
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700670 outResource->symbolState = SymbolState::kPublic;
671 return true;
Adam Lesinski27afb9e2015-11-06 18:25:04 -0800672}
673
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700674bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser,
675 ParsedResource* outResource) {
676 Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
677 if (!maybeType) {
678 mDiag->error(DiagMessage(outResource->source)
679 << "<public-group> must have a 'type' attribute");
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800680 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700681 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800682
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700683 const ResourceType* parsedType = parseResourceType(maybeType.value());
684 if (!parsedType) {
685 mDiag->error(DiagMessage(outResource->source) << "invalid resource type '"
686 << maybeType.value()
687 << "' in <public-group>");
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800688 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700689 }
690
691 Maybe<StringPiece> maybeIdStr =
692 xml::findNonEmptyAttribute(parser, "first-id");
693 if (!maybeIdStr) {
694 mDiag->error(DiagMessage(outResource->source)
695 << "<public-group> must have a 'first-id' attribute");
696 return false;
697 }
698
699 Maybe<ResourceId> maybeId =
700 ResourceUtils::parseResourceId(maybeIdStr.value());
701 if (!maybeId) {
702 mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '"
703 << maybeIdStr.value()
704 << "' in <public-group>");
705 return false;
706 }
707
708 ResourceId nextId = maybeId.value();
709
710 std::string comment;
711 bool error = false;
712 const size_t depth = parser->getDepth();
713 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
714 if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
715 comment = util::trimWhitespace(parser->getComment()).toString();
716 continue;
717 } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
718 // Skip text.
719 continue;
720 }
721
722 const Source itemSource = mSource.withLine(parser->getLineNumber());
723 const std::string& elementNamespace = parser->getElementNamespace();
724 const std::string& elementName = parser->getElementName();
725 if (elementNamespace.empty() && elementName == "public") {
726 Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
727 if (!maybeName) {
728 mDiag->error(DiagMessage(itemSource)
729 << "<public> must have a 'name' attribute");
730 error = true;
731 continue;
732 }
733
734 if (xml::findNonEmptyAttribute(parser, "id")) {
735 mDiag->error(DiagMessage(itemSource)
736 << "'id' is ignored within <public-group>");
737 error = true;
738 continue;
739 }
740
741 if (xml::findNonEmptyAttribute(parser, "type")) {
742 mDiag->error(DiagMessage(itemSource)
743 << "'type' is ignored within <public-group>");
744 error = true;
745 continue;
746 }
747
748 ParsedResource childResource;
749 childResource.name.type = *parsedType;
750 childResource.name.entry = maybeName.value().toString();
751 childResource.id = nextId;
752 childResource.comment = std::move(comment);
753 childResource.source = itemSource;
754 childResource.symbolState = SymbolState::kPublic;
755 outResource->childResources.push_back(std::move(childResource));
756
757 nextId.id += 1;
758
759 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
760 mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
761 error = true;
762 }
763 }
764 return !error;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800765}
766
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700767bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser,
768 ParsedResource* outResource) {
769 Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type");
770 if (!maybeType) {
771 mDiag->error(DiagMessage(outResource->source)
772 << "<" << parser->getElementName()
773 << "> must have a 'type' attribute");
774 return false;
775 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800776
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700777 const ResourceType* parsedType = parseResourceType(maybeType.value());
778 if (!parsedType) {
779 mDiag->error(DiagMessage(outResource->source)
780 << "invalid resource type '" << maybeType.value() << "' in <"
781 << parser->getElementName() << ">");
782 return false;
783 }
784
785 outResource->name.type = *parsedType;
786 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800787}
788
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700789bool ResourceParser::parseSymbol(xml::XmlPullParser* parser,
790 ParsedResource* outResource) {
791 if (parseSymbolImpl(parser, outResource)) {
792 outResource->symbolState = SymbolState::kPrivate;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700793 return true;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700794 }
795 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800796}
797
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700798bool ResourceParser::parseAddResource(xml::XmlPullParser* parser,
799 ParsedResource* outResource) {
800 if (parseSymbolImpl(parser, outResource)) {
801 outResource->symbolState = SymbolState::kUndefined;
802 return true;
803 }
804 return false;
805}
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700806
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700807bool ResourceParser::parseAttr(xml::XmlPullParser* parser,
808 ParsedResource* outResource) {
809 return parseAttrImpl(parser, outResource, false);
810}
811
812bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser,
813 ParsedResource* outResource, bool weak) {
814 outResource->name.type = ResourceType::kAttr;
815
816 // Attributes only end up in default configuration.
817 if (outResource->config != ConfigDescription::defaultConfig()) {
818 mDiag->warn(DiagMessage(outResource->source)
819 << "ignoring configuration '" << outResource->config
820 << "' for attribute " << outResource->name);
821 outResource->config = ConfigDescription::defaultConfig();
822 }
823
824 uint32_t typeMask = 0;
825
826 Maybe<StringPiece> maybeFormat = xml::findAttribute(parser, "format");
827 if (maybeFormat) {
828 typeMask = parseFormatAttribute(maybeFormat.value());
829 if (typeMask == 0) {
830 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
831 << "invalid attribute format '" << maybeFormat.value()
832 << "'");
833 return false;
834 }
835 }
836
837 Maybe<int32_t> maybeMin, maybeMax;
838
839 if (Maybe<StringPiece> maybeMinStr = xml::findAttribute(parser, "min")) {
840 StringPiece minStr = util::trimWhitespace(maybeMinStr.value());
841 if (!minStr.empty()) {
842 std::u16string minStr16 = util::utf8ToUtf16(minStr);
843 android::Res_value value;
844 if (android::ResTable::stringToInt(minStr16.data(), minStr16.size(),
845 &value)) {
846 maybeMin = static_cast<int32_t>(value.data);
847 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800848 }
849
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700850 if (!maybeMin) {
851 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
852 << "invalid 'min' value '" << minStr << "'");
853 return false;
854 }
855 }
856
857 if (Maybe<StringPiece> maybeMaxStr = xml::findAttribute(parser, "max")) {
858 StringPiece maxStr = util::trimWhitespace(maybeMaxStr.value());
859 if (!maxStr.empty()) {
860 std::u16string maxStr16 = util::utf8ToUtf16(maxStr);
861 android::Res_value value;
862 if (android::ResTable::stringToInt(maxStr16.data(), maxStr16.size(),
863 &value)) {
864 maybeMax = static_cast<int32_t>(value.data);
865 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800866 }
867
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700868 if (!maybeMax) {
869 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
870 << "invalid 'max' value '" << maxStr << "'");
871 return false;
872 }
873 }
874
875 if ((maybeMin || maybeMax) &&
876 (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) {
877 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
878 << "'min' and 'max' can only be used when format='integer'");
879 return false;
880 }
881
882 struct SymbolComparator {
883 bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
884 return a.symbol.name.value() < b.symbol.name.value();
885 }
886 };
887
888 std::set<Attribute::Symbol, SymbolComparator> items;
889
890 std::string comment;
891 bool error = false;
892 const size_t depth = parser->getDepth();
893 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
894 if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
895 comment = util::trimWhitespace(parser->getComment()).toString();
896 continue;
897 } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
898 // Skip text.
899 continue;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800900 }
901
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700902 const Source itemSource = mSource.withLine(parser->getLineNumber());
903 const std::string& elementNamespace = parser->getElementNamespace();
904 const std::string& elementName = parser->getElementName();
905 if (elementNamespace.empty() &&
906 (elementName == "flag" || elementName == "enum")) {
907 if (elementName == "enum") {
908 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
909 mDiag->error(DiagMessage(itemSource)
910 << "can not define an <enum>; already defined a <flag>");
911 error = true;
912 continue;
913 }
914 typeMask |= android::ResTable_map::TYPE_ENUM;
915
916 } else if (elementName == "flag") {
917 if (typeMask & android::ResTable_map::TYPE_ENUM) {
918 mDiag->error(DiagMessage(itemSource)
919 << "can not define a <flag>; already defined an <enum>");
920 error = true;
921 continue;
922 }
923 typeMask |= android::ResTable_map::TYPE_FLAGS;
924 }
925
926 if (Maybe<Attribute::Symbol> s =
927 parseEnumOrFlagItem(parser, elementName)) {
928 Attribute::Symbol& symbol = s.value();
929 ParsedResource childResource;
930 childResource.name = symbol.symbol.name.value();
931 childResource.source = itemSource;
932 childResource.value = util::make_unique<Id>();
933 outResource->childResources.push_back(std::move(childResource));
934
935 symbol.symbol.setComment(std::move(comment));
936 symbol.symbol.setSource(itemSource);
937
938 auto insertResult = items.insert(std::move(symbol));
939 if (!insertResult.second) {
940 const Attribute::Symbol& existingSymbol = *insertResult.first;
941 mDiag->error(DiagMessage(itemSource)
942 << "duplicate symbol '"
943 << existingSymbol.symbol.name.value().entry << "'");
944
945 mDiag->note(DiagMessage(existingSymbol.symbol.getSource())
946 << "first defined here");
947 error = true;
948 }
949 } else {
950 error = true;
951 }
952 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
953 mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
954 error = true;
955 }
956
957 comment = {};
958 }
959
960 if (error) {
961 return false;
962 }
963
964 std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
965 attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
966 attr->typeMask =
967 typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
968 if (maybeMin) {
969 attr->minInt = maybeMin.value();
970 }
971
972 if (maybeMax) {
973 attr->maxInt = maybeMax.value();
974 }
975 outResource->value = std::move(attr);
976 return true;
977}
978
979Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(
980 xml::XmlPullParser* parser, const StringPiece& tag) {
981 const Source source = mSource.withLine(parser->getLineNumber());
982
983 Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
984 if (!maybeName) {
985 mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <"
986 << tag << ">");
987 return {};
988 }
989
990 Maybe<StringPiece> maybeValue = xml::findNonEmptyAttribute(parser, "value");
991 if (!maybeValue) {
992 mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <"
993 << tag << ">");
994 return {};
995 }
996
997 std::u16string value16 = util::utf8ToUtf16(maybeValue.value());
998 android::Res_value val;
999 if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) {
1000 mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value()
1001 << "' for <" << tag
1002 << ">; must be an integer");
1003 return {};
1004 }
1005
1006 return Attribute::Symbol{
1007 Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())),
1008 val.data};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001009}
1010
Adam Lesinski467f1712015-11-16 17:35:44 -08001011bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001012 const Source source = mSource.withLine(parser->getLineNumber());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001013
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001014 Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
1015 if (!maybeName) {
1016 mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
1017 return false;
1018 }
1019
1020 Maybe<Reference> maybeKey =
1021 ResourceUtils::parseXmlAttributeName(maybeName.value());
1022 if (!maybeKey) {
1023 mDiag->error(DiagMessage(source) << "invalid attribute name '"
1024 << maybeName.value() << "'");
1025 return false;
1026 }
1027
1028 transformReferenceFromNamespace(parser, "", &maybeKey.value());
1029 maybeKey.value().setSource(source);
1030
1031 std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
1032 if (!value) {
1033 mDiag->error(DiagMessage(source) << "could not parse style item");
1034 return false;
1035 }
1036
1037 style->entries.push_back(
1038 Style::Entry{std::move(maybeKey.value()), std::move(value)});
1039 return true;
1040}
1041
1042bool ResourceParser::parseStyle(xml::XmlPullParser* parser,
1043 ParsedResource* outResource) {
1044 outResource->name.type = ResourceType::kStyle;
1045
1046 std::unique_ptr<Style> style = util::make_unique<Style>();
1047
1048 Maybe<StringPiece> maybeParent = xml::findAttribute(parser, "parent");
1049 if (maybeParent) {
1050 // If the parent is empty, we don't have a parent, but we also don't infer
1051 // either.
1052 if (!maybeParent.value().empty()) {
1053 std::string errStr;
1054 style->parent = ResourceUtils::parseStyleParentReference(
1055 maybeParent.value(), &errStr);
1056 if (!style->parent) {
1057 mDiag->error(DiagMessage(outResource->source) << errStr);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001058 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001059 }
1060
1061 // Transform the namespace prefix to the actual package name, and mark the
1062 // reference as
1063 // private if appropriate.
1064 transformReferenceFromNamespace(parser, "", &style->parent.value());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001065 }
1066
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001067 } else {
1068 // No parent was specified, so try inferring it from the style name.
1069 std::string styleName = outResource->name.entry;
1070 size_t pos = styleName.find_last_of(u'.');
1071 if (pos != std::string::npos) {
1072 style->parentInferred = true;
1073 style->parent = Reference(
1074 ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos)));
1075 }
1076 }
1077
1078 bool error = false;
1079 const size_t depth = parser->getDepth();
1080 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1081 if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1082 // Skip text and comments.
1083 continue;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001084 }
1085
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001086 const std::string& elementNamespace = parser->getElementNamespace();
1087 const std::string& elementName = parser->getElementName();
1088 if (elementNamespace == "" && elementName == "item") {
1089 error |= !parseStyleItem(parser, style.get());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001090
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001091 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1092 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
1093 << ":" << elementName << ">");
1094 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001095 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001096 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001097
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001098 if (error) {
1099 return false;
1100 }
1101
1102 outResource->value = std::move(style);
1103 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001104}
1105
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001106bool ResourceParser::parseArray(xml::XmlPullParser* parser,
1107 ParsedResource* outResource) {
1108 return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001109}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001110
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001111bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser,
1112 ParsedResource* outResource) {
1113 return parseArrayImpl(parser, outResource,
1114 android::ResTable_map::TYPE_INTEGER);
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001115}
1116
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001117bool ResourceParser::parseStringArray(xml::XmlPullParser* parser,
1118 ParsedResource* outResource) {
1119 return parseArrayImpl(parser, outResource,
1120 android::ResTable_map::TYPE_STRING);
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001121}
1122
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001123bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser,
1124 ParsedResource* outResource,
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001125 const uint32_t typeMask) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001126 outResource->name.type = ResourceType::kArray;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001127
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001128 std::unique_ptr<Array> array = util::make_unique<Array>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001129
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001130 bool translateable = mOptions.translatable;
1131 if (Maybe<StringPiece> translateableAttr =
1132 xml::findAttribute(parser, "translatable")) {
1133 Maybe<bool> maybeTranslateable =
1134 ResourceUtils::parseBool(translateableAttr.value());
1135 if (!maybeTranslateable) {
1136 mDiag->error(DiagMessage(outResource->source)
1137 << "invalid value for 'translatable'. Must be a boolean");
1138 return false;
Adam Lesinski458b8772016-04-25 14:20:21 -07001139 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001140 translateable = maybeTranslateable.value();
1141 }
1142 array->setTranslateable(translateable);
Adam Lesinski458b8772016-04-25 14:20:21 -07001143
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001144 bool error = false;
1145 const size_t depth = parser->getDepth();
1146 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1147 if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1148 // Skip text and comments.
1149 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001150 }
1151
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001152 const Source itemSource = mSource.withLine(parser->getLineNumber());
1153 const std::string& elementNamespace = parser->getElementNamespace();
1154 const std::string& elementName = parser->getElementName();
1155 if (elementNamespace.empty() && elementName == "item") {
1156 std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
1157 if (!item) {
1158 mDiag->error(DiagMessage(itemSource) << "could not parse array item");
1159 error = true;
1160 continue;
1161 }
1162 item->setSource(itemSource);
1163 array->items.emplace_back(std::move(item));
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001164
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001165 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1166 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
1167 << "unknown tag <" << elementNamespace << ":" << elementName
1168 << ">");
1169 error = true;
1170 }
1171 }
1172
1173 if (error) {
1174 return false;
1175 }
1176
1177 outResource->value = std::move(array);
1178 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001179}
1180
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001181bool ResourceParser::parsePlural(xml::XmlPullParser* parser,
1182 ParsedResource* outResource) {
1183 outResource->name.type = ResourceType::kPlurals;
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001184
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001185 std::unique_ptr<Plural> plural = util::make_unique<Plural>();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001186
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001187 bool error = false;
1188 const size_t depth = parser->getDepth();
1189 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1190 if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1191 // Skip text and comments.
1192 continue;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001193 }
1194
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001195 const Source itemSource = mSource.withLine(parser->getLineNumber());
1196 const std::string& elementNamespace = parser->getElementNamespace();
1197 const std::string& elementName = parser->getElementName();
1198 if (elementNamespace.empty() && elementName == "item") {
1199 Maybe<StringPiece> maybeQuantity =
1200 xml::findNonEmptyAttribute(parser, "quantity");
1201 if (!maybeQuantity) {
1202 mDiag->error(DiagMessage(itemSource)
1203 << "<item> in <plurals> requires attribute "
1204 << "'quantity'");
1205 error = true;
1206 continue;
1207 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001208
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001209 StringPiece trimmedQuantity = util::trimWhitespace(maybeQuantity.value());
1210 size_t index = 0;
1211 if (trimmedQuantity == "zero") {
1212 index = Plural::Zero;
1213 } else if (trimmedQuantity == "one") {
1214 index = Plural::One;
1215 } else if (trimmedQuantity == "two") {
1216 index = Plural::Two;
1217 } else if (trimmedQuantity == "few") {
1218 index = Plural::Few;
1219 } else if (trimmedQuantity == "many") {
1220 index = Plural::Many;
1221 } else if (trimmedQuantity == "other") {
1222 index = Plural::Other;
1223 } else {
1224 mDiag->error(DiagMessage(itemSource)
1225 << "<item> in <plural> has invalid value '"
1226 << trimmedQuantity << "' for attribute 'quantity'");
1227 error = true;
1228 continue;
1229 }
1230
1231 if (plural->values[index]) {
1232 mDiag->error(DiagMessage(itemSource) << "duplicate quantity '"
1233 << trimmedQuantity << "'");
1234 error = true;
1235 continue;
1236 }
1237
1238 if (!(plural->values[index] = parseXml(
1239 parser, android::ResTable_map::TYPE_STRING, kNoRawString))) {
1240 error = true;
1241 }
1242 plural->values[index]->setSource(itemSource);
1243
1244 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1245 mDiag->error(DiagMessage(itemSource)
1246 << "unknown tag <" << elementNamespace << ":" << elementName
1247 << ">");
1248 error = true;
1249 }
1250 }
1251
1252 if (error) {
1253 return false;
1254 }
1255
1256 outResource->value = std::move(plural);
1257 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001258}
1259
Adam Lesinski52364f72016-01-11 13:10:24 -08001260bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser,
1261 ParsedResource* outResource) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001262 outResource->name.type = ResourceType::kStyleable;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001263
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001264 // Declare-styleable is kPrivate by default, because it technically only
1265 // exists in R.java.
1266 outResource->symbolState = SymbolState::kPublic;
Adam Lesinski9f222042015-11-04 13:51:45 -08001267
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001268 // Declare-styleable only ends up in default config;
1269 if (outResource->config != ConfigDescription::defaultConfig()) {
1270 mDiag->warn(DiagMessage(outResource->source)
1271 << "ignoring configuration '" << outResource->config
1272 << "' for styleable " << outResource->name.entry);
1273 outResource->config = ConfigDescription::defaultConfig();
1274 }
1275
1276 std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
1277
1278 std::string comment;
1279 bool error = false;
1280 const size_t depth = parser->getDepth();
1281 while (xml::XmlPullParser::nextChildNode(parser, depth)) {
1282 if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
1283 comment = util::trimWhitespace(parser->getComment()).toString();
1284 continue;
1285 } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
1286 // Ignore text.
1287 continue;
Adam Lesinski52364f72016-01-11 13:10:24 -08001288 }
1289
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001290 const Source itemSource = mSource.withLine(parser->getLineNumber());
1291 const std::string& elementNamespace = parser->getElementNamespace();
1292 const std::string& elementName = parser->getElementName();
1293 if (elementNamespace.empty() && elementName == "attr") {
1294 Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name");
1295 if (!maybeName) {
1296 mDiag->error(DiagMessage(itemSource)
1297 << "<attr> tag must have a 'name' attribute");
1298 error = true;
1299 continue;
1300 }
Adam Lesinski7ff3ee12015-12-14 16:08:50 -08001301
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001302 // If this is a declaration, the package name may be in the name. Separate
1303 // these out.
1304 // Eg. <attr name="android:text" />
1305 Maybe<Reference> maybeRef =
1306 ResourceUtils::parseXmlAttributeName(maybeName.value());
1307 if (!maybeRef) {
1308 mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
1309 << maybeName.value() << "'");
1310 error = true;
1311 continue;
1312 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001313
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001314 Reference& childRef = maybeRef.value();
1315 xml::transformReferenceFromNamespace(parser, "", &childRef);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001316
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001317 // Create the ParsedResource that will add the attribute to the table.
1318 ParsedResource childResource;
1319 childResource.name = childRef.name.value();
1320 childResource.source = itemSource;
1321 childResource.comment = std::move(comment);
Adam Lesinski467f1712015-11-16 17:35:44 -08001322
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001323 if (!parseAttrImpl(parser, &childResource, true)) {
1324 error = true;
1325 continue;
1326 }
Adam Lesinski467f1712015-11-16 17:35:44 -08001327
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001328 // Create the reference to this attribute.
1329 childRef.setComment(childResource.comment);
1330 childRef.setSource(itemSource);
1331 styleable->entries.push_back(std::move(childRef));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001332
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001333 outResource->childResources.push_back(std::move(childResource));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001334
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001335 } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
1336 mDiag->error(DiagMessage(itemSource)
1337 << "unknown tag <" << elementNamespace << ":" << elementName
1338 << ">");
1339 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001340 }
1341
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001342 comment = {};
1343 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001344
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001345 if (error) {
1346 return false;
1347 }
1348
1349 outResource->value = std::move(styleable);
1350 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001351}
1352
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001353} // namespace aapt