From 83afa3d4bf18e4feb8a33d084ed181508084fc64 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 6 Jan 2012 23:18:25 +0100 Subject: XEP-0004 form support for user search. License: This patch is BSD-licensed, see http://www.opensource.org/licenses/bsd-license.php diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp index 3e734df..d90a3f8 100644 --- a/Swift/Controllers/Chat/UserSearchController.cpp +++ b/Swift/Controllers/Chat/UserSearchController.cpp @@ -123,18 +123,24 @@ void UserSearchController::handleSearchResponse(boost::shared_ptr window_->setSearchError(true); return; } + std::vector results; - foreach (SearchPayload::Item item, resultsPayload->getItems()) { - JID jid(item.jid); - std::map fields; - fields["first"] = item.first; - fields["last"] = item.last; - fields["nick"] = item.nick; - fields["email"] = item.email; - UserSearchResult result(jid, fields); - results.push_back(result); + + if (resultsPayload->getForm()) { + window_->setResultsForm(resultsPayload->getForm()); + } else { + foreach (SearchPayload::Item item, resultsPayload->getItems()) { + JID jid(item.jid); + std::map fields; + fields["first"] = item.first; + fields["last"] = item.last; + fields["nick"] = item.nick; + fields["email"] = item.email; + UserSearchResult result(jid, fields); + results.push_back(result); + } + window_->setResults(results); } - window_->setResults(results); } void UserSearchController::handleDiscoWalkFinished() { diff --git a/Swift/Controllers/UIInterfaces/UserSearchWindow.h b/Swift/Controllers/UIInterfaces/UserSearchWindow.h index 0bb7400..0bfc509 100644 --- a/Swift/Controllers/UIInterfaces/UserSearchWindow.h +++ b/Swift/Controllers/UIInterfaces/UserSearchWindow.h @@ -23,6 +23,7 @@ namespace Swift { virtual void clear() = 0; virtual void setResults(const std::vector& results) = 0; + virtual void setResultsForm(const Form::ref results) = 0; virtual void addSavedServices(const std::vector& services) = 0; virtual void setSelectedService(const JID& service) = 0; virtual void setServerSupportsSearch(bool support) = 0; diff --git a/Swift/QtUI/QtFormResultItemModel.cpp b/Swift/QtUI/QtFormResultItemModel.cpp new file mode 100644 index 0000000..5461f05 --- /dev/null +++ b/Swift/QtUI/QtFormResultItemModel.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtFormResultItemModel.h" + +#include + +#include +#include + +namespace Swift { + +QtFormResultItemModel::QtFormResultItemModel(QObject *parent) : QAbstractTableModel(parent) { + +} + +QtFormResultItemModel::QtFormResultItemModel(QObject* parent, Form::ref formResult) : QAbstractTableModel(parent), formResult_(formResult) { + +} + +void QtFormResultItemModel::setForm(Form::ref formResult) { + emit layoutAboutToBeChanged(); + formResult_ = formResult; + emit layoutChanged(); +} + +const Form::ref QtFormResultItemModel::getForm() const { + return formResult_; +} + +QVariant QtFormResultItemModel::headerData(int section, Qt::Orientation /*orientation*/, int role) const { + if (!formResult_) return QVariant(); + if (role != Qt::DisplayRole) return QVariant(); + if (static_cast(section) >= formResult_->getReportedFields().size()) return QVariant(); + return QVariant(QString::fromStdString(formResult_->getReportedFields().at(section)->getLabel())); +} + +int QtFormResultItemModel::rowCount(const QModelIndex &/*parent*/) const { + if (!formResult_) return 0; + return formResult_->getItems().size(); +} + +int QtFormResultItemModel::columnCount(const QModelIndex &/*parent*/) const { + if (!formResult_) return 0; + return formResult_->getReportedFields().size(); +} + +QVariant QtFormResultItemModel::data(const QModelIndex &index, int role) const { + if (!formResult_) return QVariant(); + if (role != Qt::DisplayRole || !index.isValid()) { + return QVariant(); + } + + if (static_cast(index.row()) >= formResult_->getItems().size()) return QVariant(); + if (static_cast(index.column()) >= formResult_->getReportedFields().size()) return QVariant(); + + Form::FormItem item = formResult_->getItems().at(index.row()); + + return QVariant(P2QSTRING(getFieldValue(item, index.column()))); +} + +const std::string QtFormResultItemModel::getFieldValue(const Form::FormItem& item, const int column) const { + // determine field name + std::string name = formResult_->getReportedFields().at(column)->getName(); + + foreach(FormField::ref field, item) { + if (field->getName() == name) { + std::string delimiter = ""; + if (boost::dynamic_pointer_cast(field)) { + delimiter = "\n"; + } else if (boost::dynamic_pointer_cast(field)) { + delimiter = ", "; + } else if (boost::dynamic_pointer_cast(field)) { + delimiter = ", "; + } + + return boost::algorithm::join(field->getRawValues(), delimiter); + } + } + + return ""; +} + +} diff --git a/Swift/QtUI/QtFormResultItemModel.h b/Swift/QtUI/QtFormResultItemModel.h new file mode 100644 index 0000000..f383f74 --- /dev/null +++ b/Swift/QtUI/QtFormResultItemModel.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include + +#include + +namespace Swift { + +class QtFormResultItemModel : public QAbstractTableModel { + Q_OBJECT + public: + QtFormResultItemModel(QObject* parent); + QtFormResultItemModel(QObject* parent, Form::ref formResult); + + void setForm(Form::ref formResult); + const Form::ref getForm() const; + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + private: + const std::string getFieldValue(const Form::FormItem& item, const int column) const; + + private: + Form::ref formResult_; +}; + +} diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 95ec49a..2283001 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -99,6 +99,7 @@ sources = [ "SystemMessageSnippet.cpp", "QtElidingLabel.cpp", "QtFormWidget.cpp", + "QtFormResultItemModel.cpp", "QtLineEdit.cpp", "QtJoinMUCWindow.cpp", "Roster/RosterModel.cpp", diff --git a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp index 3ef048b..13858a7 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp @@ -8,12 +8,31 @@ namespace Swift { -QtUserSearchFieldsPage::QtUserSearchFieldsPage() { +QtUserSearchFieldsPage::QtUserSearchFieldsPage() : formWidget_(0) { setupUi(this); } bool QtUserSearchFieldsPage::isComplete() const { - return nickInput_->isEnabled() || firstInput_->isEnabled() || lastInput_->isEnabled() || emailInput_->isEnabled(); + if (formWidget_) { + return formWidget_->isEnabled(); + } else { + return nickInput_->isEnabled() || firstInput_->isEnabled() || lastInput_->isEnabled() || emailInput_->isEnabled(); + } +} + +QtFormWidget* QtUserSearchFieldsPage::getFormWidget() { + return formWidget_; +} + +void QtUserSearchFieldsPage::setFormWidget(QtFormWidget *widget) { + if (formWidget_) { + delete formWidget_; + formWidget_ = NULL; + } + if (widget) { + formContainer_->layout()->addWidget(widget); + } + formWidget_ = widget; } void QtUserSearchFieldsPage::emitCompletenessCheck() { diff --git a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h index 12c28b5..066205b 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h +++ b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h @@ -8,6 +8,8 @@ #include +#include + #include namespace Swift { @@ -16,7 +18,14 @@ namespace Swift { public: QtUserSearchFieldsPage(); virtual bool isComplete() const; + + QtFormWidget* getFormWidget(); + void setFormWidget(QtFormWidget *widget); + public slots: void emitCompletenessCheck(); + + private: + QtFormWidget *formWidget_; }; } diff --git a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.ui b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.ui index fa6e376..f972516 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.ui +++ b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.ui @@ -11,7 +11,7 @@ - + @@ -61,7 +61,30 @@ + + + + + + + + + + Fetching search fields + + + + + + + + 0 + + + + + Qt::Vertical @@ -74,20 +97,6 @@ - - - - - - - - - - - Fetching search fields - - - diff --git a/Swift/QtUI/UserSearch/QtUserSearchResultsPage.ui b/Swift/QtUI/UserSearch/QtUserSearchResultsPage.ui index f312fde..ceb15e1 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchResultsPage.ui +++ b/Swift/QtUI/UserSearch/QtUserSearchResultsPage.ui @@ -11,10 +11,10 @@ - + - - + + false diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp index 0559683..933612c 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp @@ -6,17 +6,20 @@ #include "Swift/QtUI/UserSearch/QtUserSearchWindow.h" +#include #include #include #include #include +#include #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" #include "Swift/Controllers/UIEvents/AddContactUIEvent.h" #include "Swift/QtUI/UserSearch/UserSearchModel.h" #include "Swift/QtUI/UserSearch/UserSearchDelegate.h" #include "Swift/QtUI/QtSwiftUtil.h" +#include "Swift/QtUI/QtFormResultItemModel.h" #include "QtUserSearchFirstPage.h" #include "QtUserSearchFieldsPage.h" #include "QtUserSearchResultsPage.h" @@ -24,7 +27,7 @@ namespace Swift { -QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set& groups) : eventStream_(eventStream), type_(type) { +QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set& groups) : eventStream_(eventStream), type_(type), model_(NULL) { setupUi(this); #ifndef Q_WS_MAC setWindowIcon(QIcon(":/logo-icon-16.png")); @@ -32,7 +35,6 @@ QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWin QString title(type == UserSearchWindow::AddContact ? tr("Add Contact") : tr("Chat to User")); setWindowTitle(title); - model_ = new UserSearchModel(); delegate_ = new UserSearchDelegate(); firstPage_ = new QtUserSearchFirstPage(type, title); @@ -51,9 +53,7 @@ QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWin setPage(2, fieldsPage_); resultsPage_ = new QtUserSearchResultsPage(); - resultsPage_->results_->setModel(model_); - resultsPage_->results_->setItemDelegate(delegate_); - resultsPage_->results_->setHeaderHidden(true); + #ifdef SWIFT_PLATFORM_MACOSX resultsPage_->results_->setAlternatingRowColors(true); #endif @@ -74,7 +74,7 @@ QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWin } QtUserSearchWindow::~QtUserSearchWindow() { - + delete model_; } void QtUserSearchWindow::handleCurrentChanged(int page) { @@ -101,9 +101,28 @@ JID QtUserSearchWindow::getServerToSearch() { void QtUserSearchWindow::handleAccepted() { JID jid; if (!firstPage_->byJID_->isChecked()) { - UserSearchResult* userItem = static_cast(resultsPage_->results_->currentIndex().internalPointer()); - if (userItem) { /* Remember to leave this if we change to dynamic cast */ - jid = userItem->getJID(); + if (dynamic_cast(model_)) { + UserSearchResult* userItem = static_cast(resultsPage_->results_->currentIndex().internalPointer()); + if (userItem) { /* Remember to leave this if we change to dynamic cast */ + jid = userItem->getJID(); + } + } else { + int row = resultsPage_->results_->currentIndex().row(); + + Form::FormItem item = dynamic_cast(model_)->getForm()->getItems().at(row); + JID fallbackJid; + foreach(FormField::ref field, item) { + if (boost::dynamic_pointer_cast(field)) { + jid = JID(field->getRawValues().at(0)); + break; + } + if (field->getName() == "jid") { + fallbackJid = field->getRawValues().at(0); + } + } + if (!jid.isValid()) { + jid = fallbackJid; + } } } else { @@ -152,17 +171,21 @@ void QtUserSearchWindow::handleFirstPageRadioChange() { void QtUserSearchWindow::handleSearch() { boost::shared_ptr search(new SearchPayload()); - if (fieldsPage_->nickInput_->isEnabled()) { - search->setNick(Q2PSTRING(fieldsPage_->nickInput_->text())); - } - if (fieldsPage_->firstInput_->isEnabled()) { - search->setFirst(Q2PSTRING(fieldsPage_->firstInput_->text())); - } - if (fieldsPage_->lastInput_->isEnabled()) { - search->setLast(Q2PSTRING(fieldsPage_->lastInput_->text())); - } - if (fieldsPage_->emailInput_->isEnabled()) { - search->setEMail(Q2PSTRING(fieldsPage_->emailInput_->text())); + if (fieldsPage_->getFormWidget()) { + search->setForm(fieldsPage_->getFormWidget()->getCompletedForm()); + } else { + if (fieldsPage_->nickInput_->isEnabled()) { + search->setNick(Q2PSTRING(fieldsPage_->nickInput_->text())); + } + if (fieldsPage_->firstInput_->isEnabled()) { + search->setFirst(Q2PSTRING(fieldsPage_->firstInput_->text())); + } + if (fieldsPage_->lastInput_->isEnabled()) { + search->setLast(Q2PSTRING(fieldsPage_->lastInput_->text())); + } + if (fieldsPage_->emailInput_->isEnabled()) { + search->setEMail(Q2PSTRING(fieldsPage_->emailInput_->text())); + } } onSearchRequested(search, getServerToSearch()); } @@ -184,18 +207,41 @@ void QtUserSearchWindow::setSearchFields(boost::shared_ptr fields fieldsPage_->fetchingThrobber_->hide(); fieldsPage_->fetchingThrobber_->movie()->stop(); fieldsPage_->fetchingLabel_->hide(); + fieldsPage_->instructionsLabel_->setText(fields->getInstructions() ? P2QSTRING(fields->getInstructions().get()) : "Enter search terms"); - bool enabled[8] = {fields->getNick(), fields->getNick(), fields->getFirst(), fields->getFirst(), fields->getLast(), fields->getLast(), fields->getEMail(), fields->getEMail()}; - QWidget* legacySearchWidgets[8] = {fieldsPage_->nickInputLabel_, fieldsPage_->nickInput_, fieldsPage_->firstInputLabel_, fieldsPage_->firstInput_, fieldsPage_->lastInputLabel_, fieldsPage_->lastInput_, fieldsPage_->emailInputLabel_, fieldsPage_->emailInput_}; - for (int i = 0; i < 8; i++) { - legacySearchWidgets[i]->setVisible(enabled[i]); - legacySearchWidgets[i]->setEnabled(enabled[i]); + if (fields->getForm()) { + fieldsPage_->setFormWidget(new QtFormWidget(fields->getForm(), fieldsPage_)); + } else { + fieldsPage_->setFormWidget(NULL); + bool enabled[8] = {fields->getNick(), fields->getNick(), fields->getFirst(), fields->getFirst(), fields->getLast(), fields->getLast(), fields->getEMail(), fields->getEMail()}; + QWidget* legacySearchWidgets[8] = {fieldsPage_->nickInputLabel_, fieldsPage_->nickInput_, fieldsPage_->firstInputLabel_, fieldsPage_->firstInput_, fieldsPage_->lastInputLabel_, fieldsPage_->lastInput_, fieldsPage_->emailInputLabel_, fieldsPage_->emailInput_}; + for (int i = 0; i < 8; i++) { + legacySearchWidgets[i]->setVisible(enabled[i]); + legacySearchWidgets[i]->setEnabled(enabled[i]); + } } fieldsPage_->emitCompletenessCheck(); } void QtUserSearchWindow::setResults(const std::vector& results) { - model_->setResults(results); + UserSearchModel *newModel = new UserSearchModel(); + newModel->setResults(results); + resultsPage_->results_->setModel(newModel); + resultsPage_->results_->setItemDelegate(delegate_); + resultsPage_->results_->setHeaderHidden(true); + delete model_; + model_ = newModel; +} + +void QtUserSearchWindow::setResultsForm(Form::ref results) { + QtFormResultItemModel *newModel = new QtFormResultItemModel(this); + newModel->setForm(results); + resultsPage_->results_->setModel(newModel); + resultsPage_->results_->setItemDelegate(new QItemDelegate()); + resultsPage_->results_->setHeaderHidden(false); + resultsPage_->results_->header()->setResizeMode(QHeaderView::ResizeToContents); + delete model_; + model_ = newModel; } void QtUserSearchWindow::setSelectedService(const JID& jid) { @@ -228,7 +274,9 @@ void QtUserSearchWindow::clear() { firstPage_->howLabel_->setText(howText); firstPage_->byJID_->setChecked(true); clearForm(); - model_->clear(); + resultsPage_->results_->setModel(NULL); + delete model_; + model_ = NULL; handleFirstPageRadioChange(); restart(); lastPage_ = 1; diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.h b/Swift/QtUI/UserSearch/QtUserSearchWindow.h index bb40cd3..4cfe93f 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchWindow.h +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.h @@ -21,6 +21,7 @@ namespace Swift { class QtUserSearchFieldsPage; class QtUserSearchResultsPage; class QtUserSearchDetailsPage; + class QtFormResultItemModel; class QtUserSearchWindow : public QWizard, public UserSearchWindow, private Ui::QtUserSearchWizard { Q_OBJECT @@ -33,6 +34,7 @@ namespace Swift { virtual void clear(); virtual void show(); virtual void setResults(const std::vector& results); + virtual void setResultsForm(Form::ref results); virtual void setSelectedService(const JID& jid); virtual void setServerSupportsSearch(bool error); virtual void setSearchError(bool error); @@ -52,7 +54,7 @@ namespace Swift { private: UIEventStream* eventStream_; UserSearchWindow::Type type_; - UserSearchModel* model_; + QAbstractItemModel* model_; UserSearchDelegate* delegate_; QtUserSearchFirstPage* firstPage_; QtUserSearchFieldsPage* fieldsPage_; diff --git a/Swiften/Elements/Form.cpp b/Swiften/Elements/Form.cpp index c34b868..cf9ecf6 100644 --- a/Swiften/Elements/Form.cpp +++ b/Swiften/Elements/Form.cpp @@ -24,5 +24,20 @@ FormField::ref Form::getField(const std::string& name) const { return FormField::ref(); } +void Form::addReportedField(FormField::ref field) { + reportedFields_.push_back(field); +} + +const std::vector& Form::getReportedFields() const { + return reportedFields_; +} + +void Form::addItem(const Form::FormItem& item) { + items_.push_back(item); +} + +const std::vector& Form::getItems() const { + return items_; +} } diff --git a/Swiften/Elements/Form.h b/Swiften/Elements/Form.h index 0d1ed63..2c6f963 100644 --- a/Swiften/Elements/Form.h +++ b/Swiften/Elements/Form.h @@ -22,6 +22,7 @@ namespace Swift { class Form : public Payload { public: typedef boost::shared_ptr
ref; + typedef std::vector FormItem; enum Type { FormType, SubmitType, CancelType, ResultType }; @@ -46,8 +47,15 @@ namespace Swift { FormField::ref getField(const std::string& name) const; + void addReportedField(FormField::ref field); + const std::vector& getReportedFields() const; + + void addItem(const FormItem& item); + const std::vector& getItems() const; private: std::vector > fields_; + std::vector > reportedFields_; + std::vector items_; std::string title_; std::string instructions_; Type type_; diff --git a/Swiften/Parser/PayloadParsers/FormParser.cpp b/Swiften/Parser/PayloadParsers/FormParser.cpp index 3905302..2df0a85 100644 --- a/Swiften/Parser/PayloadParsers/FormParser.cpp +++ b/Swiften/Parser/PayloadParsers/FormParser.cpp @@ -6,9 +6,11 @@ #include +#include + namespace Swift { -FormParser::FormParser() : level_(TopLevel) { +FormParser::FormParser() : level_(TopLevel), parsingItem_(false), parsingReported_(false) { } void FormParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) { @@ -35,32 +37,43 @@ void FormParser::handleStartElement(const std::string& element, const std::strin currentText_.clear(); } else if (element == "field") { - std::string type = attributes.getAttribute("type"); - if (type == "boolean") { + std::string type; + FormField::ref correspondingReportedField; + if (!parsingItem_) { + type = attributes.getAttribute("type"); + } else { + foreach(FormField::ref field, getPayloadInternal()->getReportedFields()) { + if (field->getName() == attributes.getAttribute("var")) { + correspondingReportedField = field; + break; + } + } + } + if (type == "boolean" || boost::dynamic_pointer_cast(correspondingReportedField)) { currentFieldParseHelper_ = BooleanFormFieldParseHelper::create(); } - else if (type == "fixed") { + else if (type == "fixed" || boost::dynamic_pointer_cast(correspondingReportedField)) { currentFieldParseHelper_ = FixedFormFieldParseHelper::create(); } - else if (type == "hidden") { + else if (type == "hidden" || boost::dynamic_pointer_cast(correspondingReportedField)) { currentFieldParseHelper_ = HiddenFormFieldParseHelper::create(); } - else if (type == "jid-multi") { + else if (type == "jid-multi" || boost::dynamic_pointer_cast(correspondingReportedField)) { currentFieldParseHelper_ = JIDMultiFormFieldParseHelper::create(); } - else if (type == "jid-single") { + else if (type == "jid-single" || boost::dynamic_pointer_cast(correspondingReportedField)) { currentFieldParseHelper_ = JIDSingleFormFieldParseHelper::create(); } - else if (type == "list-multi") { + else if (type == "list-multi" || boost::dynamic_pointer_cast(correspondingReportedField)) { currentFieldParseHelper_ = ListMultiFormFieldParseHelper::create(); } - else if (type == "list-single") { + else if (type == "list-single" || boost::dynamic_pointer_cast(correspondingReportedField)) { currentFieldParseHelper_ = ListSingleFormFieldParseHelper::create(); } - else if (type == "text-multi") { + else if (type == "text-multi" || boost::dynamic_pointer_cast(correspondingReportedField)) { currentFieldParseHelper_ = TextMultiFormFieldParseHelper::create(); } - else if (type == "text-private") { + else if (type == "text-private" || boost::dynamic_pointer_cast(correspondingReportedField)) { currentFieldParseHelper_ = TextPrivateFormFieldParseHelper::create(); } else /*if (type == "text-single") || undefined */ { @@ -71,6 +84,14 @@ void FormParser::handleStartElement(const std::string& element, const std::strin currentFieldParseHelper_->getField()->setLabel(attributes.getAttribute("label")); } } + else if (element == "reported") { + parsingReported_ = true; + level_ = PayloadLevel - 1; + } + else if (element == "item") { + parsingItem_ = true; + level_ = PayloadLevel - 1; + } } else if (level_ == FieldLevel && currentFieldParseHelper_) { currentText_.clear(); @@ -104,7 +125,13 @@ void FormParser::handleEndElement(const std::string& element, const std::string& } else if (element == "field") { if (currentFieldParseHelper_) { - getPayloadInternal()->addField(currentFieldParseHelper_->getField()); + if (parsingReported_) { + getPayloadInternal()->addReportedField(currentFieldParseHelper_->getField()); + } else if (parsingItem_) { + currentFields_.push_back(currentFieldParseHelper_->getField()); + } else { + getPayloadInternal()->addField(currentFieldParseHelper_->getField()); + } currentFieldParseHelper_.reset(); } } @@ -123,6 +150,16 @@ void FormParser::handleEndElement(const std::string& element, const std::string& currentFieldParseHelper_->addValue(currentText_); } } + if (element == "reported") { + parsingReported_ = false; + level_++; + } + else if (element == "item") { + parsingItem_ = false; + level_++; + getPayloadInternal()->addItem(currentFields_); + currentFields_.clear(); + } } void FormParser::handleCharacterData(const std::string& text) { diff --git a/Swiften/Parser/PayloadParsers/FormParser.h b/Swiften/Parser/PayloadParsers/FormParser.h index eae40a1..a3e2524 100644 --- a/Swiften/Parser/PayloadParsers/FormParser.h +++ b/Swiften/Parser/PayloadParsers/FormParser.h @@ -49,6 +49,7 @@ namespace Swift { }; class JIDFieldParseHelper : public FieldParseHelper { virtual void addValue(const std::string& s) { + getField()->addRawValue(s); boost::dynamic_pointer_cast< GenericFormField >(getField())->setValue(JID(s)); } }; @@ -106,5 +107,8 @@ namespace Swift { std::string currentText_; std::string currentOptionLabel_; boost::shared_ptr currentFieldParseHelper_; + bool parsingItem_; + bool parsingReported_; + std::vector currentFields_; }; } diff --git a/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp index 86845be..c36fbeb 100644 --- a/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp +++ b/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp @@ -16,6 +16,7 @@ class FormParserTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(FormParserTest); CPPUNIT_TEST(testParse_FormInformation); CPPUNIT_TEST(testParse); + CPPUNIT_TEST(testParse_FormItems); CPPUNIT_TEST_SUITE_END(); public: @@ -115,6 +116,72 @@ class FormParserTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(std::string("foo"), boost::dynamic_pointer_cast(payload->getFields()[9])->getValue()); } + + void testParse_FormItems() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "" + "" + "jabber:iq:search" + "" + "" + "" + "" + "" + "" + "" + "" + "Benvolio" + "Montague" + "benvolio@montague.net" + "male" + "" + "" + "Romeo" + "Montague" + "romeo@montague.net" + "male" + "" + "")); + + Form* dataForm = dynamic_cast(parser.getPayload().get()); + CPPUNIT_ASSERT(dataForm); + + Form::FormItem reported = dataForm->getReportedFields(); + CPPUNIT_ASSERT_EQUAL(static_cast(4), reported.size()); + + std::vector items = dataForm->getItems(); + CPPUNIT_ASSERT_EQUAL(static_cast(2), items.size()); + + Form::FormItem item = items[0]; + CPPUNIT_ASSERT_EQUAL(static_cast(4), item.size()); + + CPPUNIT_ASSERT_EQUAL(std::string("Benvolio"), item[0]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("first"), item[0]->getName()); + CPPUNIT_ASSERT_EQUAL(std::string("Montague"), item[1]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("last"), item[1]->getName()); + JIDSingleFormField::ref jidField = boost::dynamic_pointer_cast(item[2]); + CPPUNIT_ASSERT(jidField); + CPPUNIT_ASSERT_EQUAL(JID("benvolio@montague.net"), jidField->getValue()); + CPPUNIT_ASSERT_EQUAL(std::string("jid"), item[2]->getName()); + CPPUNIT_ASSERT_EQUAL(std::string("male"), item[3]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("x-gender"), item[3]->getName()); + + item = items[1]; + CPPUNIT_ASSERT_EQUAL(static_cast(4), item.size()); + + CPPUNIT_ASSERT_EQUAL(std::string("Romeo"), item[0]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("first"), item[0]->getName()); + CPPUNIT_ASSERT_EQUAL(std::string("Montague"), item[1]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("last"), item[1]->getName()); + jidField = boost::dynamic_pointer_cast(item[2]); + CPPUNIT_ASSERT(jidField); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), jidField->getValue()); + CPPUNIT_ASSERT_EQUAL(std::string("jid"), item[2]->getName()); + CPPUNIT_ASSERT_EQUAL(std::string("male"), item[3]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("x-gender"), item[3]->getName()); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(FormParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/SearchPayloadParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/SearchPayloadParserTest.cpp index c07cd7f..ef48ced 100644 --- a/Swiften/Parser/PayloadParsers/UnitTest/SearchPayloadParserTest.cpp +++ b/Swiften/Parser/PayloadParsers/UnitTest/SearchPayloadParserTest.cpp @@ -16,6 +16,8 @@ class SearchPayloadParserTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(SearchPayloadParserTest); CPPUNIT_TEST(testParse_FormRequestResponse); CPPUNIT_TEST(testParse_Results); + CPPUNIT_TEST(testParse_FormRequestResponse_XDATA); + CPPUNIT_TEST(testParse_Results_XDATA); CPPUNIT_TEST_SUITE_END(); public: @@ -66,6 +68,114 @@ class SearchPayloadParserTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(std::string("juliet@shakespeare.lit"), payload->getItems()[0].email); CPPUNIT_ASSERT_EQUAL(JID("tybalt@shakespeare.lit"), payload->getItems()[1].jid); } + + void testParse_FormRequestResponse_XDATA() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse( + "" + "" + "Use the enclosed form to search. If your Jabber client does not" + " support Data Forms, visit http://shakespeare.lit/" + "" + "" + "User Directory Search" + "" + "Please provide the following information" + " to search for Shakespearean characters." + "" + "" + "jabber:iq:search" + "" + "" + "" + "" + "" + "" + "" + "" + "" + )); + + SearchPayload::ref payload = parser.getPayload(); + CPPUNIT_ASSERT_EQUAL(std::string("Use the enclosed form to search. If your Jabber client does not" + " support Data Forms, visit http://shakespeare.lit/"), *payload->getInstructions()); + CPPUNIT_ASSERT(payload->getForm()); + CPPUNIT_ASSERT_EQUAL(std::string("Please provide the following information" + " to search for Shakespearean characters."), payload->getForm()->getInstructions()); + } + + void testParse_Results_XDATA() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("" + " " + " " + " jabber:iq:search" + " " + " " + " " + " " + " " + " " + " " + " " + " Benvolio" + " Montague" + " benvolio@montague.net" + " male" + " " + " " + " Romeo" + " Montague" + " romeo@montague.net" + " male" + " " + " " + "")); + SearchPayload::ref payload = parser.getPayload(); + CPPUNIT_ASSERT(payload); + + Form::ref dataForm = payload->getForm(); + CPPUNIT_ASSERT(dataForm); + + Form::FormItem reported = dataForm->getReportedFields(); + CPPUNIT_ASSERT_EQUAL(static_cast(4), reported.size()); + + std::vector items = dataForm->getItems(); + CPPUNIT_ASSERT_EQUAL(static_cast(2), items.size()); + + Form::FormItem item = items[0]; + CPPUNIT_ASSERT_EQUAL(static_cast(4), item.size()); + + CPPUNIT_ASSERT_EQUAL(std::string("Benvolio"), item[0]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("first"), item[0]->getName()); + CPPUNIT_ASSERT_EQUAL(std::string("Montague"), item[1]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("last"), item[1]->getName()); + CPPUNIT_ASSERT_EQUAL(std::string("benvolio@montague.net"), item[2]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("jid"), item[2]->getName()); + CPPUNIT_ASSERT_EQUAL(std::string("male"), item[3]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("x-gender"), item[3]->getName()); + + item = items[1]; + CPPUNIT_ASSERT_EQUAL(static_cast(4), item.size()); + + CPPUNIT_ASSERT_EQUAL(std::string("Romeo"), item[0]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("first"), item[0]->getName()); + CPPUNIT_ASSERT_EQUAL(std::string("Montague"), item[1]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("last"), item[1]->getName()); + CPPUNIT_ASSERT_EQUAL(std::string("romeo@montague.net"), item[2]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("jid"), item[2]->getName()); + CPPUNIT_ASSERT_EQUAL(std::string("male"), item[3]->getRawValues()[0]); + CPPUNIT_ASSERT_EQUAL(std::string("x-gender"), item[3]->getName()); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(SearchPayloadParserTest); diff --git a/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp b/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp index 7a6bb79..15c4f32 100644 --- a/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp +++ b/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp @@ -52,12 +52,27 @@ std::string FormSerializer::serializePayload(boost::shared_ptr form) cons multiLineify(form->getInstructions(), "instructions", formElement); } foreach(boost::shared_ptr field, form->getFields()) { - formElement->addNode(fieldToXML(field)); + formElement->addNode(fieldToXML(field, true)); } + if (!form->getReportedFields().empty()) { + boost::shared_ptr reportedElement(new XMLElement("reported")); + foreach(FormField::ref field, form->getReportedFields()) { + reportedElement->addNode(fieldToXML(field, true)); + } + formElement->addNode(reportedElement); + } + foreach(Form::FormItem item, form->getItems()) { + boost::shared_ptr itemElement(new XMLElement("item")); + foreach(FormField::ref field, item) { + itemElement->addNode(fieldToXML(field, false)); + } + formElement->addNode(itemElement); + } + return formElement->serialize(); } -boost::shared_ptr FormSerializer::fieldToXML(boost::shared_ptr field) const { +boost::shared_ptr FormSerializer::fieldToXML(boost::shared_ptr field, bool withTypeAttribute) const { boost::shared_ptr fieldElement(new XMLElement("field")); if (!field->getName().empty()) { fieldElement->setAttribute("var", field->getName()); @@ -115,7 +130,7 @@ boost::shared_ptr FormSerializer::fieldToXML(boost::shared_ptr valueElement(new XMLElement("value")); valueElement->addNode(XMLTextNode::create(boost::dynamic_pointer_cast(field)->getValue().toString())); - fieldElement->addNode(valueElement); + if ( boost::dynamic_pointer_cast(field)->getValue().isValid()) fieldElement->addNode(valueElement); } else if (boost::dynamic_pointer_cast(field)) { fieldType = "list-multi"; @@ -133,7 +148,7 @@ boost::shared_ptr FormSerializer::fieldToXML(boost::shared_ptrsetAttribute("type", fieldType); } diff --git a/Swiften/Serializer/PayloadSerializers/FormSerializer.h b/Swiften/Serializer/PayloadSerializers/FormSerializer.h index 43db9e8..d10f649 100644 --- a/Swiften/Serializer/PayloadSerializers/FormSerializer.h +++ b/Swiften/Serializer/PayloadSerializers/FormSerializer.h @@ -19,7 +19,7 @@ namespace Swift { virtual std::string serializePayload(boost::shared_ptr) const; private: - boost::shared_ptr fieldToXML(boost::shared_ptr field) const; + boost::shared_ptr fieldToXML(boost::shared_ptr field, bool withTypeAttribute) const; void multiLineify(const std::string& text, const std::string& elementName, boost::shared_ptr parent) const; }; } diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp index 29e7e59..4608522 100644 --- a/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp @@ -8,13 +8,13 @@ #include #include - using namespace Swift; class FormSerializerTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(FormSerializerTest); CPPUNIT_TEST(testSerializeFormInformation); CPPUNIT_TEST(testSerializeFields); + CPPUNIT_TEST(testSerializeFormItems); CPPUNIT_TEST_SUITE_END(); public: @@ -134,6 +134,99 @@ class FormSerializerTest : public CppUnit::TestFixture { "" ""), testling.serialize(form)); } + + void testSerializeFormItems() { + FormSerializer testling; + boost::shared_ptr form(new Form(Form::ResultType)); + + + FormField::ref field = HiddenFormField::create("jabber:iq:search"); + field->setName("FORM_TYPE"); + form->addField(field); + + // reported fields + field = TextSingleFormField::create(); + field->setName("first"); + field->setLabel("Given Name"); + form->addReportedField(field); + + field = TextSingleFormField::create(); + field->setName("last"); + field->setLabel("Family Name"); + form->addReportedField(field); + + field = JIDSingleFormField::create(); + field->setName("jid"); + field->setLabel("Jabber ID"); + form->addReportedField(field); + + field = ListSingleFormField::create(); + field->setName("x-gender"); + field->setLabel("Gender"); + form->addReportedField(field); + + Form::FormItem firstItem; + field = TextSingleFormField::create("Benvolio"); + field->setName("first"); + firstItem.push_back(field); + + field = TextSingleFormField::create("Montague"); + field->setName("last"); + firstItem.push_back(field); + + field = JIDSingleFormField::create(JID("benvolio@montague.net")); + field->setName("jid"); + firstItem.push_back(field); + + field = ListSingleFormField::create("male"); + field->setName("x-gender"); + firstItem.push_back(field); + + Form::FormItem secondItem; + field = TextSingleFormField::create("Romeo"); + field->setName("first"); + secondItem.push_back(field); + + field = TextSingleFormField::create("Montague"); + field->setName("last"); + secondItem.push_back(field); + + field = JIDSingleFormField::create(JID("romeo@montague.net")); + field->setName("jid"); + secondItem.push_back(field); + + field = ListSingleFormField::create("male"); + field->setName("x-gender"); + secondItem.push_back(field); + + form->addItem(firstItem); + form->addItem(secondItem); + + CPPUNIT_ASSERT_EQUAL(std::string( + "" + "" + "jabber:iq:search" + "" + "" + "" + "" + "" + "" + "" + "" + "Benvolio" + "Montague" + "benvolio@montague.net" + "male" + "" + "" + "Romeo" + "Montague" + "romeo@montague.net" + "male" + "" + ""), testling.serialize(form)); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(FormSerializerTest); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/SearchPayloadSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/SearchPayloadSerializerTest.cpp index d0dcbef..42bff72 100644 --- a/Swiften/Serializer/PayloadSerializers/UnitTest/SearchPayloadSerializerTest.cpp +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/SearchPayloadSerializerTest.cpp @@ -15,6 +15,7 @@ class SearchPayloadSerializerTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(SearchPayloadSerializerTest); CPPUNIT_TEST(testSerialize_Request); CPPUNIT_TEST(testSerialize_Items); + CPPUNIT_TEST(testSerialize_DataForm); CPPUNIT_TEST_SUITE_END(); public: @@ -70,6 +71,105 @@ class SearchPayloadSerializerTest : public CppUnit::TestFixture { "" ), testling.serialize(payload)); } + + void testSerialize_DataForm() { + SearchPayloadSerializer testling; + + SearchPayload::ref payload(new SearchPayload()); + boost::shared_ptr form(new Form(Form::ResultType)); + + FormField::ref field = HiddenFormField::create("jabber:iq:search"); + field->setName("FORM_TYPE"); + form->addField(field); + + // reported fields + field = TextSingleFormField::create(); + field->setName("first"); + field->setLabel("Given Name"); + form->addReportedField(field); + + field = TextSingleFormField::create(); + field->setName("last"); + field->setLabel("Family Name"); + form->addReportedField(field); + + field = JIDSingleFormField::create(); + field->setName("jid"); + field->setLabel("Jabber ID"); + form->addReportedField(field); + + field = ListSingleFormField::create(); + field->setName("x-gender"); + field->setLabel("Gender"); + form->addReportedField(field); + + Form::FormItem firstItem; + field = TextSingleFormField::create("Benvolio"); + field->setName("first"); + firstItem.push_back(field); + + field = TextSingleFormField::create("Montague"); + field->setName("last"); + firstItem.push_back(field); + + field = TextSingleFormField::create("benvolio@montague.net"); + field->setName("jid"); + firstItem.push_back(field); + + field = ListSingleFormField::create("male"); + field->setName("x-gender"); + firstItem.push_back(field); + + Form::FormItem secondItem; + field = TextSingleFormField::create("Romeo"); + field->setName("first"); + secondItem.push_back(field); + + field = TextSingleFormField::create("Montague"); + field->setName("last"); + secondItem.push_back(field); + + field = TextSingleFormField::create("romeo@montague.net"); + field->setName("jid"); + secondItem.push_back(field); + + field = ListSingleFormField::create("male"); + field->setName("x-gender"); + secondItem.push_back(field); + + form->addItem(firstItem); + form->addItem(secondItem); + + payload->setForm(form); + + CPPUNIT_ASSERT_EQUAL(std::string( + "" + "" + "" + "jabber:iq:search" + "" + "" + "" + "" + "" + "" + "" + "" + "Benvolio" + "Montague" + "benvolio@montague.net" + "male" + "" + "" + "Romeo" + "Montague" + "romeo@montague.net" + "male" + "" + "" + ""), testling.serialize(payload)); + + } }; CPPUNIT_TEST_SUITE_REGISTRATION(SearchPayloadSerializerTest); -- cgit v0.10.2-6-g49f6