XSLTools

XSLForms/Output.py

260:03cee0e46a02
2005-10-03 paulb [project @ 2005-10-03 00:53:24 by paulb] Introduced input/initialiser stylesheets in place of the types stylesheets previously used.
     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 # Old implementations.   268    269 def multi_field_name(context, multivalue_name):   270     #print "multi_field_name"   271     multivalue_name = unicode(multivalue_name, "utf-8")   272     r = path_to_context(context, 1, multivalue_name)   273     return r.encode("utf-8")   274    275 def other_multi_field_names(context, multivalue_name, nodes):   276     #print "other_multi_field_names"   277     multivalue_name = unicode(multivalue_name, "utf-8")   278     names = []   279     for node in nodes:   280         name = path_to_node(libxml2dom.Node(node), 1, multivalue_name, 1)   281         if name not in names:   282             names.append(name)   283     r = ",".join(names)   284     return r.encode("utf-8")   285    286 # Utility functions.   287    288 def url_encode(context, nodes, charset="utf-8"):   289    290     """   291     Exposed as {template:url-encode(nodes)}.   292     Provides a "URL encoded" string created from the merged textual contents of   293     the given 'nodes', with the encoded character values representing characters   294     in the optional 'charset' (UTF-8 if not specified).   295    296     template:url-encode(./text(), 'iso-8859-1')   297     """   298    299     l = []   300     for node in nodes:   301         s = libxml2dom.Node(node).nodeValue   302         l.append(urllib.quote(s.encode("utf-8")).replace("/", "%2F"))   303     output = "".join(l)   304     return output   305    306 def element_path(context, field_names):   307    308     """   309     Convert the given 'field_names' back to XPath references.   310     For example:   311     /configuration$1/details$1/base-system$$value -> /*[position() = 1]/*[position() = 1]/base-system   312     If more than one field name is given - ie. 'field_names' contains a   313     comma-separated list of names - then only the first name is used.   314     """   315    316     field_name = field_names.split(",")[0]   317    318     # Get the main part of the name (where a multivalue reference was given).   319    320     field_name = get_field_name(field_name)   321    322     # Build the XPath expression.   323    324     parts = field_name.split(Constants.path_separator)   325     new_parts = []   326     for part in parts:   327         path_parts = part.split(Constants.pair_separator)   328         if len(path_parts) == 2:   329             new_parts.append("*[position() = " + path_parts[1] + "]")   330         else:   331             new_parts.append(path_parts[0])   332     return "/".join(new_parts)   333    334 # New functions.   335    336 libxsltmod.xsltRegisterExtModuleFunction("list-attribute", "http://www.boddie.org.uk/ns/xmltools/template", list_attribute)   337 libxsltmod.xsltRegisterExtModuleFunction("other-list-attributes", "http://www.boddie.org.uk/ns/xmltools/template", other_list_attributes)   338 libxsltmod.xsltRegisterExtModuleFunction("other-attributes", "http://www.boddie.org.uk/ns/xmltools/template", other_attributes)   339 libxsltmod.xsltRegisterExtModuleFunction("child-element", "http://www.boddie.org.uk/ns/xmltools/template", child_element)   340 libxsltmod.xsltRegisterExtModuleFunction("child-attribute", "http://www.boddie.org.uk/ns/xmltools/template", child_attribute)   341    342 # New names.   343    344 libxsltmod.xsltRegisterExtModuleFunction("this-element", "http://www.boddie.org.uk/ns/xmltools/template", this_element)   345 libxsltmod.xsltRegisterExtModuleFunction("this-attribute", "http://www.boddie.org.uk/ns/xmltools/template", this_attribute)   346 libxsltmod.xsltRegisterExtModuleFunction("new-attribute", "http://www.boddie.org.uk/ns/xmltools/template", new_attribute)   347 libxsltmod.xsltRegisterExtModuleFunction("other-elements", "http://www.boddie.org.uk/ns/xmltools/template", other_elements)   348    349 # Old names.   350    351 libxsltmod.xsltRegisterExtModuleFunction("this-position", "http://www.boddie.org.uk/ns/xmltools/template", this_element)   352 libxsltmod.xsltRegisterExtModuleFunction("field-name", "http://www.boddie.org.uk/ns/xmltools/template", this_attribute)   353 libxsltmod.xsltRegisterExtModuleFunction("new-field", "http://www.boddie.org.uk/ns/xmltools/template", new_attribute)   354 libxsltmod.xsltRegisterExtModuleFunction("other-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_elements)   355    356 # Old functions.   357    358 libxsltmod.xsltRegisterExtModuleFunction("multi-field-name", "http://www.boddie.org.uk/ns/xmltools/template", multi_field_name)   359 libxsltmod.xsltRegisterExtModuleFunction("other-multi-field-names", "http://www.boddie.org.uk/ns/xmltools/template", other_multi_field_names)   360    361 # Utility functions.   362    363 libxsltmod.xsltRegisterExtModuleFunction("url-encode", "http://www.boddie.org.uk/ns/xmltools/template", url_encode)   364 libxsltmod.xsltRegisterExtModuleFunction("element-path", "http://www.boddie.org.uk/ns/xmltools/template", element_path)   365    366 def get_field_name(field_or_multi_name):   367     return field_or_multi_name.split(Constants.multi_separator)[0]   368    369 # vim: tabstop=4 expandtab shiftwidth=4