diff --git a/src/zombiereloaded.sp b/src/zombiereloaded.sp index 22d9806..b0655a2 100644 --- a/src/zombiereloaded.sp +++ b/src/zombiereloaded.sp @@ -49,6 +49,7 @@ #include "zr/menu" #include "zr/cookies" #include "zr/paramtools" +#include "zr/paramparser" #include "zr/models" #include "zr/downloads" #include "zr/overlays" diff --git a/src/zr/paramparser.inc b/src/zr/paramparser.inc new file mode 100644 index 0000000..ea3a5cc --- /dev/null +++ b/src/zr/paramparser.inc @@ -0,0 +1,531 @@ +/* + * ============================================================================ + * + * Zombie:Reloaded + * + * File: paramparser.inc + * Type: Core + * Description: Provides functions for parsing single line strings with + * flags, and parameters in key=value format. + * + * Supports quoted strings and escaped characters like "\n" + * and "\t". + * + * Examle raw string: + * "type=interval -disabled msg="Title:\n\"Example\"." + * + * Copyright (C) 2009 Greyscale, Richard Helgeby + * + * 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 3 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, see . + * + * ============================================================================ + */ + +/** + * @section Limit settings. + */ +#define PARAM_NAME_MAXLEN 64 /** Maximum length of key name or flag name. */ +#define PARAM_VALUE_MAXLEN 256 /** Maximum length of value string. */ +/** + * @endsection + */ + +/** + * @section Parsing error codes. + */ +#define PARAM_ERROR_EMPTY 1 /** Source string is empty. */ +#define PARAM_ERROR_FULL 2 /** Destination array is full. */ +#define PARAM_ERROR_UNEXPECTED_KEY 3 /** Unexpected key name. Could not find a equation sign (=) after previous key name. */ +#define PARAM_ERROR_UNEXPECTED_END 4 /** Unexpected end of source string. */ +#define PARAM_ERROR_MISSING_QUOTE 5 /** Unexpected end of source string. Missing end quote character. */ +#define PARAM_ERROR_UNKNOWN 6 /** Unknown error. The parser got a invalid result from a search function it couldn't handle. */ +/** + * @endsection + */ + +/** + * Modes for what to do and expect when parsing. White space characters between + * modes are ignored. + */ +enum ParamModes +{ + ParamMode_TypeCheck, /** Check if it's a flag or a key. */ + ParamMode_Flag, /** Expect a flag name (starts with "-"). */ + ParamMode_Key, /** Expect a key name. */ + ParamMode_Equal, /** Expect a equation sign. */ + ParamMode_Value /** Expect a value string. */ +} + +/** + * Structure for storing a key/value pair. + */ +enum ParamParseResult +{ + bool:Param_IsFlag, /** Specifies whether it's a flag or not. */ + String:Param_Name[PARAM_NAME_MAXLEN], /** Key or flag name. */ + String:Param_Value[PARAM_VALUE_MAXLEN] /** Value. Only used if a key. */ +} + + + +/************************************** + * * + * PARAMETER FUNCTIONS * + * * + **************************************/ + +/** + * Parses a parameter string in "key=value" format and store the result in a + * ParamParseResult array. + * + * @param buffer A ParamParseResult array to store results. + * @param maxlen Maximum number of keys that can be stored (first + * dimension of buffer). + * @param paramString The source string to parse. String is trimmed before + * parsing. + * @param err Opional output: Error code if parsing error. + * @param errPos Opional output: Position in paramString where the error + * occoured. + * @return Number of keys parsed. + */ +stock ParamParseString(buffer[][ParamParseResult], maxlen, String:paramString[], &err = 0, &errPos = -1) +{ + /* + * VALIDATION OF INPUT AND BUFFERS + */ + + // Trim raw string. + TrimString(paramString); + + // Check if raw string is empty. + if (strlen(paramString) == 0) + { + err = PARAM_ERROR_EMPTY; + errPos = 0; + return 0; + } + + // Validate specified length of destination buffer. + if (maxlen == 0) + { + err = PARAM_ERROR_FULL; + errPos = 0; + return 0; + } + + + /* + * PARSE LOOP + */ + + // Get raw string length. + new rawlen = sizeof(paramString); + + // Initialize. Expect the start to be a key or a flag. + new ParamModes:mode = ParamMode_TypeCheck; + + // Counter for number of parameters parsed. + new paramcount; + + // Buffers for temp values. + new startpos; + new endpos; + new bool:quoteon; + decl String:value[PARAM_VALUE_MAXLEN]; + + // Loop through all characters in the string. Exclude null terminator. + for (new strpos = 0; strpos < rawlen - 1; strpos++) + { + // Check if there's space left in the destination buffer. + if (paramcount > maxlen) + { + // Exit loop. No more parameters can be parsed. + err = PARAM_ERROR_FULL; + errPos = strpos; + break; + } + + + /* + * MODE CHECK + */ + + // Check mode for deciding what to do. + switch (mode) + { + case ParamMode_TypeCheck: + { + // Find start position of first non white space character. + startpos = ParamFindStartPos(paramString, strpos); + + // Check if it's a flag type. + if (paramString[startpos] == '-') + { + // It's a flag, change mode. + mode = ParamMode_Flag; + + // Update current position. + strpos = startpos; + } + else + { + // Expect a key name. + mode = ParamMode_Key; + + // Update current position. Substract by one to include + // the current character in next mode. + strpos = startpos - 1; + } + } + case ParamMode_Flag: + { + // Find stop position (last non white space character). + endpos = ParamFindEndPos(paramString, strpos); + + // Extract key name. + StrExtract(value, sizeof(value), paramString, strpos, endpos); + + // Copy flag to destination buffer. + strcopy(buffer[paramcount][Param_Name], PARAM_NAME_MAXLEN, value); + + // Set flag type. + buffer[paramcount][Param_IsFlag] = true; + + // Increment parameter counter. + paramcount++; + + // Set next parse mode. + mode = ParamMode_TypeCheck; + } + case ParamMode_Key: + { + // Find stop position. + endpos = ParamFindEndPos(paramString, strpos); + + // Extract key name. + StrExtract(value, sizeof(value), paramString, strpos, endpos); + + // Copy key name to destination buffer. + strcopy(buffer[paramcount][Param_Name], PARAM_NAME_MAXLEN, value); + + // Make sure flag type is not set. + buffer[paramcount][Param_IsFlag] = false; + + // Note: Do not increment parameter counter until the + // entire key/value pair is parsed. + + // Set next parse mode. Expect a equation sign. + mode = ParamMode_Equal; + } + case ParamMode_Equal: + { + // Find start position of first non white space character. + startpos = ParamFindStartPos(paramString, strpos); + + // Validate position. + if (startpos >= 0) + { + // Check if it's a equation sign. + if (paramString[startpos] == '=') + { + // Change mode to expect a value at next position. + mode = ParamMode_Value; + + // Update current position. + strpos = startpos; + } + else + { + // Parse error. + err = PARAM_ERROR_UNEXPECTED_KEY; + errPos = startpos; + break; + } + } + else + { + // Parse error. + err = PARAM_ERROR_UNEXPECTED_END; + errPos = strpos; + break; + } + } + case ParamMode_Value: + { + // Find start position of first non white space character. + startpos = ParamFindStartPos(paramString, strpos); + + // Validate start position. + if (startpos >= 0) + { + // Reset quote and escape settings. + quoteon = false; + + // Loop through all characters starting from the current + // position. Exclude null terminator. + for (strpos = startpos; strpos < rawlen - 1; strpos++) + { + // Check if the current character is a special character. + if (paramString[startpos] == '"') + { + // Toggle quote. + quoteon = !quoteon; + + // Check quote state. + if (quoteon) + { + // Quote started, update start position. + startpos = strpos + 1; + } + else + { + // Quote end, set end position. + endpos = strpos - 1; + } + } + + // Check if it's a white space character or end of the string. + else if (!quoteon && (IsCharSpace(paramString[strpos]) || strpos == rawlen - 1)) + { + // End of value reached. Save positions. + endpos = strpos - 1; + + // Exit loop. + break; + } + } + + // Check if quote still haven't ended. + if (quoteon) + { + // Parse error. + err = PARAM_ERROR_MISSING_QUOTE; + errPos = strpos; + break; + } + + // Extract value string. + StrExtract(value, sizeof(value), paramString, startpos, endpos); + + // Unescape string (converting "\n" to newline, etc.). + StrUnescape(value); + + // Copy value string to destination buffer. + strcopy(buffer[paramcount][Param_Value], PARAM_VALUE_MAXLEN, value); + + // Make sure flag type is not set. + buffer[paramcount][Param_IsFlag] = false; + + // Increment parameter counter. + paramcount++; + + // Set next parse mode. Expect a key or a flag. + mode = ParamMode_TypeCheck; + } + else + { + // Parse error. + err = PARAM_ERROR_UNEXPECTED_END; + errPos = strpos; + break; + } + } + } + } + + // Return number of parameters parsed. + return paramcount; +} + +/** + * Finds the first key index in a parameter array matching the specified key. + * + * @param params A ParamParseResult array to search through. + * @param maxlen Size of parameter array (first dimension). + * @param key Key to find. + * @param caseSensitive Specifies whether the search is case sensitive or + * not (default). + * @return Index of the key if found, -1 otherwise. + */ +stock ParamFindKey(const params[][ParamParseResult], maxlen, const String:key[], bool:caseSensitive = false) +{ + // Loop through all parameters. + for (new index = 0; index < maxlen; index++) + { + // Check parameter type. + if (params[index][Param_IsFlag]) + { + // It's a flag type, skip index. + continue; + } + + // Match key name. + if (StrEqual(params[index][Param_Name], key, caseSensitive)) + { + // Key found, return the key index. + return index; + } + } + + return -1; +} + +/** + * Checks if the specified flag is set in a parameter array. + * + * @param params A ParamParseResult array to search through. + * @param maxlen Size of parameter array (first dimension). + * @param flag Flag to check. + * @param caseSensitive Specifies whether the search is case sensitive or + * not (default). + * @return True flag is found, false otherwise. + */ +stock bool:ParamHasFlag(const params[][ParamParseResult], maxlen, const String:flag[], bool:caseSensitive = false) +{ + // Loop through all parameters. + for (new index = 0; index < maxlen; index++) + { + // Check parameter type. + if (!params[index][Param_IsFlag]) + { + // It's a key type, skip index. + continue; + } + + // Match flag name. + if (StrEqual(params[index][Param_Name], flag, caseSensitive)) + { + // Flag found. + return true; + } + } + + return false; +} + + + +/************************************** + * * + * HELPER FUNCTIONS * + * * + **************************************/ + +/** + * Finds the position of the last non white space character from a specified start position. + * + * @param paramString Raw string search in. + * @param startPos Optional. Position to start searching from. + * @return Position of the last non white space character, or -1 + * if failed. + */ +stock ParamFindEndPos(const String:paramString[], startPos = 0) +{ + new rawlen = sizeof(paramString); + + // Validate string length. + if (rawlen == 0) + { + return -1; + } + + // Loop through all characters from the specified start position. + for (new strpos = startPos; strpos < rawlen; strpos++) + { + // Check if white space or if current position is the last + // character before the null terminator. + if (IsCharSpace(paramString[strpos]) || strpos == rawlen - 1) + { + return strpos - 1; + } + } + + // It should never reach this place. Added to satisfy compiler. + return -1; +} + +/** + * Finds the first non white space character in a string, starting from the + * specified position. + * + * @param paramString Raw string to search in. + * @param startPos Optional. Position to start searching from. + * @return Position of first character or -1 if failed. + */ +stock ParamFindStartPos(const String:paramString[], startPos = 0) +{ + new rawlen = sizeof(paramString); + + // Validate string length. + if (rawlen == 0) + { + return -1; + } + + // Loop through all characters from the specified start position. + for (new strpos = startPos; strpos < rawlen; strpos++) + { + // Check if not white space. + if (!IsCharSpace(paramString[strpos])) + { + return strpos; + } + } + + // No character found. + return -1; +} + +/** + * Extracts a area in a string between two positions. + * + * @param buffer Destination string buffer. + * @param maxlen Size of destination buffer. + * @param source Source string to extract from. + * @param startpos Start position of string to extract. + * @param endpos End position of string to extract. + * @return Number of cells written. + */ +stock StrExtract(String:buffer[], maxlen, const String:source[], startpos, endpos) +{ + new len; + + // Calculate string length. Also add space for null terminator. + len = endpos - startpos + 1; + + // Validate length. + if (len < 0) + { + return 0; + } + + // Extract string and store it in the buffer. + return strcopy(buffer, len, source[startpos]); +} + +/** + * Unescapes a string (replaces "\n" with newlines, etc.). + * + * @param str String to unescape. + */ +stock StrUnescape(String:str[]) +{ + new len = sizeof(str); + + ReplaceString(str, len, "\\n", "\n"); + ReplaceString(str, len, "\\r", "\r"); + ReplaceString(str, len, "\\t", "\t"); + ReplaceString(str, len, "\\\"", "\""); + ReplaceString(str, len, "\\\\", "\\"); +}