1 #!/usr/bin/env python 2 3 """ 4 XSL-based form templating. 5 6 Copyright (C) 2005 Paul Boddie <paul@boddie.org.uk> 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Lesser General Public 10 License as published by the Free Software Foundation; either 11 version 2.1 of the License, or (at your option) any later version. 12 13 This library is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Lesser General Public License for more details. 17 18 You should have received a copy of the GNU Lesser General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 """ 22 23 import Constants 24 import libxsltmod, libxml2mod 25 import libxml2dom 26 27 """ 28 import libxml2 29 30 def quiet(context, s): 31 pass 32 33 libxml2.registerErrorHandler(quiet, None) 34 """ 35 36 def path_to_node(node, attribute_ref, name, multivalue=0): 37 38 """ 39 Generate an XSLForms path to the given 'node', producing an attribute 40 reference if 'attribute_ref' is true; for example: 41 42 /package$1/discriminators$5/discriminator$1/category 43 44 Otherwise an element reference is produced; for example: 45 46 /package$1/discriminators$5/discriminator$1 47 48 Use the given 'name' to complete the path if an attribute reference is 49 required (and if a genuine attribute is found at the context node - 50 otherwise 'name' will be None and the context node will be treated like an 51 attribute). 52 53 If 'multivalue' is true and 'attribute_ref' is set, produce an attribute 54 reference using the given 'name': 55 56 /package$1/categories$1/category$$name 57 58 If 'multivalue' is true and 'attribute_ref' is not set, produce an attribute 59 reference using the given 'name' of form (element, attribute): 60 61 /package$1/categories$1/element$$attribute 62 """ 63 64 l = [] 65 # Skip attribute reference. 66 if node.nodeType == node.ATTRIBUTE_NODE: 67 node = node.parentNode 68 # Manually insert the attribute name if defined. 69 if attribute_ref: 70 # A real attribute is referenced. 71 if name is not None: 72 l.insert(0, name) 73 if multivalue: 74 l.insert(0, Constants.multi_separator) 75 l.insert(0, node.nodeName) 76 node = node.parentNode 77 l.insert(0, Constants.path_separator) 78 # Otherwise, treat the element name as an attribute name. 79 # NOTE: Not sure how useful this is. 80 else: 81 l.insert(0, node.nodeName) 82 l.insert(0, Constants.path_separator) 83 node = node.parentNode 84 # Otherwise insert any multivalue references (eg. list-attribute). 85 elif multivalue: 86 element_name, attribute_name = name 87 l.insert(0, attribute_name) 88 l.insert(0, Constants.multi_separator) 89 l.insert(0, element_name) 90 l.insert(0, Constants.path_separator) 91 92 # Element references. 93 while node is not None and node.nodeType != node.DOCUMENT_NODE: 94 l.insert(0, str(int(node.xpath("count(preceding-sibling::*) + 1")))) 95 l.insert(0, Constants.pair_separator) 96 l.insert(0, node.nodeName) 97 l.insert(0, Constants.path_separator) 98 node = node.parentNode 99 return "".join(l) 100 101 def path_to_context(context, attribute_ref, multivalue_name=None): 102 103 """ 104 As a libxslt extension function, return a string containing the XSLForms 105 path to the 'context' node, using the special "this-name" variable to 106 complete the path if an attribute reference is required (as indicated by 107 'attribute_ref' being set to true). If 'multivalue_name' is set, produce a 108 reference to a multivalued field using the given string as the attribute 109 name. 110 """ 111 112 context = libxml2mod.xmlXPathParserGetContext(context) 113 transform_context = libxsltmod.xsltXPathGetTransformContext(context) 114 name_var = libxsltmod.xsltVariableLookup(transform_context, "this-name", None) 115 if multivalue_name is not None: 116 name = multivalue_name 117 multivalue = 1 118 elif name_var is not None: 119 name = libxml2mod.xmlNodeGetContent(name_var[0]) 120 multivalue = 0 121 else: 122 name = None 123 multivalue = 0 124 node = libxml2dom.Node(libxml2mod.xmlXPathGetContextNode(context)) 125 return path_to_node(node, attribute_ref, name, multivalue) 126 127 def this_position(context): 128 #print "this_position" 129 r = path_to_context(context, 0) 130 return r.encode("utf-8") 131 132 def field_name(context): 133 #print "field_name" 134 r = path_to_context(context, 1) 135 return r.encode("utf-8") 136 137 def multi_field_name(context, multivalue_name): 138 #print "multi_field_name" 139 r = path_to_context(context, 1, multivalue_name) 140 return r.encode("utf-8") 141 142 def new_field(context, name): 143 #print "new_field" 144 r = path_to_context(context, 0) + "/" + name 145 return r.encode("utf-8") 146 147 def other_field_names(context, nodes): 148 #print "other_field_names" 149 names = [] 150 for node in nodes: 151 name = path_to_node(libxml2dom.Node(node), 0, None, 0) 152 if name not in names: 153 names.append(name) 154 r = ",".join(names) 155 return r.encode("utf-8") 156 157 def other_multi_field_names(context, multivalue_name, nodes): 158 #print "other_multi_field_names" 159 names = [] 160 for node in nodes: 161 name = path_to_node(libxml2dom.Node(node), 1, multivalue_name, 1) 162 if name not in names: 163 names.append(name) 164 r = ",".join(names) 165 return r.encode("utf-8") 166 167 # New implementations. 168 169 def list_attribute(context, element_name, attribute_name): 170 #print "list_attribute" 171 r = path_to_context(context, 0, (element_name, attribute_name)) 172 return r.encode("utf-8") 173 174 def other_list_attributes(context, element_name, attribute_name, nodes): 175 #print "other_list_attributes" 176 names = [] 177 for node in nodes: 178 name = path_to_node(libxml2dom.Node(node), 0, (element_name, attribute_name), 1) 179 if name not in names: 180 names.append(name) 181 r = ",".join(names) 182 return r.encode("utf-8") 183 184 def other_attributes(context, attribute_name, nodes): 185 #print "other_attributes" 186 # NOTE: Cannot directly reference attributes in the nodes list because 187 # NOTE: libxml2dom does not yet support parent element discovery on 188 # NOTE: attributes. 189 names = [] 190 for node in nodes: 191 name = path_to_node(libxml2dom.Node(node), 1, attribute_name, 0) 192 if name not in names: 193 names.append(name) 194 r = ",".join(names) 195 return r.encode("utf-8") 196 197 # New functions. 198 199 libxsltmod.xsltRegisterExtModuleFunction("list-attribute", "http://www.boddie.org.uk/ns/xmltools/template", list_attribute) 200 libxsltmod.xsltRegisterExtModuleFunction("other-list-attributes", "http://www.boddie.org.uk/ns/xmltools/template", other_list_attributes) 201 libxsltmod.xsltRegisterExtModuleFunction("other-attributes", "http://www.boddie.org.uk/ns/xmltools/template", other_attributes) 202 203 # New names. 204 205 libxsltmod.xsltRegisterExtModuleFunction("this-element", "http://www.boddie.org.uk/ns/xmltools/template", this_position) 206 libxsltmod.xsltRegisterExtModuleFunction("this-attribute", "http://www.boddie.org.uk/ns/xmltools/template", field_name) 207 libxsltmod.xsltRegisterExtModuleFunction("new-attribute", "http://www.boddie.org.uk/ns/xmltools/template", new_field) 208 libxsltmod.xsltRegisterExtModuleFunction("other-elements", "http://www.boddie.org.uk/ns/xmltools/template", other_field_names) 209 210 # Old names. 211 212 libxsltmod.xsltRegisterExtModuleFunction("this-position", "http://www.boddie.org.uk/ns/xmltools/template", this_position) 213 libxsltmod.xsltRegisterExtModuleFunction("field-name", "http://www.boddie.org.uk/ns/xmltools/template", field_name) 214 libxsltmod.xsltRegisterExtModuleFunction("multi-field-name", "http://www.boddie.org.uk/ns/xmltools/template", multi_field_name) 215 libxsltmod.xsltRegisterExtModuleFunction("new-field", "http://www.boddie.org.uk/ns/xmltools/template", new_field) 216 libxsltmod.xsltRegisterExtModuleFunction("other-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_field_names) 217 libxsltmod.xsltRegisterExtModuleFunction("other-multi-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_multi_field_names) 218 219 def get_field_name(field_or_multi_name): 220 return field_or_multi_name.split(Constants.multi_separator)[0] 221 222 def get_element_path(field_or_multi_name): 223 224 """ 225 Convert the given 'field_or_multi_name' back to an XPath reference. 226 For example: 227 /configuration$1/details$1/base-system$$value -> /*[position() = 1]/*[position() = 1]/base-system 228 """ 229 230 field_name = get_field_name(field_or_multi_name) 231 parts = field_name.split(Constants.path_separator) 232 new_parts = [] 233 for part in parts: 234 path_parts = part.split(Constants.pair_separator) 235 if len(path_parts) == 2: 236 new_parts.append("*[position() = " + path_parts[1] + "]") 237 else: 238 new_parts.append(path_parts[0]) 239 return "/".join(new_parts) 240 241 # vim: tabstop=4 expandtab shiftwidth=4