Viewing file: CoreFunctions.py (13.39 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
######################################################################## # $Header: /var/local/cvsroot/4Suite/Ft/Xml/XPath/CoreFunctions.py,v 1.29 2005/02/27 10:02:09 mbrown Exp $ """ The implementation of the core functions from XPath 1.0.
Copyright 2005 Fourthought, Inc. (USA). Detailed license and copyright information: http://4suite.org/COPYRIGHT Project home, documentation, distributions: http://4suite.org/ """
import warnings
from xml.dom import Node
from Ft import TranslateMessage as _ from Ft.Lib import number, boolean from Ft.Xml import EMPTY_NAMESPACE, XML_NAMESPACE from Ft.Xml.XPath import NAMESPACE_NODE from Ft.Xml.XPath import Conversions, RuntimeException from Ft.Xml.XPath.Types import NodesetType, NumberType from Ft.Xml.XPath.Types import StringType as XPathStringType
### Node Set Functions ###
def Last(context): """Function: <number> last()""" return float(context.size)
def Position(context): """Function: <number> position()""" return float(context.position)
def Count(context, nodeSet): """Function: <number> count(<node-set>)""" if not isinstance(nodeSet, NodesetType): raise RuntimeException(RuntimeException.WRONG_ARGUMENTS, 'count', _("expected node-set argument")) return float(len(nodeSet))
def Id(context, object_): """Function: <node-set> id(<object>)""" id_list = [] if not isinstance(object, NodesetType): st = Conversions.StringValue(object_) id_list = st.split() else: for n in object_: id_list.append(Conversions.StringValue(n))
doc = context.node.rootNode getElementById = getattr(doc, 'getElementById', None) if not getElementById: # We do not (cannot, really) support the id() function warnings.warn("id() function not supported") return []
nodeset = [] for id in id_list: element = getElementById(id) if element: nodeset.append(element) return nodeset
def LocalName(context, nodeSet=None): """Function: <string> local-name(<node-set>?)""" if nodeSet is None: node = context.node elif not isinstance(nodeSet, NodesetType): raise RuntimeException(RuntimeException.WRONG_ARGUMENTS, 'local-name', _("expected node-set")) elif not nodeSet: return u'' else: nodeSet.sort() node = nodeSet[0]
node_type = getattr(node, 'nodeType', None) if node_type in (Node.ELEMENT_NODE, Node.ATTRIBUTE_NODE): # localName could be null return node.localName or u'' elif node_type == NAMESPACE_NODE: # localName could be null return node.localName or u'' elif node_type == Node.PROCESSING_INSTRUCTION_NODE: # target cannot be null return node.target return u''
def NamespaceUri(context, nodeSet=None): """Function: <string> namespace-uri(<node-set>?)""" if nodeSet is None: node = context.node elif not isinstance(nodeSet, NodesetType): raise RuntimeException(RuntimeException.WRONG_ARGUMENTS, 'namespace-uri', _("expected node-set")) elif not nodeSet: return u'' else: nodeSet.sort() node = nodeSet[0]
# only elements and attributes have a namespace-uri node_type = getattr(node, 'nodeType', None) if node_type in (Node.ELEMENT_NODE, Node.ATTRIBUTE_NODE): return node.namespaceURI or u'' return u''
def Name(context, nodeSet=None): """Function: <string> name(<node-set>?)""" if nodeSet is None: node = context.node elif not isinstance(nodeSet, NodesetType): raise RuntimeException(RuntimeException.WRONG_ARGUMENTS, 'name', _("expected node-set")) elif not nodeSet: return u'' else: nodeSet.sort() node = nodeSet[0]
node_type = getattr(node, 'nodeType', None) if node_type in (Node.ELEMENT_NODE, Node.ATTRIBUTE_NODE): return node.nodeName elif node_type == NAMESPACE_NODE: # localName could be null return node.localName or u'' elif node_type == Node.PROCESSING_INSTRUCTION_NODE: # target cannot be null return node.target return u''
### String Functions ###
def String(context, object_=None): """Function: <string> string(<object>?)""" if isinstance(object_, XPathStringType): return object_ if object_ is None: object_ = context.node return Conversions.StringValue(object_)
def Concat(context, *args): """Function: <string> concat(<string>, <string>, ...)""" if len(args) < 1: raise RuntimeException(RuntimeException.WRONG_ARGUMENTS, 'concat', _("at least 2 arguments expected")) return reduce(lambda a, b: a + Conversions.StringValue(b), args, u'')
def StartsWith(context, outer, inner): """Function: <string> starts-with(<string>, <string>)""" if not isinstance(outer, XPathStringType): outer = Conversions.StringValue(outer) if not isinstance(inner, XPathStringType): inner = Conversions.StringValue(inner) if not inner: return boolean.true return outer[:len(inner)] == inner and boolean.true or boolean.false
def Contains(context, outer, inner): """Function: <string> contains(<string>, <string>)""" if not isinstance(outer, XPathStringType): outer = Conversions.StringValue(outer) if not isinstance(inner, XPathStringType): inner = Conversions.StringValue(inner) if not inner: return boolean.true return outer.find(inner) >= 0 and boolean.true or boolean.false
def SubstringBefore(context, outer, inner): """Function: <string> substring-before(<string>, <string>)""" if not isinstance(outer, XPathStringType): outer = Conversions.StringValue(outer) if not isinstance(inner, XPathStringType): inner = Conversions.StringValue(inner) if not inner: return u'' index = outer.find(inner) if index == -1: return u'' return outer[:index]
def SubstringAfter(context, outer, inner): """Function: <string> substring-after(<string>, <string>)""" if not isinstance(outer, XPathStringType): outer = Conversions.StringValue(outer) if not isinstance(inner, XPathStringType): inner = Conversions.StringValue(inner) if not inner: return u'' index = outer.find(inner) if index == -1: return u'' return outer[index+len(inner):]
def Substring(context, st, start, length=None): """Function: <string> substring(<string>, <number>, <number>?)""" if not isinstance(st, XPathStringType): st = Conversions.StringValue(st) if not isinstance(start, NumberType): start = Conversions.NumberValue(start)
# start == NaN: spec doesn't say; assume no substring to return # start == +Inf or -Inf: no substring to return if number.isnan(start) or number.isinf(start): return u''
# start is finite, safe for int() and round(). start = int(round(start)) # convert to 0-based index for python string slice if start < 1: startidx = 0 else: startidx = start - 1
# length undefined: return chars startidx to end if length is None: return st[startidx:] elif not isinstance(length, NumberType): length = Conversions.NumberValue(length)
# length == NaN: spec doesn't say; assume no substring to return if number.isnan(length): return u'' # length == +Inf: return chars startidx to end # length == -Inf: no substring to return elif number.isinf(length): if length > 0: return st[startidx:] else: return u''
# length is finite, safe for int() and round(). length = int(round(length))
# return value must end before position (start+length) # which is (start+length-1) in 0-based index endidx = start + length - 1 if endidx > startidx: return st[startidx:endidx] else: return u''
def StringLength(context, st=None): """Function: <number> string-length(<string>?)""" if st is None: st = context.node if not isinstance(st, XPathStringType): st = Conversions.StringValue(st) return float(len(st))
def Normalize(context, st=None): """Function: <string> normalize-space(<string>?)""" if st is None: st = context.node if not isinstance(st, XPathStringType): st = Conversions.StringValue(st) return u' '.join(st.split())
def Translate(context, source, fromChars, toChars): """Function: <string> translate(<string>, <string>, <string>)""" if not isinstance(source, XPathStringType): source = Conversions.StringValue(source) if not isinstance(fromChars, XPathStringType): fromChars = Conversions.StringValue(fromChars) if not isinstance(toChars, XPathStringType): toChars = Conversions.StringValue(toChars)
# remove duplicate chars from From string fromChars = reduce(lambda st, c: st + c * (st.find(c) == -1), fromChars, '') toChars = toChars[:len(fromChars)]
# string.maketrans/translate do not handle unicode translate = {} for from_char, to_char in map(None, fromChars, toChars): translate[ord(from_char)] = to_char
result = reduce(lambda a, b, t=translate: a + (t.get(ord(b), b) or ''), source, '') return result
### Boolean Functions ###
def Boolean(context, object_): """Function: <boolean> boolean(<object>)""" return Conversions.BooleanValue(object_)
def Not(context, object_): """Function: <boolean> not(<boolean>)""" return ((not Conversions.BooleanValue(object_) and boolean.true) or boolean.false)
def True(context): """Function: <boolean> true()""" return boolean.true
def False(context): """Function: <boolean> false()""" return boolean.false
def Lang(context, lang): """Function: <boolean> lang(<string>)""" lang = Conversions.StringValue(lang).lower() node = context.node while node.parentNode: for attr in node.attributes.values(): # Search for xml:lang attribute if (attr.localName == 'lang' and attr.namespaceURI == XML_NAMESPACE): value = attr.nodeValue.lower() # Exact match (PrimaryPart and possible SubPart) if value == lang: return boolean.true
# Just PrimaryPart (ignore '-' SubPart) index = value.find('-') if index != -1 and value[:index] == lang: return boolean.true
# Language doesn't match return boolean.false
# Continue to next ancestor node = node.parentNode
# No xml:lang declarations found return boolean.false
### Number Functions ###
def Number(context, object_=None): """Function: <number> number(<object>?)""" if object_ is None: object_ = [context.node] return Conversions.NumberValue(object_)
def Sum(context, nodeSet): """Function: <number> sum(<node-set>)""" if not isinstance(nodeSet, NodesetType): raise RuntimeException(RuntimeException.WRONG_ARGUMENTS, 'sum', _("expected node-set argument")) nns = map(Conversions.NumberValue, nodeSet) return reduce(lambda x, y: x + y, nns, 0)
def Floor(context, object_): """Function: <number> floor(<number>)""" num = Conversions.NumberValue(object_) if number.isnan(num) or number.isinf(num): return num elif int(num) == num: return num elif num < 0: return float(int(num) - 1) else: return float(int(num))
def Ceiling(context, object_): """Function: <number> ceiling(<number>)""" num = Conversions.NumberValue(object_) if number.isnan(num) or number.isinf(num): return num elif int(num) == num: return num elif num > 0: return float(int(num) + 1) else: return float(int(num))
def Round(context, object_): """Function: <number> round(<number>)""" num = Conversions.NumberValue(object_) if number.isnan(num) or number.isinf(num): return num elif num < 0 and num % 1.0 == 0.5: return round(num, 0) + 1 else: return round(num, 0)
### Function Mappings ###
CoreFunctions = { (EMPTY_NAMESPACE, 'last'): Last, (EMPTY_NAMESPACE, 'position'): Position, (EMPTY_NAMESPACE, 'count'): Count, (EMPTY_NAMESPACE, 'id'): Id, (EMPTY_NAMESPACE, 'local-name'): LocalName, (EMPTY_NAMESPACE, 'namespace-uri'): NamespaceUri, (EMPTY_NAMESPACE, 'name'): Name, (EMPTY_NAMESPACE, 'string'): String, (EMPTY_NAMESPACE, 'concat'): Concat, (EMPTY_NAMESPACE, 'starts-with'): StartsWith, (EMPTY_NAMESPACE, 'contains'): Contains, (EMPTY_NAMESPACE, 'substring-before'): SubstringBefore, (EMPTY_NAMESPACE, 'substring-after'): SubstringAfter, (EMPTY_NAMESPACE, 'substring'): Substring, (EMPTY_NAMESPACE, 'string-length'): StringLength, (EMPTY_NAMESPACE, 'normalize-space'): Normalize, (EMPTY_NAMESPACE, 'translate'): Translate, (EMPTY_NAMESPACE, 'boolean'): Boolean, (EMPTY_NAMESPACE, 'not'): Not, (EMPTY_NAMESPACE, 'true'): True, (EMPTY_NAMESPACE, 'false'): False, (EMPTY_NAMESPACE, 'lang'): Lang, (EMPTY_NAMESPACE, 'number'): Number, (EMPTY_NAMESPACE, 'sum'): Sum, (EMPTY_NAMESPACE, 'floor'): Floor, (EMPTY_NAMESPACE, 'ceiling'): Ceiling, (EMPTY_NAMESPACE, 'round'): Round, }
|