Home | History | Annotate | Download | only in chromeos-dbus-bindings
      1 // Copyright 2014 The Chromium OS Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chromeos-dbus-bindings/xml_interface_parser.h"
      6 
      7 #include <utility>
      8 
      9 #include <base/files/file_path.h>
     10 #include <base/files/file_util.h>
     11 #include <base/logging.h>
     12 #include <base/strings/string_util.h>
     13 #include <brillo/strings/string_utils.h>
     14 
     15 using std::string;
     16 using std::vector;
     17 
     18 namespace chromeos_dbus_bindings {
     19 
     20 // static
     21 const char XmlInterfaceParser::kArgumentTag[] = "arg";
     22 const char XmlInterfaceParser::kInterfaceTag[] = "interface";
     23 const char XmlInterfaceParser::kMethodTag[] = "method";
     24 const char XmlInterfaceParser::kNodeTag[] = "node";
     25 const char XmlInterfaceParser::kSignalTag[] = "signal";
     26 const char XmlInterfaceParser::kPropertyTag[] = "property";
     27 const char XmlInterfaceParser::kAnnotationTag[] = "annotation";
     28 const char XmlInterfaceParser::kDocStringTag[] = "tp:docstring";
     29 const char XmlInterfaceParser::kNameAttribute[] = "name";
     30 const char XmlInterfaceParser::kTypeAttribute[] = "type";
     31 const char XmlInterfaceParser::kValueAttribute[] = "value";
     32 const char XmlInterfaceParser::kDirectionAttribute[] = "direction";
     33 const char XmlInterfaceParser::kAccessAttribute[] = "access";
     34 const char XmlInterfaceParser::kArgumentDirectionIn[] = "in";
     35 const char XmlInterfaceParser::kArgumentDirectionOut[] = "out";
     36 
     37 const char XmlInterfaceParser::kTrue[] = "true";
     38 const char XmlInterfaceParser::kFalse[] = "false";
     39 
     40 const char XmlInterfaceParser::kMethodConst[] =
     41     "org.chromium.DBus.Method.Const";
     42 const char XmlInterfaceParser::kMethodAsync[] =
     43     "org.freedesktop.DBus.GLib.Async";
     44 const char XmlInterfaceParser::kMethodIncludeDBusMessage[] =
     45     "org.chromium.DBus.Method.IncludeDBusMessage";
     46 
     47 const char XmlInterfaceParser::kMethodKind[] = "org.chromium.DBus.Method.Kind";
     48 const char XmlInterfaceParser::kMethodKindSimple[] = "simple";
     49 const char XmlInterfaceParser::kMethodKindNormal[] = "normal";
     50 const char XmlInterfaceParser::kMethodKindAsync[] = "async";
     51 const char XmlInterfaceParser::kMethodKindRaw[] = "raw";
     52 
     53 namespace {
     54 
     55 string GetElementPath(const vector<string>& path) {
     56   return brillo::string_utils::Join("/", path);
     57 }
     58 
     59 }  // anonymous namespace
     60 
     61 bool XmlInterfaceParser::ParseXmlInterfaceFile(
     62     const std::string& contents,
     63     const std::vector<std::string>& ignore_interfaces) {
     64   auto parser = XML_ParserCreate(nullptr);
     65   XML_SetUserData(parser, this);
     66   XML_SetElementHandler(parser,
     67                         &XmlInterfaceParser::HandleElementStart,
     68                         &XmlInterfaceParser::HandleElementEnd);
     69   XML_SetCharacterDataHandler(parser, &XmlInterfaceParser::HandleCharData);
     70   const int kIsFinal = XML_TRUE;
     71 
     72   element_path_.clear();
     73   XML_Status res = XML_Parse(parser,
     74                              contents.c_str(),
     75                              contents.size(),
     76                              kIsFinal);
     77   XML_ParserFree(parser);
     78 
     79   if (res != XML_STATUS_OK) {
     80     LOG(ERROR) << "XML parse failure";
     81     return false;
     82   }
     83 
     84   CHECK(element_path_.empty());
     85 
     86   if (!ignore_interfaces.empty()) {
     87     // Remove interfaces whose names are in |ignore_interfaces| list.
     88     auto condition = [&ignore_interfaces](const Interface& itf) {
     89       return std::find(ignore_interfaces.begin(), ignore_interfaces.end(),
     90                        itf.name) != ignore_interfaces.end();
     91     };
     92     auto p = std::remove_if(interfaces_.begin(), interfaces_.end(), condition);
     93     interfaces_.erase(p, interfaces_.end());
     94   }
     95   return true;
     96 }
     97 
     98 void XmlInterfaceParser::OnOpenElement(
     99     const string& element_name, const XmlAttributeMap& attributes) {
    100   string prev_element;
    101   if (!element_path_.empty())
    102     prev_element = element_path_.back();
    103   element_path_.push_back(element_name);
    104   if (element_name == kNodeTag) {
    105     CHECK(prev_element.empty() || prev_element == kNodeTag)
    106         << "Unexpected tag " << element_name << " inside " << prev_element;
    107     // 'name' attribute is optional for <node> element.
    108     string name;
    109     GetElementAttribute(attributes, element_path_, kNameAttribute, &name);
    110     base::TrimWhitespaceASCII(name, base::TRIM_ALL, &name);
    111     node_names_.push_back(name);
    112   } else if (element_name == kInterfaceTag) {
    113     CHECK_EQ(kNodeTag, prev_element)
    114         << "Unexpected tag " << element_name << " inside " << prev_element;
    115     string interface_name = GetValidatedElementName(attributes, element_path_);
    116     Interface itf(interface_name,
    117                   std::vector<Interface::Method>{},
    118                   std::vector<Interface::Signal>{},
    119                   std::vector<Interface::Property>{});
    120     itf.path = node_names_.back();
    121     interfaces_.push_back(std::move(itf));
    122   } else if (element_name == kMethodTag) {
    123     CHECK_EQ(kInterfaceTag, prev_element)
    124         << "Unexpected tag " << element_name << " inside " << prev_element;
    125     interfaces_.back().methods.push_back(
    126         Interface::Method(GetValidatedElementName(attributes, element_path_)));
    127   } else if (element_name == kSignalTag) {
    128     CHECK_EQ(kInterfaceTag, prev_element)
    129         << "Unexpected tag " << element_name << " inside " << prev_element;
    130     interfaces_.back().signals.push_back(
    131         Interface::Signal(GetValidatedElementName(attributes, element_path_)));
    132   } else if (element_name == kPropertyTag) {
    133     CHECK_EQ(kInterfaceTag, prev_element)
    134         << "Unexpected tag " << element_name << " inside " << prev_element;
    135     interfaces_.back().properties.push_back(ParseProperty(attributes,
    136                                                           element_path_));
    137   } else if (element_name == kArgumentTag) {
    138     if (prev_element == kMethodTag) {
    139       AddMethodArgument(attributes);
    140     } else if (prev_element == kSignalTag) {
    141       AddSignalArgument(attributes);
    142     } else {
    143       LOG(FATAL) << "Unexpected tag " << element_name
    144                  << " inside " << prev_element;
    145     }
    146   } else if (element_name == kAnnotationTag) {
    147     string name = GetValidatedElementAttribute(attributes, element_path_,
    148                                                kNameAttribute);
    149     // Value is optional. Default to empty string if omitted.
    150     string value;
    151     GetElementAttribute(attributes, element_path_, kValueAttribute, &value);
    152     if (prev_element == kInterfaceTag) {
    153       // Parse interface annotations...
    154     } else if (prev_element == kMethodTag) {
    155       // Parse method annotations...
    156       Interface::Method& method = interfaces_.back().methods.back();
    157       if (name == kMethodConst) {
    158         CHECK(value == kTrue || value == kFalse);
    159         method.is_const = (value == kTrue);
    160       } else if (name == kMethodIncludeDBusMessage) {
    161         CHECK(value == kTrue || value == kFalse);
    162         method.include_dbus_message = (value == kTrue);
    163       } else if (name == kMethodAsync) {
    164         // Support GLib.Async annotation as well.
    165         method.kind = Interface::Method::Kind::kAsync;
    166       } else if (name == kMethodKind) {
    167         if (value == kMethodKindSimple) {
    168           method.kind = Interface::Method::Kind::kSimple;
    169         } else if (value == kMethodKindNormal) {
    170           method.kind = Interface::Method::Kind::kNormal;
    171         } else if (value == kMethodKindAsync) {
    172           method.kind = Interface::Method::Kind::kAsync;
    173         } else if (value == kMethodKindRaw) {
    174           method.kind = Interface::Method::Kind::kRaw;
    175         } else {
    176           LOG(FATAL) << "Invalid method kind: " << value;
    177         }
    178       }
    179     } else if (prev_element == kSignalTag) {
    180       // Parse signal annotations...
    181     } else if (prev_element == kPropertyTag) {
    182       // Parse property annotations...
    183     } else {
    184       LOG(FATAL) << "Unexpected tag " << element_name
    185                  << " inside " << prev_element;
    186     }
    187   } else if (element_name == kDocStringTag) {
    188     CHECK(!prev_element.empty() && prev_element != kNodeTag)
    189         << "Unexpected tag " << element_name << " inside " << prev_element;
    190   }
    191 }
    192 
    193 void XmlInterfaceParser::OnCharData(const std::string& content) {
    194   // Handle the text data only for <tp:docstring> element.
    195   if (element_path_.back() != kDocStringTag)
    196     return;
    197 
    198   CHECK_GT(element_path_.size(), 1u);
    199   string* doc_string_ptr = nullptr;
    200   string target_element = element_path_[element_path_.size() - 2];
    201   if (target_element == kInterfaceTag) {
    202     doc_string_ptr = &(interfaces_.back().doc_string);
    203   } else if (target_element == kMethodTag) {
    204     doc_string_ptr = &(interfaces_.back().methods.back().doc_string);
    205   } else if (target_element == kSignalTag) {
    206     doc_string_ptr = &(interfaces_.back().signals.back().doc_string);
    207   } else if (target_element == kPropertyTag) {
    208     doc_string_ptr = &(interfaces_.back().properties.back().doc_string);
    209   }
    210 
    211   // If <tp:docstring> is attached to elements we don't care about, do nothing.
    212   if (doc_string_ptr == nullptr)
    213     return;
    214 
    215   (*doc_string_ptr) += content;
    216 }
    217 
    218 
    219 void XmlInterfaceParser::AddMethodArgument(const XmlAttributeMap& attributes) {
    220   string argument_direction;
    221   vector<string> path = element_path_;
    222   path.push_back(kArgumentTag);
    223   bool is_direction_paramter_present = GetElementAttribute(
    224       attributes, path, kDirectionAttribute, &argument_direction);
    225   vector<Interface::Argument>* argument_list = nullptr;
    226   if (!is_direction_paramter_present ||
    227       argument_direction == kArgumentDirectionIn) {
    228     argument_list = &interfaces_.back().methods.back().input_arguments;
    229   } else if (argument_direction == kArgumentDirectionOut) {
    230     argument_list = &interfaces_.back().methods.back().output_arguments;
    231   } else {
    232     LOG(FATAL) << "Unknown method argument direction " << argument_direction;
    233   }
    234   argument_list->push_back(ParseArgument(attributes, element_path_));
    235 }
    236 
    237 void XmlInterfaceParser::AddSignalArgument(const XmlAttributeMap& attributes) {
    238   interfaces_.back().signals.back().arguments.push_back(
    239       ParseArgument(attributes, element_path_));
    240 }
    241 
    242 void XmlInterfaceParser::OnCloseElement(const string& element_name) {
    243   VLOG(1) << "Close Element " << element_name;
    244   CHECK(!element_path_.empty());
    245   CHECK_EQ(element_path_.back(), element_name);
    246   element_path_.pop_back();
    247   if (element_name == kNodeTag) {
    248     CHECK(!node_names_.empty());
    249     node_names_.pop_back();
    250   }
    251 }
    252 
    253 // static
    254 bool XmlInterfaceParser::GetElementAttribute(
    255     const XmlAttributeMap& attributes,
    256     const vector<string>& element_path,
    257     const string& element_key,
    258     string* element_value) {
    259   if (attributes.find(element_key) == attributes.end()) {
    260     return false;
    261   }
    262   *element_value = attributes.find(element_key)->second;
    263   VLOG(1) << "Got " << GetElementPath(element_path) << " element with "
    264           << element_key << " = " << *element_value;
    265   return true;
    266 }
    267 
    268 // static
    269 string XmlInterfaceParser::GetValidatedElementAttribute(
    270     const XmlAttributeMap& attributes,
    271     const vector<string>& element_path,
    272     const string& element_key) {
    273   string element_value;
    274   CHECK(GetElementAttribute(attributes,
    275                             element_path,
    276                             element_key,
    277                             &element_value))
    278       << GetElementPath(element_path) << " does not contain a " << element_key
    279       << " attribute";
    280   CHECK(!element_value.empty()) << GetElementPath(element_path) << " "
    281                                 << element_key << " attribute is empty";
    282   return element_value;
    283 }
    284 
    285 // static
    286 string XmlInterfaceParser::GetValidatedElementName(
    287     const XmlAttributeMap& attributes,
    288     const vector<string>& element_path) {
    289   return GetValidatedElementAttribute(attributes, element_path, kNameAttribute);
    290 }
    291 
    292 // static
    293 Interface::Argument XmlInterfaceParser::ParseArgument(
    294     const XmlAttributeMap& attributes, const vector<string>& element_path) {
    295   vector<string> path = element_path;
    296   path.push_back(kArgumentTag);
    297   string argument_name;
    298   // Since the "name" field is optional, use the un-validated variant.
    299   GetElementAttribute(attributes, path, kNameAttribute, &argument_name);
    300 
    301   string argument_type = GetValidatedElementAttribute(
    302       attributes, path, kTypeAttribute);
    303   return Interface::Argument(argument_name, argument_type);
    304 }
    305 
    306 // static
    307 Interface::Property XmlInterfaceParser::ParseProperty(
    308     const XmlAttributeMap& attributes,
    309     const std::vector<std::string>& element_path) {
    310   vector<string> path = element_path;
    311   path.push_back(kPropertyTag);
    312   string property_name = GetValidatedElementName(attributes, path);
    313   string property_type = GetValidatedElementAttribute(attributes, path,
    314                                                       kTypeAttribute);
    315   string property_access = GetValidatedElementAttribute(attributes, path,
    316                                                         kAccessAttribute);
    317   return Interface::Property(property_name, property_type, property_access);
    318 }
    319 
    320 // static
    321 void XmlInterfaceParser::HandleElementStart(void* user_data,
    322                                             const XML_Char* element,
    323                                             const XML_Char** attr) {
    324   XmlAttributeMap attributes;
    325   if (attr != nullptr) {
    326     for (size_t n = 0; attr[n] != nullptr && attr[n+1] != nullptr; n += 2) {
    327       auto key = attr[n];
    328       auto value = attr[n + 1];
    329       attributes.insert(std::make_pair(key, value));
    330     }
    331   }
    332   auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data);
    333   parser->OnOpenElement(element, attributes);
    334 }
    335 
    336 // static
    337 void XmlInterfaceParser::HandleElementEnd(void* user_data,
    338                                           const XML_Char* element) {
    339   auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data);
    340   parser->OnCloseElement(element);
    341 }
    342 
    343 // static
    344 void XmlInterfaceParser::HandleCharData(void* user_data,
    345                                         const char *content,
    346                                         int length) {
    347   auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data);
    348   parser->OnCharData(string(content, length));
    349 }
    350 
    351 }  // namespace chromeos_dbus_bindings
    352