Viewing file: Stylesheet.py (25.73 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
######################################################################## # $Header: /var/local/cvsroot/4Suite/Ft/Xml/Xslt/Stylesheet.py,v 1.49 2005/04/06 23:05:47 jkloth Exp $ """ xsl:stylesheet / xsl:transform implementation; various stylesheet internals
Copyright 2005 Fourthought, Inc. (USA). Detailed license and copyright information: http://4suite.org/COPYRIGHT Project home, documentation, distributions: http://4suite.org/ """
import sys from xml.dom import Node, XMLNS_NAMESPACE
from Ft.Xml import XPath from Ft.Xml.XPath import Conversions from Ft.Xml.Xslt import XSL_NAMESPACE, XsltElement, XsltException, Error from Ft.Xml.Xslt import CategoryTypes, ContentInfo, AttributeInfo from Ft.Xml.Xslt import XsltContext, PatternList from Ft.Xml.Xslt import OutputParameters, MessageSource from Ft.Xml.Xslt.LiteralElement import LiteralElement
__all__ = ['MatchTree', 'StylesheetElement']
def MatchTree(patterns, context): """ Returns all nodes, from context on down, that match the patterns """ state = context.copy()
# Save these before any changes are made to the context children = context.node.childNodes attributes = context.node.xpathAttributes or None
matched = patterns.xsltKeyPrep(context, context.node)
pos = 1 size = len(children) for node in children: context.node, context.position, context.size = node, pos, size map(lambda x, y: x.extend(y), matched, MatchTree(patterns, context)) pos += 1
if attributes: size = len(attributes) pos = 1 for node in attributes: context.node, context.position, context.size=node, pos, size map(lambda x, y: x.extend(y), matched, patterns.xsltKeyPrep(context, node)) pos += 1
context.set(state) return matched
class StylesheetElement(XsltElement): category = None content = ContentInfo.Seq( ContentInfo.Rep(ContentInfo.QName(XSL_NAMESPACE, 'xsl:import')), ContentInfo.TopLevelElements) legalAttrs = { 'id' : AttributeInfo.Id(), 'extension-element-prefixes' : AttributeInfo.Prefixes(), 'exclude-result-prefixes' : AttributeInfo.Prefixes(), 'version' : AttributeInfo.Number(required=1), }
# We don't want ourselves included in these lists since we do the walking doesSetup = doesPrime = doesIdle = 0
def __init__(self, root, namespaceUri, localName, baseUri): XsltElement.__init__(self, root, namespaceUri, localName, baseUri) self.reset1() return
def reset1(self): self.matchTemplates = {} self.namedTemplates = {} self.globalVars = {} self.initialFunctions = {} return
def reset2(self): self.outputParams = OutputParameters.OutputParameters() self.spaceRules = [] self.namespaceAliases = {} self.decimalFormats = {} return
def setup(self): """ Called only once, at the first initialization """ self.reset2()
# By sharing the same list, no special processing is needed later space_rules = [] global_vars = []
# no need for multiple filters to get these top_level_elements = { 'namespace-alias' : [], 'decimal-format' : [], 'strip-space' : space_rules, 'preserve-space' : space_rules, 'output' : [], 'template' : [], 'key' : [], 'variable' : global_vars, 'param' : global_vars, }
# this merges the XSLT children into the above dictionary # any elements whose name is not in the dictionary are thrown away reduce(lambda ignored, child, toplevel=top_level_elements: child.expandedName[0] == XSL_NAMESPACE and toplevel.get(child.expandedName[1], []).append(child), self.children, 'ignored')
# process elements which do not depend on other parts of the stylesheet self._setupNamespaceAliases(top_level_elements['namespace-alias']) self._setupDecimalFormats(top_level_elements['decimal-format']) self._setupWhitespaceRules(space_rules) self._setupOutput(top_level_elements['output']) self._setupTemplates(top_level_elements['template']) self._setupKeys(top_level_elements['key'])
# process those which could require other parts of the stylesheet if self.namespaceAliases: self._setupLiteralElements(self)
self._setupTopLevelVarParams(global_vars) return
def _setupNamespaceAliases(self, aliases): merged = {} for alias in aliases: stylesheet_ns = self.namespaces[alias._stylesheet_prefix] if stylesheet_ns in merged: existing = merged[stylesheet_ns].importIndex if existing < alias.importIndex: merged[stylesheet_ns] = alias elif existing == alias.importIndex: raise XsltException(Error.DUPLICATE_NAMESPACE_ALIAS, alias._stylesheet_prefix) else: merged[stylesheet_ns] = alias
for stylesheet_ns, alias in merged.items(): namespace = self.namespaces[alias._result_prefix] prefix = alias._result_prefix self.namespaceAliases[stylesheet_ns] = (namespace, prefix) return
def _setupDecimalFormats(self, decimal_formats): for df in decimal_formats: name, format = df.getFormatInfo() existing = self.decimalFormats.get(name) if existing and existing != format: # conflicting formats if name: # name is (namespace-uri, local-name) if name[0]: name = df.namespaces[name[0]] + ':' + name[1] else: name = name[1] else: name = '#default' raise XsltException(Error.DUPLICATE_DECIMAL_FORMAT, name) self.decimalFormats[name] = format
if None not in self.decimalFormats: # no default format specified self.decimalFormats[None] = ('.', ',', 'Infinity', '-', 'NaN', '%', unichr(0x2030), '0', '#', ';') return
def _setupWhitespaceRules(self, space_rules): # These values only affect the source document(s) # Sort in increasing import precedence, so the last one added # will have the highest import precedence space_rules.sort(lambda a, b: cmp(a.importIndex, b.importIndex)) merged = {} for rule in space_rules: strip, elements = rule.getWhitespaceInfo() for eName in elements: merged[eName] = strip
# these are sorted by priority where '*' is lowest followed by # 'prefix:*', then remaining tests star = None prefix_star = [] for (namespace, local), strip in merged.items(): rule = (namespace, local, strip) if local == '*': if namespace: prefix_star.append(rule) else: star = rule else: self.spaceRules.append(rule) self.spaceRules.extend(prefix_star) star and self.spaceRules.append(star) return
def _setupOutput(self, outputs): outputs.sort(lambda a, b: cmp(a.importIndex, b.importIndex)) for output in outputs: self.outputParams.parse(output) return
def _setupTemplates(self, templates): named_tpls = self.namedTemplates match_tpls = self.matchTemplates
shortcuts = [] for template, position in zip(templates, xrange(len(templates))): # A 'shortcut' is (sort_key, template_info) (shorts, name) = template.getTemplateInfo(position) if name: # for call-templates existing = named_tpls.get(name) if existing: if existing.importIndex == template.importIndex: raise XsltException(Error.DUPLICATE_NAMED_TEMPLATE, name) elif existing.importIndex > template.importIndex: pass else: # The new template has a higher import precedence # so replace what was already there. named_tpls[name] = template else: # The first occurrence of a template with this name. named_tpls[name] = template
shortcuts.extend(shorts)
# Sort using the sort key, where it is: # (<import precedence>, <template priority>, <stylesheet position>) shortcuts.sort() # We want the highest numbers first shortcuts.reverse()
# Make the lookup tables. A lookup table is first keyed by mode, # then keyed by node type, then, if an element, keyed by expanded name for sort_key, template_info in shortcuts: mode, pattern_info, (node_type, expanded_name) = template_info
# Save the sort key for use in matching pattern_info = (sort_key, pattern_info)
mode_table = match_tpls.get(mode) if not mode_table: mode_table = match_tpls[mode] = {}
type_table = mode_table.get(node_type) if not type_table: if node_type == Node.ELEMENT_NODE: # Elements are further keyed by expanded name type_list = [pattern_info] mode_table[node_type] = {expanded_name : type_list} else: mode_table[node_type] = [pattern_info] elif node_type == Node.ELEMENT_NODE: # Elements are further keyed by expanded name type_list = type_table.get(expanded_name) if not type_list: type_table[expanded_name] = [pattern_info] else: type_list.append(pattern_info) else: # Every other node type gets lumped into a single list # for that node type type_table.append(pattern_info)
#self._printMatchTemplates() return
#def _printMatchTemplates(self): # print "=" * 50 # print "matchTemplates:" # templates = {} # for mode in self.matchTemplates.keys(): # print "-" * 50 # print "mode:",mode # for nodetype in self.matchTemplates[mode].keys(): # print " node type:",nodetype # for patterninfo in self.matchTemplates[mode][nodetype]: # pat, axistype, template = patterninfo # print " template matching pattern %r for axis type %s" % (pat, axistype) # templates[template] = 1 # # print # for template in templates.keys(): # template._printTemplateInfo() # # return
def _setupKeys(self, keys): self._keys = [ key.getKeyInfo() for key in keys ] return
def _setupLiteralElements(self, node): if isinstance(node, LiteralElement): node.fixupAliases(self.namespaceAliases) for child in (node.children or []): self._setupLiteralElements(child) return
def _setupTopLevelVarParams(self, global_vars): self._topVariables = index, ordered = {}, [] for vp in global_vars: existing = index.get(vp._name) if vp.importIndex > (existing and existing.importIndex or -1): # current variable has a higher import precedence index[vp._name] = vp ordered.append(vp) return
############################# Prime Routines #############################
def primeStylesheet(self, contextNode, processor, topLevelParams, docUri): doc = contextNode.rootNode #self.newSource(doc, processor)
context = XsltContext.XsltContext(doc, 1, 1, processorNss=self.namespaces, stylesheet=self, processor=processor, extFunctionMap=processor.extFunctions )
baseUri = docUri or getattr(context.node, 'refUri', None) context.addDocument(doc, baseUri)
#Run prime for all instructions that declared they used it #NOTE: Must prime instructions *before* setting up variables and #parameters because vars and params might rely on context updates #while priming (e.g. if an EXSLT func:function is declared and used #in a variable defn) for instruction in self.root.primeInstructions: instruction.prime(processor, context)
self.initialFunctions.update(context.functions)
overridden_params = {} for split_name, value in topLevelParams.items(): if not isinstance(split_name, tuple): try: split_name = self.expandQName(split_name) except KeyError: continue overridden_params[split_name] = value
for vnode in self._topVariables[1]: self._computeGlobalVar(vnode._name, context, [], [], overridden_params, processor) self.globalVars.update(context.varBindings)
return
def _computeGlobalVar(self, vname, context, processed, deferred, overriddenParams, processor): vnode = self._topVariables[0][vname]
if vnode in deferred: raise XsltException(Error.CIRCULAR_VAR, vname[0], vname[1]) if vnode in processed: return # Is it an <xsl:param>? # expandedName is a tuple (namespace-uri, local-name) if vnode.expandedName[1][0] == 'p': if vname in overriddenParams: context.varBindings[vname] = overriddenParams[vname] else: finished = 0 while not finished: orig_depth = len(processor.writers) try: vnode.instantiate(context, processor) finished = 1 except XPath.RuntimeException, e: if e.errorCode == XPath.RuntimeException.UNDEFINED_VARIABLE: #Remove any aborted and possibly unbalanced #outut handlers on the stack depth = len(processor.writers) for i in xrange(depth - orig_depth): processor.writers.pop() #Defer the current and try evaluating the #one that turned up undefined deferred.append(vnode) self._computeGlobalVar((e.params[0], e.params[1]), context, processed, deferred, overriddenParams, processor) deferred.remove(vnode) else: raise #Set up so that later stylesheets will get overridden by #parameter values set in higher-priority stylesheets overriddenParams[vname] = context.varBindings[vname] else: finished = 0 while not finished: orig_depth = len(processor.writers) try: vnode.instantiate(context, processor) finished = 1 except XPath.RuntimeException, e: if e.errorCode == XPath.RuntimeException.UNDEFINED_VARIABLE: #Remove any aborted and possibly unbalanced #output handlers on the stack depth = len(processor.writers) for i in xrange(depth - orig_depth): processor.writers.pop() #Defer the current and try evaluating the #one that turned up undefined deferred.append(vnode) self._computeGlobalVar((e.params[0], e.params[1]), context, processed, deferred, overriddenParams, processor) deferred.remove(vnode) else: raise processed.append(vnode) return
def updateKey(self, doc, keyName, processor): """ Update a particular key for a new document """ if doc not in processor.keys: processor.keys[doc] = {} if not self._keys: return
context = XsltContext.XsltContext(doc, 1, 1, processorNss=self.namespaces, processor=processor, extFunctionMap=self.initialFunctions) matching_keys = [ k for k in self._keys if k[0] == keyName ] if len(matching_keys) == 1: matching_key = matching_keys[0] else: if not matching_keys: return #decorate decorated_matching_keys = [ (k[3], k) for k in matching_keys ] #sort decorated_matching_keys.sort() #undecorate/retrieve matching_key = decorated_matching_keys[-1][1] (name, match_pattern, use_expr) = matching_key[:3] patterns = PatternList([match_pattern], self.namespaces) match_lists = MatchTree(patterns, context) match_list = match_lists[0] if name not in processor.keys[doc]: processor.keys[doc][name] = {} for node in match_list: state = context.copy() context.node = node key_value_list = use_expr.evaluate(context) if not isinstance(key_value_list, list): key_value_list = [key_value_list] for obj in key_value_list: keystr = Conversions.StringValue(obj) if keystr not in processor.keys[doc][name]: processor.keys[doc][name][keystr] = [] processor.keys[doc][name][keystr].append(node) context.set(state) return
def updateAllKeys(self, context, processor): """ Update all the keys for all documents in the context Only used as an override for the default lazy key eval """ for key in self._keys: for doc in context.documents.values(): self.updateKey(doc, key[0], processor) return
############################# Exit Routines #############################
def idle(self, contextNode, processor, baseUri=None): # run idle for all instructions that declared they used it for instruction in self.root.idleInstructions: instruction.idle(processor) return
def reset(self): """ Called whenever the processor is reset, i.e. after each run Also called whenever multiple stylesheets are appended to a processor, because the new top-level elements from the new stylesheet need to be processed into the main one """ self.reset1() self.reset2() return
############################ Runtime Routines ############################
def getNamedTemplates(self): return self.namedTemplates.copy()
def getGlobalVariables(self): return self.globalVars.copy()
def getInitialFunctions(self): return self.initialFunctions.copy()
def applyTemplates(self, context, processor, params=None, maxImport=None): # Set the current node for this template application node = context.currentNode = context.node context.stylesheet = self #print #print "#" * 60 #print "finding template for node", repr(context.node)
mode_table = self.matchTemplates.get(context.mode) if not mode_table: # No patterns defined in this mode return 0
patterns = [] node_type = node.nodeType if node_type == Node.ELEMENT_NODE: table = mode_table.get(node_type) if table: key = (node.namespaceURI, node.localName) # First use those template which could match this expanded name patterns.extend(table.get(key, [])) patterns = table.get(key, []) # Then add those that are wildcard tests ('*' and 'prefix:*') patterns.extend(table.get(None, [])) else: patterns.extend(mode_table.get(node_type, []))
# For any node type add those patterns that don't have a distinct type: # node(), id() and key() patterns patterns.extend(mode_table.get(None, []))
# Early exit for no matches if not patterns: return 0
# If this is called from apply-imports, remove those patterns # with a higher import precedence than what was specified. if maxImport is not None: patterns = filter(lambda x, m=maxImport: x[0][0] < m, patterns)
# Since the patterns may come from different tables, resort them patterns.sort() patterns.reverse() # highest numbers first
if 1: # recovery_method == Recovery.SILENT # (default until recovery behaviour is selectable) # Just use the first matching pattern since they are # already sorted in descending order. for sort_key, (pattern, axis_type, template) in patterns: context.processorNss = template.namespaces if pattern.match(context, context.node, axis_type): # let Python collect this list to reduce memory usage del patterns # Make sure the template starts with a clean slate current_variables = context.varBindings context.varBindings = self.globalVars try: template.instantiate(context, processor, params) finally: context.varBindings = current_variables return 1 else: # recovery_method in (Recovery.WARNING, Recovery.NONE) # Find all templates that match the context node
# Use the import index, priority and position from the last # possible matching pattern as starting points. highest_import, highest_priority, last_position = patterns[-1][0] matches = [] for sort_key, (pattern, axis_type, template) in patterns: import_index, priority, position = sort_key
if import_index < highest_import or \ (import_index == highest_import and priority < highest_priority): # Passed the point of possible conflicts, we can stop break
context.processorNss = template.namespaces if pattern.match(context, context.node, axis_type): matches.append((template, pattern))
if len(matches) > 1: # Report the template conflicts locations = [] for template, pattern in matches: locations.append((template.baseUri, template.lineNumber, template.columnNumber, repr(pattern)))
# Sort them based on position locations.sort()
locations = [ MessageSource.TEMPLATE_CONFLICT_LOCATION % location for location in locations ] exception = XsltException(Error.MULTIPLE_MATCH_TEMPLATES, context.node, '\n'.join(locations))
if 1: # recovery_method == Recovery.WARNING processor.warning(str(exception)) else: raise exception
if matches: template = matches[0][0] # let Python collect these list to reduce memory usage del patterns del matches
# Since the patterns were sorted to start with, use the first context.processorNss = template.namespaces template.instantiate(context, processor, params) return 1
# Nothing matched return 0
def __getstate__(self): self.root.sourceNodes = {} self._input_source = None return self.__dict__
#def PrintStylesheetTree(node, stream=None, indentLevel=0, showImportIndex=0, # lastUri=None): # """ # Function to print the nodes in the stylesheet tree, to aid in debugging. # """ # stream = stream or sys.stdout # if lastUri != node.baseUri: # stream.write(indentLevel * ' ') # stream.write('====%s====\n' % node.baseUri) # lastUri = node.baseUri # stream.write(indentLevel * ' ' + str(node)) # if showImportIndex: # stream.write(' [' + str(node.importIndex) + ']') # stream.write('\n') # stream.flush() # show_ii = isinstance(node, XsltElement) and \ # node.expandedName in [(XSL_NAMESPACE, 'stylesheet'), # (XSL_NAMESPACE, 'transform')] # for child in node.children: # PrintStylesheetTree(child, stream, indentLevel+1, show_ii, lastUri) # return
|