535 lines
18 KiB
SourcePawn
535 lines
18 KiB
SourcePawn
/*
|
|
* ============================================================================
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* ============================================================================
|
|
*/
|
|
|
|
/**
|
|
* @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, "\\\\", "\\");
|
|
}
|