def SubstringBefore(context, outer, inner): """Function: <string> substring-before(<string>, <string>)""" outer = Conversions.StringValue(outer) inner = Conversions.StringValue(inner) index = string.find(outer, inner) if index == -1: return '' return outer[:index]
def SubstringAfter(context, outer, inner): """Function: <string> substring-after(<string>, <string>)""" outer = Conversions.StringValue(outer) inner = Conversions.StringValue(inner) index = string.find(outer, inner) if index == -1: return '' return outer[index + len(inner):]
def Replace(context, old, new, arg=None): """Do a global search and replace of the string contents""" if not arg: arg = context.node arg = Conversions.StringValue(arg) old = Conversions.StringValue(old) new = Conversions.StringValue(new) return string.replace(arg, old, new)
def Contains(context, outer, inner): """Function: <string> contains(<string>, <string>)""" outer = Conversions.StringValue(outer) inner = Conversions.StringValue(inner) if len(inner) == 1: return inner in outer and boolean.true or boolean.false else: return string.find(outer, inner) != -1 and boolean.true or boolean.false
def evaluate(self, context): if self._op == '=': true = boolean.true false = boolean.false else: true = boolean.false false = boolean.true lrt = self._left.evaluate(context) rrt = self._right.evaluate(context) lType = type(lrt) rType = type(rrt) if lType == types.ListType == rType: #Node set to node set for right_curr in rrt: right_curr = Conversions.StringValue(right_curr) for left_curr in lrt: if right_curr == Conversions.StringValue(left_curr): return true return false elif lType == types.ListType or rType == types.ListType: func = None if lType == types.ListType: set = lrt val = rrt else: set = rrt val = lrt if type(val) in NumberTypes: func = Conversions.NumberValue elif boolean.IsBooleanType(val): func = Conversions.BooleanValue elif type(val) == types.StringType: func = Conversions.StringValue else: #Deal with e.g. RTFs val = Conversions.StringValue(val) func = Conversions.StringValue for n in set: if func(n) == val: return true return false if boolean.IsBooleanType(lrt) or boolean.IsBooleanType(rrt): rt = Conversions.BooleanValue(lrt) == Conversions.BooleanValue(rrt) elif lType in NumberTypes or rType in NumberTypes: rt = Conversions.NumberValue(lrt) == Conversions.NumberValue(rrt) else: rt = Conversions.StringValue(lrt) == Conversions.StringValue(rrt) if rt: # Due to the swapping of true/false, true might evaluate to 0 # We cannot compact this to 'rt and true or false' return true return false
def Translate(context, source, fromChars, toChars): """Function: <string> translate(<string>, <string>, <string>)""" source = Conversions.StringValue(source) fromChars = Conversions.StringValue(fromChars) toChars = Conversions.StringValue(toChars) # 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
def String(context, object=None): """Function: <string> string(<object>?)""" if type(object) in g_stringTypes: return object if object is None: object = [context.node] return Conversions.StringValue(object)
def Map(context, funcname, *nodesets): """ Apply the function serially over the given node sets. In iteration i, the function is passed N parameters where N is the number of argument node sets. Each parameter is a node set of size 1, whose node is the ith node of the corresponding argument node set. The return value is a node set consisting of a series of result-tree nodes, each of which is a text node whose value is the string value of the result of the ith function invocation. Warning: this function uses the implied ordering of the node set Based on its implementation as a Python list. But in reality There is no reliable ordering of XPath node sets. In other words, this function is voodoo. """ (prefix, local) = ExpandQName(funcname, namespaces=context.processorNss) func = (g_extFunctions.get(expanded) or CoreFunctions.CoreFunctions.get(expanded, None)) if not func: raise Exception('Dynamically invoked function %s not found.' % funcname) flist = [f] * len(nodesets) lf = lambda x, f, *args: apply(f, args) retlist = apply(map, (lf, flist) + nodesets) proc = context.processor result_nodeset = [] for ret in retlist: proc.pushResult() proc.writers[-1].text(Conversions.StringValue(ret)) frag = proc.popResult() context.rtfs.append(frag) result_nodeset.append(frag.childNodes[0]) return result_nodeset
def SearchRePy20(context, pattern, arg=None): """Do a regular expression search against the argument (i.e. get all matches)""" if not arg: arg = context.node arg = Conversions.StringValue(arg) proc = context.processor matches_nodeset = [] _re = re.compile(pattern) _match = _re.search(arg) while _match: proc.pushResult() proc.writers[-1].startElement('Match', EMPTY_NAMESPACE) _groups = _match.groups() # .groups() return empty tuple when the pattern did not do grouping if not _groups: _groups = tuple(_match.group()) for group in _groups: proc.writers[-1].startElement('Group', EMPTY_NAMESPACE) # MatchObject groups return None if unmatched # unlike .findall() returning empty strings proc.writers[-1].text(group or '') proc.writers[-1].endElement('Group') proc.writers[-1].endElement('Match') frag = proc.popResult() context.rtfs.append(frag) matches_nodeset.append(frag.childNodes[0]) _match = _re.search(arg, _match.end()) return matches_nodeset
def Match(context, pattern, arg=None): """Do a regular expression match against the argument""" if not arg: arg = context.node arg = Conversions.StringValue(arg) bool = re.match(pattern, arg) and boolean.true or boolean.false return bool
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, c=context: a + Conversions.StringValue(b), args, '')
def distinct(context, nodeset): if type(nodeset) != type([]): raise Exception("'distinct' parameter must be of type node-set!") nodes = {} for node in nodeset: nodes[Conversions.StringValue(node)] = node return nodes.values()
def split(context, arg, delim=None): doc = context.node while doc.parentNode: doc = doc.parentNode nodeset = [] for token in string.split(Conversions.StringValue(arg), delim): nodeset.append(doc.createTextNode(token)) return nodeset
def Id(context, object): """Function: <node-set> id(<object>)""" id_list = [] if type(object) != type([]): st = Conversions.StringValue(object) id_list = string.split(st) else: for n in object: id_list.append(Conversions.StringValue(n)) rt = [] for id in id_list: doc = context.node.ownerDocument or context.node elements = Util.ElementsById(doc.documentElement, id) if len(elements) > 1: raise RuntimeException(RuntimeException.WRONG_ARGUMENTS, 'id', _("argument not unique")) elif elements: # Must be 1 rt.append(elements[0]) return rt
def Substring(context, st, start, end=None): """Function: <string> substring(<string>, <number>, <number>?)""" st = Conversions.StringValue(st) start = Conversions.NumberValue(start) if start is NaN: return '' start = int(round(start)) start = start > 1 and start - 1 or 0 if end is None: return st[start:] end = Conversions.NumberValue(end) if start is NaN: return st[start:] end = int(round(end)) return st[start:start + end]
def Lang(context, lang): """Function: <boolean> lang(<string>)""" lang = string.upper(Conversions.StringValue(lang)) node = context.node while node: lang_attr = filter(lambda x: x.name == 'xml:lang' and x.value, node.attributes.values()) value = lang_attr and lang_attr[0].nodeValue or None if value: # See if there is a suffix index = string.find(value, '-') if index != -1: value = value[:index] value = string.upper(value) return value == lang and boolean.true or boolean.false node = node.nodeType == Node.ATTRIBUTE_NODE and node.ownerElement or node.parentNode return boolean.false
def SearchRe(context, pattern, arg=None): """Do a regular expression search against the argument (i.e. get all matches)""" if not arg: arg = context.node arg = Conversions.StringValue(arg) matches = re.findall(pattern, arg) proc = context.processor matches_nodeset = [] for groups in matches: proc.pushResult() proc.writers[-1].startElement('Match', EMPTY_NAMESPACE) if type(groups) != type(()): groups = (groups, ) for group in groups: proc.writers[-1].startElement('Group', EMPTY_NAMESPACE) proc.writers[-1].text(group) proc.writers[-1].endElement('Group') proc.writers[-1].endElement('Match') frag = proc.popResult() context.rtfs.append(frag) matches_nodeset.append(frag.childNodes[0]) return matches_nodeset
def find(context, outer, inner): return string.find(Conversions.StringValue(outer), Conversions.StringValue(inner))
def EscapeUrl(context, url): "Escape illegal characters in a URL" return urllib.quote(Conversions.StringValue(url))
def Normalize(context, st=None): """Function: <string> normalize-space(<string>?)""" if st is None: st = context.node st = Conversions.StringValue(st) return string.join(string.split(st))
def StringLength(context, st=None): """Function: <number> string-length(<string>?)""" if st is None: st = context.node return len(Conversions.StringValue(st))
def Evaluate(context, expr): import pyxml.xpath return pyxml.xpath.Evaluate(Conversions.StringValue(st), context=context)
def join(context, nodeset, delim=None): comps = map(lambda x: Conversions.StringValue(x), nodeset) if delim: return string.joinfields(comps, delim) else: return string.joinfields(comps)
def StartsWith(context, outer, inner): """Function: <string> starts-with(<string>, <string>)""" outer = Conversions.StringValue(outer) inner = Conversions.StringValue(inner) return outer[:len(inner)] == inner and boolean.true or boolean.false