# AVSLib :: 'string' package :: 'core' module
# Copyright (c) 2005 George Zarkadas (gzarkadas@users.sourceforge.net)

# This program is free software; you can redistribute it and/or modify it under the terms of 
# the GNU General Public License as published by the Free Software Foundation; either 
# version 2 of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
# PURPOSE.  See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with this program; 
# if not, write to the "Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
# MA  02111-1307  USA"

DefineModule("avslib", "string", "search")

global __load__ = Eval("""
	LoadModule("avslib", "string", "core")
	LoadModule("avslib", "array", "core")
	""")

# StrFind
# Returns an array with all occurences of 'sought' inside 'base' 
# or the (zero-based) index of nth occurrence of 'sought' inside 'base'.
# Indexing is 0-based to match array indexing (which is 0-based).
# If 'index' > last occurence, the index of the last occurence is returned.

	# If sought == "" all 'index' values will return 1.
	# This is the desired behavior because according to the semantics of FindStr
	# the empty string matches (only) at the begining of the string.
	
	Function __str_find_one(string base, string sought, int index, int "__ofs")
	{
		__ofs = Default(__ofs, 0)
		slen  = StrLen(sought)
		match = FindStr(base, sought)
		base  = match > 0 ? StrMid(base, match + slen) : base
		index = index - 1
		__ofs = match > 0 ? __ofs + match + slen - 1 : __ofs
		# On return subtract the last slen - 1 (unless no match is found 
		# at the very 1st invocation), because it is added to advance counter
		# assuming that the next invocation will succeed.
		# FindStr returns 1 if sought == ""; test to avoid infinite recursion
		return sought == "" ? \
			1 : ( \
		(index < 0) || (match == 0) ? \
			(__ofs != 0 ? __ofs - slen + 1 : 0) \
		: \
			__str_find_one(base, sought, index, __ofs) )
	}
	
	# If sought == "" a one-element array with value 1 ("1") will be returned

	Function __str_find_all(string base, string sought, string "__ret", int "__ofs")
	{
		__ret = Default(__ret, "")
		__ofs = Default(__ofs, 0)
		slen  = StrLen(sought)
		match = FindStr(base, sought)
		__ret = match > 0 ? __ret.ArrayInsert(__ofs + match) : __ret
		__ofs = match > 0 ? __ofs + match + slen - 1 : __ofs
		base  = match > 0 ? StrMid(base, match + slen) : base
		# FindStr returns 1 if sought == ""; test to avoid infinite recursion
		return (match > 0) && (sought != "") \
			? __str_find_all(base, sought, __ret, __ofs) \
			: __ret
	}

Function StrFind(string base, string sought, int "index", bool "ignorecase")
{
	b_one = Defined(index)
	index = Default(index, 0)
	Assert(index >= 0, "StrFind: 'index' must be >= 0")
	# ignorecase defaults to false because this is FindStr's default
	ignorecase = Default(ignorecase, false)
	base = ignorecase ? UCase(base) : base
	sought = ignorecase ? UCase(sought) : sought
	# no need to check for sought == ""; subfunctions do it
	return b_one \
		? __str_find_one(base, sought, index) \
		: __str_find_all(base, sought)
}

# StrReplace
# Replaces all occurences of sought with rep, recursively or not; 
# If 'recurse' == true, after replacing 'sought' with 'rep' a new scan for
# 'sought' will be made from the start of the string.
# If 'index' is defined, only the (zero-based) index'th occurence will be
# replaced ('recurse' is then not effective).
# If 'sought' == "" base is returned unchanged.

	# use ignore case only while searching; on replace use 'rep' unmodified
	# FindStr|StrFind return 1 if sought==""; test to avoid infinite recursion

	Function __str_rep_one(string base, string sought, string rep, \
		int index, bool ignorecase)
	{
		pos = StrFind(base, sought, index, ignorecase)
		return (sought == "") || (pos == 0) \
			? base \
			: StrLeft(base, pos - 1) + rep + \
				StrMid(base, pos + StrLen(sought))
	}
	
	# Recursive mode: search again from the begining of the replaced string

	Function __str_rep_all_rec(string base, string sought, string rep, \
		bool ignorecase)
	{
		pos = StrFind(base, sought, 0, ignorecase)
		return (sought == "") || (pos == 0) \
			? base \
			: __str_rep_all_rec( \
				StrLeft(base, pos - 1) + rep + \
				StrMid(base, pos + StrLen(sought)), \
				sought, rep, ignorecase)
	}
	
	# Non-recursive mode: search again just *after* the replaced substring

	Function __str_rep_all_nonrec(string base, string sought, string rep, \
		bool ignorecase)
	{
		pos = StrFind(base, sought, 0, ignorecase)
		return (sought == "") || (pos == 0) \
			? base \
			: StrLeft(base, pos - 1) + rep + __str_rep_all_nonrec( \
				StrMid(base, pos + StrLen(sought)), \
				sought, rep, ignorecase)
	}

Function StrReplace(string base, string sought, string rep, int "index", \
	bool "ignorecase", bool "recurse")
{
	b_one = Defined(index)
	index = Default(index, 0)
	Assert(index >= 0, "StrReplace: 'index' must be >= 0")
	# ignorecase defaults to false by analogy to the FindStr|StrFind default
	ignorecase = Default(ignorecase, false)
	# recurse defaults to false for simplicity
	recurse = Default(recurse, false)
	# guard against infinite loop (recurse && sought is a subset of rep)
	no_inf_loop = (recurse == false) \
		|| (sought == "") \
		|| (StrFind(rep, sought, 0, ignorecase) == 0)
	Assert(no_inf_loop, "StrReplace: infinite loop detected; " + \
		"'sought' is a subset of 'rep' and 'recurse' == true.")
	return b_one ? \
		__str_rep_one(base, sought, rep, index, ignorecase) : ( \
	recurse ? \
		__str_rep_all_rec(base, sought, rep, ignorecase) \
	: \
		__str_rep_all_nonrec(base, sought, rep, ignorecase) )
}	
