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 <sstream>
20 #include <string>
21
22 #include "android-base/macros.h"
23
24 #include "Debug.h"
25 #include "ResourceUtils.h"
26 #include "util/Util.h"
27 #include "xml/XmlDom.h"
28 #include "xml/XmlUtil.h"
29
30 namespace aapt {
31
32 namespace {
33
34 /**
35 * XML Visitor that will find all <aapt:attr> elements for extraction.
36 */
37 class Visitor : public xml::PackageAwareVisitor {
38 public:
39 using xml::PackageAwareVisitor::Visit;
40
41 struct InlineDeclaration {
42 xml::Element* el;
43 std::string attr_namespace_uri;
44 std::string attr_name;
45 };
46
Visitor(IAaptContext * context,xml::XmlResource * xml_resource)47 explicit Visitor(IAaptContext* context, xml::XmlResource* xml_resource)
48 : context_(context), xml_resource_(xml_resource) {}
49
Visit(xml::Element * el)50 void Visit(xml::Element* el) override {
51 if (el->namespace_uri != xml::kSchemaAapt || el->name != "attr") {
52 xml::PackageAwareVisitor::Visit(el);
53 return;
54 }
55
56 const Source& src = xml_resource_->file.source.WithLine(el->line_number);
57
58 xml::Attribute* attr = el->FindAttribute({}, "name");
59 if (!attr) {
60 context_->GetDiagnostics()->Error(DiagMessage(src)
61 << "missing 'name' attribute");
62 error_ = true;
63 return;
64 }
65
66 Maybe<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value);
67 if (!ref) {
68 context_->GetDiagnostics()->Error(
69 DiagMessage(src) << "invalid XML attribute '" << attr->value << "'");
70 error_ = true;
71 return;
72 }
73
74 const ResourceName& name = ref.value().name.value();
75
76 // Use an empty string for the compilation package because we don't want to
77 // default to
78 // the local package if the user specified name="style" or something. This
79 // should just
80 // be the default namespace.
81 Maybe<xml::ExtractedPackage> maybe_pkg =
82 TransformPackageAlias(name.package, {});
83 if (!maybe_pkg) {
84 context_->GetDiagnostics()->Error(DiagMessage(src)
85 << "invalid namespace prefix '"
86 << name.package << "'");
87 error_ = true;
88 return;
89 }
90
91 const xml::ExtractedPackage& pkg = maybe_pkg.value();
92 const bool private_namespace =
93 pkg.private_namespace || ref.value().private_reference;
94
95 InlineDeclaration decl;
96 decl.el = el;
97 decl.attr_name = name.entry;
98 if (!pkg.package.empty()) {
99 decl.attr_namespace_uri =
100 xml::BuildPackageNamespace(pkg.package, private_namespace);
101 }
102
103 inline_declarations_.push_back(std::move(decl));
104 }
105
GetInlineDeclarations() const106 const std::vector<InlineDeclaration>& GetInlineDeclarations() const {
107 return inline_declarations_;
108 }
109
HasError() const110 bool HasError() const { return error_; }
111
112 private:
113 DISALLOW_COPY_AND_ASSIGN(Visitor);
114
115 IAaptContext* context_;
116 xml::XmlResource* xml_resource_;
117 std::vector<InlineDeclaration> inline_declarations_;
118 bool error_ = false;
119 };
120
121 } // namespace
122
Consume(IAaptContext * context,xml::XmlResource * doc)123 bool InlineXmlFormatParser::Consume(IAaptContext* context,
124 xml::XmlResource* doc) {
125 Visitor visitor(context, doc);
126 doc->root->Accept(&visitor);
127 if (visitor.HasError()) {
128 return false;
129 }
130
131 size_t name_suffix_counter = 0;
132 for (const Visitor::InlineDeclaration& decl :
133 visitor.GetInlineDeclarations()) {
134 auto new_doc = util::make_unique<xml::XmlResource>();
135 new_doc->file.config = doc->file.config;
136 new_doc->file.source = doc->file.source.WithLine(decl.el->line_number);
137 new_doc->file.name = doc->file.name;
138
139 // Modify the new entry name. We need to suffix the entry with a number to
140 // avoid
141 // local collisions, then mangle it with the empty package, such that it
142 // won't show up
143 // in R.java.
144
145 new_doc->file.name.entry =
146 NameMangler::MangleEntry({}, new_doc->file.name.entry + "__" +
147 std::to_string(name_suffix_counter));
148
149 // Extracted elements must be the only child of <aapt:attr>.
150 // Make sure there is one root node in the children (ignore empty text).
151 for (auto& child : decl.el->children) {
152 const Source child_source = doc->file.source.WithLine(child->line_number);
153 if (xml::Text* t = xml::NodeCast<xml::Text>(child.get())) {
154 if (!util::TrimWhitespace(t->text).empty()) {
155 context->GetDiagnostics()->Error(
156 DiagMessage(child_source)
157 << "can't extract text into its own resource");
158 return false;
159 }
160 } else if (new_doc->root) {
161 context->GetDiagnostics()->Error(
162 DiagMessage(child_source)
163 << "inline XML resources must have a single root");
164 return false;
165 } else {
166 new_doc->root = std::move(child);
167 new_doc->root->parent = nullptr;
168 }
169 }
170
171 // Walk up and find the parent element.
172 xml::Node* node = decl.el;
173 xml::Element* parent_el = nullptr;
174 while (node->parent &&
175 (parent_el = xml::NodeCast<xml::Element>(node->parent)) == nullptr) {
176 node = node->parent;
177 }
178
179 if (!parent_el) {
180 context->GetDiagnostics()->Error(
181 DiagMessage(new_doc->file.source)
182 << "no suitable parent for inheriting attribute");
183 return false;
184 }
185
186 // Add the inline attribute to the parent.
187 parent_el->attributes.push_back(
188 xml::Attribute{decl.attr_namespace_uri, decl.attr_name,
189 "@" + new_doc->file.name.ToString()});
190
191 // Delete the subtree.
192 for (auto iter = parent_el->children.begin();
193 iter != parent_el->children.end(); ++iter) {
194 if (iter->get() == node) {
195 parent_el->children.erase(iter);
196 break;
197 }
198 }
199
200 queue_.push_back(std::move(new_doc));
201
202 name_suffix_counter++;
203 }
204 return true;
205 }
206
207 } // namespace aapt
208