/* Written July 4th Markus Triska, triska@gmx.at Expand report templates with embedded SQL queries and reporting constructs. */ :- use_module(library(sgml)). :- use_module(library(assoc)). :- use_module(library(odbc)). :- use_module(library(clpq)). %:- use_module(library(pce)). elements_attrs([], []). elements_attrs([element(_, Attr, _)|Es], [Attr|Rest]) :- !, elements_attrs(Es, Rest). elements_attrs([_|Es], Rest) :- elements_attrs(Es, Rest). make_dialog(Tree) :- new(D, dialog('Report generator')), ( memberchk(element(uservars, _, Children0), Tree) -> elements_attrs(Children0, Children), dialog_add_uservars(Children, D) ; Children = [] ), send(D, append(button(generate, message(@prolog, gen_report, D, prolog(Tree), prolog(Children))))), send(D, open). dialog_add_uservars([], _). dialog_add_uservars([Attr|Attrs], D) :- memberchk(name=Var, Attr), send(D, append(text_item(Var))), dialog_add_uservars(Attrs, D). children_uservars([], _, UVs, UVs). children_uservars([Attr|Attrs], D, UVs0, UVs) :- memberchk(name=Var, Attr), get(D, member(Var), Widget), get(Widget, selection, Text), format("from GUI: ~w = ~w\n", [Var, Text]), put_assoc(Var, UVs0, Text, UVs1), children_uservars(Attrs, D, UVs1, UVs). gen_report(D, Tree, Children) :- format("dialog is: ~w, ~w\n", [D, Children]), empty_assoc(E), children_uservars(Children, D, E, UVs), odbc_connect(test, Connection, [user(root)]), expand_template(Tree, Connection, UVs, Expansion), format("======================================================================\n"), current_output(Output), html_write(Output, Expansion, []). show_list :- new(D, dialog('List')), send(D, append, new(B, list_browser)), send(B, append(item1)), send(B, append(item2)), send(D, append(button(print, message(@prolog, my_print, B?selection?key)))), send(D, open). my_print(X) :- format("x: ~w\n", [X]). expand_template(Tree, Connection, UVs, Expansion) :- empty_assoc(E), put_dummies(E, FEs), delete(Tree, element(uservars, _, _), Tree1), expand_all(Tree1, Connection, UVs, FEs, Expansion). user_variables(UVs) :- empty_assoc(E), put_assoc(mail, E, 'abc', UVs). put_dummies(A0, A) :- put_assoc(t, A0, row(bauer, heinz, 5), A1), put_assoc(c, A1, row(1), A2), put_assoc(a, A2, row(lala), A3), put_assoc(b, A3, row(xyz, lala), A). diff_list([], D, D). diff_list([E|Es], [E|Rest], D) :- diff_list(Es, Rest, D). expand_foreach([], _, _, _, _, _, []). expand_foreach([Row|Rows], Connection, V, UVs, FEs0, Children0, [Children|Rest]) :- put_assoc(V, FEs0, Row, FEs1), put_assoc(V, UVs, Row, UVs1), expand_all(Children0, Connection, UVs1, FEs1, Children), expand_foreach(Rows, Connection, V, UVs, FEs1, Children0, Rest). fe_column(Assoc, Var, Col, Value) :- get_assoc(Var, Assoc, Row), ( atom(Col) -> atom_number(Col, N) ; number(Col) -> N = Col ; format("could not convert ~w\n", [Col]), fail ), arg(N, Row, Value0), any_atom(Value0, Value). any_atom(Value0, Value) :- ( atom(Value0) -> Value = Value0 ; number(Value0) -> atom_number(Value, Value0) ; rational(Value0) -> V1 is float(Value0), atom_number(Value, V1) ; Value0 = date(Year,Month,Day) -> sformat(String, "~w.~w.~w", [Day,Month,Year]), string_to_atom(String, Value) ; format("any_atom: could not convert ~w\n", [Value0]), fail ). /* Tree generation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% A tree is represented as a term of the form tree(Row, Attributes, Cs, Children, Vars) where: *) Row is the tuple corresponding to this node, row(....), as fetched via ODBC *) Attributes are the HTML-attributes of the node ("leaf" etc.) *) Cs are the HTML-children of the template of this node () *) Children is a list of trees, the children of this node *) Vars is an association list with variables of this node CLP(Q) is used to post constraints declared for nodes. */ cond(A=B, Row) :- eval(A, Row, A1), eval(B, Row, B1), A1 == B1. eval(tc(N), Row, E) :- !, arg(N, Row, E). eval(N, _, N) :- number(N), !. eval(A, _, A) :- atom(A), !. filter_nonleaf([], []). filter_nonleaf([T|Ts], Ns) :- %format("filter: ~w\n", [T]), T = tree(Row, Attrs, Cs, Children0, Vars), filter_nonleaf(Children0, Children), ( memberchk(leaf=false, Attrs), Children == [] -> format("removing leaf: ~w\n\n", [T]), filter_nonleaf(Ts, Ns) ; Ns = [tree(Row, Attrs, Cs, Children, Vars)|Rest], filter_nonleaf(Ts, Rest) ). gen_tree([], _, _, _, _, []). gen_tree([Row|Rows], Connection, Conds, UVs, FEs, [Tree|Trees]) :- member(element(child, As, Cs), Conds), member(cond=C, As), atom_to_term(C, Cond, _), cond(Cond, Row), !, ( memberchk(children=ASQL, As) -> atom_to_term(ASQL, TSQL, _), gen_string(Row, UVs, FEs, TSQL, SQL), findall(R, odbc_query(Connection, SQL, R), Rs), gen_tree(Rs, Connection, Conds, UVs, FEs, Children) ; Children = [] ), empty_assoc(Empty), Tree = tree(Row, As, Cs, Children, Empty), gen_tree(Rows, Connection, Conds, UVs, FEs, Trees). gen_tree([Row|Rows], Connection, Conds, UVs, FEs, [Tree|Trees]) :- %format("gentree: no matches for row: ~w\n", [Row]), empty_assoc(Empty), Tree = tree(Row, [], [], [], Empty), gen_tree(Rows, Connection, Conds, UVs, FEs, Trees). tree_create_vars([], []). tree_create_vars([T0|Ts0], [T|Ts]) :- T0 = tree(Row, As, Cs, Children0, Vars0), tree_create_vars(Children0, Children), %format("create variables for: ~w\n", [Row]), tree_create_vars(Cs, Vars0, Vars), T = tree(Row, As, Cs, Children, Vars), tree_create_vars(Ts0, Ts). tree_create_vars([], Vars, Vars). tree_create_vars([element(var, As, _)|Es], Vars0, Vars) :- !, memberchk(name=Name, As), %format("creating variable: ~w\n", [Name]), put_assoc(Name, Vars0, _, Vars1), tree_create_vars(Es, Vars1, Vars). tree_create_vars([_|Es], Vars0, Vars) :- tree_create_vars(Es, Vars0, Vars). tree_compute_vars([], _, _, _, _, []). tree_compute_vars([T0|Ts0], Connection, Siblings, UVs, FEs, [T|Ts]) :- T0 = tree(Row, As, Cs, Children0, Vars0), tree_compute_vars(Children0, Connection, Children0, UVs, FEs, Children), tree_compute_vars(Cs, Connection, Row, Siblings, Children, UVs, FEs, Vars0, Vars), T = tree(Row, As, Cs, Children, Vars), tree_compute_vars(Ts0, Connection, Siblings, UVs, FEs, Ts). tree_compute_vars([], _, _, _, _, _, _, Vars, Vars). tree_compute_vars([element(var, As, _)|Es], Connection, Row, Siblings, Children, UVs, FEs, Vars0, Vars) :- !, format("computing: ~w for ~w\n", [As,Row]), ( memberchk(query=Q, As) -> atom_to_term(Q, T, _), gen_string(Row, UVs, FEs, T, SQL), format("ODBC: ~w\n", [SQL]), odbc_query(Connection, SQL, Result), arg(1, Result, First), memberchk(name=Name, As), put_assoc(Name, Vars0, First, Vars1), tree_compute_vars(Es, Connection, Row, Siblings, Children, UVs, FEs, Vars1, Vars) ; memberchk(expression=E, As) -> atom_to_term(E, T, _), memberchk(name=Name, As), %format("compute ~w (= ~w) for ~w, ~w...\n", [Name,E,Row,Vars0]), tree_eval_expression(T, Row, Vars0, Siblings, Children, UVs, FEs, Value), put_assoc(Name, Vars0, Value, Vars1), tree_compute_vars(Es, Connection, Row, Siblings, Children, UVs, FEs, Vars1, Vars) ; format("tree_compute_vars: cannot compute variable: ~w\n", [As]), fail ). tree_compute_vars([_|Es], Connection, Row, Siblings, Children, UVs, FEs, Vars0, Vars) :- tree_compute_vars(Es, Connection, Row, Siblings, Children, UVs, FEs, Vars0, Vars). tree_eval_expression(N, _, _, _, _, _, _, N) :- number(N), !. tree_eval_expression(ifnull(A,B), Row, Vars, Siblings, Children, UVs, FEs, Var) :- tree_eval_expression(A, Row, Vars, Siblings, Children, UVs, FEs, EA), ( EA == '$null$' -> tree_eval_expression(B, Row, Vars, Siblings, Children, UVs, FEs, Var), format("was null, replaced by: ~w\n", [Var]) ; Var = EA ). tree_eval_expression(tc(C), Row, _, _, _, _, _, Var) :- arg(C, Row, Var). tree_eval_expression(BinOp, Row, Vars, Siblings, Children, UVs, FEs, Var) :- BinOp =.. [Op,A,B], memberchk(Op, [+,-,/,*]), !, tree_eval_expression(A, Row, Vars, Siblings, Children, UVs, FEs, Var1), tree_eval_expression(B, Row, Vars, Siblings, Children, UVs, FEs, Var2), Right =.. [Op,Var1,Var2], { Var = Right }. tree_eval_expression(tv(V), _, Vars, _, _, _, _, Var) :- get_assoc(V, Vars, Var). tree_eval_expression(sum(Which, Expression), _, _, Siblings, Children, _, _, Var) :- ( Which = children -> format("summing over children...\n"), trees_sum(Children, Expression, Var) ; Which = siblings -> trees_sum(Siblings, Expression, Var) ; format("cannot build sum(~w) - unknown: ~w\n", [Expression,Which]), fail ). trees_sum(Trees, Expression, Var) :- trees_sum(Trees, Expression, 0, Var). trees_sum([], _, Sum, Sum). trees_sum([T|Ts], Expression, Sum0, Sum) :- T = tree(Row, _, _, Children, Vars), format("eval expression ~w w.r.t: ~w, ~w\n", [Expression,Row,Vars]), tree_eval_expression(Expression, Row, Vars, [T|Ts], Children, _, _, E), { Sum1 = Sum0 + E }, trees_sum(Ts, Expression, Sum1, Sum). /* Tree presentation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Fill in HTML template specification with generated tree. */ show_tree([], _, []). show_tree([tree(Row,_,_,Children,Vars)|Trees], Conds, Ts) :- format("row: ~w, ~w\n", [Row,Vars]), member(element(child, As, Cs), Conds), member(cond=C, As), atom_to_term(C, Cond, _), cond(Cond, Row), !, format("found condition: ~w\n", [C]), expand_tree(Cs, Row, Vars, Conds, Children, Expanded), diff_list(Expanded, Ts, Rest), show_tree(Trees, Conds, Rest). show_tree([_|Trees], Conds, Ts) :- format("show_tree: can not find matching condition.\n"), show_tree(Trees, Conds, Ts). expand_tree([], _, _, _, _, []). expand_tree([E0|Es0], Row, Vars, Conds, Children, Es) :- E0 = element(Element, Attr, Children0), !, ( Element == reportstring -> memberchk(value=V, Attr), atom_to_term(V, T, _), empty_assoc(Empty), % TODO: FEs ? gen_string(Row, Vars, Empty, T, String), Es = [String|Rest], expand_tree(Es0, Row, Vars, Conds, Children, Rest) ; Element == children -> show_tree(Children, Conds, Show), diff_list(Show, Es, Rest), expand_tree(Es0, Row, Vars, Conds, Children, Rest) ; expand_tree(Children0, Row, Vars, Conds, Children, Children1), Es = [element(Element, Attr, Children1)|Rest], expand_tree(Es0, Row, Vars, Conds, Children, Rest) ). expand_tree([E0|Es0], Row, Vars, Conds, Children, [E0|Es]) :- expand_tree(Es0, Row, Vars, Conds, Children, Es). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% expand_all([], _, _, _, []). expand_all([E0|Es0], Connection, UVs, FEs, Es) :- E0 = element(Element, Attr, Children0), !, format("expanding: ~w, ~w\n", [Element,Attr]), ( Element == gentree -> ( memberchk(root=R, Attr) -> true ; format("gentree: no root specified\n"), fail ), atom_to_term(R, TRoot, _), gen_string(., UVs, FEs, TRoot, SQLRoot), once(odbc_query(Connection, SQLRoot, Root)), memberchk(children=ACs, Attr), atom_to_term(ACs, TCs, _), gen_string(Root, UVs, FEs, TCs, SQLChildren), findall(Row, odbc_query(Connection,SQLChildren,Row), Rows), gen_tree(Rows, Connection, Children0, UVs, FEs, Children), empty_assoc(Empty), Tree0 = tree(Root, Attr, Children0, Children, Empty), %format("generated tree: ~w\n", [Tree0]), %format("\n\nfiltering...\n\n"), filter_nonleaf([Tree0], [Tree1]), %format("\n\nfiltered tree: ~w\n", [Tree1]), tree_create_vars([Tree1], [Tree2]), tree_compute_vars([Tree2], Connection, [Tree2], UVs, FEs, [Tree3]), %format("\n\nVariables computed: ~w\n", [Tree3]), ( memberchk(var=V, Attr) -> true ; format("gentree: missing 'var' attribute in tag\n"), fail ), put_assoc(V, UVs, Tree3, UVs1), expand_all(Es0, Connection, UVs1, FEs, Es) ; Element == showtree -> ( memberchk(var=V, Attr) -> true ; format("showtree: missing 'var' attribute in tag\n"), fail ), format("var: ~w\n", [V]), ( get_assoc(V, UVs, Tree) -> format("found\n"), true ; format("tree ~w not defined.\n", [V]), fail ), %format("showing tree: ~w\n", [Tree]), show_tree([Tree], Children0, Children1), flatten(Children1, Children), diff_list(Children, Es, Rest), %format("showing tree: ~w\n", [V]), expand_all(Es0, Connection, UVs, FEs, Rest) ; Element == reportstring -> memberchk(value=V, Attr), atom_to_term(V, T, _), gen_string(., UVs, FEs, T, String), Es = [String|Rest], expand_all(Es0, Connection, UVs, FEs, Rest) ; Element == foreach -> %format("handling foreach...~w\n", [Attr]), memberchk(query=A, Attr), memberchk(var=V, Attr), atom_to_term(A, Query, _), gen_string(., UVs, FEs, Query, SQL), format("ODBC: ~w ==> ~w\n", [SQL,V]), findall(R, odbc_query(Connection, SQL, R), Rows), %format("result: ~w\n", [Rows]), expand_foreach(Rows, Connection, V, UVs, FEs, Children0, Children1), flatten(Children1, Children), diff_list(Children, Es, Rest), expand_all(Es0, Connection, UVs, FEs, Rest) ; Element == fe_____disabled_____ -> %format("handling foreach element: ~w\n", [Attr]), memberchk(var=V, Attr), memberchk(col=Col, Attr), fe_column(FEs, V, Col, E), format("(~w,~w) ==> ~w\n", [V,Col,E]), Es = [E|Rest], expand_all(Es0, Connection, UVs, FEs, Rest) ; expand_all(Children0, Connection, UVs, FEs, Children), Es = [element(Element, Attr, Children)|Rest], expand_all(Es0, Connection, UVs, FEs, Rest) ). expand_all([E0|Es0], Connection, UVs, FEs, [E0|Es]) :- expand_all(Es0, Connection, UVs, FEs, Es). gen_string(Row, UVs, FEs, Q, A) :- gen_string_(Row, UVs, FEs, Q, G), any_atom(G, A). gen_string_(Row, UVs, FEs, Q, G) :- ( number(Q) -> G = Q ; Q = year(Date) -> gen_string_(Row, UVs, FEs, Date, D), D = date(G, _, _) ; Q = month(Date) -> gen_string_(Row, UVs, FEs, Date, D), D = date(_, G, _) ; Q = day(Date) -> gen_string_(Row, UVs, FEs, Date, D), D = date(_, _, G) ; Q = string(S) -> G = S ; Q = tc(N) -> arg(N, Row, G) ; Q = tc(Var, N) -> ( atom(Var) -> get_assoc(Var, UVs, V), arg(N, V, G) ; gen_string_(Row, UVs, FEs, Var, A), arg(N, A, G) ) ; Q = fe(Var, Col) -> fe_column(FEs, Var, Col, G) ; Q = uv(Var) -> ( get_assoc(Var, UVs, G) -> true ; format("could not fetch user variable: ~w\n", [Var]), fail ) ; Q = tv(Var) -> ( get_assoc(Var, UVs, G) -> true ; format("could not fetch tree variable: ~w\n", [Var]), fail ) ; Q = round(Expr) -> gen_string_(Row, UVs, FEs, Expr, Value), G is round(Value) ; Q = ifempty(E0, Then, Else) -> gen_string_(Row, UVs, FEs, E0, E), ( E == '' -> gen_string_(Row, UVs, FEs, Then, G) ; gen_string_(Row, UVs, FEs, Else, G) ) ; functor(Q, append, _) -> Q =.. [_|Us], maplist(gen_string(Row,UVs,FEs), Us, As), concat_atom(As, G) ; format("cannot generate string: ~w\n", [Q]), fail ). doit :- doit(_). doit(Tree) :- %load_xml_file('tree.html', Tree), %load_xml_file('fulltree.html', Tree), load_xml_file('filteredtree.html', Tree), %load_xml_file('dates.html', Tree), %format("tree: ~w\n", [Tree]), odbc_connect(test, Connection, [user(root)]), user_variables(UVs), expand_template(Tree, Connection, UVs, Expansion), format("======================================================================\n"), current_output(Output), html_write(Output, Expansion, []). gui :- load_xml_file('filteredtree.html', Tree), make_dialog(Tree).