// Copyright 2007-2011 Baptiste Lepilleur and The JsonCpp Authors // Copyright (C) 2016 InfoTeCS JSC. All rights reserved. // Distributed under MIT license, or public domain if desired and // recognized in your jurisdiction. // See file LICENSE for detail or copy at // http://jsoncpp.sourceforge.net/LICENSE #if !defined(JSON_IS_AMALGAMATION) # include # include # include # include "json_tool.h" #endif // if !defined(JSON_IS_AMALGAMATION) #include #include #include #include #include #include #include #include #include #if defined(_MSC_VER) # if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && \ _MSC_VER >= 1500 // VC++ 9.0 and above # define snprintf sprintf_s # elif _MSC_VER >= 1900 // VC++ 14.0 and above # define snprintf std::snprintf # else # define snprintf _snprintf # endif #elif defined(__ANDROID__) || defined(__QNXNTO__) # define snprintf snprintf #elif __cplusplus >= 201103L # if !defined(__MINGW32__) && !defined(__CYGWIN__) # define snprintf std::snprintf # endif #endif #if defined(__QNXNTO__) # define sscanf std::sscanf #endif #if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 // Disable warning about strdup being deprecated. # pragma warning(disable : 4996) #endif // Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile // time to change the stack limit #if !defined(JSONCPP_DEPRECATED_STACK_LIMIT) # define JSONCPP_DEPRECATED_STACK_LIMIT 1000 #endif static size_t const stackLimit_g = JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() namespace Json { #if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) typedef std::unique_ptr CharReaderPtr; #else typedef std::auto_ptr CharReaderPtr; #endif // Implementation of class Features // //////////////////////////////// Features::Features() : allowComments_(true) , strictRoot_(false) , allowDroppedNullPlaceholders_(false) , allowNumericKeys_(false) { } Features Features::all() { return Features(); } Features Features::strictMode() { Features features; features.allowComments_ = false; features.strictRoot_ = true; features.allowDroppedNullPlaceholders_ = false; features.allowNumericKeys_ = false; return features; } // Implementation of class Reader // //////////////////////////////// bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) { for (; begin < end; ++begin) if (*begin == '\n' || *begin == '\r') return true; return false; } // Class Reader // ////////////////////////////////////////////////////////////////// Reader::Reader() : errors_() , document_() , begin_() , end_() , current_() , lastValueEnd_() , lastValue_() , commentsBefore_() , features_(Features::all()) , collectComments_() { } Reader::Reader(const Features& features) : errors_() , document_() , begin_() , end_() , current_() , lastValueEnd_() , lastValue_() , commentsBefore_() , features_(features) , collectComments_() { } bool Reader::parse(const std::string& document, Value& root, bool collectComments) { this->document_.assign(document.begin(), document.end()); const char* begin = this->document_.c_str(); const char* end = begin + this->document_.length(); return this->parse(begin, end, root, collectComments); } bool Reader::parse(std::istream& sin, Value& root, bool collectComments) { // std::istream_iterator begin(sin); // std::istream_iterator end; // Those would allow streamed input from a file, if parse() were a // template function. // Since JSONCPP_STRING is reference-counted, this at least does not // create an extra copy. JSONCPP_STRING doc; std::getline(sin, doc, (char)EOF); return this->parse(doc.data(), doc.data() + doc.size(), root, collectComments); } bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments) { if (!this->features_.allowComments_) { collectComments = false; } this->begin_ = beginDoc; this->end_ = endDoc; this->collectComments_ = collectComments; this->current_ = this->begin_; this->lastValueEnd_ = 0; this->lastValue_ = 0; this->commentsBefore_.clear(); this->errors_.clear(); while (!this->nodes_.empty()) this->nodes_.pop(); this->nodes_.push(&root); bool successful = this->readValue(); Token token; this->skipCommentTokens(token); if (this->collectComments_ && !this->commentsBefore_.empty()) root.setComment(this->commentsBefore_, commentAfter); if (this->features_.strictRoot_) { if (!root.isArray() && !root.isObject()) { // Set error location to start of doc, ideally should be first token // found in doc token.type_ = tokenError; token.start_ = beginDoc; token.end_ = endDoc; this->addError( "A valid JSON document must be either an array or an object value.", token); return false; } } return successful; } bool Reader::readValue() { // readValue() may call itself only if it calls readObject() or ReadArray(). // These methods execute nodes_.push() just before and nodes_.pop)() just // after calling readValue(). parse() executes one nodes_.push(), so > // instead of >=. if (this->nodes_.size() > stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; this->skipCommentTokens(token); bool successful = true; if (this->collectComments_ && !this->commentsBefore_.empty()) { this->currentValue().setComment(this->commentsBefore_, commentBefore); this->commentsBefore_.clear(); } switch (token.type_) { case tokenObjectBegin: successful = this->readObject(token); this->currentValue().setOffsetLimit(this->current_ - this->begin_); break; case tokenArrayBegin: successful = this->readArray(token); this->currentValue().setOffsetLimit(this->current_ - this->begin_); break; case tokenNumber: successful = this->decodeNumber(token); break; case tokenString: successful = this->decodeString(token); break; case tokenTrue: { Value v(true); this->currentValue().swapPayload(v); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); } break; case tokenFalse: { Value v(false); this->currentValue().swapPayload(v); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); } break; case tokenNull: { Value v; this->currentValue().swapPayload(v); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); } break; case tokenArraySeparator: case tokenObjectEnd: case tokenArrayEnd: if (this->features_.allowDroppedNullPlaceholders_) { // "Un-read" the current token and mark the current value as a null // token. this->current_--; Value v; this->currentValue().swapPayload(v); this->currentValue().setOffsetStart(this->current_ - this->begin_ - 1); this->currentValue().setOffsetLimit(this->current_ - this->begin_); break; } // Else, fall through... default: this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); return this->addError("Syntax error: value, object or array expected.", token); } if (this->collectComments_) { this->lastValueEnd_ = this->current_; this->lastValue_ = &this->currentValue(); } return successful; } void Reader::skipCommentTokens(Token& token) { if (this->features_.allowComments_) { do { this->readToken(token); } while (token.type_ == tokenComment); } else { this->readToken(token); } } bool Reader::readToken(Token& token) { this->skipSpaces(); token.start_ = this->current_; Char c = this->getNextChar(); bool ok = true; switch (c) { case '{': token.type_ = tokenObjectBegin; break; case '}': token.type_ = tokenObjectEnd; break; case '[': token.type_ = tokenArrayBegin; break; case ']': token.type_ = tokenArrayEnd; break; case '"': token.type_ = tokenString; ok = this->readString(); break; case '/': token.type_ = tokenComment; ok = this->readComment(); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': token.type_ = tokenNumber; this->readNumber(); break; case 't': token.type_ = tokenTrue; ok = this->match("rue", 3); break; case 'f': token.type_ = tokenFalse; ok = this->match("alse", 4); break; case 'n': token.type_ = tokenNull; ok = this->match("ull", 3); break; case ',': token.type_ = tokenArraySeparator; break; case ':': token.type_ = tokenMemberSeparator; break; case 0: token.type_ = tokenEndOfStream; break; default: ok = false; break; } if (!ok) token.type_ = tokenError; token.end_ = this->current_; return true; } void Reader::skipSpaces() { while (this->current_ != this->end_) { Char c = *this->current_; if (c == ' ' || c == '\t' || c == '\r' || c == '\n') ++this->current_; else break; } } bool Reader::match(Location pattern, int patternLength) { if (this->end_ - this->current_ < patternLength) return false; int index = patternLength; while (index--) if (this->current_[index] != pattern[index]) return false; this->current_ += patternLength; return true; } bool Reader::readComment() { Location commentBegin = this->current_ - 1; Char c = this->getNextChar(); bool successful = false; if (c == '*') successful = this->readCStyleComment(); else if (c == '/') successful = this->readCppStyleComment(); if (!successful) return false; if (this->collectComments_) { CommentPlacement placement = commentBefore; if (this->lastValueEnd_ && !containsNewLine(this->lastValueEnd_, commentBegin)) { if (c != '*' || !containsNewLine(commentBegin, this->current_)) placement = commentAfterOnSameLine; } this->addComment(commentBegin, this->current_, placement); } return true; } JSONCPP_STRING Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { JSONCPP_STRING normalized; normalized.reserve(static_cast(end - begin)); Reader::Location current = begin; while (current != end) { char c = *current++; if (c == '\r') { if (current != end && *current == '\n') // convert dos EOL ++current; // convert Mac EOL normalized += '\n'; } else { normalized += c; } } return normalized; } void Reader::addComment(Location begin, Location end, CommentPlacement placement) { assert(this->collectComments_); const JSONCPP_STRING& normalized = normalizeEOL(begin, end); if (placement == commentAfterOnSameLine) { assert(this->lastValue_ != 0); this->lastValue_->setComment(normalized, placement); } else { this->commentsBefore_ += normalized; } } bool Reader::readCStyleComment() { while ((this->current_ + 1) < this->end_) { Char c = this->getNextChar(); if (c == '*' && *this->current_ == '/') break; } return this->getNextChar() == '/'; } bool Reader::readCppStyleComment() { while (this->current_ != this->end_) { Char c = this->getNextChar(); if (c == '\n') break; if (c == '\r') { // Consume DOS EOL. It will be normalized in addComment. if (this->current_ != this->end_ && *this->current_ == '\n') this->getNextChar(); // Break on Moc OS 9 EOL. break; } } return true; } void Reader::readNumber() { const char* p = this->current_; char c = '0'; // stopgap for already consumed character // integral part while (c >= '0' && c <= '9') c = (this->current_ = p) < this->end_ ? *p++ : '\0'; // fractional part if (c == '.') { c = (this->current_ = p) < this->end_ ? *p++ : '\0'; while (c >= '0' && c <= '9') c = (this->current_ = p) < this->end_ ? *p++ : '\0'; } // exponential part if (c == 'e' || c == 'E') { c = (this->current_ = p) < this->end_ ? *p++ : '\0'; if (c == '+' || c == '-') c = (this->current_ = p) < this->end_ ? *p++ : '\0'; while (c >= '0' && c <= '9') c = (this->current_ = p) < this->end_ ? *p++ : '\0'; } } bool Reader::readString() { Char c = '\0'; while (this->current_ != this->end_) { c = this->getNextChar(); if (c == '\\') this->getNextChar(); else if (c == '"') break; } return c == '"'; } bool Reader::readObject(Token& tokenStart) { Token tokenName; JSONCPP_STRING name; Value init(objectValue); this->currentValue().swapPayload(init); this->currentValue().setOffsetStart(tokenStart.start_ - this->begin_); while (this->readToken(tokenName)) { bool initialTokenOk = true; while (tokenName.type_ == tokenComment && initialTokenOk) initialTokenOk = this->readToken(tokenName); if (!initialTokenOk) break; if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object return true; name.clear(); if (tokenName.type_ == tokenString) { if (!this->decodeString(tokenName, name)) return this->recoverFromError(tokenObjectEnd); } else if (tokenName.type_ == tokenNumber && this->features_.allowNumericKeys_) { Value numberName; if (!this->decodeNumber(tokenName, numberName)) return this->recoverFromError(tokenObjectEnd); name = JSONCPP_STRING(numberName.asCString()); } else { break; } Token colon; if (!this->readToken(colon) || colon.type_ != tokenMemberSeparator) { return this->addErrorAndRecover("Missing ':' after object member name", colon, tokenObjectEnd); } Value& value = this->currentValue()[name]; this->nodes_.push(&value); bool ok = this->readValue(); this->nodes_.pop(); if (!ok) // error already set return this->recoverFromError(tokenObjectEnd); Token comma; if (!this->readToken(comma) || (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && comma.type_ != tokenComment)) { return this->addErrorAndRecover( "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); } bool finalizeTokenOk = true; while (comma.type_ == tokenComment && finalizeTokenOk) finalizeTokenOk = this->readToken(comma); if (comma.type_ == tokenObjectEnd) return true; } return this->addErrorAndRecover("Missing '}' or object member name", tokenName, tokenObjectEnd); } bool Reader::readArray(Token& tokenStart) { Value init(arrayValue); this->currentValue().swapPayload(init); this->currentValue().setOffsetStart(tokenStart.start_ - this->begin_); this->skipSpaces(); if (this->current_ != this->end_ && *this->current_ == ']') // empty array { Token endArray; this->readToken(endArray); return true; } int index = 0; for (;;) { Value& value = this->currentValue()[index++]; this->nodes_.push(&value); bool ok = this->readValue(); this->nodes_.pop(); if (!ok) // error already set return this->recoverFromError(tokenArrayEnd); Token token; // Accept Comment after last item in the array. ok = this->readToken(token); while (token.type_ == tokenComment && ok) { ok = this->readToken(token); } bool badTokenType = (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); if (!ok || badTokenType) { return this->addErrorAndRecover( "Missing ',' or ']' in array declaration", token, tokenArrayEnd); } if (token.type_ == tokenArrayEnd) break; } return true; } bool Reader::decodeNumber(Token& token) { Value decoded; if (!this->decodeNumber(token, decoded)) return false; this->currentValue().swapPayload(decoded); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); return true; } bool Reader::decodeNumber(Token& token, Value& decoded) { // Attempts to parse the number as an integer. If the number is // larger than the maximum supported value of an integer then // we decode the number as a double. Location current = token.start_; bool isNegative = *current == '-'; if (isNegative) ++current; // TODO: Help the compiler do the div and mod at compile time or get rid of // them. Value::LargestUInt maxIntegerValue = isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 : Value::maxLargestUInt; Value::LargestUInt threshold = maxIntegerValue / 10; Value::LargestUInt value = 0; while (current < token.end_) { Char c = *current++; if (c < '0' || c > '9') return this->decodeDouble(token, decoded); Value::UInt digit(static_cast(c - '0')); if (value >= threshold) { // We've hit or exceeded the max value divided by 10 (rounded down). If // a) we've only just touched the limit, b) this is the last digit, and // c) it's small enough to fit in that rounding delta, we're okay. // Otherwise treat this number as a double to avoid overflow. if (value > threshold || current != token.end_ || digit > maxIntegerValue % 10) { return this->decodeDouble(token, decoded); } } value = value * 10 + digit; } if (isNegative && value == maxIntegerValue) decoded = Value::minLargestInt; else if (isNegative) decoded = -Value::LargestInt(value); else if (value <= Value::LargestUInt(Value::maxInt)) decoded = Value::LargestInt(value); else decoded = value; return true; } bool Reader::decodeDouble(Token& token) { Value decoded; if (!this->decodeDouble(token, decoded)) return false; this->currentValue().swapPayload(decoded); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); return true; } bool Reader::decodeDouble(Token& token, Value& decoded) { double value = 0; JSONCPP_STRING buffer(token.start_, token.end_); JSONCPP_ISTRINGSTREAM is(buffer); if (!(is >> value)) return this->addError("'" + JSONCPP_STRING(token.start_, token.end_) + "' is not a number.", token); decoded = value; return true; } bool Reader::decodeString(Token& token) { JSONCPP_STRING decoded_string; if (!this->decodeString(token, decoded_string)) return false; Value decoded(decoded_string); this->currentValue().swapPayload(decoded); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); return true; } bool Reader::decodeString(Token& token, JSONCPP_STRING& decoded) { decoded.reserve(static_cast(token.end_ - token.start_ - 2)); Location current = token.start_ + 1; // skip '"' Location end = token.end_ - 1; // do not include '"' while (current != end) { Char c = *current++; if (c == '"') break; else if (c == '\\') { if (current == end) return this->addError("Empty escape sequence in string", token, current); Char escape = *current++; switch (escape) { case '"': decoded += '"'; break; case '/': decoded += '/'; break; case '\\': decoded += '\\'; break; case 'b': decoded += '\b'; break; case 'f': decoded += '\f'; break; case 'n': decoded += '\n'; break; case 'r': decoded += '\r'; break; case 't': decoded += '\t'; break; case 'u': { unsigned int unicode; if (!this->decodeUnicodeCodePoint(token, current, end, unicode)) return false; decoded += codePointToUTF8(unicode); } break; default: return this->addError("Bad escape sequence in string", token, current); } } else { decoded += c; } } return true; } bool Reader::decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode) { if (!this->decodeUnicodeEscapeSequence(token, current, end, unicode)) return false; if (unicode >= 0xD800 && unicode <= 0xDBFF) { // surrogate pairs if (end - current < 6) return this->addError( "additional six characters expected to parse unicode surrogate pair.", token, current); unsigned int surrogatePair; if (*(current++) == '\\' && *(current++) == 'u') { if (this->decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); } else return false; } else return this->addError( "expecting another \\u token to begin the second half of " "a unicode surrogate pair", token, current); } return true; } bool Reader::decodeUnicodeEscapeSequence(Token& token, Location& current, Location end, unsigned int& ret_unicode) { if (end - current < 4) return this->addError( "Bad unicode escape sequence in string: four digits expected.", token, current); int unicode = 0; for (int index = 0; index < 4; ++index) { Char c = *current++; unicode *= 16; if (c >= '0' && c <= '9') unicode += c - '0'; else if (c >= 'a' && c <= 'f') unicode += c - 'a' + 10; else if (c >= 'A' && c <= 'F') unicode += c - 'A' + 10; else return this->addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", token, current); } ret_unicode = static_cast(unicode); return true; } bool Reader::addError(const JSONCPP_STRING& message, Token& token, Location extra) { ErrorInfo info; info.token_ = token; info.message_ = message; info.extra_ = extra; this->errors_.push_back(info); return false; } bool Reader::recoverFromError(TokenType skipUntilToken) { size_t const errorCount = this->errors_.size(); Token skip; for (;;) { if (!this->readToken(skip)) this->errors_.resize(errorCount); // discard errors caused by recovery if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) break; } this->errors_.resize(errorCount); return false; } bool Reader::addErrorAndRecover(const JSONCPP_STRING& message, Token& token, TokenType skipUntilToken) { this->addError(message, token); return this->recoverFromError(skipUntilToken); } Value& Reader::currentValue() { return *(this->nodes_.top()); } Reader::Char Reader::getNextChar() { if (this->current_ == this->end_) return 0; return *this->current_++; } void Reader::getLocationLineAndColumn(Location location, int& line, int& column) const { Location current = this->begin_; Location lastLineStart = current; line = 0; while (current < location && current != this->end_) { Char c = *current++; if (c == '\r') { if (*current == '\n') ++current; lastLineStart = current; ++line; } else if (c == '\n') { lastLineStart = current; ++line; } } // column & line start at 1 column = int(location - lastLineStart) + 1; ++line; } JSONCPP_STRING Reader::getLocationLineAndColumn(Location location) const { int line, column; this->getLocationLineAndColumn(location, line, column); char buffer[18 + 16 + 16 + 1]; snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); return buffer; } // Deprecated. Preserved for backward compatibility JSONCPP_STRING Reader::getFormatedErrorMessages() const { return this->getFormattedErrorMessages(); } JSONCPP_STRING Reader::getFormattedErrorMessages() const { JSONCPP_STRING formattedMessage; for (Errors::const_iterator itError = this->errors_.begin(); itError != this->errors_.end(); ++itError) { const ErrorInfo& error = *itError; formattedMessage += "* " + this->getLocationLineAndColumn(error.token_.start_) + "\n"; formattedMessage += " " + error.message_ + "\n"; if (error.extra_) formattedMessage += "See " + this->getLocationLineAndColumn(error.extra_) + " for detail.\n"; } return formattedMessage; } std::vector Reader::getStructuredErrors() const { std::vector allErrors; for (Errors::const_iterator itError = this->errors_.begin(); itError != this->errors_.end(); ++itError) { const ErrorInfo& error = *itError; Reader::StructuredError structured; structured.offset_start = error.token_.start_ - this->begin_; structured.offset_limit = error.token_.end_ - this->begin_; structured.message = error.message_; allErrors.push_back(structured); } return allErrors; } bool Reader::pushError(const Value& value, const JSONCPP_STRING& message) { ptrdiff_t const length = this->end_ - this->begin_; if (value.getOffsetStart() > length || value.getOffsetLimit() > length) return false; Token token; token.type_ = tokenError; token.start_ = this->begin_ + value.getOffsetStart(); token.end_ = this->end_ + value.getOffsetLimit(); ErrorInfo info; info.token_ = token; info.message_ = message; info.extra_ = 0; this->errors_.push_back(info); return true; } bool Reader::pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra) { ptrdiff_t const length = this->end_ - this->begin_; if (value.getOffsetStart() > length || value.getOffsetLimit() > length || extra.getOffsetLimit() > length) return false; Token token; token.type_ = tokenError; token.start_ = this->begin_ + value.getOffsetStart(); token.end_ = this->begin_ + value.getOffsetLimit(); ErrorInfo info; info.token_ = token; info.message_ = message; info.extra_ = this->begin_ + extra.getOffsetStart(); this->errors_.push_back(info); return true; } bool Reader::good() const { return !this->errors_.size(); } // exact copy of Features class OurFeatures { public: static OurFeatures all(); bool allowComments_; bool strictRoot_; bool allowDroppedNullPlaceholders_; bool allowNumericKeys_; bool allowSingleQuotes_; bool failIfExtra_; bool rejectDupKeys_; bool allowSpecialFloats_; int stackLimit_; }; // OurFeatures // exact copy of Implementation of class Features // //////////////////////////////// OurFeatures OurFeatures::all() { return OurFeatures(); } // Implementation of class Reader // //////////////////////////////// // exact copy of Reader, renamed to OurReader class OurReader { public: typedef char Char; typedef const Char* Location; struct StructuredError { ptrdiff_t offset_start; ptrdiff_t offset_limit; JSONCPP_STRING message; }; OurReader(OurFeatures const& features); bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true); JSONCPP_STRING getFormattedErrorMessages() const; std::vector getStructuredErrors() const; bool pushError(const Value& value, const JSONCPP_STRING& message); bool pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra); bool good() const; private: OurReader(OurReader const&); // no impl void operator=(OurReader const&); // no impl enum TokenType { tokenEndOfStream = 0, tokenObjectBegin, tokenObjectEnd, tokenArrayBegin, tokenArrayEnd, tokenString, tokenNumber, tokenTrue, tokenFalse, tokenNull, tokenNaN, tokenPosInf, tokenNegInf, tokenArraySeparator, tokenMemberSeparator, tokenComment, tokenError }; class Token { public: TokenType type_; Location start_; Location end_; }; class ErrorInfo { public: Token token_; JSONCPP_STRING message_; Location extra_; }; typedef std::deque Errors; bool readToken(Token& token); void skipSpaces(); bool match(Location pattern, int patternLength); bool readComment(); bool readCStyleComment(); bool readCppStyleComment(); bool readString(); bool readStringSingleQuote(); bool readNumber(bool checkInf); bool readValue(); bool readObject(Token& token); bool readArray(Token& token); bool decodeNumber(Token& token); bool decodeNumber(Token& token, Value& decoded); bool decodeString(Token& token); bool decodeString(Token& token, JSONCPP_STRING& decoded); bool decodeDouble(Token& token); bool decodeDouble(Token& token, Value& decoded); bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode); bool decodeUnicodeEscapeSequence(Token& token, Location& current, Location end, unsigned int& unicode); bool addError(const JSONCPP_STRING& message, Token& token, Location extra = 0); bool recoverFromError(TokenType skipUntilToken); bool addErrorAndRecover(const JSONCPP_STRING& message, Token& token, TokenType skipUntilToken); void skipUntilSpace(); Value& currentValue(); Char getNextChar(); void getLocationLineAndColumn(Location location, int& line, int& column) const; JSONCPP_STRING getLocationLineAndColumn(Location location) const; void addComment(Location begin, Location end, CommentPlacement placement); void skipCommentTokens(Token& token); static JSONCPP_STRING normalizeEOL(Location begin, Location end); static bool containsNewLine(Location begin, Location end); typedef std::stack Nodes; Nodes nodes_; Errors errors_; JSONCPP_STRING document_; Location begin_; Location end_; Location current_; Location lastValueEnd_; Value* lastValue_; JSONCPP_STRING commentsBefore_; OurFeatures const features_; bool collectComments_; }; // OurReader // complete copy of Read impl, for OurReader bool OurReader::containsNewLine(OurReader::Location begin, OurReader::Location end) { for (; begin < end; ++begin) if (*begin == '\n' || *begin == '\r') return true; return false; } OurReader::OurReader(OurFeatures const& features) : errors_() , document_() , begin_() , end_() , current_() , lastValueEnd_() , lastValue_() , commentsBefore_() , features_(features) , collectComments_() { } bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments) { if (!this->features_.allowComments_) { collectComments = false; } this->begin_ = beginDoc; this->end_ = endDoc; this->collectComments_ = collectComments; this->current_ = this->begin_; this->lastValueEnd_ = 0; this->lastValue_ = 0; this->commentsBefore_.clear(); this->errors_.clear(); while (!this->nodes_.empty()) this->nodes_.pop(); this->nodes_.push(&root); bool successful = this->readValue(); Token token; this->skipCommentTokens(token); if (this->features_.failIfExtra_) { if ((this->features_.strictRoot_ || token.type_ != tokenError) && token.type_ != tokenEndOfStream) { this->addError("Extra non-whitespace after JSON value.", token); return false; } } if (this->collectComments_ && !this->commentsBefore_.empty()) root.setComment(this->commentsBefore_, commentAfter); if (this->features_.strictRoot_) { if (!root.isArray() && !root.isObject()) { // Set error location to start of doc, ideally should be first token // found in doc token.type_ = tokenError; token.start_ = beginDoc; token.end_ = endDoc; this->addError( "A valid JSON document must be either an array or an object value.", token); return false; } } return successful; } bool OurReader::readValue() { // To preserve the old behaviour we cast size_t to int. if (static_cast(this->nodes_.size()) > this->features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; this->skipCommentTokens(token); bool successful = true; if (this->collectComments_ && !this->commentsBefore_.empty()) { this->currentValue().setComment(this->commentsBefore_, commentBefore); this->commentsBefore_.clear(); } switch (token.type_) { case tokenObjectBegin: successful = this->readObject(token); this->currentValue().setOffsetLimit(this->current_ - this->begin_); break; case tokenArrayBegin: successful = this->readArray(token); this->currentValue().setOffsetLimit(this->current_ - this->begin_); break; case tokenNumber: successful = this->decodeNumber(token); break; case tokenString: successful = this->decodeString(token); break; case tokenTrue: { Value v(true); this->currentValue().swapPayload(v); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); } break; case tokenFalse: { Value v(false); this->currentValue().swapPayload(v); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); } break; case tokenNull: { Value v; this->currentValue().swapPayload(v); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); } break; case tokenNaN: { Value v(std::numeric_limits::quiet_NaN()); this->currentValue().swapPayload(v); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); } break; case tokenPosInf: { Value v(std::numeric_limits::infinity()); this->currentValue().swapPayload(v); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); } break; case tokenNegInf: { Value v(-std::numeric_limits::infinity()); this->currentValue().swapPayload(v); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); } break; case tokenArraySeparator: case tokenObjectEnd: case tokenArrayEnd: if (this->features_.allowDroppedNullPlaceholders_) { // "Un-read" the current token and mark the current value as a null // token. this->current_--; Value v; this->currentValue().swapPayload(v); this->currentValue().setOffsetStart(this->current_ - this->begin_ - 1); this->currentValue().setOffsetLimit(this->current_ - this->begin_); break; } // else, fall through ... default: this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); return this->addError("Syntax error: value, object or array expected.", token); } if (this->collectComments_) { this->lastValueEnd_ = this->current_; this->lastValue_ = &this->currentValue(); } return successful; } void OurReader::skipCommentTokens(Token& token) { if (this->features_.allowComments_) { do { this->readToken(token); } while (token.type_ == tokenComment); } else { this->readToken(token); } } bool OurReader::readToken(Token& token) { this->skipSpaces(); token.start_ = this->current_; Char c = this->getNextChar(); bool ok = true; switch (c) { case '{': token.type_ = tokenObjectBegin; break; case '}': token.type_ = tokenObjectEnd; break; case '[': token.type_ = tokenArrayBegin; break; case ']': token.type_ = tokenArrayEnd; break; case '"': token.type_ = tokenString; ok = this->readString(); break; case '\'': if (this->features_.allowSingleQuotes_) { token.type_ = tokenString; ok = this->readStringSingleQuote(); break; } // else continue case '/': token.type_ = tokenComment; ok = this->readComment(); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': token.type_ = tokenNumber; this->readNumber(false); break; case '-': if (this->readNumber(true)) { token.type_ = tokenNumber; } else { token.type_ = tokenNegInf; ok = this->features_.allowSpecialFloats_ && this->match("nfinity", 7); } break; case 't': token.type_ = tokenTrue; ok = this->match("rue", 3); break; case 'f': token.type_ = tokenFalse; ok = this->match("alse", 4); break; case 'n': token.type_ = tokenNull; ok = this->match("ull", 3); break; case 'N': if (this->features_.allowSpecialFloats_) { token.type_ = tokenNaN; ok = this->match("aN", 2); } else { ok = false; } break; case 'I': if (this->features_.allowSpecialFloats_) { token.type_ = tokenPosInf; ok = this->match("nfinity", 7); } else { ok = false; } break; case ',': token.type_ = tokenArraySeparator; break; case ':': token.type_ = tokenMemberSeparator; break; case 0: token.type_ = tokenEndOfStream; break; default: ok = false; break; } if (!ok) token.type_ = tokenError; token.end_ = this->current_; return true; } void OurReader::skipSpaces() { while (this->current_ != this->end_) { Char c = *this->current_; if (c == ' ' || c == '\t' || c == '\r' || c == '\n') ++this->current_; else break; } } bool OurReader::match(Location pattern, int patternLength) { if (this->end_ - this->current_ < patternLength) return false; int index = patternLength; while (index--) if (this->current_[index] != pattern[index]) return false; this->current_ += patternLength; return true; } bool OurReader::readComment() { Location commentBegin = this->current_ - 1; Char c = this->getNextChar(); bool successful = false; if (c == '*') successful = this->readCStyleComment(); else if (c == '/') successful = this->readCppStyleComment(); if (!successful) return false; if (this->collectComments_) { CommentPlacement placement = commentBefore; if (this->lastValueEnd_ && !containsNewLine(this->lastValueEnd_, commentBegin)) { if (c != '*' || !containsNewLine(commentBegin, this->current_)) placement = commentAfterOnSameLine; } this->addComment(commentBegin, this->current_, placement); } return true; } JSONCPP_STRING OurReader::normalizeEOL(OurReader::Location begin, OurReader::Location end) { JSONCPP_STRING normalized; normalized.reserve(static_cast(end - begin)); OurReader::Location current = begin; while (current != end) { char c = *current++; if (c == '\r') { if (current != end && *current == '\n') // convert dos EOL ++current; // convert Mac EOL normalized += '\n'; } else { normalized += c; } } return normalized; } void OurReader::addComment(Location begin, Location end, CommentPlacement placement) { assert(this->collectComments_); const JSONCPP_STRING& normalized = normalizeEOL(begin, end); if (placement == commentAfterOnSameLine) { assert(this->lastValue_ != 0); this->lastValue_->setComment(normalized, placement); } else { this->commentsBefore_ += normalized; } } bool OurReader::readCStyleComment() { while ((this->current_ + 1) < this->end_) { Char c = this->getNextChar(); if (c == '*' && *this->current_ == '/') break; } return this->getNextChar() == '/'; } bool OurReader::readCppStyleComment() { while (this->current_ != this->end_) { Char c = this->getNextChar(); if (c == '\n') break; if (c == '\r') { // Consume DOS EOL. It will be normalized in addComment. if (this->current_ != this->end_ && *this->current_ == '\n') this->getNextChar(); // Break on Moc OS 9 EOL. break; } } return true; } bool OurReader::readNumber(bool checkInf) { const char* p = this->current_; if (checkInf && p != this->end_ && *p == 'I') { this->current_ = ++p; return false; } char c = '0'; // stopgap for already consumed character // integral part while (c >= '0' && c <= '9') c = (this->current_ = p) < this->end_ ? *p++ : '\0'; // fractional part if (c == '.') { c = (this->current_ = p) < this->end_ ? *p++ : '\0'; while (c >= '0' && c <= '9') c = (this->current_ = p) < this->end_ ? *p++ : '\0'; } // exponential part if (c == 'e' || c == 'E') { c = (this->current_ = p) < this->end_ ? *p++ : '\0'; if (c == '+' || c == '-') c = (this->current_ = p) < this->end_ ? *p++ : '\0'; while (c >= '0' && c <= '9') c = (this->current_ = p) < this->end_ ? *p++ : '\0'; } return true; } bool OurReader::readString() { Char c = 0; while (this->current_ != this->end_) { c = this->getNextChar(); if (c == '\\') this->getNextChar(); else if (c == '"') break; } return c == '"'; } bool OurReader::readStringSingleQuote() { Char c = 0; while (this->current_ != this->end_) { c = this->getNextChar(); if (c == '\\') this->getNextChar(); else if (c == '\'') break; } return c == '\''; } bool OurReader::readObject(Token& tokenStart) { Token tokenName; JSONCPP_STRING name; Value init(objectValue); this->currentValue().swapPayload(init); this->currentValue().setOffsetStart(tokenStart.start_ - this->begin_); while (this->readToken(tokenName)) { bool initialTokenOk = true; while (tokenName.type_ == tokenComment && initialTokenOk) initialTokenOk = this->readToken(tokenName); if (!initialTokenOk) break; if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object return true; name.clear(); if (tokenName.type_ == tokenString) { if (!this->decodeString(tokenName, name)) return this->recoverFromError(tokenObjectEnd); } else if (tokenName.type_ == tokenNumber && this->features_.allowNumericKeys_) { Value numberName; if (!this->decodeNumber(tokenName, numberName)) return this->recoverFromError(tokenObjectEnd); name = numberName.asString(); } else { break; } Token colon; if (!this->readToken(colon) || colon.type_ != tokenMemberSeparator) { return this->addErrorAndRecover("Missing ':' after object member name", colon, tokenObjectEnd); } if (name.length() >= (1U << 30)) throwRuntimeError("keylength >= 2^30"); if (this->features_.rejectDupKeys_ && this->currentValue().isMember(name)) { JSONCPP_STRING msg = "Duplicate key: '" + name + "'"; return this->addErrorAndRecover(msg, tokenName, tokenObjectEnd); } Value& value = this->currentValue()[name]; this->nodes_.push(&value); bool ok = this->readValue(); this->nodes_.pop(); if (!ok) // error already set return this->recoverFromError(tokenObjectEnd); Token comma; if (!this->readToken(comma) || (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && comma.type_ != tokenComment)) { return this->addErrorAndRecover( "Missing ',' or '}' in object declaration", comma, tokenObjectEnd); } bool finalizeTokenOk = true; while (comma.type_ == tokenComment && finalizeTokenOk) finalizeTokenOk = this->readToken(comma); if (comma.type_ == tokenObjectEnd) return true; } return this->addErrorAndRecover("Missing '}' or object member name", tokenName, tokenObjectEnd); } bool OurReader::readArray(Token& tokenStart) { Value init(arrayValue); this->currentValue().swapPayload(init); this->currentValue().setOffsetStart(tokenStart.start_ - this->begin_); this->skipSpaces(); if (this->current_ != this->end_ && *this->current_ == ']') // empty array { Token endArray; this->readToken(endArray); return true; } int index = 0; for (;;) { Value& value = this->currentValue()[index++]; this->nodes_.push(&value); bool ok = this->readValue(); this->nodes_.pop(); if (!ok) // error already set return this->recoverFromError(tokenArrayEnd); Token token; // Accept Comment after last item in the array. ok = this->readToken(token); while (token.type_ == tokenComment && ok) { ok = this->readToken(token); } bool badTokenType = (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd); if (!ok || badTokenType) { return this->addErrorAndRecover( "Missing ',' or ']' in array declaration", token, tokenArrayEnd); } if (token.type_ == tokenArrayEnd) break; } return true; } bool OurReader::decodeNumber(Token& token) { Value decoded; if (!this->decodeNumber(token, decoded)) return false; this->currentValue().swapPayload(decoded); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); return true; } bool OurReader::decodeNumber(Token& token, Value& decoded) { // Attempts to parse the number as an integer. If the number is // larger than the maximum supported value of an integer then // we decode the number as a double. Location current = token.start_; bool isNegative = *current == '-'; if (isNegative) ++current; // TODO: Help the compiler do the div and mod at compile time or get rid of // them. Value::LargestUInt maxIntegerValue = isNegative ? Value::LargestUInt(Value::minLargestInt) : Value::maxLargestUInt; Value::LargestUInt threshold = maxIntegerValue / 10; Value::LargestUInt value = 0; while (current < token.end_) { Char c = *current++; if (c < '0' || c > '9') return this->decodeDouble(token, decoded); Value::UInt digit(static_cast(c - '0')); if (value >= threshold) { // We've hit or exceeded the max value divided by 10 (rounded down). If // a) we've only just touched the limit, b) this is the last digit, and // c) it's small enough to fit in that rounding delta, we're okay. // Otherwise treat this number as a double to avoid overflow. if (value > threshold || current != token.end_ || digit > maxIntegerValue % 10) { return this->decodeDouble(token, decoded); } } value = value * 10 + digit; } if (isNegative) decoded = -Value::LargestInt(value); else if (value <= Value::LargestUInt(Value::maxInt)) decoded = Value::LargestInt(value); else decoded = value; return true; } bool OurReader::decodeDouble(Token& token) { Value decoded; if (!this->decodeDouble(token, decoded)) return false; this->currentValue().swapPayload(decoded); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); return true; } bool OurReader::decodeDouble(Token& token, Value& decoded) { double value = 0; const int bufferSize = 32; int count; ptrdiff_t const length = token.end_ - token.start_; // Sanity check to avoid buffer overflow exploits. if (length < 0) { return this->addError("Unable to parse token length", token); } size_t const ulength = static_cast(length); // Avoid using a string constant for the format control string given to // sscanf, as this can cause hard to debug crashes on OS X. See here for more // info: // // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html char format[] = "%lf"; if (length <= bufferSize) { Char buffer[bufferSize + 1]; memcpy(buffer, token.start_, ulength); buffer[length] = 0; fixNumericLocaleInput(buffer, buffer + length); count = sscanf(buffer, format, &value); } else { JSONCPP_STRING buffer(token.start_, token.end_); count = sscanf(buffer.c_str(), format, &value); } if (count != 1) return this->addError("'" + JSONCPP_STRING(token.start_, token.end_) + "' is not a number.", token); decoded = value; return true; } bool OurReader::decodeString(Token& token) { JSONCPP_STRING decoded_string; if (!this->decodeString(token, decoded_string)) return false; Value decoded(decoded_string); this->currentValue().swapPayload(decoded); this->currentValue().setOffsetStart(token.start_ - this->begin_); this->currentValue().setOffsetLimit(token.end_ - this->begin_); return true; } bool OurReader::decodeString(Token& token, JSONCPP_STRING& decoded) { decoded.reserve(static_cast(token.end_ - token.start_ - 2)); Location current = token.start_ + 1; // skip '"' Location end = token.end_ - 1; // do not include '"' while (current != end) { Char c = *current++; if (c == '"') break; else if (c == '\\') { if (current == end) return this->addError("Empty escape sequence in string", token, current); Char escape = *current++; switch (escape) { case '"': decoded += '"'; break; case '/': decoded += '/'; break; case '\\': decoded += '\\'; break; case 'b': decoded += '\b'; break; case 'f': decoded += '\f'; break; case 'n': decoded += '\n'; break; case 'r': decoded += '\r'; break; case 't': decoded += '\t'; break; case 'u': { unsigned int unicode; if (!this->decodeUnicodeCodePoint(token, current, end, unicode)) return false; decoded += codePointToUTF8(unicode); } break; default: return this->addError("Bad escape sequence in string", token, current); } } else { decoded += c; } } return true; } bool OurReader::decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode) { unicode = 0; // Convince scanbuild this is always initialized before use. if (!this->decodeUnicodeEscapeSequence(token, current, end, unicode)) return false; if (unicode >= 0xD800 && unicode <= 0xDBFF) { // surrogate pairs if (end - current < 6) return this->addError( "additional six characters expected to parse unicode surrogate pair.", token, current); unsigned int surrogatePair; if (*(current++) == '\\' && *(current++) == 'u') { if (this->decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); } else return false; } else return this->addError( "expecting another \\u token to begin the second half of " "a unicode surrogate pair", token, current); } return true; } bool OurReader::decodeUnicodeEscapeSequence(Token& token, Location& current, Location end, unsigned int& ret_unicode) { if (end - current < 4) return this->addError( "Bad unicode escape sequence in string: four digits expected.", token, current); int unicode = 0; for (int index = 0; index < 4; ++index) { Char c = *current++; unicode *= 16; if (c >= '0' && c <= '9') unicode += c - '0'; else if (c >= 'a' && c <= 'f') unicode += c - 'a' + 10; else if (c >= 'A' && c <= 'F') unicode += c - 'A' + 10; else return this->addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", token, current); } ret_unicode = static_cast(unicode); return true; } bool OurReader::addError(const JSONCPP_STRING& message, Token& token, Location extra) { ErrorInfo info; info.token_ = token; info.message_ = message; info.extra_ = extra; this->errors_.push_back(info); return false; } bool OurReader::recoverFromError(TokenType skipUntilToken) { size_t errorCount = this->errors_.size(); Token skip; for (;;) { if (!this->readToken(skip)) this->errors_.resize(errorCount); // discard errors caused by recovery if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) break; } this->errors_.resize(errorCount); return false; } bool OurReader::addErrorAndRecover(const JSONCPP_STRING& message, Token& token, TokenType skipUntilToken) { this->addError(message, token); return this->recoverFromError(skipUntilToken); } Value& OurReader::currentValue() { return *(this->nodes_.top()); } OurReader::Char OurReader::getNextChar() { if (this->current_ == this->end_) return 0; return *this->current_++; } void OurReader::getLocationLineAndColumn(Location location, int& line, int& column) const { Location current = this->begin_; Location lastLineStart = current; line = 0; while (current < location && current != this->end_) { Char c = *current++; if (c == '\r') { if (*current == '\n') ++current; lastLineStart = current; ++line; } else if (c == '\n') { lastLineStart = current; ++line; } } // column & line start at 1 column = int(location - lastLineStart) + 1; ++line; } JSONCPP_STRING OurReader::getLocationLineAndColumn(Location location) const { int line, column; this->getLocationLineAndColumn(location, line, column); char buffer[18 + 16 + 16 + 1]; snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); return buffer; } JSONCPP_STRING OurReader::getFormattedErrorMessages() const { JSONCPP_STRING formattedMessage; for (Errors::const_iterator itError = this->errors_.begin(); itError != this->errors_.end(); ++itError) { const ErrorInfo& error = *itError; formattedMessage += "* " + this->getLocationLineAndColumn(error.token_.start_) + "\n"; formattedMessage += " " + error.message_ + "\n"; if (error.extra_) formattedMessage += "See " + this->getLocationLineAndColumn(error.extra_) + " for detail.\n"; } return formattedMessage; } std::vector OurReader::getStructuredErrors() const { std::vector allErrors; for (Errors::const_iterator itError = this->errors_.begin(); itError != this->errors_.end(); ++itError) { const ErrorInfo& error = *itError; OurReader::StructuredError structured; structured.offset_start = error.token_.start_ - this->begin_; structured.offset_limit = error.token_.end_ - this->begin_; structured.message = error.message_; allErrors.push_back(structured); } return allErrors; } bool OurReader::pushError(const Value& value, const JSONCPP_STRING& message) { ptrdiff_t length = this->end_ - this->begin_; if (value.getOffsetStart() > length || value.getOffsetLimit() > length) return false; Token token; token.type_ = tokenError; token.start_ = this->begin_ + value.getOffsetStart(); token.end_ = this->end_ + value.getOffsetLimit(); ErrorInfo info; info.token_ = token; info.message_ = message; info.extra_ = 0; this->errors_.push_back(info); return true; } bool OurReader::pushError(const Value& value, const JSONCPP_STRING& message, const Value& extra) { ptrdiff_t length = this->end_ - this->begin_; if (value.getOffsetStart() > length || value.getOffsetLimit() > length || extra.getOffsetLimit() > length) return false; Token token; token.type_ = tokenError; token.start_ = this->begin_ + value.getOffsetStart(); token.end_ = this->begin_ + value.getOffsetLimit(); ErrorInfo info; info.token_ = token; info.message_ = message; info.extra_ = this->begin_ + extra.getOffsetStart(); this->errors_.push_back(info); return true; } bool OurReader::good() const { return !this->errors_.size(); } class OurCharReader : public CharReader { bool const collectComments_; OurReader reader_; public: OurCharReader(bool collectComments, OurFeatures const& features) : collectComments_(collectComments) , reader_(features) { } bool parse(char const* beginDoc, char const* endDoc, Value* root, JSONCPP_STRING* errs) JSONCPP_OVERRIDE { bool ok = this->reader_.parse(beginDoc, endDoc, *root, this->collectComments_); if (errs) { *errs = this->reader_.getFormattedErrorMessages(); } return ok; } }; CharReaderBuilder::CharReaderBuilder() { setDefaults(&this->settings_); } CharReaderBuilder::~CharReaderBuilder() { } CharReader* CharReaderBuilder::newCharReader() const { bool collectComments = this->settings_["collectComments"].asBool(); OurFeatures features = OurFeatures::all(); features.allowComments_ = this->settings_["allowComments"].asBool(); features.strictRoot_ = this->settings_["strictRoot"].asBool(); features.allowDroppedNullPlaceholders_ = this->settings_["allowDroppedNullPlaceholders"].asBool(); features.allowNumericKeys_ = this->settings_["allowNumericKeys"].asBool(); features.allowSingleQuotes_ = this->settings_["allowSingleQuotes"].asBool(); features.stackLimit_ = this->settings_["stackLimit"].asInt(); features.failIfExtra_ = this->settings_["failIfExtra"].asBool(); features.rejectDupKeys_ = this->settings_["rejectDupKeys"].asBool(); features.allowSpecialFloats_ = this->settings_["allowSpecialFloats"].asBool(); return new OurCharReader(collectComments, features); } static void getValidReaderKeys(std::set* valid_keys) { valid_keys->clear(); valid_keys->insert("collectComments"); valid_keys->insert("allowComments"); valid_keys->insert("strictRoot"); valid_keys->insert("allowDroppedNullPlaceholders"); valid_keys->insert("allowNumericKeys"); valid_keys->insert("allowSingleQuotes"); valid_keys->insert("stackLimit"); valid_keys->insert("failIfExtra"); valid_keys->insert("rejectDupKeys"); valid_keys->insert("allowSpecialFloats"); } bool CharReaderBuilder::validate(Json::Value* invalid) const { Json::Value my_invalid; if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL Json::Value& inv = *invalid; std::set valid_keys; getValidReaderKeys(&valid_keys); Value::Members keys = this->settings_.getMemberNames(); size_t n = keys.size(); for (size_t i = 0; i < n; ++i) { JSONCPP_STRING const& key = keys[i]; if (valid_keys.find(key) == valid_keys.end()) { inv[key] = this->settings_[key]; } } return 0u == inv.size(); } Value& CharReaderBuilder::operator[](JSONCPP_STRING key) { return this->settings_[key]; } // static void CharReaderBuilder::strictMode(Json::Value* settings) { //! [CharReaderBuilderStrictMode] (*settings)["allowComments"] = false; (*settings)["strictRoot"] = true; (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; (*settings)["allowSingleQuotes"] = false; (*settings)["stackLimit"] = 1000; (*settings)["failIfExtra"] = true; (*settings)["rejectDupKeys"] = true; (*settings)["allowSpecialFloats"] = false; //! [CharReaderBuilderStrictMode] } // static void CharReaderBuilder::setDefaults(Json::Value* settings) { //! [CharReaderBuilderDefaults] (*settings)["collectComments"] = true; (*settings)["allowComments"] = true; (*settings)["strictRoot"] = false; (*settings)["allowDroppedNullPlaceholders"] = false; (*settings)["allowNumericKeys"] = false; (*settings)["allowSingleQuotes"] = false; (*settings)["stackLimit"] = 1000; (*settings)["failIfExtra"] = false; (*settings)["rejectDupKeys"] = false; (*settings)["allowSpecialFloats"] = false; //! [CharReaderBuilderDefaults] } ////////////////////////////////// // global functions bool parseFromStream(CharReader::Factory const& fact, JSONCPP_ISTREAM& sin, Value* root, JSONCPP_STRING* errs) { JSONCPP_OSTRINGSTREAM ssin; ssin << sin.rdbuf(); JSONCPP_STRING doc = ssin.str(); char const* begin = doc.data(); char const* end = begin + doc.size(); // Note that we do not actually need a null-terminator. CharReaderPtr const reader(fact.newCharReader()); return reader->parse(begin, end, root, errs); } JSONCPP_ISTREAM& operator>>(JSONCPP_ISTREAM& sin, Value& root) { CharReaderBuilder b; JSONCPP_STRING errs; bool ok = parseFromStream(b, sin, &root, &errs); if (!ok) { fprintf(stderr, "Error from reader: %s", errs.c_str()); throwRuntimeError(errs); } return sin; } } // namespace Json