1 #!/usr/bin/env python 2 3 """ 4 XSL-based form templating. 5 """ 6 7 import Constants 8 import libxsltmod, libxml2mod 9 import libxml2dom 10 11 """ 12 import libxml2 13 14 def quiet(context, s): 15 pass 16 17 libxml2.registerErrorHandler(quiet, None) 18 """ 19 20 def path_to_node(node, attribute_ref, name, multivalue=0): 21 22 """ 23 Generate an XSLForms path to the given 'node', producing an attribute 24 reference if 'attribute_ref' is true; for example: 25 26 /package#1/discriminators#5/discriminator#1/category 27 28 Otherwise an element reference is produced; for example: 29 30 /package#1/discriminators#5/discriminator#1 31 32 Use the given 'name' to complete the path if an attribute reference is 33 required (and if a genuine attribute is found at the context node - 34 otherwise 'name' will be None and the context node will be treated like an 35 attribute). 36 37 If 'multivalue' is true, produce an attribute reference using the given 38 'name' of the following form: 39 40 /package#1/categories#1/category##name 41 """ 42 43 l = [] 44 # Skip attribute reference. 45 if node.nodeType == node.ATTRIBUTE_NODE: 46 node = node.parentNode 47 # Manually insert the attribute name if defined. 48 if attribute_ref: 49 # A real attribute is referenced. 50 if name is not None: 51 l.insert(0, name) 52 if multivalue: 53 l.insert(0, Constants.multi_separator) 54 l.insert(0, node.nodeName) 55 node = node.parentNode 56 l.insert(0, Constants.path_separator) 57 # Otherwise, treat the element name as an attribute name. 58 else: 59 l.insert(0, node.nodeName) 60 l.insert(0, Constants.path_separator) 61 node = node.parentNode 62 # Element references. 63 while node is not None and node.nodeType != node.DOCUMENT_NODE: 64 l.insert(0, str(int(node.xpath("count(preceding-sibling::*) + 1")))) 65 l.insert(0, Constants.pair_separator) 66 l.insert(0, node.nodeName) 67 l.insert(0, Constants.path_separator) 68 node = node.parentNode 69 return "".join(l) 70 71 def path_to_context(context, attribute_ref, multivalue_name=None): 72 73 """ 74 As a libxslt extension function, return a string containing the XSLForms 75 path to the 'context' node, using the special "this-name" variable to 76 complete the path if an attribute reference is required (as indicated by 77 'attribute_ref' being set to true). If 'multivalue_name' is set, produce a 78 reference to a multivalued field using the given string as the attribute 79 name. 80 """ 81 82 context = libxml2mod.xmlXPathParserGetContext(context) 83 transform_context = libxsltmod.xsltXPathGetTransformContext(context) 84 name_var = libxsltmod.xsltVariableLookup(transform_context, "this-name", None) 85 if multivalue_name is not None: 86 name = multivalue_name 87 multivalue = 1 88 elif name_var is not None: 89 name = libxml2mod.xmlNodeGetContent(name_var[0]) 90 multivalue = 0 91 else: 92 name = None 93 multivalue = 0 94 node = libxml2dom.Node(libxml2mod.xmlXPathGetContextNode(context)) 95 return path_to_node(node, attribute_ref, name, multivalue) 96 97 def this_position(context): 98 #print "this_position" 99 r = path_to_context(context, 0) 100 return r.encode("utf-8") 101 102 def field_name(context): 103 #print "field_name" 104 r = path_to_context(context, 1) 105 return r.encode("utf-8") 106 107 def multi_field_name(context, multivalue_name): 108 #print "multi_field_name" 109 r = path_to_context(context, 1, multivalue_name) 110 return r.encode("utf-8") 111 112 def new_field(context, name): 113 #print "new_field" 114 r = path_to_context(context, 0) + "/" + name 115 return r.encode("utf-8") 116 117 def other_field_names(context, nodes): 118 #print "other_field_names" 119 names = [] 120 for node in nodes: 121 name = path_to_node(libxml2dom.Node(node), 0, None, 0) 122 if name not in names: 123 names.append(name) 124 r = ",".join(names) 125 return r.encode("utf-8") 126 127 def other_multi_field_names(context, multivalue_name, nodes): 128 #print "other_multi_field_names" 129 names = [] 130 for node in nodes: 131 name = path_to_node(libxml2dom.Node(node), 1, multivalue_name, 1) 132 if name not in names: 133 names.append(name) 134 r = ",".join(names) 135 return r.encode("utf-8") 136 137 libxsltmod.xsltRegisterExtModuleFunction("this-position", "http://www.boddie.org.uk/ns/xmltools/template", this_position) 138 libxsltmod.xsltRegisterExtModuleFunction("field-name", "http://www.boddie.org.uk/ns/xmltools/template", field_name) 139 libxsltmod.xsltRegisterExtModuleFunction("multi-field-name", "http://www.boddie.org.uk/ns/xmltools/template", multi_field_name) 140 libxsltmod.xsltRegisterExtModuleFunction("new-field", "http://www.boddie.org.uk/ns/xmltools/template", new_field) 141 libxsltmod.xsltRegisterExtModuleFunction("other-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_field_names) 142 libxsltmod.xsltRegisterExtModuleFunction("other-multi-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_multi_field_names) 143 144 def get_field_name(field_or_multi_name): 145 return field_or_multi_name.split(Constants.multi_separator)[0] 146 147 def get_element_path(field_or_multi_name): 148 149 """ 150 Convert the given 'field_or_multi_name' back to an XPath reference. 151 For example: 152 /configuration#1/details#1/base-system##value -> /*[position() = 1]/*[position() = 1]/base-system 153 """ 154 155 field_name = get_field_name(field_or_multi_name) 156 parts = field_name.split(Constants.path_separator) 157 new_parts = [] 158 for part in parts: 159 path_parts = part.split(Constants.pair_separator) 160 if len(path_parts) == 2: 161 new_parts.append("*[position() = " + path_parts[1] + "]") 162 else: 163 new_parts.append(path_parts[0]) 164 return "/".join(new_parts) 165 166 # vim: tabstop=4 expandtab shiftwidth=4