From 58f8b5371c1a92170466b38418170f84b62e2361 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Fri, 6 Jan 2012 19:25:04 +0100 Subject: [PATCH] Expressions: support for visitor pattern, expresion->OGCFilter now a visitor --- python/core/qgsexpression.sip | 231 ++++++++++++++++++++++++- src/core/qgsexpression.cpp | 100 +---------- src/core/qgsexpression.h | 60 ++++++- src/gui/qgssearchquerybuilder.cpp | 2 +- src/providers/wfs/CMakeLists.txt | 1 + src/providers/wfs/qgswfsconnection.cpp | 3 +- src/providers/wfs/qgswfsutils.cpp | 111 ++++++++++++ src/providers/wfs/qgswfsutils.h | 16 ++ 8 files changed, 413 insertions(+), 111 deletions(-) create mode 100644 src/providers/wfs/qgswfsutils.cpp create mode 100644 src/providers/wfs/qgswfsutils.h diff --git a/python/core/qgsexpression.sip b/python/core/qgsexpression.sip index 63539292c290..48ac49e16e37 100644 --- a/python/core/qgsexpression.sip +++ b/python/core/qgsexpression.sip @@ -51,14 +51,231 @@ public: //! (used by internal functions) QgsDistanceArea* geomCalculator(); - // + // - // tells whether the identifier is a name of existing function - static bool isFunctionName( QString name ); + enum UnaryOperator + { + uoNot, + uoMinus, + }; + enum BinaryOperator + { + // logical + boOr, + boAnd, - // return index of the function in BuiltinFunctions array - static int functionIndex( QString name ); + // comparison + boEQ, // = + boNE, // <> + boLE, // <= + boGE, // >= + boLT, // < + boGT, // > + boRegexp, + boLike, + boILike, + boIs, + boIsNot, + + // math + boPlus, + boMinus, + boMul, + boDiv, + boMod, + boPow, + + // strings + boConcat, + }; + + + struct FunctionDef /NoDefaultCtors/ + { + //FunctionDef( QString fnname, int params, FcnEval fcn, QString group, QString helpText = QString(), bool usesGeometry = false ); + /** The name of the function. */ + QString mName; + /** The number of parameters this function takes. */ + int mParams; + /** Pointer to fucntion. */ + //FcnEval mFcn; + /** Does this function use a geometry object. */ + bool mUsesGeometry; + /** The group the function belongs to. */ + QString mGroup; + /** The help text for the function. */ + QString mHelpText; + }; + + static const QList &BuiltinFunctions(); + + // tells whether the identifier is a name of existing function + static bool isFunctionName( QString name ); + + // return index of the function in BuiltinFunctions array + static int functionIndex( QString name ); + + /** Returns the number of functions defined in the parser + * @return The number of function defined in the parser. + */ + static int functionCount(); + + //! return quoted column reference (in double quotes) + static QString quotedColumnRef( QString name ); + + ////// + + class Node + { + public: + virtual ~Node(); + // abstract virtual eval function + // errors are reported to the parent + virtual QVariant eval( QgsExpression* parent, QgsFeature* f ) = 0; + + // abstract virtual preparation function + // errors are reported to the parent + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ) = 0; + + virtual QString dump() const = 0; + + virtual QStringList referencedColumns() const = 0; + virtual bool needsGeometry() const = 0; + + // support for visitor pattern + virtual void accept( QgsExpression::Visitor& v ) = 0; + }; + + class NodeList + { + public: + NodeList(); + ~NodeList(); + void append( QgsExpression::Node* node ); + int count(); + QList list(); + + virtual QString dump() const; + }; + + class NodeUnaryOperator : QgsExpression::Node + { + public: + NodeUnaryOperator( QgsExpression::UnaryOperator op, QgsExpression::Node* operand ); + ~NodeUnaryOperator(); + + QgsExpression::UnaryOperator op(); + QgsExpression::Node* operand(); + + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); + virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); + virtual QString dump() const; + virtual QStringList referencedColumns() const; + virtual bool needsGeometry() const; + virtual void accept( QgsExpression::Visitor& v ); + }; + + class NodeBinaryOperator : QgsExpression::Node + { + public: + NodeBinaryOperator( QgsExpression::BinaryOperator op, QgsExpression::Node* opLeft, QgsExpression::Node* opRight ); + ~NodeBinaryOperator(); + + QgsExpression::BinaryOperator op(); + QgsExpression::Node* opLeft(); + QgsExpression::Node* opRight(); + + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); + virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); + virtual QString dump() const; + virtual QStringList referencedColumns() const; + virtual bool needsGeometry() const; + virtual void accept( QgsExpression::Visitor& v ); + }; + + class NodeInOperator : QgsExpression::Node + { + public: + NodeInOperator( QgsExpression::Node* node, QgsExpression::NodeList* list, bool notin = false ); + ~NodeInOperator(); + + QgsExpression::Node* node(); + bool isNotIn(); + QgsExpression::NodeList* list(); + + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); + virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); + virtual QString dump() const; + virtual QStringList referencedColumns() const; + virtual bool needsGeometry() const; + virtual void accept( QgsExpression::Visitor& v ); + }; + + class NodeFunction : QgsExpression::Node + { + public: + NodeFunction( int fnIndex, QgsExpression::NodeList* args ); + //NodeFunction( QString name, QgsExpression::NodeList* args ); + ~NodeFunction(); + + int fnIndex(); + QgsExpression::NodeList* args(); + + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); + virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); + virtual QString dump() const; + virtual QStringList referencedColumns() const; + virtual bool needsGeometry() const; + virtual void accept( QgsExpression::Visitor& v ); + }; + + class NodeLiteral : QgsExpression::Node + { + public: + NodeLiteral( QVariant value ); + + QVariant value(); + + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); + virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); + virtual QString dump() const; + virtual QStringList referencedColumns() const; + virtual bool needsGeometry() const; + virtual void accept( QgsExpression::Visitor& v ); + }; + + class NodeColumnRef : QgsExpression::Node + { + public: + NodeColumnRef( QString name ); + + QString name(); + + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); + virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); + virtual QString dump() const; + virtual QStringList referencedColumns() const; + virtual bool needsGeometry() const; + virtual void accept( QgsExpression::Visitor& v ); + }; + + ////// + + /** support for visitor pattern - algorithms dealing with the expressions + may be implemented without modifying the Node classes */ + class Visitor + { + public: + virtual ~Visitor(); + virtual void visit( QgsExpression::NodeUnaryOperator* n ) = 0; + virtual void visit( QgsExpression::NodeBinaryOperator* n ) = 0; + virtual void visit( QgsExpression::NodeInOperator* n ) = 0; + virtual void visit( QgsExpression::NodeFunction* n ) = 0; + virtual void visit( QgsExpression::NodeLiteral* n ) = 0; + virtual void visit( QgsExpression::NodeColumnRef* n ) = 0; + }; + + /** entry function for the visitor pattern */ + void acceptVisitor( QgsExpression::Visitor& v ); - //! return quoted column reference (in double quotes) - static QString quotedColumnRef( QString name ); }; diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp index e0e075faed2b..05dab18069d1 100644 --- a/src/core/qgsexpression.cpp +++ b/src/core/qgsexpression.cpp @@ -541,6 +541,12 @@ QString QgsExpression::dump() const return mRootNode->dump(); } +void QgsExpression::acceptVisitor( QgsExpression::Visitor& v ) +{ + if ( mRootNode ) + mRootNode->accept( v ); +} + /////////////////////////////////////////////// // nodes @@ -969,97 +975,3 @@ QString QgsExpression::NodeColumnRef::dump() const { return mName; } - -bool QgsExpression::toOGCFilter( QDomDocument& doc ) const -{ - if ( !mRootNode ) - { - return false; - } - - doc.clear(); - QDomElement filterElem = doc.createElement( "Filter" ); - doc.appendChild( filterElem ); - return mRootNode->toOGCFilter( doc, filterElem ); -} - -bool QgsExpression::NodeBinaryOperator::toOGCFilter( QDomDocument& doc, QDomElement& parent ) const -{ - QDomElement opElem; - switch ( mOp ) - { - case boEQ: - opElem = doc.createElement( "PropertyIsEqualTo" ); - break; - case boNE: - opElem = doc.createElement( "PropertyIsNotEqualTo" ); - break; - case boLE: - opElem = doc.createElement( "PropertyIsLessThanOrEqualTo" ); - break; - case boGE: - opElem = doc.createElement( "PropertyIsLessThanOrEqualTo" ); - break; - case boLT: - opElem = doc.createElement( "PropertyIsLessThan" ); - break; - case boGT: - opElem = doc.createElement( "PropertyIsGreaterThan" ); - break; - case boOr: - opElem = doc.createElement( "Or" ); - break; - case boAnd: - opElem = doc.createElement( "And" ); - break; - default: - return false; - } - - if ( mOpLeft ) - { - mOpLeft->toOGCFilter( doc, opElem ); - } - if ( mOpRight ) - { - mOpRight->toOGCFilter( doc, opElem ); - } - - parent.appendChild( opElem ); - return true; -} - -bool QgsExpression::NodeLiteral::toOGCFilter( QDomDocument& doc, QDomElement& parent ) const -{ - QDomElement literalElem = doc.createElement( "Literal" ); - QDomText literalText = doc.createTextNode( mValue.toString() ); - literalElem.appendChild( literalText ); - parent.appendChild( literalElem ); - return true; -} - -bool QgsExpression::NodeColumnRef::toOGCFilter( QDomDocument& doc, QDomElement& parent ) const -{ - QDomElement propertyElem = doc.createElement( "PropertyName" ); - QDomText propertyText = doc.createTextNode( mName ); - propertyElem.appendChild( propertyText ); - parent.appendChild( propertyElem ); - return true; -} - -bool QgsExpression::NodeUnaryOperator::toOGCFilter( QDomDocument& doc, QDomElement& parent ) const -{ - if ( mOp == uoNot ) - { - QDomElement notElem = doc.createElement( "Not" ); - if ( mOperand ) - { - if ( mOperand->toOGCFilter( doc, notElem ) ) - { - parent.appendChild( notElem ); - return true; - } - } - } - return false; -} diff --git a/src/core/qgsexpression.h b/src/core/qgsexpression.h index 1843b3b90d54..2295c7803125 100644 --- a/src/core/qgsexpression.h +++ b/src/core/qgsexpression.h @@ -122,9 +122,6 @@ class CORE_EXPORT QgsExpression //! Return the parsed expression as a string - useful for debugging QString dump() const; - //! Creates ogc filter xml document. Supports minimum standard filter according to the OGC filter specs (=,!=,<,>,<=,>=,AND,OR,NOT) - //! @return true in case of success. False if string contains something that goes beyond the minimum standard filter - bool toOGCFilter( QDomDocument& doc ) const; //! Return calculator used for distance and area calculations //! (used by internal functions) @@ -211,6 +208,8 @@ class CORE_EXPORT QgsExpression ////// + class Visitor; // visitor interface is defined below + class Node { public: @@ -227,7 +226,9 @@ class CORE_EXPORT QgsExpression virtual QStringList referencedColumns() const = 0; virtual bool needsGeometry() const = 0; - virtual bool toOGCFilter( QDomDocument& doc, QDomElement& parent ) const { Q_UNUSED( doc ); Q_UNUSED( parent ); return false; } + + // support for visitor pattern + virtual void accept( Visitor& v ) = 0; }; class NodeList @@ -250,12 +251,16 @@ class CORE_EXPORT QgsExpression NodeUnaryOperator( UnaryOperator op, Node* operand ) : mOp( op ), mOperand( operand ) {} ~NodeUnaryOperator() { delete mOperand; } + UnaryOperator op() { return mOp; } + Node* operand() { return mOperand; } + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; virtual QStringList referencedColumns() const { return mOperand->referencedColumns(); } virtual bool needsGeometry() const { return mOperand->needsGeometry(); } - virtual bool toOGCFilter( QDomDocument& doc, QDomElement& parent ) const; + virtual void accept( Visitor& v ) { v.visit( this ); } + protected: UnaryOperator mOp; Node* mOperand; @@ -267,12 +272,16 @@ class CORE_EXPORT QgsExpression NodeBinaryOperator( BinaryOperator op, Node* opLeft, Node* opRight ) : mOp( op ), mOpLeft( opLeft ), mOpRight( opRight ) {} ~NodeBinaryOperator() { delete mOpLeft; delete mOpRight; } + BinaryOperator op() { return mOp; } + Node* opLeft() { return mOpLeft; } + Node* opRight() { return mOpRight; } + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; virtual QStringList referencedColumns() const { return mOpLeft->referencedColumns() + mOpRight->referencedColumns(); } virtual bool needsGeometry() const { return mOpLeft->needsGeometry() || mOpRight->needsGeometry(); } - virtual bool toOGCFilter( QDomDocument& doc, QDomElement& parent ) const; + virtual void accept( Visitor& v ) { v.visit( this ); } protected: bool compare( double diff ); @@ -290,11 +299,17 @@ class CORE_EXPORT QgsExpression NodeInOperator( Node* node, NodeList* list, bool notin = false ) : mNode( node ), mList( list ), mNotIn( notin ) {} ~NodeInOperator() { delete mNode; delete mList; } + Node* node() { return mNode; } + bool isNotIn() { return mNotIn; } + NodeList* list() { return mList; } + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; virtual QStringList referencedColumns() const { QStringList lst( mNode->referencedColumns() ); foreach( Node* n, mList->list() ) lst.append( n->referencedColumns() ); return lst; } virtual bool needsGeometry() const { bool needs = false; foreach( Node* n, mList->list() ) needs |= n->needsGeometry(); return needs; } + virtual void accept( Visitor& v ) { v.visit( this ); } + protected: Node* mNode; NodeList* mList; @@ -308,11 +323,16 @@ class CORE_EXPORT QgsExpression //NodeFunction( QString name, NodeList* args ) : mName(name), mArgs(args) {} ~NodeFunction() { delete mArgs; } + int fnIndex() { return mFnIndex; } + NodeList* args() { return mArgs; } + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; virtual QStringList referencedColumns() const { QStringList lst; if ( !mArgs ) return lst; foreach( Node* n, mArgs->list() ) lst.append( n->referencedColumns() ); return lst; } virtual bool needsGeometry() const { bool needs = BuiltinFunctions()[mFnIndex].mUsesGeometry; if ( mArgs ) { foreach( Node* n, mArgs->list() ) needs |= n->needsGeometry(); } return needs; } + virtual void accept( Visitor& v ) { v.visit( this ); } + protected: //QString mName; int mFnIndex; @@ -324,12 +344,15 @@ class CORE_EXPORT QgsExpression public: NodeLiteral( QVariant value ) : mValue( value ) {} + QVariant value() { return mValue; } + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; virtual QStringList referencedColumns() const { return QStringList(); } virtual bool needsGeometry() const { return false; } - virtual bool toOGCFilter( QDomDocument& doc, QDomElement& parent ) const; + virtual void accept( Visitor& v ) { v.visit( this ); } + protected: QVariant mValue; }; @@ -339,17 +362,38 @@ class CORE_EXPORT QgsExpression public: NodeColumnRef( QString name ) : mName( name ), mIndex( -1 ) {} + QString name() { return mName; } + virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; virtual QStringList referencedColumns() const { return QStringList( mName ); } virtual bool needsGeometry() const { return false; } - virtual bool toOGCFilter( QDomDocument& doc, QDomElement& parent ) const; + virtual void accept( Visitor& v ) { v.visit( this ); } + protected: QString mName; int mIndex; }; + ////// + + /** support for visitor pattern - algorithms dealing with the expressions + may be implemented without modifying the Node classes */ + class Visitor + { + public: + virtual ~Visitor() {} + virtual void visit( NodeUnaryOperator* n ) = 0; + virtual void visit( NodeBinaryOperator* n ) = 0; + virtual void visit( NodeInOperator* n ) = 0; + virtual void visit( NodeFunction* n ) = 0; + virtual void visit( NodeLiteral* n ) = 0; + virtual void visit( NodeColumnRef* n ) = 0; + }; + + /** entry function for the visitor pattern */ + void acceptVisitor( Visitor& v ); protected: diff --git a/src/gui/qgssearchquerybuilder.cpp b/src/gui/qgssearchquerybuilder.cpp index 5ee68a38a307..a8a403c00e12 100644 --- a/src/gui/qgssearchquerybuilder.cpp +++ b/src/gui/qgssearchquerybuilder.cpp @@ -442,7 +442,7 @@ void QgsSearchQueryBuilder::loadQuery() QString newQueryText = query; #if 0 - // TODO: QgsExpression does not support overwriting of existing expressions + // TODO: implement with visitor pattern in QgsExpression QStringList attributes = searchTree->referencedColumns(); QMap< QString, QString> attributesToReplace; diff --git a/src/providers/wfs/CMakeLists.txt b/src/providers/wfs/CMakeLists.txt index 06227f7de811..1119228752fc 100644 --- a/src/providers/wfs/CMakeLists.txt +++ b/src/providers/wfs/CMakeLists.txt @@ -8,6 +8,7 @@ SET(WFS_SRCS qgswfsdataitems.cpp qgswfsdata.cpp qgswfssourceselect.cpp + qgswfsutils.cpp ) SET (WFS_MOC_HDRS diff --git a/src/providers/wfs/qgswfsconnection.cpp b/src/providers/wfs/qgswfsconnection.cpp index d1b708725dc2..1f2a124722d4 100644 --- a/src/providers/wfs/qgswfsconnection.cpp +++ b/src/providers/wfs/qgswfsconnection.cpp @@ -2,6 +2,7 @@ #include "qgsexpression.h" #include "qgslogger.h" #include "qgsnetworkaccessmanager.h" +#include "qgswfsutils.h" #include #include #include @@ -69,7 +70,7 @@ QString QgsWFSConnection::uriGetFeature( QString typeName, QString crsString, QS { //if not, if must be a QGIS expression QgsExpression filterExpression( filter ); - if ( !filterExpression.toOGCFilter( filterDoc ) ) + if ( !QgsWFSUtils::expressionToOGCFilter( filterExpression, filterDoc ) ) { //error } diff --git a/src/providers/wfs/qgswfsutils.cpp b/src/providers/wfs/qgswfsutils.cpp new file mode 100644 index 000000000000..f3e8da47e360 --- /dev/null +++ b/src/providers/wfs/qgswfsutils.cpp @@ -0,0 +1,111 @@ +#include "qgswfsutils.h" + +#include "qgsexpression.h" + + +class QgsExpressionOGCVisitor : public QgsExpression::Visitor +{ + public: + QgsExpressionOGCVisitor( QDomDocument& doc, QDomElement& parent ) + : mDoc( doc ), mParent( parent ), mResult( false ) + {} + + bool result() { return mResult; } + + void visit( QgsExpression::NodeUnaryOperator* n ) + { + mResult = false; + + if ( n->op() == QgsExpression::uoNot && n->operand() ) + { + QDomElement notElemParent = mParent; + QDomElement notElem = mDoc.createElement( "Not" ); + + mParent = notElem; + n->operand()->accept( *this ); + if ( !mResult ) + return; // visit failed + + mParent = notElemParent; + mParent.appendChild( notElem ); + mResult = true; + } + } + + void visit( QgsExpression::NodeBinaryOperator* n ) + { + QString opName; + switch ( n->op() ) + { + case QgsExpression::boEQ: opName = "PropertyIsEqualTo"; break; + case QgsExpression::boNE: opName = "PropertyIsNotEqualTo"; break; + case QgsExpression::boLE: opName = "PropertyIsLessThanOrEqualTo"; break; + case QgsExpression::boGE: opName = "PropertyIsLessThanOrEqualTo"; break; + case QgsExpression::boLT: opName = "PropertyIsLessThan"; break; + case QgsExpression::boGT: opName = "PropertyIsGreaterThan"; break; + case QgsExpression::boOr: opName = "Or"; break; + case QgsExpression::boAnd: opName = "And"; break; + default: break; + } + + mResult = false; + if ( opName.isEmpty() || !n->opLeft() || !n->opRight() ) + return; // unknown operation -> fail + + QDomElement opElem = mDoc.createElement( opName ); + QDomElement opElemParent = mParent; + + mParent = opElem; + n->opLeft()->accept( *this ); + if ( !mResult ) + return; // visit failed + + mParent = opElem; + n->opRight()->accept( *this ); + if ( !mResult ) + return; // visit failed + + mParent = opElemParent; + mParent.appendChild( opElem ); + mResult = true; + } + + void visit( QgsExpression::NodeInOperator* ) { mResult = false; } + void visit( QgsExpression::NodeFunction* ) { mResult = false; } + + void visit( QgsExpression::NodeLiteral* n ) + { + QDomElement literalElem = mDoc.createElement( "Literal" ); + QDomText literalText = mDoc.createTextNode( n->value().toString() ); + literalElem.appendChild( literalText ); + mParent.appendChild( literalElem ); + mResult = true; + } + + void visit( QgsExpression::NodeColumnRef* n ) + { + QDomElement propertyElem = mDoc.createElement( "PropertyName" ); + QDomText propertyText = mDoc.createTextNode( n->name() ); + propertyElem.appendChild( propertyText ); + mParent.appendChild( propertyElem ); + mResult = true; + } + + protected: + QDomDocument mDoc; + QDomElement mParent; + bool mResult; + +}; + + +bool QgsWFSUtils::expressionToOGCFilter( QgsExpression& exp, QDomDocument& doc ) +{ + doc.clear(); + QDomElement filterElem = doc.createElement( "Filter" ); + doc.appendChild( filterElem ); + + QgsExpressionOGCVisitor v( doc, filterElem ); + exp.acceptVisitor( v ); + return v.result(); +} diff --git a/src/providers/wfs/qgswfsutils.h b/src/providers/wfs/qgswfsutils.h new file mode 100644 index 000000000000..cbf42fa30050 --- /dev/null +++ b/src/providers/wfs/qgswfsutils.h @@ -0,0 +1,16 @@ +#ifndef QGSWFSUTILS_H +#define QGSWFSUTILS_H + +class QgsExpression; +class QDomDocument; + +class QgsWFSUtils +{ + public: + + //! Creates ogc filter xml document. Supports minimum standard filter according to the OGC filter specs (=,!=,<,>,<=,>=,AND,OR,NOT) + //! @return true in case of success. False if string contains something that goes beyond the minimum standard filter + static bool expressionToOGCFilter( QgsExpression& exp, QDomDocument& doc ); +}; + +#endif // QGSWFSUTILS_H