|
|
|
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
|
|
|
|
|
|
#include "cmJSONState.h"
|
|
|
|
|
|
|
|
#include <iterator>
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
#include <cm3p/json/reader.h>
|
|
|
|
#include <cm3p/json/value.h>
|
|
|
|
|
|
|
|
#include "cmsys/FStream.hxx"
|
|
|
|
|
|
|
|
#include "cmStringAlgorithms.h"
|
|
|
|
|
|
|
|
cmJSONState::cmJSONState(const std::string& filename, Json::Value* root)
|
|
|
|
{
|
|
|
|
cmsys::ifstream fin(filename.c_str(), std::ios::in | std::ios::binary);
|
|
|
|
if (!fin) {
|
|
|
|
this->AddError(cmStrCat("File not found: ", 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<char>(fin),
|
|
|
|
std::istreambuf_iterator<char>());
|
|
|
|
if (this->doc.empty()) {
|
|
|
|
this->AddError("A JSON document cannot be empty");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
fin.seekg(finBegin);
|
|
|
|
|
|
|
|
// Parse the document.
|
|
|
|
Json::CharReaderBuilder builder;
|
|
|
|
Json::CharReaderBuilder::strictMode(&builder.settings_);
|
|
|
|
std::string errMsg;
|
|
|
|
if (!Json::parseFromStream(builder, fin, root, &errMsg)) {
|
|
|
|
errMsg = cmStrCat("JSON Parse Error: ", filename, ":\n", errMsg);
|
|
|
|
this->AddError(errMsg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void cmJSONState::AddError(std::string const& errMsg)
|
|
|
|
{
|
|
|
|
this->errors.push_back(Error(errMsg));
|
|
|
|
}
|
|
|
|
|
|
|
|
void cmJSONState::AddErrorAtValue(std::string const& errMsg,
|
|
|
|
const Json::Value* 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.push_back(Error(loc, errMsg));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string cmJSONState::GetErrorMessage(bool showContext)
|
|
|
|
{
|
|
|
|
std::string message;
|
|
|
|
for (auto const& error : this->errors) {
|
|
|
|
message = cmStrCat(message, error.GetErrorMessage(), "\n");
|
|
|
|
if (showContext) {
|
|
|
|
Location loc = error.GetLocation();
|
|
|
|
if (loc.column > 0) {
|
|
|
|
message = cmStrCat(message, GetJsonContext(loc), "\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
message = cmStrCat("\n", message);
|
|
|
|
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 "";
|
|
|
|
}
|
|
|
|
|
|
|
|
const Json::Value* 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, const Json::Value* value)
|
|
|
|
{
|
|
|
|
this->parseStack.push_back(JsonPair(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;
|
|
|
|
const char* beginDoc = doc.data();
|
|
|
|
const char* 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 };
|
|
|
|
}
|