diff options
author | Paul Stewart <pstew@chromium.org> | 2014-08-19 21:18:12 -0700 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2014-09-05 01:16:38 +0000 |
commit | 2b1c6124a7c74225e231132a68e1a92723a26da9 (patch) | |
tree | 082bcc8fa57d88b8a8ab2d45b791d27ef92feff1 | |
parent | 4a4c706fa989e4643cb5b3d675656586b42d8950 (diff) | |
download | dbus-binding-generator-2b1c6124a7c74225e231132a68e1a92723a26da9.tar.gz |
chromeos-dbus-bindings: XML parser
Add an expat-based XML parser to parse an XML interface
description into a structure.
BUG=chromium:404505
TEST=New unit tests
Change-Id: Ibc8e2b50d7e33ff209158962db3f6f97b8cfac97
Reviewed-on: https://chromium-review.googlesource.com/213242
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
-rw-r--r-- | chromeos-dbus-bindings/chromeos-dbus-bindings.gyp | 44 | ||||
-rw-r--r-- | chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc | 9 | ||||
-rw-r--r-- | chromeos-dbus-bindings/interface.h | 57 | ||||
-rw-r--r-- | chromeos-dbus-bindings/testrunner.cc | 16 | ||||
-rw-r--r-- | chromeos-dbus-bindings/xml_interface_parser.cc | 194 | ||||
-rw-r--r-- | chromeos-dbus-bindings/xml_interface_parser.h | 105 | ||||
-rw-r--r-- | chromeos-dbus-bindings/xml_interface_parser_unittest.cc | 115 |
7 files changed, 540 insertions, 0 deletions
diff --git a/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp b/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp index fafc408..3633561 100644 --- a/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp +++ b/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp @@ -20,11 +20,55 @@ }, 'targets': [ { + 'target_name': 'libchromeos-dbus-bindings', + 'type': 'static_library', + 'sources': [ + 'xml_interface_parser.cc', + ], + 'variables': { + 'exported_deps': [ + 'expat', + ], + 'deps': ['<@(exported_deps)'], + }, + 'all_dependent_settings': { + 'variables': { + 'deps': [ + '<@(exported_deps)', + ], + }, + }, + 'link_settings': { + 'variables': { + 'deps': [ + 'expat', + ], + }, + }, + }, + { 'target_name': 'generate-chromeos-dbus-bindings', 'type': 'executable', + 'dependencies': ['libchromeos-dbus-bindings'], 'sources': [ 'generate_chromeos_dbus_bindings.cc', ] }, ], + 'conditions': [ + ['USE_test == 1', { + 'targets': [ + { + 'target_name': 'chromeos_dbus_bindings_unittest', + 'type': 'executable', + 'dependencies': ['libchromeos-dbus-bindings'], + 'includes': ['../../platform2/common-mk/common_test.gypi'], + 'sources': [ + 'testrunner.cc', + 'xml_interface_parser_unittest.cc', + ], + }, + ], + }], + ], } diff --git a/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc b/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc index b7836ba..c043079 100644 --- a/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc +++ b/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc @@ -5,8 +5,11 @@ #include <string> #include <base/command_line.h> +#include <base/files/file_path.h> #include <base/logging.h> +#include "chromeos-dbus-bindings/xml_interface_parser.h" + namespace switches { static const char kHelp[] = "help"; @@ -35,5 +38,11 @@ int main(int argc, char** argv) { std::string input = cl->GetSwitchValueASCII(switches::kInput); + chromeos_dbus_bindings::XmlInterfaceParser parser; + if (!parser.ParseXmlInterfaceFile(base::FilePath(input))) { + LOG(ERROR) << "Failed to parse interface file."; + return 1; + } + return 0; } diff --git a/chromeos-dbus-bindings/interface.h b/chromeos-dbus-bindings/interface.h new file mode 100644 index 0000000..04de002 --- /dev/null +++ b/chromeos-dbus-bindings/interface.h @@ -0,0 +1,57 @@ +// 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. + +#ifndef CHROMEOS_DBUS_BINDINGS_INTERFACE_H_ +#define CHROMEOS_DBUS_BINDINGS_INTERFACE_H_ + +#include <string> +#include <vector> + +namespace chromeos_dbus_bindings { + +struct Interface { + struct Argument { + Argument(const std::string& name_in, + const std::string& type_in) : name(name_in), type(type_in) {} + std::string name; + std::string type; + }; + struct Method { + Method(const std::string& name_in, + const std::vector<Argument>& input_arguments_in, + const std::vector<Argument>& output_arguments_in) + : name(name_in), + input_arguments(input_arguments_in), + output_arguments(output_arguments_in) {} + Method(const std::string& name_in, + const std::vector<Argument>& input_arguments_in) + : name(name_in), + input_arguments(input_arguments_in) {} + explicit Method(const std::string& name_in) : name(name_in) {} + std::string name; + std::vector<Argument> input_arguments; + std::vector<Argument> output_arguments; + }; + struct Signal { + Signal(const std::string& name_in, + const std::vector<Argument>& arguments_in) + : name(name_in), arguments(arguments_in) {} + explicit Signal(const std::string& name_in) : name(name_in) {} + std::string name; + std::vector<Argument> arguments; + }; + + Interface() = default; + Interface(const std::string& name_in, + const std::vector<Method>& methods_in, + const std::vector<Signal>& signals_in) + : name(name_in), methods(methods_in), signals(signals_in) {} + std::string name; + std::vector<Method> methods; + std::vector<Signal> signals; +}; + +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_INTERFACE_H_ diff --git a/chromeos-dbus-bindings/testrunner.cc b/chromeos-dbus-bindings/testrunner.cc new file mode 100644 index 0000000..766c3f2 --- /dev/null +++ b/chromeos-dbus-bindings/testrunner.cc @@ -0,0 +1,16 @@ +// Copyright (c) 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 <base/at_exit.h> +#include <base/command_line.h> +#include <chromeos/syslog_logging.h> +#include <gtest/gtest.h> + +int main(int argc, char** argv) { + base::AtExitManager exit_manager; + CommandLine::Init(argc, argv); + chromeos::InitLog(chromeos::kLogToStderr); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/chromeos-dbus-bindings/xml_interface_parser.cc b/chromeos-dbus-bindings/xml_interface_parser.cc new file mode 100644 index 0000000..d75c2a7 --- /dev/null +++ b/chromeos-dbus-bindings/xml_interface_parser.cc @@ -0,0 +1,194 @@ +// 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/file_util.h> +#include <base/files/file_path.h> +#include <base/logging.h> +#include <base/stl_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::kNameAttribute[] = "name"; +const char XmlInterfaceParser::kTypeAttribute[] = "type"; +const char XmlInterfaceParser::kDirectionAttribute[] = "direction"; +const char XmlInterfaceParser::kArgumentDirectionIn[] = "in"; +const char XmlInterfaceParser::kArgumentDirectionOut[] = "out"; + +bool XmlInterfaceParser::ParseXmlInterfaceFile( + const base::FilePath& interface_file) { + string contents; + if (!base::ReadFileToString(interface_file, &contents)) { + LOG(ERROR) << "Failed to read file " << interface_file.value(); + return false; + } + auto parser = XML_ParserCreate(nullptr); + XML_SetUserData(parser, this); + XML_SetElementHandler(parser, + &XmlInterfaceParser::HandleElementStart, + &XmlInterfaceParser::HandleElementEnd); + 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) { + element_path_.push_back(element_name); + if (element_path_ == vector<string> { kNodeTag, kInterfaceTag }) { + string interface_name = GetValidatedElementName(attributes, kInterfaceTag); + CHECK(interface_.name.empty()) + << "Found a second interface named " << interface_name << ". " + << "Interface " << interface_.name << " has already been parsed."; + interface_.name = interface_name; + } else if (element_path_ == vector<string> { + kNodeTag, kInterfaceTag, kMethodTag }) { + interface_.methods.push_back( + Interface::Method(GetValidatedElementName(attributes, kMethodTag))); + } else if (element_path_ == vector<string> { + kNodeTag, kInterfaceTag, kMethodTag, kArgumentTag }) { + AddMethodArgument(attributes); + } else if (element_path_ == vector<string> { + kNodeTag, kInterfaceTag, kSignalTag }) { + interface_.signals.push_back( + Interface::Signal(GetValidatedElementName(attributes, kSignalTag))); + } else if (element_path_ == vector<string> { + kNodeTag, kInterfaceTag, kSignalTag, kArgumentTag }) { + AddSignalArgument(attributes); + } +} + +void XmlInterfaceParser::AddMethodArgument(const XmlAttributeMap& attributes) { + CHECK(!interface_.methods.empty()) + << " we have a method argument but the interface has no methods"; + const string& argument_direction = GetValidatedElementAttribute( + attributes, kArgumentTag, kDirectionAttribute); + vector<Interface::Argument>* argument_list; + if (argument_direction == kArgumentDirectionIn) { + argument_list = &interface_.methods.back().input_arguments; + } else if (argument_direction == kArgumentDirectionOut) { + argument_list = &interface_.methods.back().output_arguments; + } else { + LOG(FATAL) << "Unknown method argument direction " << argument_direction; + } + argument_list->push_back(ParseArgument(attributes, kMethodTag)); +} + +void XmlInterfaceParser::AddSignalArgument(const XmlAttributeMap& attributes) { + CHECK(interface_.signals.size()) + << " we have a signal argument but the interface has no signals"; + interface_.signals.back().arguments.push_back( + ParseArgument(attributes, kSignalTag)); +} + +void XmlInterfaceParser::OnCloseElement(const string& element_name) { + LOG(INFO) << "Close Element " << element_name; + CHECK(!element_path_.empty()); + CHECK_EQ(element_path_.back(), element_name); + element_path_.pop_back(); +} + +// static +bool XmlInterfaceParser::GetElementAttribute( + const XmlAttributeMap& attributes, + const string& element_type, + const string& element_key, + string* element_value) { + if (!ContainsKey(attributes, element_key)) { + return false; + } + *element_value = attributes.find(element_key)->second; + LOG(INFO) << "Got " << element_type << " element with " + << element_key << " = " << *element_value; + return true; +} + +// static +string XmlInterfaceParser::GetValidatedElementAttribute( + const XmlAttributeMap& attributes, + const string& element_type, + const string& element_key) { + string element_value; + CHECK(GetElementAttribute(attributes, + element_type, + element_key, + &element_value)) + << element_type << " does not contain a " << element_key << " attribute"; + CHECK(!element_value.empty()) << element_type << " " << element_key + << " attribute is empty"; + return element_value; +} + +// static +string XmlInterfaceParser::GetValidatedElementName( + const XmlAttributeMap& attributes, + const string& element_type) { + return GetValidatedElementAttribute(attributes, element_type, kNameAttribute); +} + +// static +Interface::Argument XmlInterfaceParser::ParseArgument( + const XmlAttributeMap& attributes, const string& element_type) { + string element_and_argument = element_type + " " + kArgumentTag; + string argument_name; + // Since the "name" field is optional, use the un-validated variant. + GetElementAttribute(attributes, + element_and_argument, + kNameAttribute, + &argument_name); + + string argument_type = GetValidatedElementAttribute( + attributes, element_and_argument, kTypeAttribute); + return Interface::Argument(argument_name, argument_type); +} + +// 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); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/xml_interface_parser.h b/chromeos-dbus-bindings/xml_interface_parser.h new file mode 100644 index 0000000..84c7dd8 --- /dev/null +++ b/chromeos-dbus-bindings/xml_interface_parser.h @@ -0,0 +1,105 @@ +// 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. + +#ifndef CHROMEOS_DBUS_BINDINGS_XML_INTERFACE_PARSER_H_ +#define CHROMEOS_DBUS_BINDINGS_XML_INTERFACE_PARSER_H_ + +#include <expat.h> + +#include <map> +#include <string> +#include <vector> + +#include <base/macros.h> + +#include "chromeos-dbus-bindings/interface.h" + +namespace base { + +class FilePath; + +} // namespace base + +namespace chromeos_dbus_bindings { + +class XmlInterfaceParser { + public: + using XmlAttributeMap = std::map<std::string, std::string>; + + XmlInterfaceParser() = default; + virtual ~XmlInterfaceParser() = default; + + virtual bool ParseXmlInterfaceFile(const base::FilePath& interface_file); + const Interface& interface() const { return interface_; } + + private: + friend class XmlInterfaceParserTest; + + // XML tag names. + static const char kArgumentTag[]; + static const char kInterfaceTag[]; + static const char kMethodTag[]; + static const char kNodeTag[]; + static const char kSignalTag[]; + + // XML attribute names. + static const char kNameAttribute[]; + static const char kTypeAttribute[]; + static const char kDirectionAttribute[]; + + // XML argument directions. + static const char kArgumentDirectionIn[]; + static const char kArgumentDirectionOut[]; + + // Element callbacks on |this| called by HandleElementStart() and + // HandleElementEnd(), respectively. + void OnOpenElement(const std::string& element_name, + const XmlAttributeMap& attributes); + void OnCloseElement(const std::string& element_name); + + // Methods for appending individual argument elements to the parser. + void AddMethodArgument(const XmlAttributeMap& attributes); + void AddSignalArgument(const XmlAttributeMap& attributes); + + // Finds the |element_key| element in |attributes|. Returns true and sets + // |element_value| on success. Returns false otherwise. + static bool GetElementAttribute(const XmlAttributeMap& attributes, + const std::string& element_type, + const std::string& element_key, + std::string* element_value); + + // Asserts that a non-empty |element_key| attribute appears in |attributes|. + // Returns the name on success, triggers a CHECK() otherwise. + static std::string GetValidatedElementAttribute( + const XmlAttributeMap& attributes, + const std::string& element_type, + const std::string& element_key); + + // Calls GetValidatedElementAttribute() for for the "name" property. + static std::string GetValidatedElementName( + const XmlAttributeMap& attributes, + const std::string& element_type); + + // Method for extracting signal/method tag attributes to a struct. + static Interface::Argument ParseArgument(const XmlAttributeMap& attributes, + const std::string& element_type); + + // Expat element callback functions. + static void HandleElementStart(void* user_data, + const XML_Char* element, + const XML_Char** attr); + static void HandleElementEnd(void* user_data, const XML_Char* element); + + // The output of the parse. + Interface interface_; + + // Tracks where in the element traversal our parse has taken us. + std::vector<std::string> element_path_; + + DISALLOW_COPY_AND_ASSIGN(XmlInterfaceParser); +}; + +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_XML_INTERFACE_PARSER_H_ diff --git a/chromeos-dbus-bindings/xml_interface_parser_unittest.cc b/chromeos-dbus-bindings/xml_interface_parser_unittest.cc new file mode 100644 index 0000000..4add88b --- /dev/null +++ b/chromeos-dbus-bindings/xml_interface_parser_unittest.cc @@ -0,0 +1,115 @@ +// 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 <base/file_util.h> +#include <base/files/file_path.h> +#include <base/files/scoped_temp_dir.h> +#include <gtest/gtest.h> + +#include "chromeos-dbus-bindings/interface.h" + +using std::string; +using testing::Test; + +namespace chromeos_dbus_bindings { + +namespace { + +const char kBadInterfaceFileContents0[] = "This has no resemblance to XML"; +const char kBadInterfaceFileContents1[] = "<node>"; +const char kGoodInterfaceFileContents[] = + "<node>\n" + " <interface name=\"fi.w1.wpa_supplicant1.Interface\">\n" + " <method name=\"Scan\">\n" + " <arg name=\"args\" type=\"a{sv}\" direction=\"in\"/>\n" + " </method>\n" + " <method name=\"GetBlob\">\n" + " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"data\" type=\"ay\" direction=\"out\"/>\n" + " </method>\n" + " <property name=\"Capabilities\" type=\"a{sv}\" access=\"read\"/>\n" + " <signal name=\"BSSRemoved\">\n" + " <arg name=\"BSS\" type=\"o\"/>\n" + " </signal>\n" + " </interface>\n" + "</node>\n"; +const char kInterfaceName[] = "fi.w1.wpa_supplicant1.Interface"; +const char kScanMethod[] = "Scan"; +const char kArgsArgument[] = "args"; +const char kArrayStringVariantType[] = "a{sv}"; +const char kGetBlobMethod[] = "GetBlob"; +const char kNameArgument[] = "name"; +const char kDataArgument[] = "data"; +const char kStringType[] = "s"; +const char kArrayByteType[] = "ay"; +const char kBssRemovedSignal[] = "BSSRemoved"; +const char kBssArgument[] = "BSS"; +const char kObjectType[] = "o"; +} // namespace + +class XmlInterfaceParserTest : public Test { + public: + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + protected: + bool ParseXmlContents(const string& contents) { + base::FilePath path = temp_dir_.path().Append("interface.xml"); + EXPECT_TRUE(base::WriteFile(path, contents.c_str(), contents.size())); + return parser_.ParseXmlInterfaceFile(path); + } + + base::ScopedTempDir temp_dir_; + XmlInterfaceParser parser_; +}; + +TEST_F(XmlInterfaceParserTest, BadInputFile) { + EXPECT_FALSE(parser_.ParseXmlInterfaceFile(base::FilePath())); + EXPECT_FALSE(ParseXmlContents(kBadInterfaceFileContents0)); + EXPECT_FALSE(ParseXmlContents(kBadInterfaceFileContents1)); +} + +TEST_F(XmlInterfaceParserTest, GoodInputFile) { + EXPECT_TRUE(ParseXmlContents(kGoodInterfaceFileContents)); + const Interface& interface = parser_.interface(); + EXPECT_EQ(kInterfaceName, interface.name); + ASSERT_EQ(2, interface.methods.size()); + ASSERT_EQ(1, interface.signals.size()); + + // <method name="Scan"> + EXPECT_EQ(kScanMethod, interface.methods[0].name); + ASSERT_EQ(1, interface.methods[0].input_arguments.size()); + + // <arg name="args" type="a{sv}" direction="in"/> + EXPECT_EQ(kArgsArgument, interface.methods[0].input_arguments[0].name); + EXPECT_EQ(kArrayStringVariantType, + interface.methods[0].input_arguments[0].type); + EXPECT_EQ(0, interface.methods[0].output_arguments.size()); + + // <method name="GetBlob"> + EXPECT_EQ(kGetBlobMethod, interface.methods[1].name); + EXPECT_EQ(1, interface.methods[1].input_arguments.size()); + EXPECT_EQ(1, interface.methods[1].output_arguments.size()); + + // <arg name="name" type="s" direction="in"/> + EXPECT_EQ(kNameArgument, interface.methods[1].input_arguments[0].name); + EXPECT_EQ(kStringType, interface.methods[1].input_arguments[0].type); + + // <arg name="data" type="ay" direction="out"/> + EXPECT_EQ(kDataArgument, interface.methods[1].output_arguments[0].name); + EXPECT_EQ(kArrayByteType, interface.methods[1].output_arguments[0].type); + + // <signal name="BSSRemoved"> + EXPECT_EQ(kBssRemovedSignal, interface.signals[0].name); + EXPECT_EQ(1, interface.signals[0].arguments.size()); + + // <arg name="BSS" type="o"/> + EXPECT_EQ(kBssArgument, interface.signals[0].arguments[0].name); + EXPECT_EQ(kObjectType, interface.signals[0].arguments[0].type); +} + +} // namespace chromeos_dbus_bindings |