//
// StringPrototype.cs:
//
// Author:
// Cesar Lopez Nataren (cesar@ciencias.unam.mx)
//
// (C) 2003, Cesar Lopez Nataren
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using Microsoft.JScript.Vsa;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Collections;
namespace Microsoft.JScript {
public class StringPrototype : StringObject {
internal static StringPrototype Proto = new StringPrototype ();
/* Note that the implementation of this HTML tag stuff is pretty dumb.
* It does not do any escaping of HTML characters like '<', '>' or '"' which means that you can do
* HTML injection via for example "foo".anchor("NAME\" style=\"font-size: 500pt;") which will result
* in , however Mozilla, Internet Explorer and Opera all do
* the same thing and I could not find any standard against this behavior. */
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_anchor)]
public static string anchor (object thisObj, object anchorName)
{
return "" + Convert.ToString (thisObj) + "";
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_big)]
public static string big (object thisObj)
{
return "" + Convert.ToString (thisObj) + "";
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_blink)]
public static string blink (object thisObj)
{
return "";
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_bold)]
public static string bold (object thisObj)
{
return "" + Convert.ToString (thisObj) + "";
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_charAt)]
public static string charAt (object thisObj, double pos)
{
int _pos = Convert.ToInt32 (pos);
string string_obj = Convert.ToString (thisObj);
if (_pos < 0 || _pos >= string_obj.Length)
return "";
return string_obj.Substring (_pos, 1);
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_charCodeAt)]
public static object charCodeAt (object thisObj, double arg)
{
string string_obj = Convert.ToString (thisObj);
int pos = Convert.ToInt32 (arg);
if (pos < 0 || pos >= string_obj.Length)
return Double.NaN;
return string_obj [pos];
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject | JSFunctionAttributeEnum.HasVarArgs, JSBuiltin.String_concat)]
public static string concat (object thisObj, params object [] args)
{
string result = Convert.ToString (thisObj);
foreach (object arg in args)
result += Convert.ToString (arg);
return result;
}
public static StringConstructor constructor {
get { return StringConstructor.Ctr; }
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_fixed)]
public static string @fixed (object thisObj)
{
return "" + Convert.ToString (thisObj) + "";
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_fontcolor)]
public static string fontcolor (object thisObj, object colorName)
{
return "" + Convert.ToString (thisObj) + "";
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_fontsize)]
public static string fontsize (object thisObj, object fontsize)
{
return "" + Convert.ToString (thisObj) + "";
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_indexOf)]
public static int indexOf (object thisObj, object searchString, double position)
{
string string_obj = Convert.ToString (thisObj);
string search_obj = Convert.ToString (searchString);
int pos = (int) position;
if (pos < 0)
pos = 0;
else if (pos > string_obj.Length)
pos = string_obj.Length;
return string_obj.IndexOf (search_obj, pos);
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_italics)]
public static string italics (object thisObj)
{
return "" + Convert.ToString (thisObj) + "";
}
//
// Note: I think the signature Microsoft uses makes
// standards-compliant behavior impossible. If
// position is not supplied the standard says to
// default to the string's length. We can not do this
// with their signature, because position will
// automatically be forced to 0 in that case. Because
// of that we currently use 'object position' instead
// of their 'double position' in lastIndexOfGood
// (which we use from our own compiler) and a wrapper
// around that for when we run MS IL.
//
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject)]
public static int lastIndexOfGood (object thisObj, object searchString, object position)
{
string string_obj = Convert.ToString (thisObj);
string search_obj = Convert.ToString (searchString);
int pos;
if (position == null)
pos = string_obj.Length;
else {
pos = Convert.ToInt32 (position);
if (pos < 0)
pos = 0;
else if (pos > string_obj.Length)
pos = string_obj.Length;
}
int result = string_obj.LastIndexOf (search_obj, pos);
// string:LastIndexOf ignores matches at string start if pos is 0
if (result == -1 && pos == 0 && string_obj.StartsWith (search_obj))
return 0;
return result;
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_lastIndexOf)]
public static int lastIndexOf (object thisObj, object searchString, object position)
{
return lastIndexOfGood (thisObj, searchString, position);
}
public static int lastIndexOf (object thisObj, object searchString, double position)
{
return lastIndexOfGood (thisObj, searchString, position);
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_link)]
public static string link (object thisObj, object linkRef)
{
return "" + Convert.ToString (thisObj) + "";
}
[MonoTODO ("I18N Needs checking -- contact flgr@ccan.de for details")]
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_localeCompare)]
public static int localeCompare (object thisObj, object thatObj)
{
// TODO FIXME I18N: Verify that we do the right thing even in border cases.
string string_a = Convert.ToString (thisObj);
string string_b = Convert.ToString (thatObj);
int caseless_result = String.Compare (string_a, string_b, true);
/* I have no idea if this is enough to fix the behavior in all cases, but it at least makes
* localeCompare("abc", "ABC") work as in MS JS.NET -- this will likely be revised after
* more testing.
*
* Related to http://bugzilla.ximian.com/show_bug.cgi?id=70478?
*/
if (caseless_result == 0)
return -String.Compare (string_a, string_b, false);
else
return String.Compare (string_a, string_b, false);
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject | JSFunctionAttributeEnum.HasEngine, JSBuiltin.String_match)]
public static object match (object thisObj, VsaEngine engine, object regExp)
{
string string_obj = Convert.ToString (thisObj);
RegExpObject regex_obj = Convert.ToRegExp (regExp);
bool global = regex_obj.global;
if (!global)
return RegExpPrototype.exec (regex_obj, string_obj);
MatchCollection md = regex_obj.regex.Matches (string_obj);
uint n = (uint) md.Count;
Match lastMatch = md [(int) (n - 1)];
regex_obj.lastIndex = lastMatch.Index + 1;
RegExpConstructor.UpdateLastMatch (lastMatch, string_obj);
ArrayObject result = new ArrayObject ();
result.length = n;
for (uint i = 0; i < n; i++)
result.elems [i] = md [(int) i].Value;
return result;
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_replace)]
public static string replace (object thisObj, object regExp, object replacement)
{
string string_obj = Convert.ToString (thisObj);
if (!(regExp is RegExpObject)) {
string match_str = Convert.ToString (regExp);
string replace_str = Convert.ToString (replacement);
int match_pos = string_obj.IndexOf (match_str);
if (match_pos == -1)
return string_obj;
return String.Concat (string_obj.Substring (0, match_pos), replace_str,
string_obj.Substring (match_pos + match_str.Length));
}
RegExpObject regex_obj = (RegExpObject) regExp;
int count = regex_obj.global ? -1 : 1;
if (!(replacement is ScriptFunction))
return regex_obj.regex.Replace (string_obj, Convert.ToString (replacement), count);
ScriptFunction fun = (ScriptFunction) replacement;
MatchEvaluator wrap_fun = new MatchEvaluator (new ReplaceDelegate (fun, string_obj).Replace);
return regex_obj.regex.Replace (string_obj, wrap_fun, count);
}
private class ReplaceDelegate {
private ScriptFunction fun;
private string string_obj;
internal ReplaceDelegate (ScriptFunction fun, string string_obj)
{
this.fun = fun;
this.string_obj = string_obj;
}
internal string Replace (Match md)
{
ArrayList arg_list = new ArrayList ();
foreach (Group cap in md.Groups)
arg_list.Add (cap.Value);
arg_list.Add (md.Index);
arg_list.Add (string_obj);
return Convert.ToString (fun.Invoke (null, arg_list.ToArray ()));
}
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject | JSFunctionAttributeEnum.HasEngine, JSBuiltin.String_search)]
public static int search (object thisObj, VsaEngine engine, object regExp)
{
string string_obj = Convert.ToString (thisObj);
RegExpObject regex_obj = Convert.ToRegExp (regExp);
Match md = regex_obj.regex.Match (string_obj);
/* Note: Microsoft's implementation updates the lastIndex property of regex_obj here, but
* ECMA-262, 15.5.4.12, NOTE 1 explicitely says not to do so. We do the ECMA-262 behavior. */
if (md.Success)
return md.Index;
else
return -1;
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_slice)]
public static string slice (object thisObj, double start, object end)
{
string string_obj = Convert.ToString (thisObj);
int string_len = string_obj.Length;
int _start, _end;
if (start > string_len)
_start = string_len;
else {
_start = (int) start;
if (_start < 0)
_start += string_len;
}
if (end == null)
_end = string_len;
else {
_end = Convert.ToInt32 (end);
if (_end < 0)
_end += string_len;
else if (_end > string_len)
_end = string_len;
}
if (_end < _start)
_end = _start;
return string_obj.Substring (_start, _end - _start);
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_small)]
public static string small (object thisObj)
{
return "" + Convert.ToString (thisObj) + "";
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject | JSFunctionAttributeEnum.HasEngine, JSBuiltin.String_split)]
public static ArrayObject split (object thisObj, VsaEngine engine,
object separator, object limit)
{
string string_obj = Convert.ToString (thisObj);
int length = string_obj.Length;
int max_count = (limit != null) ? Convert.ToInt32 (limit) : -1;
ArrayObject result = new ArrayObject ();
if (separator == null) {
result.length = (uint) 1;
result.elems [(uint) 0] = string_obj;
return result;
}
int start_pos = 0;
int end_pos = -1;
int match_len = 0;
uint count = 0;
int sep_len = 0;
if (!(separator is RegExpObject)) {
string sep_str = Convert.ToString (separator);
sep_len = sep_str.Length;
if (string_obj.Length == 0) {
if (sep_len > 0) {
result.length = (uint) 1;
result.elems [(uint) 0] = string_obj;
}
return result;
}
while (end_pos != length && (max_count == -1 || count < max_count)) {
end_pos = (length != 0) ? string_obj.IndexOf (sep_str, start_pos) : length;
if (end_pos == -1)
end_pos = length;
else if (sep_len == 0)
end_pos++;
match_len = end_pos - start_pos;
result.elems [count] = string_obj.Substring (start_pos, match_len);
start_pos += match_len + sep_len;
count++;
}
result.length = count;
return result;
}
RegExpObject sep_re = (RegExpObject) separator;
MatchCollection md = sep_re.regex.Matches (string_obj);
uint n = (uint) md.Count;
Match match = null;
for (int i = 0; i < n; i++) {
if (max_count != -1 && count >= max_count)
break;
match = md [i];
sep_len = match.Length;
end_pos = match.Index;
match_len = end_pos - start_pos;
//
// The specification says that "ab".split(/a*/) is ["", "b"], but would
// that would also mean that "abcdef".split(/./).length would not be 6.
// I'm currently going with the Rhino behavior that seems to make more
// sense.
//
if (!(end_pos == 0 && sep_len == 0)) {
result.elems [count] = string_obj.Substring (start_pos, match_len);
count++;
}
bool first_cap = true;
foreach (Capture cap in match.Groups) {
if (max_count != -1 && count >= max_count)
break;
if (first_cap) {
first_cap = false;
continue;
}
result.elems [count] = cap.Value;
count++;
}
start_pos += match_len + sep_len;
}
if (n > 0 && (max_count == -1 || count < max_count)) {
sep_re.lastIndex = match.Index + match.Length;
RegExpConstructor.UpdateLastMatch (match, string_obj);
if (start_pos < length) {
result.elems [count] = string_obj.Substring (start_pos);
count++;
}
} else if (n == 0) {
result.elems [(uint) 0] = string_obj;
count++;
}
result.length = count;
return result;
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_strike)]
public static string strike (object thisObj)
{
return "" + Convert.ToString (thisObj) + "";
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_sub)]
public static string sub (object thisObj)
{
return "" + Convert.ToString (thisObj) + "";
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_substr)]
public static string substr (object thisObj, double start, object count)
{
string string_obj = Convert.ToString (thisObj);
int string_len = string_obj.Length;
int _start, _end;
if (start > string_len)
_start = string_len;
else {
_start = (int) start;
if (_start < 0)
_start += string_len;
}
if (count == null)
_end = string_len;
else {
int _count = Convert.ToInt32 (count);
_end = _start + _count;
if (_end < 0)
return "";
else if (_end > string_len)
_end = string_len;
}
return string_obj.Substring (_start, _end - _start);
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_substring)]
public static string substring (object thisObj, double start, object end)
{
string string_obj = Convert.ToString (thisObj);
int string_len = string_obj.Length;
int _start, _end;
if (start == Double.NaN || start < 0)
_start = 0;
else if (start > string_len)
_start = string_len;
else
_start = (int) start;
if (end == null)
_end = string_len;
else {
_end = Convert.ToInt32 (end);
if (_end == Double.NaN || _end < 0)
_end = 0;
else if (_end > string_len)
_end = string_len;
}
if (_end < _start) {
int temp = _start;
_start = _end;
_end = temp;
}
return string_obj.Substring (_start, _end - _start);
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_sup)]
public static string sup (object thisObj)
{
return "" + Convert.ToString (thisObj) + "";
}
/* TODO FIXME I18N: Somebody who is familar with locales should check if the definition of these really is
* this simple. */
[MonoTODO ("I18N Needs checking -- contact flgr@ccan.de for details")]
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_toLocaleLowerCase)]
public static string toLocaleLowerCase (object thisObj)
{
string string_obj = Convert.ToString (thisObj);
return string_obj.ToLower ();
}
[MonoTODO ("I18N Needs checking -- contact flgr@ccan.de for details")]
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_toLocaleUpperCase)]
public static string toLocaleUpperCase (object thisObj)
{
string string_obj = Convert.ToString (thisObj);
return string_obj.ToUpper ();
}
[MonoTODO ("I18N Needs checking -- contact flgr@ccan.de for details")]
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_toLowerCase)]
public static string toLowerCase (object thisObj)
{
string string_obj = Convert.ToString (thisObj);
return string_obj.ToLower (CultureInfo.InvariantCulture);
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_toString)]
public static string toString (object thisObj)
{
if (!Convert.IsString (thisObj))
throw new JScriptException (JSError.StringExpected);
else
return Convert.ToString (thisObj);
}
[MonoTODO ("I18N Needs checking -- contact flgr@ccan.de for details")]
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_toUpperCase)]
public static string toUpperCase (object thisObj)
{
string string_obj = Convert.ToString (thisObj);
return string_obj.ToUpper (CultureInfo.InvariantCulture);
}
[JSFunctionAttribute (JSFunctionAttributeEnum.HasThisObject, JSBuiltin.String_valueOf)]
public static object valueOf (object thisObj)
{
return toString (thisObj);
}
}
}