1 #!/usr/bin/env python 2 3 """ 4 XSL-based form templating. 5 """ 6 7 import Constants 8 import libxslt 9 import libxml2 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.type == "attribute": 46 node = node.parent 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.name) 55 node = node.parent 56 l.insert(0, Constants.path_separator) 57 # Otherwise, treat the element name as an attribute name. 58 else: 59 l.insert(0, node.name) 60 l.insert(0, Constants.path_separator) 61 node = node.parent 62 # Element references. 63 while node is not None and node.type != "document_xml": 64 l.insert(0, str(int(node.xpathEval("count(preceding-sibling::*) + 1")))) 65 l.insert(0, Constants.pair_separator) 66 l.insert(0, node.name) 67 l.insert(0, Constants.path_separator) 68 node = node.parent 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 pctxt = libxslt.xpathParserContext(_obj=context) 83 context = pctxt.context() 84 node = context.contextNode() 85 transform_context = context.transformContext() 86 name_var = transform_context.variableLookup("this-name", None) 87 if multivalue_name is not None: 88 name = multivalue_name 89 multivalue = 1 90 elif name_var is not None: 91 name = name_var[0].content 92 multivalue = 0 93 else: 94 name = None 95 multivalue = 0 96 return path_to_node(node, attribute_ref, name, multivalue) 97 98 def this_position(context): 99 return path_to_context(context, 0) 100 101 def field_name(context): 102 return path_to_context(context, 1) 103 104 def multi_field_name(context, multivalue_name): 105 return path_to_context(context, 1, multivalue_name) 106 107 def new_field(context, name): 108 return path_to_context(context, 0) + "/" + name 109 110 def other_field_names(context, nodes): 111 names = [] 112 for node in nodes: 113 n = libxml2.xmlNode(node) 114 name = path_to_node(n, 0, None, 0) 115 if name not in names: 116 names.append(name) 117 return ",".join(names) 118 119 def other_multi_field_names(context, multivalue_name, nodes): 120 names = [] 121 for node in nodes: 122 n = libxml2.xmlNode(node) 123 name = path_to_node(n, 1, multivalue_name, 1) 124 if name not in names: 125 names.append(name) 126 return ",".join(names) 127 128 libxslt.registerExtModuleFunction("this-position", "http://www.boddie.org.uk/ns/xmltools/template", this_position) 129 libxslt.registerExtModuleFunction("field-name", "http://www.boddie.org.uk/ns/xmltools/template", field_name) 130 libxslt.registerExtModuleFunction("multi-field-name", "http://www.boddie.org.uk/ns/xmltools/template", multi_field_name) 131 libxslt.registerExtModuleFunction("new-field", "http://www.boddie.org.uk/ns/xmltools/template", new_field) 132 libxslt.registerExtModuleFunction("other-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_field_names) 133 libxslt.registerExtModuleFunction("other-multi-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_multi_field_names) 134 135 def get_field_name(field_or_multi_name): 136 return field_or_multi_name.split(Constants.multi_separator)[0] 137 138 def get_element_path(field_or_multi_name): 139 140 """ 141 Convert the given 'field_or_multi_name' back to an XPath reference. 142 For example: 143 /configuration#1/details#1/base-system##value -> /*[position() = 1]/*[position() = 1]/base-system 144 """ 145 146 field_name = get_field_name(field_or_multi_name) 147 parts = field_name.split(Constants.path_separator) 148 new_parts = [] 149 for part in parts: 150 path_parts = part.split(Constants.pair_separator) 151 if len(path_parts) == 2: 152 new_parts.append("*[position() = " + path_parts[1] + "]") 153 else: 154 new_parts.append(path_parts[0]) 155 return "/".join(new_parts) 156 157 # vim: tabstop=4 expandtab shiftwidth=4