blob: 44710ebc9dc44530e9eea5a93cb1bc5123617ffa [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"
22#include "XmlPullParser.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080023
Adam Lesinski9e10ac72015-10-16 14:37:48 -070024#include "util/Util.h"
25
Adam Lesinski769de982015-04-10 19:43:55 -070026#include <sstream>
27
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080028namespace aapt {
29
Adam Lesinski1ab598f2015-08-14 14:26:04 -070030constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080031
Adam Lesinski1ab598f2015-08-14 14:26:04 -070032static Maybe<StringPiece16> findAttribute(XmlPullParser* parser, const StringPiece16& name) {
33 auto iter = parser->findAttribute(u"", name);
34 if (iter != parser->endAttributes()) {
35 return StringPiece16(util::trimWhitespace(iter->value));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080036 }
37 return {};
38}
39
Adam Lesinski1ab598f2015-08-14 14:26:04 -070040static Maybe<StringPiece16> findNonEmptyAttribute(XmlPullParser* parser,
41 const StringPiece16& name) {
42 auto iter = parser->findAttribute(u"", name);
43 if (iter != parser->endAttributes()) {
44 StringPiece16 trimmed = util::trimWhitespace(iter->value);
45 if (!trimmed.empty()) {
46 return trimmed;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080047 }
48 }
49 return {};
50}
51
Adam Lesinski1ab598f2015-08-14 14:26:04 -070052ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
Adam Lesinski9ba47d82015-10-13 11:37:10 -070053 const ConfigDescription& config,
54 const ResourceParserOptions& options) :
55 mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080056}
57
58/**
59 * Build a string from XML that converts nested elements into Span objects.
60 */
61bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
62 StyleString* outStyleString) {
63 std::vector<Span> spanStack;
64
65 outRawString->clear();
66 outStyleString->spans.clear();
67 util::StringBuilder builder;
68 size_t depth = 1;
69 while (XmlPullParser::isGoodEvent(parser->next())) {
70 const XmlPullParser::Event event = parser->getEvent();
71 if (event == XmlPullParser::Event::kEndElement) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070072 if (!parser->getElementNamespace().empty()) {
73 // We already warned and skipped the start element, so just skip here too
74 continue;
75 }
76
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080077 depth--;
78 if (depth == 0) {
79 break;
80 }
81
82 spanStack.back().lastChar = builder.str().size();
83 outStyleString->spans.push_back(spanStack.back());
84 spanStack.pop_back();
85
86 } else if (event == XmlPullParser::Event::kText) {
87 // TODO(adamlesinski): Verify format strings.
88 outRawString->append(parser->getText());
89 builder.append(parser->getText());
90
91 } else if (event == XmlPullParser::Event::kStartElement) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070092 if (!parser->getElementNamespace().empty()) {
93 if (parser->getElementNamespace() != sXliffNamespaceUri) {
94 // Only warn if this isn't an xliff namespace.
95 mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
96 << "skipping element '"
97 << parser->getElementName()
98 << "' with unknown namespace '"
99 << parser->getElementNamespace()
100 << "'");
101 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800102 continue;
103 }
104 depth++;
105
106 // Build a span object out of the nested element.
107 std::u16string spanName = parser->getElementName();
108 const auto endAttrIter = parser->endAttributes();
109 for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) {
110 spanName += u";";
111 spanName += attrIter->name;
112 spanName += u"=";
113 spanName += attrIter->value;
114 }
115
116 if (builder.str().size() > std::numeric_limits<uint32_t>::max()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700117 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
118 << "style string '" << builder.str() << "' is too long");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800119 return false;
120 }
121 spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
122
123 } else if (event == XmlPullParser::Event::kComment) {
124 // Skip
125 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700126 assert(false);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800127 }
128 }
129 assert(spanStack.empty() && "spans haven't been fully processed");
130
131 outStyleString->str = builder.str();
132 return true;
133}
134
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700135bool ResourceParser::parse(XmlPullParser* parser) {
136 bool error = false;
137 const size_t depth = parser->getDepth();
138 while (XmlPullParser::nextChildNode(parser, depth)) {
139 if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
140 // Skip comments and text.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800141 continue;
142 }
143
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700144 if (!parser->getElementNamespace().empty() || parser->getElementName() != u"resources") {
145 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
146 << "root element must be <resources>");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800147 return false;
148 }
149
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700150 error |= !parseResources(parser);
151 break;
152 };
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800153
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700154 if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
155 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
156 << "xml parser error: " << parser->getLastError());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800157 return false;
158 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700159 return !error;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800160}
161
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700162static bool shouldStripResource(XmlPullParser* parser, const Maybe<std::u16string> productToMatch) {
163 assert(parser->getEvent() == XmlPullParser::Event::kStartElement);
164
165 if (Maybe<StringPiece16> maybeProduct = findNonEmptyAttribute(parser, u"product")) {
166 if (!productToMatch) {
167 if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") {
168 // We didn't specify a product and this is not a default product, so skip.
169 return true;
170 }
171 } else {
172 if (productToMatch && maybeProduct.value() != productToMatch.value()) {
173 // We specified a product, but they don't match.
174 return true;
175 }
176 }
177 }
178 return false;
179}
180
181/**
182 * A parsed resource ready to be added to the ResourceTable.
183 */
184struct ParsedResource {
185 ResourceName name;
186 Source source;
187 ResourceId id;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700188 SymbolState symbolState = SymbolState::kUndefined;
Adam Lesinskie78fd612015-10-22 12:48:43 -0700189 std::u16string comment;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700190 std::unique_ptr<Value> value;
191 std::list<ParsedResource> childResources;
192};
193
194// Recursively adds resources to the ResourceTable.
195static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config,
196 IDiagnostics* diag, ParsedResource* res) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700197 if (res->symbolState != SymbolState::kUndefined) {
Adam Lesinskie78fd612015-10-22 12:48:43 -0700198 Symbol symbol;
199 symbol.state = res->symbolState;
200 symbol.source = res->source;
201 symbol.comment = res->comment;
202 if (!table->setSymbolState(res->name, res->id, symbol, diag)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700203 return false;
204 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700205 }
206
207 if (!res->value) {
208 return true;
209 }
210
Adam Lesinskie78fd612015-10-22 12:48:43 -0700211 // Attach the comment, source and config to the value.
212 res->value->setComment(std::move(res->comment));
213 res->value->setSource(std::move(res->source));
214
215 if (!table->addResource(res->name, res->id, config, std::move(res->value), diag)) {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700216 return false;
217 }
218
219 bool error = false;
220 for (ParsedResource& child : res->childResources) {
221 error |= !addResourcesToTable(table, config, diag, &child);
222 }
223 return !error;
224}
225
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800226bool ResourceParser::parseResources(XmlPullParser* parser) {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700227 std::set<ResourceName> strippedResources;
228
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700229 bool error = false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800230 std::u16string comment;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700231 const size_t depth = parser->getDepth();
232 while (XmlPullParser::nextChildNode(parser, depth)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800233 const XmlPullParser::Event event = parser->getEvent();
234 if (event == XmlPullParser::Event::kComment) {
235 comment = parser->getComment();
236 continue;
237 }
238
239 if (event == XmlPullParser::Event::kText) {
240 if (!util::trimWhitespace(parser->getText()).empty()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700241 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
242 << "plain text not allowed here");
243 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800244 }
245 continue;
246 }
247
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700248 assert(event == XmlPullParser::Event::kStartElement);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800249
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700250 if (!parser->getElementNamespace().empty()) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800251 // Skip unknown namespace.
252 continue;
253 }
254
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700255 std::u16string elementName = parser->getElementName();
256 if (elementName == u"skip" || elementName == u"eat-comment") {
257 comment = u"";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800258 continue;
259 }
260
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700261 Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
262 if (!maybeName) {
263 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
264 << "<" << elementName << "> tag must have a 'name' attribute");
265 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800266 continue;
267 }
268
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700269 // Check if we should skip this product.
270 const bool stripResource = shouldStripResource(parser, mOptions.product);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800271
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700272 if (elementName == u"item") {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800273 // Items simply have their type encoded in the type attribute.
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700274 if (Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type")) {
275 elementName = maybeType.value().toString();
276 } else {
277 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
278 << "<item> must have a 'type' attribute");
279 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800280 continue;
281 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800282 }
283
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700284 ParsedResource parsedResource;
285 parsedResource.name.entry = maybeName.value().toString();
286 parsedResource.source = mSource.withLine(parser->getLineNumber());
Adam Lesinskie78fd612015-10-22 12:48:43 -0700287 parsedResource.comment = std::move(comment);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800288
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700289 bool result = true;
290 if (elementName == u"id") {
291 parsedResource.name.type = ResourceType::kId;
292 parsedResource.value = util::make_unique<Id>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700293 } else if (elementName == u"string") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700294 parsedResource.name.type = ResourceType::kString;
295 result = parseString(parser, &parsedResource);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700296 } else if (elementName == u"color") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700297 parsedResource.name.type = ResourceType::kColor;
298 result = parseColor(parser, &parsedResource);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700299 } else if (elementName == u"drawable") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700300 parsedResource.name.type = ResourceType::kDrawable;
301 result = parseColor(parser, &parsedResource);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700302 } else if (elementName == u"bool") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700303 parsedResource.name.type = ResourceType::kBool;
304 result = parsePrimitive(parser, &parsedResource);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700305 } else if (elementName == u"integer") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700306 parsedResource.name.type = ResourceType::kInteger;
307 result = parsePrimitive(parser, &parsedResource);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700308 } else if (elementName == u"dimen") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700309 parsedResource.name.type = ResourceType::kDimen;
310 result = parsePrimitive(parser, &parsedResource);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700311 } else if (elementName == u"style") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700312 parsedResource.name.type = ResourceType::kStyle;
313 result = parseStyle(parser, &parsedResource);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700314 } else if (elementName == u"plurals") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700315 parsedResource.name.type = ResourceType::kPlurals;
316 result = parsePlural(parser, &parsedResource);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700317 } else if (elementName == u"array") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700318 parsedResource.name.type = ResourceType::kArray;
319 result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_ANY);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700320 } else if (elementName == u"string-array") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700321 parsedResource.name.type = ResourceType::kArray;
322 result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_STRING);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700323 } else if (elementName == u"integer-array") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700324 parsedResource.name.type = ResourceType::kIntegerArray;
325 result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_INTEGER);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700326 } else if (elementName == u"declare-styleable") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700327 parsedResource.name.type = ResourceType::kStyleable;
328 result = parseDeclareStyleable(parser, &parsedResource);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700329 } else if (elementName == u"attr") {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700330 parsedResource.name.type = ResourceType::kAttr;
331 result = parseAttr(parser, &parsedResource);
332 } else if (elementName == u"public") {
333 result = parsePublic(parser, &parsedResource);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700334 } else if (elementName == u"java-symbol" || elementName == u"symbol") {
335 result = parseSymbol(parser, &parsedResource);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700336 } else {
337 mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
338 << "unknown resource type '" << elementName << "'");
339 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700340
341 if (result) {
342 // We successfully parsed the resource.
343
344 if (stripResource) {
345 // Record that we stripped out this resource name.
346 // We will check that at least one variant of this resource was included.
347 strippedResources.insert(parsedResource.name);
348 } else {
349 error |= !addResourcesToTable(mTable, mConfig, mDiag, &parsedResource);
350 }
351 } else {
352 error = true;
353 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800354 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700355
356 // Check that we included at least one variant of each stripped resource.
357 for (const ResourceName& strippedResource : strippedResources) {
358 if (!mTable->findResource(strippedResource)) {
359 // Failed to find the resource.
360 mDiag->error(DiagMessage(mSource) << "resource '" << strippedResource << "' "
361 "was filtered out but no product variant remains");
362 error = true;
363 }
364 }
365
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700366 return !error;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800367}
368
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800369enum {
370 kAllowRawString = true,
371 kNoRawString = false
372};
373
374/**
375 * Reads the entire XML subtree and attempts to parse it as some Item,
376 * with typeMask denoting which items it can be. If allowRawValue is
377 * true, a RawString is returned if the XML couldn't be parsed as
378 * an Item. If allowRawValue is false, nullptr is returned in this
379 * case.
380 */
Adam Lesinskie78fd612015-10-22 12:48:43 -0700381std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint32_t typeMask,
382 const bool allowRawValue) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800383 const size_t beginXmlLine = parser->getLineNumber();
384
385 std::u16string rawValue;
386 StyleString styleString;
387 if (!flattenXmlSubtree(parser, &rawValue, &styleString)) {
388 return {};
389 }
390
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800391 if (!styleString.spans.empty()) {
392 // This can only be a StyledString.
393 return util::make_unique<StyledString>(
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700394 mTable->stringPool.makeRef(styleString, StringPool::Context{ 1, mConfig }));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800395 }
396
397 auto onCreateReference = [&](const ResourceName& name) {
Adam Lesinski24aad162015-04-24 19:19:30 -0700398 // name.package can be empty here, as it will assume the package name of the table.
Adam Lesinskie78fd612015-10-22 12:48:43 -0700399 std::unique_ptr<Id> id = util::make_unique<Id>();
400 id->setSource(mSource.withLine(beginXmlLine));
401 mTable->addResource(name, {}, std::move(id), mDiag);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800402 };
403
404 // Process the raw value.
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700405 std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask,
406 onCreateReference);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800407 if (processedItem) {
Adam Lesinski24aad162015-04-24 19:19:30 -0700408 // Fix up the reference.
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700409 if (Reference* ref = valueCast<Reference>(processedItem.get())) {
410 if (Maybe<ResourceName> transformedName =
411 parser->transformPackage(ref->name.value(), u"")) {
412 ref->name = std::move(transformedName);
Adam Lesinski24aad162015-04-24 19:19:30 -0700413 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700414 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800415 return processedItem;
416 }
417
418 // Try making a regular string.
419 if (typeMask & android::ResTable_map::TYPE_STRING) {
420 // Use the trimmed, escaped string.
421 return util::make_unique<String>(
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700422 mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800423 }
424
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800425 if (allowRawValue) {
Adam Lesinskie78fd612015-10-22 12:48:43 -0700426 // We can't parse this so return a RawString if we are allowed.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800427 return util::make_unique<RawString>(
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700428 mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800429 }
Adam Lesinskie78fd612015-10-22 12:48:43 -0700430
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800431 return {};
432}
433
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700434bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResource) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700435 const Source source = mSource.withLine(parser->getLineNumber());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800436
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700437 // TODO(adamlesinski): Read "untranslateable" attribute.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800438
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700439 outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
440 if (!outResource->value) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700441 mDiag->error(DiagMessage(source) << "not a valid string");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800442 return false;
443 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700444 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800445}
446
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700447bool ResourceParser::parseColor(XmlPullParser* parser, ParsedResource* outResource) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700448 const Source source = mSource.withLine(parser->getLineNumber());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800449
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700450 outResource->value = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
451 if (!outResource->value) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700452 mDiag->error(DiagMessage(source) << "invalid color");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800453 return false;
454 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700455 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800456}
457
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700458bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outResource) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700459 const Source source = mSource.withLine(parser->getLineNumber());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800460
461 uint32_t typeMask = 0;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700462 switch (outResource->name.type) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700463 case ResourceType::kInteger:
464 typeMask |= android::ResTable_map::TYPE_INTEGER;
465 break;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800466
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700467 case ResourceType::kDimen:
468 typeMask |= android::ResTable_map::TYPE_DIMENSION
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700469 | android::ResTable_map::TYPE_FLOAT
470 | android::ResTable_map::TYPE_FRACTION;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700471 break;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800472
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700473 case ResourceType::kBool:
474 typeMask |= android::ResTable_map::TYPE_BOOLEAN;
475 break;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800476
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700477 default:
478 assert(false);
479 break;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800480 }
481
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700482 outResource->value = parseXml(parser, typeMask, kNoRawString);
483 if (!outResource->value) {
484 mDiag->error(DiagMessage(source) << "invalid " << outResource->name.type);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800485 return false;
486 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700487 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800488}
489
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700490bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResource) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700491 const Source source = mSource.withLine(parser->getLineNumber());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800492
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700493 Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
494 if (!maybeType) {
495 mDiag->error(DiagMessage(source) << "<public> must have a 'type' attribute");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800496 return false;
497 }
498
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700499 const ResourceType* parsedType = parseResourceType(maybeType.value());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800500 if (!parsedType) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700501 mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value()
502 << "' in <public>");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800503 return false;
504 }
505
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700506 outResource->name.type = *parsedType;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800507
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700508 if (Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"id")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800509 android::Res_value val;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700510 bool result = android::ResTable::stringToInt(maybeId.value().data(),
511 maybeId.value().size(), &val);
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700512 ResourceId resourceId(val.data);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800513 if (!result || !resourceId.isValid()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700514 mDiag->error(DiagMessage(source) << "invalid resource ID '" << maybeId.value()
515 << "' in <public>");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800516 return false;
517 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700518 outResource->id = resourceId;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800519 }
520
521 if (*parsedType == ResourceType::kId) {
522 // An ID marked as public is also the definition of an ID.
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700523 outResource->value = util::make_unique<Id>();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800524 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700525
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700526 outResource->symbolState = SymbolState::kPublic;
527 return true;
528}
529
530bool ResourceParser::parseSymbol(XmlPullParser* parser, ParsedResource* outResource) {
531 const Source source = mSource.withLine(parser->getLineNumber());
532
533 Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
534 if (!maybeType) {
535 mDiag->error(DiagMessage(source) << "<" << parser->getElementName() << "> must have a "
536 "'type' attribute");
537 return false;
538 }
539
540 const ResourceType* parsedType = parseResourceType(maybeType.value());
541 if (!parsedType) {
542 mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value()
543 << "' in <" << parser->getElementName() << ">");
544 return false;
545 }
546
547 outResource->name.type = *parsedType;
548 outResource->symbolState = SymbolState::kPrivate;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700549 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800550}
551
552static uint32_t parseFormatType(const StringPiece16& piece) {
553 if (piece == u"reference") return android::ResTable_map::TYPE_REFERENCE;
554 else if (piece == u"string") return android::ResTable_map::TYPE_STRING;
555 else if (piece == u"integer") return android::ResTable_map::TYPE_INTEGER;
556 else if (piece == u"boolean") return android::ResTable_map::TYPE_BOOLEAN;
557 else if (piece == u"color") return android::ResTable_map::TYPE_COLOR;
558 else if (piece == u"float") return android::ResTable_map::TYPE_FLOAT;
559 else if (piece == u"dimension") return android::ResTable_map::TYPE_DIMENSION;
560 else if (piece == u"fraction") return android::ResTable_map::TYPE_FRACTION;
561 else if (piece == u"enum") return android::ResTable_map::TYPE_ENUM;
562 else if (piece == u"flags") return android::ResTable_map::TYPE_FLAGS;
563 return 0;
564}
565
566static uint32_t parseFormatAttribute(const StringPiece16& str) {
567 uint32_t mask = 0;
568 for (StringPiece16 part : util::tokenize(str, u'|')) {
569 StringPiece16 trimmedPart = util::trimWhitespace(part);
570 uint32_t type = parseFormatType(trimmedPart);
571 if (type == 0) {
572 return 0;
573 }
574 mask |= type;
575 }
576 return mask;
577}
578
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700579
580bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) {
581 outResource->source = mSource.withLine(parser->getLineNumber());
582 return parseAttrImpl(parser, outResource, false);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800583}
584
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700585bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800586 uint32_t typeMask = 0;
587
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700588 Maybe<StringPiece16> maybeFormat = findAttribute(parser, u"format");
589 if (maybeFormat) {
590 typeMask = parseFormatAttribute(maybeFormat.value());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800591 if (typeMask == 0) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700592 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
593 << "invalid attribute format '" << maybeFormat.value() << "'");
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700594 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800595 }
596 }
597
Adam Lesinski769de982015-04-10 19:43:55 -0700598 // If this is a declaration, the package name may be in the name. Separate these out.
599 // Eg. <attr name="android:text" />
600 // No format attribute is allowed.
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700601 if (weak && !maybeFormat) {
Adam Lesinski769de982015-04-10 19:43:55 -0700602 StringPiece16 package, type, name;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700603 ResourceUtils::extractResourceName(outResource->name.entry, &package, &type, &name);
Adam Lesinski769de982015-04-10 19:43:55 -0700604 if (type.empty() && !package.empty()) {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700605 outResource->name.package = package.toString();
606 outResource->name.entry = name.toString();
Adam Lesinski769de982015-04-10 19:43:55 -0700607 }
608 }
609
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800610 std::vector<Attribute::Symbol> items;
611
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700612 std::u16string comment;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800613 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700614 const size_t depth = parser->getDepth();
615 while (XmlPullParser::nextChildNode(parser, depth)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800616 if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700617 // Skip comments and text.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800618 continue;
619 }
620
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700621 const std::u16string& elementNamespace = parser->getElementNamespace();
622 const std::u16string& elementName = parser->getElementName();
623 if (elementNamespace == u"" && (elementName == u"flag" || elementName == u"enum")) {
624 if (elementName == u"enum") {
625 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
626 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
627 << "can not define an <enum>; already defined a <flag>");
628 error = true;
629 continue;
630 }
631 typeMask |= android::ResTable_map::TYPE_ENUM;
632 } else if (elementName == u"flag") {
633 if (typeMask & android::ResTable_map::TYPE_ENUM) {
634 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
635 << "can not define a <flag>; already defined an <enum>");
636 error = true;
637 continue;
638 }
639 typeMask |= android::ResTable_map::TYPE_FLAGS;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800640 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800641
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700642 if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700643 ParsedResource childResource;
644 childResource.name = s.value().symbol.name.value();
645 childResource.source = mSource.withLine(parser->getLineNumber());
646 childResource.value = util::make_unique<Id>();
647 outResource->childResources.push_back(std::move(childResource));
648 items.push_back(std::move(s.value()));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800649 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700650 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800651 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700652 } else if (elementName == u"skip" || elementName == u"eat-comment") {
653 comment = u"";
654
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800655 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700656 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
657 << ":" << elementName << ">");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800658 error = true;
659 }
660 }
661
662 if (error) {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700663 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800664 }
665
666 std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
667 attr->symbols.swap(items);
Adam Lesinskica2fc352015-04-03 12:08:26 -0700668 attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700669 outResource->value = std::move(attr);
670 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800671}
672
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700673Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser,
674 const StringPiece16& tag) {
675 const Source source = mSource.withLine(parser->getLineNumber());
676
677 Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
678 if (!maybeName) {
679 mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">");
680 return {};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800681 }
682
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700683 Maybe<StringPiece16> maybeValue = findNonEmptyAttribute(parser, u"value");
684 if (!maybeValue) {
685 mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">");
686 return {};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800687 }
688
689 android::Res_value val;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700690 if (!android::ResTable::stringToInt(maybeValue.value().data(),
691 maybeValue.value().size(), &val)) {
692 mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value()
693 << "' for <" << tag << ">; must be an integer");
694 return {};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800695 }
696
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700697 return Attribute::Symbol{
Adam Lesinskie78fd612015-10-22 12:48:43 -0700698 Reference(ResourceName({}, ResourceType::kId, maybeName.value().toString())),
699 val.data };
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800700}
701
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700702static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800703 str = util::trimWhitespace(str);
704 const char16_t* const start = str.data();
705 const char16_t* const end = start + str.size();
706 const char16_t* p = start;
707
708 StringPiece16 package;
709 StringPiece16 name;
710 while (p != end) {
711 if (*p == u':') {
712 package = StringPiece16(start, p - start);
713 name = StringPiece16(p + 1, end - (p + 1));
714 break;
715 }
716 p++;
717 }
718
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700719 return ResourceName{ package.toString(), ResourceType::kAttr,
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700720 name.empty() ? str.toString() : name.toString() };
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800721}
722
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700723
724bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) {
725 const Source source = mSource.withLine(parser->getLineNumber());
726
727 Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
728 if (!maybeName) {
729 mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800730 return false;
731 }
732
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700733 Maybe<ResourceName> maybeKey = parseXmlAttributeName(maybeName.value());
734 if (!maybeKey) {
735 mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800736 return false;
737 }
738
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700739 if (Maybe<ResourceName> transformedName = parser->transformPackage(maybeKey.value(), u"")) {
740 maybeKey = std::move(transformedName);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800741 }
742
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800743 std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
744 if (!value) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700745 mDiag->error(DiagMessage(source) << "could not parse style item");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800746 return false;
747 }
748
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700749 style->entries.push_back(Style::Entry{ Reference(maybeKey.value()), std::move(value) });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800750 return true;
751}
752
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700753bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResource) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700754 const Source source = mSource.withLine(parser->getLineNumber());
Adam Lesinskibdaa0922015-05-08 20:16:23 -0700755 std::unique_ptr<Style> style = util::make_unique<Style>();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800756
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700757 Maybe<StringPiece16> maybeParent = findAttribute(parser, u"parent");
758 if (maybeParent) {
759 // If the parent is empty, we don't have a parent, but we also don't infer either.
760 if (!maybeParent.value().empty()) {
761 std::string errStr;
762 style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr);
763 if (!style->parent) {
764 mDiag->error(DiagMessage(source) << errStr);
765 return false;
766 }
767
768 if (Maybe<ResourceName> transformedName =
769 parser->transformPackage(style->parent.value().name.value(), u"")) {
770 style->parent.value().name = std::move(transformedName);
771 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800772 }
773
Adam Lesinskibdaa0922015-05-08 20:16:23 -0700774 } else {
775 // No parent was specified, so try inferring it from the style name.
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700776 std::u16string styleName = outResource->name.entry;
Adam Lesinskibdaa0922015-05-08 20:16:23 -0700777 size_t pos = styleName.find_last_of(u'.');
778 if (pos != std::string::npos) {
779 style->parentInferred = true;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700780 style->parent = Reference(
781 ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos)));
Adam Lesinskibdaa0922015-05-08 20:16:23 -0700782 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800783 }
784
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800785 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700786 std::u16string comment;
787 const size_t depth = parser->getDepth();
788 while (XmlPullParser::nextChildNode(parser, depth)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800789 if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700790 // Skip text and comments.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800791 continue;
792 }
793
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700794 const std::u16string& elementNamespace = parser->getElementNamespace();
795 const std::u16string& elementName = parser->getElementName();
796 if (elementNamespace == u"" && elementName == u"item") {
797 error |= !parseStyleItem(parser, style.get());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800798
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700799 } else if (elementNamespace.empty() &&
800 (elementName == u"skip" || elementName == u"eat-comment")) {
801 comment = u"";
802
803 } else {
804 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
805 << ":" << elementName << ">");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800806 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800807 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800808 }
809
810 if (error) {
811 return false;
812 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700813
814 outResource->value = std::move(style);
815 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700816}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800817
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700818bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResource,
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700819 uint32_t typeMask) {
820 const Source source = mSource.withLine(parser->getLineNumber());
821 std::unique_ptr<Array> array = util::make_unique<Array>();
822
823 std::u16string comment;
824 bool error = false;
825 const size_t depth = parser->getDepth();
826 while (XmlPullParser::nextChildNode(parser, depth)) {
827 if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
828 // Skip text and comments.
829 continue;
830 }
831
832 const Source itemSource = mSource.withLine(parser->getLineNumber());
833 const std::u16string& elementNamespace = parser->getElementNamespace();
834 const std::u16string& elementName = parser->getElementName();
835 if (elementNamespace.empty() && elementName == u"item") {
836 std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString);
837 if (!item) {
838 mDiag->error(DiagMessage(itemSource) << "could not parse array item");
839 error = true;
840 continue;
841 }
842 array->items.emplace_back(std::move(item));
843
844 } else if (elementNamespace.empty() &&
845 (elementName == u"skip" || elementName == u"eat-comment")) {
846 comment = u"";
847
848 } else {
849 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
850 << "unknown tag <" << elementNamespace << ":" << elementName << ">");
851 error = true;
852 }
853 }
854
855 if (error) {
856 return false;
857 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700858
859 outResource->value = std::move(array);
860 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800861}
862
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700863bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResource) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700864 const Source source = mSource.withLine(parser->getLineNumber());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800865 std::unique_ptr<Plural> plural = util::make_unique<Plural>();
866
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700867 std::u16string comment;
868 bool error = false;
869 const size_t depth = parser->getDepth();
870 while (XmlPullParser::nextChildNode(parser, depth)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800871 if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700872 // Skip text and comments.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800873 continue;
874 }
875
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700876 const std::u16string& elementNamespace = parser->getElementNamespace();
877 const std::u16string& elementName = parser->getElementName();
878 if (elementNamespace.empty() && elementName == u"item") {
879 const auto endAttrIter = parser->endAttributes();
880 auto attrIter = parser->findAttribute(u"", u"quantity");
881 if (attrIter == endAttrIter || attrIter->value.empty()) {
882 mDiag->error(DiagMessage(source) << "<item> in <plurals> requires attribute "
883 << "'quantity'");
884 error = true;
885 continue;
886 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800887
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700888 StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
889 size_t index = 0;
890 if (trimmedQuantity == u"zero") {
891 index = Plural::Zero;
892 } else if (trimmedQuantity == u"one") {
893 index = Plural::One;
894 } else if (trimmedQuantity == u"two") {
895 index = Plural::Two;
896 } else if (trimmedQuantity == u"few") {
897 index = Plural::Few;
898 } else if (trimmedQuantity == u"many") {
899 index = Plural::Many;
900 } else if (trimmedQuantity == u"other") {
901 index = Plural::Other;
902 } else {
903 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
904 << "<item> in <plural> has invalid value '" << trimmedQuantity
905 << "' for attribute 'quantity'");
906 error = true;
907 continue;
908 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800909
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700910 if (plural->values[index]) {
911 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
912 << "duplicate quantity '" << trimmedQuantity << "'");
913 error = true;
914 continue;
915 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800916
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700917 if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING,
918 kNoRawString))) {
919 error = true;
920 }
921 } else if (elementNamespace.empty() &&
922 (elementName == u"skip" || elementName == u"eat-comment")) {
923 comment = u"";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800924 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700925 mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
926 << elementName << ">");
927 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800928 }
929 }
930
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700931 if (error) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800932 return false;
933 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700934
935 outResource->value = std::move(plural);
936 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800937}
938
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700939bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700940 const Source source = mSource.withLine(parser->getLineNumber());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800941 std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
942
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700943 std::u16string comment;
944 bool error = false;
945 const size_t depth = parser->getDepth();
946 while (XmlPullParser::nextChildNode(parser, depth)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800947 if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700948 // Ignore text and comments.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800949 continue;
950 }
951
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700952 const std::u16string& elementNamespace = parser->getElementNamespace();
953 const std::u16string& elementName = parser->getElementName();
954 if (elementNamespace.empty() && elementName == u"attr") {
955 const auto endAttrIter = parser->endAttributes();
956 auto attrIter = parser->findAttribute(u"", u"name");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800957 if (attrIter == endAttrIter || attrIter->value.empty()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700958 mDiag->error(DiagMessage(source) << "<attr> tag must have a 'name' attribute");
959 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800960 continue;
961 }
962
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700963 ParsedResource childResource;
964 childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value);
965 childResource.source = mSource.withLine(parser->getLineNumber());
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800966
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700967 if (!parseAttrImpl(parser, &childResource, true)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700968 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800969 continue;
970 }
971
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700972 styleable->entries.push_back(Reference(childResource.name));
973 outResource->childResources.push_back(std::move(childResource));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800974
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700975 } else if (elementNamespace.empty() &&
976 (elementName == u"skip" || elementName == u"eat-comment")) {
977 comment = u"";
978
979 } else {
980 mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
981 << elementName << ">");
982 error = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800983 }
984 }
985
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700986 if (error) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800987 return false;
988 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700989
990 outResource->value = std::move(styleable);
991 return true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800992}
993
994} // namespace aapt