33#include < iostream>
44#include < filesystem> // C++17
55#include < sstream>
6+ #include < algorithm>
67// //////////////////////////////////////////////////////////////////////////////
78
89namespace sjv
910{
11+
12+ // ////////// PUBLIC
13+
1014 bool SJV::verify_json (const json &input, const json &rules)
1115 {
1216 log.clear ();
1317 return verify_json (" /" , input, rules);
14- }
18+ };
19+
20+ json SJV::inject_defaults (const json &input, const json &rules)
21+ {
22+ // The code below assumes that the input satisfies the rules
23+ assert (verify_json (input, rules));
24+
25+ // Find all the default rules
26+ json default_rules = collect_default_rules (rules);
27+
28+ // Flatten the input
29+ json flat = input.flatten ();
30+ json out_flat = flat;
31+
32+ // For each rule, go over all entries of a flattened input
33+ for (auto rule : default_rules)
34+ {
35+ for (auto e : flat.items ())
36+ {
37+ // If the pointer matches a strict subset of it, add it to the flattened
38+ std::tuple<bool , string> subset = is_subset_pointer (e.key (), string (rule[" pointer" ]));
39+ if (std::get<0 >(subset))
40+ if (!out_flat.contains (std::get<1 >(subset)))
41+ out_flat[std::get<1 >(subset)] = rule[" default" ];
42+
43+ // Try it also without the last entry to capture object types which are empty
44+ string key_parent = e.key ().substr (0 ,e.key ().find_last_of (" /\\ " ));
45+
46+ subset = is_subset_pointer (key_parent, string (rule[" pointer" ]));
47+ if (std::get<0 >(subset))
48+ if (!out_flat.contains (std::get<1 >(subset)))
49+ out_flat[std::get<1 >(subset)] = rule[" default" ];
50+
51+ }
52+ // Special case as "/" is not inserted in the flat representation
53+ std::tuple<bool , string> subset = is_subset_pointer (" /" , string (rule[" pointer" ]));
54+ if (std::get<0 >(subset))
55+ if (!out_flat.contains (std::get<1 >(subset)))
56+ out_flat[std::get<1 >(subset)] = rule[" default" ];
57+ }
58+
59+ // Unflatten the input
60+ json output = out_flat.unflatten ();
61+
62+ // Certify the validity of the final file
63+ assert (verify_json (input, rules));
64+
65+ return output;
66+ };
67+
68+ std::string SJV::log2str ()
69+ {
70+ std::stringstream s;
71+
72+ for (log_item i : log)
73+ {
74+ s << i.first << " : " << i.second << std::endl;
75+ }
76+ return s.str ();
77+ };
78+
79+ // ////////// PRIVATE
1580
1681 bool SJV::verify_json (const string &pointer, const json &input, const json &rules)
1782 {
83+ // if (pointer == "/geometry/*/surface_selection/*")
84+ // std::cout << "gotcha" << std::endl;
1885 // Find all rules that apply for the input node
1986 // TODO: accelerate this
2087 std::vector<json> matching_rules = collect_pointer (pointer, rules);
@@ -26,16 +93,24 @@ namespace sjv
2693
2794 if (strict)
2895 return false ;
96+ else
97+ return true ;
2998 }
3099
31100 // Test all rules, only one must pass, otherwise throw exception
32101 int count = 0 ;
102+ json single_matched_rule;
33103
34104 for (auto i : matching_rules)
105+ {
35106 if (verify_rule (input, i))
107+ {
36108 count++;
109+ single_matched_rule = i;
110+ }
111+ }
37112
38- if (count == 0 && !matching_rules.empty ())
113+ if (count == 0 && !matching_rules.empty ())
39114 {
40115 // Before giving up, try boxing a primitive type
41116 if (boxing_primitive && !input.is_array ())
@@ -44,7 +119,7 @@ namespace sjv
44119 // Make sure there are some rules for the boxed version before recursively checking
45120 if (collect_pointer (new_pointer, rules).size () > 0 )
46121 if (verify_json (new_pointer, input, rules))
47- return true ;
122+ return true ;
48123 }
49124
50125 std::stringstream s;
@@ -74,7 +149,7 @@ namespace sjv
74149 json defaults = collect_default_rules (new_pointer, rules);
75150
76151 // if it is mandatory, make sure there are no defaults
77- if (matching_rules[ 0 ] .contains (" required" ) && contained_in_list (i.key (), matching_rules[ 0 ] [" required" ]))
152+ if (single_matched_rule .contains (" required" ) && contained_in_list (i.key (), single_matched_rule [" required" ]))
78153 {
79154 if (defaults.size () != 0 )
80155 {
@@ -83,7 +158,7 @@ namespace sjv
83158 }
84159 }
85160 // if it is optional, there should be only one default in the specs
86- else if (matching_rules[ 0 ] .contains (" optional" ) && contained_in_list (i.key (), matching_rules[ 0 ] [" optional" ]))
161+ else if (single_matched_rule .contains (" optional" ) && contained_in_list (i.key (), single_matched_rule [" optional" ]))
87162 {
88163 if (defaults.size () != 1 )
89164 {
@@ -274,17 +349,21 @@ namespace sjv
274349 return false ;
275350
276351 return true ;
277- }
352+ };
278353
279- std::string SJV::log2str ( )
354+ json SJV::collect_default_rules ( const json &rules )
280355 {
281- std::stringstream s;
356+ // Find all rules that apply for the input node
357+ // TODO: accelerate this
282358
283- for (log_item i : log)
359+ std::vector<json> matching_rules;
360+ for (auto i : rules)
284361 {
285- s << i.first << " : " << i.second << std::endl;
362+ if (i.contains (" default" ))
363+ matching_rules.push_back (i);
286364 }
287- return s.str ();
365+
366+ return matching_rules;
288367 };
289368
290369 json SJV::collect_default_rules (const string &pointer, const json &rules)
@@ -307,7 +386,7 @@ namespace sjv
307386 return std::find (list.begin (), list.end (), item) != list.end ();
308387 };
309388
310- std::vector<json> SJV::collect_pointer (const string& pointer, const json& rules)
389+ std::vector<json> SJV::collect_pointer (const string & pointer, const json & rules)
311390 {
312391 std::vector<json> matching_rules;
313392 for (auto i : rules)
@@ -317,4 +396,89 @@ namespace sjv
317396 }
318397 return matching_rules;
319398 };
399+
400+ json SJV::find_valid_rule (const string &pointer, const json &input, const json &rules)
401+ {
402+ for (auto i : collect_pointer (pointer, rules))
403+ if (verify_rule (input, i))
404+ return i;
405+
406+ return json ();
407+ };
408+
409+ std::tuple<bool , string> SJV::is_subset_pointer (const string &json, const string &pointer)
410+ {
411+ // Splits a string into tokens using the deliminator delim
412+ auto tokenize = [](std::string const &str, const char delim) {
413+ size_t start;
414+ size_t end = 0 ;
415+ std::vector<string> out;
416+
417+ while ((start = str.find_first_not_of (delim, end)) != std::string::npos)
418+ {
419+ end = str.find (delim, start);
420+ out.push_back (str.substr (start, end - start));
421+ }
422+
423+ return out;
424+ };
425+
426+ // Replaces occurrences of a substring
427+ auto replace_all = [](std::string str, const std::string &from, const std::string &to) {
428+ size_t start_pos = 0 ;
429+ while ((start_pos = str.find (from, start_pos)) != std::string::npos)
430+ {
431+ str.replace (start_pos, from.length (), to);
432+ start_pos += to.length (); // Handles case where 'to' is a substring of 'from'
433+ }
434+ return str;
435+ };
436+
437+ // Check if a string is an integer
438+ auto is_number = [](const string &str) {
439+ for (char const &c : str)
440+ if (std::isdigit (c) == 0 )
441+ return false ;
442+ return true ;
443+ };
444+
445+ // Tokenize json_pointer
446+ std::vector<string> json_t = tokenize (json, ' /' );
447+ std::vector<string> pointer_t = tokenize (pointer, ' /' );
448+
449+ // if the json is not shorter or if there are no tokens, give up
450+ if ((json_t .size () >= pointer_t .size ())
451+ || (pointer_t .size () == 0 ))
452+ return {false , " " };
453+
454+ std::string buf = " " ;
455+ // if it is shorter, match every entry
456+ for (unsigned i = 0 ; i < pointer_t .size (); ++i)
457+ {
458+ // if there is no corresponding entry on json, copy and move on
459+ if (json_t .size () <= i)
460+ {
461+ buf.append (" /" + replace_all (pointer_t [i], " *" , " 0" ));
462+ }
463+ // if there is an entry on json and it is the same, move on
464+ else if (json_t [i] == pointer_t [i])
465+ {
466+ buf.append (" /" + pointer_t [i]);
467+ }
468+ // if the pointer contains a star, then accept any integer on json
469+ else if (pointer_t [i] == " *" )
470+ {
471+ if (is_number (json_t [i]))
472+ buf.append (" /" + json_t [i]);
473+ }
474+ // if no rule matches it is not a match
475+ else
476+ {
477+ return {false , " " };
478+ }
479+ }
480+
481+ return {true , buf};
482+ };
483+
320484} // namespace sjv
0 commit comments