########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Xml/Xslt/StylesheetTree.py,v 1.23 2005/05/11 16:11:05 jkloth Exp $
"""
Node classes for the stylesheet tree
Copyright 2004 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""
from Ft.Xml import EMPTY_NAMESPACE
from Ft.Xml.Domlette import GetAllNs
from Ft.Xml.Xslt import XSL_NAMESPACE,XsltException,XsltRuntimeException,Error
from Ft.Xml.Xslt import AttributeValueTemplate
from Ft.Xml.Xslt import CategoryTypes
from Ft.Xml.Xslt import ContentInfo
from Ft.Xml.XPath import parser
_xpath_parser = parser
from Ft.Xml.Xslt import parser
_xpattern_parser = parser
del parser
class XsltNode:
# positional information (for debugger and errors)
# baseUri also used to set base URI of rtf nodes
baseUri = ''
lineNumber = '??'
columnNumber = '??'
importIndex = -1
root = None
parent = None
expandedName = (None, None)
nodeName = None
children = None
attributes = None
# hints for performance
doesSetup = False
doesPrime = False
doesIdle = False
def isLastChild(self):
siblings = self.parent.children
if siblings.index(self) == len(siblings) - 1:
return 1
else:
isLast = 1
for node in siblings[siblings.index(self)+1:]:
if not node.isPseudoNode():
isLast = 0
break
return isLast
def setup(self):
return
def prime(self, processor, context):
return
def idle(self, processor):
return
def instantiate(self, context, processor):
return
def isPseudoNode(self):
return False
def pprint(self, _indent=''):
print _indent + str(self)
if self.children:
_indent += ' '
for child in self.children:
child.pprint(_indent)
return
class XsltRoot(XsltNode):
content = ContentInfo.Alt(ContentInfo.QName(XSL_NAMESPACE, 'xsl:stylesheet'),
ContentInfo.QName(XSL_NAMESPACE, 'xsl:transform'),
ContentInfo.ResultElements)
validator = ContentInfo.Validator(content)
nodeName = u'#document'
def __init__(self, baseUri):
self.root = self
self.baseUri = baseUri
# cache of original stylesheet source docs, keyed by URI
self.sources = {} # XML strings
self.sourceNodes = {} # Domlette documents
# Regarding the cache:
#
# XSLT's document() function requires that the original stylesheet
# documents be accessible as separate source document trees, but the
# point of StylesheetReader is to avoid creating a Domlette document
# for each stylesheet while building a single, compact stylesheet tree
# (using the classes in this module) via the fastest parsing method
# available. In order to support document(), we have to make it
# possible to create, as needed, a Domlette document for each original
# stylesheet, just in case somewhere in the stylesheet tree a
# document() call or extension function makes reference to one of
# those original documents. document('') is the most common case, but
# not the only one.
#
# For now, we cache the original bytes of the document entity in the
# stylesheet tree's XsltRoot object, and implement document() such
# that it looks for documents in the Domlette cache in the XsltContext
# first, then tries the cache we set up here, before falling back on
# the normal resolver to fetch fresh content. If the doc is found in
# our cache, then it is parsed and the Domlette is stored in the
# XsltContext for future access. See XsltFunctions.py.
#
# Future optimization ideas:
# - After the stylesheet tree is built, look for document() calls
# in the XPath expressions, and if they all use single arguments
# that are hard-coded URI refs, resolve the refs to absolute form,
# and discard any cached docs that aren't in the resulting list.
# - Compress the cached strings, and decompress on the fly.
#
# FIXME: The cache does not include external entities or XIncludes;
# they are refetched each time the cached document entity is read.
# It is rare for a stylesheet to use ext. entities/XIncludes, though.
self.primeInstructions = []
self.idleInstructions = []
self.stylesheet = None
self.children = []
return
def appendChild(self, child):
# The validator ensures that only one child will be added
child.parent = self
self.stylesheet = child
self.children = [child]
return
def __str__(self):
return "<XsltRoot at 0x%x>" % id(self)
# implements default behaviour for extension elements
class XsltElement(XsltNode):
category = CategoryTypes.RESULT_ELEMENT
content = ContentInfo.Template
validator = ContentInfo.Validator(content)
legalAttrs = None # this means no error checking or defaulting
def __init__(self, root, namespaceUri, localName, baseUri):
self.root = root
self.baseUri = baseUri
self.expandedName = (namespaceUri, localName)
self.children = []
self.attributes = {}
self.namespaces = {}
return
def insertChild(self, index, child):
"""INTERNAL USE ONLY"""
self.children.insert(index, child)
child.parent = self
if child.doesSetup:
child.setup()
return
def appendChild(self, child):
"""INTERNAL USE ONLY"""
self.children.append(child)
child.parent = self
if child.doesSetup:
child.setup()
return
def parseAVT(self, avt):
"""DEPRECATED: specify an attribute in 'legalAttrs' instead."""
if avt is None: return None
try:
return AttributeValueTemplate.AttributeValueTemplate(avt)
except SyntaxError, error:
raise XsltException(Error.INVALID_AVT, avt, self.baseUri,
self.lineNumber, self.columnNumber,
str(error))
except XsltException, error:
raise XsltException(Error.INVALID_AVT, avt, self.baseUri,
self.lineNumber, self.columnNumber,
error.args[0])
def parseExpression(self, expression):
"""DEPRECATED: specify an attribute in 'legalAttrs' instead."""
if expression is None: return None
p = _xpath_parser.new()
try:
return p.parse(expression)
except SyntaxError, error:
raise XsltException(Error.INVALID_EXPRESSION, expression,
self.baseUri, self.lineNumber,
self.columnNumber, str(error))
def parsePattern(self, pattern):
"""DEPRECATED: specify an attribute in 'legalAttrs' instead."""
if pattern is None: return None
p = _xpattern_parser.new()
try:
return p.parse(pattern)
except SyntaxError, error:
raise XsltException(Error.INVALID_PATTERN, pattern,
self.baseUri, self.lineNumber,
self.columnNumber, str(error))
def splitQName(self, qname):
"""DEPRECATED: specify an attribute in 'legalAttrs' instead."""
if not qname: return None
index = qname.find(':')
if index != -1:
split = (qname[:index], qname[index+1:])
else:
split = (None, qname)
return split
def expandQName(self, qname, refNode=None):
"""DEPRECATED: specify an attribute in 'legalAttrs' instead."""
if not qname: return None
if refNode:
namespaces = GetAllNs(refNode)
else:
namespaces = self.namespaces
prefix, local = self.splitQName(qname)
if prefix:
try:
expanded = (namespaces[prefix], local)
except KeyError:
raise XsltRuntimeException(Error.UNDEFINED_PREFIX,
self, prefix)
else:
expanded = (EMPTY_NAMESPACE, local)
return expanded
def instantiate(self, context, processor):
"""
Implements default behavior of instantiating each child in the order
that they appear in the stylesheet.
"""
context.processorNss = self.namespaces
context.currentInstruction = self
for child in self.children:
child.instantiate(context, processor)
return
def processChildren(self, context, processor):
"""
Iterates over the children, instantiating them in the order that they
appear in the stylesheet.
"""
context.processorNss = self.namespaces
context.currentInstruction = self
for child in self.children:
child.instantiate(context, processor)
return
def __str__(self):
#FIXME: Should this use self.__class__ or sth rather than hardcoding "XsltElement"?
return ("<XsltElement at 0x%x:"
" name %r, %d attributes, %d children, precedence %d>") % (
id(self), self.nodeName, len(self.attributes), len(self.children),
self.importIndex)
class XsltText(XsltNode):
nodeName = u'#text'
def __init__(self, root, baseUri, data):
self.root = root
self.baseUri = baseUri
self.data = data
return
def instantiate(self, context, processor):
processor.writers[-1].text(self.data)
return
def __str__(self):
if len(self.data) > 20:
data = self.data[:20] + '...'
else:
data = self.data
return "<XsltText at 0x%x: %s>" % (id(self), repr(data))
|