/* 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 "cmsys/FStream.hxx" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" cmJSONState::cmJSONState(std::string jsonFile, Json::Value* root) : 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; } // If there's a BOM, toss it. cmsys::FStream::ReadBOM(fin); // Save the entire document. std::streampos finBegin = fin.tellg(); this->doc = std::string(std::istreambuf_iterator(fin), std::istreambuf_iterator()); if (this->doc.empty()) { this->AddError("A JSON document cannot be empty"); return; } fin.seekg(finBegin); Json::CharReaderBuilder builder; Json::CharReaderBuilder::strictMode(&builder.settings_); std::string errMsg; #if JSONCPP_VERSION_HEXA >= 0x01090600 // Has StructuredError std::unique_ptr const reader(builder.newCharReader()); reader->parse(doc.data(), doc.data() + doc.size(), root, &errMsg); std::vector structuredErrors = reader->getStructuredErrors(); for (auto const& structuredError : structuredErrors) { this->AddErrorAtOffset(structuredError.message, structuredError.offset_start); } #else // No StructuredError Available, Use error string from jsonCpp if (!Json::parseFromStream(builder, fin, root, &errMsg)) { errMsg = cmStrCat("JSON Parse Error: ", this->Filename, ":\n", errMsg); this->AddError(errMsg); } #endif } 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"); } } 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(); } 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 }; }