1 /*
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
17 #include "ResourceParser.h"
18
19 #include <functional>
20 #include <limits>
21 #include <sstream>
22
23 #include <android-base/logging.h>
24 #include <idmap2/Policies.h>
25
26 #include "ResourceTable.h"
27 #include "ResourceUtils.h"
28 #include "ResourceValues.h"
29 #include "ValueVisitor.h"
30 #include "text/Utf8Iterator.h"
31 #include "util/ImmutableMap.h"
32
33 #include "util/Util.h"
34 #include "xml/XmlPullParser.h"
35
36 using ::aapt::ResourceUtils::StringBuilder;
37 using ::aapt::text::Utf8Iterator;
38 using ::android::ConfigDescription;
39 using ::android::StringPiece;
40
41 using android::idmap2::policy::kPolicyStringToFlag;
42
43 namespace aapt {
44 namespace {
45 constexpr const char* kPublicGroupTag = "public-group";
46 constexpr const char* kStagingPublicGroupTag = "staging-public-group";
47 constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final";
48 } // namespace
49
50 constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
51
52 // Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
ShouldIgnoreElement(StringPiece ns,StringPiece name)53 static bool ShouldIgnoreElement(StringPiece ns, StringPiece name) {
54 return ns.empty() && (name == "skip" || name == "eat-comment");
55 }
56
ParseFormatTypeNoEnumsOrFlags(StringPiece piece)57 static uint32_t ParseFormatTypeNoEnumsOrFlags(StringPiece piece) {
58 if (piece == "reference") {
59 return android::ResTable_map::TYPE_REFERENCE;
60 } else if (piece == "string") {
61 return android::ResTable_map::TYPE_STRING;
62 } else if (piece == "integer") {
63 return android::ResTable_map::TYPE_INTEGER;
64 } else if (piece == "boolean") {
65 return android::ResTable_map::TYPE_BOOLEAN;
66 } else if (piece == "color") {
67 return android::ResTable_map::TYPE_COLOR;
68 } else if (piece == "float") {
69 return android::ResTable_map::TYPE_FLOAT;
70 } else if (piece == "dimension") {
71 return android::ResTable_map::TYPE_DIMENSION;
72 } else if (piece == "fraction") {
73 return android::ResTable_map::TYPE_FRACTION;
74 }
75 return 0;
76 }
77
ParseFormatType(StringPiece piece)78 static uint32_t ParseFormatType(StringPiece piece) {
79 if (piece == "enum") {
80 return android::ResTable_map::TYPE_ENUM;
81 } else if (piece == "flags") {
82 return android::ResTable_map::TYPE_FLAGS;
83 }
84 return ParseFormatTypeNoEnumsOrFlags(piece);
85 }
86
ParseFormatAttribute(StringPiece str)87 static uint32_t ParseFormatAttribute(StringPiece str) {
88 uint32_t mask = 0;
89 for (StringPiece part : util::Tokenize(str, '|')) {
90 StringPiece trimmed_part = util::TrimWhitespace(part);
91 uint32_t type = ParseFormatType(trimmed_part);
92 if (type == 0) {
93 return 0;
94 }
95 mask |= type;
96 }
97 return mask;
98 }
99
100 // A parsed resource ready to be added to the ResourceTable.
101 struct ParsedResource {
102 ResourceName name;
103 ConfigDescription config;
104 std::string product;
105 android::Source source;
106
107 ResourceId id;
108 Visibility::Level visibility_level = Visibility::Level::kUndefined;
109 bool staged_api = false;
110 bool allow_new = false;
111 std::optional<OverlayableItem> overlayable_item;
112 std::optional<StagedId> staged_alias;
113
114 std::string comment;
115 std::unique_ptr<Value> value;
116 std::list<ParsedResource> child_resources;
117 };
118
119 // Recursively adds resources to the ResourceTable.
AddResourcesToTable(ResourceTable * table,android::IDiagnostics * diag,ParsedResource * res)120 static bool AddResourcesToTable(ResourceTable* table, android::IDiagnostics* diag,
121 ParsedResource* res) {
122 StringPiece trimmed_comment = util::TrimWhitespace(res->comment);
123 if (trimmed_comment.size() != res->comment.size()) {
124 // Only if there was a change do we re-assign.
125 res->comment = std::string(trimmed_comment);
126 }
127
128 NewResourceBuilder res_builder(res->name);
129 if (res->visibility_level != Visibility::Level::kUndefined) {
130 Visibility visibility;
131 visibility.level = res->visibility_level;
132 visibility.staged_api = res->staged_api;
133 visibility.source = res->source;
134 visibility.comment = res->comment;
135 res_builder.SetVisibility(visibility);
136 }
137
138 if (res->id.is_valid()) {
139 res_builder.SetId(res->id);
140 }
141
142 if (res->allow_new) {
143 AllowNew allow_new;
144 allow_new.source = res->source;
145 allow_new.comment = res->comment;
146 res_builder.SetAllowNew(allow_new);
147 }
148
149 if (res->overlayable_item) {
150 res_builder.SetOverlayable(res->overlayable_item.value());
151 }
152
153 if (res->value != nullptr) {
154 // Attach the comment, source and config to the value.
155 res->value->SetComment(std::move(res->comment));
156 res->value->SetSource(std::move(res->source));
157 res_builder.SetValue(std::move(res->value), res->config, res->product);
158 }
159
160 if (res->staged_alias) {
161 res_builder.SetStagedId(res->staged_alias.value());
162 }
163
164 bool error = false;
165 if (!res->name.entry.empty()) {
166 if (!table->AddResource(res_builder.Build(), diag)) {
167 return false;
168 }
169 }
170 for (ParsedResource& child : res->child_resources) {
171 error |= !AddResourcesToTable(table, diag, &child);
172 }
173 return !error;
174 }
175
176 // Convenient aliases for more readable function calls.
177 enum { kAllowRawString = true, kNoRawString = false };
178
ResourceParser(android::IDiagnostics * diag,ResourceTable * table,const android::Source & source,const ConfigDescription & config,const ResourceParserOptions & options)179 ResourceParser::ResourceParser(android::IDiagnostics* diag, ResourceTable* table,
180 const android::Source& source, const ConfigDescription& config,
181 const ResourceParserOptions& options)
182 : diag_(diag), table_(table), source_(source), config_(config), options_(options) {
183 }
184
185 // Base class Node for representing the various Spans and UntranslatableSections of an XML string.
186 // This will be used to traverse and flatten the XML string into a single std::string, with all
187 // Span and Untranslatable data maintained in parallel, as indices into the string.
188 class Node {
189 public:
190 virtual ~Node() = default;
191
192 // Adds the given child node to this parent node's set of child nodes, moving ownership to the
193 // parent node as well.
194 // Returns a pointer to the child node that was added as a convenience.
195 template <typename T>
AddChild(std::unique_ptr<T> node)196 T* AddChild(std::unique_ptr<T> node) {
197 T* raw_ptr = node.get();
198 children.push_back(std::move(node));
199 return raw_ptr;
200 }
201
Build(StringBuilder * builder) const202 virtual void Build(StringBuilder* builder) const {
203 for (const auto& child : children) {
204 child->Build(builder);
205 }
206 }
207
208 std::vector<std::unique_ptr<Node>> children;
209 };
210
211 // A chunk of text in the XML string. This lives between other tags, such as XLIFF tags and Spans.
212 class SegmentNode : public Node {
213 public:
214 std::string data;
215
Build(StringBuilder * builder) const216 void Build(StringBuilder* builder) const override {
217 builder->AppendText(data);
218 }
219 };
220
221 // A tag that will be encoded into the final flattened string. Tags like <b> or <i>.
222 class SpanNode : public Node {
223 public:
224 std::string name;
225
Build(StringBuilder * builder) const226 void Build(StringBuilder* builder) const override {
227 StringBuilder::SpanHandle span_handle = builder->StartSpan(name);
228 Node::Build(builder);
229 builder->EndSpan(span_handle);
230 }
231 };
232
233 // An XLIFF 'g' tag, which marks a section of the string as untranslatable.
234 class UntranslatableNode : public Node {
235 public:
Build(StringBuilder * builder) const236 void Build(StringBuilder* builder) const override {
237 StringBuilder::UntranslatableHandle handle = builder->StartUntranslatable();
238 Node::Build(builder);
239 builder->EndUntranslatable(handle);
240 }
241 };
242
243 // Build a string from XML that converts nested elements into Span objects.
FlattenXmlSubtree(xml::XmlPullParser * parser,std::string * out_raw_string,android::StyleString * out_style_string,std::vector<UntranslatableSection> * out_untranslatable_sections)244 bool ResourceParser::FlattenXmlSubtree(
245 xml::XmlPullParser* parser, std::string* out_raw_string, android::StyleString* out_style_string,
246 std::vector<UntranslatableSection>* out_untranslatable_sections) {
247 std::string raw_string;
248 std::string current_text;
249
250 // The first occurrence of a <xliff:g> tag. Nested <xliff:g> tags are illegal.
251 std::optional<size_t> untranslatable_start_depth;
252
253 Node root;
254 std::vector<Node*> node_stack;
255 node_stack.push_back(&root);
256
257 bool saw_span_node = false;
258 SegmentNode* first_segment = nullptr;
259 SegmentNode* last_segment = nullptr;
260
261 size_t depth = 1;
262 while (depth > 0 && xml::XmlPullParser::IsGoodEvent(parser->Next())) {
263 const xml::XmlPullParser::Event event = parser->event();
264
265 // First take care of any SegmentNodes that should be created.
266 if (event == xml::XmlPullParser::Event::kStartElement
267 || event == xml::XmlPullParser::Event::kEndElement) {
268 if (!current_text.empty()) {
269 auto segment_node = util::make_unique<SegmentNode>();
270 segment_node->data = std::move(current_text);
271
272 last_segment = node_stack.back()->AddChild(std::move(segment_node));
273 if (first_segment == nullptr) {
274 first_segment = last_segment;
275 }
276 current_text = {};
277 }
278 }
279
280 switch (event) {
281 case xml::XmlPullParser::Event::kText: {
282 current_text += parser->text();
283 raw_string += parser->text();
284 } break;
285
286 case xml::XmlPullParser::Event::kStartElement: {
287 if (parser->element_namespace().empty()) {
288 // This is an HTML tag which we encode as a span. Add it to the span stack.
289 std::unique_ptr<SpanNode> span_node = util::make_unique<SpanNode>();
290 span_node->name = parser->element_name();
291 const auto end_attr_iter = parser->end_attributes();
292 for (auto attr_iter = parser->begin_attributes(); attr_iter != end_attr_iter;
293 ++attr_iter) {
294 span_node->name += ";";
295 span_node->name += attr_iter->name;
296 span_node->name += "=";
297 span_node->name += attr_iter->value;
298 }
299
300 node_stack.push_back(node_stack.back()->AddChild(std::move(span_node)));
301 saw_span_node = true;
302 } else if (parser->element_namespace() == sXliffNamespaceUri) {
303 // This is an XLIFF tag, which is not encoded as a span.
304 if (parser->element_name() == "g") {
305 // Check that an 'untranslatable' tag is not already being processed. Nested
306 // <xliff:g> tags are illegal.
307 if (untranslatable_start_depth) {
308 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
309 << "illegal nested XLIFF 'g' tag");
310 return false;
311 } else {
312 // Mark the beginning of an 'untranslatable' section.
313 untranslatable_start_depth = depth;
314 node_stack.push_back(
315 node_stack.back()->AddChild(util::make_unique<UntranslatableNode>()));
316 }
317 } else {
318 // Ignore unknown XLIFF tags, but don't warn.
319 node_stack.push_back(node_stack.back()->AddChild(util::make_unique<Node>()));
320 }
321 } else {
322 // Besides XLIFF, any other namespaced tag is unsupported and ignored.
323 diag_->Warn(android::DiagMessage(source_.WithLine(parser->line_number()))
324 << "ignoring element '" << parser->element_name()
325 << "' with unknown namespace '" << parser->element_namespace() << "'");
326 node_stack.push_back(node_stack.back()->AddChild(util::make_unique<Node>()));
327 }
328
329 // Enter one level inside the element.
330 depth++;
331 } break;
332
333 case xml::XmlPullParser::Event::kEndElement: {
334 // Return one level from within the element.
335 depth--;
336 if (depth == 0) {
337 break;
338 }
339
340 node_stack.pop_back();
341 if (untranslatable_start_depth == depth) {
342 // This is the end of an untranslatable section.
343 untranslatable_start_depth = {};
344 }
345 } break;
346
347 default:
348 // ignore.
349 break;
350 }
351 }
352
353 // Validity check to make sure we processed all the nodes.
354 CHECK(node_stack.size() == 1u);
355 CHECK(node_stack.back() == &root);
356
357 if (!saw_span_node) {
358 // If there were no spans, we must treat this string a little differently (according to AAPT).
359 // Find and strip the leading whitespace from the first segment, and the trailing whitespace
360 // from the last segment.
361 if (first_segment != nullptr) {
362 // Trim leading whitespace.
363 StringPiece trimmed = util::TrimLeadingWhitespace(first_segment->data);
364 if (trimmed.size() != first_segment->data.size()) {
365 first_segment->data = std::string(trimmed);
366 }
367 }
368
369 if (last_segment != nullptr) {
370 // Trim trailing whitespace.
371 StringPiece trimmed = util::TrimTrailingWhitespace(last_segment->data);
372 if (trimmed.size() != last_segment->data.size()) {
373 last_segment->data = std::string(trimmed);
374 }
375 }
376 }
377
378 // Have the XML structure flatten itself into the StringBuilder. The StringBuilder will take
379 // care of recording the correctly adjusted Spans and UntranslatableSections.
380 StringBuilder builder;
381 root.Build(&builder);
382 if (!builder) {
383 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
384 << builder.GetError());
385 return false;
386 }
387
388 ResourceUtils::FlattenedXmlString flattened_string = builder.GetFlattenedString();
389 *out_raw_string = std::move(raw_string);
390 *out_untranslatable_sections = std::move(flattened_string.untranslatable_sections);
391 out_style_string->str = std::move(flattened_string.text);
392 out_style_string->spans = std::move(flattened_string.spans);
393 return true;
394 }
395
Parse(xml::XmlPullParser * parser)396 bool ResourceParser::Parse(xml::XmlPullParser* parser) {
397 bool error = false;
398 const size_t depth = parser->depth();
399 while (xml::XmlPullParser::NextChildNode(parser, depth)) {
400 if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
401 // Skip comments and text.
402 continue;
403 }
404
405 if (!parser->element_namespace().empty() || parser->element_name() != "resources") {
406 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
407 << "root element must be <resources>");
408 return false;
409 }
410
411 error |= !ParseResources(parser);
412 break;
413 };
414
415 if (parser->event() == xml::XmlPullParser::Event::kBadDocument) {
416 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
417 << "xml parser error: " << parser->error());
418 return false;
419 }
420 return !error;
421 }
422
ParseResources(xml::XmlPullParser * parser)423 bool ResourceParser::ParseResources(xml::XmlPullParser* parser) {
424 std::set<ResourceName> stripped_resources;
425
426 bool error = false;
427 std::string comment;
428 const size_t depth = parser->depth();
429 while (xml::XmlPullParser::NextChildNode(parser, depth)) {
430 const xml::XmlPullParser::Event event = parser->event();
431 if (event == xml::XmlPullParser::Event::kComment) {
432 comment = parser->comment();
433 continue;
434 }
435
436 if (event == xml::XmlPullParser::Event::kText) {
437 if (!util::TrimWhitespace(parser->text()).empty()) {
438 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
439 << "plain text not allowed here");
440 error = true;
441 }
442 continue;
443 }
444
445 CHECK(event == xml::XmlPullParser::Event::kStartElement);
446
447 if (!parser->element_namespace().empty()) {
448 // Skip unknown namespace.
449 continue;
450 }
451
452 std::string element_name = parser->element_name();
453 if (element_name == "skip" || element_name == "eat-comment") {
454 comment = "";
455 continue;
456 }
457
458 ParsedResource parsed_resource;
459 parsed_resource.config = config_;
460 parsed_resource.source = source_.WithLine(parser->line_number());
461 parsed_resource.comment = std::move(comment);
462 comment.clear();
463 if (options_.visibility) {
464 parsed_resource.visibility_level = options_.visibility.value();
465 }
466
467 // Extract the product name if it exists.
468 if (std::optional<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) {
469 parsed_resource.product = std::string(maybe_product.value());
470 }
471
472 // Parse the resource regardless of product.
473 if (!ParseResource(parser, &parsed_resource)) {
474 error = true;
475 continue;
476 }
477
478 if (!AddResourcesToTable(table_, diag_, &parsed_resource)) {
479 error = true;
480 }
481 }
482
483 // Check that we included at least one variant of each stripped resource.
484 for (const ResourceName& stripped_resource : stripped_resources) {
485 if (!table_->FindResource(stripped_resource)) {
486 // Failed to find the resource.
487 diag_->Error(android::DiagMessage(source_)
488 << "resource '" << stripped_resource
489 << "' was filtered out but no product variant remains");
490 error = true;
491 }
492 }
493
494 return !error;
495 }
496
ParseResource(xml::XmlPullParser * parser,ParsedResource * out_resource)497 bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
498 ParsedResource* out_resource) {
499 struct ItemTypeFormat {
500 ResourceType type;
501 uint32_t format;
502 };
503
504 using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*,
505 ParsedResource*)>;
506
507 static const auto elToItemMap = ImmutableMap<std::string, ItemTypeFormat>::CreatePreSorted({
508 {"bool", {ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN}},
509 {"color", {ResourceType::kColor, android::ResTable_map::TYPE_COLOR}},
510 {"configVarying", {ResourceType::kConfigVarying, android::ResTable_map::TYPE_ANY}},
511 {"dimen",
512 {ResourceType::kDimen,
513 android::ResTable_map::TYPE_FLOAT | android::ResTable_map::TYPE_FRACTION |
514 android::ResTable_map::TYPE_DIMENSION}},
515 {"drawable", {ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR}},
516 {"fraction",
517 {ResourceType::kFraction,
518 android::ResTable_map::TYPE_FLOAT | android::ResTable_map::TYPE_FRACTION |
519 android::ResTable_map::TYPE_DIMENSION}},
520 {"integer", {ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER}},
521 {"string", {ResourceType::kString, android::ResTable_map::TYPE_STRING}},
522 });
523
524 static const auto elToBagMap = ImmutableMap<std::string, BagParseFunc>::CreatePreSorted({
525 {"add-resource", std::mem_fn(&ResourceParser::ParseAddResource)},
526 {"array", std::mem_fn(&ResourceParser::ParseArray)},
527 {"attr", std::mem_fn(&ResourceParser::ParseAttr)},
528 {"configVarying",
529 std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kConfigVarying,
530 std::placeholders::_2, std::placeholders::_3)},
531 {"declare-styleable", std::mem_fn(&ResourceParser::ParseDeclareStyleable)},
532 {"integer-array", std::mem_fn(&ResourceParser::ParseIntegerArray)},
533 {"java-symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
534 {"overlayable", std::mem_fn(&ResourceParser::ParseOverlayable)},
535 {"plurals", std::mem_fn(&ResourceParser::ParsePlural)},
536 {"public", std::mem_fn(&ResourceParser::ParsePublic)},
537 {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)},
538 {"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)},
539 {"staging-public-group-final", std::mem_fn(&ResourceParser::ParseStagingPublicGroupFinal)},
540 {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)},
541 {"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle,
542 std::placeholders::_2, std::placeholders::_3)},
543 {"symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
544 });
545
546 std::string resource_type = parser->element_name();
547
548 // The value format accepted for this resource.
549 uint32_t resource_format = 0u;
550
551 bool can_be_item = true;
552 bool can_be_bag = true;
553 if (resource_type == "item") {
554 can_be_bag = false;
555
556 // The default format for <item> is any. If a format attribute is present, that one will
557 // override the default.
558 resource_format = android::ResTable_map::TYPE_ANY;
559
560 // Items have their type encoded in the type attribute.
561 if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
562 resource_type = std::string(maybe_type.value());
563 } else {
564 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
565 << "<item> must have a 'type' attribute");
566 return false;
567 }
568
569 if (std::optional<StringPiece> maybe_format = xml::FindNonEmptyAttribute(parser, "format")) {
570 // An explicit format for this resource was specified. The resource will
571 // retain its type in its name, but the accepted value for this type is
572 // overridden.
573 resource_format = ParseFormatTypeNoEnumsOrFlags(maybe_format.value());
574 if (!resource_format) {
575 diag_->Error(android::DiagMessage(out_resource->source)
576 << "'" << maybe_format.value() << "' is an invalid format");
577 return false;
578 }
579 }
580 } else if (resource_type == "bag") {
581 can_be_item = false;
582
583 // Bags have their type encoded in the type attribute.
584 if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
585 resource_type = std::string(maybe_type.value());
586 } else {
587 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
588 << "<bag> must have a 'type' attribute");
589 return false;
590 }
591 }
592
593 // Get the name of the resource. This will be checked later, because not all
594 // XML elements require a name.
595 std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
596
597 if (resource_type == "id") {
598 if (!maybe_name) {
599 diag_->Error(android::DiagMessage(out_resource->source)
600 << "<" << parser->element_name() << "> missing 'name' attribute");
601 return false;
602 }
603
604 out_resource->name.type =
605 ResourceNamedTypeWithDefaultName(ResourceType::kId).ToResourceNamedType();
606 out_resource->name.entry = std::string(maybe_name.value());
607
608 // Ids either represent a unique resource id or reference another resource id
609 auto item = ParseItem(parser, out_resource, resource_format);
610 if (!item) {
611 return false;
612 }
613
614 String* empty = ValueCast<String>(out_resource->value.get());
615 if (empty && *empty->value == "") {
616 // If no inner element exists, represent a unique identifier
617 out_resource->value = util::make_unique<Id>();
618 } else {
619 Reference* ref = ValueCast<Reference>(out_resource->value.get());
620 if (ref && !ref->name && !ref->id) {
621 // A null reference also means there is no inner element when ids are in the form:
622 // <id name="name"/>
623 out_resource->value = util::make_unique<Id>();
624 } else if (!ref || ref->name.value().type.type != ResourceType::kId) {
625 // If an inner element exists, the inner element must be a reference to another resource id
626 diag_->Error(android::DiagMessage(out_resource->source)
627 << "<" << parser->element_name()
628 << "> inner element must either be a resource reference or empty");
629 return false;
630 }
631 }
632
633 return true;
634 } else if (resource_type == "macro") {
635 if (!maybe_name) {
636 diag_->Error(android::DiagMessage(out_resource->source)
637 << "<" << parser->element_name() << "> missing 'name' attribute");
638 return false;
639 }
640
641 out_resource->name.type =
642 ResourceNamedTypeWithDefaultName(ResourceType::kMacro).ToResourceNamedType();
643 out_resource->name.entry = std::string(maybe_name.value());
644 return ParseMacro(parser, out_resource);
645 }
646
647 if (can_be_item) {
648 const auto item_iter = elToItemMap.find(resource_type);
649 if (item_iter != elToItemMap.end()) {
650 // This is an item, record its type and format and start parsing.
651
652 if (!maybe_name) {
653 diag_->Error(android::DiagMessage(out_resource->source)
654 << "<" << parser->element_name() << "> missing 'name' attribute");
655 return false;
656 }
657
658 out_resource->name.type =
659 ResourceNamedTypeWithDefaultName(item_iter->second.type).ToResourceNamedType();
660 out_resource->name.entry = std::string(maybe_name.value());
661
662 // Only use the implied format of the type when there is no explicit format.
663 if (resource_format == 0u) {
664 resource_format = item_iter->second.format;
665 }
666
667 if (!ParseItem(parser, out_resource, resource_format)) {
668 return false;
669 }
670 return true;
671 }
672 }
673
674 // This might be a bag or something.
675 if (can_be_bag) {
676 const auto bag_iter = elToBagMap.find(resource_type);
677 if (bag_iter != elToBagMap.end()) {
678 // Ensure we have a name (unless this is a <public-group> or <overlayable>).
679 if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag &&
680 resource_type != kStagingPublicGroupFinalTag && resource_type != "overlayable") {
681 if (!maybe_name) {
682 diag_->Error(android::DiagMessage(out_resource->source)
683 << "<" << parser->element_name() << "> missing 'name' attribute");
684 return false;
685 }
686
687 out_resource->name.entry = std::string(maybe_name.value());
688 }
689
690 // Call the associated parse method. The type will be filled in by the
691 // parse func.
692 if (!bag_iter->second(this, parser, out_resource)) {
693 return false;
694 }
695 return true;
696 }
697 }
698
699 if (can_be_item) {
700 // Try parsing the elementName (or type) as a resource. These shall only be
701 // resources like 'layout' or 'xml' and they can only be references.
702 std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(resource_type);
703 if (parsed_type) {
704 if (!maybe_name) {
705 diag_->Error(android::DiagMessage(out_resource->source)
706 << "<" << parser->element_name() << "> missing 'name' attribute");
707 return false;
708 }
709
710 out_resource->name.type = parsed_type->ToResourceNamedType();
711 out_resource->name.entry = std::string(maybe_name.value());
712 out_resource->value = ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
713 if (!out_resource->value) {
714 diag_->Error(android::DiagMessage(out_resource->source)
715 << "invalid value for type '" << *parsed_type << "'. Expected a reference");
716 return false;
717 }
718 return true;
719 }
720 }
721
722 // If the resource type was not recognized, write the error and return false.
723 diag_->Error(android::DiagMessage(out_resource->source)
724 << "unknown resource type '" << resource_type << "'");
725 return false;
726 }
727
ParseItem(xml::XmlPullParser * parser,ParsedResource * out_resource,const uint32_t format)728 bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
729 ParsedResource* out_resource,
730 const uint32_t format) {
731 if (format == android::ResTable_map::TYPE_STRING) {
732 return ParseString(parser, out_resource);
733 }
734
735 out_resource->value = ParseXml(parser, format, kNoRawString);
736 if (!out_resource->value) {
737 diag_->Error(android::DiagMessage(out_resource->source)
738 << "invalid " << out_resource->name.type);
739 return false;
740 }
741 return true;
742 }
743
CreateFlattenSubTree(xml::XmlPullParser * parser)744 std::optional<FlattenedXmlSubTree> ResourceParser::CreateFlattenSubTree(
745 xml::XmlPullParser* parser) {
746 const size_t begin_xml_line = parser->line_number();
747
748 std::string raw_value;
749 android::StyleString style_string;
750 std::vector<UntranslatableSection> untranslatable_sections;
751 if (!FlattenXmlSubtree(parser, &raw_value, &style_string, &untranslatable_sections)) {
752 return {};
753 }
754
755 return FlattenedXmlSubTree{.raw_value = raw_value,
756 .style_string = style_string,
757 .untranslatable_sections = untranslatable_sections,
758 .namespace_resolver = parser,
759 .source = source_.WithLine(begin_xml_line)};
760 }
761
762 /**
763 * Reads the entire XML subtree and attempts to parse it as some Item,
764 * with typeMask denoting which items it can be. If allowRawValue is
765 * true, a RawString is returned if the XML couldn't be parsed as
766 * an Item. If allowRawValue is false, nullptr is returned in this
767 * case.
768 */
ParseXml(xml::XmlPullParser * parser,const uint32_t type_mask,const bool allow_raw_value)769 std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, const uint32_t type_mask,
770 const bool allow_raw_value) {
771 auto sub_tree = CreateFlattenSubTree(parser);
772 if (!sub_tree.has_value()) {
773 return {};
774 }
775 return ParseXml(sub_tree.value(), type_mask, allow_raw_value, *table_, config_, *diag_);
776 }
777
ParseXml(const FlattenedXmlSubTree & xmlsub_tree,const uint32_t type_mask,const bool allow_raw_value,ResourceTable & table,const android::ConfigDescription & config,android::IDiagnostics & diag)778 std::unique_ptr<Item> ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub_tree,
779 const uint32_t type_mask, const bool allow_raw_value,
780 ResourceTable& table,
781 const android::ConfigDescription& config,
782 android::IDiagnostics& diag) {
783 if (!xmlsub_tree.style_string.spans.empty()) {
784 // This can only be a StyledString.
785 std::unique_ptr<StyledString> styled_string =
786 util::make_unique<StyledString>(table.string_pool.MakeRef(
787 xmlsub_tree.style_string,
788 android::StringPool::Context(android::StringPool::Context::kNormalPriority, config)));
789 styled_string->untranslatable_sections = xmlsub_tree.untranslatable_sections;
790 return std::move(styled_string);
791 }
792
793 auto on_create_reference = [&](const ResourceName& name) {
794 // name.package can be empty here, as it will assume the package name of the
795 // table.
796 auto id = util::make_unique<Id>();
797 id->SetSource(xmlsub_tree.source);
798 return table.AddResource(NewResourceBuilder(name).SetValue(std::move(id)).Build(), &diag);
799 };
800
801 // Process the raw value.
802 std::unique_ptr<Item> processed_item = ResourceUtils::TryParseItemForAttribute(
803 &diag, xmlsub_tree.raw_value, type_mask, on_create_reference);
804 if (processed_item) {
805 // Fix up the reference.
806 if (auto ref = ValueCast<Reference>(processed_item.get())) {
807 ref->allow_raw = allow_raw_value;
808 ResolvePackage(xmlsub_tree.namespace_resolver, ref);
809 }
810 return processed_item;
811 }
812
813 // Try making a regular string.
814 if (type_mask & android::ResTable_map::TYPE_STRING) {
815 // Use the trimmed, escaped string.
816 std::unique_ptr<String> string = util::make_unique<String>(table.string_pool.MakeRef(
817 xmlsub_tree.style_string.str, android::StringPool::Context(config)));
818 string->untranslatable_sections = xmlsub_tree.untranslatable_sections;
819 return std::move(string);
820 }
821
822 if (allow_raw_value) {
823 // We can't parse this so return a RawString if we are allowed.
824 return util::make_unique<RawString>(table.string_pool.MakeRef(
825 util::TrimWhitespace(xmlsub_tree.raw_value), android::StringPool::Context(config)));
826 } else if (util::TrimWhitespace(xmlsub_tree.raw_value).empty()) {
827 // If the text is empty, and the value is not allowed to be a string, encode it as a @null.
828 return ResourceUtils::MakeNull();
829 }
830 return {};
831 }
832
ParseString(xml::XmlPullParser * parser,ParsedResource * out_resource)833 bool ResourceParser::ParseString(xml::XmlPullParser* parser,
834 ParsedResource* out_resource) {
835 bool formatted = true;
836 if (std::optional<StringPiece> formatted_attr = xml::FindAttribute(parser, "formatted")) {
837 std::optional<bool> maybe_formatted = ResourceUtils::ParseBool(formatted_attr.value());
838 if (!maybe_formatted) {
839 diag_->Error(android::DiagMessage(out_resource->source)
840 << "invalid value for 'formatted'. Must be a boolean");
841 return false;
842 }
843 formatted = maybe_formatted.value();
844 }
845
846 bool translatable = options_.translatable;
847 if (std::optional<StringPiece> translatable_attr = xml::FindAttribute(parser, "translatable")) {
848 std::optional<bool> maybe_translatable = ResourceUtils::ParseBool(translatable_attr.value());
849 if (!maybe_translatable) {
850 diag_->Error(android::DiagMessage(out_resource->source)
851 << "invalid value for 'translatable'. Must be a boolean");
852 return false;
853 }
854 translatable = maybe_translatable.value();
855 }
856
857 out_resource->value =
858 ParseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
859 if (!out_resource->value) {
860 diag_->Error(android::DiagMessage(out_resource->source) << "not a valid string");
861 return false;
862 }
863
864 if (String* string_value = ValueCast<String>(out_resource->value.get())) {
865 string_value->SetTranslatable(translatable);
866
867 if (formatted && translatable) {
868 if (!util::VerifyJavaStringFormat(*string_value->value)) {
869 android::DiagMessage msg(out_resource->source);
870 msg << "multiple substitutions specified in non-positional format; "
871 "did you mean to add the formatted=\"false\" attribute?";
872 if (options_.error_on_positional_arguments) {
873 diag_->Error(msg);
874 return false;
875 }
876
877 diag_->Warn(msg);
878 }
879 }
880
881 } else if (StyledString* string_value = ValueCast<StyledString>(out_resource->value.get())) {
882 string_value->SetTranslatable(translatable);
883 }
884 return true;
885 }
886
ParseMacro(xml::XmlPullParser * parser,ParsedResource * out_resource)887 bool ResourceParser::ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_resource) {
888 auto sub_tree = CreateFlattenSubTree(parser);
889 if (!sub_tree) {
890 return false;
891 }
892
893 if (out_resource->config != ConfigDescription::DefaultConfig()) {
894 diag_->Error(android::DiagMessage(out_resource->source)
895 << "<macro> tags cannot be declared in configurations other than the default "
896 "configuration'");
897 return false;
898 }
899
900 auto macro = std::make_unique<Macro>();
901 macro->raw_value = std::move(sub_tree->raw_value);
902 macro->style_string = std::move(sub_tree->style_string);
903 macro->untranslatable_sections = std::move(sub_tree->untranslatable_sections);
904
905 for (const auto& decl : parser->package_decls()) {
906 macro->alias_namespaces.emplace_back(
907 Macro::Namespace{.alias = decl.prefix,
908 .package_name = decl.package.package,
909 .is_private = decl.package.private_namespace});
910 }
911
912 out_resource->value = std::move(macro);
913 return true;
914 }
915
ParsePublic(xml::XmlPullParser * parser,ParsedResource * out_resource)916 bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) {
917 if (options_.visibility) {
918 diag_->Error(android::DiagMessage(out_resource->source)
919 << "<public> tag not allowed with --visibility flag");
920 return false;
921 }
922
923 if (out_resource->config != ConfigDescription::DefaultConfig()) {
924 diag_->Warn(android::DiagMessage(out_resource->source)
925 << "ignoring configuration '" << out_resource->config << "' for <public> tag");
926 }
927
928 std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
929 if (!maybe_type) {
930 diag_->Error(android::DiagMessage(out_resource->source)
931 << "<public> must have a 'type' attribute");
932 return false;
933 }
934
935 std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(maybe_type.value());
936 if (!parsed_type) {
937 diag_->Error(android::DiagMessage(out_resource->source)
938 << "invalid resource type '" << maybe_type.value() << "' in <public>");
939 return false;
940 }
941
942 out_resource->name.type = parsed_type->ToResourceNamedType();
943
944 if (std::optional<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "id")) {
945 std::optional<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value());
946 if (!maybe_id) {
947 diag_->Error(android::DiagMessage(out_resource->source)
948 << "invalid resource ID '" << maybe_id_str.value() << "' in <public>");
949 return false;
950 }
951 out_resource->id = maybe_id.value();
952 }
953
954 if (parsed_type->type == ResourceType::kId) {
955 // An ID marked as public is also the definition of an ID.
956 out_resource->value = util::make_unique<Id>();
957 }
958
959 out_resource->visibility_level = Visibility::Level::kPublic;
960 return true;
961 }
962
963 template <typename Func>
ParseGroupImpl(xml::XmlPullParser * parser,ParsedResource * out_resource,const char * tag_name,android::IDiagnostics * diag,Func && func)964 bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
965 const char* tag_name, android::IDiagnostics* diag, Func&& func) {
966 if (out_resource->config != ConfigDescription::DefaultConfig()) {
967 diag->Warn(android::DiagMessage(out_resource->source)
968 << "ignoring configuration '" << out_resource->config << "' for <" << tag_name
969 << "> tag");
970 }
971
972 std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
973 if (!maybe_type) {
974 diag->Error(android::DiagMessage(out_resource->source)
975 << "<" << tag_name << "> must have a 'type' attribute");
976 return false;
977 }
978
979 std::optional<ResourceNamedTypeRef> maybe_parsed_type =
980 ParseResourceNamedType(maybe_type.value());
981 if (!maybe_parsed_type) {
982 diag->Error(android::DiagMessage(out_resource->source)
983 << "invalid resource type '" << maybe_type.value() << "' in <" << tag_name << ">");
984 return false;
985 }
986 auto parsed_type = maybe_parsed_type->ToResourceNamedType();
987
988 std::optional<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id");
989 if (!maybe_id_str) {
990 diag->Error(android::DiagMessage(out_resource->source)
991 << "<" << tag_name << "> must have a 'first-id' attribute");
992 return false;
993 }
994
995 std::optional<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value());
996 if (!maybe_id) {
997 diag->Error(android::DiagMessage(out_resource->source)
998 << "invalid resource ID '" << maybe_id_str.value() << "' in <" << tag_name << ">");
999 return false;
1000 }
1001
1002 std::string comment;
1003 ResourceId next_id = maybe_id.value();
1004 bool error = false;
1005 const size_t depth = parser->depth();
1006 while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1007 if (parser->event() == xml::XmlPullParser::Event::kComment) {
1008 comment = std::string(util::TrimWhitespace(parser->comment()));
1009 continue;
1010 } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1011 // Skip text.
1012 continue;
1013 }
1014
1015 const android::Source item_source = out_resource->source.WithLine(parser->line_number());
1016 const std::string& element_namespace = parser->element_namespace();
1017 const std::string& element_name = parser->element_name();
1018 if (element_namespace.empty() && element_name == "public") {
1019 auto maybe_name = xml::FindNonEmptyAttribute(parser, "name");
1020 if (!maybe_name) {
1021 diag->Error(android::DiagMessage(item_source) << "<public> must have a 'name' attribute");
1022 error = true;
1023 continue;
1024 }
1025
1026 if (xml::FindNonEmptyAttribute(parser, "id")) {
1027 diag->Error(android::DiagMessage(item_source)
1028 << "'id' is ignored within <" << tag_name << ">");
1029 error = true;
1030 continue;
1031 }
1032
1033 if (xml::FindNonEmptyAttribute(parser, "type")) {
1034 diag->Error(android::DiagMessage(item_source)
1035 << "'type' is ignored within <" << tag_name << ">");
1036 error = true;
1037 continue;
1038 }
1039
1040 if (maybe_name.value().substr(0, std::strlen("removed_")) == "removed_") {
1041 // Skip resources that have been removed from the framework, but leave a hole so that
1042 // other staged resources don't shift and break apps previously compiled against them
1043 next_id.id++;
1044 continue;
1045 }
1046
1047 ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
1048 .name = ResourceName{{}, parsed_type, std::string(maybe_name.value())},
1049 .source = item_source,
1050 .comment = std::move(comment),
1051 });
1052 comment.clear();
1053
1054 // Execute group specific code.
1055 func(entry_res, next_id);
1056
1057 next_id.id++;
1058 } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1059 diag->Error(android::DiagMessage(item_source) << ":" << element_name << ">");
1060 error = true;
1061 }
1062 }
1063 return !error;
1064 }
1065
ParseStagingPublicGroup(xml::XmlPullParser * parser,ParsedResource * out_resource)1066 bool ResourceParser::ParseStagingPublicGroup(xml::XmlPullParser* parser,
1067 ParsedResource* out_resource) {
1068 return ParseGroupImpl(parser, out_resource, kStagingPublicGroupTag, diag_,
1069 [](ParsedResource& parsed_entry, ResourceId id) {
1070 parsed_entry.id = id;
1071 parsed_entry.staged_api = true;
1072 parsed_entry.visibility_level = Visibility::Level::kPublic;
1073 });
1074 }
1075
ParseStagingPublicGroupFinal(xml::XmlPullParser * parser,ParsedResource * out_resource)1076 bool ResourceParser::ParseStagingPublicGroupFinal(xml::XmlPullParser* parser,
1077 ParsedResource* out_resource) {
1078 return ParseGroupImpl(parser, out_resource, kStagingPublicGroupFinalTag, diag_,
1079 [](ParsedResource& parsed_entry, ResourceId id) {
1080 parsed_entry.staged_alias = StagedId{id, parsed_entry.source};
1081 });
1082 }
1083
ParsePublicGroup(xml::XmlPullParser * parser,ParsedResource * out_resource)1084 bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1085 if (options_.visibility) {
1086 diag_->Error(android::DiagMessage(out_resource->source)
1087 << "<" << kPublicGroupTag << "> tag not allowed with --visibility flag");
1088 return false;
1089 }
1090
1091 return ParseGroupImpl(parser, out_resource, kPublicGroupTag, diag_,
1092 [](ParsedResource& parsed_entry, ResourceId id) {
1093 parsed_entry.id = id;
1094 parsed_entry.visibility_level = Visibility::Level::kPublic;
1095 });
1096 }
1097
ParseSymbolImpl(xml::XmlPullParser * parser,ParsedResource * out_resource)1098 bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser,
1099 ParsedResource* out_resource) {
1100 std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
1101 if (!maybe_type) {
1102 diag_->Error(android::DiagMessage(out_resource->source)
1103 << "<" << parser->element_name() << "> must have a 'type' attribute");
1104 return false;
1105 }
1106
1107 std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(maybe_type.value());
1108 if (!parsed_type) {
1109 diag_->Error(android::DiagMessage(out_resource->source)
1110 << "invalid resource type '" << maybe_type.value() << "' in <"
1111 << parser->element_name() << ">");
1112 return false;
1113 }
1114
1115 out_resource->name.type = parsed_type->ToResourceNamedType();
1116 return true;
1117 }
1118
ParseSymbol(xml::XmlPullParser * parser,ParsedResource * out_resource)1119 bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1120 if (options_.visibility) {
1121 diag_->Error(android::DiagMessage(out_resource->source)
1122 << "<java-symbol> and <symbol> tags not allowed with --visibility flag");
1123 return false;
1124 }
1125 if (out_resource->config != ConfigDescription::DefaultConfig()) {
1126 diag_->Warn(android::DiagMessage(out_resource->source)
1127 << "ignoring configuration '" << out_resource->config << "' for <"
1128 << parser->element_name() << "> tag");
1129 }
1130
1131 if (!ParseSymbolImpl(parser, out_resource)) {
1132 return false;
1133 }
1134
1135 out_resource->visibility_level = Visibility::Level::kPrivate;
1136 return true;
1137 }
1138
ParseOverlayable(xml::XmlPullParser * parser,ParsedResource * out_resource)1139 bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1140 if (out_resource->config != ConfigDescription::DefaultConfig()) {
1141 diag_->Warn(android::DiagMessage(out_resource->source)
1142 << "ignoring configuration '" << out_resource->config << "' for <overlayable> tag");
1143 }
1144
1145 std::optional<StringPiece> overlayable_name = xml::FindNonEmptyAttribute(parser, "name");
1146 if (!overlayable_name) {
1147 diag_->Error(android::DiagMessage(out_resource->source)
1148 << "<overlayable> tag must have a 'name' attribute");
1149 return false;
1150 }
1151
1152 const std::string kActorUriScheme =
1153 android::base::StringPrintf("%s://", Overlayable::kActorScheme);
1154 std::optional<StringPiece> overlayable_actor = xml::FindNonEmptyAttribute(parser, "actor");
1155 if (overlayable_actor && !util::StartsWith(overlayable_actor.value(), kActorUriScheme)) {
1156 diag_->Error(android::DiagMessage(out_resource->source)
1157 << "specified <overlayable> tag 'actor' attribute must use the scheme '"
1158 << Overlayable::kActorScheme << "'");
1159 return false;
1160 }
1161
1162 // Create a overlayable entry grouping that represents this <overlayable>
1163 auto overlayable = std::make_shared<Overlayable>(
1164 overlayable_name.value(), (overlayable_actor) ? overlayable_actor.value() : "",
1165 source_);
1166
1167 bool error = false;
1168 std::string comment;
1169 PolicyFlags current_policies = PolicyFlags::NONE;
1170 const size_t start_depth = parser->depth();
1171 while (xml::XmlPullParser::IsGoodEvent(parser->Next())) {
1172 xml::XmlPullParser::Event event = parser->event();
1173 if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth) {
1174 // Break the loop when exiting the <overlayable>
1175 break;
1176 } else if (event == xml::XmlPullParser::Event::kEndElement
1177 && parser->depth() == start_depth + 1) {
1178 // Clear the current policies when exiting the <policy> tags
1179 current_policies = PolicyFlags::NONE;
1180 continue;
1181 } else if (event == xml::XmlPullParser::Event::kComment) {
1182 // Retrieve the comment of individual <item> tags
1183 comment = parser->comment();
1184 continue;
1185 } else if (event != xml::XmlPullParser::Event::kStartElement) {
1186 // Skip to the start of the next element
1187 continue;
1188 }
1189
1190 const android::Source element_source = source_.WithLine(parser->line_number());
1191 const std::string& element_name = parser->element_name();
1192 const std::string& element_namespace = parser->element_namespace();
1193 if (element_namespace.empty() && element_name == "item") {
1194 if (current_policies == PolicyFlags::NONE) {
1195 diag_->Error(android::DiagMessage(element_source)
1196 << "<item> within an <overlayable> must be inside a <policy> block");
1197 error = true;
1198 continue;
1199 }
1200
1201 // Items specify the name and type of resource that should be overlayable
1202 std::optional<StringPiece> item_name = xml::FindNonEmptyAttribute(parser, "name");
1203 if (!item_name) {
1204 diag_->Error(android::DiagMessage(element_source)
1205 << "<item> within an <overlayable> must have a 'name' attribute");
1206 error = true;
1207 continue;
1208 }
1209
1210 std::optional<StringPiece> item_type = xml::FindNonEmptyAttribute(parser, "type");
1211 if (!item_type) {
1212 diag_->Error(android::DiagMessage(element_source)
1213 << "<item> within an <overlayable> must have a 'type' attribute");
1214 error = true;
1215 continue;
1216 }
1217
1218 std::optional<ResourceNamedTypeRef> type = ParseResourceNamedType(item_type.value());
1219 if (!type) {
1220 diag_->Error(android::DiagMessage(element_source)
1221 << "invalid resource type '" << item_type.value()
1222 << "' in <item> within an <overlayable>");
1223 error = true;
1224 continue;
1225 }
1226
1227 OverlayableItem overlayable_item(overlayable);
1228 overlayable_item.policies = current_policies;
1229 overlayable_item.comment = comment;
1230 overlayable_item.source = element_source;
1231
1232 ParsedResource child_resource{};
1233 child_resource.name.type = type->ToResourceNamedType();
1234 child_resource.name.entry = std::string(item_name.value());
1235 child_resource.overlayable_item = overlayable_item;
1236 out_resource->child_resources.push_back(std::move(child_resource));
1237
1238 } else if (element_namespace.empty() && element_name == "policy") {
1239 if (current_policies != PolicyFlags::NONE) {
1240 // If the policy list is not empty, then we are currently inside a policy element
1241 diag_->Error(android::DiagMessage(element_source)
1242 << "<policy> blocks cannot be recursively nested");
1243 error = true;
1244 break;
1245 } else if (std::optional<StringPiece> maybe_type =
1246 xml::FindNonEmptyAttribute(parser, "type")) {
1247 // Parse the polices separated by vertical bar characters to allow for specifying multiple
1248 // policies. Items within the policy tag will have the specified policy.
1249 for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) {
1250 StringPiece trimmed_part = util::TrimWhitespace(part);
1251 const auto policy = std::find_if(kPolicyStringToFlag.begin(),
1252 kPolicyStringToFlag.end(),
1253 [trimmed_part](const auto& it) {
1254 return trimmed_part == it.first;
1255 });
1256 if (policy == kPolicyStringToFlag.end()) {
1257 diag_->Error(android::DiagMessage(element_source)
1258 << "<policy> has unsupported type '" << trimmed_part << "'");
1259 error = true;
1260 continue;
1261 }
1262
1263 current_policies |= policy->second;
1264 }
1265 } else {
1266 diag_->Error(android::DiagMessage(element_source)
1267 << "<policy> must have a 'type' attribute");
1268 error = true;
1269 continue;
1270 }
1271 } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1272 diag_->Error(android::DiagMessage(element_source)
1273 << "invalid element <" << element_name << "> "
1274 << " in <overlayable>");
1275 error = true;
1276 break;
1277 }
1278
1279 comment.clear();
1280 }
1281
1282 return !error;
1283 }
1284
ParseAddResource(xml::XmlPullParser * parser,ParsedResource * out_resource)1285 bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1286 if (ParseSymbolImpl(parser, out_resource)) {
1287 out_resource->visibility_level = Visibility::Level::kUndefined;
1288 out_resource->allow_new = true;
1289 return true;
1290 }
1291 return false;
1292 }
1293
ParseAttr(xml::XmlPullParser * parser,ParsedResource * out_resource)1294 bool ResourceParser::ParseAttr(xml::XmlPullParser* parser,
1295 ParsedResource* out_resource) {
1296 return ParseAttrImpl(parser, out_resource, false);
1297 }
1298
ParseAttrImpl(xml::XmlPullParser * parser,ParsedResource * out_resource,bool weak)1299 bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser,
1300 ParsedResource* out_resource, bool weak) {
1301 out_resource->name.type =
1302 ResourceNamedTypeWithDefaultName(ResourceType::kAttr).ToResourceNamedType();
1303
1304 // Attributes only end up in default configuration.
1305 if (out_resource->config != ConfigDescription::DefaultConfig()) {
1306 diag_->Warn(android::DiagMessage(out_resource->source)
1307 << "ignoring configuration '" << out_resource->config << "' for attribute "
1308 << out_resource->name);
1309 out_resource->config = ConfigDescription::DefaultConfig();
1310 }
1311
1312 uint32_t type_mask = 0;
1313
1314 std::optional<StringPiece> maybe_format = xml::FindAttribute(parser, "format");
1315 if (maybe_format) {
1316 type_mask = ParseFormatAttribute(maybe_format.value());
1317 if (type_mask == 0) {
1318 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
1319 << "invalid attribute format '" << maybe_format.value() << "'");
1320 return false;
1321 }
1322 }
1323
1324 std::optional<int32_t> maybe_min, maybe_max;
1325
1326 if (std::optional<StringPiece> maybe_min_str = xml::FindAttribute(parser, "min")) {
1327 StringPiece min_str = util::TrimWhitespace(maybe_min_str.value());
1328 if (!min_str.empty()) {
1329 std::u16string min_str16 = android::util::Utf8ToUtf16(min_str);
1330 android::Res_value value;
1331 if (android::ResTable::stringToInt(min_str16.data(), min_str16.size(), &value)) {
1332 maybe_min = static_cast<int32_t>(value.data);
1333 }
1334 }
1335
1336 if (!maybe_min) {
1337 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
1338 << "invalid 'min' value '" << min_str << "'");
1339 return false;
1340 }
1341 }
1342
1343 if (std::optional<StringPiece> maybe_max_str = xml::FindAttribute(parser, "max")) {
1344 StringPiece max_str = util::TrimWhitespace(maybe_max_str.value());
1345 if (!max_str.empty()) {
1346 std::u16string max_str16 = android::util::Utf8ToUtf16(max_str);
1347 android::Res_value value;
1348 if (android::ResTable::stringToInt(max_str16.data(), max_str16.size(), &value)) {
1349 maybe_max = static_cast<int32_t>(value.data);
1350 }
1351 }
1352
1353 if (!maybe_max) {
1354 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
1355 << "invalid 'max' value '" << max_str << "'");
1356 return false;
1357 }
1358 }
1359
1360 if ((maybe_min || maybe_max) &&
1361 (type_mask & android::ResTable_map::TYPE_INTEGER) == 0) {
1362 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
1363 << "'min' and 'max' can only be used when format='integer'");
1364 return false;
1365 }
1366
1367 struct SymbolComparator {
1368 bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) const {
1369 return a.symbol.name.value() < b.symbol.name.value();
1370 }
1371 };
1372
1373 std::set<Attribute::Symbol, SymbolComparator> items;
1374
1375 std::string comment;
1376 bool error = false;
1377 const size_t depth = parser->depth();
1378 while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1379 if (parser->event() == xml::XmlPullParser::Event::kComment) {
1380 comment = std::string(util::TrimWhitespace(parser->comment()));
1381 continue;
1382 } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1383 // Skip text.
1384 continue;
1385 }
1386
1387 const android::Source item_source = source_.WithLine(parser->line_number());
1388 const std::string& element_namespace = parser->element_namespace();
1389 const std::string& element_name = parser->element_name();
1390 if (element_namespace.empty() && (element_name == "flag" || element_name == "enum")) {
1391 if (element_name == "enum") {
1392 if (type_mask & android::ResTable_map::TYPE_FLAGS) {
1393 diag_->Error(android::DiagMessage(item_source)
1394 << "can not define an <enum>; already defined a <flag>");
1395 error = true;
1396 continue;
1397 }
1398 type_mask |= android::ResTable_map::TYPE_ENUM;
1399
1400 } else if (element_name == "flag") {
1401 if (type_mask & android::ResTable_map::TYPE_ENUM) {
1402 diag_->Error(android::DiagMessage(item_source)
1403 << "can not define a <flag>; already defined an <enum>");
1404 error = true;
1405 continue;
1406 }
1407 type_mask |= android::ResTable_map::TYPE_FLAGS;
1408 }
1409
1410 if (std::optional<Attribute::Symbol> s = ParseEnumOrFlagItem(parser, element_name)) {
1411 Attribute::Symbol& symbol = s.value();
1412 ParsedResource child_resource;
1413 child_resource.name = symbol.symbol.name.value();
1414 child_resource.source = item_source;
1415 child_resource.value = util::make_unique<Id>();
1416 if (options_.visibility) {
1417 child_resource.visibility_level = options_.visibility.value();
1418 }
1419 out_resource->child_resources.push_back(std::move(child_resource));
1420
1421 symbol.symbol.SetComment(std::move(comment));
1422 symbol.symbol.SetSource(item_source);
1423
1424 auto insert_result = items.insert(std::move(symbol));
1425 if (!insert_result.second) {
1426 const Attribute::Symbol& existing_symbol = *insert_result.first;
1427 diag_->Error(android::DiagMessage(item_source)
1428 << "duplicate symbol '" << existing_symbol.symbol.name.value().entry << "'");
1429
1430 diag_->Note(android::DiagMessage(existing_symbol.symbol.GetSource())
1431 << "first defined here");
1432 error = true;
1433 }
1434 } else {
1435 error = true;
1436 }
1437 } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1438 diag_->Error(android::DiagMessage(item_source) << ":" << element_name << ">");
1439 error = true;
1440 }
1441
1442 comment = {};
1443 }
1444
1445 if (error) {
1446 return false;
1447 }
1448
1449 std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(
1450 type_mask ? type_mask : uint32_t{android::ResTable_map::TYPE_ANY});
1451 attr->SetWeak(weak);
1452 attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
1453 attr->min_int = maybe_min.value_or(std::numeric_limits<int32_t>::min());
1454 attr->max_int = maybe_max.value_or(std::numeric_limits<int32_t>::max());
1455 out_resource->value = std::move(attr);
1456 return true;
1457 }
1458
ParseEnumOrFlagItem(xml::XmlPullParser * parser,StringPiece tag)1459 std::optional<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(xml::XmlPullParser* parser,
1460 StringPiece tag) {
1461 const android::Source source = source_.WithLine(parser->line_number());
1462
1463 std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
1464 if (!maybe_name) {
1465 diag_->Error(android::DiagMessage(source)
1466 << "no attribute 'name' found for tag <" << tag << ">");
1467 return {};
1468 }
1469
1470 std::optional<StringPiece> maybe_value = xml::FindNonEmptyAttribute(parser, "value");
1471 if (!maybe_value) {
1472 diag_->Error(android::DiagMessage(source)
1473 << "no attribute 'value' found for tag <" << tag << ">");
1474 return {};
1475 }
1476
1477 std::u16string value16 = android::util::Utf8ToUtf16(maybe_value.value());
1478 android::Res_value val;
1479 if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) {
1480 diag_->Error(android::DiagMessage(source) << "invalid value '" << maybe_value.value()
1481 << "' for <" << tag << ">; must be an integer");
1482 return {};
1483 }
1484
1485 return Attribute::Symbol{
1486 Reference(ResourceNameRef({}, ResourceNamedTypeWithDefaultName(ResourceType::kId),
1487 maybe_name.value())),
1488 val.data, val.dataType};
1489 }
1490
ParseStyleItem(xml::XmlPullParser * parser,Style * style)1491 bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) {
1492 const android::Source source = source_.WithLine(parser->line_number());
1493
1494 std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
1495 if (!maybe_name) {
1496 diag_->Error(android::DiagMessage(source) << "<item> must have a 'name' attribute");
1497 return false;
1498 }
1499
1500 std::optional<Reference> maybe_key = ResourceUtils::ParseXmlAttributeName(maybe_name.value());
1501 if (!maybe_key) {
1502 diag_->Error(android::DiagMessage(source)
1503 << "invalid attribute name '" << maybe_name.value() << "'");
1504 return false;
1505 }
1506
1507 ResolvePackage(parser, &maybe_key.value());
1508 maybe_key.value().SetSource(source);
1509
1510 std::unique_ptr<Item> value = ParseXml(parser, 0, kAllowRawString);
1511 if (!value) {
1512 diag_->Error(android::DiagMessage(source) << "could not parse style item");
1513 return false;
1514 }
1515
1516 style->entries.push_back(Style::Entry{std::move(maybe_key.value()), std::move(value)});
1517 return true;
1518 }
1519
ParseStyle(const ResourceType type,xml::XmlPullParser * parser,ParsedResource * out_resource)1520 bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* parser,
1521 ParsedResource* out_resource) {
1522 out_resource->name.type = ResourceNamedTypeWithDefaultName(type).ToResourceNamedType();
1523
1524 std::unique_ptr<Style> style = util::make_unique<Style>();
1525
1526 std::optional<StringPiece> maybe_parent = xml::FindAttribute(parser, "parent");
1527 if (maybe_parent) {
1528 // If the parent is empty, we don't have a parent, but we also don't infer either.
1529 if (!maybe_parent.value().empty()) {
1530 std::string err_str;
1531 style->parent = ResourceUtils::ParseStyleParentReference(maybe_parent.value(), &err_str);
1532 if (!style->parent) {
1533 diag_->Error(android::DiagMessage(out_resource->source) << err_str);
1534 return false;
1535 }
1536
1537 // Transform the namespace prefix to the actual package name, and mark the reference as
1538 // private if appropriate.
1539 ResolvePackage(parser, &style->parent.value());
1540 }
1541
1542 } else {
1543 // No parent was specified, so try inferring it from the style name.
1544 std::string style_name = out_resource->name.entry;
1545 size_t pos = style_name.find_last_of(u'.');
1546 if (pos != std::string::npos) {
1547 style->parent_inferred = true;
1548 style->parent = Reference(ResourceName(
1549 {}, ResourceNamedTypeWithDefaultName(ResourceType::kStyle), style_name.substr(0, pos)));
1550 }
1551 }
1552
1553 bool error = false;
1554 const size_t depth = parser->depth();
1555 while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1556 if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1557 // Skip text and comments.
1558 continue;
1559 }
1560
1561 const std::string& element_namespace = parser->element_namespace();
1562 const std::string& element_name = parser->element_name();
1563 if (element_namespace == "" && element_name == "item") {
1564 error |= !ParseStyleItem(parser, style.get());
1565
1566 } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1567 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
1568 << ":" << element_name << ">");
1569 error = true;
1570 }
1571 }
1572
1573 if (error) {
1574 return false;
1575 }
1576
1577 out_resource->value = std::move(style);
1578 return true;
1579 }
1580
ParseArray(xml::XmlPullParser * parser,ParsedResource * out_resource)1581 bool ResourceParser::ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1582 uint32_t resource_format = android::ResTable_map::TYPE_ANY;
1583 if (std::optional<StringPiece> format_attr = xml::FindNonEmptyAttribute(parser, "format")) {
1584 resource_format = ParseFormatTypeNoEnumsOrFlags(format_attr.value());
1585 if (resource_format == 0u) {
1586 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
1587 << "'" << format_attr.value() << "' is an invalid format");
1588 return false;
1589 }
1590 }
1591 return ParseArrayImpl(parser, out_resource, resource_format);
1592 }
1593
ParseIntegerArray(xml::XmlPullParser * parser,ParsedResource * out_resource)1594 bool ResourceParser::ParseIntegerArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1595 return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_INTEGER);
1596 }
1597
ParseStringArray(xml::XmlPullParser * parser,ParsedResource * out_resource)1598 bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1599 return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_STRING);
1600 }
1601
ParseArrayImpl(xml::XmlPullParser * parser,ParsedResource * out_resource,const uint32_t typeMask)1602 bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser,
1603 ParsedResource* out_resource,
1604 const uint32_t typeMask) {
1605 out_resource->name.type =
1606 ResourceNamedTypeWithDefaultName(ResourceType::kArray).ToResourceNamedType();
1607
1608 std::unique_ptr<Array> array = util::make_unique<Array>();
1609
1610 bool translatable = options_.translatable;
1611 if (std::optional<StringPiece> translatable_attr = xml::FindAttribute(parser, "translatable")) {
1612 std::optional<bool> maybe_translatable = ResourceUtils::ParseBool(translatable_attr.value());
1613 if (!maybe_translatable) {
1614 diag_->Error(android::DiagMessage(out_resource->source)
1615 << "invalid value for 'translatable'. Must be a boolean");
1616 return false;
1617 }
1618 translatable = maybe_translatable.value();
1619 }
1620 array->SetTranslatable(translatable);
1621
1622 bool error = false;
1623 const size_t depth = parser->depth();
1624 while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1625 if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1626 // Skip text and comments.
1627 continue;
1628 }
1629
1630 const android::Source item_source = source_.WithLine(parser->line_number());
1631 const std::string& element_namespace = parser->element_namespace();
1632 const std::string& element_name = parser->element_name();
1633 if (element_namespace.empty() && element_name == "item") {
1634 std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString);
1635 if (!item) {
1636 diag_->Error(android::DiagMessage(item_source) << "could not parse array item");
1637 error = true;
1638 continue;
1639 }
1640 item->SetSource(item_source);
1641 array->elements.emplace_back(std::move(item));
1642
1643 } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1644 diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
1645 << "unknown tag <" << element_namespace << ":" << element_name << ">");
1646 error = true;
1647 }
1648 }
1649
1650 if (error) {
1651 return false;
1652 }
1653
1654 out_resource->value = std::move(array);
1655 return true;
1656 }
1657
ParsePlural(xml::XmlPullParser * parser,ParsedResource * out_resource)1658 bool ResourceParser::ParsePlural(xml::XmlPullParser* parser,
1659 ParsedResource* out_resource) {
1660 out_resource->name.type =
1661 ResourceNamedTypeWithDefaultName(ResourceType::kPlurals).ToResourceNamedType();
1662
1663 std::unique_ptr<Plural> plural = util::make_unique<Plural>();
1664
1665 bool error = false;
1666 const size_t depth = parser->depth();
1667 while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1668 if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1669 // Skip text and comments.
1670 continue;
1671 }
1672
1673 const android::Source item_source = source_.WithLine(parser->line_number());
1674 const std::string& element_namespace = parser->element_namespace();
1675 const std::string& element_name = parser->element_name();
1676 if (element_namespace.empty() && element_name == "item") {
1677 std::optional<StringPiece> maybe_quantity = xml::FindNonEmptyAttribute(parser, "quantity");
1678 if (!maybe_quantity) {
1679 diag_->Error(android::DiagMessage(item_source) << "<item> in <plurals> requires attribute "
1680 << "'quantity'");
1681 error = true;
1682 continue;
1683 }
1684
1685 StringPiece trimmed_quantity =
1686 util::TrimWhitespace(maybe_quantity.value());
1687 size_t index = 0;
1688 if (trimmed_quantity == "zero") {
1689 index = Plural::Zero;
1690 } else if (trimmed_quantity == "one") {
1691 index = Plural::One;
1692 } else if (trimmed_quantity == "two") {
1693 index = Plural::Two;
1694 } else if (trimmed_quantity == "few") {
1695 index = Plural::Few;
1696 } else if (trimmed_quantity == "many") {
1697 index = Plural::Many;
1698 } else if (trimmed_quantity == "other") {
1699 index = Plural::Other;
1700 } else {
1701 diag_->Error(android::DiagMessage(item_source)
1702 << "<item> in <plural> has invalid value '" << trimmed_quantity
1703 << "' for attribute 'quantity'");
1704 error = true;
1705 continue;
1706 }
1707
1708 if (plural->values[index]) {
1709 diag_->Error(android::DiagMessage(item_source)
1710 << "duplicate quantity '" << trimmed_quantity << "'");
1711 error = true;
1712 continue;
1713 }
1714
1715 if (!(plural->values[index] = ParseXml(
1716 parser, android::ResTable_map::TYPE_STRING, kNoRawString))) {
1717 error = true;
1718 continue;
1719 }
1720
1721 plural->values[index]->SetSource(item_source);
1722
1723 } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1724 diag_->Error(android::DiagMessage(item_source)
1725 << "unknown tag <" << element_namespace << ":" << element_name << ">");
1726 error = true;
1727 }
1728 }
1729
1730 if (error) {
1731 return false;
1732 }
1733
1734 out_resource->value = std::move(plural);
1735 return true;
1736 }
1737
ParseDeclareStyleable(xml::XmlPullParser * parser,ParsedResource * out_resource)1738 bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser,
1739 ParsedResource* out_resource) {
1740 out_resource->name.type =
1741 ResourceNamedTypeWithDefaultName(ResourceType::kStyleable).ToResourceNamedType();
1742
1743 if (!options_.preserve_visibility_of_styleables) {
1744 // This was added in change Idd21b5de4d20be06c6f8c8eb5a22ccd68afc4927 to mimic aapt1, but no one
1745 // knows exactly what for.
1746 //
1747 // FWIW, styleables only appear in generated R classes. For custom views these should always be
1748 // package-private (to be used only by the view class); themes are a different story.
1749 out_resource->visibility_level = Visibility::Level::kPublic;
1750 }
1751
1752 // Declare-styleable only ends up in default config;
1753 if (out_resource->config != ConfigDescription::DefaultConfig()) {
1754 diag_->Warn(android::DiagMessage(out_resource->source)
1755 << "ignoring configuration '" << out_resource->config << "' for styleable "
1756 << out_resource->name.entry);
1757 out_resource->config = ConfigDescription::DefaultConfig();
1758 }
1759
1760 std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
1761
1762 std::string comment;
1763 bool error = false;
1764 const size_t depth = parser->depth();
1765 while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1766 if (parser->event() == xml::XmlPullParser::Event::kComment) {
1767 comment = std::string(util::TrimWhitespace(parser->comment()));
1768 continue;
1769 } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1770 // Ignore text.
1771 continue;
1772 }
1773
1774 const android::Source item_source = source_.WithLine(parser->line_number());
1775 const std::string& element_namespace = parser->element_namespace();
1776 const std::string& element_name = parser->element_name();
1777 if (element_namespace.empty() && element_name == "attr") {
1778 std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
1779 if (!maybe_name) {
1780 diag_->Error(android::DiagMessage(item_source)
1781 << "<attr> tag must have a 'name' attribute");
1782 error = true;
1783 continue;
1784 }
1785
1786 // If this is a declaration, the package name may be in the name. Separate
1787 // these out.
1788 // Eg. <attr name="android:text" />
1789 std::optional<Reference> maybe_ref = ResourceUtils::ParseXmlAttributeName(maybe_name.value());
1790 if (!maybe_ref) {
1791 diag_->Error(android::DiagMessage(item_source)
1792 << "<attr> tag has invalid name '" << maybe_name.value() << "'");
1793 error = true;
1794 continue;
1795 }
1796
1797 Reference& child_ref = maybe_ref.value();
1798 xml::ResolvePackage(parser, &child_ref);
1799
1800 // Create the ParsedResource that will add the attribute to the table.
1801 ParsedResource child_resource;
1802 child_resource.name = child_ref.name.value();
1803 child_resource.source = item_source;
1804 child_resource.comment = std::move(comment);
1805 comment.clear();
1806 if (options_.visibility) {
1807 child_resource.visibility_level = options_.visibility.value();
1808 }
1809
1810 if (!ParseAttrImpl(parser, &child_resource, true)) {
1811 error = true;
1812 continue;
1813 }
1814
1815 // Create the reference to this attribute.
1816 child_ref.SetComment(child_resource.comment);
1817 child_ref.SetSource(item_source);
1818 styleable->entries.push_back(std::move(child_ref));
1819
1820 // Do not add referenced attributes that do not define a format to the table.
1821 CHECK(child_resource.value != nullptr);
1822 Attribute* attr = ValueCast<Attribute>(child_resource.value.get());
1823
1824 CHECK(attr != nullptr);
1825 if (attr->type_mask != android::ResTable_map::TYPE_ANY) {
1826 out_resource->child_resources.push_back(std::move(child_resource));
1827 }
1828
1829 } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1830 diag_->Error(android::DiagMessage(item_source)
1831 << "unknown tag <" << element_namespace << ":" << element_name << ">");
1832 error = true;
1833 }
1834
1835 comment = {};
1836 }
1837
1838 if (error) {
1839 return false;
1840 }
1841
1842 out_resource->value = std::move(styleable);
1843 return true;
1844 }
1845
1846 } // namespace aapt
1847