• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <iostream>
19 #include <json/json.h>
20 #include <memory>
21 #include <sstream>
22 
23 struct Options {
24   Json::String path;
25   Json::Features features;
26   bool parseOnly;
27 
28   typedef Json::String (*writeFuncType)(Json::Value const&);
29 
30   writeFuncType write;
31 };
32 
normalizeFloatingPointStr(double value)33 static Json::String normalizeFloatingPointStr(double value) {
34   char buffer[32];
35   jsoncpp_snprintf(buffer, sizeof(buffer), "%.16g", value);
36   buffer[sizeof(buffer) - 1] = 0;
37   Json::String s(buffer);
38   Json::String::size_type index = s.find_last_of("eE");
39   if (index != Json::String::npos) {
40     Json::String::size_type hasSign =
41         (s[index + 1] == '+' || s[index + 1] == '-') ? 1 : 0;
42     Json::String::size_type exponentStartIndex = index + 1 + hasSign;
43     Json::String normalized = s.substr(0, exponentStartIndex);
44     Json::String::size_type indexDigit =
45         s.find_first_not_of('0', exponentStartIndex);
46     Json::String exponent = "0";
47     if (indexDigit != Json::String::npos) // There is an exponent different
48                                           // from 0
49     {
50       exponent = s.substr(indexDigit);
51     }
52     return normalized + exponent;
53   }
54   return s;
55 }
56 
readInputTestFile(const char * path)57 static Json::String readInputTestFile(const char* path) {
58   FILE* file = fopen(path, "rb");
59   if (!file)
60     return "";
61   fseek(file, 0, SEEK_END);
62   long const size = ftell(file);
63   size_t const usize = static_cast<size_t>(size);
64   fseek(file, 0, SEEK_SET);
65   char* buffer = new char[usize + 1];
66   buffer[usize] = 0;
67   Json::String text;
68   if (fread(buffer, 1, usize, file) == usize)
69     text = buffer;
70   fclose(file);
71   delete[] buffer;
72   return text;
73 }
74 
printValueTree(FILE * fout,Json::Value & value,const Json::String & path=".")75 static void printValueTree(FILE* fout, Json::Value& value,
76                            const Json::String& path = ".") {
77   if (value.hasComment(Json::commentBefore)) {
78     fprintf(fout, "%s\n", value.getComment(Json::commentBefore).c_str());
79   }
80   switch (value.type()) {
81   case Json::nullValue:
82     fprintf(fout, "%s=null\n", path.c_str());
83     break;
84   case Json::intValue:
85     fprintf(fout, "%s=%s\n", path.c_str(),
86             Json::valueToString(value.asLargestInt()).c_str());
87     break;
88   case Json::uintValue:
89     fprintf(fout, "%s=%s\n", path.c_str(),
90             Json::valueToString(value.asLargestUInt()).c_str());
91     break;
92   case Json::realValue:
93     fprintf(fout, "%s=%s\n", path.c_str(),
94             normalizeFloatingPointStr(value.asDouble()).c_str());
95     break;
96   case Json::stringValue:
97     fprintf(fout, "%s=\"%s\"\n", path.c_str(), value.asString().c_str());
98     break;
99   case Json::booleanValue:
100     fprintf(fout, "%s=%s\n", path.c_str(), value.asBool() ? "true" : "false");
101     break;
102   case Json::arrayValue: {
103     fprintf(fout, "%s=[]\n", path.c_str());
104     Json::ArrayIndex size = value.size();
105     for (Json::ArrayIndex index = 0; index < size; ++index) {
106       static char buffer[16];
107       jsoncpp_snprintf(buffer, sizeof(buffer), "[%u]", index);
108       printValueTree(fout, value[index], path + buffer);
109     }
110   } break;
111   case Json::objectValue: {
112     fprintf(fout, "%s={}\n", path.c_str());
113     Json::Value::Members members(value.getMemberNames());
114     std::sort(members.begin(), members.end());
115     Json::String suffix = *(path.end() - 1) == '.' ? "" : ".";
116     for (Json::Value::Members::const_iterator it = members.begin();
117          it != members.end(); it++) {
118       const Json::String& name = *it;
119       printValueTree(fout, value[name], path + suffix + name);
120     }
121   } break;
122   default:
123     break;
124   }
125 
126   if (value.hasComment(Json::commentAfter)) {
127     fprintf(fout, "%s\n", value.getComment(Json::commentAfter).c_str());
128   }
129 }
130 
parseAndSaveValueTree(const Json::String & input,const Json::String & actual,const Json::String & kind,const Json::Features & features,bool parseOnly,Json::Value * root,bool use_legacy)131 static int parseAndSaveValueTree(const Json::String& input,
132                                  const Json::String& actual,
133                                  const Json::String& kind,
134                                  const Json::Features& features, bool parseOnly,
135                                  Json::Value* root, bool use_legacy) {
136   if (!use_legacy) {
137     Json::CharReaderBuilder builder;
138 
139     builder.settings_["allowComments"] = features.allowComments_;
140     builder.settings_["strictRoot"] = features.strictRoot_;
141     builder.settings_["allowDroppedNullPlaceholders"] =
142         features.allowDroppedNullPlaceholders_;
143     builder.settings_["allowNumericKeys"] = features.allowNumericKeys_;
144 
145     Json::CharReader* reader(builder.newCharReader());
146     Json::String errors;
147     const bool parsingSuccessful =
148         reader->parse(input.data(), input.data() + input.size(), root, &errors);
149 
150     if (!parsingSuccessful) {
151       std::cerr << "Failed to parse " << kind << " file: " << std::endl
152                 << errors << std::endl;
153       return 1;
154     }
155     delete reader;
156     // We may instead check the legacy implementation (to ensure it doesn't
157     // randomly get broken).
158   } else {
159     Json::Reader reader(features);
160     const bool parsingSuccessful =
161         reader.parse(input.data(), input.data() + input.size(), *root);
162     if (!parsingSuccessful) {
163       std::cerr << "Failed to parse " << kind << " file: " << std::endl
164                 << reader.getFormatedErrorMessages() << std::endl;
165       return 1;
166     }
167   }
168 
169   if (!parseOnly) {
170     FILE* factual = fopen(actual.c_str(), "wt");
171     if (!factual) {
172       std::cerr << "Failed to create '" << kind << "' actual file."
173                 << std::endl;
174       return 2;
175     }
176     printValueTree(factual, *root);
177     fclose(factual);
178   }
179   return 0;
180 }
181 // static Json::String useFastWriter(Json::Value const& root) {
182 //   Json::FastWriter writer;
183 //   writer.enableYAMLCompatibility();
184 //   return writer.write(root);
185 // }
useStyledWriter(Json::Value const & root)186 static Json::String useStyledWriter(Json::Value const& root) {
187   Json::StyledWriter writer;
188   return writer.write(root);
189 }
useStyledStreamWriter(Json::Value const & root)190 static Json::String useStyledStreamWriter(Json::Value const& root) {
191   Json::StyledStreamWriter writer;
192   Json::OStringStream sout;
193   writer.write(sout, root);
194   return sout.str();
195 }
useBuiltStyledStreamWriter(Json::Value const & root)196 static Json::String useBuiltStyledStreamWriter(Json::Value const& root) {
197   Json::StreamWriterBuilder builder;
198   return Json::writeString(builder, root);
199 }
rewriteValueTree(const Json::String & rewritePath,const Json::Value & root,Options::writeFuncType write,Json::String * rewrite)200 static int rewriteValueTree(const Json::String& rewritePath,
201                             const Json::Value& root,
202                             Options::writeFuncType write,
203                             Json::String* rewrite) {
204   *rewrite = write(root);
205   FILE* fout = fopen(rewritePath.c_str(), "wt");
206   if (!fout) {
207     std::cerr << "Failed to create rewrite file: " << rewritePath << std::endl;
208     return 2;
209   }
210   fprintf(fout, "%s\n", rewrite->c_str());
211   fclose(fout);
212   return 0;
213 }
214 
removeSuffix(const Json::String & path,const Json::String & extension)215 static Json::String removeSuffix(const Json::String& path,
216                                  const Json::String& extension) {
217   if (extension.length() >= path.length())
218     return Json::String("");
219   Json::String suffix = path.substr(path.length() - extension.length());
220   if (suffix != extension)
221     return Json::String("");
222   return path.substr(0, path.length() - extension.length());
223 }
224 
printConfig()225 static void printConfig() {
226 // Print the configuration used to compile JsonCpp
227 #if defined(JSON_NO_INT64)
228   std::cout << "JSON_NO_INT64=1" << std::endl;
229 #else
230   std::cout << "JSON_NO_INT64=0" << std::endl;
231 #endif
232 }
233 
printUsage(const char * argv[])234 static int printUsage(const char* argv[]) {
235   std::cout << "Usage: " << argv[0] << " [--strict] input-json-file"
236             << std::endl;
237   return 3;
238 }
239 
parseCommandLine(int argc,const char * argv[],Options * opts)240 static int parseCommandLine(int argc, const char* argv[], Options* opts) {
241   opts->parseOnly = false;
242   opts->write = &useStyledWriter;
243   if (argc < 2) {
244     return printUsage(argv);
245   }
246   int index = 1;
247   if (Json::String(argv[index]) == "--json-checker") {
248     opts->features = Json::Features::strictMode();
249     opts->parseOnly = true;
250     ++index;
251   }
252   if (Json::String(argv[index]) == "--json-config") {
253     printConfig();
254     return 3;
255   }
256   if (Json::String(argv[index]) == "--json-writer") {
257     ++index;
258     Json::String const writerName(argv[index++]);
259     if (writerName == "StyledWriter") {
260       opts->write = &useStyledWriter;
261     } else if (writerName == "StyledStreamWriter") {
262       opts->write = &useStyledStreamWriter;
263     } else if (writerName == "BuiltStyledStreamWriter") {
264       opts->write = &useBuiltStyledStreamWriter;
265     } else {
266       std::cerr << "Unknown '--json-writer' " << writerName << std::endl;
267       return 4;
268     }
269   }
270   if (index == argc || index + 1 < argc) {
271     return printUsage(argv);
272   }
273   opts->path = argv[index];
274   return 0;
275 }
276 
runTest(Options const & opts,bool use_legacy)277 static int runTest(Options const& opts, bool use_legacy) {
278   int exitCode = 0;
279 
280   Json::String input = readInputTestFile(opts.path.c_str());
281   if (input.empty()) {
282     std::cerr << "Invalid input file: " << opts.path << std::endl;
283     return 3;
284   }
285 
286   Json::String basePath = removeSuffix(opts.path, ".json");
287   if (!opts.parseOnly && basePath.empty()) {
288     std::cerr << "Bad input path '" << opts.path
289               << "'. Must end with '.expected'" << std::endl;
290     return 3;
291   }
292 
293   Json::String const actualPath = basePath + ".actual";
294   Json::String const rewritePath = basePath + ".rewrite";
295   Json::String const rewriteActualPath = basePath + ".actual-rewrite";
296 
297   Json::Value root;
298   exitCode = parseAndSaveValueTree(input, actualPath, "input", opts.features,
299                                    opts.parseOnly, &root, use_legacy);
300   if (exitCode || opts.parseOnly) {
301     return exitCode;
302   }
303 
304   Json::String rewrite;
305   exitCode = rewriteValueTree(rewritePath, root, opts.write, &rewrite);
306   if (exitCode) {
307     return exitCode;
308   }
309 
310   Json::Value rewriteRoot;
311   exitCode = parseAndSaveValueTree(rewrite, rewriteActualPath, "rewrite",
312                                    opts.features, opts.parseOnly, &rewriteRoot,
313                                    use_legacy);
314 
315   return exitCode;
316 }
317 
main(int argc,const char * argv[])318 int main(int argc, const char* argv[]) {
319   Options opts;
320   try {
321     int exitCode = parseCommandLine(argc, argv, &opts);
322     if (exitCode != 0) {
323       std::cerr << "Failed to parse command-line." << std::endl;
324       return exitCode;
325     }
326 
327     const int modern_return_code = runTest(opts, false);
328     if (modern_return_code) {
329       return modern_return_code;
330     }
331 
332     const std::string filename =
333         opts.path.substr(opts.path.find_last_of("\\/") + 1);
334     const bool should_run_legacy = (filename.rfind("legacy_", 0) == 0);
335     if (should_run_legacy) {
336       return runTest(opts, true);
337     }
338   } catch (const std::exception& e) {
339     std::cerr << "Unhandled exception:" << std::endl << e.what() << std::endl;
340     return 1;
341   }
342 }
343 
344 #if defined(__GNUC__)
345 #pragma GCC diagnostic pop
346 #endif
347