Skip to content

Commit

Permalink
[BUGFIX] Support OGC PropertyIsLike attributs
Browse files Browse the repository at this point in the history
The OGC PropertyIsLike element can have 4 attributs:
* matchCase to specify LIKE or ILIKE
* wildCard to specify a wildcard char symbol
* signleChar to specify a single char symbol
* escape to specify an escape char symbol
  • Loading branch information
rldhont committed Oct 6, 2016
1 parent 584ce22 commit 72f93c9
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 10 deletions.
85 changes: 77 additions & 8 deletions src/core/qgsogcutils.cpp
Expand Up @@ -1666,6 +1666,10 @@ static int binaryOperatorFromTagName( const QString& tagName )

static QString binaryOperatorToTagName( QgsExpression::BinaryOperator op )
{
if ( op == QgsExpression::boILike )
{
return "PropertyIsLike";
}
return binaryOperatorsTagNamesMap().key( op, QString() );
}

Expand Down Expand Up @@ -1752,6 +1756,11 @@ QgsExpression::NodeBinaryOperator* QgsOgcUtils::nodeBinaryOperatorFromOgcFilter(
return nullptr;
}

if ( op == QgsExpression::boLike && element.hasAttribute( "matchCase" ) && element.attribute( "matchCase" ) == "false" )
{
op = QgsExpression::boILike;
}

QDomElement operandElem = element.firstChildElement();
QgsExpression::Node *expr = nodeFromOgcFilter( operandElem, errorMessage ), *leftOp = expr;
if ( !expr )
Expand All @@ -1772,6 +1781,64 @@ QgsExpression::NodeBinaryOperator* QgsOgcUtils::nodeBinaryOperatorFromOgcFilter(
return nullptr;
}

if ( op == QgsExpression::boLike || op == QgsExpression::boILike )
{
QString wildCard;
if ( element.hasAttribute( "wildCard" ) )
{
wildCard = element.attribute( "wildCard" );
}
QString singleChar;
if ( element.hasAttribute( "singleChar" ) )
{
singleChar = element.attribute( "singleChar" );
}
QString escape = "\\";
if ( element.hasAttribute( "escape" ) )
{
escape = element.attribute( "escape" );
}
// replace
QString oprValue = static_cast<const QgsExpression::NodeLiteral*>( opRight )->value().toString();
if ( !wildCard.isEmpty() && wildCard != "%" )
{
oprValue.replace( '%', "\\%" );
if ( oprValue.startsWith( wildCard ) )
{
oprValue.replace( 0, 1, "%" );
}
QRegExp rx( "[^" + QRegExp::escape( escape ) + "](" + QRegExp::escape( wildCard ) + ")" );
int pos = 0;
while (( pos = rx.indexIn( oprValue, pos ) ) != -1 )
{
oprValue.replace( pos + 1, 1, "%" );
pos += 1;
}
oprValue.replace( escape + wildCard, wildCard );
}
if ( !singleChar.isEmpty() && singleChar != "_" )
{
oprValue.replace( '_', "\\_" );
if ( oprValue.startsWith( singleChar ) )
{
oprValue.replace( 0, 1, "_" );
}
QRegExp rx( "[^" + QRegExp::escape( escape ) + "](" + QRegExp::escape( singleChar ) + ")" );
int pos = 0;
while (( pos = rx.indexIn( oprValue, pos ) ) != -1 )
{
oprValue.replace( pos + 1, 1, "_" );
pos += 1;
}
oprValue.replace( escape + singleChar, singleChar );
}
if ( !escape.isEmpty() && escape != "\\" )
{
oprValue.replace( escape + escape, escape );
}
opRight = new QgsExpression::NodeLiteral( oprValue );
}

expr = new QgsExpression::NodeBinaryOperator( static_cast< QgsExpression::BinaryOperator >( op ), expr, opRight );
}

