# -*- coding: iso-8859-2 -*-
# $Date: 2008-06-08 23:00:14 $, $Revision: 1.7 $
#
# String replace with callbacks. Inspirated with 
# JavaScript String method.
#
# Function s_replace replaces substring, re_replace
# replaces regular expression;  function replace
# just combine these two.
#
#
# Author: Wojciech Muła
# e-mail: wojciech_mula@poczta.onet.pl
# www:    http://0x80.pl
#
# License: public domain

__all__ = ["replace"]

import re
RegexpType = type(re.compile(" "))

def replace(string, repl, callback, count=-1):
	if not callable(callback):
		raise ValueError("callback must be a callable object")

	if type(repl) in [str, unicode]:
		return s_replace(string, repl, callback, count)
	elif type(repl) is RegexpType:
		return re_replace(string, repl, callback, count)
	else:
		raise TypeError("second argument have to be string or regular expression")
	


def s_replace(string, substr, callback, count=-1):
	"""
	Return a copy of 'string' with all occurrences of 'substr'
	are replaced by result of 'callback'.

	Callback have to return strings; it gets three arguments
	1. 'string'
	2. 'substr'
	3. current position of 'substr'
	
	If the optional argument count is given, only the first count
	occurrences are replaced.
	"""
		
	if not substr:
		raise ValueError("empty separator")
	
	n = len(substr)
	
	def partition(string):
		p = string.find(substr)
		if p == -1:
			return (string, "", "")
		else:
			return (string[:p], substr, string[p+n:])
	
	result = []

	whole_string = string
	
	p = 0
	while True:
		s1, s2, s3 = partition(string)
		
		if not s2:
			result.append(s1)
			break
		if count == 0:
			result.append(string)
			break

		result.append(s1)
		p      = p + len(s1)
		count  = count - 1
		string = s3

		result.append(callback(whole_string, s2, p))
	
	return ''.join(result)


def re_replace(string, regexp, callback, count=-1):
	"""
	Return a copy of 'string' with all matches of 'regexp'
	are replaced by result of 'callback'.

	Callback have to return strings; it gets three arguments
	1. 'string'
	2. match object
	3. **relative** position of matched substring

	To extrace matched substring match.start() and match.end()
	have to be shifted by relative position.
	
	If the optional argument count is given, only the first count
	occurrences are replaced.
	"""
	
	def partition(string):
		m = regexp.search(string)
		if not m:
			return (string, "", "")
		else:
			return (string[:m.start()], m, string[m.end():])
	
	result = []
	whole_string = string
	
	p = 0
	while True:
		s1, s2, s3 = partition(string)
		
		if not s2:
			result.append(s1)
			break
		if count == 0:
			result.append(string)
			break

		result.append(s1)
		count  = count - 1
		string = s3

		result.append(callback(whole_string, s2, p))
		p = p + s2.end()
	
	return ''.join(result)
	

if __name__ == '__main__':
	
	n = 0
	def cb(s, s2, p):
		global n
		n += 1
		return "(%d)%s(%d)" % (n, s2, n)

	print replace("To jest kot, kot ktory ma kotke tu i kotke tam.", "kot", cb, 1); n=0
	print replace("To jest kot, kot ktory ma kotke tu i kotke tam.", "kot", cb, 2); n=0
	print replace("To jest kot, kot ktory ma kotke tu i kotke tam.", "kot", cb, 3); n=0
	print replace("To jest kot, kot ktory ma kotke tu i kotke tam.", "kot", cb, 4); n=0
	print replace("To jest kot, kot ktory ma kotke tu i kotke tam.", "kot", cb); n=0

	print
	print "="*77
	print 

	n = 0
	def cb(s, m, p):
		global n
		n += 1
		return "(%d)%s(%d)" % (n, s[p+m.start():p+m.end()], n)

	regexp = re.compile("(aaa|bbb|ccc|ddd)")
	print replace("To jest aaa, bbb ktory ma cccke tu i dddke tam.", regexp, cb, 1); n=0
	print replace("To jest aaa, bbb ktory ma cccke tu i dddke tam.", regexp, cb, 2); n=0
	print replace("To jest aaa, bbb ktory ma cccke tu i dddke tam.", regexp, cb, 3); n=0
	print replace("To jest aaa, bbb ktory ma cccke tu i dddke tam.", regexp, cb, 4); n=0
	print replace("To jest aaa, bbb ktory ma cccke tu i dddke tam.", regexp, cb); n=0

# vim: ts=4 sw=4 nowrap noexpandtab

