| /* 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 <istream> | |
| #include <iterator> | |
| #include <memory> | |
| #include <sstream> | |
| #include <cm3p/json/reader.h> | |
| #include <cm3p/json/value.h> | |
| #include <cm3p/json/version.h> | |
| #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(std::move(message), filenameName, ':', loc.line, ": "); | |
| } | |
| message = cmStrCat(std::move(message), error.GetErrorMessage(), '\n'); | |
| if (showContext && loc.line > 0) { | |
| message = cmStrCat(std::move(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) { | |
| auto next = std::next(it); | |
| if (next != this->parseStack.end()) { | |
| return next->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) { | |
| auto next = std::next(it); | |
| if (next != this->parseStack.end()) { | |
| return next->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<char>(jsonIStream), | |
| std::istreambuf_iterator<char>()); | |
| 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<Json::CharReader> 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<Json::CharReader::StructuredError> 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 ", std::move(errMsg)); | |
| } else { | |
| errMsg = cmStrCat("JSON Parse Error: ", this->Filename, ":\n", | |
| std::move(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 }; | |
| } |