From b47f03db332619fae408cf273b77a82c3068a39b Mon Sep 17 00:00:00 2001 From: rldhont Date: Mon, 3 Oct 2016 14:28:10 +0200 Subject: [PATCH 1/3] Fix testqgsogcutils --- tests/src/core/testqgsogcutils.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/src/core/testqgsogcutils.cpp b/tests/src/core/testqgsogcutils.cpp index 4b4e621e087..1371eeae823 100644 --- a/tests/src/core/testqgsogcutils.cpp +++ b/tests/src/core/testqgsogcutils.cpp @@ -107,14 +107,14 @@ void TestQgsOgcUtils::testGeometryToGML() QVERIFY( !elemPoint.isNull() ); doc.appendChild( elemPoint ); - QCOMPARE( doc.toString( -1 ), QString( "111,222" ) ); + QCOMPARE( doc.toString( -1 ), QString( "111,222" ) ); doc.removeChild( elemPoint ); QDomElement elemLine = QgsOgcUtils::geometryToGML( &geomLine, doc ); QVERIFY( !elemLine.isNull() ); doc.appendChild( elemLine ); - QCOMPARE( doc.toString( -1 ), QString( "111,222 222,222" ) ); + QCOMPARE( doc.toString( -1 ), QString( "111,222 222,222" ) ); doc.removeChild( elemLine ); // Test GML3 @@ -359,7 +359,7 @@ void TestQgsOgcUtils::testExpressionToOgcFilter_data() "" "" "geometry" - "5,6 5,6" + "5,6 5,6" "" "" ); @@ -367,7 +367,7 @@ void TestQgsOgcUtils::testExpressionToOgcFilter_data() "" "" "geometry" - "5,6" + "5,6" "" "" ); @@ -375,7 +375,7 @@ void TestQgsOgcUtils::testExpressionToOgcFilter_data() "" "" "geometry" - "5,6" + "5,6" "" "" ); } @@ -726,7 +726,7 @@ void TestQgsOgcUtils::testSQLStatementToOgcFilter_data() "" "" "geom" - "5,6" + "5,6" "" "" ); @@ -735,7 +735,7 @@ void TestQgsOgcUtils::testSQLStatementToOgcFilter_data() "" "" "geom" - "5,6" + "5,6" "" "" ); From 1d6e5d28e6951df4e5fef9e0927e036e237c0b2b Mon Sep 17 00:00:00 2001 From: rldhont Date: Fri, 30 Sep 2016 16:40:15 +0200 Subject: [PATCH 2/3] [BUGFIX] Support OGC PropertyIsLike attributs 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 --- src/core/qgsogcutils.cpp | 85 +++++++++++++++++++++++++++--- tests/src/core/testqgsogcutils.cpp | 48 ++++++++++++++++- 2 files changed, 123 insertions(+), 10 deletions(-) diff --git a/src/core/qgsogcutils.cpp b/src/core/qgsogcutils.cpp index bd4a2584652..693cae80ae0 100644 --- a/src/core/qgsogcutils.cpp +++ b/src/core/qgsogcutils.cpp @@ -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() ); } @@ -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 ) @@ -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( 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 ); } @@ -2289,13 +2356,13 @@ QDomElement QgsOgcUtilsExprToFilter::expressionBinaryOperatorToOgcFilter( const if ( op == QgsExpression::boILike ) boElem.setAttribute( "matchCase", "false" ); - // setup wildcards to + // setup wildCards to 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 ); @@ -2712,6 +2779,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() ) { @@ -2727,13 +2796,13 @@ QDomElement QgsOgcUtilsSQLStatementToFilter::toOgcFilter( const QgsSQLStatement: if ( op == QgsSQLStatement::boILike ) boElem.setAttribute( "matchCase", "false" ); - // setup wildcards to + // setup wildCards to 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 ); diff --git a/tests/src/core/testqgsogcutils.cpp b/tests/src/core/testqgsogcutils.cpp index 1371eeae823..dd0d510963f 100644 --- a/tests/src/core/testqgsogcutils.cpp +++ b/tests/src/core/testqgsogcutils.cpp @@ -180,13 +180,41 @@ void TestQgsOgcUtils::testExpressionFromOgcFilter_data() "" ) << 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( "" - "" + "" "NAME*QGIS*" "" ) << QString( "NAME LIKE '*QGIS*'" ); + QTest::newRow( "ilike" ) << QString( + "" + "" + "NAME*QGIS*" + "" ) + << QString( "NAME ILIKE '*QGIS*'" ); + + // different wildCards + QTest::newRow( "like wildCard" ) << QString( + "" + "" + "NAME*%QGIS*\\*" + "" ) + << QString( "NAME LIKE '%\\\\%QGIS%*'" ); + // different single chars + QTest::newRow( "like single char" ) << QString( + "" + "" + "NAME._QGIS.\\." + "" ) + << QString( "NAME LIKE '_\\\\_QGIS_.'" ); + // different single chars + QTest::newRow( "like escape char" ) << QString( + "" + "" + "NAME_QGIS.!.!!%QGIS*!*" + "" ) + << QString( "NAME LIKE '\\\\_QGIS_.!\\\\%QGIS%*'" ); QTest::newRow( "is null" ) << QString( "" @@ -301,6 +329,22 @@ void TestQgsOgcUtils::testExpressionToOgcFilter_data() "" "" ); + QTest::newRow( "like" ) << QString( "NAME LIKE '*QGIS*'" ) << QString( + "" + "" + "NAME" + "*QGIS*" + "" + "" ); + + QTest::newRow( "ilike" ) << QString( "NAME ILIKE '*QGIS*'" ) << QString( + "" + "" + "NAME" + "*QGIS*" + "" + "" ); + QTest::newRow( "is null" ) << QString( "A IS NULL" ) << QString( "" "" From 52a78def7f81eebc4b067048043dc27df3f1549c Mon Sep 17 00:00:00 2001 From: rldhont Date: Tue, 4 Oct 2016 11:40:55 +0200 Subject: [PATCH 3/3] Reactivate ogcutils tests and update its --- ci/travis/linux/script.sh | 2 +- tests/src/core/testqgsogcutils.cpp | 172 +++++++++++++++++++++++------ 2 files changed, 141 insertions(+), 33 deletions(-) diff --git a/ci/travis/linux/script.sh b/ci/travis/linux/script.sh index 0ab8c4c1b41..d7e680ea9c7 100755 --- a/ci/travis/linux/script.sh +++ b/ci/travis/linux/script.sh @@ -21,5 +21,5 @@ export CCACHE_TEMPDIR=/tmp DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -xvfb-run ctest -V -E "qgis_openstreetmaptest|qgis_wcsprovidertest|PyQgsWFSProviderGUI|qgis_ziplayertest|qgis_ogcutilstest|$(cat ${DIR}/blacklist.txt | paste -sd '|' -)" -S ./qgis-test-travis.ctest --output-on-failure +xvfb-run ctest -V -E "qgis_openstreetmaptest|qgis_wcsprovidertest|PyQgsWFSProviderGUI|qgis_ziplayertest|$(cat ${DIR}/blacklist.txt | paste -sd '|' -)" -S ./qgis-test-travis.ctest --output-on-failure # xvfb-run ctest -V -E "qgis_openstreetmaptest|qgis_wcsprovidertest" -S ./qgis-test-travis.ctest --output-on-failure diff --git a/tests/src/core/testqgsogcutils.cpp b/tests/src/core/testqgsogcutils.cpp index dd0d510963f..9fd87c395ca 100644 --- a/tests/src/core/testqgsogcutils.cpp +++ b/tests/src/core/testqgsogcutils.cpp @@ -93,12 +93,126 @@ void TestQgsOgcUtils::testGeometryFromGML() QVERIFY( geomBox.wkbType() == QgsWkbTypes::Polygon ); } +static bool compareElements( QDomElement& element1, QDomElement& element2 ) +{ + QString tag1 = element1.tagName(); + tag1.replace( QRegExp( ".*:" ), "" ); + QString tag2 = element2.tagName(); + tag2.replace( QRegExp( ".*:" ), "" ); + if ( tag1 != tag2 ) + { + qDebug( "Different tag names: %s, %s", tag1.toAscii().data(), tag2.toAscii().data() ); + return false ; + } + + if ( element1.hasAttributes() != element2.hasAttributes() ) + { + qDebug( "Different hasAttributes: %s, %s", tag1.toAscii().data(), tag2.toAscii().data() ); + return false; + } + + if ( element1.hasAttributes() ) + { + QDomNamedNodeMap attrs1 = element1.attributes(); + QDomNamedNodeMap attrs2 = element2.attributes(); + + if ( attrs1.size() != attrs2.size() ) + { + qDebug( "Different attributes size: %s, %s", tag1.toAscii().data(), tag2.toAscii().data() ); + return false; + } + + for ( int i = 0 ; i < attrs1.size() ; ++i ) + { + QDomNode node1 = attrs1.item( i ); + QDomAttr attr1 = node1.toAttr(); + + if ( !element2.hasAttribute( attr1.name() ) ) + { + qDebug( "Element2 has not attribute: %s, %s, %s", tag1.toAscii().data(), tag2.toAscii().data(), attr1.name().toAscii().data() ); + return false; + } + + if ( element2.attribute( attr1.name() ) != attr1.value() ) + { + qDebug( "Element2 attribute has not the same value: %s, %s, %s", tag1.toAscii().data(), tag2.toAscii().data(), attr1.name().toAscii().data() ); + return false; + } + } + } + + if ( element1.hasChildNodes() != element2.hasChildNodes() ) + { + qDebug( "Different childNodes: %s, %s", tag1.toAscii().data(), tag2.toAscii().data() ); + return false; + } + + if ( element1.hasChildNodes() ) + { + QDomNodeList nodes1 = element1.childNodes(); + QDomNodeList nodes2 = element2.childNodes(); + + if ( nodes1.size() != nodes2.size() ) + { + qDebug( "Different childNodes size: %s, %s", tag1.toAscii().data(), tag2.toAscii().data() ); + return false; + } + + for ( int i = 0 ; i < nodes1.size() ; ++i ) + { + QDomNode node1 = nodes1.at( i ); + QDomNode node2 = nodes2.at( i ); + if ( node1.isElement() && node2.isElement() ) + { + QDomElement elt1 = node1.toElement(); + QDomElement elt2 = node2.toElement(); + + if ( !compareElements( elt1, elt2 ) ) + return false; + } + else if ( node1.isText() && node2.isText() ) + { + QDomText txt1 = node1.toText(); + QDomText txt2 = node2.toText(); + + if ( txt1.data() != txt2.data() ) + { + qDebug( "Different text data: %s %s", tag1.toAscii().data(), txt1.data().toAscii().data() ); + qDebug( "Different text data: %s %s", tag2.toAscii().data(), txt2.data().toAscii().data() ); + return false; + } + } + } + } + + if ( element1.text() != element2.text() ) + { + qDebug( "Different text: %s %s", tag1.toAscii().data(), element1.text().toAscii().data() ); + qDebug( "Different text: %s %s", tag2.toAscii().data(), element2.text().toAscii().data() ); + return false; + } + + return true; +} +static QDomElement comparableElement( const QString& xmlText ) +{ + QDomDocument doc; + if ( !doc.setContent( xmlText ) ) + return QDomElement(); + return doc.documentElement(); +} + + void TestQgsOgcUtils::testGeometryToGML() { QDomDocument doc; QgsGeometry geomPoint( QgsGeometry::fromPoint( QgsPoint( 111, 222 ) ) ); QgsGeometry geomLine( QgsGeometry::fromWkt( "LINESTRING(111 222, 222 222)" ) ); + // Elements to compare + QDomElement xmlElem; + QDomElement ogcElem; + // Test GML2 QDomElement elemInvalid = QgsOgcUtils::geometryToGML( 0, doc ); QVERIFY( elemInvalid.isNull() ); @@ -107,14 +221,18 @@ void TestQgsOgcUtils::testGeometryToGML() QVERIFY( !elemPoint.isNull() ); doc.appendChild( elemPoint ); - QCOMPARE( doc.toString( -1 ), QString( "111,222" ) ); + xmlElem = comparableElement( QString( "111,222" ) ); + ogcElem = comparableElement( doc.toString( -1 ) ); + QVERIFY( compareElements( xmlElem, ogcElem ) ); doc.removeChild( elemPoint ); QDomElement elemLine = QgsOgcUtils::geometryToGML( &geomLine, doc ); QVERIFY( !elemLine.isNull() ); doc.appendChild( elemLine ); - QCOMPARE( doc.toString( -1 ), QString( "111,222 222,222" ) ); + xmlElem = comparableElement( QString( "111,222 222,222" ) ); + ogcElem = comparableElement( doc.toString( -1 ) ); + QVERIFY( compareElements( xmlElem, ogcElem ) ); doc.removeChild( elemLine ); // Test GML3 @@ -125,14 +243,18 @@ void TestQgsOgcUtils::testGeometryToGML() QVERIFY( !elemPoint.isNull() ); doc.appendChild( elemPoint ); - QCOMPARE( doc.toString( -1 ), QString( "111 222" ) ); + xmlElem = comparableElement( QString( "111 222" ) ); + ogcElem = comparableElement( doc.toString( -1 ) ); + QVERIFY( compareElements( xmlElem, ogcElem ) ); doc.removeChild( elemPoint ); elemLine = QgsOgcUtils::geometryToGML( &geomLine, doc, "GML3" ); QVERIFY( !elemLine.isNull() ); doc.appendChild( elemLine ); - QCOMPARE( doc.toString( -1 ), QString( "111 222 222 222" ) ); + xmlElem = comparableElement( QString( "111 222 222 222" ) ); + ogcElem = comparableElement( doc.toString( -1 ) ); + QVERIFY( compareElements( xmlElem, ogcElem ) ); doc.removeChild( elemLine ); } @@ -287,7 +409,10 @@ void TestQgsOgcUtils::testExpressionToOgcFilter() qDebug( "EXPR: %s", exp.expression().toAscii().data() ); qDebug( "OGC : %s", doc.toString( -1 ).toAscii().data() ); - QCOMPARE( xmlText, doc.toString( -1 ) ); + + QDomElement xmlElem = comparableElement( xmlText ); + QDomElement ogcElem = comparableElement( doc.toString( -1 ) ); + QVERIFY( compareElements( xmlElem, ogcElem ) ); } void TestQgsOgcUtils::testExpressionToOgcFilter_data() @@ -449,7 +574,10 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS11() qDebug( "SRSNAME: %s", srsName.toAscii().data() ); qDebug( "OGC : %s", doc.toString( -1 ).toAscii().data() ); - QCOMPARE( xmlText, doc.toString( -1 ) ); + + QDomElement xmlElem = comparableElement( xmlText ); + QDomElement ogcElem = comparableElement( doc.toString( -1 ) ); + QVERIFY( compareElements( xmlElem, ogcElem ) ); } void TestQgsOgcUtils::testExpressionToOgcFilterWFS11_data() @@ -473,14 +601,6 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS11_data() "" ); } -static QString normalizeXML( const QString& xmlText ) -{ - QDomDocument doc; - if ( !doc.setContent( xmlText, true ) ) - return QString(); - return doc.toString( -1 ); -} - void TestQgsOgcUtils::testExpressionToOgcFilterWFS20() { QFETCH( QString, exprText ); @@ -506,15 +626,9 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS20() qDebug( "SRSNAME: %s", srsName.toAscii().data() ); qDebug( "OGC : %s", doc.toString( -1 ).toAscii().data() ); - QString normalizedExpected( normalizeXML( xmlText ) ); - QString normalizedGot( normalizeXML( doc.toString( -1 ) ) ); - - if ( normalizedExpected != normalizedGot ) - { - qDebug( "Normalized expected: %s", normalizedExpected.toAscii().data() ); - qDebug( "Normalized got: %s", normalizedGot.toAscii().data() ); - } - QCOMPARE( normalizedExpected, normalizedGot ); + QDomElement xmlElem = comparableElement( xmlText ); + QDomElement ogcElem = comparableElement( doc.toString( -1 ) ); + QVERIFY( compareElements( xmlElem, ogcElem ) ); } void TestQgsOgcUtils::testExpressionToOgcFilterWFS20_data() @@ -614,15 +728,9 @@ void TestQgsOgcUtils::testSQLStatementToOgcFilter() filterVersion == QgsOgcUtils::FILTER_FES_2_0 ? "FES 2.0" : "unknown" ); qDebug( "OGC : %s", doc.toString( -1 ).toAscii().data() ); - QString normalizedExpected( normalizeXML( xmlText ) ); - QString normalizedGot( normalizeXML( doc.toString( -1 ) ) ); - - if ( normalizedExpected != normalizedGot ) - { - qDebug( "Normalized expected: %s", normalizedExpected.toAscii().data() ); - qDebug( "Normalized got: %s", normalizedGot.toAscii().data() ); - } - QCOMPARE( normalizedExpected, normalizedGot ); + QDomElement xmlElem = comparableElement( xmlText ); + QDomElement ogcElem = comparableElement( doc.toString( -1 ) ); + QVERIFY( compareElements( xmlElem, ogcElem ) ); } void TestQgsOgcUtils::testSQLStatementToOgcFilter_data()