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