From e1f9cdc38b600d930760ed0e0b867ab739127f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be> Date: Wed, 18 Aug 2010 22:08:34 +0200 Subject: Added command parser. diff --git a/Swiften/Base/Shared.h b/Swiften/Base/Shared.h new file mode 100644 index 0000000..612fe5b --- /dev/null +++ b/Swiften/Base/Shared.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +namespace Swift { + template<typename T> + class Shared { + public: + typedef boost::shared_ptr<T> ref; + + template<typename X> + static boost::shared_ptr<T> cast(const X& from) { + return boost::dynamic_pointer_cast<T>(from); + } + }; +} diff --git a/Swiften/Elements/Command.h b/Swiften/Elements/Command.h index c802035..91ca915 100644 --- a/Swiften/Elements/Command.h +++ b/Swiften/Elements/Command.h @@ -7,13 +7,15 @@ #pragma once #include "Swiften/Base/String.h" +#include "Swiften/Base/Shared.h" #include "Swiften/Elements/Payload.h" +#include "Swiften/Elements/Form.h" namespace Swift { /** * Ad-Hoc Command (XEP-0050). */ - class Command : public Payload { + class Command : public Payload, public Shared<Command> { public: enum Status {Executing, Completed, Canceled, NoStatus}; enum Action {Cancel, Execute, Complete, Prev, Next, NoAction}; @@ -29,26 +31,36 @@ namespace Swift { public: Command(const String& node, const String& sessionID, Status status) { constructor(node, sessionID, NoAction, status);} - Command(const String& node, const String& sessionID = "", Action action = Execute) { constructor(node, sessionID, action, NoStatus); } + Command(const String& node = "", const String& sessionID = "", Action action = Execute) { constructor(node, sessionID, action, NoStatus); } const String& getNode() const { return node_; } + void setNode(const String& node) { node_ = node; } + const String& getSessionID() const { return sessionID_; } - Action getPerformedAction() const { return performedAction_; } + void setSessionID(const String& id) { sessionID_ = id; } + + Action getAction() const { return action_; } + void setAction(Action action) { action_ = action; } + void setExecuteAction(Action action) { executeAction_ = action; } Action getExecuteAction() const { return executeAction_; } + Status getStatus() const { return status_; } + void setStatus(Status status) { status_ = status; } + void addAvailableAction(Action action) { availableActions_.push_back(action); } const std::vector<Action>& getAvailableActions() const { return availableActions_; } void addNote(const Note& note) { notes_.push_back(note); } const std::vector<Note>& getNotes() const { return notes_; } - boost::shared_ptr<Payload> getPayload() const { return payload_; } - void setPayload(boost::shared_ptr<Payload> payload) { payload_ = payload; } + + Form::ref getForm() const { return form_; } + void setForm(Form::ref payload) { form_ = payload; } private: void constructor(const String& node, const String& sessionID, Action action, Status status) { node_ = node; sessionID_ = sessionID; - performedAction_ = action; + action_ = action; status_ = status; executeAction_ = NoAction; } @@ -56,11 +68,11 @@ namespace Swift { private: String node_; String sessionID_; - Action performedAction_; + Action action_; Status status_; Action executeAction_; std::vector<Action> availableActions_; std::vector<Note> notes_; - boost::shared_ptr<Payload> payload_; + Form::ref form_; }; } diff --git a/Swiften/Elements/Form.h b/Swiften/Elements/Form.h index ed77d2b..34068ee 100644 --- a/Swiften/Elements/Form.h +++ b/Swiften/Elements/Form.h @@ -11,6 +11,7 @@ #include "Swiften/Elements/Payload.h" #include "Swiften/Elements/FormField.h" #include "Swiften/Base/String.h" +#include "Swiften/Base/Shared.h" #include "Swiften/JID/JID.h" namespace Swift { @@ -19,7 +20,7 @@ namespace Swift { * For the relevant Fields, the parsers and serialisers protect the API user against * the strange multi-value instead of newline thing by transforming them. */ - class Form : public Payload { + class Form : public Payload, public Shared<Form> { public: enum Type { FormType, SubmitType, CancelType, ResultType }; diff --git a/Swiften/Parser/PayloadParsers/CommandParser.cpp b/Swiften/Parser/PayloadParsers/CommandParser.cpp new file mode 100644 index 0000000..4e80829 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/CommandParser.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Parser/PayloadParsers/CommandParser.h" +#include "Swiften/Parser/PayloadParsers/FormParserFactory.h" +#include "Swiften/Parser/PayloadParsers/FormParser.h" + +namespace Swift { + +CommandParser::CommandParser() : level_(TopLevel), inNote_(false), inActions_(false), formParser_(0) { + formParserFactory_ = new FormParserFactory(); +} + +CommandParser::~CommandParser() { + delete formParserFactory_; +} + +void CommandParser::handleStartElement(const String& element, const String& ns, const AttributeMap& attributes) { + ++level_; + if (level_ == PayloadLevel) { + boost::optional<Command::Action> action = parseAction(attributes.getAttribute("action")); + if (action) { + getPayloadInternal()->setAction(*action); + } + + String status = attributes.getAttribute("status"); + if (status == "executing") { + getPayloadInternal()->setStatus(Command::Executing); + } + else if (status == "completed") { + getPayloadInternal()->setStatus(Command::Completed); + } + else if (status == "canceled") { + getPayloadInternal()->setStatus(Command::Canceled); + } + + getPayloadInternal()->setNode(attributes.getAttribute("node")); + getPayloadInternal()->setSessionID(attributes.getAttribute("sessionid")); + } + else if (level_ == FormOrNoteOrActionsLevel) { + assert(!formParser_); + if (formParserFactory_->canParse(element, ns, attributes)) { + formParser_ = dynamic_cast<FormParser*>(formParserFactory_->createPayloadParser()); + assert(formParser_); + } + else if (element == "note") { + inNote_ = true; + currentText_.clear(); + String noteType = attributes.getAttribute("type"); + if (noteType == "info") { + noteType_ = Command::Note::Info; + } + else if (noteType == "warn") { + noteType_ = Command::Note::Warn; + } + else if (noteType == "error") { + noteType_ = Command::Note::Error; + } + else { + noteType_ = Command::Note::Info; + } + } + else if (element == "actions") { + inActions_ = true; + boost::optional<Command::Action> action = parseAction(attributes.getAttribute("execute")); + if (action) { + getPayloadInternal()->setExecuteAction(*action); + } + } + } + else if (level_ == ActionsActionLevel) { + } + + if (formParser_) { + formParser_->handleStartElement(element, ns, attributes); + } +} + +void CommandParser::handleEndElement(const String& element, const String& ns) { + if (formParser_) { + formParser_->handleEndElement(element, ns); + } + + if (level_ == FormOrNoteOrActionsLevel) { + if (formParser_) { + Form::ref form = Form::cast(formParser_->getPayload()); + assert(form); + getPayloadInternal()->setForm(form); + delete formParser_; + formParser_ = 0; + } + else if (inNote_) { + inNote_ = false; + getPayloadInternal()->addNote(Command::Note(currentText_, noteType_)); + } + else if (inActions_) { + inActions_ = false; + } + } + else if (level_ == ActionsActionLevel && inActions_) { + boost::optional<Command::Action> action = parseAction(element); + if (action) { + getPayloadInternal()->addAvailableAction(*action); + } + } + --level_; +} + +void CommandParser::handleCharacterData(const String& data) { + if (formParser_) { + formParser_->handleCharacterData(data); + } + else { + currentText_ += data; + } +} + +boost::optional<Command::Action> CommandParser::parseAction(const String& action) { + if (action == "execute") { + return Command::Execute; + } + else if (action == "cancel") { + return Command::Cancel; + } + else if (action == "prev") { + return Command::Prev; + } + else if (action == "next") { + return Command::Next; + } + else if (action == "complete") { + return Command::Complete; + } + return boost::optional<Command::Action>(); +} + +} diff --git a/Swiften/Parser/PayloadParsers/CommandParser.h b/Swiften/Parser/PayloadParsers/CommandParser.h new file mode 100644 index 0000000..a682a80 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/CommandParser.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/optional.hpp> + +#include "Swiften/Elements/Command.h" +#include "Swiften/Parser/GenericPayloadParser.h" + +namespace Swift { + class FormParserFactory; + class FormParser; + + class CommandParser : public GenericPayloadParser<Command> { + public: + CommandParser(); + ~CommandParser(); + + virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes); + virtual void handleEndElement(const String& element, const String&); + virtual void handleCharacterData(const String& data); + + private: + static boost::optional<Command::Action> parseAction(const String& action); + + private: + enum Level { + TopLevel = 0, + PayloadLevel = 1, + FormOrNoteOrActionsLevel = 2, + ActionsActionLevel = 3 + }; + int level_; + bool inNote_; + bool inActions_; + Command::Note::Type noteType_; + FormParserFactory* formParserFactory_; + FormParser* formParser_; + String currentText_; + }; +} diff --git a/Swiften/Parser/PayloadParsers/CommandParserFactory.h b/Swiften/Parser/PayloadParsers/CommandParserFactory.h new file mode 100644 index 0000000..da2f484 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/CommandParserFactory.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Parser/PayloadParserFactory.h" +#include "Swiften/Parser/PayloadParsers/CommandParser.h" + +namespace Swift { + class PayloadParserFactoryCollection; + + class CommandParserFactory : public PayloadParserFactory { + public: + CommandParserFactory() { + } + + virtual bool canParse(const String& element, const String& ns, const AttributeMap&) const { + return ns == "http://jabber.org/protocol/commands" && element == "command"; + } + + virtual PayloadParser* createPayloadParser() { + return new CommandParser(); + } + + }; +} diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp index b115b10..ce6c9f8 100644 --- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp @@ -25,6 +25,7 @@ #include "Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h" #include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h" #include "Swiften/Parser/PayloadParsers/FormParserFactory.h" +#include "Swiften/Parser/PayloadParsers/CommandParserFactory.h" #include "Swiften/Parser/PayloadParsers/VCardUpdateParserFactory.h" #include "Swiften/Parser/PayloadParsers/VCardParserFactory.h" #include "Swiften/Parser/PayloadParsers/RawXMLPayloadParserFactory.h" @@ -53,6 +54,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { factories_.push_back(shared_ptr<PayloadParserFactory>(new SecurityLabelParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new SecurityLabelsCatalogParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new FormParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new CommandParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new VCardUpdateParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new VCardParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new PrivateStorageParserFactory(this))); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/CommandParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/CommandParserTest.cpp new file mode 100644 index 0000000..5f11718 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/CommandParserTest.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" +#include "Swiften/Elements/Command.h" + +using namespace Swift; + +class CommandParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(CommandParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST(testParse_Result); + CPPUNIT_TEST(testParse_Form); + CPPUNIT_TEST_SUITE_END(); + + public: + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<command xmlns='http://jabber.org/protocol/commands' node='list' action='prev' sessionid='myid'/>" + )); + + Command::ref payload = parser.getPayload<Command>(); + CPPUNIT_ASSERT_EQUAL(Command::Prev, payload->getAction()); + CPPUNIT_ASSERT_EQUAL(String("list"), payload->getNode()); + CPPUNIT_ASSERT_EQUAL(String("myid"), payload->getSessionID()); + } + + void testParse_Result() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<command xmlns='http://jabber.org/protocol/commands' node='config' status='completed' sessionid='myid'>" + "<note type='warn'>Service 'httpd' has been configured.</note>" + "<note type='error'>I lied.</note>" + "<actions execute='next'>" + "<prev/>" + "<next/>" + "</actions>" + "</command>" + )); + + Command::ref payload = parser.getPayload<Command>(); + CPPUNIT_ASSERT_EQUAL(Command::Completed, payload->getStatus()); + std::vector<Command::Note> notes = payload->getNotes(); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(notes.size())); + CPPUNIT_ASSERT_EQUAL(Command::Note::Warn, notes[0].type); + CPPUNIT_ASSERT_EQUAL(String("Service 'httpd' has been configured."), notes[0].note); + CPPUNIT_ASSERT_EQUAL(Command::Note::Error, notes[1].type); + CPPUNIT_ASSERT_EQUAL(String("I lied."), notes[1].note); + std::vector<Command::Action> actions = payload->getAvailableActions(); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(actions.size())); + CPPUNIT_ASSERT_EQUAL(Command::Prev, actions[0]); + CPPUNIT_ASSERT_EQUAL(Command::Next, actions[1]); + CPPUNIT_ASSERT_EQUAL(Command::Next, payload->getExecuteAction()); + } + + void testParse_Form() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "<command xmlns='http://jabber.org/protocol/commands' node='config' status='completed'>" + "<x type=\"result\" xmlns=\"jabber:x:data\">" + "<title>Bot Configuration</title>" + "<instructions>Hello!</instructions>" + "<instructions>Fill out this form to configure your new bot!</instructions>" + "</x>" + "</command>" + )); + + Command::ref payload = parser.getPayload<Command>(); + Form::ref form = payload->getForm(); + CPPUNIT_ASSERT_EQUAL(String("Bot Configuration"), form->getTitle()); + CPPUNIT_ASSERT_EQUAL(String("Hello!\nFill out this form to configure your new bot!"), form->getInstructions()); + CPPUNIT_ASSERT_EQUAL(Form::ResultType, form->getType()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(CommandParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp index e7d80e3..0d6e85e 100644 --- a/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp +++ b/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp @@ -7,8 +7,8 @@ #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> -#include "Swiften/Parser/PayloadParsers/FormParser.h" #include "Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h" +#include "Swiften/Elements/Form.h" using namespace Swift; diff --git a/Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h b/Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h index 9ce0ac4..ee64181 100644 --- a/Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h +++ b/Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h @@ -12,6 +12,8 @@ #include "Swiften/Parser/XMLParser.h" #include "Swiften/Parser/XMLParserClient.h" #include "Swiften/Parser/PlatformXMLParserFactory.h" +#include "Swiften/Elements/Payload.h" +#include "Swiften/Parser/PayloadParser.h" namespace Swift { class PayloadsParserTester : public XMLParserClient { @@ -52,6 +54,11 @@ namespace Swift { return payloadParser->getPayload(); } + template<typename T> + boost::shared_ptr<T> getPayload() const { + return boost::dynamic_pointer_cast<T>(payloadParser->getPayload()); + } + private: XMLParser* xmlParser; FullPayloadParserFactoryCollection factories; diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript index 1826432..4efd343 100644 --- a/Swiften/Parser/SConscript +++ b/Swiften/Parser/SConscript @@ -24,6 +24,7 @@ sources = [ "PayloadParsers/DiscoItemsParser.cpp", "PayloadParsers/ErrorParser.cpp", "PayloadParsers/FormParser.cpp", + "PayloadParsers/CommandParser.cpp", "PayloadParsers/FullPayloadParserFactoryCollection.cpp", "PayloadParsers/PriorityParser.cpp", "PayloadParsers/PrivateStorageParser.cpp", diff --git a/Swiften/SConscript b/Swiften/SConscript index f44d9a3..866baa9 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -167,6 +167,7 @@ if env["SCONS_STAGE"] == "build" : File("Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/FormParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/CommandParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/RawXMLPayloadParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp"), diff --git a/Swiften/Serializer/PayloadSerializers/CommandSerializer.cpp b/Swiften/Serializer/PayloadSerializers/CommandSerializer.cpp index 3ac0c2c..eb45ca2 100644 --- a/Swiften/Serializer/PayloadSerializers/CommandSerializer.cpp +++ b/Swiften/Serializer/PayloadSerializers/CommandSerializer.cpp @@ -13,10 +13,11 @@ #include "Swiften/Serializer/XML/XMLTextNode.h" #include "Swiften/Serializer/XML/XMLRawTextNode.h" #include "Swiften/Serializer/PayloadSerializerCollection.h" +#include "Swiften/Serializer/PayloadSerializers/FormSerializer.h" namespace Swift { -CommandSerializer::CommandSerializer(PayloadSerializerCollection* serializers) : serializers(serializers) { +CommandSerializer::CommandSerializer() { } String CommandSerializer::serializePayload(boost::shared_ptr<Command> command) const { @@ -27,7 +28,7 @@ String CommandSerializer::serializePayload(boost::shared_ptr<Command> command) c commandElement.setAttribute("sessionid", command->getSessionID()); } - String action = actionToString(command->getPerformedAction()); + String action = actionToString(command->getAction()); if (!action.isEmpty()) { commandElement.setAttribute("action", action); } @@ -72,12 +73,9 @@ String CommandSerializer::serializePayload(boost::shared_ptr<Command> command) c commandElement.addNode(noteElement); } - boost::shared_ptr<Payload> payload = command->getPayload(); - if (payload) { - PayloadSerializer* serializer = serializers->getPayloadSerializer(payload); - if (serializer) { - commandElement.addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(serializer->serialize(payload)))); - } + Form::ref form = command->getForm(); + if (form) { + commandElement.addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(FormSerializer().serialize(form)))); } return commandElement.serialize(); } diff --git a/Swiften/Serializer/PayloadSerializers/CommandSerializer.h b/Swiften/Serializer/PayloadSerializers/CommandSerializer.h index 6a0c067..4b71aea 100644 --- a/Swiften/Serializer/PayloadSerializers/CommandSerializer.h +++ b/Swiften/Serializer/PayloadSerializers/CommandSerializer.h @@ -14,12 +14,11 @@ namespace Swift { class CommandSerializer : public GenericPayloadSerializer<Command> { public: - CommandSerializer(PayloadSerializerCollection* serializers); + CommandSerializer(); virtual String serializePayload(boost::shared_ptr<Command>) const; private: - PayloadSerializerCollection* serializers; String actionToString(Command::Action action) const; }; } diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp index 04615a2..275cbf1 100644 --- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp @@ -61,7 +61,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() { serializers_.push_back(new DelaySerializer()); serializers_.push_back(new FormSerializer()); serializers_.push_back(new PrivateStorageSerializer(this)); - serializers_.push_back(new CommandSerializer(this)); + serializers_.push_back(new CommandSerializer()); foreach(PayloadSerializer* serializer, serializers_) { addSerializer(serializer); } -- cgit v0.10.2-6-g49f6