/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file LICENSE.rst or https://cmake.org/licensing for details. */ #include "cmJSONState.h" #include #include #include #include #include #include #include #include "cmsys/FStream.hxx" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" cmJSONState::cmJSONState(std::string jsonFile, Json::Value* root, StrictMode strictMode) : Filename(std::move(jsonFile)) { cmsys::ifstream fin(this->Filename.c_str(), std::ios::in | std::ios::binary); if (!fin) { this->AddError(cmStrCat("File not found: ", this->Filename)); return; } this->ReadJSONStream(fin, root, strictMode); } cmJSONState::cmJSONState(std::istream& jsonIStream, Json::Value* root, StrictMode strictMode) { this->ReadJSONStream(jsonIStream, root, strictMode); } void cmJSONState::AddError(std::string const& errMsg) { this->errors.emplace_back(errMsg); } void cmJSONState::AddErrorAtValue(std::string const& errMsg, Json::Value const* value) { if (value && !value->isNull()) { this->AddErrorAtOffset(errMsg, value->getOffsetStart()); } else { this->AddError(errMsg); } } void cmJSONState::AddErrorAtOffset(std::string const& errMsg, std::ptrdiff_t offset) { if (doc.empty()) { this->AddError(errMsg); } else { Location loc = LocateInDocument(offset); this->errors.emplace_back(loc, errMsg); } } std::string cmJSONState::GetErrorMessage(bool showContext) { std::string message; std::string filenameName = cmSystemTools::GetFilenameName(this->Filename); for (auto const& error : this->errors) { Location loc = error.GetLocation(); if (!filenameName.empty() && loc.line > 0) { message = cmStrCat(message, filenameName, ':', loc.line, ": "); } message = cmStrCat(message, error.GetErrorMessage(), '\n'); if (showContext && loc.line > 0) { message = cmStrCat(message, GetJsonContext(loc), '\n'); } } if (!message.empty()) { message.pop_back(); } return message; } std::string cmJSONState::key() { if (!this->parseStack.empty()) { return this->parseStack.back().first; } return ""; } std::string cmJSONState::key_after(std::string const& k) { for (auto it = this->parseStack.begin(); it != this->parseStack.end(); ++it) { if (it->first == k && (++it) != this->parseStack.end()) { return it->first; } } return ""; } Json::Value const* cmJSONState::value_after(std::string const& k) { for (auto it = this->parseStack.begin(); it != this->parseStack.end(); ++it) { if (it->first == k && (++it) != this->parseStack.end()) { return it->second; } } return nullptr; } void cmJSONState::push_stack(std::string const& k, Json::Value const* value) { this->parseStack.emplace_back(k, value); } void cmJSONState::pop_stack() { this->parseStack.pop_back(); } void cmJSONState::ReadJSONStream(std::istream& jsonIStream, Json::Value* root, StrictMode strictMode) { // If there's a BOM, toss it. cmsys::FStream::ReadBOM(jsonIStream); // Save the entire document. std::streampos inBegin = jsonIStream.tellg(); this->doc = std::string(std::istreambuf_iterator(jsonIStream), std::istreambuf_iterator()); if (this->doc.empty()) { this->AddError("A JSON document cannot be empty"); return; } jsonIStream.seekg(inBegin); Json::CharReaderBuilder builder; if (strictMode == StrictMode::Strict) { Json::CharReaderBuilder::strictMode(&builder.settings_); } std::string errMsg; #if JSONCPP_VERSION_HEXA >= 0x01090600 // Has StructuredError std::unique_ptr const reader(builder.newCharReader()); # if JSON_USE_EXCEPTION // Only this CharReader-based parse() can throw, and only when built with // support for exceptions. Older jsoncpp aborts on bad inputs instead. try { # endif reader->parse(this->doc.data(), this->doc.data() + this->doc.size(), root, &errMsg); std::vector structuredErrors = reader->getStructuredErrors(); for (auto const& structuredError : structuredErrors) { this->AddErrorAtOffset(structuredError.message, structuredError.offset_start); } # if JSON_USE_EXCEPTION } catch (Json::Exception const& e) { if (this->Filename.empty()) { errMsg = cmStrCat("JSON Parse Error:\n ", e.what()); } else { errMsg = cmStrCat("JSON Parse Error: ", this->Filename, ":\n", e.what()); } this->AddError(errMsg); } # endif #else // No StructuredError Available, Use error string from jsonCpp if (!Json::parseFromStream(builder, jsonIStream, root, &errMsg)) { if (this->Filename.empty()) { errMsg = cmStrCat("JSON Parse Error:\n ", errMsg); } else { errMsg = cmStrCat("JSON Parse Error: ", this->Filename, ":\n", errMsg); } this->AddError(errMsg); } #endif } std::string cmJSONState::GetJsonContext(Location loc) { std::string line; std::stringstream sstream(doc); for (int i = 0; i < loc.line; ++i) { std::getline(sstream, line, '\n'); } return cmStrCat(line, '\n', std::string(loc.column - 1, ' '), '^'); } cmJSONState::Location cmJSONState::LocateInDocument(ptrdiff_t offset) { int line = 1; int col = 1; char const* beginDoc = doc.data(); char const* last = beginDoc + offset; for (; beginDoc != last; ++beginDoc) { switch (*beginDoc) { case '\r': if (beginDoc + 1 != last && beginDoc[1] == '\n') { continue; // consume CRLF as a single token. } CM_FALLTHROUGH; // CR without a following LF is same as LF case '\n': col = 1; ++line; break; default: ++col; break; } } return { line, col }; }