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 #context = libxsltmod.xsltXPathGetTransformContext(pctxt) 84 transform_context = libxsltmod.xsltXPathGetTransformContext(context) 85 name_var = libxsltmod.xsltVariableLookup(transform_context, "this-name", None) 86 if multivalue_name is not None: 87 name = multivalue_name 88 multivalue = 1 89 elif name_var is not None: 90 name = name_var[0].content 91 multivalue = 0 92 else: 93 name = None 94 multivalue = 0 95 node = libxml2dom.Node(libxml2mod.xmlXPathGetContextNode(context)) 96 return path_to_node(node, attribute_ref, name, multivalue) 97 98 def this_position(context): 99 r = path_to_context(context, 0) 100 return r.encode("utf-8") 101 102 def field_name(context): 103 r = path_to_context(context, 1) 104 return r.encode("utf-8") 105 106 def multi_field_name(context, multivalue_name): 107 r = path_to_context(context, 1, multivalue_name) 108 return r.encode("utf-8") 109 110 def new_field(context, name): 111 r = path_to_context(context, 0) + "/" + name 112 return r.encode("utf-8") 113 114 def other_field_names(context, nodes): 115 names = [] 116 for node in nodes: 117 name = path_to_node(libxml2dom.Node(node), 0, None, 0) 118 if name not in names: 119 names.append(name) 120 r = ",".join(names) 121 return r.encode("utf-8") 122 123 def other_multi_field_names(context, multivalue_name, nodes): 124 names = [] 125 for node in nodes: 126 name = path_to_node(libxml2dom.Node(node), 1, multivalue_name, 1) 127 if name not in names: 128 names.append(name) 129 r = ",".join(names) 130 return r.encode("utf-8") 131 132 libxsltmod.xsltRegisterExtModuleFunction("this-position", "http://www.boddie.org.uk/ns/xmltools/template", this_position) 133 libxsltmod.xsltRegisterExtModuleFunction("field-name", "http://www.boddie.org.uk/ns/xmltools/template", field_name) 134 libxsltmod.xsltRegisterExtModuleFunction("multi-field-name", "http://www.boddie.org.uk/ns/xmltools/template", multi_field_name) 135 libxsltmod.xsltRegisterExtModuleFunction("new-field", "http://www.boddie.org.uk/ns/xmltools/template", new_field) 136 libxsltmod.xsltRegisterExtModuleFunction("other-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_field_names) 137 libxsltmod.xsltRegisterExtModuleFunction("other-multi-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_multi_field_names) 138 139 def get_field_name(field_or_multi_name): 140 return field_or_multi_name.split(Constants.multi_separator)[0] 141 142 def get_element_path(field_or_multi_name): 143 144 """ 145 Convert the given 'field_or_multi_name' back to an XPath reference. 146 For example: 147 /configuration#1/details#1/base-system##value -> /*[position() = 1]/*[position() = 1]/base-system 148 """ 149 150 field_name = get_field_name(field_or_multi_name) 151 parts = field_name.split(Constants.path_separator) 152 new_parts = [] 153 for part in parts: 154 path_parts = part.split(Constants.pair_separator) 155 if len(path_parts) == 2: 156 new_parts.append("*[position() = " + path_parts[1] + "]") 157 else: 158 new_parts.append(path_parts[0]) 159 return "/".join(new_parts) 160 161 # vim: tabstop=4 expandtab shiftwidth=4