povray/source/base/processoptions.cpp
2013-11-06 13:07:19 -05:00

1535 lines
39 KiB
C++

/*******************************************************************************
* processoptions.cpp
*
* This module contains the C++ interface for option processing.
*
* ---------------------------------------------------------------------------
* Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
* Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
*
* POV-Ray is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* POV-Ray 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------------
* POV-Ray is based on the popular DKB raytracer version 2.12.
* DKBTrace was originally written by David K. Buck.
* DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
* ---------------------------------------------------------------------------
* $File: //depot/public/povray/3.x/source/base/processoptions.cpp $
* $Revision: #1 $
* $Change: 6069 $
* $DateTime: 2013/11/06 11:59:40 $
* $Author: chrisc $
*******************************************************************************/
#include <cstdarg>
#include <cctype>
// configbase.h must always be the first POV file included within base *.cpp files
#include "base/configbase.h"
#include "base/povmscpp.h"
#include "base/processoptions.h"
#include "base/stringutilities.h"
#include "base/povmsgid.h"
#include "base/pov_err.h"
// this must be the last file included
#include "base/povdebug.h"
namespace pov_base
{
ProcessOptions::ProcessOptions(INI_Parser_Table *pit, Cmd_Parser_Table *pct)
{
parse_ini_table = pit;
parse_cmd_table = pct;
}
ProcessOptions::~ProcessOptions()
{
}
int ProcessOptions::ParseFile(const char *filespec, POVMSObjectPtr obj)
{
ITextStream *file = NULL;
const char *currentsection = NULL;
char *sectionname = NULL;
char *filename = NULL;
int err = kNoErr;
POVMSObjectPtr section = NULL;
int currentline = 1;
// split the INI files specification into filename and optional section name
err = Parse_INI_Specification(filespec, filename, sectionname);
if(err == kNoErr)
{
file = OpenFileForRead(filename, obj);
if(file == NULL)
{
// all errors here are non-fatal, the calling code has to decide if an error is fatal
ParseError("Cannot open INI file '%s'.", filename);
err= kCannotOpenFileErr;
}
}
if(err == kNoErr)
{
int token = 0;
// apply options prior to any named section
section = obj;
// read the whole file to the end
while((file->eof() == false) && (err == kNoErr))
{
currentline += Parse_INI_Skip_Space(file, true);
token = file->getchar();
// INI file section name
if(token == '[')
{
// free old section name, if any
if(currentsection != NULL)
delete[] currentsection;
// read until the section name end marker
currentsection = Parse_INI_String(file, ']');
// if the user specified a section name, compare the two and enable reading
if((sectionname != NULL) && (currentsection != NULL))
{
if(pov_stricmp(currentsection, sectionname) == 0)
section = obj; // named section matches specified section name, apply options
else
section = NULL; // named section does not match specified section name, ignore options
}
// if there was no user specified section name, ignore all named sections
else
section = NULL; // no section name was specified, ignore options in named section
}
// skip lines that do not belong to the desired sections
else if(section == NULL)
{
currentline += Parse_INI_Skip_Line(file);
}
// fully process lines in desired sections
else
{
switch(token)
{
// end of file
case EOF:
break;
// quoted string
case '\"':
case '\'':
err = kFalseErr;
break;
// POV-Ray-style INI file with command-line switch
case '+':
case '-':
// POV-Ray-style INI file with system specific command-line switch on some systems (i.e. Windos)
#if(FILENAME_SEPARATOR != '/')
case '/':
#endif
err = Parse_INI_Switch(file, token, section);
break;
// INI file comment
case ';':
case '#':
currentline += Parse_INI_Skip_Line(file);
break;
// INI file option
default:
if(isalnum(token) || (token == '_'))
{
file->ungetchar(token);
ITextStream::FilePos backtrackpos = file->tellg();
err = Parse_INI_Option(file, section);
// only one option is allowed per line
if(err == kNoErr)
currentline += Parse_INI_Skip_Line(file);
else if(err == kParseErr)
{
file->seekg(backtrackpos);
err = kFalseErr;
}
}
else
err = kFalseErr;
break;
}
// if nothing else was appropriate, assume it is some other kind of string requiring special attention
if(err == kFalseErr)
{
char *plainstring = NULL;
if((token != '\"') && (token != '\''))
{
// if there were no quotes, just read up to the next space or newline
plainstring = Parse_INI_String(file, -2, true);
// see if it is probably a standard INI file entry which wasn't recognised
if(plainstring != NULL)
{
if(strchr(plainstring, '=') != NULL)
err = kParseErr;
}
}
else
plainstring = Parse_INI_String(file, token, true);
if(err == kFalseErr)
err = ProcessUnknownString(plainstring, obj);
if(plainstring != NULL)
delete[] plainstring;
}
}
}
// all errors here are non-fatal, the calling code has to decide if an error is fatal
if(err != kNoErr)
{
if(currentsection != NULL)
{
ParseErrorAt(file,
"Cannot continue to process INI file '%s' due to a parse error in line %d section '%s'.\n"
"This is not a valid INI file. Check the file for syntax errors, correct them, and try again!\n"
"Valid options in INI files are explained in detail in the reference part of the documentation.",
filename, currentline, currentsection);
}
else
{
ParseErrorAt(file,
"Cannot continue to process INI file '%s' due to a parse error in line %d.\n"
"This is not a valid INI file. Check the file for syntax errors, correct them, and try again!\n"
"Valid options in INI files are explained in detail in the reference part of the documentation.",
filename, currentline);
}
}
if(currentsection != NULL)
delete[] currentsection;
}
if(filename != NULL)
delete[] filename;
if(sectionname != NULL)
delete[] sectionname;
if(file != NULL)
delete file;
return err;
}
int ProcessOptions::ParseString(const char *commandline, POVMSObjectPtr obj, bool singleswitch)
{
int err = kNoErr;
// read the whole command-line to the end
while((*commandline != 0) && (err == kNoErr))
{
if(singleswitch == false) // see if quotes had been stripped outside POV-Ray
Parse_CL_Skip_Space(commandline);
switch(*commandline)
{
// end of command-line
case 0:
break;
// quoted string
case '\"':
case '\'':
err = kFalseErr;
break;
// switch
case '+':
case '-':
// system specific switch on some systems (i.e. Windos)
#if(FILENAME_SEPARATOR != '/')
case '/':
#endif
commandline++;
err = Parse_CL_Switch(commandline, *(commandline - 1), obj, singleswitch);
break;
// INI file style option
default:
if(isalnum(*commandline) || (*commandline == '_'))
{
const char *cltemp = commandline;
err = Parse_CL_Option(commandline, obj, singleswitch);
if(err == kParseErr)
{
commandline = cltemp;
err = kFalseErr;
}
}
else
err = kFalseErr;
break;
}
// if nothing else was appropriate, assume it is some other kind of string requiring special attention
if(err == kFalseErr)
{
int chr = *commandline;
char *plainstring = NULL;
if((chr == '\"') || (chr == '\''))
{
commandline++;
plainstring = Parse_CL_String(commandline, chr);
}
// if there were no quotes, just read up to the next space or newline
else if(singleswitch == false) // see if quotes had been stripped outside POV-Ray
{
const char *oldcmdline = commandline;
plainstring = Parse_CL_String(commandline, -2);
// check if it is probably a standard INI file entry which wasn't recognised
if((plainstring != NULL) && (strchr(plainstring, '=') != NULL))
{
err = kParseErr;
commandline = oldcmdline; // so it will be printed
}
}
else
plainstring = Parse_CL_String(commandline, 0);
if(err == kFalseErr)
err = ProcessUnknownString(plainstring, obj);
if(plainstring != NULL)
delete[] plainstring;
}
}
// all errors here are non-fatal, the calling code has to decide if an error is fatal
if(err != kNoErr)
{
if(*commandline != 0)
{
ParseError("Cannot process command-line at '%s' due to a parse error.\n"
"This is not a valid command-line. Check the command-line for syntax errors, correct them, and try again.\n"
"Valid command-line switches are explained in detail in the reference part of the documentation.",
commandline);
}
else
{
ParseError("Cannot process command-line due to a parse error.\n"
"This is not a valid command-line. Check the command-line for syntax errors, correct them, and try again.\n"
"Valid command-line switches are explained in detail in the reference part of the documentation.");
}
}
return err;
}
int ProcessOptions::WriteFile(OTextStream *ini_file, POVMSObjectPtr obj)
{
struct INI_Parser_Table *table = parse_ini_table;
// find the keyword
while(table->keyword != NULL)
{
if(WriteOptionFilter(table) == true)
Output_INI_Option(table, obj, ini_file);
table++;
}
return kNoErr;
}
int ProcessOptions::WriteFile(const char *filename, POVMSObjectPtr obj)
{
OTextStream *ini_file;
int err = kNoErr;
if(!POV_ALLOW_FILE_WRITE(filename, POV_File_Text_INI))
return kCannotOpenFileErr;
ini_file = OpenFileForWrite(filename, obj);
if(ini_file == NULL)
return kCannotOpenFileErr;
err = WriteFile (ini_file, obj);
delete ini_file;
return err;
}
bool ProcessOptions::IsTrue(const char *value)
{
return (Matches("on",value) || Matches("true",value) ||
Matches("yes",value) || Matches("1",value));
}
bool ProcessOptions::IsFalse(const char *value)
{
return (Matches("off",value) || Matches("false",value) ||
Matches("no",value) || Matches("0",value));
}
bool ProcessOptions::Matches(const char *v1, const char *v2)
{
int i = 0;
int ans = 1;
while((ans) && (v1[i] != 0) && (v2[i] != 0))
{
ans = ans && (int)(v1[i] == tolower(v2[i]));
i++;
}
return (ans != 0);
}
int ProcessOptions::POVMSAttr_SetUTF8String(POVMSAttributePtr attr, POVMSType type, const char *s)
{
UCS2 *ustr = new UCS2[strlen(s) + 1];
size_t len = ConvertUTF8ToUCS2(s, ustr);
int err = POVMSAttr_Set(attr, type, (void *)ustr, (int(len) * 2) + 2);
delete[] ustr;
return err;
}
int ProcessOptions::POVMSAttr_GetUTF8String(POVMSAttributePtr attr, POVMSType type, char *s, int *maxdatasize)
{
int ulen = 0;
int err = POVMSAttr_Size(attr, &ulen);
UCS2 *ustr = new UCS2[ulen / 2 + 1];
if(err == kNoErr)
err = POVMSAttr_Get(attr, type, (void *)ustr, &ulen);
if(err == kNoErr)
*maxdatasize = ConvertUCS2ToUTF8(ustr, s);
else
*maxdatasize = 0;
delete[] ustr;
return err;
}
int ProcessOptions::POVMSUtil_SetUTF8String(POVMSObjectPtr object, POVMSType key, const char *s)
{
UCS2 *ustr = new UCS2[strlen(s) + 1];
size_t len = ConvertUTF8ToUCS2(s, ustr);
int err = POVMSUtil_SetUCS2String(object, key, ustr);
delete[] ustr;
return err;
}
const unsigned char gUTF8SequenceArray[256] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5
};
const unsigned int gUTF8Offsets[6] =
{
0x00000000UL,
0x00003080UL,
0x000E2080UL,
0x03C82080UL,
0xFA082080UL,
0x82082080UL
};
// Note that this is duplicate code, but cleaning up all the string handling is beyond the scope of POV-Ray 3.7 :-( [trf]
size_t ProcessOptions::ConvertUTF8ToUCS2(const char *text_array, UCS2 *char_array)
{
UCS4 chr;
int i, j, k, seqlen;
int text_array_size = strlen(text_array);
for(i = 0, k = 0; i < text_array_size; k++, i++)
{
seqlen = gUTF8SequenceArray[text_array[i]];
chr = 0;
for(j = seqlen; j > 0; j--)
{
chr += text_array[i];
chr <<= 6;
i++;
}
chr += text_array[i];
chr -= gUTF8Offsets[seqlen];
if(chr <= 0x0000FFFFUL)
char_array[k] = chr;
else
char_array[k] = 0x0000FFFDUL;
}
char_array[k] = 0;
return k;
}
size_t ProcessOptions::ConvertUCS2ToUTF8(const UCS2 *char_array, char *text_array)
{
int i, k, len;
unsigned int chr;
for(len = 0, i = 0; char_array[i] != 0; i++)
{
if(char_array[i] < 0x00000080)
len += 1;
else if(char_array[i] < 0x00000800)
len += 2;
else
len += 3;
}
for(i = 0, k = 0; (char_array[i] != 0) && (k < len); i++)
{
chr = char_array[i];
if(chr < 0x00000080)
{
text_array[k] = char(chr);
k++;
}
else if (chr < 0x00000800)
{
text_array[k] = ((chr >> 6) | 0xC0);
k++;
text_array[k] = ((chr | 0x80) & 0xBF);
k++;
}
else
{
if(chr >= 0x00010000)
chr = (unsigned int)0x0000FFFDUL;
text_array[k] = ((chr >> 12) | 0xE0);
k++;
text_array[k] = (((chr >> 6) | 0x80) & 0xBF);
k++;
text_array[k] = ((chr | 0x80) & 0xBF);
k++;
}
}
text_array[k] = 0;
return size_t(len);
}
int ProcessOptions::ReadSpecialOptionHandler(INI_Parser_Table *, char *, POVMSObjectPtr)
{
// do nothing by default
return kNoErr;
}
int ProcessOptions::ReadSpecialSwitchHandler(Cmd_Parser_Table *, char *, POVMSObjectPtr, bool)
{
// do nothing by default
return kNoErr;
}
int ProcessOptions::WriteSpecialOptionHandler(INI_Parser_Table *, POVMSObjectPtr, OTextStream *)
{
// do nothing by default
return kNoErr;
}
bool ProcessOptions::WriteOptionFilter(INI_Parser_Table *)
{
// filter nothing out by default
return true;
}
bool ProcessOptions::ProcessUnknownSwitch(char *, char *, POVMSObjectPtr)
{
// accept no unknown switches by default
return false;
}
int ProcessOptions::ProcessUnknownString(char *, POVMSObjectPtr)
{
// do nothing by default
return kNoErr;
}
void ProcessOptions::ParseError(const char *format, ...)
{
va_list marker;
char error_buffer[1024];
va_start(marker, format);
vsnprintf(error_buffer, 1023, format, marker);
va_end(marker);
fprintf(stderr, "%s\n", error_buffer);
}
void ProcessOptions::ParseErrorAt(ITextStream *file, const char *format, ...)
{
va_list marker;
char error_buffer[1024];
va_start(marker, format);
vsnprintf(error_buffer, 1023, format, marker);
va_end(marker);
fprintf(stderr, "%s\nFile '%s' at line '%u'", error_buffer, UCS2toASCIIString(file->name()).c_str(), (unsigned int) file->line());
}
void ProcessOptions::WriteError(const char *format, ...)
{
va_list marker;
char error_buffer[1024];
va_start(marker, format);
vsnprintf(error_buffer, 1023, format, marker);
va_end(marker);
fprintf(stderr, "%s\n", error_buffer);
}
int ProcessOptions::Output_INI_Option(INI_Parser_Table *option, POVMSObjectPtr obj, OTextStream *file)
{
POVMSFloat floatval;
POVMSBool b;
POVMSInt intval;
int err = 0;
int l;
POVMSAttribute item;
char *bufptr;
switch(option->type)
{
case kPOVMSType_Int:
if(POVMSUtil_GetInt(obj, option->key, &intval) == 0)
file->printf("%s=%d\n", option->keyword, (int)intval);
break;
case kPOVMSType_Float:
if(POVMSUtil_GetFloat(obj, option->key, &floatval) == 0)
file->printf("%s=%g\n", option->keyword, (float)floatval);
break;
case kPOVMSType_Bool:
if(POVMSUtil_GetBool(obj, option->key, &b) == 0)
{
if(b == true)
file->printf("%s=On\n", option->keyword);
else
file->printf("%s=Off\n", option->keyword);
}
break;
case kPOVMSType_CString:
err = POVMSObject_Get(obj, &item, option->key);
if(err != 0)
break;
// get the file name and path string
l = 0;
err = POVMSAttr_Size(&item, &l);
if(l > 0)
{
bufptr = new char[l];
bufptr[0] = 0;
if(POVMSAttr_Get(&item, kPOVMSType_CString, bufptr, &l) == 0)
file->printf("%s=\"%s\"\n", option->keyword, bufptr);
delete[] bufptr;
}
(void)POVMSAttr_Delete(&item);
break;
case kPOVMSType_UCS2String:
err = POVMSObject_Get(obj, &item, option->key);
if(err != 0)
break;
// get the file name and path string
l = 0;
err = POVMSAttr_Size(&item, &l);
if(l > 0)
{
bufptr = new char[l * 3];
bufptr[0] = 0;
if(POVMSAttr_GetUTF8String(&item, kPOVMSType_UCS2String, bufptr, &l) == 0)
file->printf("%s=\"%s\"\n", option->keyword, bufptr);
delete[] bufptr;
}
(void)POVMSAttr_Delete(&item);
break;
case kPOVMSType_WildCard:
WriteSpecialOptionHandler(option, obj, file);
break;
case 0:
// deprecated option, skip
break;
default:
WriteError("Ignoring unknown INI option.");
break;
}
return err;
}
int ProcessOptions::Parse_INI_Specification(const char *filespec, char *&filename, char *&sectionname)
{
const char *sectionpos = strchr(filespec, '[');
// if there is no section string, this is the whole filename
if(sectionpos == NULL)
{
filename = new char[strlen(filespec) + 1];
strcpy(filename, filespec);
}
// if there is a section string, the filename ends at the section beginning
else
{
const char *sectionend = strchr(filespec, ']');
// if there was no section end, this file specification is invalid
if(sectionend == NULL)
return kParamErr;
// if there valid section specification, use it
else
{
// section string starts at sectionpos + 1 and has the length sectionend - sectionpos - 1 + terminating zero
sectionname = new char[sectionend - sectionpos];
strncpy(sectionname, sectionpos + 1, sectionend - sectionpos - 1);
sectionname[sectionend - sectionpos - 1] = 0;
// filename string ends at sectionpos and the the length sectionpos - filespec + terminating zero
filename = new char[sectionpos - filespec + 1];
strncpy(filename, filespec, sectionpos - filespec);
filename[sectionpos - filespec] = 0;
}
}
return kNoErr;
}
int ProcessOptions::Parse_INI_Skip_Space(ITextStream *file, bool countnewlines)
{
int linecount = 0;
// read until the end of file or until a non-space is found
while(file->eof() == false)
{
int chr = file->getchar();
// count newlinegets
if((chr == 10) && (countnewlines == true))
{
int c2 = file->getchar();
if(c2 != 13)
file->ungetchar(c2);
linecount++;
}
else if((chr == 13) && (countnewlines == true))
{
int c2 = file->getchar();
if(c2 != 10)
file->ungetchar(c2);
linecount++;
}
// apart from a newline, spaces and tabs are considered "space"
else if((chr != ' ') && (chr != '\t'))
{
file->ungetchar(chr);
break;
}
}
return linecount;
}
int ProcessOptions::Parse_INI_Skip_Line(ITextStream *file)
{
int linecount = 0;
// read until the end of file or until a newline is found
while(file->eof() == false)
{
int chr = file->getchar();
// count newlines
if(chr == 10)
{
int c2 = file->getchar();
if(c2 != 13)
file->ungetchar(c2);
linecount++;
break;
}
else if(chr == 13)
{
int c2 = file->getchar();
if(c2 != 10)
file->ungetchar(c2);
linecount++;
break;
}
}
return linecount;
}
int ProcessOptions::Parse_INI_Option(ITextStream *file, POVMSObjectPtr obj)
{
struct INI_Parser_Table *table = parse_ini_table;
char *value = NULL;
char *key = NULL;
int chr = 0;
int err = kNoErr;
// read the key string
key = Parse_INI_String(file);
if(key == NULL)
{
ParseErrorAt(file, "Expected key in INI file, no key was found.");
return kParseErr;
}
// find the keyword
while(table->keyword != NULL)
{
if(pov_stricmp(table->keyword, key) == 0)
break;
table++;
}
// return if no valid keyword has been found
if(table->keyword == NULL)
{
ParseErrorAt(file, "Unknown key '%s' in INI file.", key);
delete[] key;
return kParseErr;
}
else
{
delete[] key;
key = NULL;
}
// skip any spaces
(void)Parse_INI_Skip_Space(file, false);
// expect the equal sign
if(file->getchar() != '=')
return kParseErr;
// skip any spaces
(void)Parse_INI_Skip_Space(file, false);
// TODO: whether or not to apply special parsing ought to be encoded into the INI_Parser_Table,
// but for now it's hard-coded here. special raw parsing is applied to INI options that we don't
// want to do much processing on (such as removing quotes or guessing if they are filenames).
// currently shellout commands are in that category since they may contain quoted paths or
// quoted/escaped options. We only apply this special case here, where we know the input data
// is a file (as it's also possible to specify INI options on the command-line).
if (toupper(table->keyword[strlen(table->keyword) - 1]) == 'D' && // the command and return keywords have the same kPOVAttrib_ values; we only want the command
(table->key == kPOVAttrib_PostFrameCommand || table->key == kPOVAttrib_PreFrameCommand ||
table->key == kPOVAttrib_PostSceneCommand || table->key == kPOVAttrib_PreSceneCommand ||
table->key == kPOVAttrib_UserAbortCommand || table->key == kPOVAttrib_FatalErrorCommand))
{
value = Parse_Raw_INI_String(file);
}
else
{
// if it looks like a quoted string, attempt to extract and handle that
chr = file->getchar();
if((chr == '\"') || (chr == '\''))
value = Parse_INI_String(file, chr);
// if there were no quotes, just read up to the next space or newline
else
{
file->ungetchar(chr);
value = Parse_INI_String(file, -2, true);
}
}
if(value == NULL)
{
ParseErrorAt(file, "Expected value in INI file, no value was found.");
return kParseErr;
}
if(table->key == 0)
ParseErrorAt(file, "INI option '%s' is no longer used and will result in an error in future versions of POV-Ray.", table->keyword);
else
err = Process_INI_Option(table, value, obj);
delete[] value;
value = NULL;
// skip any spaces
(void)Parse_INI_Skip_Space(file, false);
// if there is a comma, parse more values and append them
chr = file->getchar();
if(chr == ',')
{
// read more value strings
while((file->eof() == false) && (err == kNoErr))
{
// skip any spaces
(void)Parse_INI_Skip_Space(file, false);
// if the string is quoted, parse it matching quotes
chr = file->getchar();
if((chr == '\"') || (chr == '\''))
value = Parse_INI_String(file, chr);
// if there were no quotes, just read up to the next space or newline
else
{
file->ungetchar(chr);
value = Parse_INI_String(file, -2);
}
if(value == NULL)
{
ParseErrorAt(file, "Expected value in INI file, no value was found.");
return kParseErr;
}
err = Process_INI_Option(table, value, obj);
delete[] value;
value = NULL;
// skip any spaces
(void)Parse_INI_Skip_Space(file, false);
// if there is no other comma, stop parsing values
chr = file->getchar();
if(chr != ',')
{
file->ungetchar(chr);
break;
}
}
}
else
file->ungetchar(chr);
return err;
}
int ProcessOptions::Parse_INI_Switch(ITextStream *file, int token, POVMSObjectPtr obj)
{
struct Cmd_Parser_Table *table = parse_cmd_table;
char *value = NULL;
char *key = NULL;
int err = kNoErr;
int chr = 0;
// read the switch string
key = Parse_INI_String(file);
if(key == NULL)
{
ParseErrorAt(file, "Expected command-line switch in INI file, no command-line switch was found.");
err = kParseErr;
}
else
{
// if there is a quoted string directory following the switch, parse it matching quotes
chr = file->getchar();
if((chr == '\"') || (chr == '\''))
{
value = Parse_INI_String(file, chr);
if(value == NULL)
ParseErrorAt(file, "Expected command-line switch in INI file to be followed by quoted parameter.");
}
else
file->ungetchar(chr);
// find the command-line switch
while(table->command != NULL)
{
char *srcptr = key;
const char *dstptr = table->command;
// compared ignoring case until the end of either string has been reached
while((toupper(*srcptr) == toupper(*dstptr)) && (*srcptr != 0) && (*dstptr != 0))
{
srcptr++;
dstptr++;
}
// if the end of the switch string in the table had been reached, see if there are parameters
// to consider and if there are, expect the source string to be followed by those parameters
if((*dstptr) == 0)
{
// if there was a quoted value string and the switch string is longer, this is an unknown switch
if((value != NULL) && (*srcptr != 0))
{
table = NULL;
break;
}
// if there was a quoted value string and the switch matches, use the value string as parameter
else if((value != NULL) && (*srcptr == 0))
srcptr = value;
// only if a paremeter is expected allow it, and vice versa
if(((*srcptr > ' ') && (table->type != kPOVMSType_Null)) || ((*srcptr <= ' ') && (table->type == kPOVMSType_Null)))
{
err = Process_Switch(table, srcptr, obj, (token != '-'));
break;
}
}
table++;
}
// if there was no sucessful match so far, see if it is a system specific switch
if((table == NULL) || (table->command == NULL))
{
if(ProcessUnknownSwitch(key, value, obj) == false)
{
if(value != NULL)
ParseErrorAt(file, "Unknown switch '%s' with value '%s' in INI file.", key, value);
else
ParseErrorAt(file, "Unknown switch '%s' in INI file.", key);
err = kParseErr;
}
else
err = kNoErr;
}
}
if(key != NULL)
delete[] key;
if(value != NULL)
delete[] value;
return err;
}
char *ProcessOptions::Parse_INI_String(ITextStream *file, int endchr, bool smartmode)
{
char *str = new char[65536];
char *pos = str;
while((pos - str) < 65535)
{
int chr = file->getchar();
// terminate the string if the end of file has been reached
if(chr == EOF)
break;
// parse to the next space or special token
else if((endchr == -1) || (endchr == -2))
{
if((smartmode == true) && ((chr == ' ') || (chr == '\t')))
{
// In "smart mode" the function called below tries to detect if the
// user is trying to use an unquoted path as value of an INI option.
// The detection logic is detailed in the function below!
file->ungetchar(chr);
if(Parse_INI_String_Smartmode(file) == false)
break;
else
{
chr = file->getchar();
endchr = -3; // switch to special mode
}
}
else if(isspace(chr) || (chr == ',') || (chr == ';') || (chr == '#') || (chr == '\"') || (chr == '\'') ||
((endchr == -1) && ((chr == '[') || (chr == ']') || (chr == '='))))
{
file->ungetchar(chr);
break;
}
}
// this should only be switched on by "smart mode" and parses to either the end of the line or the comment string
else if(endchr == -3)
{
if((chr == ';') || (chr == '#') || (chr == 10) || (chr == 13))
{
file->ungetchar(chr);
break;
}
}
// parse to the next character specified by the caller in endchr
else if(chr == endchr)
break;
*pos = chr;
pos++;
}
*pos = 0;
return str;
}
// special case of parsing an INI string: we don't want to interpret anything other than
// a comment character or end-of-line, as the string itself will be passed on to other
// code that requires it more or less verbatim. an example of this is the shellout commands,
// which may contain a quoted program name (perhaps because it embeds spaces) followed by
// unquoted or quoted parameters.
// NB returned string may have trailing blanks.
char *ProcessOptions::Parse_Raw_INI_String(ITextStream *file)
{
char *str = new char[65536];
char *pos = str;
bool inSQ = false;
bool inDQ = false;
bool hadEscape = false;
const char *msg = "Possible unbalanced quotes detected in INI option being parsed in raw mode";
str[0] = 0;
for (int i = 0; i < 65536; i++, *pos = 0)
{
int ch = file->getchar();
if (hadEscape && (ch == '"' || ch == '\'' || ch == '\\'))
{
hadEscape = false;
*pos++ = ch;
continue;
}
hadEscape = false;
switch (ch)
{
case EOF:
if (inSQ || inDQ)
ParseErrorAt(file, msg);
return str;
case '\r':
case '\n':
file->ungetchar(ch);
if (inSQ || inDQ)
ParseErrorAt(file, msg);
return str;
case ';':
case '#':
if (!inSQ && !inDQ)
{
file->ungetchar(ch);
return str;
}
break;
case '\\':
hadEscape = true;
break;
case '"':
inDQ = !inDQ;
break;
case '\'':
inSQ = !inSQ;
break ;
default:
break;
}
*pos++ = ch;
}
if (inSQ || inDQ)
ParseErrorAt(file, msg);
return str;
}
bool ProcessOptions::Parse_INI_String_Smartmode(ITextStream *file)
{
ITextStream::FilePos backtrackpos = file->tellg();
bool result = false; // false - end string here, true - continue parsing string
struct INI_Parser_Table *table = parse_ini_table;
char *key = NULL;
(void)Parse_INI_Skip_Space(file, false);
switch(file->getchar())
{
// end of file
case EOF:
break; // return false, parsing more of the string simply is not possible
// INI file comment
case ';':
case '#':
break; // return false, this is a comment which terminates the string
// INI value list separator
case ',':
break; // return false, this is a value list of unquoted strings
// POV-Ray-style INI file with command-line switch
case '+':
case '-':
// POV-Ray-style INI file with system specific command-line switch on some systems (i.e. Windos)
#if(FILENAME_SEPARATOR != '/')
case '/':
#endif
if(isalpha(file->getchar()))
break; // return false, this is most likely a command-line
else
file->seekg(backtrackpos); // most likely an unquoted string, so allow parsing it as a whole
// INI file option
default:
// read the key string
key = Parse_INI_String(file);
if(key != NULL)
{
// find the keyword
while(table->keyword != NULL)
{
if(pov_stricmp(table->keyword, key) == 0)
break;
table++;
}
// if no valid keyword has been found
if(table->keyword == NULL)
{
result = true; // return true, this is most likely an unquoted path
ParseErrorAt(file,
"Most likely detected an unquoted string with spaces in INI file. Assuming string ends at the of the line.\n"
"Make sure all strings with spaces are properly quoted in the INI file.\n"
"Use either \" or \' to quote strings. For details, please check the user manual!");
}
delete[] key;
key = NULL;
}
break; // return false, unless the code above did not find a valid keyword
}
file->seekg(backtrackpos);
return result;
}
void ProcessOptions::Parse_CL_Skip_Space(const char *&commandline)
{
// read until the end of the string or until a non-space is found
while(*commandline != 0)
{
// spaces and tabs are considered "space"
if((*commandline != ' ') && (*commandline != '\t'))
break;
commandline++;
}
}
int ProcessOptions::Parse_CL_Switch(const char *&commandline, int token, POVMSObjectPtr obj, bool singleswitch)
{
struct Cmd_Parser_Table *table = parse_cmd_table;
char *value = NULL;
char *key = NULL;
int err = kNoErr;
int chr = 0;
// read the switch string
if(singleswitch == false) // see if quotes had been stripped outside POV-Ray
key = Parse_CL_String(commandline);
else
key = Parse_CL_String(commandline, 0);
if(key == NULL)
{
ParseError("Expected command-line switch on command-line, no command-line switch was found.");
err = kParseErr;
}
else
{
// if there is a quoted string directly following the switch, parse its matching quotes
chr = *commandline;
commandline++;
if((chr == '\"') || (chr == '\''))
{
value = Parse_CL_String(commandline, chr);
if(value == NULL)
ParseError("Expected command-line switch on command-line to be followed by quoted parameter.");
}
else
commandline--;
// find the command-line switch
while(table->command != NULL)
{
char *srcptr = key;
const char *dstptr = table->command;
// compared ignoring case until the end of either string has been reached
while((toupper(*srcptr) == toupper(*dstptr)) && (*srcptr != 0) && (*dstptr != 0))
{
srcptr++;
dstptr++;
}
// if the end of the switch string in the table had been reached, see if there are parameters
// to consider and if there are, expect the source string to be followed by those parameters
if((*dstptr) == 0)
{
// if there was a quoted value string and the switch string is longer, this is an unknown switch
if((value != NULL) && (*srcptr != 0))
{
table = NULL;
break;
}
// if there was a quoted value string and the switch matches, use the value string as parameter
else if((value != NULL) && (*srcptr == 0))
srcptr = value;
// only if a paremeter is expected allow it, and vice versa
if(((*srcptr > ' ') && (table->type != kPOVMSType_Null)) || ((*srcptr <= ' ') && (table->type == kPOVMSType_Null)))
{
err = Process_Switch(table, srcptr, obj, (token != '-'));
break;
}
}
table++;
}
// if there was no successful match so far, see if it is a system specific switch
if((table == NULL) || (table->command == NULL))
{
if(ProcessUnknownSwitch(key, value, obj) == false)
{
if(value != NULL)
ParseError("Unknown switch '%s' with value '%s' on command-line.", key, value);
else
ParseError("Unknown switch '%s' on command-line.", key);
err = kParseErr;
}
else
err = kNoErr;
}
}
if(key != NULL)
delete[] key;
if(value != NULL)
delete[] value;
return err;
}
int ProcessOptions::Parse_CL_Option(const char *&commandline, POVMSObjectPtr obj, bool singleswitch)
{
struct INI_Parser_Table *table = parse_ini_table;
char *value = NULL;
char *key = NULL;
char chr = 0;
int err = kNoErr;
// read the key string
key = Parse_CL_String(commandline);
if(key == NULL)
{
ParseError("Expected INI file key on command-line, no key was found.");
return kParseErr;
}
// find the keyword
while(table->keyword != NULL)
{
if(pov_stricmp(table->keyword, key) == 0)
break;
table++;
}
// return false if no valid keyword has been found
if(table->keyword == NULL)
{
delete[] key;
return kParseErr;
}
else
{
delete[] key;
key = NULL;
}
// expect the equal sign
if(*commandline != '=')
return kParseErr;
commandline++;
// if the string is quoted, parse it matching quotes
chr = *commandline;
if((chr == '\"') || (chr == '\''))
{
commandline++;
value = Parse_CL_String(commandline, chr);
}
// if there were no quotes, just read up to the next space or newline
else if(singleswitch == false) // see if quotes had been stripped outside POV-Ray
value = Parse_CL_String(commandline, -2);
else
value = Parse_CL_String(commandline, 0);
if(value == NULL)
{
ParseError("Expected value on command-line, no value was found.");
return kParseErr;
}
err = Process_INI_Option(table, value, obj);
delete[] value;
value = NULL;
return err;
}
char *ProcessOptions::Parse_CL_String(const char *&commandline, int endchr)
{
int maxlen = strlen(commandline) + 1;
char *str = new char[maxlen];
char *pos = str;
while(*commandline != 0)
{
int chr = *commandline;
if(endchr <= -1)
{
if(isspace(chr) || (chr == ';') || (chr == '#') || (chr == '\"') || (chr == '\''))
break;
else if((endchr == -1) && ((chr == '[') || (chr == ']') || (chr == '=')))
break;
}
commandline++;
if(chr == endchr)
break;
*pos = chr;
pos++;
}
*pos = 0;
return str;
}
int ProcessOptions::Process_INI_Option(INI_Parser_Table *option, char *param, POVMSObjectPtr obj)
{
double floatval = 0.0;
int intval = 0;
int err = kNoErr;
switch(option->type)
{
case kPOVMSType_Int:
if(sscanf(param, "%d", &intval) == 1)
err = POVMSUtil_SetInt(obj, option->key, intval);
else
{
ParseError("Integer parameter expected for option '%s', found '%s'.", option->keyword, param);
err = kParseErr;
}
break;
case kPOVMSType_Float:
if(sscanf(param, "%lf", &floatval) == 1)
err = POVMSUtil_SetFloat(obj, option->key, floatval);
else
{
ParseError("Floating-point parameter expected for option '%s', found '%s'.", option->keyword, param);
err = kParseErr;
}
break;
case kPOVMSType_Bool:
err = POVMSUtil_SetBool(obj, option->key, IsTrue(param));
break;
case kPOVMSType_CString:
err = POVMSUtil_SetString(obj, option->key, param);
break;
case kPOVMSType_UCS2String:
err = POVMSUtil_SetUTF8String(obj, option->key, param);
break;
case kPOVMSType_WildCard:
err = ReadSpecialOptionHandler(option, param, obj);
break;
default:
err = kParseErr;
break;
}
return err;
}
int ProcessOptions::Process_Switch(Cmd_Parser_Table *option, char *param, POVMSObjectPtr obj, bool is_on)
{
double floatval = 0.0;
int intval = 0;
int err = 0;
if(option->is_switch != kPOVMSType_Null)
{
err = POVMSUtil_SetBool(obj, option->is_switch, is_on);
if(err != kNoErr)
return err;
}
switch(option->type)
{
case kPOVMSType_Int:
if(sscanf(param, "%d", &intval) == 1)
err = POVMSUtil_SetInt(obj, option->key, intval);
else
{
ParseError("Integer parameter expected for switch '%s', found '%s'.", option->command, param);
err = kParseErr;
}
break;
case kPOVMSType_Float:
if(sscanf(param, "%lf", &floatval) == 1)
err = POVMSUtil_SetFloat(obj, option->key, floatval);
else
{
ParseError("Floating-point parameter expected for switch '%s', found '%s'.", option->command, param);
err = kParseErr;
}
break;
case kPOVMSType_Bool:
err = POVMSUtil_SetBool(obj, option->key, IsTrue(param));
break;
case kPOVMSType_CString:
err = POVMSUtil_SetString(obj, option->key, param);
break;
case kPOVMSType_UCS2String:
err = POVMSUtil_SetUTF8String(obj, option->key, param);
break;
case kPOVMSType_WildCard:
err = ReadSpecialSwitchHandler(option, param, obj, is_on);
break;
case kPOVMSType_Null:
break;
default:
err = kParseErr;
break;
}
return err;
}
}