########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Xml/XPath/Util.py,v 1.23 2006/01/15 00:55:53 jkloth Exp $
"""
General utilities for XPath applications
Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""
import os
from xml.dom import Node
from Ft.Xml import EMPTY_NAMESPACE
from Ft.Xml.Domlette import GetAllNs
from Ft.Xml.Lib.XmlString import SplitQName
# NOTE: XPathParser and Context are added to this module by __init__.py
__all__ = [# XPath expression parser:
#'XPathParser', #is this necessary to expose?
# XPath expression processing:
'Compile', 'Evaluate', 'SimpleEvaluate',
# DOM preparation for XPath processing:
'NormalizeNode',
# misc XPath related utilities:
'ExpandQName',
]
def ExpandQName(qname, refNode=None, namespaces=None):
"""
Expand the given QName in the context of the given node,
or in the given namespace dictionary.
Returns a 2-tuple consisting of the namespace URI and local name.
"""
nss = {}
if refNode:
nss = GetAllNs(refNode)
elif namespaces:
nss = namespaces
(prefix, local) = SplitQName(qname)
#We're not to use the default namespace
if prefix:
try:
split_name = (nss[prefix], local)
except KeyError:
from Ft.Xml.XPath import RuntimeException
raise RuntimeException(RuntimeException.UNDEFINED_PREFIX,
prefix)
else:
split_name = (EMPTY_NAMESPACE, local)
return split_name
def NormalizeNode(node):
"""
NormalizeNode is used to prepare a DOM for XPath evaluation.
1. Convert CDATA Sections to Text Nodes.
2. Normalize all text nodes (adjacent nodes are merged into the first one).
"""
node = node.firstChild
while node:
if node.nodeType == Node.CDATA_SECTION_NODE:
# If followed by a text node, add this data to it
if node.nextSibling and node.nextSibling.nodeType == Node.TEXT_NODE:
node.nextSibling.insertData(0, node.data)
elif node.data:
# Replace this node with a new text node
text = node.ownerDocument.createTextNode(node.data)
node.parentNode.replaceChild(text, node)
node = text
else:
# It is empty, get rid of it
next = node.nextSibling
node.parentNode.removeChild(node)
node = next
# Just in case it is None
continue
elif node.nodeType == Node.TEXT_NODE:
next = node.nextSibling
while next and next.nodeType in [Node.TEXT_NODE,
Node.CDATA_SECTION_NODE]:
node.appendData(next.data)
node.parentNode.removeChild(next)
next = node.nextSibling
if not node.data:
# Remove any empty text nodes
next = node.nextSibling
node.parentNode.removeChild(node)
node = next
# Just in case it is None
continue
elif node.nodeType == Node.ELEMENT_NODE:
for attr in node.attributes.values():
if len(attr.childNodes) > 1:
NormalizeNode(attr)
NormalizeNode(node)
node = node.nextSibling
return
# -- Core XPath API ---------------------------------------------------------
def SimpleEvaluate(expr, node, explicitNss=None):
"""
Designed to be the most simple/brain-dead interface to using XPath
Usually invoked through Node objects using:
node.xpath(expr[, explicitNss])
expr - XPath expression in string or compiled form
node - the node to be used as core of the context for evaluating the XPath
explicitNss - (optional) any additional or overriding namespace mappings
in the form of a dictionary of prefix: namespace
the base namespace mappings are taken from in-scope
declarations on the given node. This explicit dictionary
is suprimposed on the base mappings
"""
if 'EXTMODULES' in os.environ:
ext_modules = os.environ["EXTMODULES"].split(':')
else:
ext_modules = []
explicitNss = explicitNss or {}
nss = GetAllNs(node)
nss.update(explicitNss)
context = Context.Context(node, 0, 0, processorNss=nss,
extModuleList=ext_modules)
if hasattr(expr, "evaluate"):
retval = expr.evaluate(context)
else:
retval = XPathParser.new().parse(expr).evaluate(context)
return retval
def Evaluate(expr, contextNode=None, context=None):
"""
Evaluates the given XPath expression.
Two arguments are required: the expression (as a string or compiled
expression object), and a context. The context can be given as a
Domlette node via the 'contextNode' named argument, or can be given as
an Ft.Xml.XPath.Context.Context object via the 'context' named
argument.
If namespace bindings or variable bindings are needed, use a
Context object. If extension functions are needed, either use a
Context object, or set the EXTMODULES environment variable to be a
':'-separated list of names of Python modules that implement
extension functions.
The return value will be one of the following:
node-set: list of Domlette node objects (xml.dom.Node based);
string: Unicode string type;
number: float type;
boolean: Ft.Lib.boolean C extension object;
or a non-XPath object (i.e. as returned by an extension function).
"""
if 'EXTMODULES' in os.environ:
ext_modules = os.environ["EXTMODULES"].split(':')
else:
ext_modules = []
if contextNode and context:
con = context.clone()
con.node = contextNode
elif context:
con = context
elif contextNode:
#contextNode should be a node, not a context obj,
#but this is a common error. Be forgiving?
if isinstance(contextNode, Context.Context):
con = contextNode
else:
con = Context.Context(contextNode, 0, 0, extModuleList=ext_modules)
else:
# import here to avoid circularity
from Ft.Xml.XPath import RuntimeException
raise RuntimeException(RuntimeException.NO_CONTEXT)
if hasattr(expr, "evaluate"):
retval = expr.evaluate(con)
else:
retval = XPathParser.new().parse(expr).evaluate(con)
return retval
def Compile(expr):
"""
Given an XPath expression as a string, returns an object that allows
an evaluation engine to operate on the expression efficiently.
This "compiled" expression object can be passed to the Evaluate
function instead of a string, in order to shorten the amount of time
needed to evaluate the expression.
"""
if not isinstance(expr, (str, unicode)):
raise TypeError("Expected string, found %s" % type(expr))
try:
return XPathParser.new().parse(expr)
except SyntaxError, error:
# import here to avoid circularity
from Ft.Xml.XPath import CompiletimeException
raise CompiletimeException(CompiletimeException.SYNTAX, 0, 0, str(error))
except:
import traceback, cStringIO
stream = cStringIO.StringIO()
traceback.print_exc(None, stream)
# import here to avoid circularity
from Ft.Xml.XPath import CompiletimeException
raise CompiletimeException(CompiletimeException.INTERNAL, stream.getvalue())
|