Cogs.Core
JsonParser.cpp
1#include "JsonParser.h"
2
3#include "Context.h"
4#include "Resources/ResourceStore.h"
5
6#include "Foundation/Logging/Logger.h"
7#include "Foundation/Platform/FileContents.h"
8
9#define _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING
10#include "rapidjson/reader.h"
11#include "rapidjson/error/en.h"
12#undef _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING
13
14#include <algorithm>
15#include <filesystem>
16#include "zstd.h"
17
18namespace
19{
20 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("JsonParser");
21}
22
23namespace Cogs
24{
25 size_t countLines(const StringView & source, size_t /*offset*/)
26 {
27 size_t count = 0;
28
29 const char* data = source.data();
30
31 while (data != source.end()) {
32 if (*data == '\n') count++;
33 ++data;
34 }
35
36 return count;
37 }
38
39 const char * findLineOffset(const StringView & source, size_t offset, int direction)
40 {
41 int skip = direction > 0 ? 1 : -1;
42 int lines = 0;
43 int target = direction > 0 ? direction - 1 : -direction;
44
45 auto term = direction > 0 ? source.end() : source.begin();
46 auto cur = source.data() + offset;
47
48 if (direction == 1 && (*cur == '\n')) return cur + 1;
49 else if (direction == 1 && (*cur == '\r')) return cur + 2;
50
51 while (cur != term) {
52 if (*cur == '\n') {
53 if (++lines > target) {
54 ++cur;
55 break;
56 }
57 }
58 cur += skip;
59 }
60
61 return cur;
62 }
63
64 StringView findLine(const StringView & source, size_t offset, int direction)
65 {
66 if (source.empty()) return {};
67 if (offset >= source.size()) return {};
68
69 const char* begin = findLineOffset(source, offset, direction);
70 const char* end = findLineOffset(source, offset, direction + 1);
71 size_t len = std::distance(begin, end);
72
73 assert(len >= 0 && "JSON string offsets invalid.");
74
75 return StringView(begin, len > 0 ? len - 2 : 0);
76 }
77}
78
79Document Cogs::Core::parseJson(const StringView & content, JsonParseFlags flags)
80{
81 Document document;
82 Cogs::Memory::MemoryBuffer decompressed(MemBlockType::IOResource);
83 StringView jsonContent = content;
84
85 bool zstdMagic = (4 < content.size()) && (uint8_t(content[0])== 0x28 && uint8_t(content[1]) == 0xb5 && uint8_t(content[2]) == 0x2f && uint8_t(content[3]) == 0xfd);
86 // Handle JSON files compressed with ZSTD
87 if (zstdMagic ||((flags & JsonParseFlags::Compressed) == JsonParseFlags::Compressed))
88 {
89 LOG_TRACE(logger, "ZSTD detected");
90
91 ZSTD_DCtx* zstd_dctx = ZSTD_createDCtx();
92 assert(zstd_dctx);
93
94 const auto size = ZSTD_getFrameContentSize(content.data(), content.size());
95 if (size == ZSTD_CONTENTSIZE_UNKNOWN) {
96 LOG_ERROR(logger, "ZSTD_CONTENTSIZE_UNKNOWN");
97 return document;
98 }
99 else if (size == ZSTD_CONTENTSIZE_ERROR) {
100 LOG_ERROR(logger, "ZSTD_CONTENTSIZE_ERROR");
101 return document;
102 }
103#if defined( EMSCRIPTEN )
104 // 'size_t' and 'unsigned long long' are the same types on x64 platform.
105 // The 'if' statement is only valid on x32 (Emscripten).
106 else if (std::numeric_limits<size_t>::max() < size) {
107 LOG_ERROR(logger, "File too large: %s", std::to_string(size).c_str());
108 return document;
109 }
110#endif
111
112 decompressed.resize(static_cast<size_t>(size), false);
113 const auto dataSize = ZSTD_decompressDCtx(zstd_dctx, decompressed.data(), decompressed.size(), content.data(), content.size());
114 if (ZSTD_isError(dataSize)) {
115 LOG_ERROR(logger, "ZSTD decompression failed: %s", ZSTD_getErrorName(dataSize));
116 return document;
117 }
118
119 ZSTD_freeDCtx(zstd_dctx);
120 LOG_TRACE(logger, "ZSTD decoding done");
121
122 const void* dataPtr = decompressed.data();
123 jsonContent = StringView(static_cast<const char*>(dataPtr), dataSize);
124 }
125
126 // Parse JSON data
127 document.Parse<ParseFlag::kParseCommentsFlag | kParseTrailingCommasFlag>(jsonContent.data(), jsonContent.size());
128 if (document.HasParseError()) {
129 const ParseErrorCode errorCode = document.GetParseError();
130 const size_t offset = document.GetErrorOffset();
131
132 auto prevLine = findLine(jsonContent, offset, -1);
133 auto currLine = findLine(jsonContent, offset, 0);
134 auto nextLine = findLine(jsonContent, offset, 1);
135
136 if (currLine.size()) {
137 LOG_ERROR(logger, "Error parsing JSON: %s", GetParseError_En(errorCode));
138 auto lineNo = countLines(jsonContent, offset);
139
140 if (prevLine.size()) LOG_ERROR(logger, "%3zd: %.*s", lineNo - 1, StringViewFormat(prevLine));
141 if (currLine.size()) LOG_ERROR(logger, "%3zd: %.*s", lineNo, StringViewFormat(currLine));
142 auto len = (jsonContent.data() + offset) - currLine.data();
143 auto lineOffset = std::max((size_t)0, (size_t)len);
144
145 std::string spaces(lineOffset, ' ');
146 spaces += "^";
147
148 if (jsonContent[offset] == '\n') spaces += " (\\n)";
149 else if (jsonContent[offset] == '\r') spaces += " (\\r)";
150
151 LOG_ERROR(logger, " %s", spaces.c_str());
152
153 if (nextLine.size()) LOG_ERROR(logger, "%3zd: %.*s", lineNo + 1, StringViewFormat(nextLine));
154 } else {
155 LOG_ERROR(logger, "Error parsing JSON: %s", GetParseError_En(errorCode));
156 }
157 }
158
159 return document;
160}
161
162Document Cogs::Core::parseJson(Context * context, const StringView & fileName, JsonParseFlags flags)
163{
164 ResourceStoreFlags resourceFlags = ResourceStoreFlags::None;
165#ifndef EMSCRIPTEN
166 if ((flags & JsonParseFlags::NoCachedContent) == JsonParseFlags::NoCachedContent) {
167 resourceFlags |= ResourceStoreFlags::NoCachedContent;
168 }
169 else if ((flags & JsonParseFlags::PreferUncachedContent) == JsonParseFlags::PreferUncachedContent) {
170 resourceFlags |= ResourceStoreFlags::PreferUncachedContent;
171 }
172#endif
173
174 ResourceBuffer content = context->resourceStore->getResourceContents(fileName, resourceFlags);
175 if (!content.size()) {
176 LOG_ERROR(logger, "Could not open JSON file %.*s.", StringViewFormat(fileName));
177 return {};
178 }
179
180 return parseJson(content.toStringView(), flags);
181}
182
183Document Cogs::Core::parseJson(class Context* /*context*/, std::unique_ptr<FileContents> contents, JsonParseFlags flags)
184{
185 return parseJson(StringView(static_cast<const char*>(contents->data()), contents->size), flags);
186}
Log implementation class.
Definition: LogManager.h:139
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
Contains all Cogs related functionality.
Definition: FieldSetter.h:23