/********************************************************************** * $Id$ * * Project: MapServer * Purpose: OGC Filter Encoding implementation * Author: Y. Assefa, DM Solutions Group (assefa@dmsolutions.ca) * ********************************************************************** * Copyright (c) 2003, Y. Assefa, DM Solutions Group Inc * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies of this Software or works derived from this Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ****************************************************************************/ #include "mapogcfilter.h" #include "mapserver.h" #include "mapows.h" #include "mapowscommon.h" #include "cpl_minixml.h" #include static std::string FLTEscapePropertyName(const char *pszStr, char chEscapeChar) { std::string ret; for (; *pszStr; ++pszStr) { if (*pszStr == chEscapeChar) { ret += chEscapeChar; ret += chEscapeChar; } else { ret += *pszStr; } } return ret; } std::string msGetLikePatternAsRegex(const FEPropertyIsLike *propIsLike, const char *pszValue) { /* From * http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap09.html#tag_09_04 */ /* also add double quote because we are within a string */ const char *pszRegexSpecialCharsAndDoubleQuote = "\\^${[().*+?|\""; const char *pszWild = propIsLike->pszWildCard; const char *pszSingle = propIsLike->pszSingleChar; const char *pszEscape = propIsLike->pszEscapeChar; std::string expr; const size_t nLength = strlen(pszValue); if (nLength > 0) { expr += '^'; } for (size_t i = 0; i < nLength; i++) { if (pszValue[i] == pszSingle[0]) { expr += '.'; /* The Filter escape character is supposed to only escape the single, * wildcard and escape character */ } else if (pszValue[i] == pszEscape[0] && (pszValue[i + 1] == pszSingle[0] || pszValue[i + 1] == pszWild[0] || pszValue[i + 1] == pszEscape[0])) { if (pszValue[i + 1] == '\\') { /* Tricky case: \ must be escaped ncce in the regular expression context so that the regexp matches it as an ordinary character. But as \ is also the escape character for MapServer string, we must escape it again. */ expr += "\\" "\\" "\\" "\\"; } else { /* If the escaped character is itself a regular expression special * character */ /* we need to regular-expression-escape-it ! */ if (strchr(pszRegexSpecialCharsAndDoubleQuote, pszValue[i + 1])) { expr += '\\'; } expr += pszValue[i + 1]; } i++; } else if (pszValue[i] == pszWild[0]) { expr += ".*"; } /* Escape regular expressions special characters and double quote */ else if (strchr(pszRegexSpecialCharsAndDoubleQuote, pszValue[i])) { if (pszValue[i] == '\\') { /* See above explantation */ expr += "\\" "\\" "\\" "\\"; } else { expr += '\\'; expr += pszValue[i]; } } else { expr += pszValue[i]; } } if (nLength > 0) { expr += '$'; } return expr; } static std::string FLTGetIsLikeComparisonCommonExpression(const FilterEncodingNode *psFilterNode) { if (!psFilterNode || !psFilterNode->pOther || !psFilterNode->psLeftNode || !psFilterNode->psRightNode || !psFilterNode->psRightNode->pszValue) return std::string(); const FEPropertyIsLike *propIsLike = (const FEPropertyIsLike *)psFilterNode->pOther; const char *pszWild = propIsLike->pszWildCard; const char *pszSingle = propIsLike->pszSingleChar; const char *pszEscape = propIsLike->pszEscapeChar; const bool bCaseInsensitive = propIsLike->bCaseInsensitive != 0; if (!pszWild || strlen(pszWild) == 0 || !pszSingle || strlen(pszSingle) == 0 || !pszEscape || strlen(pszEscape) == 0) return std::string(); /* -------------------------------------------------------------------- */ /* Use operand with regular expressions. */ /* -------------------------------------------------------------------- */ std::string expr("(\"["); /* attribute */ expr += FLTEscapePropertyName(psFilterNode->psLeftNode->pszValue, '"'); /* #3521 */ if (bCaseInsensitive) expr += "]\" ~* \""; else expr += "]\" ~ \""; expr += msGetLikePatternAsRegex(propIsLike, psFilterNode->psRightNode->pszValue); expr += "\")"; return expr; } static std::string FLTGetIsBetweenComparisonCommonExpresssion(FilterEncodingNode *psFilterNode, layerObj *lp) { if (psFilterNode->psLeftNode == NULL || psFilterNode->psRightNode == NULL) return std::string(); /* -------------------------------------------------------------------- */ /* Get the bounds value which are stored like boundmin;boundmax */ /* -------------------------------------------------------------------- */ const auto bounds = msStringSplit(psFilterNode->psRightNode->pszValue, ';'); if (bounds.size() != 2) { return std::string(); } /* -------------------------------------------------------------------- */ /* check if the value is a numeric value or alphanumeric. If it */ /* is alphanumeric, add quotes around attribute and values. */ /* -------------------------------------------------------------------- */ bool bString = false; bool bDateTime = false; const char *pszType = msOWSLookupMetadata( &(lp->metadata), "OFG", (std::string(psFilterNode->psLeftNode->pszValue) + "_type").c_str()); if (pszType != NULL && (strcasecmp(pszType, "Character") == 0)) bString = true; else if (pszType != NULL && (strcasecmp(pszType, "Date") == 0)) bDateTime = true; else if (FLTIsNumeric(bounds[0].c_str()) == MS_FALSE) bString = true; if (!bString && !bDateTime) { if (FLTIsNumeric(bounds[1].c_str()) == MS_FALSE) bString = true; } std::string expr; /* -------------------------------------------------------------------- */ /* build expression. */ /* -------------------------------------------------------------------- */ /* attribute */ if (bString) expr += "(\"["; else expr += "(["; expr += FLTEscapePropertyName(psFilterNode->psLeftNode->pszValue, bString ? '"' : ']'); if (bString) expr += "]\" "; else expr += "] "; expr += " >= "; if (bString) { expr += '\"'; } else if (bDateTime) { expr += '`'; } expr += msStdStringEscape(bounds[0].c_str()); if (bString) { expr += '\"'; } else if (bDateTime) { expr += '`'; } expr += " AND "; if (bString) expr += " \"["; else expr += " ["; /* attribute */ expr += psFilterNode->psLeftNode->pszValue; if (bString) expr += "]\" "; else expr += "] "; expr += " <= "; if (bString) { expr += '\"'; } else if (bDateTime) { expr += '`'; } expr += msStdStringEscape(bounds[1].c_str()); if (bString) { expr += '\"'; } else if (bDateTime) { expr += '`'; } expr += ')'; return expr; } std::string FLTGetBinaryComparisonCommonExpression(layerObj *lp, const char *pszPropertyName, bool bForceString, const char *pszOp, const char *pszValue) { assert(pszPropertyName); assert(pszOp); /* -------------------------------------------------------------------- */ /* check if the value is a numeric value or alphanumeric. If it */ /* is alphanumeric, add quotes around attribute and values. */ /* -------------------------------------------------------------------- */ bool bString = false; bool bDateTime = false; if (pszValue) { const char *pszType = msOWSLookupMetadata(&(lp->metadata), "OFG", (std::string(pszPropertyName) + "_type").c_str()); if (pszType != NULL && (strcasecmp(pszType, "Character") == 0)) bString = true; else if (pszType != NULL && (strcasecmp(pszType, "Date") == 0)) bDateTime = true; else if (FLTIsNumeric(pszValue) == MS_FALSE) bString = true; } /* special case to be able to have empty strings in the expression. */ /* propertyislike is always treated as string */ if (!pszValue || bForceString) bString = true; /* attribute */ std::string expr; if (bString) expr = "(\"["; else expr = "(["; expr += FLTEscapePropertyName(pszPropertyName, bString ? '"' : ']'); if (bString) expr += "]\" "; else expr += "] "; expr += pszOp; expr += ' '; /* value */ if (bString) { expr += "\""; } else if (bDateTime) { expr += "`"; } if (pszValue) { expr += msStdStringEscape(pszValue); } if (bString) { expr += "\""; } else if (bDateTime) { expr += "`"; } expr += ")"; return expr; } static std::string FLTGetBinaryComparisonCommonExpression(FilterEncodingNode *psFilterNode, layerObj *lp) { const char *pszPropertyName = psFilterNode->psLeftNode->pszValue; const char *pszValue = psFilterNode->psRightNode->pszValue; const char *pszXMLOp = psFilterNode->pszValue; if (pszPropertyName && pszXMLOp) { /* special case to be able to have empty strings in the expression. */ /* propertyislike is always treated as string */ const bool bForceString = (strcasecmp(pszXMLOp, "PropertyIsLike") == 0); const char *pszOp = nullptr; if (strcasecmp(pszXMLOp, "PropertyIsEqualTo") == 0) { /* case insensitive set ? */ if (psFilterNode->psRightNode->pOther && (*(int *)psFilterNode->psRightNode->pOther) == 1) pszOp = "=*"; else pszOp = "="; } else if (strcasecmp(pszXMLOp, "PropertyIsNotEqualTo") == 0) pszOp = "!="; else if (strcasecmp(pszXMLOp, "PropertyIsLessThan") == 0) pszOp = "<"; else if (strcasecmp(pszXMLOp, "PropertyIsGreaterThan") == 0) pszOp = ">"; else if (strcasecmp(pszXMLOp, "PropertyIsLessThanOrEqualTo") == 0) pszOp = "<="; else if (strcasecmp(pszXMLOp, "PropertyIsGreaterThanOrEqualTo") == 0) pszOp = ">="; else if (strcasecmp(pszXMLOp, "PropertyIsLike") == 0) pszOp = "~"; else return std::string(); return FLTGetBinaryComparisonCommonExpression( lp, pszPropertyName, bForceString, pszOp, pszValue); } return std::string(); } static std::string FLTGetLogicalComparisonCommonExpression(FilterEncodingNode *psFilterNode, layerObj *lp) { std::string expr; /* -------------------------------------------------------------------- */ /* OR and AND */ /* -------------------------------------------------------------------- */ if (psFilterNode->psLeftNode && psFilterNode->psRightNode) { char *pszTmp = FLTGetCommonExpression(psFilterNode->psLeftNode, lp); if (!pszTmp) return std::string(); expr = '('; expr += pszTmp; msFree(pszTmp); expr += ' '; expr += psFilterNode->pszValue; expr += ' '; pszTmp = FLTGetCommonExpression(psFilterNode->psRightNode, lp); if (!pszTmp) { return std::string(); } expr += pszTmp; msFree(pszTmp); expr += ')'; } /* -------------------------------------------------------------------- */ /* NOT */ /* -------------------------------------------------------------------- */ else if (psFilterNode->psLeftNode && strcasecmp(psFilterNode->pszValue, "NOT") == 0) { char *pszTmp = FLTGetCommonExpression(psFilterNode->psLeftNode, lp); if (!pszTmp) return std::string(); expr = "(NOT "; expr += pszTmp; msFree(pszTmp); expr += ')'; } return expr; } static std::string FLTGetSpatialComparisonCommonExpression(FilterEncodingNode *psNode, layerObj *lp) { std::string expr; double dfDistance = -1; shapeObj *psTmpShape = NULL; bool bBBoxQuery = false; bool bAlreadyReprojected = false; if (lp == NULL) return std::string(); /* get the shape */ if (FLTIsBBoxFilter(psNode)) { rectObj sQueryRect; FLTGetBBOX(psNode, &sQueryRect); char szPolygon[512]; snprintf(szPolygon, sizeof(szPolygon), "POLYGON((%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f " "%.18f))", sQueryRect.minx, sQueryRect.miny, sQueryRect.minx, sQueryRect.maxy, sQueryRect.maxx, sQueryRect.maxy, sQueryRect.maxx, sQueryRect.miny, sQueryRect.minx, sQueryRect.miny); psTmpShape = msShapeFromWKT(szPolygon); /* ** This is a horrible hack to deal with world-extent requests and ** reprojection. msProjectRect() detects if reprojection from longlat to ** projected SRS, and in that case it transforms the bbox to *-1e-15,-1e-15,1e15,1e15 ** to ensure that all features are returned. ** ** Make wfs_200_cite_filter_bbox_world.xml and *wfs_200_cite_postgis_bbox_world.xml pass */ if (fabs(sQueryRect.minx - -180.0) < 1e-5 && fabs(sQueryRect.miny - -90.0) < 1e-5 && fabs(sQueryRect.maxx - 180.0) < 1e-5 && fabs(sQueryRect.maxy - 90.0) < 1e-5) { if (lp->projection.numargs > 0) { projectionObj sProjTmp; if (psNode->pszSRS) { msInitProjection(&sProjTmp); msProjectionInheritContextFrom(&sProjTmp, &lp->projection); } if (psNode->pszSRS) { /* Use the non EPSG variant since axis swapping is done in * FLTDoAxisSwappingIfNecessary */ if (msLoadProjectionString(&sProjTmp, psNode->pszSRS) == 0) { msProjectRect(&sProjTmp, &lp->projection, &sQueryRect); } } else if (lp->map->projection.numargs > 0) msProjectRect(&lp->map->projection, &lp->projection, &sQueryRect); if (psNode->pszSRS) msFreeProjection(&sProjTmp); } if (sQueryRect.minx <= -1e14) { msFreeShape(psTmpShape); msFree(psTmpShape); psTmpShape = (shapeObj *)msSmallMalloc(sizeof(shapeObj)); msInitShape(psTmpShape); msRectToPolygon(sQueryRect, psTmpShape); bAlreadyReprojected = true; } } bBBoxQuery = true; } else { /* other geos type operations */ /* project shape to layer projection. If the proj is not part of the filter query, assume that the cooredinates are in the map projection */ int nUnit = -1; shapeObj *psQueryShape = FLTGetShape(psNode, &dfDistance, &nUnit); if ((strcasecmp(psNode->pszValue, "DWithin") == 0 || strcasecmp(psNode->pszValue, "Beyond") == 0) && dfDistance > 0) { int nLayerUnit = lp->units; if (nLayerUnit == -1) nLayerUnit = GetMapserverUnitUsingProj(&lp->projection); if (nLayerUnit == -1) nLayerUnit = lp->map->units; if (nLayerUnit == -1) nLayerUnit = GetMapserverUnitUsingProj(&lp->map->projection); if (nUnit >= 0 && nUnit != nLayerUnit) dfDistance *= msInchesPerUnit(nUnit, 0) / msInchesPerUnit(nLayerUnit, 0); /* target is layer units */ } psTmpShape = psQueryShape; } if (psTmpShape) { /* ** target is layer projection */ if (!bAlreadyReprojected && lp->projection.numargs > 0) { projectionObj sProjTmp; if (psNode->pszSRS) { msInitProjection(&sProjTmp); msProjectionInheritContextFrom(&sProjTmp, &lp->projection); } if (psNode->pszSRS) { /* Use the non EPSG variant since axis swapping is done in * FLTDoAxisSwappingIfNecessary */ if (msLoadProjectionString(&sProjTmp, psNode->pszSRS) == 0) { msProjectShape(&sProjTmp, &lp->projection, psTmpShape); } } else if (lp->map->projection.numargs > 0) msProjectShape(&lp->map->projection, &lp->projection, psTmpShape); if (psNode->pszSRS) msFreeProjection(&sProjTmp); } /* function name */ if (bBBoxQuery) { expr = "intersects"; } else { if (strncasecmp(psNode->pszValue, "intersect", 9) == 0) expr = "intersects"; else { expr = msStringToLower(std::string(psNode->pszValue)); } } /* geometry binding */ expr += "([shape],fromText('"; /* filter geometry */ char *pszWktText = msGEOSShapeToWKT(psTmpShape); expr += pszWktText ? pszWktText : "Cannot translate shape to WKT"; expr += "')"; msGEOSFreeWKT(pszWktText); /* (optional) beyond/dwithin distance, always 0.0 since we apply the * distance as a buffer earlier */ if ((strcasecmp(psNode->pszValue, "DWithin") == 0 || strcasecmp(psNode->pszValue, "Beyond") == 0)) { char szBuffer[32]; snprintf(szBuffer, sizeof(szBuffer), ",%g", dfDistance); expr += szBuffer; } /* terminate the function */ expr += ") = TRUE"; } /* ** Cleanup */ if (bBBoxQuery) { msFreeShape(psTmpShape); msFree(psTmpShape); } return expr; } static std::string FLTGetFeatureIdCommonExpression(FilterEncodingNode *psFilterNode, layerObj *lp) { std::string expr; #if defined(USE_WMS_SVR) || defined(USE_WFS_SVR) || defined(USE_WCS_SVR) || \ defined(USE_SOS_SVR) if (psFilterNode->pszValue) { const char *pszAttribute = msOWSLookupMetadata(&(lp->metadata), "OFG", "featureid"); if (pszAttribute) { const auto tokens = msStringSplit(psFilterNode->pszValue, ','); if (!tokens.empty()) { bool bString = false; for (size_t i = 0; i < tokens.size(); i++) { const char *pszId = tokens[i].c_str(); const char *pszDot = strrchr(pszId, '.'); if (pszDot) pszId = pszDot + 1; if (i == 0) { if (FLTIsNumeric(pszId) == MS_FALSE) bString = true; } if (!expr.empty()) expr += " OR "; else expr = '('; if (bString) { expr += "(\"["; expr += FLTEscapePropertyName(pszAttribute, '"'); expr += "]\" == \""; expr += pszId; expr += "\")"; } else { expr += "(["; expr += FLTEscapePropertyName(pszAttribute, ']'); expr += "] == "; expr += pszId; expr += ")"; } } } } /* opening and closing brackets are needed for mapserver expressions */ if (!expr.empty()) expr += ')'; } #endif return expr; } std::string FLTGetTimeExpression(FilterEncodingNode *psFilterNode, layerObj *lp) { if (lp == NULL) return std::string(); std::string expr; const char *pszTimeField = nullptr; const char *pszTimeValue = FLTGetDuring(psFilterNode, &pszTimeField); if (pszTimeField && pszTimeValue) { expressionObj old_filter; msInitExpression(&old_filter); msCopyExpression(&old_filter, &lp->filter); /* save existing filter */ msFreeExpression(&lp->filter); if (msLayerSetTimeFilter(lp, pszTimeValue, pszTimeField) == MS_TRUE && lp->filter.string) { expr = lp->filter.string; } msCopyExpression(&lp->filter, &old_filter); /* restore old filter */ msFreeExpression(&old_filter); } return expr; } char *FLTGetCommonExpression(FilterEncodingNode *psFilterNode, layerObj *lp) { char *pszExpression = NULL; if (!psFilterNode) return NULL; if (psFilterNode->eType == FILTER_NODE_TYPE_COMPARISON) { if (psFilterNode->psLeftNode && psFilterNode->psRightNode) { if (FLTIsBinaryComparisonFilterType(psFilterNode->pszValue)) pszExpression = msStrdup( FLTGetBinaryComparisonCommonExpression(psFilterNode, lp).c_str()); else if (strcasecmp(psFilterNode->pszValue, "PropertyIsLike") == 0) pszExpression = msStrdup( FLTGetIsLikeComparisonCommonExpression(psFilterNode).c_str()); else if (strcasecmp(psFilterNode->pszValue, "PropertyIsBetween") == 0) pszExpression = msStrdup( FLTGetIsBetweenComparisonCommonExpresssion(psFilterNode, lp) .c_str()); } } else if (psFilterNode->eType == FILTER_NODE_TYPE_LOGICAL) { pszExpression = msStrdup( FLTGetLogicalComparisonCommonExpression(psFilterNode, lp).c_str()); } else if (psFilterNode->eType == FILTER_NODE_TYPE_SPATIAL) { pszExpression = msStrdup( FLTGetSpatialComparisonCommonExpression(psFilterNode, lp).c_str()); } else if (psFilterNode->eType == FILTER_NODE_TYPE_FEATUREID) { pszExpression = msStrdup(FLTGetFeatureIdCommonExpression(psFilterNode, lp).c_str()); } else if (psFilterNode->eType == FILTER_NODE_TYPE_TEMPORAL) { pszExpression = msStrdup(FLTGetTimeExpression(psFilterNode, lp).c_str()); } return pszExpression; } int FLTApplyFilterToLayerCommonExpression(mapObj *map, int iLayerIndex, const char *pszExpression) { return FLTApplyFilterToLayerCommonExpressionWithRect( map, iLayerIndex, pszExpression, map->extent); } /* rect must be in map->projection */ int FLTApplyFilterToLayerCommonExpressionWithRect(mapObj *map, int iLayerIndex, const char *pszExpression, rectObj rect) { int retval; const int save_startindex = map->query.startindex; const int save_maxfeatures = map->query.maxfeatures; const int save_only_cache_result_count = map->query.only_cache_result_count; const int save_cache_shapes = map->query.cache_shapes; const int save_max_cached_shape_count = map->query.max_cached_shape_count; const int save_max_cached_shape_ram_amount = map->query.max_cached_shape_ram_amount; msInitQuery(&(map->query)); map->query.startindex = save_startindex; map->query.maxfeatures = save_maxfeatures; map->query.only_cache_result_count = save_only_cache_result_count; map->query.cache_shapes = save_cache_shapes; map->query.max_cached_shape_count = save_max_cached_shape_count; map->query.max_cached_shape_ram_amount = save_max_cached_shape_ram_amount; map->query.mode = MS_QUERY_MULTIPLE; map->query.layer = iLayerIndex; map->query.rect = rect; if (pszExpression) { map->query.type = MS_QUERY_BY_FILTER; msInitExpression(&map->query.filter); map->query.filter.string = msStrdup(pszExpression); map->query.filter.type = MS_EXPRESSION; /* a logical expression */ retval = msQueryByFilter(map); } else { map->query.type = MS_QUERY_BY_RECT; retval = msQueryByRect(map); } return retval; }