/src/CMake/Source/cmJSONState.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | |
4 | | #include "cmJSONState.h" |
5 | | |
6 | | #include <iterator> |
7 | | #include <memory> |
8 | | #include <sstream> |
9 | | |
10 | | #include <cm3p/json/reader.h> |
11 | | #include <cm3p/json/value.h> |
12 | | #include <cm3p/json/version.h> |
13 | | |
14 | | #include "cmsys/FStream.hxx" |
15 | | |
16 | | #include "cmStringAlgorithms.h" |
17 | | #include "cmSystemTools.h" |
18 | | |
19 | | cmJSONState::cmJSONState(std::string jsonFile, Json::Value* root) |
20 | 0 | : Filename(std::move(jsonFile)) |
21 | 0 | { |
22 | 0 | cmsys::ifstream fin(this->Filename.c_str(), std::ios::in | std::ios::binary); |
23 | 0 | if (!fin) { |
24 | 0 | this->AddError(cmStrCat("File not found: ", this->Filename)); |
25 | 0 | return; |
26 | 0 | } |
27 | | // If there's a BOM, toss it. |
28 | 0 | cmsys::FStream::ReadBOM(fin); |
29 | | |
30 | | // Save the entire document. |
31 | 0 | std::streampos finBegin = fin.tellg(); |
32 | 0 | this->doc = std::string(std::istreambuf_iterator<char>(fin), |
33 | 0 | std::istreambuf_iterator<char>()); |
34 | 0 | if (this->doc.empty()) { |
35 | 0 | this->AddError("A JSON document cannot be empty"); |
36 | 0 | return; |
37 | 0 | } |
38 | 0 | fin.seekg(finBegin); |
39 | |
|
40 | 0 | Json::CharReaderBuilder builder; |
41 | 0 | Json::CharReaderBuilder::strictMode(&builder.settings_); |
42 | 0 | std::string errMsg; |
43 | |
|
44 | 0 | #if JSONCPP_VERSION_HEXA >= 0x01090600 |
45 | | // Has StructuredError |
46 | 0 | std::unique_ptr<Json::CharReader> const reader(builder.newCharReader()); |
47 | 0 | reader->parse(doc.data(), doc.data() + doc.size(), root, &errMsg); |
48 | 0 | std::vector<Json::CharReader::StructuredError> structuredErrors = |
49 | 0 | reader->getStructuredErrors(); |
50 | 0 | for (auto const& structuredError : structuredErrors) { |
51 | 0 | this->AddErrorAtOffset(structuredError.message, |
52 | 0 | structuredError.offset_start); |
53 | 0 | } |
54 | | #else |
55 | | // No StructuredError Available, Use error string from jsonCpp |
56 | | if (!Json::parseFromStream(builder, fin, root, &errMsg)) { |
57 | | errMsg = cmStrCat("JSON Parse Error: ", this->Filename, ":\n", errMsg); |
58 | | this->AddError(errMsg); |
59 | | } |
60 | | #endif |
61 | 0 | } |
62 | | |
63 | | void cmJSONState::AddError(std::string const& errMsg) |
64 | 0 | { |
65 | 0 | this->errors.emplace_back(errMsg); |
66 | 0 | } |
67 | | |
68 | | void cmJSONState::AddErrorAtValue(std::string const& errMsg, |
69 | | Json::Value const* value) |
70 | 0 | { |
71 | 0 | if (value && !value->isNull()) { |
72 | 0 | this->AddErrorAtOffset(errMsg, value->getOffsetStart()); |
73 | 0 | } else { |
74 | 0 | this->AddError(errMsg); |
75 | 0 | } |
76 | 0 | } |
77 | | |
78 | | void cmJSONState::AddErrorAtOffset(std::string const& errMsg, |
79 | | std::ptrdiff_t offset) |
80 | 0 | { |
81 | 0 | if (doc.empty()) { |
82 | 0 | this->AddError(errMsg); |
83 | 0 | } else { |
84 | 0 | Location loc = LocateInDocument(offset); |
85 | 0 | this->errors.emplace_back(loc, errMsg); |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | | std::string cmJSONState::GetErrorMessage(bool showContext) |
90 | 0 | { |
91 | 0 | std::string message; |
92 | 0 | std::string filenameName = cmSystemTools::GetFilenameName(this->Filename); |
93 | 0 | for (auto const& error : this->errors) { |
94 | 0 | Location loc = error.GetLocation(); |
95 | 0 | if (!filenameName.empty() && loc.line > 0) { |
96 | 0 | message = cmStrCat(message, filenameName, ':', loc.line, ": "); |
97 | 0 | } |
98 | 0 | message = cmStrCat(message, error.GetErrorMessage(), '\n'); |
99 | 0 | if (showContext && loc.line > 0) { |
100 | 0 | message = cmStrCat(message, GetJsonContext(loc), '\n'); |
101 | 0 | } |
102 | 0 | } |
103 | 0 | message.pop_back(); |
104 | 0 | return message; |
105 | 0 | } |
106 | | |
107 | | std::string cmJSONState::key() |
108 | 0 | { |
109 | 0 | if (!this->parseStack.empty()) { |
110 | 0 | return this->parseStack.back().first; |
111 | 0 | } |
112 | 0 | return ""; |
113 | 0 | } |
114 | | |
115 | | std::string cmJSONState::key_after(std::string const& k) |
116 | 0 | { |
117 | 0 | for (auto it = this->parseStack.begin(); it != this->parseStack.end(); |
118 | 0 | ++it) { |
119 | 0 | if (it->first == k && (++it) != this->parseStack.end()) { |
120 | 0 | return it->first; |
121 | 0 | } |
122 | 0 | } |
123 | 0 | return ""; |
124 | 0 | } |
125 | | |
126 | | Json::Value const* cmJSONState::value_after(std::string const& k) |
127 | 0 | { |
128 | 0 | for (auto it = this->parseStack.begin(); it != this->parseStack.end(); |
129 | 0 | ++it) { |
130 | 0 | if (it->first == k && (++it) != this->parseStack.end()) { |
131 | 0 | return it->second; |
132 | 0 | } |
133 | 0 | } |
134 | 0 | return nullptr; |
135 | 0 | } |
136 | | |
137 | | void cmJSONState::push_stack(std::string const& k, Json::Value const* value) |
138 | 0 | { |
139 | 0 | this->parseStack.emplace_back(k, value); |
140 | 0 | } |
141 | | |
142 | | void cmJSONState::pop_stack() |
143 | 0 | { |
144 | 0 | this->parseStack.pop_back(); |
145 | 0 | } |
146 | | |
147 | | std::string cmJSONState::GetJsonContext(Location loc) |
148 | 0 | { |
149 | 0 | std::string line; |
150 | 0 | std::stringstream sstream(doc); |
151 | 0 | for (int i = 0; i < loc.line; ++i) { |
152 | 0 | std::getline(sstream, line, '\n'); |
153 | 0 | } |
154 | 0 | return cmStrCat(line, '\n', std::string(loc.column - 1, ' '), '^'); |
155 | 0 | } |
156 | | |
157 | | cmJSONState::Location cmJSONState::LocateInDocument(ptrdiff_t offset) |
158 | 0 | { |
159 | 0 | int line = 1; |
160 | 0 | int col = 1; |
161 | 0 | char const* beginDoc = doc.data(); |
162 | 0 | char const* last = beginDoc + offset; |
163 | 0 | for (; beginDoc != last; ++beginDoc) { |
164 | 0 | switch (*beginDoc) { |
165 | 0 | case '\r': |
166 | 0 | if (beginDoc + 1 != last && beginDoc[1] == '\n') { |
167 | 0 | continue; // consume CRLF as a single token. |
168 | 0 | } |
169 | 0 | CM_FALLTHROUGH; // CR without a following LF is same as LF |
170 | 0 | case '\n': |
171 | 0 | col = 1; |
172 | 0 | ++line; |
173 | 0 | break; |
174 | 0 | default: |
175 | 0 | ++col; |
176 | 0 | break; |
177 | 0 | } |
178 | 0 | } |
179 | 0 | return { line, col }; |
180 | 0 | } |