# HG changeset patch # User paulb # Date 1103502639 0 # Node ID 8a3b53c14b8a382a824cfc65a195e3d3d377b229 # Parent 15f403f72a2b1e49265b5fff04fa390d24f62d77 [project @ 2004-12-20 00:30:39 by paulb] Initial revision diff -r 15f403f72a2b -r 8a3b53c14b8a XSLForms/Fields.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/XSLForms/Fields.py Mon Dec 20 00:30:39 2004 +0000 @@ -0,0 +1,332 @@ +#!/usr/bin/env python + +""" +Classes which process field collections, producing instance +documents. Each field entry consists of a field name mapped +to a string value, where the field name may have the following +formats: + + /name1#n1/name2 + /name1#n1/name2#n2/name3 + /name1#n1/name2#n2/name3#n3/name4 + ... + +The indexes n1, n2, n3, ... indicate the position of elements +(starting from 1) in the entire element list, whose elements +may have different names. For example: + + /zoo#1/name + /zoo#1/cage#1:name + /zoo#1/cage#2:name + /zoo#1/funding#3/contributor#1/name + +Some fields may contain the "=" string. This string is +reserved and all text following it is meant to specify a path +into a particular document. For example: + + _action_add_animal=/zoo#1/cage#2 +""" + +import libxml2dom +from xml.dom import EMPTY_NAMESPACE + +class FieldsError(Exception): + pass + +class Fields: + + """ + A class which converts fields in the documented form to XML + instance documents. + """ + + _path_separator = "/" + _pair_separator = "#" + _selector_indicator = "=" + + def __init__(self, encoding="utf-8"): + + """ + Initialise the fields processor with the given 'encoding', + which is optional and which only applies to field data in + Python string form (and not Unicode objects). + """ + + self.encoding = encoding + + def complete_documents(self, documents, fields): + + """ + Complete the given 'documents' using the 'fields' items list. + """ + + for field, value in fields: + + # Ignore selectors. + + if field.find(self._selector_indicator) != -1: + continue + + model_name, components = self._get_model_name_and_components(field) + if model_name is None: + continue + + # Convert the value to Unicode if necessary. + + if type(value) == type(""): + value = unicode(value, encoding=self.encoding) + + # Get a new instance document if none has been made for the + # model. + + if not documents.has_key(model_name): + documents[model_name] = self._new_instance(model_name) + node = documents[model_name] + + # Traverse the components within the instance. + + for component in components: + t = component.split(self._pair_separator) + if len(t) == 1: + node.setAttributeNS(EMPTY_NAMESPACE, t[0], value) + break + + elif len(t) == 2: + + # Convert from one-based indexing (the position() function) + # to zero-based indexing. + + name, index = t[0], int(t[1]) - 1 + if index < 0: + break + node = self._enter_element(node, name, index) + + def complete_selectors(self, selectors, fields, documents): + + """ + Fill in the given 'selectors' dictionary using the given + 'fields' so that it contains mappings from selector names to + parts of the specified 'documents'. + """ + + for field, value in fields: + + # Process selectors only. + + selector_components = field.split(self._selector_indicator) + if len(selector_components) < 2: + continue + + # Get the selector name and path. + # Note that the joining of the components uses the separator, + # but the separator really should not exist in the path. + + selector_name = selector_components[0] + path = self._selector_indicator.join(selector_components[1:]) + + model_name, components = self._get_model_name_and_components(path) + if model_name is None: + continue + + # Go to the instance element. + + if not documents.has_key(model_name) or documents[model_name] is None: + continue + + node = documents[model_name] + + # Traverse the path to find the part of the document to be + # selected. + + for component in components: + t = component.split(self._pair_separator) + if len(t) == 1: + + # Select attribute. + + node = node.getAttributeNodeNS(EMPTY_NAMESPACE, t[0]) + break + + elif len(t) == 2: + + # Convert from one-based indexing (the position() function) + # to zero-based indexing. + + name, index = t[0], int(t[1]) - 1 + if index < 0: + break + + # NOTE: Controversial creation of potentially non-existent + # NOTE: nodes. + + node = self._enter_element(node, name, index) + + if not selectors.has_key(selector_name): + selectors[selector_name] = [] + selectors[selector_name].append(node) + + def _enter_element(self, node, name, index): + + """ + From 'node' enter the element with the given 'name' at the + given 'index' position amongst the child elements. Create + missing child elements if necessary. + """ + + self._ensure_elements(node, index) + + elements = node.xpath("*") + if elements[index].localName == "placeholder": + new_node = node.ownerDocument.createElementNS(EMPTY_NAMESPACE, name) + node.replaceChild(new_node, elements[index]) + else: + new_node = elements[index] + if new_node.localName != name: + raise FieldsError, (new_node.localName, name, elements, index) + + # Enter the newly-created element. + + return new_node + + def _get_model_name_and_components(self, field): + + """ + From 'field', return the model name and components which + describe the path within the instance document associated + with that model. + """ + + # Get the components of the field name. + # Example: /name1#n1/name2#n2/name3 + # Expected: ['', 'name1#n1', 'name2#n2', 'name3'] + + components = field.split(self._path_separator) + if len(components) < 2: + return None, None + + # Extract the model name from the top-level element + # specification. + # Expected: ['name1', 'n1'] + + model_name_and_index = components[1].split(self._pair_separator) + if len(model_name_and_index) != 2: + return None, None + + # Expected: 'name1', ['', 'name1#n1', 'name2#n2', 'name3'] + + return model_name_and_index[0], components[1:] + + def _ensure_elements(self, document, index): + + """ + In the given 'document', extend the child elements list + so that a node can be stored at the given 'index'. + """ + + elements = document.xpath("*") + i = len(elements) + while i <= index: + new_node = document.ownerDocument.createElementNS(EMPTY_NAMESPACE, "placeholder") + document.appendChild(new_node) + i += 1 + + def make_documents(self, fields): + + """ + Make a dictionary mapping model names to new documents prepared + from the given 'fields' dictionary. + """ + + documents = {} + self.complete_documents(documents, fields) + + # Fix the dictionary to return the actual document root. + + for model_name, instance_root in documents.items(): + documents[model_name] = instance_root + return documents + + def get_selectors(self, fields, documents): + + """ + Get a dictionary containing a mapping of selector names to + selected parts of the given 'documents'. + """ + + selectors = {} + self.complete_selectors(selectors, fields, documents) + return selectors + + def _new_instance(self, name): + + "Return an instance root of the given 'name' in a new document." + + return libxml2dom.createDocument(EMPTY_NAMESPACE, name, None) + +if __name__ == "__main__": + + d = [ + ("_action_update", "Some value"), + ("_action_delete=/zoo#1/cage#2", "Some value"), + ("/actions#1/update#1/selected", "Some value"), # Not actually used in output documents or input. + ("/zoo#1/name", "The Zoo זרו"), + ("/zoo#1/cage#1/name", "reptiles"), + ("/zoo#1/cage#1/capacity", "5"), + ("/zoo#1/cage#1/animal#1/name", "Monty"), + ("/zoo#1/cage#1/animal#1/species#1/name", "Python"), + ("/zoo#1/cage#1/animal#1/property#2/name", "texture"), + ("/zoo#1/cage#1/animal#1/property#2/value", "scaled"), + ("/zoo#1/cage#1/animal#1/property#3/name", "length"), + ("/zoo#1/cage#1/animal#1/property#3/value", "5m"), + ("/zoo#1/cage#1/animal#2/name", "Vincent"), + ("/zoo#1/cage#1/animal#2/species#1/name", "Lizard"), + ("/zoo#1/cage#1/animal#2/property#2/name", "colour"), + ("/zoo#1/cage#1/animal#2/property#2/value", "variable"), + ("/zoo#1/cage#1/animal#2/property#3/name", "length"), + ("/zoo#1/cage#1/animal#2/property#3/value", "1m"), + ("/zoo#1/cage#2/name", "mammals"), + ("/zoo#1/cage#2/capacity", "25"), + ("/zoo#1/cage#2/animal#1/name", "Simon"), + ("/zoo#1/cage#2/animal#1/species#1/name", "Giraffe"), + ("/zoo#1/cage#2/animal#2/name", "Leonard"), + ("/zoo#1/cage#2/animal#2/species#1/name", "Lion"), + ("/zoo#1/cage#2/animal#2/property#2/name", "danger"), + ("/zoo#1/cage#2/animal#2/property#2/value", "high"), + ("/zoo#1/funding#3/type", "private"), + ("/zoo#1/funding#3/contributor#1/name", "Animal Corporation"), + ("/zoo#1/funding#3/contributor#1/amount", "543210.987") + ] + + import time + import sys, cmdsyntax + + # Find the documents. + + syntax = cmdsyntax.Syntax(""" + --plain-output=OUTPUT_FILE + --instance-name=NAME + """) + + syntax_matches = syntax.get_args(sys.argv[1:]) + + try: + args = syntax_matches[0] + except IndexError: + print syntax.syntax + sys.exit(1) + + # Create an object to interpret the test data. + + fields = Fields("iso-8859-1") + + t = time.time() + documents = fields.make_documents(d) + print "Building time", time.time() - t + + t = time.time() + libxml2dom.toStream(documents[args["instance-name"]], stream=open(args["plain-output"], "wb"), encoding="utf-8") + print "Prettyprinting time", time.time() - t + + print "Selectors", repr(fields.get_selectors(d, documents)) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 15f403f72a2b -r 8a3b53c14b8a XSLForms/Output.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/XSLForms/Output.py Mon Dec 20 00:30:39 2004 +0000 @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +""" +XSL-based form templating. +""" + +import libxslt + +""" +import libxml2 + +def quiet(context, s): + pass + +libxml2.registerErrorHandler(quiet, None) +""" + +def path_to_node(node): + + "Generate an XSLForms path to the given 'node'." + + l = [] + # Attribute reference. + l.insert(0, node.name) + l.insert(0, "/") + node = node.parent + # Element references. + while node is not None and node.type != "document_xml": + l.insert(0, str(int(node.xpathEval("count(preceding-sibling::*) + 1")))) + l.insert(0, "#") + l.insert(0, node.name) + l.insert(0, "/") + node = node.parent + return "".join(l) + +def this_position(context): + + """ + As a libxslt extension function, return a string containing the XSLForms + path to the 'context' node. + """ + + pctxt = libxslt.xpathParserContext(_obj=context) + context = pctxt.context() + node = context.contextNode() + return path_to_node(node) + +libxslt.registerExtModuleFunction("this-position", "http://www.boddie.org.uk/ns/xmltools/template", this_position) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 15f403f72a2b -r 8a3b53c14b8a XSLForms/Prepare.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/XSLForms/Prepare.py Mon Dec 20 00:30:39 2004 +0000 @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +""" +Preparation of templating stylesheets. +""" + +import XSLOutput +import libxml2dom +import os + +resource_dir = os.path.join(os.path.split(__file__)[0], "XSL") + +def make_stylesheet(template_name, output_name, stylesheet_name="Prepare.xsl", encoding="utf-8"): + global resource_dir + proc = XSLOutput.Processor([os.path.join(resource_dir, stylesheet_name)]) + template = libxml2dom.parse(template_name) + proc.send_output(open(output_name, "wb"), encoding, template) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 15f403f72a2b -r 8a3b53c14b8a XSLForms/XSL/Prepare.xsl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/XSLForms/XSL/Prepare.xsl Mon Dec 20 00:30:39 2004 +0000 @@ -0,0 +1,141 @@ + + + + + + http://www.boddie.org.uk/ns/xmltools/template-expr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 15f403f72a2b -r 8a3b53c14b8a XSLForms/__init__.py diff -r 15f403f72a2b -r 8a3b53c14b8a XSLOutput.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/XSLOutput.py Mon Dec 20 00:30:39 2004 +0000 @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +""" +XSL output classes and functions. +""" + +# NOTE: Make this use other implementations, too. + +import libxslt +import libxml2dom + +class OutputError(Exception): + pass + +class Processor: + + """ + A handler which can prepare output for an XMLTools2 template. + """ + + def __init__(self, filenames, references=None): + + """ + Initialise the handler with the 'filenames' of stylesheets producing the + final output, a 'references' dictionary indicating related stylesheets. + """ + + self.references = references or {} + + # Remember the stylesheet documents. + + self.stylesheets = [] + for filename in filenames: + self.stylesheets.append(libxslt.parseStylesheetFile(filename)) + + def __del__(self): + + """ + Tidy up the stylesheet documents. + """ + + for stylesheet in self.stylesheets: + stylesheet.freeStylesheet() + + def send_output(self, stream, encoding, document): + + """ + Send output to the given 'stream' using the given output encoding for + the given 'document'. + """ + + result = self._get_result(document) + + if result is not None: + # Since result is a native node, use the serialize method. + stream.write(result.serialize(encoding)) + result.freeDoc() + else: + raise OutputError, "Transformation failed." + + def get_result(self, document): + + """ + Return a transformed document produced from the object's stylesheets and + the given 'document'. + """ + + result = self._get_result(document) + + if result is not None: + return libxml2dom.Node(result) + else: + raise OutputError, "Transformation failed." + + def _get_result(self, document): + + """ + Return a transformation of the given 'document'. + """ + + if hasattr(document, "as_native_node"): + document = document.as_native_node() + + # Transform the localised instance into the final output. + + parameters = {} + for name, reference in self.references.items(): + parameters[name.encode("utf-8")] = ("document('%s')" % reference).encode("utf-8") + + last_result = document + for stylesheet in self.stylesheets: + result = stylesheet.applyStylesheet(last_result, parameters) + if last_result is not None: + if last_result != document: + last_result.freeDoc() + last_result = result + else: + raise OutputError, "Transformation failed." + + return result + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 15f403f72a2b -r 8a3b53c14b8a examples/Common/PEP241/Resources/pep241_error.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/Common/PEP241/Resources/pep241_error.xml Mon Dec 20 00:30:39 2004 +0000 @@ -0,0 +1,47 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 15f403f72a2b -r 8a3b53c14b8a examples/Common/PEP241/Resources/pep241_template.xhtml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/Common/PEP241/Resources/pep241_template.xhtml Mon Dec 20 00:30:39 2004 +0000 @@ -0,0 +1,184 @@ + + + + + PEP 241 Package Registry + + + + + +

