// 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 #include #include #include #include #include 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::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"; namespace { string GetElementPath(const vector& path) { return chromeos::string_utils::Join('/', path); } } // anonymous namespace bool XmlInterfaceParser::ParseXmlInterfaceFile(const std::string& contents) { 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()); 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 element. string name; GetElementAttribute(attributes, element_path_, kNameAttribute, &name); // Treat object path of "/" as empty/unspecified, since that happens a lot // in existing XML files. People don't know that 'name' can be omitted, so // they use "/" to denote some fictional D-Bus path for the object. base::TrimWhitespaceASCII(name, base::TRIM_ALL, &name); if (name == "/") name.clear(); 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{}, std::vector{}, std::vector{}); 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 == 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 { 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 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 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 path = element_path_; path.push_back(kArgumentTag); bool is_direction_paramter_present = GetElementAttribute( attributes, path, kDirectionAttribute, &argument_direction); vector* 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_)); } void XmlInterfaceParser::AddSignalArgument(const XmlAttributeMap& attributes) { interfaces_.back().signals.back().arguments.push_back( ParseArgument(attributes, element_path_)); } 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& 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& 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& element_path) { return GetValidatedElementAttribute(attributes, element_path, kNameAttribute); } // static Interface::Argument XmlInterfaceParser::ParseArgument( const XmlAttributeMap& attributes, const vector& element_path) { vector 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& element_path) { vector 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(user_data); parser->OnOpenElement(element, attributes); } // static void XmlInterfaceParser::HandleElementEnd(void* user_data, const XML_Char* element) { auto parser = reinterpret_cast(user_data); parser->OnCloseElement(element); } // static void XmlInterfaceParser::HandleCharData(void* user_data, const char *content, int length) { auto parser = reinterpret_cast(user_data); parser->OnCharData(string(content, length)); } } // namespace chromeos_dbus_bindings