XSLTools

XSLForms/Output.py

211:a4006b6da1a0
2005-08-25 paulb [project @ 2005-08-25 14:43:34 by paulb] Changed the path encoding to UTF-8 - if this is not correct, ISO-8859-1 will be tried anyway.
     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 and 'attribute_ref' is set, produce an attribute    54     reference using the given 'name':    55     56     /package$1/categories$1/category$$name    57     58     If 'multivalue' is true and 'attribute_ref' is not set, produce an attribute    59     reference using the given 'name' of form (element, attribute):    60     61     /package$1/categories$1/element$$attribute    62     """    63     64     l = []    65     # Skip attribute reference.    66     if node.nodeType == node.ATTRIBUTE_NODE:    67         node = node.parentNode    68     # Manually insert the attribute name if defined.    69     if attribute_ref:    70         # A real attribute is referenced.    71         if name is not None:    72             l.insert(0, name)    73             if multivalue:    74                 l.insert(0, Constants.multi_separator)    75                 l.insert(0, node.nodeName)    76                 node = node.parentNode    77             l.insert(0, Constants.path_separator)    78         # Otherwise, treat the element name as an attribute name.    79         # NOTE: Not sure how useful this is.    80         else:    81             l.insert(0, node.nodeName)    82             l.insert(0, Constants.path_separator)    83             node = node.parentNode    84     # Otherwise insert any multivalue references (eg. list-attribute).    85     elif multivalue:    86         element_name, attribute_name = name    87         l.insert(0, attribute_name)    88         l.insert(0, Constants.multi_separator)    89         l.insert(0, element_name)    90         l.insert(0, Constants.path_separator)    91     92     # Element references.    93     while node is not None and node.nodeType != node.DOCUMENT_NODE:    94         l.insert(0, str(int(node.xpath("count(preceding-sibling::*) + 1"))))    95         l.insert(0, Constants.pair_separator)    96         l.insert(0, node.nodeName)    97         l.insert(0, Constants.path_separator)    98         node = node.parentNode    99     return "".join(l)   100    101 def path_to_context(context, attribute_ref, multivalue_name=None):   102    103     """   104     As a libxslt extension function, return a string containing the XSLForms   105     path to the 'context' node, using the special "this-name" variable to   106     complete the path if an attribute reference is required (as indicated by   107     'attribute_ref' being set to true). If 'multivalue_name' is set, produce a   108     reference to a multivalued field using the given string as the attribute   109     name.   110     """   111    112     context = libxml2mod.xmlXPathParserGetContext(context)   113     transform_context = libxsltmod.xsltXPathGetTransformContext(context)   114     name_var = libxsltmod.xsltVariableLookup(transform_context, "this-name", None)   115     if multivalue_name is not None:   116         name = multivalue_name   117         multivalue = 1   118     elif name_var is not None:   119         name = libxml2mod.xmlNodeGetContent(name_var[0])   120         multivalue = 0   121     else:   122         name = None   123         multivalue = 0   124     node = libxml2dom.Node(libxml2mod.xmlXPathGetContextNode(context))   125     return path_to_node(node, attribute_ref, name, multivalue)   126    127 # Exposed extension functions.   128    129 def this_element(context):   130    131     """   132     Exposed as {template:this-element()}.   133     Provides a reference to the current element in the form data structure.   134     """   135    136     #print "this_element"   137     r = path_to_context(context, 0)   138     return r.encode("utf-8")   139    140 def this_attribute(context):   141    142     """   143     Exposed as {template:this-attribute()}.   144     Provides a reference to the current attribute in the form data structure.   145     """   146    147     #print "this_attribute"   148     r = path_to_context(context, 1)   149     return r.encode("utf-8")   150    151 def new_attribute(context, name):   152    153     """   154     Exposed as {template:new-attribute(name)}.   155     Provides a reference to a new attribute of the given 'name' on the current   156     element in the form data structure.   157     """   158    159     #print "new_attribute"   160     r = path_to_context(context, 0) + "/" + name   161     return r.encode("utf-8")   162    163 def other_elements(context, nodes):   164    165     """   166     Exposed as {template:other-elements(nodes)}.   167     Provides a reference to other elements in the form data structure according   168     to the specified 'nodes' parameter (an XPath expression in the template).   169     """   170    171     #print "other_elements"   172     names = []   173     for node in nodes:   174         name = path_to_node(libxml2dom.Node(node), 0, None, 0)   175         if name not in names:   176             names.append(name)   177     r = ",".join(names)   178     return r.encode("utf-8")   179    180 def list_attribute(context, element_name, attribute_name):   181    182     """   183     Exposed as {template:list-attribute(element_name, attribute_name)}.   184     Provides a reference to one or many elements of the given 'element_name'   185     found under the current element in the form data structure having   186     attributes with the given 'attribute_name'.   187     """   188    189     #print "list_attribute"   190     r = path_to_context(context, 0, (element_name, attribute_name))   191     return r.encode("utf-8")   192    193 def other_list_attributes(context, element_name, attribute_name, nodes):   194    195     """   196     Exposed as {template:other-list-attributes(element_name, attribute_name, nodes)}.   197     Provides a reference to other elements in the form data structure, found   198     under the specified 'nodes' (described using an XPath expression in the   199     template) having the given 'element_name' and bearing attributes of the   200     given 'attribute_name'.   201     """   202    203     #print "other_list_attributes"   204     names = []   205     for node in nodes:   206         name = path_to_node(libxml2dom.Node(node), 0, (element_name, attribute_name), 1)   207         if name not in names:   208             names.append(name)   209     r = ",".join(names)   210     return r.encode("utf-8")   211    212 def other_attributes(context, attribute_name, nodes):   213    214     """   215     Exposed as {template:other-attributes(name, nodes)}.   216     Provides a reference to attributes in the form data structure of the given   217     'attribute_name' residing on the specified 'nodes' (described using an XPath   218     expression in the template).   219     """   220    221     #print "other_attributes"   222     # NOTE: Cannot directly reference attributes in the nodes list because   223     # NOTE: libxml2dom does not yet support parent element discovery on   224     # NOTE: attributes.   225     names = []   226     for node in nodes:   227         name = path_to_node(libxml2dom.Node(node), 1, attribute_name, 0)   228         if name not in names:   229             names.append(name)   230     r = ",".join(names)   231     return r.encode("utf-8")   232    233 def child_element(context, element_name, position, node_paths):   234    235     """   236     Exposed as {template:child-element(element_name, position, node_paths)}.   237     Provides relative paths to the specifed 'element_name', having the given   238     'position' (1-based) under each element specified in 'node_paths' (provided   239     by calls to other extension functions in the template). For example:   240    241     template:child-element('comment', 1, template:this-element()) -> '.../comment$1'   242     """   243    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     l = []   262     for node_path in node_paths.split(","):   263         l.append(node_path + Constants.path_separator + attribute_name)   264     return ",".join(l).encode("utf-8")   265    266 # Old implementations.   267    268 def multi_field_name(context, multivalue_name):   269     #print "multi_field_name"   270     r = path_to_context(context, 1, multivalue_name)   271     return r.encode("utf-8")   272    273 def other_multi_field_names(context, multivalue_name, nodes):   274     #print "other_multi_field_names"   275     names = []   276     for node in nodes:   277         name = path_to_node(libxml2dom.Node(node), 1, multivalue_name, 1)   278         if name not in names:   279             names.append(name)   280     r = ",".join(names)   281     return r.encode("utf-8")   282    283 # New functions.   284    285 libxsltmod.xsltRegisterExtModuleFunction("list-attribute", "http://www.boddie.org.uk/ns/xmltools/template", list_attribute)   286 libxsltmod.xsltRegisterExtModuleFunction("other-list-attributes", "http://www.boddie.org.uk/ns/xmltools/template", other_list_attributes)   287 libxsltmod.xsltRegisterExtModuleFunction("other-attributes", "http://www.boddie.org.uk/ns/xmltools/template", other_attributes)   288 libxsltmod.xsltRegisterExtModuleFunction("child-element", "http://www.boddie.org.uk/ns/xmltools/template", child_element)   289 libxsltmod.xsltRegisterExtModuleFunction("child-attribute", "http://www.boddie.org.uk/ns/xmltools/template", child_attribute)   290    291 # New names.   292    293 libxsltmod.xsltRegisterExtModuleFunction("this-element", "http://www.boddie.org.uk/ns/xmltools/template", this_element)   294 libxsltmod.xsltRegisterExtModuleFunction("this-attribute", "http://www.boddie.org.uk/ns/xmltools/template", this_attribute)   295 libxsltmod.xsltRegisterExtModuleFunction("new-attribute", "http://www.boddie.org.uk/ns/xmltools/template", new_attribute)   296 libxsltmod.xsltRegisterExtModuleFunction("other-elements", "http://www.boddie.org.uk/ns/xmltools/template", other_elements)   297    298 # Old names.   299    300 libxsltmod.xsltRegisterExtModuleFunction("this-position", "http://www.boddie.org.uk/ns/xmltools/template", this_element)   301 libxsltmod.xsltRegisterExtModuleFunction("field-name", "http://www.boddie.org.uk/ns/xmltools/template", this_attribute)   302 libxsltmod.xsltRegisterExtModuleFunction("new-field", "http://www.boddie.org.uk/ns/xmltools/template", new_attribute)   303 libxsltmod.xsltRegisterExtModuleFunction("other-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_elements)   304    305 # Old functions.   306    307 libxsltmod.xsltRegisterExtModuleFunction("multi-field-name", "http://www.boddie.org.uk/ns/xmltools/template", multi_field_name)   308 libxsltmod.xsltRegisterExtModuleFunction("other-multi-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_multi_field_names)   309    310 def get_field_name(field_or_multi_name):   311     return field_or_multi_name.split(Constants.multi_separator)[0]   312    313 def get_element_path(field_or_multi_name):   314    315     """   316     Convert the given 'field_or_multi_name' back to an XPath reference.   317     For example:   318     /configuration$1/details$1/base-system$$value -> /*[position() = 1]/*[position() = 1]/base-system   319     """   320    321     field_name = get_field_name(field_or_multi_name)   322     parts = field_name.split(Constants.path_separator)   323     new_parts = []   324     for part in parts:   325         path_parts = part.split(Constants.pair_separator)   326         if len(path_parts) == 2:   327             new_parts.append("*[position() = " + path_parts[1] + "]")   328         else:   329             new_parts.append(path_parts[0])   330     return "/".join(new_parts)   331    332 # vim: tabstop=4 expandtab shiftwidth=4