PEP 241 Package Registry

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Summary
Package name + +
Package version0.2
SummaryAn XML-based + Web forms processing package.
DescriptionInsert + longer description here!
Only {size} characters can be used in a + description.
Home pagehttp://www.boddie.org.uk/python/XMLForms2/index.html
LicenceLGPL
Discriminators
Discriminator + +
Platforms
Platform nameLinux (with more listed below) +
+
Supported platforms
Platform nameLinux (with more listed below) +
+
Keywords
KeywordWeb programming +
Authors
Author namePaul Boddie +
Author contactOslo, Norway
Author e-mailpaul@boddie.org.uk
Dependencies
Package nameXMLTools2 +
Package version0.2
+
Actions
+ +
+
+ + diff -r 15f403f72a2b -r 8a3b53c14b8a examples/Common/PEP241/Resources/styles.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/Common/PEP241/Resources/styles.css Mon Dec 20 00:30:39 2004 +0000 @@ -0,0 +1,11 @@ +BODY { +background-color: white; +} + +.heading { +background-color: silver; +} + +.error { +color: red; +} diff -r 15f403f72a2b -r 8a3b53c14b8a scripts/output.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/output.py Mon Dec 20 00:30:39 2004 +0000 @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +"Prepare a templating stylesheet." + +import XSLForms.Output +import XSLOutput +import libxml2dom +import sys + +if __name__ == "__main__": + try: + input_xml = sys.argv[1] + trans_xsl = sys.argv[2] + output_xml = sys.argv[3] + except IndexError: + print "Please specify an input filename, a template filename and an output filename." + print "For example:" + print "output.py input.xml output.xsl output.xhtml" + sys.exit(1) + + proc = XSLOutput.Processor([trans_xsl]) + proc.send_output(open(output_xml, "wb"), "utf-8", libxml2dom.parse(input_xml)) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 15f403f72a2b -r 8a3b53c14b8a scripts/prepare.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/prepare.py Mon Dec 20 00:30:39 2004 +0000 @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +"Prepare a templating stylesheet." + +import XSLForms.Prepare +import sys + +if __name__ == "__main__": + try: + input_xml = sys.argv[1] + output_xml = sys.argv[2] + except IndexError: + print "Please specify a template and an output filename." + print "For example:" + print "prepare.py template.xhtml output.xsl" + sys.exit(1) + + XSLForms.Prepare.make_stylesheet(input_xml, output_xml) + +# vim: tabstop=4 expandtab shiftwidth=4