| 1 | | % (c) 2009-2025 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, |
| 2 | | % Heinrich Heine Universitaet Duesseldorf |
| 3 | | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) |
| 4 | | |
| 5 | | :- module(logger, [set_log_file/1, get_log_file/1, set_logging_mode/1, |
| 6 | | writeln_log/1, |
| 7 | | writeln_log_time/1, |
| 8 | | write_xml_element_to_log/2, |
| 9 | | write_prolog_term_as_xml_to_log/1, |
| 10 | | write_bstate_to_log/1, write_bstate_to_log/2, |
| 11 | | start_xml_group_in_log/1, start_xml_group_in_log/3, |
| 12 | | stop_xml_group_in_log/1, stop_xml_group_in_log_no_statistics/1, |
| 13 | | close_all_xml_groups_in_log_until/1, |
| 14 | | logging_is_enabled/0, |
| 15 | | read_xml_log_file/2]). |
| 16 | | |
| 17 | | :- use_module(module_information). |
| 18 | | |
| 19 | | :- module_info(group,infrastructure). |
| 20 | | :- module_info(description,'This module is responsible for (xml and prolog) logging.'). |
| 21 | | |
| 22 | | |
| 23 | | :- use_module(self_check). |
| 24 | | :- use_module(error_manager). |
| 25 | | :- use_module(probsrc(xml_prob),[xml_parse/3, xml_parse/2]). |
| 26 | | :- use_module(library(lists)). |
| 27 | | |
| 28 | | :- set_prolog_flag(double_quotes, codes). |
| 29 | | |
| 30 | | :- dynamic logfile/1. |
| 31 | | set_log_file(F) :- retractall(logfile(_)), assertz(logfile(F)). |
| 32 | | get_log_file(F) :- logfile(F). |
| 33 | | |
| 34 | | :- dynamic logging_mode/1. |
| 35 | | logging_mode(prolog). |
| 36 | | % valid modes: prolog and xml |
| 37 | | set_logging_mode(Mode) :- retractall(logging_mode(_)), assertz(logging_mode(Mode)), |
| 38 | | (Mode=xml -> format_log_header(reset,'<?xml version="1.0" encoding="UTF-8"?>~n',[]) ; true). |
| 39 | | |
| 40 | | % TO DO: use: |
| 41 | | %get_preference(xml_encoding,EncodingPref), |
| 42 | | |
| 43 | | logging_is_enabled :- logfile(_),!. |
| 44 | | |
| 45 | | prolog_log_file(F) :- logfile(F), logging_mode(prolog). |
| 46 | | |
| 47 | | reset_logger :- retractall(open_xml_group(_,_)), |
| 48 | | retractall(logfile(_)), set_logging_mode(prolog). |
| 49 | | |
| 50 | | % TO DO: try and move most writeln_log calls to write_xml_element_to_log format |
| 51 | | writeln_log(Term) :- |
| 52 | | (prolog_log_file(F) |
| 53 | | -> open(F,append,S,[encoding(utf8)]), |
| 54 | | write_term(S,Term,[quoted(true)]), write(S,'.'),nl(S), |
| 55 | | close(S) |
| 56 | | ; true |
| 57 | | ). |
| 58 | | |
| 59 | | open_logfile(Stream) :- logfile(F), open(F,append,Stream,[encoding(utf8)]). |
| 60 | | |
| 61 | | format_log(FormatString,Args) :- %format(FormatString,Args),nl, |
| 62 | | (logfile(F) |
| 63 | | -> open(F,append,S,[encoding(utf8)]), |
| 64 | | format(S,FormatString,Args), |
| 65 | | close(S) |
| 66 | | ; true |
| 67 | | ). |
| 68 | | |
| 69 | | writeln_log_time(Term) :- |
| 70 | | (prolog_log_file(_) -> |
| 71 | | statistics(runtime,[Time,_]), |
| 72 | | statistics(walltime,[WTime,_]), |
| 73 | | statistics(memory_used,M), MB is M / 1000000, % used instead of deprecated 1048576 |
| 74 | | Term=..[H|Args], |
| 75 | | append(Args,[Time,WTime,mb(MB)],NArgs), |
| 76 | | NT =.. [H|NArgs], |
| 77 | | writeln_log(NT) |
| 78 | | ; true). |
| 79 | | |
| 80 | | :- use_module(library(file_systems),[file_exists/1]). |
| 81 | | format_log_header(reset,FormatString,Args) :- !, % reset means we want to start with a fresh log file |
| 82 | | (logfile(F) |
| 83 | | -> open(F,write,S,[encoding(utf8)]), |
| 84 | | format(S,FormatString,Args), |
| 85 | | close(S) |
| 86 | | ; true |
| 87 | | ). |
| 88 | | format_log_header(_,FormatString,Args) :- |
| 89 | | % like format_log, but only writes if the file does not exist yet |
| 90 | | (logfile(F), \+ file_exists(F) |
| 91 | | -> open(F,append,S,[encoding(utf8)]), |
| 92 | | format(S,FormatString,Args), |
| 93 | | close(S) |
| 94 | | ; true |
| 95 | | ). |
| 96 | | |
| 97 | | :- assert_must_succeed((logger:xml_encode_text("b<>c",R),R=="b<>c")). |
| 98 | | xml_encode_text(Codes,Res) :- XML = xml([],[pcdata(Codes)]), xml_parse(Encoded,XML),!,Res=Encoded. |
| 99 | | xml_encode_text(Codes,Encoded) :- format(user_error,'Could not encode for XML: ~s~n',[Codes]), |
| 100 | | Encoded=Codes. |
| 101 | | % we could also use xml:pcdata_generation(Codes, Encoded, []). |
| 102 | | |
| 103 | | |
| 104 | | |
| 105 | | :- assert_must_succeed((logger:xml_encode_element(check_goal,[true/1],R),R=="<check_goal true=\"1\" />")). |
| 106 | | xml_encode_element(Tag,Attributes,Encoded2) :- |
| 107 | | maplist(prepare_attribute,Attributes,XMLAttr), |
| 108 | | XML = xml([],[element(Tag,XMLAttr,[])]), |
| 109 | | xml_parse(Encoded,XML), |
| 110 | | peel_off_leading_newline(Encoded,Encoded2). |
| 111 | | |
| 112 | | peel_off_leading_newline([10|T],R) :- !, peel_off_leading_newline(T,R). |
| 113 | | peel_off_leading_newline([13|T],R) :- !, peel_off_leading_newline(T,R). |
| 114 | | peel_off_leading_newline(R,R). |
| 115 | | |
| 116 | | % write a tag with attributes to the log file |
| 117 | | write_xml_element_to_log(_,_) :- \+ logfile(_), !. |
| 118 | | write_xml_element_to_log(Tag,Attributes) :- logging_mode(xml),!, |
| 119 | | (xml_encode_element(Tag,Attributes,Encoded) -> true |
| 120 | | ; add_internal_error('Could not encode xml: ',Tag), |
| 121 | | Encoded = "<error/>" |
| 122 | | ), |
| 123 | | indent_log(WS), |
| 124 | | format_log("~s~s~n",[WS,Encoded]). |
| 125 | | write_xml_element_to_log(Tag,Attributes) :- |
| 126 | | Term =.. [Tag,Attributes], |
| 127 | | format_log("~w.~n",[Term]). |
| 128 | | |
| 129 | | % write a Prolog Term either as Prolog Term in Prolog mode or in nested XML form |
| 130 | | %write_term_to_log(Term) :- logging_mode(xml),!, |
| 131 | | % write_prolog_term_as_xml_to_log(Term). |
| 132 | | %write_term_to_log(Term) :- writeln_log(Term). |
| 133 | | |
| 134 | | write_prolog_term_as_xml_to_log(A) :- number(A),!, |
| 135 | | indent_log(WS), |
| 136 | | format_log("~s<number>~w</number>~n",[WS,A]). |
| 137 | | write_prolog_term_as_xml_to_log(A) :- var(A),!, |
| 138 | | indent_log(WS), |
| 139 | | format_log("~s<variable>~w</variable>~n",[WS,A]). |
| 140 | | write_prolog_term_as_xml_to_log(A) :- atomic(A),!, convert_to_codes(A,Codes), |
| 141 | | xml_encode_text(Codes,Encoded), |
| 142 | | indent_log(WS), |
| 143 | ? | (is_a_file_path(Encoded) -> format_log("~s<path>~s</path>~n",[WS,Encoded]) |
| 144 | | ; format_log("~s<atom>~s</atom>~n",[WS,Encoded])). |
| 145 | | write_prolog_term_as_xml_to_log(A/B) :- !, |
| 146 | | start_xml_group_in_log(bind), |
| 147 | | write_prolog_term_as_xml_to_log(A), |
| 148 | | write_prolog_term_as_xml_to_log(B), |
| 149 | | stop_xml_group_in_log_no_statistics(bind). |
| 150 | | write_prolog_term_as_xml_to_log([H|T]) :- !, % Note: we assume we have a proper list ! |
| 151 | | start_xml_group_in_log(list), |
| 152 | | maplist(write_prolog_term_as_xml_to_log,[H|T]), |
| 153 | | stop_xml_group_in_log_no_statistics(list). |
| 154 | | write_prolog_term_as_xml_to_log(T) :- T =.. [Functor|Args], |
| 155 | | %TO DO, something like: escape / xml_encode_text(Functor,EFunc), |
| 156 | | encode_functor(Functor,XML_Functor), |
| 157 | | start_xml_group_in_log(XML_Functor), |
| 158 | | maplist(write_prolog_term_as_xml_to_log,Args), |
| 159 | | stop_xml_group_in_log_no_statistics(XML_Functor). |
| 160 | | |
| 161 | | encode_functor('-',R) :- !, R='prolog-'. |
| 162 | | encode_functor(X,X). |
| 163 | | |
| 164 | | write_bstate_to_log(State) :- write_bstate_to_log(State,''). |
| 165 | | |
| 166 | | % in response to logxml_write_vars |
| 167 | | write_bstate_to_log(State,Prefix) :- logging_mode(xml),!, |
| 168 | | start_xml_group_in_log(state), |
| 169 | | atom_codes(Prefix,PrefixCodes), |
| 170 | | (State=root -> start_xml_group_in_log(root), stop_xml_group_in_log_no_statistics(root) |
| 171 | | ; maplist(write_b_binding_as_xml_to_log(PrefixCodes),State) -> true |
| 172 | | ; add_internal_error('Could not write state to xml logfile: ',write_bstate_to_log(State))), |
| 173 | | stop_xml_group_in_log_no_statistics(state). |
| 174 | | write_bstate_to_log(_,_Prefix). |
| 175 | | |
| 176 | | write_b_binding_as_xml_to_log(Prefix,bind(VarName,Value)) :- |
| 177 | | atom_codes(VarName,Codes), |
| 178 | | append(Prefix,_,Codes), % check that variable name starts with prefix |
| 179 | | !, |
| 180 | | start_xml_group_in_log(variable,name,VarName), % Not: already escapes for XML; |
| 181 | | % TODO: distinguish between constants/variables |
| 182 | | xml_write_b_value_to_log(Value), |
| 183 | | stop_xml_group_in_log_no_statistics(variable). |
| 184 | | write_b_binding_as_xml_to_log(_,_). |
| 185 | | |
| 186 | | xml_write_b_value_to_log(Value) :- |
| 187 | | open_logfile(Stream), |
| 188 | | indent_log(WS),format(Stream,'~s ',[WS]), |
| 189 | | xml_write_b_value(Value,Stream), |
| 190 | | format(Stream,'~n',[]), |
| 191 | | close(Stream). |
| 192 | | |
| 193 | | :- use_module(probsrc(custom_explicit_sets),[expand_custom_set_to_list/2]). |
| 194 | | :- use_module(probsrc(translate),[translate_bvalue_to_codes/2]). |
| 195 | | xml_write_b_value_map(Stream,O) :- xml_write_b_value(O,Stream). |
| 196 | | xml_write_b_value(Var,Stream) :- var(Var),!, |
| 197 | | add_internal_error('Illegal variable value:',xml_write_b_value(Var,Stream)), |
| 198 | | format(Stream,'<value>~w</value>',[Var]). |
| 199 | | xml_write_b_value((Fst,Snd),Stream) :- !, |
| 200 | | write(Stream,'<pair><fst>'), |
| 201 | | xml_write_b_value(Fst,Stream), |
| 202 | | write(Stream,'</fst><snd>'), |
| 203 | | xml_write_b_value(Snd,Stream), |
| 204 | | write(Stream,'</snd></pair> '). |
| 205 | | xml_write_b_value([],Stream) :- !,write(Stream,'<empty_set></empty_set> '). |
| 206 | | xml_write_b_value(CS,Stream) :- custom_set_to_expand(CS),!, |
| 207 | | expand_custom_set_to_list(CS,Elements), |
| 208 | | write(Stream,'<set>'), |
| 209 | | maplist(xml_write_b_value_map(Stream),Elements), |
| 210 | | write(Stream,'</set> '). |
| 211 | | xml_write_b_value([H|T],Stream) :- !, |
| 212 | | write(Stream,'<set>'), |
| 213 | | maplist(xml_write_b_value_map(Stream),[H|T]), |
| 214 | | write(Stream,'</set> '). |
| 215 | | xml_write_b_value(rec(Fields),Stream) :- !, |
| 216 | | write(Stream,'<record>'), |
| 217 | | maplist(xml_write_b_field_value(Stream),Fields), |
| 218 | | write(Stream,'</record> '). |
| 219 | | xml_write_b_value(string(S),Stream) :- !, |
| 220 | | atom_codes(S,Codes), |
| 221 | | xml_encode_text(Codes,Encoded), |
| 222 | | format(Stream,'<string>~s</string>',[Encoded]). |
| 223 | | xml_write_b_value(int(N),Stream) :- !, |
| 224 | | format(Stream,'<integer>~w</integer>',[N]). |
| 225 | | xml_write_b_value(pred_true,Stream) :- !, |
| 226 | | format(Stream,'<bool>TRUE</bool>',[]). |
| 227 | | xml_write_b_value(pred_false,Stream) :- !, |
| 228 | | format(Stream,'<bool>FALSE</bool>',[]). |
| 229 | | xml_write_b_value(fd(Nr,Type),Stream) :- !, |
| 230 | | translate_bvalue_to_codes(fd(Nr,Type),SValue), |
| 231 | | format(Stream,"<enum type=\"~w\" nr=\"~w\">~s</enum>",[Type,Nr,SValue]). |
| 232 | | xml_write_b_value(Value,Stream) :- |
| 233 | | is_custom_explicit_set(Value,xml_write), |
| 234 | | is_interval_closure(Value,Low,Up), |
| 235 | | !, |
| 236 | | write(Stream,'<interval_set><from>'), |
| 237 | | xml_write_b_value(int(Low),Stream), |
| 238 | | write(Stream,'</from><to>'), |
| 239 | | xml_write_b_value(int(Up),Stream), |
| 240 | | write(Stream,'</to></interval_set>'). |
| 241 | | xml_write_b_value(Value,Stream) :- % other value, freetype, freeval, closure, ... |
| 242 | | is_custom_explicit_set(Value,xml_write), |
| 243 | | !, |
| 244 | | translate_bvalue_to_codes(Value,SValue), |
| 245 | | xml_encode_text(SValue,Encoded), |
| 246 | | format(Stream,'<symbolic_set>~s</symbolic_set>',[Encoded]). |
| 247 | | xml_write_b_value(Value,Stream) :- % other value freeval, ... |
| 248 | | translate_bvalue_to_codes(Value,SValue), |
| 249 | | xml_encode_text(SValue,Encoded), |
| 250 | | format(Stream,'<value>~s</value>',[Encoded]). |
| 251 | | % TO DO: check if there are uncovered values, e.g., freeval(ID,Case,Value) |
| 252 | | |
| 253 | | :- use_module(custom_explicit_sets,[is_interval_closure/3, |
| 254 | | is_custom_explicit_set/2, dont_expand_this_explicit_set/2]). |
| 255 | | custom_set_to_expand(avl_set(_)). |
| 256 | | custom_set_to_expand(CS) :- nonvar(CS), |
| 257 | | is_custom_explicit_set(CS,xml_write), |
| 258 | | \+ dont_expand_this_explicit_set(CS,1000). |
| 259 | | |
| 260 | | xml_write_b_field_value(Stream,field(Name,Val)) :- |
| 261 | | atom_codes(Name,Codes), xml_attribute_escape(Codes,Encoded), |
| 262 | | format(Stream,'<field name=\"~s\">',[Encoded]), |
| 263 | | xml_write_b_value(Val,Stream), write(Stream,'</field>'). |
| 264 | | |
| 265 | | % --------------------------- |
| 266 | | |
| 267 | | :- use_module(tools_platform, [host_platform/1]). |
| 268 | ? | is_a_file_path(Codes) :- member(47,Codes). |
| 269 | | is_a_file_path(Codes) :- host_platform(windows), member(92,Codes). % windows |
| 270 | | |
| 271 | | prepare_attribute('='(Tag,Atom),'='(Tag,Codes)) :- convert_to_codes(Atom,Codes). |
| 272 | | prepare_attribute('/'(Tag,Atom),'='(Tag,Codes)) :- convert_to_codes(Atom,Codes). |
| 273 | | |
| 274 | | :- use_module(library(codesio),[write_to_codes/2]). |
| 275 | | convert_to_codes(V,Codes) :- var(V),!,Codes="_". |
| 276 | | convert_to_codes([H|T],Codes) :- number(H),!, Codes=[H|T]. |
| 277 | | convert_to_codes(N,Codes) :- number(N),!, number_codes(N,Codes). |
| 278 | | convert_to_codes(A,Codes) :- atom(A),!,atom_codes(A,Codes). |
| 279 | | convert_to_codes(A,Codes) :- write_to_codes(A,Codes). |
| 280 | | |
| 281 | | :- dynamic open_xml_group/2, nesting_level/1. |
| 282 | | nesting_level(0). |
| 283 | | update_nesting_level(X) :- retract(nesting_level(Y)), |
| 284 | | New is Y+X, assertz(nesting_level(New)). |
| 285 | | |
| 286 | | space(32). |
| 287 | | indent_log(WS) :- nesting_level(Lvl), length(WS,Lvl), |
| 288 | | maplist(space,WS). |
| 289 | | |
| 290 | ? | check_and_generate_group_stats(Group,Stats) :- open_xml_group(A,_),!, |
| 291 | | (A=Group |
| 292 | | -> retract(open_xml_group(Group,WTimeStart)), |
| 293 | | (Stats=no_statistics -> true |
| 294 | | ; statistics(walltime,[WTimeEnd,_]), |
| 295 | | Delta is WTimeEnd - WTimeStart, |
| 296 | | statistics(memory_used,M), |
| 297 | | write_xml_element_to_log(statistics,[walltime/Delta,walltime_since_start/WTimeEnd,memory_used/M]) |
| 298 | | ), |
| 299 | | update_nesting_level(-1) |
| 300 | | ; add_internal_error('XML closing tag mismatch: ', Group/A), |
| 301 | | stop_xml_group_in_log(A,Stats), % close offending group and try again |
| 302 | | check_and_generate_group_stats(Group,Stats) |
| 303 | | ). |
| 304 | | check_and_generate_group_stats(Group,_) :- |
| 305 | | add_internal_error('XML closing tag error, no tag open: ', Group). |
| 306 | | |
| 307 | | start_xml_group_in_log(Group) :- logging_mode(xml),!, |
| 308 | | statistics(walltime,[WTime,_]), |
| 309 | | indent_log(WS), |
| 310 | | asserta(open_xml_group(Group,WTime)), |
| 311 | | update_nesting_level(1), |
| 312 | | format_log("~s<~w>~n",[WS,Group]). |
| 313 | | start_xml_group_in_log(_). |
| 314 | | |
| 315 | | :- use_module(tools, [xml_attribute_escape/2]). % attribute values have a less stringent encoding than xml_encode_text |
| 316 | | % we currently only support a single attribute and value |
| 317 | | start_xml_group_in_log(_,_,_) :- \+ logging_mode(xml),!. |
| 318 | | start_xml_group_in_log(Group,Attr,Value) :- |
| 319 | | statistics(walltime,[WTime,_]), |
| 320 | | indent_log(WS), |
| 321 | | asserta(open_xml_group(Group,WTime)), |
| 322 | | update_nesting_level(1), |
| 323 | | convert_to_codes(Value,ValueC), |
| 324 | | xml_attribute_escape(ValueC,EValueC), |
| 325 | | format_log("~s<~w ~w=\"~s\">~n",[WS,Group,Attr,EValueC]),!. |
| 326 | | start_xml_group_in_log(Group,Attr,Value) :- |
| 327 | | add_internal_error('Call failed: ', start_xml_group_in_log(Group,Attr,Value)). |
| 328 | | |
| 329 | | stop_xml_group_in_log(_,_) :- \+ logging_mode(xml),!. |
| 330 | | stop_xml_group_in_log(Group,Stats) :- |
| 331 | | check_and_generate_group_stats(Group,Stats), |
| 332 | | indent_log(WS), |
| 333 | | format_log("~s</~w>~n",[WS,Group]),!. |
| 334 | | stop_xml_group_in_log(Group,Stats) :- |
| 335 | | add_internal_error('Call failed: ', stop_xml_group_in_log(Group,Stats)). |
| 336 | | |
| 337 | | |
| 338 | | stop_xml_group_in_log(Group) :- stop_xml_group_in_log(Group,statistics). |
| 339 | | stop_xml_group_in_log_no_statistics(Group) :- stop_xml_group_in_log(Group,no_statistics). |
| 340 | | |
| 341 | | % call if you need to prematurely exit probcli |
| 342 | | %close_all_xml_groups_in_log :- close_all_xml_groups_in_log_until('probcli-run'). |
| 343 | | close_all_xml_groups_in_log_until(Until) :- open_xml_group(Group,_), Until \== Group, |
| 344 | | !, |
| 345 | | stop_xml_group_in_log(Group), |
| 346 | | close_all_xml_groups_in_log_until(Until). |
| 347 | | close_all_xml_groups_in_log_until(_). |
| 348 | | |
| 349 | | |
| 350 | | |
| 351 | | :- use_module(tools, [safe_read_string_from_file/3]). |
| 352 | | :- use_module(debug, [formatsilent/2]). |
| 353 | | |
| 354 | | read_xml_log_file(File,[errors/NrErrors,warnings/NrWarnings,expected_errors/NrExpErrors]) :- |
| 355 | | Encoding=auto, % (must be "auto", "UTF-8", "UTF-16", "ISO-8859-1",...) |
| 356 | | statistics(walltime,_), |
| 357 | | absolute_file_name(File,AFile), |
| 358 | | safe_read_string_from_file(AFile,Encoding,Codes), |
| 359 | | (xml_parse(Codes,xml(_Atts,Content),[format(true)]) -> true |
| 360 | | ; add_error(read_xml,'Converting file contents to XML failed: ',AFile),fail), |
| 361 | | statistics(walltime,[_,W2]), |
| 362 | | formatsilent('% Walltime ~w ms to parse and convert XML in ~w~n',[W2,AFile]), |
| 363 | | Content = [element('probcli-run',[],InnerContent)|_], |
| 364 | | check_log(InnerContent,0), |
| 365 | | member(element('probcli-errors',[errors=EC,warnings=WC|TErrs],[]),InnerContent), |
| 366 | | number_codes(NrErrors,EC), |
| 367 | | number_codes(NrWarnings,WC), |
| 368 | | (member(expected_errors=ExpE,TErrs),number_codes(NrExpErrors,ExpE) -> true ; NrExpErrors=0). |
| 369 | | |
| 370 | | %extract_xml_log_file([element('probcli-run',[],Cont],Entries) :- |
| 371 | | %extract_entry(element(Name,Attrs,Content) |
| 372 | | |
| 373 | | extract_attribute(Attr=Codes,Attr=Atom) :- atom_codes(Atom,Codes). |
| 374 | | |
| 375 | | % TO DO: extract interesting information, e.g., for test_runner |
| 376 | | check_log([],_) :- !. |
| 377 | | check_log([H|T],Level) :- !, L1 is Level+1, check_log(H,L1), |
| 378 | | check_log(T,Level). |
| 379 | | check_log(element(Name,Attrs,Cont),Level) :- maplist(extract_attribute,Attrs,EAttrs), |
| 380 | | !, |
| 381 | | indentws(Level), format('<~w ~w>~n',[Name,EAttrs]), |
| 382 | | L1 is Level + 1, |
| 383 | | check_log(Cont,L1). |
| 384 | | check_log(pcdata(L),Level) :- !, indentws(Level), format('~s~n',[L]). |
| 385 | | check_log(X,Level) :- indentws(Level),print(X),nl. |
| 386 | | |
| 387 | | indentws(0) :- !. |
| 388 | | indentws(X) :- X>0, print(' '), X1 is X-1, indentws(X1). |
| 389 | | |
| 390 | | /* |
| 391 | | <?xml version="1.0" encoding="ASCII"?> |
| 392 | | |
| 393 | | | ?- xml_parse("<PT ID=\"2\" stID=\"3\"/>",R). |
| 394 | | R = xml([],[element('PT',['ID'=[50],stID=[51]],[])]) ? |
| 395 | | |
| 396 | | <PointsTelegram elementID="W90" stationID="FR" interlockingID="FR" interlockingElementID="W90"/> |
| 397 | | |
| 398 | | |
| 399 | | */ |
| 400 | | |
| 401 | | % ------------------------------------------- |
| 402 | | |
| 403 | | :- use_module(eventhandling,[register_event_listener/3]). |
| 404 | | :- register_event_listener(reset_prob,reset_logger, |
| 405 | | 'Reset Logger just like after starup_prob'). |
| 406 | | |