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, produce an attribute reference using the given 54 'name' of the following form: 55 56 /package#1/categories#1/category##name 57 """ 58 59 l = [] 60 # Skip attribute reference. 61 if node.nodeType == node.ATTRIBUTE_NODE: 62 node = node.parentNode 63 # Manually insert the attribute name if defined. 64 if attribute_ref: 65 # A real attribute is referenced. 66 if name is not None: 67 l.insert(0, name) 68 if multivalue: 69 l.insert(0, Constants.multi_separator) 70 l.insert(0, node.nodeName) 71 node = node.parentNode 72 l.insert(0, Constants.path_separator) 73 # Otherwise, treat the element name as an attribute name. 74 else: 75 l.insert(0, node.nodeName) 76 l.insert(0, Constants.path_separator) 77 node = node.parentNode 78 # Element references. 79 while node is not None and node.nodeType != node.DOCUMENT_NODE: 80 l.insert(0, str(int(node.xpath("count(preceding-sibling::*) + 1")))) 81 l.insert(0, Constants.pair_separator) 82 l.insert(0, node.nodeName) 83 l.insert(0, Constants.path_separator) 84 node = node.parentNode 85 return "".join(l) 86 87 def path_to_context(context, attribute_ref, multivalue_name=None): 88 89 """ 90 As a libxslt extension function, return a string containing the XSLForms 91 path to the 'context' node, using the special "this-name" variable to 92 complete the path if an attribute reference is required (as indicated by 93 'attribute_ref' being set to true). If 'multivalue_name' is set, produce a 94 reference to a multivalued field using the given string as the attribute 95 name. 96 """ 97 98 context = libxml2mod.xmlXPathParserGetContext(context) 99 transform_context = libxsltmod.xsltXPathGetTransformContext(context) 100 name_var = libxsltmod.xsltVariableLookup(transform_context, "this-name", None) 101 if multivalue_name is not None: 102 name = multivalue_name 103 multivalue = 1 104 elif name_var is not None: 105 name = libxml2mod.xmlNodeGetContent(name_var[0]) 106 multivalue = 0 107 else: 108 name = None 109 multivalue = 0 110 node = libxml2dom.Node(libxml2mod.xmlXPathGetContextNode(context)) 111 return path_to_node(node, attribute_ref, name, multivalue) 112 113 def this_position(context): 114 #print "this_position" 115 r = path_to_context(context, 0) 116 return r.encode("utf-8") 117 118 def field_name(context): 119 #print "field_name" 120 r = path_to_context(context, 1) 121 return r.encode("utf-8") 122 123 def multi_field_name(context, multivalue_name): 124 #print "multi_field_name" 125 r = path_to_context(context, 1, multivalue_name) 126 return r.encode("utf-8") 127 128 def new_field(context, name): 129 #print "new_field" 130 r = path_to_context(context, 0) + "/" + name 131 return r.encode("utf-8") 132 133 def other_field_names(context, nodes): 134 #print "other_field_names" 135 names = [] 136 for node in nodes: 137 name = path_to_node(libxml2dom.Node(node), 0, None, 0) 138 if name not in names: 139 names.append(name) 140 r = ",".join(names) 141 return r.encode("utf-8") 142 143 def other_multi_field_names(context, multivalue_name, nodes): 144 #print "other_multi_field_names" 145 names = [] 146 for node in nodes: 147 name = path_to_node(libxml2dom.Node(node), 1, multivalue_name, 1) 148 if name not in names: 149 names.append(name) 150 r = ",".join(names) 151 return r.encode("utf-8") 152 153 libxsltmod.xsltRegisterExtModuleFunction("this-position", "http://www.boddie.org.uk/ns/xmltools/template", this_position) 154 libxsltmod.xsltRegisterExtModuleFunction("field-name", "http://www.boddie.org.uk/ns/xmltools/template", field_name) 155 libxsltmod.xsltRegisterExtModuleFunction("multi-field-name", "http://www.boddie.org.uk/ns/xmltools/template", multi_field_name) 156 libxsltmod.xsltRegisterExtModuleFunction("new-field", "http://www.boddie.org.uk/ns/xmltools/template", new_field) 157 libxsltmod.xsltRegisterExtModuleFunction("other-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_field_names) 158 libxsltmod.xsltRegisterExtModuleFunction("other-multi-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_multi_field_names) 159 160 def get_field_name(field_or_multi_name): 161 return field_or_multi_name.split(Constants.multi_separator)[0] 162 163 def get_element_path(field_or_multi_name): 164 165 """ 166 Convert the given 'field_or_multi_name' back to an XPath reference. 167 For example: 168 /configuration#1/details#1/base-system##value -> /*[position() = 1]/*[position() = 1]/base-system 169 """ 170 171 field_name = get_field_name(field_or_multi_name) 172 parts = field_name.split(Constants.path_separator) 173 new_parts = [] 174 for part in parts: 175 path_parts = part.split(Constants.pair_separator) 176 if len(path_parts) == 2: 177 new_parts.append("*[position() = " + path_parts[1] + "]") 178 else: 179 new_parts.append(path_parts[0]) 180 return "/".join(new_parts) 181 182 # vim: tabstop=4 expandtab shiftwidth=4