• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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