/* * ============================================================================ * * 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-2013 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 if the current quote is not escaped. if (paramString[startpos - 1] != '\\') { 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, "\\\\", "\\"); }