351 lines
14 KiB
C++
351 lines
14 KiB
C++
// 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/files/file_path.h>
|
|
#include <base/files/file_util.h>
|
|
#include <base/logging.h>
|
|
#include <base/strings/string_util.h>
|
|
#include <brillo/strings/string_utils.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";
|
|
|
|
namespace {
|
|
|
|
string GetElementPath(const vector<string>& path) {
|
|
return brillo::string_utils::Join("/", 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 {
|
|
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_));
|
|
}
|
|
|
|
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<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
|