1 // Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
2 // Distributed under MIT license, or public domain if desired and
3 // recognized in your jurisdiction.
4 // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
5 
6 #if defined(__GNUC__)
7 #pragma GCC diagnostic push
8 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
9 #elif defined(_MSC_VER)
10 #pragma warning(disable : 4996)
11 #endif
12 
13 /* This executable is used for testing parser/writer using real JSON files.
14  */
15 
16 #include <algorithm> // sort
17 #include <cstdio>
18 #include <json/json.h>
19 #include <sstream>
20 
21 struct Options {
22   Json::String path;
23   Json::Features features;
24   bool parseOnly;
25   using writeFuncType = Json::String (*)(Json::Value const&);
26   writeFuncType write;
27 };
28 
normalizeFloatingPointStr(double value)29 static Json::String normalizeFloatingPointStr(double value) {
30   char buffer[32];
31   jsoncpp_snprintf(buffer, sizeof(buffer), "%.16g", value);
32   buffer[sizeof(buffer) - 1] = 0;
33   Json::String s(buffer);
34   Json::String::size_type index = s.find_last_of("eE");
35   if (index != Json::String::npos) {
36     Json::String::size_type hasSign =
37         (s[index + 1] == '+' || s[index + 1] == '-') ? 1 : 0;
38     Json::String::size_type exponentStartIndex = index + 1 + hasSign;
39     Json::String normalized = s.substr(0, exponentStartIndex);
40     Json::String::size_type indexDigit =
41         s.find_first_not_of('0', exponentStartIndex);
42     Json::String exponent = "0";
43     if (indexDigit != Json::String::npos) // There is an exponent different
44                                           // from 0
45     {
46       exponent = s.substr(indexDigit);
47     }
48     return normalized + exponent;
49   }
50   return s;
51 }
52 
readInputTestFile(const char * path)53 static Json::String readInputTestFile(const char* path) {
54   FILE* file = fopen(path, "rb");
55   if (!file)
56     return "";
57   fseek(file, 0, SEEK_END);
58   long const size = ftell(file);
59   size_t const usize = static_cast<unsigned long>(size);
60   fseek(file, 0, SEEK_SET);
61   char* buffer = new char[size + 1];
62   buffer[size] = 0;
63   Json::String text;
64   if (fread(buffer, 1, usize, file) == usize)
65     text = buffer;
66   fclose(file);
67   delete[] buffer;
68   return text;
69 }
70 
71 static void
printValueTree(FILE * fout,Json::Value & value,const Json::String & path=".")72 printValueTree(FILE* fout, Json::Value& value, const Json::String& path = ".") {
73   if (value.hasComment(Json::commentBefore)) {
74     fprintf(fout, "%s\n", value.getComment(Json::commentBefore).c_str());
75   }
76   switch (value.type()) {
77   case Json::nullValue:
78     fprintf(fout, "%s=null\n", path.c_str());
79     break;
80   case Json::intValue:
81     fprintf(fout, "%s=%s\n", path.c_str(),
82             Json::valueToString(value.asLargestInt()).c_str());
83     break;
84   case Json::uintValue:
85     fprintf(fout, "%s=%s\n", path.c_str(),
86             Json::valueToString(value.asLargestUInt()).c_str());
87     break;
88   case Json::realValue:
89     fprintf(fout, "%s=%s\n", path.c_str(),
90             normalizeFloatingPointStr(value.asDouble()).c_str());
91     break;
92   case Json::stringValue:
93     fprintf(fout, "%s=\"%s\"\n", path.c_str(), value.asString().c_str());
94     break;
95   case Json::booleanValue:
96     fprintf(fout, "%s=%s\n", path.c_str(), value.asBool() ? "true" : "false");
97     break;
98   case Json::arrayValue: {
99     fprintf(fout, "%s=[]\n", path.c_str());
100     Json::ArrayIndex size = value.size();
101     for (Json::ArrayIndex index = 0; index < size; ++index) {
102       static char buffer[16];
103       jsoncpp_snprintf(buffer, sizeof(buffer), "[%u]", index);
104       printValueTree(fout, value[index], path + buffer);
105     }
106   } break;
107   case Json::objectValue: {
108     fprintf(fout, "%s={}\n", path.c_str());
109     Json::Value::Members members(value.getMemberNames());
110     std::sort(members.begin(), members.end());
111     Json::String suffix = *(path.end() - 1) == '.' ? "" : ".";
112     for (auto name : members) {
113       printValueTree(fout, value[name], path + suffix + name);
114     }
115   } break;
116   default:
117     break;
118   }
119 
120   if (value.hasComment(Json::commentAfter)) {
121     fprintf(fout, "%s\n", value.getComment(Json::commentAfter).c_str());
122   }
123 }
124 
parseAndSaveValueTree(const Json::String & input,const Json::String & actual,const Json::String & kind,const Json::Features & features,bool parseOnly,Json::Value * root)125 static int parseAndSaveValueTree(const Json::String& input,
126                                  const Json::String& actual,
127                                  const Json::String& kind,
128                                  const Json::Features& features,
129                                  bool parseOnly,
130                                  Json::Value* root) {
131   Json::Reader reader(features);
132   bool parsingSuccessful =
133       reader.parse(input.data(), input.data() + input.size(), *root);
134   if (!parsingSuccessful) {
135     printf("Failed to parse %s file: \n%s\n", kind.c_str(),
136            reader.getFormattedErrorMessages().c_str());
137     return 1;
138   }
139   if (!parseOnly) {
140     FILE* factual = fopen(actual.c_str(), "wt");
141     if (!factual) {
142       printf("Failed to create %s actual file.\n", kind.c_str());
143       return 2;
144     }
145     printValueTree(factual, *root);
146     fclose(factual);
147   }
148   return 0;
149 }
150 // static Json::String useFastWriter(Json::Value const& root) {
151 //   Json::FastWriter writer;
152 //   writer.enableYAMLCompatibility();
153 //   return writer.write(root);
154 // }
useStyledWriter(Json::Value const & root)155 static Json::String useStyledWriter(Json::Value const& root) {
156   Json::StyledWriter writer;
157   return writer.write(root);
158 }
useStyledStreamWriter(Json::Value const & root)159 static Json::String useStyledStreamWriter(Json::Value const& root) {
160   Json::StyledStreamWriter writer;
161   Json::OStringStream sout;
162   writer.write(sout, root);
163   return sout.str();
164 }
useBuiltStyledStreamWriter(Json::Value const & root)165 static Json::String useBuiltStyledStreamWriter(Json::Value const& root) {
166   Json::StreamWriterBuilder builder;
167   return Json::writeString(builder, root);
168 }
rewriteValueTree(const Json::String & rewritePath,const Json::Value & root,Options::writeFuncType write,Json::String * rewrite)169 static int rewriteValueTree(const Json::String& rewritePath,
170                             const Json::Value& root,
171                             Options::writeFuncType write,
172                             Json::String* rewrite) {
173   *rewrite = write(root);
174   FILE* fout = fopen(rewritePath.c_str(), "wt");
175   if (!fout) {
176     printf("Failed to create rewrite file: %s\n", rewritePath.c_str());
177     return 2;
178   }
179   fprintf(fout, "%s\n", rewrite->c_str());
180   fclose(fout);
181   return 0;
182 }
183 
removeSuffix(const Json::String & path,const Json::String & extension)184 static Json::String removeSuffix(const Json::String& path,
185                                  const Json::String& extension) {
186   if (extension.length() >= path.length())
187     return Json::String("");
188   Json::String suffix = path.substr(path.length() - extension.length());
189   if (suffix != extension)
190     return Json::String("");
191   return path.substr(0, path.length() - extension.length());
192 }
193 
printConfig()194 static void printConfig() {
195 // Print the configuration used to compile JsonCpp
196 #if defined(JSON_NO_INT64)
197   printf("JSON_NO_INT64=1\n");
198 #else
199   printf("JSON_NO_INT64=0\n");
200 #endif
201 }
202 
printUsage(const char * argv[])203 static int printUsage(const char* argv[]) {
204   printf("Usage: %s [--strict] input-json-file", argv[0]);
205   return 3;
206 }
207 
parseCommandLine(int argc,const char * argv[],Options * opts)208 static int parseCommandLine(int argc, const char* argv[], Options* opts) {
209   opts->parseOnly = false;
210   opts->write = &useStyledWriter;
211   if (argc < 2) {
212     return printUsage(argv);
213   }
214   int index = 1;
215   if (Json::String(argv[index]) == "--json-checker") {
216     opts->features = Json::Features::strictMode();
217     opts->parseOnly = true;
218     ++index;
219   }
220   if (Json::String(argv[index]) == "--json-config") {
221     printConfig();
222     return 3;
223   }
224   if (Json::String(argv[index]) == "--json-writer") {
225     ++index;
226     Json::String const writerName(argv[index++]);
227     if (writerName == "StyledWriter") {
228       opts->write = &useStyledWriter;
229     } else if (writerName == "StyledStreamWriter") {
230       opts->write = &useStyledStreamWriter;
231     } else if (writerName == "BuiltStyledStreamWriter") {
232       opts->write = &useBuiltStyledStreamWriter;
233     } else {
234       printf("Unknown '--json-writer %s'\n", writerName.c_str());
235       return 4;
236     }
237   }
238   if (index == argc || index + 1 < argc) {
239     return printUsage(argv);
240   }
241   opts->path = argv[index];
242   return 0;
243 }
runTest(Options const & opts)244 static int runTest(Options const& opts) {
245   int exitCode = 0;
246 
247   Json::String input = readInputTestFile(opts.path.c_str());
248   if (input.empty()) {
249     printf("Failed to read input or empty input: %s\n", opts.path.c_str());
250     return 3;
251   }
252 
253   Json::String basePath = removeSuffix(opts.path, ".json");
254   if (!opts.parseOnly && basePath.empty()) {
255     printf("Bad input path. Path does not end with '.expected':\n%s\n",
256            opts.path.c_str());
257     return 3;
258   }
259 
260   Json::String const actualPath = basePath + ".actual";
261   Json::String const rewritePath = basePath + ".rewrite";
262   Json::String const rewriteActualPath = basePath + ".actual-rewrite";
263 
264   Json::Value root;
265   exitCode = parseAndSaveValueTree(input, actualPath, "input", opts.features,
266                                    opts.parseOnly, &root);
267   if (exitCode || opts.parseOnly) {
268     return exitCode;
269   }
270   Json::String rewrite;
271   exitCode = rewriteValueTree(rewritePath, root, opts.write, &rewrite);
272   if (exitCode) {
273     return exitCode;
274   }
275   Json::Value rewriteRoot;
276   exitCode = parseAndSaveValueTree(rewrite, rewriteActualPath, "rewrite",
277                                    opts.features, opts.parseOnly, &rewriteRoot);
278   if (exitCode) {
279     return exitCode;
280   }
281   return 0;
282 }
main(int argc,const char * argv[])283 int main(int argc, const char* argv[]) {
284   Options opts;
285   try {
286     int exitCode = parseCommandLine(argc, argv, &opts);
287     if (exitCode != 0) {
288       printf("Failed to parse command-line.");
289       return exitCode;
290     }
291     return runTest(opts);
292   } catch (const std::exception& e) {
293     printf("Unhandled exception:\n%s\n", e.what());
294     return 1;
295   }
296 }
297 
298 #if defined(__GNUC__)
299 #pragma GCC diagnostic pop
300 #endif
301