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