XSLTools

XSLForms/Output.py

375:6029e3d9497f
2005-11-01 paulb [project @ 2005-11-01 18:57:41 by paulb] Added request_refresh as the application method for refreshing a form. Added tentative functionality for editing, including addition and removal of widgets.
     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 import urllib    27     28 def path_to_node(node, attribute_ref, name, multivalue=0):    29     30     """    31     Generate an XSLForms path to the given 'node', producing an attribute    32     reference if 'attribute_ref' is true; for example:    33     34     /package$1/discriminators$5/discriminator$1/category    35     36     Otherwise an element reference is produced; for example:    37     38     /package$1/discriminators$5/discriminator$1    39     40     Use the given 'name' to complete the path if an attribute reference is    41     required (and if a genuine attribute is found at the context node -    42     otherwise 'name' will be None and the context node will be treated like an    43     attribute).    44     45     If 'multivalue' is true and 'attribute_ref' is set, produce an attribute    46     reference using the given 'name':    47     48     /package$1/categories$1/category$$name    49     50     If 'multivalue' is true and 'attribute_ref' is not set, produce an attribute    51     reference using the given 'name' of form (element, attribute):    52     53     /package$1/categories$1/element$$attribute    54     """    55     56     l = []    57     # Skip attribute reference.    58     if node.nodeType == node.ATTRIBUTE_NODE:    59         node = node.parentNode    60     # Manually insert the attribute name if defined.    61     if attribute_ref:    62         # A real attribute is referenced.    63         if name is not None:    64             l.insert(0, name)    65             if multivalue:    66                 l.insert(0, Constants.multi_separator)    67                 l.insert(0, node.nodeName)    68                 node = node.parentNode    69             l.insert(0, Constants.path_separator)    70         # Otherwise, treat the element name as an attribute name.    71         # NOTE: Not sure how useful this is.    72         else:    73             l.insert(0, node.nodeName)    74             l.insert(0, Constants.path_separator)    75             node = node.parentNode    76     # Otherwise insert any multivalue references (eg. list-attribute).    77     elif multivalue:    78         element_name, attribute_name = name    79         l.insert(0, attribute_name)    80         l.insert(0, Constants.multi_separator)    81         l.insert(0, element_name)    82         l.insert(0, Constants.path_separator)    83     84     # Element references.    85     while node is not None and node.nodeType != node.DOCUMENT_NODE:    86         l.insert(0, str(int(node.xpath("count(preceding-sibling::*) + 1"))))    87         l.insert(0, Constants.pair_separator)    88         l.insert(0, node.nodeName)    89         l.insert(0, Constants.path_separator)    90         node = node.parentNode    91     return "".join(l)    92     93 def path_to_context(context, attribute_ref, multivalue_name=None):    94     95     """    96     As a libxslt extension function, return a string containing the XSLForms    97     path to the 'context' node, using the special "this-name" variable to    98     complete the path if an attribute reference is required (as indicated by    99     'attribute_ref' being set to true). If 'multivalue_name' is set, produce a   100     reference to a multivalued field using the given string as the attribute   101     name.   102     """   103    104     context = libxml2mod.xmlXPathParserGetContext(context)   105     transform_context = libxsltmod.xsltXPathGetTransformContext(context)   106     name_var = libxsltmod.xsltVariableLookup(transform_context, "this-name", None)   107     if multivalue_name is not None:   108         name = multivalue_name   109         multivalue = 1   110     elif name_var is not None:   111         name = libxml2mod.xmlNodeGetContent(name_var[0])   112         name = unicode(name, "utf-8")   113         multivalue = 0   114     else:   115         name = None   116         multivalue = 0   117     node = libxml2dom.Node(libxml2mod.xmlXPathGetContextNode(context))   118     return path_to_node(node, attribute_ref, name, multivalue)   119    120 # Exposed extension functions.   121    122 def this_element(context):   123    124     """   125     Exposed as {template:this-element()}.   126     Provides a reference to the current element in the form data structure.   127     """   128    129     #print "this_element"   130     r = path_to_context(context, 0)   131     return r.encode("utf-8")   132    133 def this_attribute(context):   134    135     """   136     Exposed as {template:this-attribute()}.   137     Provides a reference to the current attribute in the form data structure.   138     """   139    140     #print "this_attribute"   141     r = path_to_context(context, 1)   142     return r.encode("utf-8")   143    144 def new_attribute(context, name):   145    146     """   147     Exposed as {template:new-attribute(name)}.   148     Provides a reference to a new attribute of the given 'name' on the current   149     element in the form data structure.   150     """   151    152     #print "new_attribute"   153     name = unicode(name, "utf-8")   154     r = path_to_context(context, 0) + "/" + name   155     return r.encode("utf-8")   156    157 def other_elements(context, nodes):   158    159     """   160     Exposed as {template:other-elements(nodes)}.   161     Provides a reference to other elements in the form data structure according   162     to the specified 'nodes' parameter (an XPath expression in the template).   163     """   164    165     #print "other_elements"   166     names = []   167     for node in nodes:   168         name = path_to_node(libxml2dom.Node(node), 0, None, 0)   169         if name not in names:   170             names.append(name)   171     r = ",".join(names)   172     return r.encode("utf-8")   173    174 def list_attribute(context, element_name, attribute_name):   175    176     """   177     Exposed as {template:list-attribute(element_name, attribute_name)}.   178     Provides a reference to one or many elements of the given 'element_name'   179     found under the current element in the form data structure having   180     attributes with the given 'attribute_name'.   181     """   182    183     #print "list_attribute"   184     element_name = unicode(element_name, "utf-8")   185     attribute_name = unicode(attribute_name, "utf-8")   186     r = path_to_context(context, 0, (element_name, attribute_name))   187     return r.encode("utf-8")   188    189 def other_list_attributes(context, element_name, attribute_name, nodes):   190    191     """   192     Exposed as {template:other-list-attributes(element_name, attribute_name, nodes)}.   193     Provides a reference to other elements in the form data structure, found   194     under the specified 'nodes' (described using an XPath expression in the   195     template) having the given 'element_name' and bearing attributes of the   196     given 'attribute_name'.   197     """   198    199     #print "other_list_attributes"   200     element_name = unicode(element_name, "utf-8")   201     attribute_name = unicode(attribute_name, "utf-8")   202     names = []   203     for node in nodes:   204         name = path_to_node(libxml2dom.Node(node), 0, (element_name, attribute_name), 1)   205         if name not in names:   206             names.append(name)   207     r = ",".join(names)   208     return r.encode("utf-8")   209    210 def other_attributes(context, attribute_name, nodes):   211    212     """   213     Exposed as {template:other-attributes(name, nodes)}.   214     Provides a reference to attributes in the form data structure of the given   215     'attribute_name' residing on the specified 'nodes' (described using an XPath   216     expression in the template).   217     """   218    219     #print "other_attributes"   220     attribute_name = unicode(attribute_name, "utf-8")   221     # NOTE: Cannot directly reference attributes in the nodes list because   222     # NOTE: libxml2dom does not yet support parent element discovery on   223     # NOTE: attributes.   224     names = []   225     for node in nodes:   226         name = path_to_node(libxml2dom.Node(node), 1, attribute_name, 0)   227         if name not in names:   228             names.append(name)   229     r = ",".join(names)   230     return r.encode("utf-8")   231    232 def child_element(context, element_name, position, node_paths):   233    234     """   235     Exposed as {template:child-element(element_name, position, node_paths)}.   236     Provides relative paths to the specifed 'element_name', having the given   237     'position' (1-based) under each element specified in 'node_paths' (provided   238     by calls to other extension functions in the template). For example:   239    240     template:child-element('comment', 1, template:this-element()) -> '.../comment$1'   241     """   242    243     element_name = unicode(element_name, "utf-8")   244     l = []   245     for node_path in node_paths.split(","):   246         l.append(node_path + Constants.path_separator + element_name   247             + Constants.pair_separator + str(int(position)))   248     return ",".join(l).encode("utf-8")   249    250 def child_attribute(context, attribute_name, node_paths):   251    252     """   253     Exposed as {template:child-attribute(attribute_name, node_paths)}.   254     Provides a relative path to the specifed 'attribute_name' for each element   255     specified in 'node_paths' (provided by calls to other extension functions in   256     the template). For example:   257    258     template:child-attribute('value', template:this-element()) -> '.../value'   259     """   260    261     attribute_name = unicode(attribute_name, "utf-8")   262     l = []   263     for node_path in node_paths.split(","):   264         l.append(node_path + Constants.path_separator + attribute_name)   265     return ",".join(l).encode("utf-8")   266    267 def selector_name(context, field_name, nodes):   268    269     """   270     Exposed as {template:selector-name(field_name, nodes)}.   271     Provides a selector field name defined using 'field_name' and referring to   272     the given 'nodes'. For example:   273    274     template:selector-name('add-platform', package/platforms) -> 'add-platform=/package$1/platforms$1'   275    276     NOTE: The 'nodes' must be element references.   277     """   278    279     #print "selector_name"   280     names = []   281     for node in nodes:   282         name = path_to_node(libxml2dom.Node(node), 0, None, 0)   283         if name not in names:   284             names.append(field_name + "=" + name)   285     r = ",".join(names)   286     return r.encode("utf-8")   287    288 # Old implementations.   289    290 def multi_field_name(context, multivalue_name):   291     #print "multi_field_name"   292     multivalue_name = unicode(multivalue_name, "utf-8")   293     r = path_to_context(context, 1, multivalue_name)   294     return r.encode("utf-8")   295    296 def other_multi_field_names(context, multivalue_name, nodes):   297     #print "other_multi_field_names"   298     multivalue_name = unicode(multivalue_name, "utf-8")   299     names = []   300     for node in nodes:   301         name = path_to_node(libxml2dom.Node(node), 1, multivalue_name, 1)   302         if name not in names:   303             names.append(name)   304     r = ",".join(names)   305     return r.encode("utf-8")   306    307 # Utility functions.   308    309 def url_encode(context, nodes, charset="utf-8"):   310    311     """   312     Exposed as {template:url-encode(nodes)}.   313     Provides a "URL encoded" string created from the merged textual contents of   314     the given 'nodes', with the encoded character values representing characters   315     in the optional 'charset' (UTF-8 if not specified).   316    317     template:url-encode(./text(), 'iso-8859-1')   318     """   319    320     l = []   321     for node in nodes:   322         s = libxml2dom.Node(node).nodeValue   323         l.append(urllib.quote(s.encode("utf-8")).replace("/", "%2F"))   324     output = "".join(l)   325     return output   326    327 def element_path(context, field_names):   328    329     """   330     Convert the given 'field_names' back to XPath references.   331     For example:   332     /configuration$1/details$1/base-system$$value -> /*[position() = 1]/*[position() = 1]/base-system   333     If more than one field name is given - ie. 'field_names' contains a   334     comma-separated list of names - then only the first name is used.   335     """   336    337     field_name = field_names.split(",")[0]   338    339     # Get the main part of the name (where a multivalue reference was given).   340    341     field_name = get_field_name(field_name)   342    343     # Build the XPath expression.   344    345     parts = field_name.split(Constants.path_separator)   346     new_parts = []   347     for part in parts:   348         path_parts = part.split(Constants.pair_separator)   349         if len(path_parts) == 2:   350             new_parts.append("*[position() = " + path_parts[1] + "]")   351         else:   352             new_parts.append(path_parts[0])   353     return "/".join(new_parts)   354    355 # New functions.   356    357 libxsltmod.xsltRegisterExtModuleFunction("list-attribute", "http://www.boddie.org.uk/ns/xmltools/template", list_attribute)   358 libxsltmod.xsltRegisterExtModuleFunction("other-list-attributes", "http://www.boddie.org.uk/ns/xmltools/template", other_list_attributes)   359 libxsltmod.xsltRegisterExtModuleFunction("other-attributes", "http://www.boddie.org.uk/ns/xmltools/template", other_attributes)   360 libxsltmod.xsltRegisterExtModuleFunction("child-element", "http://www.boddie.org.uk/ns/xmltools/template", child_element)   361 libxsltmod.xsltRegisterExtModuleFunction("child-attribute", "http://www.boddie.org.uk/ns/xmltools/template", child_attribute)   362 libxsltmod.xsltRegisterExtModuleFunction("selector-name", "http://www.boddie.org.uk/ns/xmltools/template", selector_name)   363    364 # New names.   365    366 libxsltmod.xsltRegisterExtModuleFunction("this-element", "http://www.boddie.org.uk/ns/xmltools/template", this_element)   367 libxsltmod.xsltRegisterExtModuleFunction("this-attribute", "http://www.boddie.org.uk/ns/xmltools/template", this_attribute)   368 libxsltmod.xsltRegisterExtModuleFunction("new-attribute", "http://www.boddie.org.uk/ns/xmltools/template", new_attribute)   369 libxsltmod.xsltRegisterExtModuleFunction("other-elements", "http://www.boddie.org.uk/ns/xmltools/template", other_elements)   370    371 # Old names.   372    373 libxsltmod.xsltRegisterExtModuleFunction("this-position", "http://www.boddie.org.uk/ns/xmltools/template", this_element)   374 libxsltmod.xsltRegisterExtModuleFunction("field-name", "http://www.boddie.org.uk/ns/xmltools/template", this_attribute)   375 libxsltmod.xsltRegisterExtModuleFunction("new-field", "http://www.boddie.org.uk/ns/xmltools/template", new_attribute)   376 libxsltmod.xsltRegisterExtModuleFunction("other-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_elements)   377    378 # Old functions.   379    380 libxsltmod.xsltRegisterExtModuleFunction("multi-field-name", "http://www.boddie.org.uk/ns/xmltools/template", multi_field_name)   381 libxsltmod.xsltRegisterExtModuleFunction("other-multi-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_multi_field_names)   382    383 # Utility functions.   384    385 libxsltmod.xsltRegisterExtModuleFunction("url-encode", "http://www.boddie.org.uk/ns/xmltools/template", url_encode)   386 libxsltmod.xsltRegisterExtModuleFunction("element-path", "http://www.boddie.org.uk/ns/xmltools/template", element_path)   387    388 def get_field_name(field_or_multi_name):   389     return field_or_multi_name.split(Constants.multi_separator)[0]   390    391 # vim: tabstop=4 expandtab shiftwidth=4