| // Copyright 2014 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos-dbus-bindings/xml_interface_parser.h" |
| |
| #include <utility> |
| |
| #include <base/check.h> |
| #include <base/check_op.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_util.h> |
| |
| using std::string; |
| using std::vector; |
| |
| namespace chromeos_dbus_bindings { |
| |
| // static |
| const char XmlInterfaceParser::kArgumentTag[] = "arg"; |
| const char XmlInterfaceParser::kInterfaceTag[] = "interface"; |
| const char XmlInterfaceParser::kMethodTag[] = "method"; |
| const char XmlInterfaceParser::kNodeTag[] = "node"; |
| const char XmlInterfaceParser::kSignalTag[] = "signal"; |
| const char XmlInterfaceParser::kPropertyTag[] = "property"; |
| const char XmlInterfaceParser::kAnnotationTag[] = "annotation"; |
| const char XmlInterfaceParser::kDocStringTag[] = "tp:docstring"; |
| const char XmlInterfaceParser::kNameAttribute[] = "name"; |
| const char XmlInterfaceParser::kTypeAttribute[] = "type"; |
| const char XmlInterfaceParser::kValueAttribute[] = "value"; |
| const char XmlInterfaceParser::kDirectionAttribute[] = "direction"; |
| const char XmlInterfaceParser::kAccessAttribute[] = "access"; |
| const char XmlInterfaceParser::kArgumentDirectionIn[] = "in"; |
| const char XmlInterfaceParser::kArgumentDirectionOut[] = "out"; |
| |
| const char XmlInterfaceParser::kTrue[] = "true"; |
| const char XmlInterfaceParser::kFalse[] = "false"; |
| |
| const char XmlInterfaceParser::kMethodConst[] = |
| "org.chromium.DBus.Method.Const"; |
| const char XmlInterfaceParser::kMethodAsync[] = |
| "org.freedesktop.DBus.GLib.Async"; |
| const char XmlInterfaceParser::kMethodIncludeDBusMessage[] = |
| "org.chromium.DBus.Method.IncludeDBusMessage"; |
| |
| const char XmlInterfaceParser::kMethodKind[] = "org.chromium.DBus.Method.Kind"; |
| const char XmlInterfaceParser::kMethodKindSimple[] = "simple"; |
| const char XmlInterfaceParser::kMethodKindNormal[] = "normal"; |
| const char XmlInterfaceParser::kMethodKindAsync[] = "async"; |
| const char XmlInterfaceParser::kMethodKindRaw[] = "raw"; |
| |
| const char XmlInterfaceParser::kArgumentProtobufClass[] = |
| "org.chromium.DBus.Argument.ProtobufClass"; |
| |
| namespace { |
| |
| string GetElementPath(const vector<string>& path) { |
| return base::JoinString(path, "/"); |
| } |
| |
| } // anonymous namespace |
| |
| bool XmlInterfaceParser::ParseXmlInterfaceFile( |
| const std::string& contents, |
| const std::vector<std::string>& ignore_interfaces) { |
| auto parser = XML_ParserCreate(nullptr); |
| XML_SetUserData(parser, this); |
| XML_SetElementHandler(parser, &XmlInterfaceParser::HandleElementStart, |
| &XmlInterfaceParser::HandleElementEnd); |
| XML_SetCharacterDataHandler(parser, &XmlInterfaceParser::HandleCharData); |
| const int kIsFinal = XML_TRUE; |
| |
| element_path_.clear(); |
| XML_Status res = |
| XML_Parse(parser, contents.c_str(), contents.size(), kIsFinal); |
| XML_ParserFree(parser); |
| |
| if (res != XML_STATUS_OK) { |
| LOG(ERROR) << "XML parse failure"; |
| return false; |
| } |
| |
| CHECK(element_path_.empty()); |
| |
| if (!ignore_interfaces.empty()) { |
| // Remove interfaces whose names are in |ignore_interfaces| list. |
| auto condition = [&ignore_interfaces](const Interface& itf) { |
| return std::find(ignore_interfaces.begin(), ignore_interfaces.end(), |
| itf.name) != ignore_interfaces.end(); |
| }; |
| auto p = std::remove_if(interfaces_.begin(), interfaces_.end(), condition); |
| interfaces_.erase(p, interfaces_.end()); |
| } |
| return true; |
| } |
| |
| void XmlInterfaceParser::OnOpenElement(const string& element_name, |
| const XmlAttributeMap& attributes) { |
| string prev_element; |
| if (!element_path_.empty()) |
| prev_element = element_path_.back(); |
| element_path_.push_back(element_name); |
| if (element_name == kNodeTag) { |
| CHECK(prev_element.empty() || prev_element == kNodeTag) |
| << "Unexpected tag " << element_name << " inside " << prev_element; |
| // 'name' attribute is optional for <node> element. |
| string name; |
| GetElementAttribute(attributes, element_path_, kNameAttribute, &name); |
| base::TrimWhitespaceASCII(name, base::TRIM_ALL, &name); |
| node_names_.push_back(name); |
| } else if (element_name == kInterfaceTag) { |
| CHECK_EQ(kNodeTag, prev_element) |
| << "Unexpected tag " << element_name << " inside " << prev_element; |
| string interface_name = GetValidatedElementName(attributes, element_path_); |
| Interface itf(interface_name, std::vector<Interface::Method>{}, |
| std::vector<Interface::Signal>{}, |
| std::vector<Interface::Property>{}); |
| itf.path = node_names_.back(); |
| interfaces_.push_back(std::move(itf)); |
| } else if (element_name == kMethodTag) { |
| CHECK_EQ(kInterfaceTag, prev_element) |
| << "Unexpected tag " << element_name << " inside " << prev_element; |
| interfaces_.back().methods.push_back( |
| Interface::Method(GetValidatedElementName(attributes, element_path_))); |
| } else if (element_name == kSignalTag) { |
| CHECK_EQ(kInterfaceTag, prev_element) |
| << "Unexpected tag " << element_name << " inside " << prev_element; |
| interfaces_.back().signals.push_back( |
| Interface::Signal(GetValidatedElementName(attributes, element_path_))); |
| } else if (element_name == kPropertyTag) { |
| CHECK_EQ(kInterfaceTag, prev_element) |
| << "Unexpected tag " << element_name << " inside " << prev_element; |
| interfaces_.back().properties.push_back( |
| ParseProperty(attributes, element_path_)); |
| } else if (element_name == kArgumentTag) { |
| if (prev_element == kMethodTag) { |
| AddMethodArgument(attributes); |
| } else if (prev_element == kSignalTag) { |
| AddSignalArgument(attributes); |
| } else { |
| LOG(FATAL) << "Unexpected tag " << element_name << " inside " |
| << prev_element; |
| } |
| } else if (element_name == kAnnotationTag) { |
| string name = |
| GetValidatedElementAttribute(attributes, element_path_, kNameAttribute); |
| // Value is optional. Default to empty string if omitted. |
| string value; |
| GetElementAttribute(attributes, element_path_, kValueAttribute, &value); |
| if (prev_element == kInterfaceTag) { |
| // Parse interface annotations... |
| } else if (prev_element == kMethodTag) { |
| // Parse method annotations... |
| Interface::Method& method = interfaces_.back().methods.back(); |
| if (name == kMethodConst) { |
| CHECK(value == kTrue || value == kFalse); |
| method.is_const = (value == kTrue); |
| } else if (name == kMethodIncludeDBusMessage) { |
| CHECK(value == kTrue || value == kFalse); |
| method.include_dbus_message = (value == kTrue); |
| } else if (name == kMethodAsync) { |
| // Support GLib.Async annotation as well. |
| method.kind = Interface::Method::Kind::kAsync; |
| } else if (name == kMethodKind) { |
| if (value == kMethodKindSimple) { |
| method.kind = Interface::Method::Kind::kSimple; |
| } else if (value == kMethodKindNormal) { |
| method.kind = Interface::Method::Kind::kNormal; |
| } else if (value == kMethodKindAsync) { |
| method.kind = Interface::Method::Kind::kAsync; |
| } else if (value == kMethodKindRaw) { |
| method.kind = Interface::Method::Kind::kRaw; |
| } else { |
| LOG(FATAL) << "Invalid method kind: " << value; |
| } |
| } |
| } else if (prev_element == kSignalTag) { |
| // Parse signal annotations... |
| } else if (prev_element == kPropertyTag) { |
| // Parse property annotations... |
| } else if (prev_element == kArgumentTag) { |
| // Retrieve protobuf type if any. |
| if (name == kArgumentProtobufClass) { |
| CHECK_EQ(last_arg_->type, "ay"); |
| last_arg_->type = string(kProtobufType) + value; |
| } |
| } else { |
| LOG(FATAL) << "Unexpected tag " << element_name << " inside " |
| << prev_element; |
| } |
| } else if (element_name == kDocStringTag) { |
| CHECK(!prev_element.empty() && prev_element != kNodeTag) |
| << "Unexpected tag " << element_name << " inside " << prev_element; |
| } |
| } |
| |
| void XmlInterfaceParser::OnCharData(const std::string& content) { |
| // Handle the text data only for <tp:docstring> element. |
| if (element_path_.back() != kDocStringTag) |
| return; |
| |
| CHECK_GT(element_path_.size(), 1u); |
| string* doc_string_ptr = nullptr; |
| string target_element = element_path_[element_path_.size() - 2]; |
| if (target_element == kInterfaceTag) { |
| doc_string_ptr = &(interfaces_.back().doc_string); |
| } else if (target_element == kMethodTag) { |
| doc_string_ptr = &(interfaces_.back().methods.back().doc_string); |
| } else if (target_element == kSignalTag) { |
| doc_string_ptr = &(interfaces_.back().signals.back().doc_string); |
| } else if (target_element == kPropertyTag) { |
| doc_string_ptr = &(interfaces_.back().properties.back().doc_string); |
| } |
| |
| // If <tp:docstring> is attached to elements we don't care about, do nothing. |
| if (doc_string_ptr == nullptr) |
| return; |
| |
| (*doc_string_ptr) += content; |
| } |
| |
| void XmlInterfaceParser::AddMethodArgument(const XmlAttributeMap& attributes) { |
| string argument_direction; |
| vector<string> path = element_path_; |
| path.push_back(kArgumentTag); |
| bool is_direction_paramter_present = GetElementAttribute( |
| attributes, path, kDirectionAttribute, &argument_direction); |
| vector<Interface::Argument>* argument_list = nullptr; |
| if (!is_direction_paramter_present || |
| argument_direction == kArgumentDirectionIn) { |
| argument_list = &interfaces_.back().methods.back().input_arguments; |
| } else if (argument_direction == kArgumentDirectionOut) { |
| argument_list = &interfaces_.back().methods.back().output_arguments; |
| } else { |
| LOG(FATAL) << "Unknown method argument direction " << argument_direction; |
| } |
| argument_list->push_back(ParseArgument(attributes, element_path_)); |
| last_arg_ = &argument_list->back(); |
| } |
| |
| void XmlInterfaceParser::AddSignalArgument(const XmlAttributeMap& attributes) { |
| vector<Interface::Argument>* argument_list = |
| &interfaces_.back().signals.back().arguments; |
| argument_list->push_back(ParseArgument(attributes, element_path_)); |
| last_arg_ = &argument_list->back(); |
| } |
| |
| void XmlInterfaceParser::OnCloseElement(const string& element_name) { |
| VLOG(1) << "Close Element " << element_name; |
| CHECK(!element_path_.empty()); |
| CHECK_EQ(element_path_.back(), element_name); |
| element_path_.pop_back(); |
| if (element_name == kNodeTag) { |
| CHECK(!node_names_.empty()); |
| node_names_.pop_back(); |
| } |
| } |
| |
| // static |
| bool XmlInterfaceParser::GetElementAttribute(const XmlAttributeMap& attributes, |
| const vector<string>& element_path, |
| const string& element_key, |
| string* element_value) { |
| if (attributes.find(element_key) == attributes.end()) { |
| return false; |
| } |
| *element_value = attributes.find(element_key)->second; |
| VLOG(1) << "Got " << GetElementPath(element_path) << " element with " |
| << element_key << " = " << *element_value; |
| return true; |
| } |
| |
| // static |
| string XmlInterfaceParser::GetValidatedElementAttribute( |
| const XmlAttributeMap& attributes, |
| const vector<string>& element_path, |
| const string& element_key) { |
| string element_value; |
| CHECK(GetElementAttribute(attributes, element_path, element_key, |
| &element_value)) |
| << GetElementPath(element_path) << " does not contain a " << element_key |
| << " attribute"; |
| CHECK(!element_value.empty()) << GetElementPath(element_path) << " " |
| << element_key << " attribute is empty"; |
| return element_value; |
| } |
| |
| // static |
| string XmlInterfaceParser::GetValidatedElementName( |
| const XmlAttributeMap& attributes, const vector<string>& element_path) { |
| return GetValidatedElementAttribute(attributes, element_path, kNameAttribute); |
| } |
| |
| // static |
| Interface::Argument XmlInterfaceParser::ParseArgument( |
| const XmlAttributeMap& attributes, const vector<string>& element_path) { |
| vector<string> path = element_path; |
| path.push_back(kArgumentTag); |
| string argument_name; |
| // Since the "name" field is optional, use the un-validated variant. |
| GetElementAttribute(attributes, path, kNameAttribute, &argument_name); |
| |
| string argument_type = |
| GetValidatedElementAttribute(attributes, path, kTypeAttribute); |
| return Interface::Argument(argument_name, argument_type); |
| } |
| |
| // static |
| Interface::Property XmlInterfaceParser::ParseProperty( |
| const XmlAttributeMap& attributes, |
| const std::vector<std::string>& element_path) { |
| vector<string> path = element_path; |
| path.push_back(kPropertyTag); |
| string property_name = GetValidatedElementName(attributes, path); |
| string property_type = |
| GetValidatedElementAttribute(attributes, path, kTypeAttribute); |
| string property_access = |
| GetValidatedElementAttribute(attributes, path, kAccessAttribute); |
| return Interface::Property(property_name, property_type, property_access); |
| } |
| |
| // static |
| void XmlInterfaceParser::HandleElementStart(void* user_data, |
| const XML_Char* element, |
| const XML_Char** attr) { |
| XmlAttributeMap attributes; |
| if (attr != nullptr) { |
| for (size_t n = 0; attr[n] != nullptr && attr[n + 1] != nullptr; n += 2) { |
| auto key = attr[n]; |
| auto value = attr[n + 1]; |
| attributes.insert(std::make_pair(key, value)); |
| } |
| } |
| auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data); |
| parser->OnOpenElement(element, attributes); |
| } |
| |
| // static |
| void XmlInterfaceParser::HandleElementEnd(void* user_data, |
| const XML_Char* element) { |
| auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data); |
| parser->OnCloseElement(element); |
| } |
| |
| // static |
| void XmlInterfaceParser::HandleCharData(void* user_data, |
| const char* content, |
| int length) { |
| auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data); |
| parser->OnCharData(string(content, length)); |
| } |
| |
| } // namespace chromeos_dbus_bindings |