Home | History | Annotate | Download | only in compile
      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 
     41   explicit Visitor(IAaptContext* context, xml::XmlResource* xml_resource)
     42       : context_(context), xml_resource_(xml_resource) {}
     43 
     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 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(DiagMessage(src) << "missing 'name' attribute");
     55       error_ = true;
     56       return;
     57     }
     58 
     59     Maybe<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value);
     60     if (!ref) {
     61       context_->GetDiagnostics()->Error(DiagMessage(src) << "invalid XML attribute '" << attr->value
     62                                                          << "'");
     63       error_ = true;
     64       return;
     65     }
     66 
     67     const ResourceName& name = ref.value().name.value();
     68     Maybe<xml::ExtractedPackage> maybe_pkg = TransformPackageAlias(name.package);
     69     if (!maybe_pkg) {
     70       context_->GetDiagnostics()->Error(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 
     96   const std::vector<InlineDeclaration>& GetInlineDeclarations() const {
     97     return inline_declarations_;
     98   }
     99 
    100   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 
    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 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(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(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(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