Expand Down Expand Up @@ -2289,13 +2356,13 @@ QDomElement QgsOgcUtilsExprToFilter::expressionBinaryOperatorToOgcFilter( const
if ( op == QgsExpression::boILike )
boElem.setAttribute( "matchCase", "false" );

// setup wildcards to <ogc:PropertyIsLike>
// setup wildCards to <ogc:PropertyIsLike>
boElem.setAttribute( "wildCard", "%" );
boElem.setAttribute( "singleChar", "?" );
boElem.setAttribute( "singleChar", "_" );
if ( mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
boElem.setAttribute( "escape", "!" );
boElem.setAttribute( "escape", "\\" );
else
boElem.setAttribute( "escapeChar", "!" );
boElem.setAttribute( "escapeChar", "\\" );
}

boElem.appendChild( leftElem );
Expand Down Expand Up @@ -2716,6 +2783,8 @@ QDomElement QgsOgcUtilsSQLStatementToFilter::toOgcFilter( const QgsSQLStatement:
opText = "PropertyIsGreaterThan";
else if ( op == QgsSQLStatement::boLike )
opText = "PropertyIsLike";
else if ( op == QgsSQLStatement::boILike )
opText = "PropertyIsLike";

if ( opText.isEmpty() )
{
Expand All @@ -2731,13 +2800,13 @@ QDomElement QgsOgcUtilsSQLStatementToFilter::toOgcFilter( const QgsSQLStatement:
if ( op == QgsSQLStatement::boILike )
boElem.setAttribute( "matchCase", "false" );

// setup wildcards to <ogc:PropertyIsLike>
// setup wildCards to <ogc:PropertyIsLike>
boElem.setAttribute( "wildCard", "%" );
boElem.setAttribute( "singleChar", "?" );
boElem.setAttribute( "singleChar", "_" );
if ( mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
boElem.setAttribute( "escape", "!" );
boElem.setAttribute( "escape", "\\" );
else
boElem.setAttribute( "escapeChar", "!" );
boElem.setAttribute( "escapeChar", "\\" );
}

boElem.appendChild( leftElem );
Expand Down
48 changes: 46 additions & 2 deletions tests/src/core/testqgsogcutils.cpp
Expand Up @@ -180,13 +180,41 @@ void TestQgsOgcUtils::testExpressionFromOgcFilter_data()
"</Filter>" )
<< QString( "POPULATION >= 100 AND POPULATION <= 200" );

// TODO: needs to handle different wildcards, single chars, escape chars
// handle different wildcards, single chars, escape chars
QTest::newRow( "like" ) << QString(
"<Filter>"
"<PropertyIsLike wildcard='*' singleChar='.' escape='!'>"
"<PropertyIsLike wildCard=\"%\" singleChar=\"_\" escape=\"\\\">"
"<PropertyName>NAME</PropertyName><Literal>*QGIS*</Literal></PropertyIsLike>"
"</Filter>" )
<< QString( "NAME LIKE '*QGIS*'" );
QTest::newRow( "ilike" ) << QString(
"<Filter>"
"<PropertyIsLike matchCase=\"false\" wildCard=\"%\" singleChar=\"_\" escape=\"\\\">"
"<PropertyName>NAME</PropertyName><Literal>*QGIS*</Literal></PropertyIsLike>"
"</Filter>" )
<< QString( "NAME ILIKE '*QGIS*'" );

// different wildCards
QTest::newRow( "like wildCard" ) << QString(
"<Filter>"
"<PropertyIsLike wildCard='*' singleChar='.' escape=\"\\\">"
"<PropertyName>NAME</PropertyName><Literal>*%QGIS*\\*</Literal></PropertyIsLike>"
"</Filter>" )
<< QString( "NAME LIKE '%\\\\%QGIS%*'" );
// different single chars
QTest::newRow( "like single char" ) << QString(
"<Filter>"
"<PropertyIsLike wildCard='*' singleChar='.' escape=\"\\\">"
"<PropertyName>NAME</PropertyName><Literal>._QGIS.\\.</Literal></PropertyIsLike>"
"</Filter>" )
<< QString( "NAME LIKE '_\\\\_QGIS_.'" );
// different single chars
QTest::newRow( "like escape char" ) << QString(
"<Filter>"
"<PropertyIsLike wildCard=\"*\" singleChar=\".\" escape=\"!\">"
"<PropertyName>NAME</PropertyName><Literal>_QGIS.!.!!%QGIS*!*</Literal></PropertyIsLike>"
"</Filter>" )
<< QString( "NAME LIKE '\\\\_QGIS_.!\\\\%QGIS%*'" );

QTest::newRow( "is null" ) << QString(
"<Filter>"
Expand Down Expand Up @@ -301,6 +329,22 @@ void TestQgsOgcUtils::testExpressionToOgcFilter_data()
"</ogc:And>"
"</ogc:Filter>" );

QTest::newRow( "like" ) << QString( "NAME LIKE '*QGIS*'" ) << QString(
"<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\">"
"<ogc:PropertyIsLike singleChar=\"_\" escape=\"\\\" wildCard=\"%\">"
"<ogc:PropertyName>NAME</ogc:PropertyName>"
"<ogc:Literal>*QGIS*</ogc:Literal>"
"</ogc:PropertyIsLike>"
"</ogc:Filter>" );

QTest::newRow( "ilike" ) << QString( "NAME ILIKE '*QGIS*'" ) << QString(
"<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\">"
"<ogc:PropertyIsLike matchCase=\"false\" singleChar=\"_\" escape=\"\\\" wildCard=\"%\">"
"<ogc:PropertyName>NAME</ogc:PropertyName>"
"<ogc:Literal>*QGIS*</ogc:Literal>"
"</ogc:PropertyIsLike>"
"</ogc:Filter>" );

QTest::newRow( "is null" ) << QString( "A IS NULL" ) << QString(
"<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\">"
"<ogc:PropertyIsNull>"
Expand Down

0 comments on commit 72f93c9

Please sign in to comment.