1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "compile/InlineXmlFormatParser.h"
18 
19 #include <string>
20 
21 #include "ResourceUtils.h"
22 #include "util/Util.h"
23 #include "xml/XmlDom.h"
24 #include "xml/XmlUtil.h"
25 
26 namespace aapt {
27 
28 namespace {
29 
30 struct InlineDeclaration {
31   xml::Element* el;
32   std::string attr_namespace_uri;
33   std::string attr_name;
34 };
35 
36 // XML Visitor that will find all <aapt:attr> elements for extraction.
37 class Visitor : public xml::PackageAwareVisitor {
38  public:
39   using xml::PackageAwareVisitor::Visit;
40 
Visitor(IAaptContext * context,xml::XmlResource * xml_resource)41   explicit Visitor(IAaptContext* context, xml::XmlResource* xml_resource)
42       : context_(context), xml_resource_(xml_resource) {}
43 
Visit(xml::Element * el)44   void Visit(xml::Element* el) override {
45     if (el->namespace_uri != xml::kSchemaAapt || el->name != "attr") {
46       xml::PackageAwareVisitor::Visit(el);
47       return;
48     }
49 
50     const android::Source src = xml_resource_->file.source.WithLine(el->line_number);
51 
52     xml::Attribute* attr = el->FindAttribute({}, "name");
53     if (!attr) {
54       context_->GetDiagnostics()->Error(android::DiagMessage(src) << "missing 'name' attribute");
55       error_ = true;
56       return;
57     }
58 
59     std::optional<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value);
60     if (!ref) {
61       context_->GetDiagnostics()->Error(android::DiagMessage(src)
62                                         << "invalid XML attribute '" << attr->value << "'");
63       error_ = true;
64       return;
65     }
66 
67     const ResourceName& name = ref.value().name.value();
68     std::optional<xml::ExtractedPackage> maybe_pkg = TransformPackageAlias(name.package);
69     if (!maybe_pkg) {
70       context_->GetDiagnostics()->Error(android::DiagMessage(src)
71                                         << "invalid namespace prefix '" << name.package << "'");
72       error_ = true;
73       return;
74     }
75 
76     const xml::ExtractedPackage& pkg = maybe_pkg.value();
77     const bool private_namespace = pkg.private_namespace || ref.value().private_reference;
78 
79     InlineDeclaration decl;
80     decl.el = el;
81     decl.attr_name = name.entry;
82 
83     // We need to differentiate between no-namespace defined, or the alias resolves to an empty
84     // package, which means we must use the res-auto schema.
85     if (!name.package.empty()) {
86       if (pkg.package.empty()) {
87         decl.attr_namespace_uri = xml::kSchemaAuto;
88       } else {
89         decl.attr_namespace_uri = xml::BuildPackageNamespace(pkg.package, private_namespace);
90       }
91     }
92 
93     inline_declarations_.push_back(std::move(decl));
94   }
95 
GetInlineDeclarations() const96   const std::vector<InlineDeclaration>& GetInlineDeclarations() const {
97     return inline_declarations_;
98   }
99 
HasError() const100   bool HasError() const {
101     return error_;
102   }
103 
104  private:
105   DISALLOW_COPY_AND_ASSIGN(Visitor);
106 
107   IAaptContext* context_;
108   xml::XmlResource* xml_resource_;
109   std::vector<InlineDeclaration> inline_declarations_;
110   bool error_ = false;
111 };
112 
113 }  // namespace
114 
Consume(IAaptContext * context,xml::XmlResource * doc)115 bool InlineXmlFormatParser::Consume(IAaptContext* context, xml::XmlResource* doc) {
116   Visitor visitor(context, doc);
117   doc->root->Accept(&visitor);
118   if (visitor.HasError()) {
119     return false;
120   }
121 
122   size_t name_suffix_counter = 0;
123   for (const InlineDeclaration& decl : visitor.GetInlineDeclarations()) {
124     // Create a new XmlResource with the same ResourceFile as the base XmlResource.
125     auto new_doc = util::make_unique<xml::XmlResource>(doc->file);
126 
127     // Attach the line number.
128     new_doc->file.source.line = decl.el->line_number;
129 
130     // Modify the new entry name. We need to suffix the entry with a number to
131     // avoid local collisions, then mangle it with the empty package, such that it won't show up
132     // in R.java.
133     new_doc->file.name.entry = NameMangler::MangleEntry(
134         {}, new_doc->file.name.entry + "__" + std::to_string(name_suffix_counter));
135 
136     // Extracted elements must be the only child of <aapt:attr>.
137     // Make sure there is one root node in the children (ignore empty text).
138     for (std::unique_ptr<xml::Node>& child : decl.el->children) {
139       const android::Source child_source = doc->file.source.WithLine(child->line_number);
140       if (xml::Text* t = xml::NodeCast<xml::Text>(child.get())) {
141         if (!util::TrimWhitespace(t->text).empty()) {
142           context->GetDiagnostics()->Error(android::DiagMessage(child_source)
143                                            << "can't extract text into its own resource");
144           return false;
145         }
146       } else if (new_doc->root) {
147         context->GetDiagnostics()->Error(android::DiagMessage(child_source)
148                                          << "inline XML resources must have a single root");
149         return false;
150       } else {
151         new_doc->root.reset(static_cast<xml::Element*>(child.release()));
152         new_doc->root->parent = nullptr;
153         // Copy down the namespace declarations
154         new_doc->root->namespace_decls = doc->root->namespace_decls;
155         // Recurse for nested inlines
156         Consume(context, new_doc.get());
157       }
158     }
159 
160     // Get the parent element of <aapt:attr>
161     xml::Element* parent_el = decl.el->parent;
162     if (!parent_el) {
163       context->GetDiagnostics()->Error(android::DiagMessage(new_doc->file.source)
164                                        << "no suitable parent for inheriting attribute");
165       return false;
166     }
167 
168     // Add the inline attribute to the parent.
169     parent_el->attributes.push_back(xml::Attribute{decl.attr_namespace_uri, decl.attr_name,
170                                                    "@" + new_doc->file.name.to_string()});
171 
172     // Delete the subtree.
173     for (auto iter = parent_el->children.begin(); iter != parent_el->children.end(); ++iter) {
174       if (iter->get() == decl.el) {
175         parent_el->children.erase(iter);
176         break;
177       }
178     }
179 
180     queue_.push_back(std::move(new_doc));
181 
182     name_suffix_counter++;
183   }
184   return true;
185 }
186 
187 }  // namespace aapt
188