IniFile::IniFile(const std::string& filename) { log_debug("read ini-file \"" << filename << '"'); std::ifstream in(filename.c_str()); if (!in) throw std::runtime_error("could not open file \"" + filename + '"'); IniFileEvent ev(*this); IniParser(ev).parse(in); }
namespace duct { const char* __ini_tokenName(const Token& token) { switch (token.getType()) { case NULL_TOKEN: return "NULLToken"; case StringToken: return "StringToken"; case QuotedStringToken: return "QuotedStringToken"; case NumberToken: return "NumberToken"; case DoubleToken: return "DoubleToken"; case EqualsToken: return "EqualsToken"; case NodeToken: return "NodeToken"; case CommentToken: return "CommentToken"; case EOFToken: return "EOFToken"; case EOLToken: return "EOLToken"; } return "UNKNOWNToken"; } // class IniParser implementation CharacterSet IniParser::_whitespaceset=CharacterSet("\t "); CharacterSet IniParser::_numberset=CharacterSet("0-9\\-+"); CharacterSet IniParser::_digitset=CharacterSet(".0-9\\-+"); IniParser::IniParser() : _handler(NULL) { reset(); } IniParser::IniParser(Stream* stream) : _handler(NULL) { initWithStream(stream); } IniParser::~IniParser() { reset(); } void IniParser::setHandler(ParserHandler* handler) { _handler=(IniParserHandler*)handler; } ParserHandler* IniParser::getHandler() { return _handler; } bool IniParser::parse() { //nextChar(); skipWhitespace(); nextToken(); readToken(); if (_curchar==CHAR_EOF) { _token.reset(EOFToken); _handler->handleToken(_token); // Just to make sure the EOF gets handled (data might not end with a newline, causing an EOFToken) return false; } else if (_token.getType()==EOFToken) { return false; } return true; } void IniParser::skipWhitespace() { while (_curchar!=CHAR_EOF && _whitespaceset.contains(_curchar)) nextChar(); } Token& IniParser::nextToken() { _token.reset(NULL_TOKEN); switch (_curchar) { case CHAR_QUOTE: _token.setType(QuotedStringToken); break; case CHAR_SEMICOLON: _token.setType(CommentToken); break; case CHAR_EOF: _token.setType(EOFToken); break; case CHAR_NEWLINE: _token.setType(EOLToken); break; case CHAR_DECIMALPOINT: _token.setType(DoubleToken); _token.addChar(_curchar); // Add the decimal break; case CHAR_EQUALSIGN: _token.setType(EqualsToken); break; case CHAR_OPENBRACKET: _token.setType(NodeToken); break; default: if (_numberset.contains(_curchar)) { _token.setType(NumberToken); } else { _token.setType(StringToken); } break; } _token.setPosition(_line, _column); return _token; } void IniParser::readToken() { //printf("(IniParser::readToken) token-type:%s line:%d, col:%d\n", __ini_tokenName(_token), _token.getLine(), _token.getColumn()); switch (_token.getType()) { case QuotedStringToken: readQuotedStringToken(); nextChar(); break; case StringToken: readStringToken(); break; case NumberToken: readNumberToken(); break; case DoubleToken: nextChar(); readDoubleToken(); break; case EqualsToken: nextChar(); break; case CommentToken: skipToEOL(); //nextChar(); // Bad to get the next char, as it could be the EOL needed to terminate the current identifier break; case NodeToken: readNodeToken(); nextChar(); break; case EOLToken: nextChar(); break; case EOFToken: // Do nothing break; default: throw IniParserException(PARSERERROR_PARSER, "IniParser::readToken", NULL, this, "Unhandled token: %s", __ini_tokenName(_token)); break; } _handler->handleToken(_token); } void IniParser::readNumberToken() { while (_curchar!=CHAR_EOF) { if (_curchar==CHAR_QUOTE) { throw IniParserException(PARSERERROR_PARSER, "IniParser::readNumberToken", &_token, this, "Unexpected quote"); } else if (_curchar==CHAR_NEWLINE || _whitespaceset.contains(_curchar) || _curchar==CHAR_SEMICOLON /*|| _curchar==CHAR_EQUALSIGN*/) { break; } else { if (_numberset.contains(_curchar)) { _token.addChar(_curchar); } else if (_curchar==CHAR_DECIMALPOINT) { _token.addChar(_curchar); nextChar(); _token.setType(DoubleToken); readDoubleToken(); return; } else { _token.setType(StringToken); readStringToken(); return; } } nextChar(); } } void IniParser::readDoubleToken() { while (_curchar!=CHAR_EOF) { if (_curchar==CHAR_QUOTE) { throw IniParserException(PARSERERROR_PARSER, "IniParser::readDoubleToken", &_token, this, "Unexpected quote"); } else if (_curchar==CHAR_NEWLINE || _whitespaceset.contains(_curchar) || _curchar==CHAR_SEMICOLON /*^^^|| _curchar==CHAR_EQUALSIGN*/) { break; } else { if (_numberset.contains(_curchar)) { _token.addChar(_curchar); } else { // (_curchar==CHAR_DECIMALPOINT) // The token should've already contained a decimal point, so it must be a string. _token.setType(StringToken); readStringToken(); return; } } nextChar(); } } void IniParser::readStringToken() { while (_curchar!=CHAR_EOF) { if (_curchar==CHAR_QUOTE) { throw IniParserException(PARSERERROR_PARSER, "IniParser::readStringToken", &_token, this, "Unexpected quote"); } else if (_curchar==CHAR_NEWLINE /*^^^|| _whitespaceset.contains(_curchar)*/ || _curchar==CHAR_SEMICOLON || _curchar==CHAR_EQUALSIGN) { break; } else { _token.addChar(_curchar); } nextChar(); } } void IniParser::readQuotedStringToken() { nextChar(); // Skip the first character (will be the initial quote) while (_curchar!=CHAR_QUOTE) { switch (_curchar) { case CHAR_EOF: throw IniParserException(PARSERERROR_PARSER, "IniParser::readQuotedStringToken", &_token, this, "Encountered EOF whilst reading quoted string"); break; case CHAR_NEWLINE: throw IniParserException(PARSERERROR_PARSER, "IniParser::readQuotedStringToken", &_token, this, "Unexpected EOL (expected quote)"); default: _token.addChar(_curchar); break; } nextChar(); } } void IniParser::readNodeToken() { nextChar(); // Skip initial bracket while (_curchar!=CHAR_EOF) { if (_curchar==CHAR_OPENBRACKET) { throw IniParserException(PARSERERROR_PARSER, "IniParser::readNodeToken", &_token, this, "Unexpected open bracket"); } else if (_curchar==CHAR_SEMICOLON) { throw IniParserException(PARSERERROR_PARSER, "IniParser::readNodeToken", &_token, this, "Unexpected semicolon"); } else if (_curchar==CHAR_NEWLINE) { throw IniParserException(PARSERERROR_PARSER, "IniParser::readNodeToken", &_token, this, "Unexpected end of line"); } else if (_curchar==CHAR_CLOSEBRACKET /*|| _whitespaceset.contains(_curchar)*/) { break; } else { _token.addChar(_curchar); } nextChar(); } } // class IniParserException implementation IniParserException::IniParserException(IniParserError error, const char* reporter, const Token* token, const IniParser* parser, const char* fmt, ...) { _error=error; _reporter=reporter; _token=token; _parser=parser; char temp[256]; va_list args; va_start(args, fmt); vsprintf(temp, fmt, args); va_end(args); temp[255]='\0'; if (_parser && !_token) { _token=&_parser->getToken(); } if (_token && _parser) { sprintf(_message, "(%s) [%s] from line: %d, col: %d to line: %d, col: %d: %s", _reporter, errorToString(_error), _token->getLine(), _token->getColumn(), _parser->getLine(), _parser->getColumn(), temp); } else if (_token) { sprintf(_message, "(%s) [%s] at line: %d, col: %d: %s", _reporter, errorToString(_error), _token->getLine(), _token->getColumn(), temp); } else if (_parser) { sprintf(_message, "(%s) [%s] at line: %d, col: %d: %s", _reporter, errorToString(_error), _parser->getLine(), _parser->getColumn(), temp); } else { sprintf(_message, "(%s) [%s]: %s", _reporter, errorToString(_error), temp); } _message[511]='\0'; } const char* IniParserException::what() const throw() { return _message; } const char* IniParserException::errorToString(IniParserError error) { switch (error) { case PARSERERROR_PARSER: return "ERROR_PARSER"; case PARSERERROR_HIERARCHY: return "ERROR_HIERARCHY"; case PARSERERROR_MEMALLOC: return "ERROR_MEMALLOC"; default: return "ERROR_UNKNOWN"; } } // class IniParserHandler implementation IniParserHandler::IniParserHandler(IniParser& parser) : _parser(parser), _equals(false), _rootnode(NULL), _currentnode(NULL) { _parser.setHandler(this); } void IniParserHandler::setParser(Parser& parser) { _parser=(IniParser&)parser; _parser.setHandler(this); } Parser& IniParserHandler::getParser() { return _parser; } void IniParserHandler::throwex(IniParserException e) { freeData(); throw e; } void IniParserHandler::clean() { _currentnode=NULL; _rootnode=NULL; _varname.remove(); _equals=false; } bool IniParserHandler::process() { _rootnode=new Node(NULL); _currentnode=_rootnode; while (_parser.parse()) { } finish(); return true; } Node* IniParserHandler::processFromStream(Stream* stream) { _parser.initWithStream(stream); process(); Node* node=_rootnode; // Store before cleaning clean(); _parser.reset(); return node; } void IniParserHandler::freeData() { if (_currentnode) { if (_rootnode==_currentnode || _currentnode->getParent()!=_rootnode) { // delete the root if the root and the current node are the same or if the current node has been parented delete _rootnode; } else if (_currentnode->getParent()==NULL) { // delete the root and the current node if the current node has not been parented delete _rootnode; delete _currentnode; } } else if (_rootnode) { delete _rootnode; } clean(); } void IniParserHandler::handleToken(Token& token) { switch (token.getType()) { case StringToken: case QuotedStringToken: { if (_varname.length()>0 && _equals) { if (token.getType()==StringToken) { int bv=Variable::stringToBool(token.toString()); if (bv!=-1) { addValueAndReset(new BoolVariable((bv==1) ? true : false, _varname)); return; } } addValueAndReset(new StringVariable(token.toString(), _varname)); } else if (_varname.length()>0) { throwex(IniParserException(PARSERERROR_PARSER, "IniParserHandler::handleToken", &token, &_parser, "Expected equals sign, got string")); } else { _varname.setTo(token.toString()).trim(); } } break; case NumberToken: if (_varname.length()>0 && _equals) { addValueAndReset(new IntVariable(token.toInt(), _varname)); } else { throwex(IniParserException(PARSERERROR_PARSER, "IniParserHandler::handleToken", &token, &_parser, "A number cannot be an identifier")); } break; case DoubleToken: if (_varname.length()>0 && _equals) { addValueAndReset(new FloatVariable(token.toFloat(), _varname)); } else { throwex(IniParserException(PARSERERROR_PARSER, "IniParserHandler::handleToken", &token, &_parser, "A number cannot be an identifier")); } break; case EqualsToken: if (_varname.length()==0) { throwex(IniParserException(PARSERERROR_PARSER, "IniParserHandler::handleToken", &token, &_parser, "Expected string, got equality sign")); } else if (_equals) { throwex(IniParserException(PARSERERROR_PARSER, "IniParserHandler::handleToken", &token, &_parser, "Expected value, got equality sign")); } else { _equals=true; } break; case NodeToken: { if (_varname.length()==0) { _varname.setTo(token.toString()).trim(); _currentnode=new Node(_varname, _rootnode); // Trim whitespace _varname.remove(); // clear the string _rootnode->add(_currentnode); } else { throwex(IniParserException(PARSERERROR_PARSER, "IniParserHandler::handleToken", &token, &_parser, "NodeToken: Unknown error. _varname length is>0")); } } break; case CommentToken: // Do nothing break; case EOLToken: case EOFToken: finish(); break; default: //printf("IniParserHandler::handleToken Unhandled token of type: %s\n", __ini_tokenName(token)); break; } } void IniParserHandler::finish() { if (_varname.length()>0 && _equals) { addValueAndReset(new StringVariable("", _varname)); } else if (_varname.length()>0) { throwex(IniParserException(PARSERERROR_PARSER, "IniParserHandler::finish", NULL, &_parser, "Expected equality sign, got EOL or EOF")); } } void IniParserHandler::reset() { _varname.remove(); _equals=false; } void IniParserHandler::addValueAndReset(ValueVariable* value) { _currentnode->add(value); reset(); } // class IniFormatter implementation IniParser IniFormatter::_parser=IniParser(); IniParserHandler IniFormatter::_handler=IniParserHandler(IniFormatter::_parser); bool IniFormatter::formatValue(const ValueVariable& value, UnicodeString& result, unsigned int nameformat, unsigned int varformat) { if (value.getName().length()>0) { value.getNameFormatted(result, nameformat); UnicodeString temp; value.getValueFormatted(temp, varformat); result.append('=').append(temp); return true; } else { result.remove(); // clear the result string debug_print("Value name is 0-length"); } return false; } Node* IniFormatter::loadFromFile(const char* path, const char* encoding) { Stream* stream=FileStream::readFile(path, encoding); if (stream) { Node* root=_handler.processFromStream(stream); stream->close(); delete stream; return root; } return NULL; } Node* IniFormatter::loadFromFile(const std::string& path, const char* encoding) { return loadFromFile(path.c_str(), encoding); } Node* IniFormatter::loadFromFile(const UnicodeString& path, const char* encoding) { std::string temp; path.toUTF8String(temp); return loadFromFile(temp.c_str(), encoding); } Node* IniFormatter::loadFromStream(Stream* stream) { if (stream) { return _handler.processFromStream(stream); } return NULL; } bool IniFormatter::writeToFile(const Node* root, const char* path, const char* encoding, unsigned int nameformat, unsigned int varformat) { Stream* stream=FileStream::writeFile(path, encoding); if (stream) { writeToStream(root, stream, 0, nameformat, varformat); stream->close(); return true; } return false; } bool IniFormatter::writeToFile(const Node* root, const std::string& path, const char* encoding, unsigned int nameformat, unsigned int varformat) { return writeToFile(root, path.c_str(), encoding, nameformat, varformat); } bool IniFormatter::writeToFile(const Node* root, const UnicodeString& path, const char* encoding, unsigned int nameformat, unsigned int varformat) { std::string temp; path.toUTF8String(temp); return writeToFile(root, temp.c_str(), encoding, nameformat, varformat); } bool IniFormatter::writeToStream(const Node* root, Stream* stream, unsigned int tcount, unsigned int nameformat, unsigned int varformat) { if (root && stream) { UnicodeString temp; if (root->getParent() && root->getName().length()>0) { // cheap way of saying the node is not a root node writeTabs(stream, tcount, false); root->getNameFormatted(temp, nameformat); stream->writeChar16('['); temp.append(']'); stream->writeLine(temp); } Node* node; ValueVariable* value; for (VarList::const_iterator iter=root->begin(); iter!=root->end(); ++iter) { value=dynamic_cast<ValueVariable*>(*iter); node=dynamic_cast<Node*>(*iter); if (node) { writeToStream(node, stream, tcount, nameformat, varformat); } else if (value) { if (formatValue(*value, temp, nameformat, varformat)) { writeTabs(stream, tcount, false); stream->writeLine(temp); } } } return true; } return false; } void IniFormatter::writeTabs(Stream* stream, unsigned int count, bool newline) { while (0<count--) { stream->writeChar16('\t'); } if (newline) stream->writeChar16('\n'); } } // namespace duct
IniFile::IniFile(std::istream& in) { IniFileEvent ev(*this); IniParser(ev).parse(in); }