(* * <COPYRIGHT> * Copyright 2002 Joachim Schrod Network and Publication Consultance GmbH, Gerd Stolpmann * * <GPL> * This file is part of WDialog. * * WDialog is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * WDialog is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WDialog; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * </> *) (* $Id: wd_templrep.ml,v 3.7 2006-03-08 00:56:45 stolpmann Exp $ * ---------------------------------------------------------------------- * *) open Pxp_types open Pxp_document open Pxp_dtd open Wd_types open Wd_brexpr module D = Wd_dictionary (* The data type of a tree with placeholders (parameters). The placeholders * may occur in data nodes, or in attribute values. *) type 'ext tree = Element of 'ext tree_element (* <x> ... </x> *) | Data of 'ext tree_data (* arbitrary text *) | Param of (string * string list) (* ${paramname/enc1/enc2/...} *) | Expr of (expr_oe * string * 'ext node * (string*int*int)) (* for $[expr/enc1/enc2/...]: * (expr, expr_as_string, parent_node, parent_node_pos) *) constraint 'ext = 'ext node #extension and 'ext tree_element = { el_name : string; (* The name of the element *) el_attributes : (string * att_expr_value) list; (* (name, value) list. The values are sequences of normal text and * parameters. * E.g. att="a $x y" is repesented as * [ "att", [ Att_verb "a "; Att_param "x"; Att_verb " y" ]] * * Note: * Attributes with an empty string as value are represented as * [ Att_verb "" ]. Attributes with implied value are represented * as []. *) el_unsplit_attributes : (string * att_value) list; (* The original attributes. *) el_have_placeholders_in_atts : bool; (* Whether there are parameters in attribute values or not *) el_node : 'ext node; (* A reference to the original XML node *) el_subtrees : 'ext tree list; (* The trees within this element *) } and 'ext tree_data = { dt_value : string; (* The string *) dt_node : 'ext node; (* Reference to the original node *) dt_clonable : bool; (* true if dt_value = dt_node # data, i.e. if there is not any * '$' sequence in the data node, and the node need not to be * splitted up. * There is an important optimization basing on this information. *) } and att_expr_value = | Att_expr_value of expr_string | Att_expr_implied ;; type 'ext t = { contents : 'ext tree list; app : application_type; template_name : string; (* Only used for error messages *) mk_uiencode : unit -> 'ext node; } ;; type 'ext param = { param_tree : 'ext Pxp_document.node; param_text : string Lazy.t; } let get_parameters t = let params = ref D.empty in let add p = params := D.add p () !params in let rec scan_tree = function Element e -> List.iter (function | (_, Att_expr_implied) -> () | (_, Att_expr_value e) -> let pl = params_in_expr_string e in List.iter add pl ) e.el_attributes; List.iter scan_tree e.el_subtrees; | Param (p,_) -> add p | Expr (e, _, _, _) -> let pl = params_in_expr e in List.iter add pl | _ -> () in List.iter scan_tree t.contents; !params ;; let scan_attributes n = (* Transform the attribute list of 'n' into a list like el_attributes. *) let enc = n # encoding in let atts = n # attributes in let atts' = List.map (fun (att_name, att_value) -> match att_value with Value s -> let v = parse_expr_string ~enable_param:true ~enable_brexpr:true enc s in att_name, (Att_expr_value v) | Valuelist sl -> let s = String.concat " " sl in let v = parse_expr_string ~enable_param:true ~enable_brexpr:true enc s in att_name, (Att_expr_value v) | Implied_value -> att_name, Att_expr_implied ) atts in atts' ;; let placeholder_exists l = (* return 'true' if there are placeholders in l, i.e. non-constant text *) List.exists (function | (n,Att_expr_implied) -> false | (n,Att_expr_value v) -> List.exists (function | (`Literal _, _) -> false | _ -> true) v ) l ;; let rec find_parent_element n = match n # node_type with T_element _ -> n | _ -> find_parent_element (n#parent) ;; let rec prepare_tree_with_parameters ~mk_uiencode name app nl = (* We need the 'treelist', the 'tree' type representation of the * prepared XML tree 'nl' (=nodelist). * The other components 'app' and 'template_name' are already known * and can be entered directly. *) let treelist = List.flatten (List.map scan_tree nl) in { contents = treelist; app = app; template_name = name; mk_uiencode = mk_uiencode; } and scan_tree n = (* Transform the node tree 'n' into the corresponding 'tree list' value *) let enc = n # encoding in match n # node_type with T_element element_name -> (* An element is simply converted to the corresponding representation. * Only the attributes have to be processed, but that is done * separately in the function scan_attributes. *) let scanned_atts = scan_attributes n in [ Element { el_name = element_name; el_attributes = scanned_atts; el_unsplit_attributes = n # attributes; el_have_placeholders_in_atts = placeholder_exists scanned_atts; el_node = n; el_subtrees = List.flatten (List.map scan_tree (n # sub_nodes)); } ] | T_data -> (* A data node may contain parameters. So we must apply a regexp * on the data string to split the node up. *) let l = parse_expr_string ~enable_param:true ~enable_brexpr:true enc n#data in List.map (function | (`Literal s, _) -> Data { dt_value = s; dt_node = n; dt_clonable = (List.length l = 1); } | (`Expr_strconst s, _) -> Data { dt_value = s; dt_node = n; dt_clonable = false; } | (`Expr_param(name,enclist), _) -> Param(name,enclist) | (#expr_oe as e, in_s) -> let pos = try (find_parent_element n) # position with Not_found -> assert false in Expr(e,in_s,n,pos) ) l | _ -> assert false (* other types of node cannot occur ! *) ;; let instantiate dlg r params container = let dtd = r.app#dtd in let rec lookup key l = (* Look up the parameter 'key' in the parameter dict list 'l', or * raise Not_found. *) match l with [] -> raise Not_found | x :: l' -> try Wd_dictionary.find key x with Not_found -> lookup key l' in let transform_to_uiencode n enclist = (* Wrap the node tree [n] into <ui:encode enc="ENCLIST">...</ui:encode> *) if enclist = [] then n else let n' = r.mk_uiencode() in n' # set_attributes [ "enc", Valuelist enclist ]; n' # set_nodes [n]; n' in let lookup_text p = (* Return the text string for parameter 'p' or raise a Runtime_error, * if 'p' does not exist *) try Lazy.force (lookup p params).param_text with Not_found -> raise (Instantiation_error (r.template_name ^ ": Nothing to instantiate for template parameter `$" ^ p ^ "'")) in let lookup_tree p = (* Return the XML tree for parameter 'p' or raise a Runtime_error, * if 'p' does not exist *) try (lookup p params).param_tree with Not_found -> raise (Instantiation_error (r.template_name ^ ": Nothing to instantiate for template parameter `$" ^ p ^ "'")) in let raise_insterr_with_loc (entity,line,col) msg expr_str = if line >= 0 then raise (Instantiation_error ("In entity " ^ entity ^ ", near line " ^ string_of_int line ^ ", position " ^ string_of_int col ^ ", within the expression " ^ expr_str ^ ": " ^ msg)) else raise (Instantiation_error ("In the expression " ^ expr_str ^ ": " ^ msg)) in let eval_expr_prot (entity,line,col) expr expr_str = let subst (p_name,enclist) = Wd_brexpr_eval.apply_oe r.app (lookup_text p_name) enclist in try Wd_brexpr_eval.eval_expr_oe dlg (subst_expr_params ~subst expr) with | (Instantiation_error msg as e) -> (* From subst_expr_params *) raise_insterr_with_loc (entity,line,col) msg expr_str | Eval_error_noloc msg -> (* From eval_expr *) raise_insterr_with_loc (entity,line,col) msg expr_str in let rec inst_attributes pos l = (* Instantiate the attributes in the list 'l', an el_attributes-like * list. Returns the attributes in a form that can be passed as * argument to the method create_element. *) match l with [] -> [] | (n, Att_expr_implied) :: l' -> (* Implied value: leave this out *) inst_attributes pos l' | (n, Att_expr_value v) :: l' -> (* Normal value *) let v' = String.concat "" (List.map (function | (`Literal s, _) -> s | (`Expr_param (p,enclist), _) -> Wd_brexpr_eval.apply_oe r.app (lookup_text p) enclist | ((#expr_oe as e), in_s) -> eval_expr_prot pos e in_s ) v ) in (n, v') :: inst_attributes pos l' in let rec inst tree = (* Instantiates the 'tree' by descending recursively into the tree, * calling the right instantiation function for every node, * and creating a new XML tree with the same structure as 'tree'. * The XML tree is returned. *) match tree with Element el -> (* If there are no attributes with parameters, some speedup is * possible. *) if el.el_have_placeholders_in_atts then begin (* The slower but general case. *) let pos = el.el_node # position in let inst_atts = inst_attributes pos el.el_attributes in let c = el.el_node # create_element dtd (T_element el.el_name) inst_atts in c # set_nodes (List.map inst (el.el_subtrees)); c end else begin (* The simple and fast case *) let c = el.el_node # orphaned_flat_clone in c # quick_set_attributes (el.el_unsplit_attributes); c # set_nodes (List.map inst (el.el_subtrees)); c end | Data dt -> if dt.dt_clonable then (* Optimization: We can simply clone dt_node to get the new * node. This is much faster because: * - The clone and the original share the data string; it is * not copied * - The 'data_node' class (in Parse) does some optimizations, too. * Especially the HTML-escaped version of the data string is * precomputed, and this second string can be shared, too. *) dt.dt_node # orphaned_flat_clone else (* The general case: Create a new data node with contents * dt_value. *) dt.dt_node # create_data dtd dt.dt_value | Param (p,enclist) -> let replacement = lookup_tree p in transform_to_uiencode replacement#orphaned_clone enclist | Expr (expr,expr_str,dt_node,pos) -> let value = eval_expr_prot pos expr expr_str in dt_node # create_data dtd value in let nodes = List.map inst r.contents in container # set_nodes nodes ;; (* ====================================================================== * History: * * $Log: wd_templrep.ml,v $ * Revision 3.7 2006-03-08 00:56:45 stolpmann * Addition of Table_value, Matrix_value and Function_value. * * New parser for initial expressions. It is now possible to * use $[...] in variable initializers. * * Extended bracket expressions: functions, let expressions, * word literals, matrix literals. * * New type for message catalogs, although not yet implemented. * * Revision 3.6 2005/06/11 14:24:14 stolpmann * Extension of bracket expressions: many new functions. * Functions in bracket expressions may now lazily evaluate their arguments. * ui:if and ui:ifvar may refer to any functions defined for bracket * expressions. * New: ui:ifexpr * Parsing of bracket expressions is now done with ulex. Private interfaces * of PXP are no longer used for this purpose. * Serialization has been rewritten, and it is now safe. * Fix: In ui:context there may now be macro references at any location. * Also documented all these changes. * * Revision 3.5 2004/08/01 18:31:35 stolpmann * Updated PXP lexer code to SVN version of PXP. * Note: This is a temporary solution, not for release! * * Revision 3.4 2002/10/20 19:39:17 stolpmann * New feature: The brackets $[...] can contain expressions, * not only variables * * Revision 3.3 2002/02/26 23:56:35 stolpmann * Fix: $[var] in attributes is no longer optimized away in * some cases * * Revision 3.2 2002/02/14 16:15:21 stolpmann * Added copyright notice. * * Revision 3.1 2002/02/12 20:29:20 stolpmann * Initial release at sourceforge. * * Revision 1.8 2002/01/30 15:13:33 gerd * New: The notations ${name/enc1/...} and $[name/enc1/...] are * recognized and handled properly. * * Revision 1.7 2002/01/27 19:13:15 gerd * New: [get_parameters] * * Revision 1.6 2002/01/26 22:38:26 gerd * Changed the type for passing parameters to templates. It is * now syntax_tree dict instead of a simple list. Furthermore, the * method [instantiate] no longer returns the new context. The new element * ui:context creates the new contexts now. * * Revision 1.5 2002/01/24 23:34:33 gerd * The function [instantiate] raises [Instantiation_error] on * errors. * * Revision 1.4 2002/01/14 15:03:24 gerd * Major change: Typing has been completely revised, and almost * every tiny thing has now a new type. Also renamed a lot. * * Revision 1.3 2001/05/21 12:52:27 gerd * Added a type constraint due to stricter type checking in * O'Caml 3.01 * * Revision 1.2 2000/09/21 15:12:34 gerd * Updated for O'Caml 3 and PXP * * Revision 1.1 2000/05/08 10:36:37 gerd * Initial revision. * * *)