diff --git a/libretroshare/src/util/rsstring.cc b/libretroshare/src/util/rsstring.cc index 1f7eec6de..f105a72b7 100644 --- a/libretroshare/src/util/rsstring.cc +++ b/libretroshare/src/util/rsstring.cc @@ -229,14 +229,11 @@ static int vasprintf(char **sptr, const char *fmt, va_list argv) //} #endif -int rs_sprintf(std::string &str, const char *fmt, ...) +int rs_sprintf_args(std::string &str, const char *fmt, va_list ap) { char *buffer = NULL; - va_list ap; - va_start(ap, fmt); - int retval = vasprintf(&buffer, fmt, ap); - va_end(ap); + int retval = vasprintf(&buffer, fmt, (va_list) ap); if (retval >= 0) { if (buffer) { @@ -252,15 +249,23 @@ int rs_sprintf(std::string &str, const char *fmt, ...) return retval; } -int rs_sprintf_append(std::string &str, const char *fmt, ...) +int rs_sprintf(std::string &str, const char *fmt, ...) { va_list ap; - char *buffer = NULL; va_start(ap, fmt); - int retval = vasprintf(&buffer, fmt, ap); + int retval = rs_sprintf_args(str, fmt, ap); va_end(ap); + return retval; +} + +int rs_sprintf_append_args(std::string &str, const char *fmt, va_list ap) +{ + char *buffer = NULL; + + int retval = vasprintf(&buffer, fmt, (va_list) ap); + if (retval >= 0) { if (buffer) { str.append(buffer); @@ -271,6 +276,17 @@ int rs_sprintf_append(std::string &str, const char *fmt, ...) return retval; } +int rs_sprintf_append(std::string &str, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + int retval = rs_sprintf_append_args(str, fmt, ap); + va_end(ap); + + return retval; +} + void stringToUpperCase(const std::string& s, std::string &upper) { upper = s ; diff --git a/libretroshare/src/util/rsstring.h b/libretroshare/src/util/rsstring.h index d6f74b60d..be26fe54d 100644 --- a/libretroshare/src/util/rsstring.h +++ b/libretroshare/src/util/rsstring.h @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. ****************************************************************/ @@ -23,6 +23,7 @@ #define RSSTRING_H_ #include +#include namespace librs { namespace util { @@ -39,7 +40,9 @@ bool ConvertUtf16ToUtf8(const std::wstring& source, std::string& dest); #define UINT64FMT "%llu" #endif +int rs_sprintf_args(std::string &str, const char *fmt, va_list ap); int rs_sprintf(std::string &str, const char *fmt, ...); +int rs_sprintf_append_args(std::string &str, const char *fmt, va_list ap); int rs_sprintf_append(std::string &str, const char *fmt, ...); void stringToUpperCase(const std::string& s, std::string &upper); diff --git a/plugins/FeedReader/FeedReader.pro b/plugins/FeedReader/FeedReader.pro index 51cca6812..312585e61 100644 --- a/plugins/FeedReader/FeedReader.pro +++ b/plugins/FeedReader/FeedReader.pro @@ -51,8 +51,8 @@ FORMS = gui/FeedReaderDialog.ui \ TARGET = FeedReader RESOURCES = gui/FeedReader_images.qrc \ - lang/FeedReader_lang.qrc - + lang/FeedReader_lang.qrc + TRANSLATIONS += \ lang/FeedReader_cs.ts \ lang/FeedReader_da.ts \ @@ -76,17 +76,17 @@ linux-* { INCLUDEPATH += $${LIBXML2_DIR} - LIBS += -lcurl -lxml2 + LIBS += -lcurl -lxml2 -lxslt } win32 { - DEFINES += CURL_STATICLIB LIBXML_STATIC + DEFINES += CURL_STATICLIB LIBXML_STATIC LIBXSLT_STATIC LIBEXSLT_STATIC CURL_DIR = ../../../curl-7.26.0 LIBXML2_DIR = ../../../libxml2-2.8.0 - LIBICONV_DIR = ../../../libiconv-1.14 + LIBXSLT_DIR = ../../../libxslt-1.1.28 - INCLUDEPATH += $${CURL_DIR}/include $${LIBXML2_DIR}/include $${LIBICONV_DIR}/include + INCLUDEPATH += $${CURL_DIR}/include $${LIBXML2_DIR}/include $${LIBXSLT_DIR} $${LIBICONV_DIR}/include - LIBS += -lcurl -lxml2 -lws2_32 -lwldap32 + LIBS += -lcurl -lxml2 -lxslt -lws2_32 -lwldap32 } diff --git a/plugins/FeedReader/gui/AddFeedDialog.cpp b/plugins/FeedReader/gui/AddFeedDialog.cpp index c3fff7bf6..8e0ba0f74 100644 --- a/plugins/FeedReader/gui/AddFeedDialog.cpp +++ b/plugins/FeedReader/gui/AddFeedDialog.cpp @@ -53,7 +53,6 @@ AddFeedDialog::AddFeedDialog(RsFeedReader *feedReader, FeedReaderNotify *notify, /* currently only for loacl feeds */ connect(ui->saveCompletePageCheckBox, SIGNAL(toggled(bool)), this, SLOT(denyForumToggled())); - connect(ui->embedImagesCheckBox, SIGNAL(toggled(bool)), this, SLOT(denyForumToggled())); connect(ui->urlLineEdit, SIGNAL(textChanged(QString)), this, SLOT(validate())); connect(ui->nameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(validate())); @@ -78,6 +77,9 @@ AddFeedDialog::AddFeedDialog(RsFeedReader *feedReader, FeedReaderNotify *notify, /* not yet supported */ ui->authenticationGroupBox->setEnabled(false); + mTransformationType = RS_FEED_TRANSFORMATION_TYPE_NONE; + ui->transformationTypeLabel->setText(FeedReaderStringDefs::transforationTypeString(mTransformationType)); + /* fill own forums */ std::list forumList; if (rsForums->getForumList(forumList)) { @@ -163,7 +165,7 @@ void AddFeedDialog::typeForumToggled() void AddFeedDialog::denyForumToggled() { - if (ui->saveCompletePageCheckBox->isChecked() || ui->embedImagesCheckBox->isChecked()) { + if (ui->saveCompletePageCheckBox->isChecked()) { ui->typeForumRadio->setEnabled(false); ui->typeLocalRadio->setChecked(true); } else { @@ -230,7 +232,6 @@ bool AddFeedDialog::fillFeed(const std::string &feedId) if (feedInfo.flag.forum) { ui->typeForumRadio->setChecked(true); ui->saveCompletePageCheckBox->setEnabled(false); - ui->embedImagesCheckBox->setEnabled(false); if (feedInfo.forumId.empty()) { ui->forumNameLabel->setText(tr("Not yet created")); @@ -263,8 +264,12 @@ bool AddFeedDialog::fillFeed(const std::string &feedId) ui->useStandardStorageTimeCheckBox->setChecked(feedInfo.flag.standardStorageTime); ui->storageTimeSpinBox->setValue(feedInfo.storageTime / (60 * 60 *24)); + mTransformationType = feedInfo.transformationType; mXPathsToUse = feedInfo.xpathsToUse; mXPathsToRemove = feedInfo.xpathsToRemove; + mXslt = feedInfo.xslt; + + ui->transformationTypeLabel->setText(FeedReaderStringDefs::transforationTypeString(mTransformationType)); } return true; @@ -306,8 +311,10 @@ void AddFeedDialog::getFeedInfo(FeedInfo &feedInfo) feedInfo.flag.standardStorageTime = ui->useStandardStorageTimeCheckBox->isChecked(); feedInfo.storageTime = ui->storageTimeSpinBox->value() * 60 *60 * 24; + feedInfo.transformationType = mTransformationType; feedInfo.xpathsToUse = mXPathsToUse; feedInfo.xpathsToRemove = mXPathsToRemove; + feedInfo.xslt = mXslt; } void AddFeedDialog::createFeed() @@ -345,6 +352,7 @@ void AddFeedDialog::preview() PreviewFeedDialog dialog(mFeedReader, mNotify, feedInfo, this); if (dialog.exec() == QDialog::Accepted) { - dialog.getXPaths(mXPathsToUse, mXPathsToRemove); + mTransformationType = dialog.getData(mXPathsToUse, mXPathsToRemove, mXslt); + ui->transformationTypeLabel->setText(FeedReaderStringDefs::transforationTypeString(mTransformationType)); } } diff --git a/plugins/FeedReader/gui/AddFeedDialog.h b/plugins/FeedReader/gui/AddFeedDialog.h index 44066c522..7b953908d 100644 --- a/plugins/FeedReader/gui/AddFeedDialog.h +++ b/plugins/FeedReader/gui/AddFeedDialog.h @@ -63,8 +63,10 @@ private: std::string mFeedId; std::string mParentId; + RsFeedTransformationType mTransformationType; std::list mXPathsToUse; std::list mXPathsToRemove; + std::string mXslt; Ui::AddFeedDialog *ui; }; diff --git a/plugins/FeedReader/gui/AddFeedDialog.ui b/plugins/FeedReader/gui/AddFeedDialog.ui index 9cc73e83e..41a949545 100644 --- a/plugins/FeedReader/gui/AddFeedDialog.ui +++ b/plugins/FeedReader/gui/AddFeedDialog.ui @@ -21,7 +21,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -36,6 +45,61 @@ QFrame::Raised + + + + Type + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + + Forum + + + + + + + + + + + 0 + 0 + + + + Forum name + + + + + + + + + Local Feed + + + + + + @@ -76,63 +140,6 @@ - - - - Update interval - - - - - - Use standard update interval - - - - - - - Interval in minutes (0 = manual) - - - - - - - - 50 - 16777215 - - - - - - - - - - Last update - - - - - - - - 0 - 0 - - - - Never - - - - - - - - @@ -209,47 +216,47 @@ - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - Preview - - - - - - - + + - Type + Update interval - - - 6 - - - + + + + + Use standard update interval + + + + + + + Interval in minutes (0 = manual) + + + + + + + + 50 + 16777215 + + + + + + - + - Forum + Last update - - - - + 0 @@ -257,78 +264,25 @@ - Forum name + Never - - - - Local Feed - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Misc - - - - - - Activated - - - - - - - Use name and description from feed - - - - - - - Update forum information - - - - - - - Embed images (experimental for local feeds) - - - - - - - Save complete web page (experimental for local feeds) - - - - - + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + @@ -368,6 +322,83 @@ + + + + Transformation + + + + + + + + Transformation type + + + + + + + + 0 + 0 + + + + Preview && Transformation + + + + + + + + + + + + Misc + + + + + + Activated + + + + + + + Use name and description from feed + + + + + + + Update forum information + + + + + + + Embed images + + + + + + + Save complete web page (experimental for local feeds) + + + + + + diff --git a/plugins/FeedReader/gui/FeedReaderFeedItem.cpp b/plugins/FeedReader/gui/FeedReaderFeedItem.cpp index 3c6298152..5a9eaa040 100644 --- a/plugins/FeedReader/gui/FeedReaderFeedItem.cpp +++ b/plugins/FeedReader/gui/FeedReaderFeedItem.cpp @@ -67,7 +67,7 @@ FeedReaderFeedItem::FeedReaderFeedItem(RsFeedReader *feedReader, FeedReaderNotif ui->titleLabel->setText(QString::fromUtf8(feedInfo.name.c_str())); ui->msgTitleLabel->setText(QString::fromUtf8(msgInfo.title.c_str())); - ui->descriptionLabel->setText(QString::fromUtf8(msgInfo.description.c_str())); + ui->descriptionLabel->setText(QString::fromUtf8((msgInfo.descriptionTransformed.empty() ? msgInfo.description : msgInfo.descriptionTransformed).c_str())); ui->dateTimeLabel->setText(DateTime::formatLongDateTime(msgInfo.pubDate)); diff --git a/plugins/FeedReader/gui/FeedReaderMessageWidget.cpp b/plugins/FeedReader/gui/FeedReaderMessageWidget.cpp index 685c3538a..f7eebb105 100644 --- a/plugins/FeedReader/gui/FeedReaderMessageWidget.cpp +++ b/plugins/FeedReader/gui/FeedReaderMessageWidget.cpp @@ -219,7 +219,16 @@ void FeedReaderMessageWidget::setFeedId(const std::string &feedId) mFeedInfo = FeedInfo(); } - ui->msgReadAllButton->setEnabled(!mFeedId.empty()); + if (mFeedId.empty()) { + ui->msgReadAllButton->setEnabled(false); + ui->msgTreeWidget->setPlaceholderText(""); + } else { + if (mFeedInfo.flag.forum) { + ui->msgTreeWidget->setPlaceholderText(tr("The messages will be added to the forum")); + } else { + ui->msgTreeWidget->setPlaceholderText(""); + } + } updateMsgs(); updateCurrentMessage(); @@ -307,6 +316,11 @@ void FeedReaderMessageWidget::msgTreeCustomPopupMenu(QPoint /*point*/) action = contextMnu.addAction(QIcon(""), tr("Remove"), this, SLOT(removeMsg())); action->setEnabled(!selectedItems.empty()); + contextMnu.addSeparator(); + + action = contextMnu.addAction(QIcon(""), tr("Retransform"), this, SLOT(retransformMsg())); + action->setEnabled((mFeedInfo.transformationType != RS_FEED_TRANSFORMATION_TYPE_NONE) && !selectedItems.empty()); + contextMnu.exec(QCursor::pos()); } @@ -495,6 +509,12 @@ void FeedReaderMessageWidget::msgChanged(const QString &feedId, const QString &m } } + if (type == NOTIFY_TYPE_MOD) { + if (msgId.toStdString() == currentMsgId()) { + updateCurrentMessage(); + } + } + if (type == NOTIFY_TYPE_ADD) { QTreeWidgetItem *item = new RSTreeWidgetItem(mMsgCompareRole); updateMsgItem(item, msgInfo); @@ -591,7 +611,7 @@ void FeedReaderMessageWidget::updateCurrentMessage() setMsgAsReadUnread(row, setToReadOnActive); } - QString msgTxt = RsHtml().formatText(ui->msgText->document(), QString::fromUtf8(msgInfo.description.c_str()), RSHTML_FORMATTEXT_EMBED_LINKS); + QString msgTxt = RsHtml().formatText(ui->msgText->document(), QString::fromUtf8((msgInfo.descriptionTransformed.empty() ? msgInfo.description : msgInfo.descriptionTransformed).c_str()), RSHTML_FORMATTEXT_EMBED_LINKS); ui->msgText->setHtml(msgTxt); ui->msgTitle->setText(QString::fromUtf8(msgInfo.title.c_str())); @@ -744,6 +764,20 @@ void FeedReaderMessageWidget::removeMsg() mFeedReader->removeMsgs(mFeedId, msgIds); } +void FeedReaderMessageWidget::retransformMsg() +{ + if (mFeedId.empty()) { + return; + } + + QList selectedItems = ui->msgTreeWidget->selectedItems(); + QList::iterator it; + + for (it = selectedItems.begin(); it != selectedItems.end(); ++it) { + mFeedReader->retransformMsg(mFeedId, (*it)->data(COLUMN_MSG_DATA, ROLE_MSG_ID).toString().toStdString()); + } +} + void FeedReaderMessageWidget::processFeed() { if (mFeedId.empty()) { diff --git a/plugins/FeedReader/gui/FeedReaderMessageWidget.h b/plugins/FeedReader/gui/FeedReaderMessageWidget.h index e3741c1b5..e4034f966 100644 --- a/plugins/FeedReader/gui/FeedReaderMessageWidget.h +++ b/plugins/FeedReader/gui/FeedReaderMessageWidget.h @@ -52,6 +52,7 @@ private slots: void processFeed(); void openLinkMsg(); void copyLinkMsg(); + void retransformMsg(); /* FeedReaderNotify */ void feedChanged(const QString &feedId, int type); diff --git a/plugins/FeedReader/gui/FeedReaderMessageWidget.ui b/plugins/FeedReader/gui/FeedReaderMessageWidget.ui index 8f2376075..5f5be1353 100644 --- a/plugins/FeedReader/gui/FeedReaderMessageWidget.ui +++ b/plugins/FeedReader/gui/FeedReaderMessageWidget.ui @@ -17,7 +17,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -36,7 +45,16 @@ QFrame::Sunken - + + 2 + + + 2 + + + 2 + + 2 @@ -138,7 +156,7 @@ - + 9 @@ -278,6 +296,11 @@ QLineEdit
gui/common/LineEditClear.h
+ + RSTreeWidget + QTreeWidget +
gui/common/RSTreeWidget.h
+
diff --git a/plugins/FeedReader/gui/FeedReaderStringDefs.cpp b/plugins/FeedReader/gui/FeedReaderStringDefs.cpp index d7cd8d502..e40052b36 100644 --- a/plugins/FeedReader/gui/FeedReaderStringDefs.cpp +++ b/plugins/FeedReader/gui/FeedReaderStringDefs.cpp @@ -71,7 +71,7 @@ QString FeedReaderStringDefs::workState(FeedInfo::WorkState state) return QApplication::translate("FeedReaderStringDefs", "Processing"); } - return ""; + return QApplication::translate("FeedReaderStringDefs", "Unknown"); } QString FeedReaderStringDefs::errorString(const FeedInfo &feedInfo) @@ -135,6 +135,15 @@ QString FeedReaderStringDefs::errorString(RsFeedReaderErrorState errorState, con case RS_FEED_ERRORSTATE_PROCESS_XPATH_NO_RESULT: errorText = QApplication::translate("FeedReaderStringDefs", "Empty XPath result"); break; + case RS_FEED_ERRORSTATE_PROCESS_XSLT_FORMAT_ERROR: + errorText = QApplication::translate("FeedReaderStringDefs", "XSLT format error"); + break; + case RS_FEED_ERRORSTATE_PROCESS_XSLT_TRANSFORM_ERROR: + errorText = QApplication::translate("FeedReaderStringDefs", "XSLT transformation error"); + break; + case RS_FEED_ERRORSTATE_PROCESS_XSLT_NO_RESULT: + errorText = QApplication::translate("FeedReaderStringDefs", "Empty XSLT result"); + break; default: errorText = QApplication::translate("FeedReaderStringDefs", "Unknown error"); @@ -146,3 +155,17 @@ QString FeedReaderStringDefs::errorString(RsFeedReaderErrorState errorState, con return errorText; } + +QString FeedReaderStringDefs::transforationTypeString(RsFeedTransformationType transformationType) +{ + switch (transformationType) { + case RS_FEED_TRANSFORMATION_TYPE_NONE: + return QApplication::translate("FeedReaderStringDefs", "No transformation"); + case RS_FEED_TRANSFORMATION_TYPE_XPATH: + return QApplication::translate("FeedReaderStringDefs", "XPath"); + case RS_FEED_TRANSFORMATION_TYPE_XSLT: + return QApplication::translate("FeedReaderStringDefs", "XSLT"); + } + + return QApplication::translate("FeedReaderStringDefs", "Unknown"); +} diff --git a/plugins/FeedReader/gui/FeedReaderStringDefs.h b/plugins/FeedReader/gui/FeedReaderStringDefs.h index 13f9afa30..96c013a69 100644 --- a/plugins/FeedReader/gui/FeedReaderStringDefs.h +++ b/plugins/FeedReader/gui/FeedReaderStringDefs.h @@ -35,6 +35,7 @@ public: static QString workState(FeedInfo::WorkState state); static QString errorString(const FeedInfo &feedInfo); static QString errorString(RsFeedReaderErrorState errorState, const std::string &errorString); + static QString transforationTypeString(RsFeedTransformationType transformationType); }; #endif diff --git a/plugins/FeedReader/gui/PreviewFeedDialog.cpp b/plugins/FeedReader/gui/PreviewFeedDialog.cpp index df61431e9..916aed82c 100644 --- a/plugins/FeedReader/gui/PreviewFeedDialog.cpp +++ b/plugins/FeedReader/gui/PreviewFeedDialog.cpp @@ -30,7 +30,6 @@ #include "util/HandleRichText.h" #include "gui/settings/rsharesettings.h" -#include "interface/rsFeedReader.h" #include "retroshare/rsiface.h" #include "util/HTMLWrapper.h" @@ -146,35 +145,44 @@ PreviewFeedDialog::PreviewFeedDialog(RsFeedReader *feedReader, FeedReaderNotify ui->setupUi(this); ui->feedNameLabel->clear(); - ui->useXPathCheckBox->setChecked(true); /* connect signals */ connect(ui->previousPushButton, SIGNAL(clicked()), this, SLOT(previousMsg())); connect(ui->nextPushButton, SIGNAL(clicked()), this, SLOT(nextMsg())); - connect(ui->closeStructureButton, SIGNAL(clicked()), this, SLOT(showStructureFrame())); - connect(ui->structureButton, SIGNAL(toggled(bool)), this, SLOT(showStructureFrame(bool))); - connect(ui->xpathPushButton, SIGNAL(toggled(bool)), this, SLOT(showXPathFrame(bool))); - connect(ui->useXPathCheckBox, SIGNAL(toggled(bool)), this, SLOT(fillStructureTree())); + connect(ui->structureButton, SIGNAL(toggled(bool)), this, SLOT(showStructureFrame())); connect(ui->xpathUseListWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(xpathListCustomPopupMenu(QPoint))); connect(ui->xpathRemoveListWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(xpathListCustomPopupMenu(QPoint))); connect(ui->xpathUseListWidget->itemDelegate(), SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), this, SLOT(xpathCloseEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); connect(ui->xpathRemoveListWidget->itemDelegate(), SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), this, SLOT(xpathCloseEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + connect(ui->transformationTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(transformationTypeChanged())); connect(mNotify, SIGNAL(feedChanged(QString,int)), this, SLOT(feedChanged(QString,int))); connect(mNotify, SIGNAL(msgChanged(QString,QString,int)), this, SLOT(msgChanged(QString,QString,int))); + ui->transformationTypeComboBox->addItem(FeedReaderStringDefs::transforationTypeString(RS_FEED_TRANSFORMATION_TYPE_NONE), RS_FEED_TRANSFORMATION_TYPE_NONE); + ui->transformationTypeComboBox->addItem(FeedReaderStringDefs::transforationTypeString(RS_FEED_TRANSFORMATION_TYPE_XPATH), RS_FEED_TRANSFORMATION_TYPE_XPATH); + ui->transformationTypeComboBox->addItem(FeedReaderStringDefs::transforationTypeString(RS_FEED_TRANSFORMATION_TYPE_XSLT), RS_FEED_TRANSFORMATION_TYPE_XSLT); + + ui->xsltTextEdit->setPlaceholderText(tr("XSLT is used on focus lost or when Ctrl+Enter is pressed")); + // ui->documentTreeWidget->setItemDelegate(new PreviewItemDelegate(ui->documentTreeWidget)); - ui->structureFrame->hide(); + showStructureFrame(); + + /* Set initial size the splitter */ +// QList sizes; +// sizes << 300 << 300 << 150; // Qt calculates the right sizes +// ui->splitter->setSizes(sizes); if (mFeedReader->addPreviewFeed(feedInfo, mFeedId)) { setFeedInfo(""); } else { setFeedInfo(tr("Cannot create preview")); } - setXPathInfo(""); - showXPathFrame(true); + setTransformationInfo(""); + + /* fill xpath/xslt expressions */ + ui->transformationTypeComboBox->setCurrentIndex(ui->transformationTypeComboBox->findData(feedInfo.transformationType)); - /* fill xpath expressions */ QListWidgetItem *item; std::string xpath; foreach(xpath, feedInfo.xpathsToUse){ @@ -188,12 +196,15 @@ PreviewFeedDialog::PreviewFeedDialog(RsFeedReader *feedReader, FeedReaderNotify ui->xpathRemoveListWidget->addItem(item); } + ui->xsltTextEdit->setPlainText(QString::fromUtf8(feedInfo.xslt.c_str())); + updateMsgCount(); ui->xpathUseListWidget->installEventFilter(this); ui->xpathUseListWidget->viewport()->installEventFilter(this); ui->xpathRemoveListWidget->installEventFilter(this); ui->xpathRemoveListWidget->viewport()->installEventFilter(this); + ui->xsltTextEdit->installEventFilter(this); /* load settings */ processSettings(true); @@ -234,8 +245,6 @@ void PreviewFeedDialog::processSettings(bool load) bool PreviewFeedDialog::eventFilter(QObject *obj, QEvent *event) { - long todo_here; - if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent) { @@ -247,16 +256,28 @@ bool PreviewFeedDialog::eventFilter(QObject *obj, QEvent *event) QListWidgetItem *item = listWidget->currentItem(); if (item) { delete(item); - processXPath(); + processTransformation(); } return true; // eat event } } } + if ((keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) && (keyEvent->modifiers() & Qt::ControlModifier)) { + /* Ctrl+Enter pressed */ + if (obj == ui->xsltTextEdit) { + processTransformation(); + return true; // eat event + } + } } } if (event->type() == QEvent::Drop) { - processXPath(); + processTransformation(); + } + if (event->type() == QEvent::FocusOut) { + if (obj == ui->xsltTextEdit) { + processTransformation(); + } } /* pass the event on to the parent class */ return QDialog::eventFilter(obj, event); @@ -338,27 +359,51 @@ void PreviewFeedDialog::msgChanged(const QString &feedId, const QString &msgId, updateMsgCount(); } -void PreviewFeedDialog::showStructureFrame(bool show) +void PreviewFeedDialog::showStructureFrame() { - ui->structureButton->setChecked(show); - ui->structureFrame->setVisible(show); + bool show = ui->structureButton->isChecked(); + RsFeedTransformationType transformationType = (RsFeedTransformationType) ui->transformationTypeComboBox->itemData(ui->transformationTypeComboBox->currentIndex()).toInt(); - if (show) { - fillStructureTree(); + ui->structureTreeFrame->setVisible(show); + + switch (transformationType) { + case RS_FEED_TRANSFORMATION_TYPE_NONE: + ui->msgTextOrg->hide(); + ui->structureTreeWidgetOrg->hide(); + ui->transformationFrame->hide(); + ui->xpathFrame->hide(); + ui->xsltFrame->hide(); + break; + case RS_FEED_TRANSFORMATION_TYPE_XPATH: + ui->msgTextOrg->setVisible(show); + ui->structureTreeWidgetOrg->show(); + ui->transformationFrame->setVisible(show); + ui->xpathFrame->show(); + ui->xsltFrame->hide(); + break; + case RS_FEED_TRANSFORMATION_TYPE_XSLT: + ui->msgTextOrg->setVisible(show); + ui->structureTreeWidgetOrg->show(); + ui->transformationFrame->setVisible(show); + ui->xpathFrame->hide(); + ui->xsltFrame->show(); + break; } + + if (ui->msgTextOrg->isVisible()) { + QString msgTxt = RsHtml().formatText(ui->msgTextOrg->document(), QString::fromUtf8(mDescription.c_str()), RSHTML_FORMATTEXT_EMBED_LINKS); + ui->msgTextOrg->setHtml(msgTxt); + } else { + ui->msgTextOrg->clear(); + } + fillStructureTree(false); + fillStructureTree(true); } -void PreviewFeedDialog::showXPathFrame(bool show) +void PreviewFeedDialog::transformationTypeChanged() { - ui->xpathFrame->setVisible(show); - - if (show) { - ui->xpathPushButton->setToolTip(tr("Hide XPath expressions")); - ui->xpathPushButton->setIcon(QIcon(":images/show_toolbox_frame.png")); - } else { - ui->xpathPushButton->setToolTip(tr("Show XPath expressions")); - ui->xpathPushButton->setIcon(QIcon(":images/hide_toolbox_frame.png")); - } + showStructureFrame(); + processTransformation(); } void PreviewFeedDialog::xpathListCustomPopupMenu(QPoint /*point*/) @@ -396,7 +441,7 @@ void PreviewFeedDialog::xpathListCustomPopupMenu(QPoint /*point*/) void PreviewFeedDialog::xpathCloseEditor(QWidget */*editor*/, QAbstractItemDelegate::EndEditHint /*hint*/) { - processXPath(); + processTransformation(); } void PreviewFeedDialog::addXPath() @@ -468,7 +513,7 @@ void PreviewFeedDialog::removeXPath() delete(item); } - processXPath(); + processTransformation(); } int PreviewFeedDialog::getMsgPos() @@ -491,10 +536,10 @@ void PreviewFeedDialog::setFeedInfo(const QString &info) ui->feedInfoLabel->setVisible(!info.isEmpty()); } -void PreviewFeedDialog::setXPathInfo(const QString &info) +void PreviewFeedDialog::setTransformationInfo(const QString &info) { - ui->xpathInfoLabel->setText(info); - ui->xpathInfoLabel->setVisible(!info.isEmpty()); + ui->transformationInfoLabel->setText(info); + ui->transformationInfoLabel->setVisible(!info.isEmpty()); } void PreviewFeedDialog::fillFeedInfo(const FeedInfo &feedInfo) @@ -551,8 +596,9 @@ void PreviewFeedDialog::updateMsg() if (mMsgId.empty() || !mFeedReader->getMsgInfo(mFeedId, mMsgId, msgInfo)) { ui->msgTitle->clear(); ui->msgText->clear(); + ui->msgTextOrg->clear(); mDescription.clear(); - mDescriptionXPath.clear(); + mDescriptionTransformed.clear(); return; } @@ -561,8 +607,15 @@ void PreviewFeedDialog::updateMsg() /* store description */ mDescription = msgInfo.description; + if (ui->msgTextOrg->isVisible()) { + QString msgTxt = RsHtml().formatText(ui->msgTextOrg->document(), QString::fromUtf8(mDescription.c_str()), RSHTML_FORMATTEXT_EMBED_LINKS); + ui->msgTextOrg->setHtml(msgTxt); + } + + showStructureFrame(); + /* process xpath */ - processXPath(); + processTransformation(); } static void buildNodeText(HTMLWrapper &html, xmlNodePtr node, QString &text) @@ -699,44 +752,52 @@ static void examineChildElements(QTreeWidget *treeWidget, HTMLWrapper &html, QLi // treeWidget->setItemWidget(item, 0, label); } -void PreviewFeedDialog::fillStructureTree() +void PreviewFeedDialog::fillStructureTree(bool transform) { - if (!ui->structureTreeWidget->isVisible()) { - return; + if (transform && ui->structureTreeWidget->isVisible()) { + if (mDescriptionTransformed.empty()) { + ui->structureTreeWidget->clear(); + } else { + HTMLWrapper html; + if (html.readHTML(mDescriptionTransformed.c_str(), "")) { + xmlNodePtr root = html.getRootElement(); + if (root) { + QList nodes; + nodes.push_back(root); + examineChildElements(ui->structureTreeWidget, html, nodes, ui->structureTreeWidget->invisibleRootItem()); + ui->structureTreeWidget->resizeColumnToContents(0); + } + } else { + QTreeWidgetItem *item = new QTreeWidgetItem; + item->setText(0, tr("Error parsing document") + ": " + QString::fromUtf8(html.lastError().c_str())); + ui->structureTreeWidget->addTopLevelItem(item); + } + } } -// if (ui->structureTreeWidget->topLevelItemCount() > 0) { -// return; -// } - - if (mDescriptionXPath.empty()) { - ui->structureTreeWidget->clear(); - return; + if (!transform && ui->structureTreeWidgetOrg->isVisible()) { + if (mDescription.empty()) { + ui->structureTreeWidgetOrg->clear(); + } else { + HTMLWrapper html; + if (html.readHTML(mDescription.c_str(), "")) { + xmlNodePtr root = html.getRootElement(); + if (root) { + QList nodes; + nodes.push_back(root); + examineChildElements(ui->structureTreeWidgetOrg, html, nodes, ui->structureTreeWidgetOrg->invisibleRootItem()); + ui->structureTreeWidgetOrg->resizeColumnToContents(0); + } + } else { + QTreeWidgetItem *item = new QTreeWidgetItem; + item->setText(0, tr("Error parsing document") + ": " + QString::fromUtf8(html.lastError().c_str())); + ui->structureTreeWidgetOrg->addTopLevelItem(item); + } + } } - - bool useXPath = ui->useXPathCheckBox->isChecked(); - - HTMLWrapper html; - if (!html.readHTML(useXPath ? mDescriptionXPath.c_str() : mDescription.c_str(), "")) { - QTreeWidgetItem *item = new QTreeWidgetItem; - item->setText(0, tr("Error parsing document")); - ui->structureTreeWidget->addTopLevelItem(item); - - return; - } - - xmlNodePtr root = html.getRootElement(); - if (!root) { - return; - } - - QList nodes; - nodes.push_back(root); - examineChildElements(ui->structureTreeWidget, html, nodes, ui->structureTreeWidget->invisibleRootItem()); - ui->structureTreeWidget->resizeColumnToContents(0); } -void PreviewFeedDialog::getXPaths(std::list &xpathsToUse, std::list &xpathsToRemove) +RsFeedTransformationType PreviewFeedDialog::getData(std::list &xpathsToUse, std::list &xpathsToRemove, std::string &xslt) { xpathsToUse.clear(); xpathsToRemove.clear(); @@ -751,24 +812,40 @@ void PreviewFeedDialog::getXPaths(std::list &xpathsToUse, std::list for (row = 0; row < rowCount; ++row) { xpathsToRemove.push_back(ui->xpathRemoveListWidget->item(row)->text().toUtf8().constData()); } + + xslt = ui->xsltTextEdit->toPlainText().toUtf8().constData(); + + return (RsFeedTransformationType) ui->transformationTypeComboBox->itemData(ui->transformationTypeComboBox->currentIndex()).toInt(); } -void PreviewFeedDialog::processXPath() +void PreviewFeedDialog::processTransformation() { std::list xpathsToUse; std::list xpathsToRemove; + std::string xslt; - getXPaths(xpathsToUse, xpathsToRemove); + RsFeedTransformationType transformationType = getData(xpathsToUse, xpathsToRemove, xslt); - mDescriptionXPath = mDescription; + mDescriptionTransformed = mDescription; std::string errorString; - RsFeedReaderErrorState result = mFeedReader->processXPath(xpathsToUse, xpathsToRemove, mDescriptionXPath, errorString); - setXPathInfo(FeedReaderStringDefs::errorString(result, errorString)); + + RsFeedReaderErrorState result = RS_FEED_ERRORSTATE_OK; + switch (transformationType) { + case RS_FEED_TRANSFORMATION_TYPE_NONE: + break; + case RS_FEED_TRANSFORMATION_TYPE_XPATH: + result = mFeedReader->processXPath(xpathsToUse, xpathsToRemove, mDescriptionTransformed, errorString); + break; + case RS_FEED_TRANSFORMATION_TYPE_XSLT: + result = mFeedReader->processXslt(xslt, mDescriptionTransformed, errorString); + break; + } + setTransformationInfo(FeedReaderStringDefs::errorString(result, errorString)); /* fill message */ - QString msgTxt = RsHtml().formatText(ui->msgText->document(), QString::fromUtf8(mDescriptionXPath.c_str()), RSHTML_FORMATTEXT_EMBED_LINKS); + QString msgTxt = RsHtml().formatText(ui->msgText->document(), QString::fromUtf8(mDescriptionTransformed.c_str()), RSHTML_FORMATTEXT_EMBED_LINKS); ui->msgText->setHtml(msgTxt); /* fill structure */ - fillStructureTree(); + fillStructureTree(true); } diff --git a/plugins/FeedReader/gui/PreviewFeedDialog.h b/plugins/FeedReader/gui/PreviewFeedDialog.h index d6c7e3da9..cc9cc7cca 100644 --- a/plugins/FeedReader/gui/PreviewFeedDialog.h +++ b/plugins/FeedReader/gui/PreviewFeedDialog.h @@ -25,6 +25,8 @@ #include #include +#include "interface/rsFeedReader.h" + namespace Ui { class PreviewFeedDialog; } @@ -54,12 +56,12 @@ class FeedInfo; class PreviewFeedDialog : public QDialog { Q_OBJECT - + public: PreviewFeedDialog(RsFeedReader *feedReader, FeedReaderNotify *notify, const FeedInfo &feedInfo, QWidget *parent = 0); ~PreviewFeedDialog(); - void getXPaths(std::list &xpathsToUse, std::list &xpathsToRemove); + RsFeedTransformationType getData(std::list &xpathsToUse, std::list &xpathsToRemove, std::string &xslt); protected: bool eventFilter(QObject *obj, QEvent *ev); @@ -67,14 +69,13 @@ protected: private slots: void previousMsg(); void nextMsg(); - void showStructureFrame(bool show = false); - void showXPathFrame(bool show); + void showStructureFrame(); void xpathListCustomPopupMenu(QPoint point); void xpathCloseEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint); void addXPath(); void editXPath(); void removeXPath(); - void fillStructureTree(); + void transformationTypeChanged(); /* FeedReaderNotify */ void feedChanged(const QString &feedId, int type); @@ -84,11 +85,12 @@ private: void processSettings(bool load); int getMsgPos(); void setFeedInfo(const QString &info); - void setXPathInfo(const QString &info); + void setTransformationInfo(const QString &info); void fillFeedInfo(const FeedInfo &feedInfo); void updateMsgCount(); void updateMsg(); - void processXPath(); + void fillStructureTree(bool transform); + void processTransformation(); RsFeedReader *mFeedReader; FeedReaderNotify *mNotify; @@ -96,7 +98,7 @@ private: std::string mMsgId; std::list mMsgIds; std::string mDescription; - std::string mDescriptionXPath; + std::string mDescriptionTransformed; Ui::PreviewFeedDialog *ui; }; diff --git a/plugins/FeedReader/gui/PreviewFeedDialog.ui b/plugins/FeedReader/gui/PreviewFeedDialog.ui index cc1ef9b38..ddda31182 100644 --- a/plugins/FeedReader/gui/PreviewFeedDialog.ui +++ b/plugins/FeedReader/gui/PreviewFeedDialog.ui @@ -7,7 +7,7 @@ 0 0 800 - 783 + 521
@@ -21,275 +21,337 @@ 0 - - - - - - 0 - 0 - - - - Name: - - - - - - - Feed name - - - true - - - - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 118 - 116 - 108 - - - - - - - - Feed information - - - true - - - - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 118 - 116 - 108 - - - - - - - - XPath information - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - Previous - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - - - - - - 0 - 0 - - - - 0/0 - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - - - - - - 0 - 0 - - - - Next - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Structure - - - true - - - - - - - - - - - - 0 - 0 - - - - Title: - - - - - - - - 0 - 24 - - - - true - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - + - + 0 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QLayout::SetMaximumSize + + + + + + 0 + 0 + + + + Name: + + + + + + + Feed name + + + true + + + + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 118 + 116 + 108 + + + + + + + + Feed information + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 16777215 + 100 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 118 + 116 + 108 + + + + + + + + Transformation information + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Previous + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + + 0 + 0 + + + + 0/0 + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + + 0 + 0 + + + + Next + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Transformation type + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + Structure + + + true + + + + + + + + + + + + 0 + 0 + + + + Title: + + + + + + + + 0 + 24 + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + + QFrame::StyledPanel @@ -302,18 +364,8 @@ false - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 - + + 0 @@ -327,247 +379,211 @@ 0
- - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 16 - 16 - - - - QToolButton -{ - border-image: url(:/images/closenormal.png) -} - -QToolButton:hover -{ -border-image: url(:/images/closehover.png) -} - -QToolButton:pressed { -border-image: url(:/images/closepressed.png) -} - - - ... - - - true - - - - + - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - Qt::Horizontal - - - false - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 2 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Use XPath - - - - - - - true - - - false - - - false - - - - 1 - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 2 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - XPath use - - - - - - - Qt::CustomContextMenu - - - QAbstractItemView::InternalMove - - - - - - - XPath remove - - - - - - - Qt::CustomContextMenu - - - QAbstractItemView::InternalMove - - - - - - - - - - - - - - 14 - 31 - - - - - 14 - 31 - - - - true - - - - - - - Qt::Vertical - - - - 13 - 13 - - - - - - - + + +
+ + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + false + + + false + + + + 1 + + + + + + + + true + + + false + + + false + + + + 1 + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + XPath use + + + + + + + Qt::CustomContextMenu + + + QAbstractItemView::InternalMove + + + + + + + + + + + XPath remove + + + + + + + Qt::CustomContextMenu + + + QAbstractItemView::InternalMove + + + + + + + - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + titleFrame + splitter + buttonBox + transformationInfoScrollArea + + + RSPlainTextEdit + QPlainTextEdit +
gui/common/RSPlainTextEdit.h
+
+
diff --git a/plugins/FeedReader/interface/rsFeedReader.h b/plugins/FeedReader/interface/rsFeedReader.h index 53729359f..9112adfc4 100644 --- a/plugins/FeedReader/interface/rsFeedReader.h +++ b/plugins/FeedReader/interface/rsFeedReader.h @@ -26,6 +26,9 @@ #include #include +class RsFeedReader; +extern RsFeedReader *rsFeedReader; + enum RsFeedReaderErrorState { RS_FEED_ERRORSTATE_OK = 0, @@ -47,13 +50,12 @@ enum RsFeedReaderErrorState { RS_FEED_ERRORSTATE_PROCESS_HTML_ERROR = 150, RS_FEED_ERRORSTATE_PROCESS_XPATH_INTERNAL_ERROR = 151, RS_FEED_ERRORSTATE_PROCESS_XPATH_WRONG_EXPRESSION = 152, - RS_FEED_ERRORSTATE_PROCESS_XPATH_NO_RESULT = 153 + RS_FEED_ERRORSTATE_PROCESS_XPATH_NO_RESULT = 153, + RS_FEED_ERRORSTATE_PROCESS_XSLT_FORMAT_ERROR = 154, + RS_FEED_ERRORSTATE_PROCESS_XSLT_TRANSFORM_ERROR = 155, + RS_FEED_ERRORSTATE_PROCESS_XSLT_NO_RESULT = 156 }; - -class RsFeedReader; -extern RsFeedReader *rsFeedReader; - enum RsFeedAddResult { RS_FEED_ADD_RESULT_SUCCESS, @@ -64,6 +66,13 @@ enum RsFeedAddResult RS_FEED_ADD_RESULT_FEED_IS_NO_FOLDER }; +enum RsFeedTransformationType +{ + RS_FEED_TRANSFORMATION_TYPE_NONE = 0, + RS_FEED_TRANSFORMATION_TYPE_XPATH = 1, + RS_FEED_TRANSFORMATION_TYPE_XSLT = 2 +}; + class FeedInfo { public: @@ -96,28 +105,31 @@ public: flag.embedImages = false; flag.saveCompletePage = false; flag.preview = false; + transformationType = RS_FEED_TRANSFORMATION_TYPE_NONE; } - std::string feedId; - std::string parentId; - std::string url; - std::string name; - std::string description; - std::string icon; - std::string user; - std::string password; - std::string proxyAddress; - uint16_t proxyPort; - uint32_t updateInterval; - time_t lastUpdate; - uint32_t storageTime; - std::string forumId; - WorkState workstate; - RsFeedReaderErrorState errorState; - std::string errorString; + std::string feedId; + std::string parentId; + std::string url; + std::string name; + std::string description; + std::string icon; + std::string user; + std::string password; + std::string proxyAddress; + uint16_t proxyPort; + uint32_t updateInterval; + time_t lastUpdate; + uint32_t storageTime; + std::string forumId; + WorkState workstate; + RsFeedReaderErrorState errorState; + std::string errorString; - std::list xpathsToUse; - std::list xpathsToRemove; + RsFeedTransformationType transformationType; + std::list xpathsToUse; + std::list xpathsToRemove; + std::string xslt; struct { bool folder : 1; @@ -152,6 +164,7 @@ public: std::string link; std::string author; std::string description; + std::string descriptionTransformed; time_t pubDate; struct { @@ -202,8 +215,10 @@ public: virtual bool getFeedMsgIdList(const std::string &feedId, std::list &msgIds) = 0; virtual bool processFeed(const std::string &feedId) = 0; virtual bool setMessageRead(const std::string &feedId, const std::string &msgId, bool read) = 0; + virtual bool retransformMsg(const std::string &feedId, const std::string &msgId) = 0; virtual RsFeedReaderErrorState processXPath(const std::list &xpathsToUse, const std::list &xpathsToRemove, std::string &description, std::string &errorString) = 0; + virtual RsFeedReaderErrorState processXslt(const std::string &xslt, std::string &description, std::string &errorString) = 0; }; #endif diff --git a/plugins/FeedReader/services/p3FeedReader.cc b/plugins/FeedReader/services/p3FeedReader.cc index 35dcd5049..bfb307a0c 100644 --- a/plugins/FeedReader/services/p3FeedReader.cc +++ b/plugins/FeedReader/services/p3FeedReader.cc @@ -88,8 +88,10 @@ static void feedToInfo(const RsFeedReaderFeed *feed, FeedInfo &info) info.errorState = feed->errorState; info.errorString = feed->errorString; + info.transformationType = feed->transformationType; info.xpathsToUse = feed->xpathsToUse.ids; info.xpathsToRemove = feed->xpathsToRemove.ids; + info.xslt = feed->xslt; info.flag.folder = (feed->flag & RS_FEED_FLAG_FOLDER); info.flag.infoFromFeed = (feed->flag & RS_FEED_FLAG_INFO_FROM_FEED); @@ -144,8 +146,10 @@ static void infoToFeed(const FeedInfo &info, RsFeedReaderFeed *feed, bool add) feed->forumId = info.forumId; } + feed->transformationType = info.transformationType; feed->xpathsToUse.ids = info.xpathsToUse; feed->xpathsToRemove.ids = info.xpathsToRemove; + feed->xslt = info.xslt; // feed->preview = info.flag.preview; @@ -200,6 +204,7 @@ static void feedMsgToInfo(const RsFeedReaderMsg *msg, FeedMsgInfo &info) info.link = msg->link; info.author = msg->author; info.description = msg->description; + info.descriptionTransformed = msg->descriptionTransformed; info.pubDate = msg->pubDate; info.flag.isnew = (msg->flag & RS_FEEDMSG_FLAG_NEW); @@ -491,6 +496,7 @@ RsFeedAddResult p3FeedReader::setFeed(const std::string &feedId, const FeedInfo forumId = fi->forumId; librs::util::ConvertUtf8ToUtf16(fi->name, forumInfo.forumName); librs::util::ConvertUtf8ToUtf16(fi->description, forumInfo.forumDesc); + forumInfo.forumName.insert(0, FEEDREADER_FORUM_PREFIX); } } @@ -759,8 +765,11 @@ bool p3FeedReader::removeMsg(const std::string &feedId, const std::string &msgId return false; } - msgIt->second->flag |= RS_FEEDMSG_FLAG_DELETED | RS_FEEDMSG_FLAG_READ; - msgIt->second->flag &= ~RS_FEEDMSG_FLAG_NEW; + RsFeedReaderMsg *mi = msgIt->second; + mi->flag |= RS_FEEDMSG_FLAG_DELETED | RS_FEEDMSG_FLAG_READ; + mi->flag &= ~RS_FEEDMSG_FLAG_NEW; + mi->description.clear(); + mi->descriptionTransformed.clear(); } if (changed) { @@ -805,8 +814,11 @@ bool p3FeedReader::removeMsgs(const std::string &feedId, const std::listsecond->flag |= RS_FEEDMSG_FLAG_DELETED | RS_FEEDMSG_FLAG_READ; - msgIt->second->flag &= ~RS_FEEDMSG_FLAG_NEW; + RsFeedReaderMsg *mi = msgIt->second; + mi->flag |= RS_FEEDMSG_FLAG_DELETED | RS_FEEDMSG_FLAG_READ; + mi->flag &= ~RS_FEEDMSG_FLAG_NEW; + mi->description.clear(); + mi->descriptionTransformed.clear(); removedMsgs.push_back(*idIt); } @@ -1121,11 +1133,74 @@ bool p3FeedReader::setMessageRead(const std::string &feedId, const std::string & return true; } +bool p3FeedReader::retransformMsg(const std::string &feedId, const std::string &msgId) +{ + bool msgChanged = false; + bool feedChanged = false; + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::map::iterator feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setMessageRead - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + RsFeedReaderFeed *fi = feedIt->second; + + std::map::iterator msgIt; + msgIt = fi->msgs.find(msgId); + if (msgIt == fi->msgs.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setMessageRead - msg " << msgId << " not found" << std::endl; +#endif + return false; + } + + RsFeedReaderMsg *mi = msgIt->second; + + std::string errorString; + std::string descriptionTransformed = mi->descriptionTransformed; + if (p3FeedReaderThread::processTransformation(*fi, mi, errorString) == RS_FEED_ERRORSTATE_OK) { + if (mi->descriptionTransformed != descriptionTransformed) { + msgChanged = true; + } + } else { + if (!errorString.empty()) { + fi->errorString = errorString; + feedChanged = true; + } + } + } + + if (feedChanged || msgChanged) { + IndicateConfigChanged(); + if (mNotify) { + if (feedChanged) { + mNotify->notifyFeedChanged(feedId, NOTIFY_TYPE_MOD); + } + if (msgChanged) { + mNotify->notifyMsgChanged(feedId, msgId, NOTIFY_TYPE_MOD); + } + } + } + + return true; +} + RsFeedReaderErrorState p3FeedReader::processXPath(const std::list &xpathsToUse, const std::list &xpathsToRemove, std::string &description, std::string &errorString) { return p3FeedReaderThread::processXPath(xpathsToUse, xpathsToRemove, description, errorString); } +RsFeedReaderErrorState p3FeedReader::processXslt(const std::string &xslt, std::string &description, std::string &errorString) +{ + return p3FeedReaderThread::processXslt(xslt, description, errorString); +} + /***************************************************************************/ /****************************** p3Service **********************************/ /***************************************************************************/ @@ -1776,7 +1851,8 @@ void p3FeedReader::onProcessSuccess_addMsgs(const std::string &feedId, std::list if (forum) { miNew->flag = RS_FEEDMSG_FLAG_DELETED; forumMsgs.push_back(*miNew); -// miNew->description.clear(); + miNew->description.clear(); + miNew->descriptionTransformed.clear(); } else { miNew->flag = RS_FEEDMSG_FLAG_NEW; addedMsgs.push_back(miNew->msgId); @@ -1816,7 +1892,7 @@ void p3FeedReader::onProcessSuccess_addMsgs(const std::string &feedId, std::list forumMsgInfo.forumId = forumId; librs::util::ConvertUtf8ToUtf16(mi.title, forumMsgInfo.title); - std::string description = mi.description; + std::string description = mi.descriptionTransformed.empty() ? mi.description : mi.descriptionTransformed; /* add link */ if (!mi.link.empty()) { description += "
" + mi.link + ""; diff --git a/plugins/FeedReader/services/p3FeedReader.h b/plugins/FeedReader/services/p3FeedReader.h index 34901620e..b92c89dab 100644 --- a/plugins/FeedReader/services/p3FeedReader.h +++ b/plugins/FeedReader/services/p3FeedReader.h @@ -62,8 +62,10 @@ public: virtual bool getFeedMsgIdList(const std::string &feedId, std::list &msgIds); virtual bool processFeed(const std::string &feedId); virtual bool setMessageRead(const std::string &feedId, const std::string &msgId, bool read); + virtual bool retransformMsg(const std::string &feedId, const std::string &msgId); virtual RsFeedReaderErrorState processXPath(const std::list &xpathsToUse, const std::list &xpathsToRemove, std::string &description, std::string &errorString); + virtual RsFeedReaderErrorState processXslt(const std::string &xslt, std::string &description, std::string &errorString); /****************** p3Service STUFF ******************/ virtual int tick(); diff --git a/plugins/FeedReader/services/p3FeedReaderThread.cc b/plugins/FeedReader/services/p3FeedReaderThread.cc index d6a70c14f..b5094691a 100644 --- a/plugins/FeedReader/services/p3FeedReaderThread.cc +++ b/plugins/FeedReader/services/p3FeedReaderThread.cc @@ -117,6 +117,10 @@ void p3FeedReaderThread::run() delete (*it1); } } else { + result = processTransformation(feed, mi, errorString); + if (result != RS_FEED_ERRORSTATE_OK) { + break; + } ++it; } } @@ -258,14 +262,14 @@ static bool getFavicon(CURLWrapper &CURL, const std::string &url, std::string &i return result; } -RsFeedReaderErrorState p3FeedReaderThread::download(const RsFeedReaderFeed &feed, std::string &content, std::string &icon, std::string &error) +RsFeedReaderErrorState p3FeedReaderThread::download(const RsFeedReaderFeed &feed, std::string &content, std::string &icon, std::string &errorString) { #ifdef FEEDREADER_DEBUG std::cerr << "p3FeedReaderThread::download - feed " << feed.feedId << " (" << feed.name << ")" << std::endl; #endif content.clear(); - error.clear(); + errorString.clear(); RsFeedReaderErrorState result; @@ -291,7 +295,7 @@ RsFeedReaderErrorState p3FeedReaderThread::download(const RsFeedReaderFeed &feed result = RS_FEED_ERRORSTATE_OK; } else { result = RS_FEED_ERRORSTATE_DOWNLOAD_UNKNOWN_CONTENT_TYPE; - error = contentType; + errorString = contentType; } } break; @@ -300,17 +304,17 @@ RsFeedReaderErrorState p3FeedReaderThread::download(const RsFeedReaderFeed &feed break; default: result = RS_FEED_ERRORSTATE_DOWNLOAD_UNKOWN_RESPONSE_CODE; - rs_sprintf(error, "%ld", responseCode); + rs_sprintf(errorString, "%ld", responseCode); } getFavicon(CURL, feed.url, icon); } else { result = RS_FEED_ERRORSTATE_DOWNLOAD_ERROR; - error = curl_easy_strerror(code); + errorString = curl_easy_strerror(code); } #ifdef FEEDREADER_DEBUG - std::cerr << "p3FeedReaderThread::download - feed " << feed.feedId << " (" << feed.name << "), result " << result << ", error = " << error << std::endl; + std::cerr << "p3FeedReaderThread::download - feed " << feed.feedId << " (" << feed.name << "), result " << result << ", error = " << errorString << std::endl; #endif return result; @@ -805,7 +809,7 @@ static time_t parseISO8601Date(const std::string &pubDate) return result; } -RsFeedReaderErrorState p3FeedReaderThread::process(const RsFeedReaderFeed &feed, std::list &entries, std::string &error) +RsFeedReaderErrorState p3FeedReaderThread::process(const RsFeedReaderFeed &feed, std::list &entries, std::string &errorString) { #ifdef FEEDREADER_DEBUG std::cerr << "p3FeedReaderThread::process - feed " << feed.feedId << " (" << feed.name << ")" << std::endl; @@ -826,7 +830,7 @@ RsFeedReaderErrorState p3FeedReaderThread::process(const RsFeedReaderFeed &feed, feedFormat = FORMAT_ATOM; } else { result = RS_FEED_ERRORSTATE_PROCESS_UNKNOWN_FORMAT; - error = "Only RSS, RDF or ATOM supported"; + errorString = "Only RSS, RDF or ATOM supported"; } if (result == RS_FEED_ERRORSTATE_OK) { @@ -962,19 +966,23 @@ RsFeedReaderErrorState p3FeedReaderThread::process(const RsFeedReaderFeed &feed, } } else { result = RS_FEED_ERRORSTATE_PROCESS_UNKNOWN_FORMAT; - error = "Channel not found"; + errorString = "Channel not found"; } } } else { result = RS_FEED_ERRORSTATE_PROCESS_UNKNOWN_FORMAT; - error = "Can't read document"; + errorString = "Can't read document"; } } else { result = RS_FEED_ERRORSTATE_PROCESS_INTERNAL_ERROR; + errorString = xml.lastError(); } #ifdef FEEDREADER_DEBUG - std::cerr << "p3FeedReaderThread::process - feed " << feed.feedId << " (" << feed.name << "), result " << result << ", error = " << error << std::endl; + std::cerr << "p3FeedReaderThread::process - feed " << feed.feedId << " (" << feed.name << "), result " << result << ", error = " << errorString << std::endl; + if (result == RS_FEED_ERRORSTATE_PROCESS_INTERNAL_ERROR) { + std::cerr << " Error: " << errorString << std::endl; + } #endif return result; @@ -1059,7 +1067,7 @@ RsFeedReaderErrorState p3FeedReaderThread::processMsg(const RsFeedReaderFeed &fe } /* check if string contains xml chars (very simple test) */ - if (msg->description.find('<') == std::string::npos) { + if (msg->description.find('<') == std::string::npos && feed.transformationType == RS_FEED_TRANSFORMATION_TYPE_NONE) { return result; } @@ -1164,10 +1172,6 @@ RsFeedReaderErrorState p3FeedReaderThread::processMsg(const RsFeedReaderFeed &fe } nodesToDelete.clear(); - if (isRunning() && !feed.preview) { - result = processXPath(feed.xpathsToUse.ids, feed.xpathsToRemove.ids, html, errorString); - } - if (isRunning() && result == RS_FEED_ERRORSTATE_OK) { unsigned int xpathCount; unsigned int xpathIndex; @@ -1241,8 +1245,10 @@ RsFeedReaderErrorState p3FeedReaderThread::processMsg(const RsFeedReaderFeed &fe if (result == RS_FEED_ERRORSTATE_OK) { if (isRunning()) { if (!html.saveHTML(msg->description)) { + errorString = html.lastError(); #ifdef FEEDREADER_DEBUG std::cerr << "p3FeedReaderThread::processHTML - feed " << feed.feedId << " (" << feed.name << ") cannot dump html" << std::endl; + std::cerr << " Error: " << errorString << std::endl; #endif result = RS_FEED_ERRORSTATE_PROCESS_INTERNAL_ERROR; } @@ -1255,8 +1261,10 @@ RsFeedReaderErrorState p3FeedReaderThread::processMsg(const RsFeedReaderFeed &fe result = RS_FEED_ERRORSTATE_PROCESS_HTML_ERROR; } } else { + errorString = html.lastError(); #ifdef FEEDREADER_DEBUG std::cerr << "p3FeedReaderThread::processHTML - feed " << feed.feedId << " (" << feed.name << ") cannot read html" << std::endl; + std::cerr << " Error: " << errorString << std::endl; #endif result = RS_FEED_ERRORSTATE_PROCESS_HTML_ERROR; } @@ -1265,6 +1273,30 @@ RsFeedReaderErrorState p3FeedReaderThread::processMsg(const RsFeedReaderFeed &fe return result; } +RsFeedReaderErrorState p3FeedReaderThread::processTransformation(const RsFeedReaderFeed &feed, RsFeedReaderMsg *msg, std::string &errorString) +{ + RsFeedReaderErrorState result = RS_FEED_ERRORSTATE_OK; + + switch (feed.transformationType) { + case RS_FEED_TRANSFORMATION_TYPE_NONE: + break; + case RS_FEED_TRANSFORMATION_TYPE_XPATH: + msg->descriptionTransformed = msg->description; + result = processXPath(feed.xpathsToUse.ids, feed.xpathsToRemove.ids, msg->descriptionTransformed, errorString); + break; + case RS_FEED_TRANSFORMATION_TYPE_XSLT: + msg->descriptionTransformed = msg->description; + result = processXslt(feed.xslt, msg->descriptionTransformed, errorString); + break; + } + + if (msg->descriptionTransformed == msg->description) { + msg->descriptionTransformed.clear(); + } + + return result; +} + RsFeedReaderErrorState p3FeedReaderThread::processXPath(const std::list &xpathsToUse, const std::list &xpathsToRemove, HTMLWrapper &html, std::string &errorString) { long todo_fill_errorString; @@ -1376,8 +1408,6 @@ RsFeedReaderErrorState p3FeedReaderThread::processXPath(const std::listchildren && xmlResult.nodeName(root->children) == "body") { + root = root->children->children; + } + } + HTMLWrapper htmlNew; + if (htmlNew.createHTML()) { + xmlNodePtr body = htmlNew.getBody(); + if (body) { + /* copy result nodes */ + xmlNodePtr node; + for (node = root; node; node = node->next) { + xmlNodePtr newNode = xmlCopyNode(node, 1); + if (newNode) { + if (!xmlAddChild(body, newNode)) { + xmlFreeNode(newNode); + break; + } + } else { + result = RS_FEED_ERRORSTATE_PROCESS_INTERNAL_ERROR; +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReaderThread::processXslt - node copy error" << std::endl; +#endif + break; + } + } + } else { + result = RS_FEED_ERRORSTATE_PROCESS_HTML_ERROR; + } + } else { + result = RS_FEED_ERRORSTATE_PROCESS_HTML_ERROR; + } + + if (result == RS_FEED_ERRORSTATE_OK) { + html = htmlNew; + } + } else { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReaderThread::processXslt - no result" << std::endl; +#endif + result = RS_FEED_ERRORSTATE_PROCESS_XSLT_NO_RESULT; + } + + return result; +} + +RsFeedReaderErrorState p3FeedReaderThread::processXslt(const std::string &xslt, std::string &description, std::string &errorString) +{ + if (xslt.empty()) { + return RS_FEED_ERRORSTATE_OK; + } + + RsFeedReaderErrorState result = RS_FEED_ERRORSTATE_OK; + + /* process description */ + long todo; // encoding + HTMLWrapper html; + if (html.readHTML(description.c_str(), "")) { + xmlNodePtr root = html.getRootElement(); + if (root) { + result = processXslt(xslt, html, errorString); + + if (result == RS_FEED_ERRORSTATE_OK) { + if (!html.saveHTML(description)) { + errorString = html.lastError(); +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReaderThread::processXslt - cannot dump html" << std::endl; + std::cerr << " Error: " << errorString << std::endl; +#endif + result = RS_FEED_ERRORSTATE_PROCESS_INTERNAL_ERROR; + } + } + } else { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReaderThread::processXslt - no root element" << std::endl; +#endif + errorString = "No root element found"; + result = RS_FEED_ERRORSTATE_PROCESS_HTML_ERROR; + } + } else { + errorString = html.lastError(); +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReaderThread::processXslt - cannot read html" << std::endl; + std::cerr << " Error: " << errorString << std::endl; #endif result = RS_FEED_ERRORSTATE_PROCESS_HTML_ERROR; } diff --git a/plugins/FeedReader/services/p3FeedReaderThread.h b/plugins/FeedReader/services/p3FeedReaderThread.h index 9e3e926b9..e6c43f0dd 100644 --- a/plugins/FeedReader/services/p3FeedReaderThread.h +++ b/plugins/FeedReader/services/p3FeedReaderThread.h @@ -51,11 +51,15 @@ public: static RsFeedReaderErrorState processXPath(const std::list &xpathsToUse, const std::list &xpathsToRemove, std::string &description, std::string &errorString); static RsFeedReaderErrorState processXPath(const std::list &xpathsToUse, const std::list &xpathsToRemove, HTMLWrapper &html, std::string &errorString); + static RsFeedReaderErrorState processXslt(const std::string &xslt, std::string &description, std::string &errorString); + static RsFeedReaderErrorState processXslt(const std::string &xslt, HTMLWrapper &html, std::string &errorString); + + static RsFeedReaderErrorState processTransformation(const RsFeedReaderFeed &feed, RsFeedReaderMsg *msg, std::string &errorString); private: virtual void run(); - RsFeedReaderErrorState download(const RsFeedReaderFeed &feed, std::string &content, std::string &icon, std::string &error); - RsFeedReaderErrorState process(const RsFeedReaderFeed &feed, std::list &entries, std::string &error); + RsFeedReaderErrorState download(const RsFeedReaderFeed &feed, std::string &content, std::string &icon, std::string &errorString); + RsFeedReaderErrorState process(const RsFeedReaderFeed &feed, std::list &entries, std::string &errorString); std::string getProxyForFeed(const RsFeedReaderFeed &feed); RsFeedReaderErrorState processMsg(const RsFeedReaderFeed &feed, RsFeedReaderMsg *msg, std::string &errorString); diff --git a/plugins/FeedReader/services/rsFeedReaderItems.cc b/plugins/FeedReader/services/rsFeedReaderItems.cc index 1d5f60cdd..0c4c40d98 100644 --- a/plugins/FeedReader/services/rsFeedReaderItems.cc +++ b/plugins/FeedReader/services/rsFeedReaderItems.cc @@ -51,8 +51,10 @@ void RsFeedReaderFeed::clear() icon.clear(); errorState = RS_FEED_ERRORSTATE_OK; errorString.clear(); + transformationType = RS_FEED_TRANSFORMATION_TYPE_NONE; xpathsToUse.ids.clear(); xpathsToRemove.ids.clear(); + xslt.clear(); preview = false; workstate = WAITING; @@ -85,8 +87,10 @@ uint32_t RsFeedReaderSerialiser::sizeFeed(RsFeedReaderFeed *item) s += GetTlvStringSize(item->forumId); s += sizeof(uint32_t); /* errorstate */ s += GetTlvStringSize(item->errorString); + s += sizeof(uint32_t); /* transformationType */ s += item->xpathsToUse.TlvSize(); s += item->xpathsToRemove.TlvSize(); + s += GetTlvStringSize(item->xslt); return s; } @@ -110,7 +114,7 @@ bool RsFeedReaderSerialiser::serialiseFeed(RsFeedReaderFeed *item, void *data, u offset += 8; /* add values */ - ok &= setRawUInt16(data, tlvsize, &offset, 0); /* version */ + ok &= setRawUInt16(data, tlvsize, &offset, 1); /* version */ ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_GENID, item->feedId); ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->parentId); ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_LINK, item->url); @@ -128,8 +132,10 @@ bool RsFeedReaderSerialiser::serialiseFeed(RsFeedReaderFeed *item, void *data, u ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->forumId); ok &= setRawUInt32(data, tlvsize, &offset, item->errorState); ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->errorString); + ok &= setRawUInt32(data, tlvsize, &offset, item->transformationType); ok &= item->xpathsToUse.SetTlv(data, tlvsize, &offset); ok &= item->xpathsToRemove.SetTlv(data, tlvsize, &offset); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->xslt); if (offset != tlvsize) { @@ -192,9 +198,28 @@ RsFeedReaderFeed *RsFeedReaderSerialiser::deserialiseFeed(void *data, uint32_t * ok &= getRawUInt32(data, rssize, &offset, &errorState); item->errorState = (RsFeedReaderErrorState) errorState; ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->errorString); + if (version >= 1) { + uint32_t value = RS_FEED_TRANSFORMATION_TYPE_NONE; + ok &= getRawUInt32(data, rssize, &offset, &value); + if (ok) + { + item->transformationType = (RsFeedTransformationType) value; + } + } ok &= item->xpathsToUse.GetTlv(data, rssize, &offset); ok &= item->xpathsToRemove.GetTlv(data, rssize, &offset); + if (version >= 1) { + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->xslt); + } + if (version == 0) + { + if (!item->xpathsToUse.ids.empty() || !item->xpathsToRemove.ids.empty()) + { + /* set transformation type */ + item->transformationType = RS_FEED_TRANSFORMATION_TYPE_XPATH; + } + } if (offset != rssize) { /* error */ @@ -226,6 +251,7 @@ void RsFeedReaderMsg::clear() link.clear(); author.clear(); description.clear(); + descriptionTransformed.clear(); pubDate = 0; flag = 0; } @@ -245,6 +271,7 @@ uint32_t RsFeedReaderSerialiser::sizeMsg(RsFeedReaderMsg *item) s += GetTlvStringSize(item->link); s += GetTlvStringSize(item->author); s += GetTlvStringSize(item->description); + s += GetTlvStringSize(item->descriptionTransformed); s += sizeof(time_t); /* pubDate */ s += sizeof(uint32_t); /* flag */ @@ -270,13 +297,14 @@ bool RsFeedReaderSerialiser::serialiseMsg(RsFeedReaderMsg *item, void *data, uin offset += 8; /* add values */ - ok &= setRawUInt16(data, tlvsize, &offset, 0); /* version */ + ok &= setRawUInt16(data, tlvsize, &offset, 1); /* version */ ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_GENID, item->msgId); ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->feedId); ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_NAME, item->title); ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_LINK, item->link); ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->author); ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_COMMENT, item->description); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_COMMENT, item->descriptionTransformed); ok &= setRawUInt32(data, tlvsize, &offset, item->pubDate); ok &= setRawUInt32(data, tlvsize, &offset, item->flag); @@ -328,6 +356,9 @@ RsFeedReaderMsg *RsFeedReaderSerialiser::deserialiseMsg(void *data, uint32_t *pk ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_LINK, item->link); ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->author); ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_COMMENT, item->description); + if (version >= 1) { + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_COMMENT, item->descriptionTransformed); + } ok &= getRawUInt32(data, rssize, &offset, (uint32_t*) &(item->pubDate)); ok &= getRawUInt32(data, rssize, &offset, &(item->flag)); diff --git a/plugins/FeedReader/services/rsFeedReaderItems.h b/plugins/FeedReader/services/rsFeedReaderItems.h index f0d087d8b..85174d37f 100644 --- a/plugins/FeedReader/services/rsFeedReaderItems.h +++ b/plugins/FeedReader/services/rsFeedReaderItems.h @@ -65,26 +65,28 @@ public: virtual void clear(); virtual std::ostream& print(std::ostream &out, uint16_t indent = 0); - std::string feedId; - std::string parentId; - std::string name; - std::string url; - std::string user; - std::string password; - std::string proxyAddress; - uint16_t proxyPort; - uint32_t updateInterval; - time_t lastUpdate; - uint32_t flag; // RS_FEED_FLAG_... - std::string forumId; - uint32_t storageTime; - std::string description; - std::string icon; - RsFeedReaderErrorState errorState; - std::string errorString; + std::string feedId; + std::string parentId; + std::string name; + std::string url; + std::string user; + std::string password; + std::string proxyAddress; + uint16_t proxyPort; + uint32_t updateInterval; + time_t lastUpdate; + uint32_t flag; // RS_FEED_FLAG_... + std::string forumId; + uint32_t storageTime; + std::string description; + std::string icon; + RsFeedReaderErrorState errorState; + std::string errorString; - RsTlvStringSet xpathsToUse; - RsTlvStringSet xpathsToRemove; + RsFeedTransformationType transformationType; + RsTlvStringSet xpathsToUse; + RsTlvStringSet xpathsToRemove; + std::string xslt; /* Not Serialised */ bool preview; @@ -113,6 +115,7 @@ public: std::string link; std::string author; std::string description; + std::string descriptionTransformed; time_t pubDate; uint32_t flag; // RS_FEEDMSG_FLAG_... }; diff --git a/plugins/FeedReader/util/HTMLWrapper.cpp b/plugins/FeedReader/util/HTMLWrapper.cpp index 12c7c727a..7d02d826c 100644 --- a/plugins/FeedReader/util/HTMLWrapper.cpp +++ b/plugins/FeedReader/util/HTMLWrapper.cpp @@ -32,7 +32,10 @@ bool HTMLWrapper::readHTML(const char *html, const char *url) { cleanup(); - mDocument = htmlReadMemory(html, strlen(html), url, "", HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING | HTML_PARSE_COMPACT | HTML_PARSE_NONET | HTML_PARSE_NOBLANKS); + handleError(true, mLastErrorString); + mDocument = htmlReadMemory(html, strlen(html), url, "", /*HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING | */HTML_PARSE_COMPACT | HTML_PARSE_NONET | HTML_PARSE_NOBLANKS); + handleError(false, mLastErrorString); + if (mDocument) { return true; } @@ -48,7 +51,10 @@ bool HTMLWrapper::saveHTML(std::string &html) xmlChar *newHtml = NULL; int newHtmlSize = 0; + handleError(true, mLastErrorString); htmlDocDumpMemoryFormat(mDocument, &newHtml, &newHtmlSize, 0); + handleError(false, mLastErrorString); + if (newHtml) { convertToString(newHtml, html); xmlFree(newHtml); diff --git a/plugins/FeedReader/util/XMLWrapper.cpp b/plugins/FeedReader/util/XMLWrapper.cpp index 2dd905fcc..a625a104b 100644 --- a/plugins/FeedReader/util/XMLWrapper.cpp +++ b/plugins/FeedReader/util/XMLWrapper.cpp @@ -25,6 +25,14 @@ #include "XMLWrapper.h" #include "XPathWrapper.h" +#include +#include +#include +#include + +static RsMutex xmlMtx("XMLWrapper"); +static std::string xmlErrorString; + XMLWrapper::XMLWrapper() { mDocument = NULL; @@ -42,6 +50,35 @@ XMLWrapper::~XMLWrapper() xmlCharEncCloseFunc(mCharEncodingHandler); } +static void xmlErrorHandler(void */*context*/, const char *msg, ...) +{ + va_list vl; + + va_start(vl, msg); + rs_sprintf_append_args(xmlErrorString, msg, vl); + va_end(vl); +} + +void XMLWrapper::handleError(bool init, std::string &errorString) +{ + if (init) { + xmlMtx.lock(); + xmlErrorString.clear(); + errorString.clear(); + + xsltSetGenericErrorFunc(this, xmlErrorHandler); + xmlSetGenericErrorFunc(this, xmlErrorHandler); + } else { + xsltSetGenericErrorFunc(NULL, NULL); + xmlSetGenericErrorFunc(NULL, NULL); + + errorString = xmlErrorString; + xmlErrorString.clear(); + + xmlMtx.unlock(); + } +} + void XMLWrapper::trimString(std::string &string) { /* trim left */ @@ -59,7 +96,6 @@ void XMLWrapper::trimString(std::string &string) } } - XMLWrapper &XMLWrapper::operator=(const XMLWrapper &xml) { cleanup(); @@ -80,6 +116,13 @@ void XMLWrapper::cleanup() } } +void XMLWrapper::attach(xmlDocPtr document) +{ + cleanup(); + + mDocument = document; +} + bool XMLWrapper::convertToString(const xmlChar *xmlText, std::string &text) { bool result = false; @@ -138,7 +181,10 @@ bool XMLWrapper::readXML(const char *xml) { cleanup(); - mDocument = xmlReadDoc(BAD_CAST xml, "", NULL, XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_COMPACT | XML_PARSE_NOENT | XML_PARSE_NOCDATA); + handleError(true, mLastErrorString); + mDocument = xmlReadDoc(BAD_CAST xml, "", NULL, /*XML_PARSE_NOERROR | XML_PARSE_NOWARNING | */XML_PARSE_COMPACT | XML_PARSE_NOENT | XML_PARSE_NOCDATA); + handleError(false, mLastErrorString); + if (mDocument) { return true; } @@ -358,3 +404,23 @@ XPathWrapper *XMLWrapper::createXPath() return NULL; } + +bool XMLWrapper::transform(const XMLWrapper &style, XMLWrapper &result) +{ + handleError(true, mLastErrorString); + + xmlDocPtr resultDoc = NULL; + + xsltStylesheetPtr stylesheet = xsltParseStylesheetDoc(style.getDocument()); + if (stylesheet) { + resultDoc = xsltApplyStylesheet(stylesheet, getDocument(), NULL); + stylesheet->doc = NULL; // xsltFreeStylesheet is freeing doc + xsltFreeStylesheet(stylesheet); + } + + result.attach(resultDoc); + + handleError(false, mLastErrorString); + + return resultDoc ? true : false; +} diff --git a/plugins/FeedReader/util/XMLWrapper.h b/plugins/FeedReader/util/XMLWrapper.h index a815f7a95..ada98274f 100644 --- a/plugins/FeedReader/util/XMLWrapper.h +++ b/plugins/FeedReader/util/XMLWrapper.h @@ -39,12 +39,16 @@ public: XMLWrapper &operator=(const XMLWrapper &xml); void cleanup(); + std::string lastError() { return mLastErrorString; } bool readXML(const char *xml); xmlDocPtr getDocument() const; xmlNodePtr getRootElement() const; + bool convertToString(const xmlChar *xmlText, std::string &text); + bool convertFromString(const char *text, xmlChar *&xmlText); + std::string nodeName(xmlNodePtr node); std::string attrName(xmlAttrPtr attr); @@ -62,12 +66,16 @@ public: XPathWrapper *createXPath(); - bool convertToString(const xmlChar *xmlText, std::string &text); - bool convertFromString(const char *text, xmlChar *&xmlText); + bool transform(const XMLWrapper &style, XMLWrapper &result); + +protected: + void attach(xmlDocPtr document); + void handleError(bool init, std::string &errorString); protected: xmlDocPtr mDocument; xmlCharEncodingHandlerPtr mCharEncodingHandler; + std::string mLastErrorString; }; -#endif +#endif diff --git a/retroshare-gui/src/gui/common/RSPlainTextEdit.cpp b/retroshare-gui/src/gui/common/RSPlainTextEdit.cpp new file mode 100644 index 000000000..611cae2bb --- /dev/null +++ b/retroshare-gui/src/gui/common/RSPlainTextEdit.cpp @@ -0,0 +1,54 @@ +/**************************************************************** + * + * RetroShare is distributed under the following license: + * + * Copyright (C) 2013, RetroShare Team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#include + +#include "RSPlainTextEdit.h" + +RSPlainTextEdit::RSPlainTextEdit(QWidget *parent) + : QPlainTextEdit(parent) +{ +} + +void RSPlainTextEdit::setPlaceholderText(const QString &text) +{ + mPlaceholderText = text; + viewport()->repaint(); +} + +void RSPlainTextEdit::paintEvent(QPaintEvent *event) +{ + QPlainTextEdit::paintEvent(event); + + if (mPlaceholderText.isEmpty() == false && toPlainText().isEmpty()) { + QWidget *vieportWidget = viewport(); + QPainter painter(vieportWidget); + + QPen pen = painter.pen(); + QColor color = pen.color(); + color.setAlpha(128); + pen.setColor(color); + painter.setPen(pen); + + painter.drawText(QRect(QPoint(), vieportWidget->size()), Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextWordWrap, mPlaceholderText); + } +} diff --git a/retroshare-gui/src/gui/common/RSPlainTextEdit.h b/retroshare-gui/src/gui/common/RSPlainTextEdit.h new file mode 100644 index 000000000..8ea9fdc3f --- /dev/null +++ b/retroshare-gui/src/gui/common/RSPlainTextEdit.h @@ -0,0 +1,44 @@ +/**************************************************************** + * + * RetroShare is distributed under the following license: + * + * Copyright (C) 2013, RetroShare Team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#ifndef RSPLAINTEXTEDIT_H +#define RSPLAINTEXTEDIT_H + +#include + +class RSPlainTextEdit : public QPlainTextEdit +{ + Q_OBJECT + +public: + RSPlainTextEdit(QWidget *parent = 0); + + void setPlaceholderText(const QString &text); + +protected: + void paintEvent(QPaintEvent *event); + +private: + QString mPlaceholderText; +}; + +#endif // RSPLAINTEXTEDIT_H diff --git a/retroshare-gui/src/retroshare-gui.pro b/retroshare-gui/src/retroshare-gui.pro index 34b19cc79..75ba3a0a5 100644 --- a/retroshare-gui/src/retroshare-gui.pro +++ b/retroshare-gui/src/retroshare-gui.pro @@ -397,6 +397,7 @@ HEADERS += rshare.h \ gui/common/GroupDefs.h \ gui/common/Emoticons.h \ gui/common/RSListWidgetItem.h \ + gui/common/RSPlainTextEdit.h \ gui/common/RSTreeWidget.h \ gui/common/RSTreeWidgetItem.h \ gui/common/RSTabWidget.h \ @@ -665,6 +666,7 @@ SOURCES += main.cpp \ gui/common/GroupDefs.cpp \ gui/common/Emoticons.cpp \ gui/common/RSListWidgetItem.cpp \ + gui/common/RSPlainTextEdit.cpp \ gui/common/RSTreeWidget.cpp \ gui/common/RSTreeWidgetItem.cpp \ gui/common/RSTabWidget.cpp \