| 1 | % Heinrich Heine Universitaet Duesseldorf | |
| 2 | % (c) 2021-2026 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, | |
| 3 | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) | |
| 4 | ||
| 5 | :- module(visb_json_utils,[get_attr/3, get_attr_with_pos/5, | |
| 6 | del_attr/4, del_attr_with_pos/6, non_det_del_attr_with_pos/6, | |
| 7 | force_del_attr/5, force_del_attr_with_pos/6, | |
| 8 | get_attr_true/3, is_optional_json_item/1, is_optional_json_item/3, | |
| 9 | json_true_value/1, json_false_value/1, | |
| 10 | check_json_string/4, | |
| 11 | get_pos_from_list/3, construct_prob_pos_term/3, | |
| 12 | replace_in_json/4, multi_replace_in_json/4, | |
| 13 | check_all_same_length_json/3]). | |
| 14 | ||
| 15 | :- use_module(probsrc(module_information),[module_info/2]). | |
| 16 | :- module_info(group,visualization). | |
| 17 | :- module_info(description,'This module provides utilities to access and transform JSON terms for VisB.'). | |
| 18 | ||
| 19 | % Note: error messages use visb_visualiser as source, as they relate to VisB JSON files | |
| 20 | ||
| 21 | :- use_module(probsrc(error_manager)). | |
| 22 | :- use_module(probsrc(self_check)). | |
| 23 | :- use_module(probsrc(tools_strings), [ajoin/2]). | |
| 24 | :- use_module(library(lists)). | |
| 25 | ||
| 26 | % JSON terms as produced by the JSON parser with position_infos(true): | |
| 27 | % json(List) where List contains '='(Attr,Value,From-To) entries | |
| 28 | ||
| 29 | % ---------------- | |
| 30 | % accessing attributes | |
| 31 | ||
| 32 | get_attr(Attr,List,Val) :- member('='(Attr,Val,_),List). | |
| 33 | get_attr_with_pos(Attr,List,Val,File,Pos) :- | |
| 34 | El = '='(Attr,Val,_), | |
| 35 | member(El,List), | |
| 36 | get_pos_from_list([El],File,Pos). | |
| 37 | ||
| 38 | del_attr(Attr,List,Val,Rest) :- select('='(Attr,Val,_Pos),List,Rest). | |
| 39 | %del_attr_with_pos(Attr,List,Val,Rest,Pos) :- select('='(Attr,Val,Pos),List,Rest). | |
| 40 | del_attr_with_pos(Attr,List,Val,Rest,File,PosTerm) :- select('='(Attr,Val,From-To),List,Rest),!, | |
| 41 | PosTerm = src_position_with_filename_and_ec(From,1,To,1,File). | |
| 42 | non_det_del_attr_with_pos(Attr,List,Val,Rest,File,PosTerm) :- select('='(Attr,Val,From-To),List,Rest), | |
| 43 | PosTerm = src_position_with_filename_and_ec(From,1,To,1,File). | |
| 44 | ||
| 45 | force_del_attr(Attr,List,Val,Rest,File) :- force_del_attr_with_pos(Attr,List,Val,Rest,File,_). | |
| 46 | ||
| 47 | force_del_attr_with_pos(Attr,List,Val,Rest,File,PosTerm) :- select('='(Attr,Val,From-To),List,Rest),!, | |
| 48 | %TODO: we would ideally want to extract the exact column position of Val, for this we need to extend the JSON parser first | |
| 49 | PosTerm = src_position_with_filename_and_ec(From,1,To,1,File). | |
| 50 | force_del_attr_with_pos(Attr,List,_,_,File,_) :- get_pos_from_list(List,File,Pos),!, | |
| 51 | add_error(visb_visualiser,'The JSON object has no attribute:',Attr,Pos),fail. | |
| 52 | ||
| 53 | construct_prob_pos_term(From-To,File,PosTerm) :- !, | |
| 54 | PosTerm = src_position_with_filename_and_ec(From,1,To,1,File). | |
| 55 | construct_prob_pos_term(Pos,_,Pos). | |
| 56 | ||
| 57 | get_pos_from_list(json(List),File,Pos) :- !, get_pos_from_list(List,File,Pos). | |
| 58 | get_pos_from_list([H|T],File,RPos) :- | |
| 59 | H='='(_,_,From-_), | |
| 60 | last([H|T], | |
| 61 | '='(_,_,_-To)), | |
| 62 | !, | |
| 63 | RPos=src_position_with_filename_and_ec(From,1,To,1,File). | |
| 64 | get_pos_from_list(_,File,File). % should we provide another term? | |
| 65 | ||
| 66 | check_json_string(Term,_,_,Atom) :- Term = string(Atom), atom(Atom), !. | |
| 67 | check_json_string(Term,Attr,Pos,_) :- !, | |
| 68 | ajoin(['Illegal JSON attribute ',Attr,', expected a JSON string:'],Msg), | |
| 69 | add_error(visb_visualiser,Msg,Term,Pos),fail. | |
| 70 | ||
| 71 | % ---------------- | |
| 72 | % JSON boolean values and flags | |
| 73 | ||
| 74 | get_attr_true(Attr,JsonList,File) :- | |
| 75 | get_attr_with_pos(Attr,JsonList,TRUE,File,Pos), | |
| 76 | (json_true_value(TRUE) -> true | |
| 77 | ; json_false_value(TRUE) -> fail | |
| 78 | ; ajoin(['Value for attribute ',Attr,' is not true or false: '],Msg), | |
| 79 | add_warning(visb_visualiser,Msg,TRUE,Pos) | |
| 80 | ). | |
| 81 | ||
| 82 | % check if a JSON item has been marked as optional, e.g., "optional":true | |
| 83 | is_optional_json_item(JsonList) :- | |
| 84 | get_attr(optional,JsonList,Val), | |
| 85 | \+ json_false_value(Val). % optional not marked as false | |
| 86 | is_optional_json_item(JsonList,File,Pos) :- | |
| 87 | get_attr_with_pos(optional,JsonList,Val,File,Pos), \+ json_false_value(Val). | |
| 88 | ||
| 89 | json_true_value(@(true)). | |
| 90 | json_true_value(string(true)). | |
| 91 | json_true_value(number(1)). | |
| 92 | ||
| 93 | json_false_value(@(false)). | |
| 94 | json_false_value(string(false)). | |
| 95 | json_false_value(number(0)). | |
| 96 | ||
| 97 | % ---------------- | |
| 98 | % replacing %Nr patterns, used for for-loops and repeat in VisB JSON files | |
| 99 | ||
| 100 | check_all_same_length_json(List,ExpectedLen,Pos) :- | |
| 101 | nth1(Nr,List,Json), | |
| 102 | (Json=array(SubList),length(SubList,Len) -> Len \= ExpectedLen ; Len='not a list'), | |
| 103 | !, | |
| 104 | ajoin(['The element number ',Nr,' of the repeat list has not the expected length of ',ExpectedLen,', length is: '],Msg), | |
| 105 | add_warning(visb_visualiser,Msg,Len,Pos). | |
| 106 | check_all_same_length_json(_,_,_). | |
| 107 | ||
| 108 | json_value_to_codes(@(Literal), C) :- atom(Literal), !, atom_codes(Literal, C). | |
| 109 | json_value_to_codes(number(Number), C) :- number(Number), !, number_codes(Number, C). | |
| 110 | json_value_to_codes(string(String), C) :- atom(String), !, atom_codes(String, C). | |
| 111 | json_value_to_codes(Json, _) :- !, add_error(json_value_to_codes,'Cannot convert json term to codes:', Json), fail. | |
| 112 | ||
| 113 | % replace (possibly) multiple patterns at once | |
| 114 | multi_replace_in_json(array([]),_RepCount,JsonIn,JsonOut) :- !, JsonOut=JsonIn. | |
| 115 | multi_replace_in_json(array([IterElem|TIT]),RepCount,JsonIn,JsonOut) :- | |
| 116 | !, | |
| 117 | number_codes(RepCount,Pat), | |
| 118 | json_value_to_codes(IterElem,IterElemC), | |
| 119 | %format('Replacing in JSON: ~s -> ~s~n',[Pat,IterElemC]), | |
| 120 | replace_in_json(Pat,IterElemC,JsonIn,Json), | |
| 121 | R1 is RepCount+1, | |
| 122 | multi_replace_in_json(array(TIT),R1,Json,JsonOut). | |
| 123 | multi_replace_in_json(IterElem,RepCount,JsonIn,JsonOut) :- % a value, not a list | |
| 124 | !, | |
| 125 | number_codes(RepCount,Pat), | |
| 126 | json_value_to_codes(IterElem,IterElemC), | |
| 127 | %format('Replacing in JSON: ~s -> ~s~n',[Pat,IterElemC]), | |
| 128 | replace_in_json(Pat,IterElemC,JsonIn,JsonOut). | |
| 129 | ||
| 130 | % replace a pattern code prefixed by % by another code list | |
| 131 | replace_in_json(Pattern,RepStr,string(Val),string(NewVal)) :- !, replace_atom(Pattern,RepStr,Val,NewVal). | |
| 132 | replace_in_json(Pattern,RepStr,array(Vals),array(NewVals)) :- !, maplist(replace_in_json(Pattern,RepStr),Vals,NewVals). | |
| 133 | replace_in_json(Pattern,RepStr,json(Vals),json(NewVals)) :- !, maplist(replace_in_json_pair(Pattern,RepStr),Vals,NewVals). | |
| 134 | replace_in_json(_,_,Val,Val). % number/literal (no replacement necessary) | |
| 135 | ||
| 136 | replace_in_json_pair(Pattern,RepStr,'='(KIn,VIn,Pos),'='(KOut,VOut,Pos)) :- | |
| 137 | replace_atom(Pattern,RepStr,KIn,KOut), | |
| 138 | replace_in_json(Pattern,RepStr,VIn,VOut). | |
| 139 | ||
| 140 | replace_atom(Pattern,RepStr,Val,NewVal) :- | |
| 141 | atom_codes(Val,Codes), | |
| 142 | replace_pat(Pattern,RepStr,NewCodes,Codes,[]), | |
| 143 | atom_codes(NewVal,NewCodes). % TO DO: keep as codes for efficiency | |
| 144 | ||
| 145 | :- assert_must_succeed((visb_json_utils: replace_pat("0","_1_",Res,"ab%0cd",[]), Res == "ab_1_cd")). | |
| 146 | :- assert_must_succeed((visb_json_utils: replace_pat("0","",Res,"ab%0%0cd%0",[]), Res == "abcd")). | |
| 147 | :- assert_must_succeed((visb_json_utils: replace_pat("1","x",Res,"%1 %10 %1",[]), Res == "x %10 x")). | |
| 148 | % dcg utility to replace %Pat by NewStr constructing Res | |
| 149 | replace_pat(Pat,NewStr,Res) --> "%", Pat, \+ digit_follows_number(Pat), !, | |
| 150 | {append(NewStr,TR,Res)}, replace_pat(Pat,NewStr,TR). | |
| 151 | replace_pat(Pat,RepStr,[H|T]) --> [H],!, replace_pat(Pat,RepStr,T). | |
| 152 | replace_pat(_,_,[]) --> []. | |
| 153 | ||
| 154 | % check that a numeric pattern like %1 is not the prefix of a longer one like %10 | |
| 155 | digit_follows_number(Pat,S,S) :- last(Pat,C), is_digit_code(C), S=[D|_], is_digit_code(D). | |
| 156 | is_digit_code(C) :- C >= 0'0, C =< 0'9. |