KJS

string_object.cpp
1 /*
2  * This file is part of the KDE libraries
3  * Copyright (C) 1999-2001 Harri Porten ([email protected])
4  * Copyright (C) 2004 Apple Computer, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  *
20  */
21 
22 #include "string_object.h"
23 #include "string_object.lut.h"
24 
25 #include "error_object.h"
26 #include "operations.h"
27 #include "PropertyNameArray.h"
28 #include "regexp_object.h"
29 #include "commonunicode.h"
30 #include <wtf/unicode/libc/UnicodeLibC.h>
31 
32 #if PLATFORM(WIN_OS)
33 #include <windows.h>
34 #endif
35 
36 using namespace WTF;
37 
38 namespace KJS
39 {
40 
41 // ------------------------------ StringInstance ----------------------------
42 
43 const ClassInfo StringInstance::info = {"String", nullptr, nullptr, nullptr};
44 
45 StringInstance::StringInstance(JSObject *proto)
46  : JSWrapperObject(proto), m_conversionsCustomized(false)
47 {
48  setInternalValue(jsString(""));
49 }
50 
51 StringInstance::StringInstance(JSObject *proto, StringImp *string)
52  : JSWrapperObject(proto), m_conversionsCustomized(false)
53 {
54  setInternalValue(string);
55 }
56 
57 StringInstance::StringInstance(JSObject *proto, const UString &string)
58  : JSWrapperObject(proto), m_conversionsCustomized(false)
59 {
60  setInternalValue(jsString(string));
61 }
62 
63 JSValue *StringInstance::lengthGetter(ExecState *, JSObject *, const Identifier &, const PropertySlot &slot)
64 {
65  return jsNumber(static_cast<StringInstance *>(slot.slotBase())->internalValue()->value().size());
66 }
67 
68 JSValue *StringInstance::indexGetter(ExecState *, JSObject *, unsigned, const PropertySlot &slot)
69 {
70  const UChar c = static_cast<StringInstance *>(slot.slotBase())->internalValue()->value()[slot.index()];
71  return jsString(UString(&c, 1));
72 }
73 
74 bool StringInstance::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
75 {
76  if (propertyName == exec->propertyNames().length) {
77  slot.setCustom(this, lengthGetter);
78  return true;
79  }
80 
81  bool isStrictUInt32;
82  unsigned i = propertyName.toStrictUInt32(&isStrictUInt32);
83  unsigned length = internalValue()->value().size();
84  if (isStrictUInt32 && i < length) {
85  slot.setCustomIndex(this, i, indexGetter);
86  return true;
87  }
88 
89  return JSObject::getOwnPropertySlot(exec, propertyName, slot);
90 }
91 
92 bool StringInstance::getOwnPropertySlot(ExecState *exec, unsigned propertyName, PropertySlot &slot)
93 {
94  unsigned length = internalValue()->value().size();
95  if (propertyName < length) {
96  slot.setCustomIndex(this, propertyName, indexGetter);
97  return true;
98  }
99 
100  return JSObject::getOwnPropertySlot(exec, Identifier::from(propertyName), slot);
101 }
102 
103 bool StringInstance::getOwnPropertyDescriptor(ExecState *exec, const Identifier &propertyName, PropertyDescriptor &desc)
104 {
105  if (propertyName == exec->propertyNames().length) {
106  desc.setPropertyDescriptorValues(exec, jsNumber(internalValue()->value().size()), ReadOnly | DontDelete | DontEnum);
107  return true;
108  }
109 
110  return KJS::JSObject::getOwnPropertyDescriptor(exec, propertyName, desc);
111 }
112 
113 void StringInstance::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
114 {
115  if (propertyName == exec->propertyNames().length) {
116  return;
117  }
118 
119  if (propertyName == exec->propertyNames().valueOf || propertyName == exec->propertyNames().toString) {
120  m_conversionsCustomized = true;
121  }
122 
123  JSObject::put(exec, propertyName, value, attr);
124 }
125 
126 bool StringInstance::deleteProperty(ExecState *exec, const Identifier &propertyName)
127 {
128  if (propertyName == exec->propertyNames().length) {
129  return false;
130  }
131  return JSObject::deleteProperty(exec, propertyName);
132 }
133 
134 void StringInstance::getOwnPropertyNames(ExecState *exec, PropertyNameArray &propertyNames, PropertyMap::PropertyMode mode)
135 {
136  int size = internalValue()->getString().size();
137  for (int i = 0; i < size; i++) {
138  propertyNames.add(Identifier(UString::from(i)));
139  }
140 
141  if (mode == PropertyMap::IncludeDontEnumProperties) {
142  propertyNames.add(exec->propertyNames().length);
143  }
144 
145  return JSObject::getOwnPropertyNames(exec, propertyNames, mode);
146 }
147 
148 UString StringInstance::toString(ExecState *exec) const
149 {
150  if (prototype() == originalProto() && !conversionsCustomized() &&
151  !static_cast<StringPrototype *>(prototype())->conversionsCustomized()) {
152  return internalValue()->value();
153  } else {
154  return JSObject::toString(exec);
155  }
156 }
157 
158 JSObject *StringInstance::valueClone(Interpreter *targetCtx) const
159 {
160  return new StringInstance(targetCtx->builtinStringPrototype(), internalValue());
161 }
162 
163 // ------------------------------ StringPrototype ---------------------------
164 const ClassInfo StringPrototype::info = {"String", &StringInstance::info, &stringTable, nullptr};
165 /* Source for string_object.lut.h
166 @begin stringTable 26
167  toString StringProtoFunc::ToString DontEnum|Function 0
168  valueOf StringProtoFunc::ValueOf DontEnum|Function 0
169  charAt StringProtoFunc::CharAt DontEnum|Function 1
170  charCodeAt StringProtoFunc::CharCodeAt DontEnum|Function 1
171  concat StringProtoFunc::Concat DontEnum|Function 1
172  endsWith StringProtoFunc::EndsWith DontEnum|Function 1
173  includes StringProtoFunc::Includes DontEnum|Function 1
174  indexOf StringProtoFunc::IndexOf DontEnum|Function 1
175  lastIndexOf StringProtoFunc::LastIndexOf DontEnum|Function 1
176  match StringProtoFunc::Match DontEnum|Function 1
177  replace StringProtoFunc::Replace DontEnum|Function 2
178  search StringProtoFunc::Search DontEnum|Function 1
179  slice StringProtoFunc::Slice DontEnum|Function 2
180  split StringProtoFunc::Split DontEnum|Function 2
181  startsWith StringProtoFunc::StartsWith DontEnum|Function 1
182  substr StringProtoFunc::Substr DontEnum|Function 2
183  substring StringProtoFunc::Substring DontEnum|Function 2
184  toLowerCase StringProtoFunc::ToLowerCase DontEnum|Function 0
185  toUpperCase StringProtoFunc::ToUpperCase DontEnum|Function 0
186  toLocaleLowerCase StringProtoFunc::ToLocaleLowerCase DontEnum|Function 0
187  toLocaleUpperCase StringProtoFunc::ToLocaleUpperCase DontEnum|Function 0
188  trim StringProtoFunc::Trim DontEnum|Function 0
189  localeCompare StringProtoFunc::LocaleCompare DontEnum|Function 1
190  repeat StringProtoFunc::Repeat DontEnum|Function 1
191 
192 #
193 # Under here: html extension, should only exist if KJS_PURE_ECMA is not defined
194 # I guess we need to generate two hashtables in the .lut.h file, and use #ifdef
195 # to select the right one... TODO. #####
196  big StringProtoFunc::Big DontEnum|Function 0
197  small StringProtoFunc::Small DontEnum|Function 0
198  blink StringProtoFunc::Blink DontEnum|Function 0
199  bold StringProtoFunc::Bold DontEnum|Function 0
200  fixed StringProtoFunc::Fixed DontEnum|Function 0
201  italics StringProtoFunc::Italics DontEnum|Function 0
202  strike StringProtoFunc::Strike DontEnum|Function 0
203  sub StringProtoFunc::Sub DontEnum|Function 0
204  sup StringProtoFunc::Sup DontEnum|Function 0
205  fontcolor StringProtoFunc::Fontcolor DontEnum|Function 1
206  fontsize StringProtoFunc::Fontsize DontEnum|Function 1
207  anchor StringProtoFunc::Anchor DontEnum|Function 1
208  link StringProtoFunc::Link DontEnum|Function 1
209  trimLeft StringProtoFunc::TrimLeft DontEnum|Function 0
210  trimRight StringProtoFunc::TrimRight DontEnum|Function 0
211 @end
212 */
213 // ECMA 15.5.4
214 StringPrototype::StringPrototype(ExecState *exec, ObjectPrototype *objProto)
215  : StringInstance(objProto)
216 {
217  // The constructor will be added later, after StringObjectImp has been built
218  putDirect(exec->propertyNames().length, jsNumber(0), DontDelete | ReadOnly | DontEnum);
219 }
220 
221 bool StringPrototype::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
222 {
223  return getStaticFunctionSlot<StringProtoFunc, StringInstance>(exec, &stringTable, this, propertyName, slot);
224 }
225 
226 // ------------------------------ StringProtoFunc ---------------------------
227 
228 StringProtoFunc::StringProtoFunc(ExecState *exec, int i, int len, const Identifier &name)
229  : InternalFunctionImp(static_cast<FunctionPrototype *>(exec->lexicalInterpreter()->builtinFunctionPrototype()), name)
230  , id(i)
231 {
232  putDirect(exec->propertyNames().length, len, DontDelete | ReadOnly | DontEnum);
233 }
234 
235 static inline void expandSourceRanges(UString::Range *&array, int &count, int &capacity)
236 {
237  int newCapacity;
238  if (capacity == 0) {
239  newCapacity = 16;
240  } else {
241  newCapacity = capacity * 2;
242  }
243 
244  UString::Range *newArray = new UString::Range[newCapacity];
245  for (int i = 0; i < count; i++) {
246  newArray[i] = array[i];
247  }
248 
249  delete [] array;
250 
251  capacity = newCapacity;
252  array = newArray;
253 }
254 
255 static void pushSourceRange(UString::Range *&array, int &count, int &capacity, UString::Range range)
256 {
257  if (count + 1 > capacity) {
258  expandSourceRanges(array, count, capacity);
259  }
260 
261  array[count] = range;
262  count++;
263 }
264 
265 static inline void expandReplacements(UString *&array, int &count, int &capacity)
266 {
267  int newCapacity;
268  if (capacity == 0) {
269  newCapacity = 16;
270  } else {
271  newCapacity = capacity * 2;
272  }
273 
274  UString *newArray = new UString[newCapacity];
275  for (int i = 0; i < count; i++) {
276  newArray[i] = array[i];
277  }
278 
279  delete [] array;
280 
281  capacity = newCapacity;
282  array = newArray;
283 }
284 
285 static void pushReplacement(UString *&array, int &count, int &capacity, UString replacement)
286 {
287  if (count + 1 > capacity) {
288  expandReplacements(array, count, capacity);
289  }
290 
291  array[count] = replacement;
292  count++;
293 }
294 
295 static inline UString substituteBackreferences(const UString &replacement, const UString &source, int *ovector, RegExp *reg)
296 {
297  UString substitutedReplacement = replacement;
298 
299  int i = -1;
300  while ((i = substitutedReplacement.find(UString("$"), i + 1)) != -1) {
301  if (i + 1 == substitutedReplacement.size()) {
302  break;
303  }
304 
305  unsigned short ref = substitutedReplacement[i + 1].unicode();
306  int backrefStart = 0;
307  int backrefLength = 0;
308  int advance = 0;
309 
310  if (ref == '$') { // "$$" -> "$"
311  substitutedReplacement = substitutedReplacement.substr(0, i + 1) + substitutedReplacement.substr(i + 2);
312  continue;
313  } else if (ref == '&') {
314  backrefStart = ovector[0];
315  backrefLength = ovector[1] - backrefStart;
316  } else if (ref == '`') {
317  backrefStart = 0;
318  backrefLength = ovector[0];
319  } else if (ref == '\'') {
320  backrefStart = ovector[1];
321  backrefLength = source.size() - backrefStart;
322  } else if (ref >= '0' && ref <= '9') {
323  // 1- and 2-digit back references are allowed
324  unsigned backrefIndex = ref - '0';
325  if (backrefIndex > (unsigned)reg->subPatterns()) {
326  continue;
327  }
328  if (substitutedReplacement.size() > i + 2) {
329  ref = substitutedReplacement[i + 2].unicode();
330  if (ref >= '0' && ref <= '9') {
331  backrefIndex = 10 * backrefIndex + ref - '0';
332  if (backrefIndex > (unsigned)reg->subPatterns()) {
333  backrefIndex = backrefIndex / 10; // Fall back to the 1-digit reference
334  } else {
335  advance = 1;
336  }
337  }
338  }
339  backrefStart = ovector[2 * backrefIndex];
340  backrefLength = ovector[2 * backrefIndex + 1] - backrefStart;
341  } else {
342  continue;
343  }
344 
345  substitutedReplacement = substitutedReplacement.substr(0, i) + source.substr(backrefStart, backrefLength) + substitutedReplacement.substr(i + 2 + advance);
346  i += backrefLength - 1; // - 1 offsets 'i + 1'
347  }
348 
349  return substitutedReplacement;
350 }
351 
352 static inline int localeCompare(const UString &a, const UString &b)
353 {
354 #if PLATFORM(WIN_OS)
355  int retval = CompareStringW(LOCALE_USER_DEFAULT, 0,
356  reinterpret_cast<LPCWSTR>(a.data()), a.size(),
357  reinterpret_cast<LPCWSTR>(b.data()), b.size());
358  return !retval ? retval : retval - 2;
359 #elif PLATFORM(CF)
360  CFStringRef sa = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar *>(a.data()), a.size(), kCFAllocatorNull);
361  CFStringRef sb = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar *>(b.data()), b.size(), kCFAllocatorNull);
362 
363  int retval = CFStringCompare(sa, sb, kCFCompareLocalized);
364 
365  CFRelease(sa);
366  CFRelease(sb);
367 
368  return retval;
369 #else
370  // ### use as fallback only. implement locale aware version.
371  // ### other browsers have more detailed return values than -1, 0 and 1
372  return compare(a, b);
373 #endif
374 }
375 
376 static JSValue *replace(ExecState *exec, const UString &source, JSValue *pattern, JSValue *replacement)
377 {
378  JSObject *replacementFunction = nullptr;
379  UString replacementString;
380 
381  if (JSValue::isObject(replacement) && JSValue::toObject(replacement, exec)->implementsCall()) {
382  replacementFunction = JSValue::toObject(replacement, exec);
383  } else {
384  replacementString = JSValue::toString(replacement, exec);
385  }
386 
387  if (JSValue::isObject(pattern) && static_cast<JSObject *>(pattern)->inherits(&RegExpImp::info)) {
388  RegExp *reg = static_cast<RegExpImp *>(pattern)->regExp();
389  bool global = reg->flags() & RegExp::Global;
390 
391  RegExpObjectImp *regExpObj = static_cast<RegExpObjectImp *>(exec->lexicalInterpreter()->builtinRegExp());
392 
393  int matchIndex = 0;
394  int lastIndex = 0;
395  int startPosition = 0;
396 
397  UString::Range *sourceRanges = nullptr;
398  int sourceRangeCount = 0;
399  int sourceRangeCapacity = 0;
400  UString *replacements = nullptr;
401  int replacementCount = 0;
402  int replacementCapacity = 0;
403 
404  // This is either a loop (if global is set) or a one-way (if not).
405  RegExpStringContext ctx(source);
406  do {
407  int *ovector;
408  UString matchString = regExpObj->performMatch(reg, exec, ctx, source, startPosition, &matchIndex, &ovector);
409  if (matchIndex == -1) {
410  break;
411  }
412  int matchLen = matchString.size();
413 
414  pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, matchIndex - lastIndex));
415  UString substitutedReplacement;
416  if (replacementFunction) {
417  int completeMatchStart = ovector[0];
418  List args;
419 
420  args.append(jsString(matchString));
421 
422  for (unsigned i = 0; i < reg->subPatterns(); i++) {
423  int matchStart = ovector[(i + 1) * 2];
424  int matchLen = ovector[(i + 1) * 2 + 1] - matchStart;
425 
426  args.append(jsString(source.substr(matchStart, matchLen)));
427  }
428 
429  args.append(jsNumber(completeMatchStart));
430  args.append(jsString(source));
431 
432  substitutedReplacement = JSValue::toString(replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), args), exec);
433  } else {
434  substitutedReplacement = substituteBackreferences(replacementString, source, ovector, reg);
435  }
436  pushReplacement(replacements, replacementCount, replacementCapacity, substitutedReplacement);
437 
438  lastIndex = matchIndex + matchLen;
439  startPosition = lastIndex;
440 
441  // special case of empty match
442  if (matchLen == 0) {
443  startPosition++;
444  if (startPosition > source.size()) {
445  break;
446  }
447  }
448  } while (global);
449 
450  if (lastIndex < source.size()) {
451  pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, source.size() - lastIndex));
452  }
453 
454  UString result;
455  if (sourceRanges) {
456  result = source.spliceSubstringsWithSeparators(sourceRanges, sourceRangeCount, replacements, replacementCount);
457  }
458 
459  delete [] sourceRanges;
460  delete [] replacements;
461 
462  return jsString(result);
463  }
464 
465  // First arg is a string
466  UString patternString = JSValue::toString(pattern, exec);
467  int matchPos = source.find(patternString);
468  int matchLen = patternString.size();
469  // Do the replacement
470  if (matchPos == -1) {
471  return jsString(source);
472  }
473 
474  if (replacementFunction) {
475  List args;
476 
477  args.append(jsString(source.substr(matchPos, matchLen)));
478  args.append(jsNumber(matchPos));
479  args.append(jsString(source));
480 
481  replacementString = JSValue::toString(replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), args), exec);
482  }
483 
484  return jsString(source.substr(0, matchPos) + replacementString + source.substr(matchPos + matchLen));
485 }
486 
487 static UnicodeSupport::StringConversionFunction toUpperF = Unicode::toUpper;
488 static UnicodeSupport::StringConversionFunction toLowerF = Unicode::toLower;
489 
490 void StringProtoFunc::setToLowerFunction(UnicodeSupport::StringConversionFunction f)
491 {
492  toLowerF = f;
493 }
494 
495 void StringProtoFunc::setToUpperFunction(UnicodeSupport::StringConversionFunction f)
496 {
497  toUpperF = f;
498 }
499 
500 // ECMA 15.5.4.2 - 15.5.4.20
501 JSValue *StringProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
502 {
503  JSValue *result = nullptr;
504 
505  // toString and valueOf are no generic function.
506  if (id == ToString || id == ValueOf) {
507  if (!thisObj || !thisObj->inherits(&StringInstance::info)) {
508  return throwError(exec, TypeError);
509  }
510 
511  return jsString(static_cast<StringInstance *>(thisObj)->internalValue()->toString(exec));
512  }
513 
514  UString u, u2, u3;
515  int pos, p0, i;
516  double dpos;
517  double d = 0.0;
518 
519  UString s = thisObj->toString(exec);
520 
521  int len = s.size();
522  JSValue *a0 = args[0];
523  JSValue *a1 = args[1];
524 
525  switch (id) {
526  case ToString:
527  case ValueOf:
528  // handled above
529  break;
530  case CharAt:
531  // Other browsers treat an omitted parameter as 0 rather than NaN.
532  // That doesn't match the ECMA standard, but is needed for site compatibility.
533  dpos = JSValue::isUndefined(a0) ? 0 : JSValue::toInteger(a0, exec);
534  if (dpos >= 0 && dpos < len) { // false for NaN
535  u = s.substr(static_cast<int>(dpos), 1);
536  } else {
537  u = "";
538  }
539  result = jsString(u);
540  break;
541  case CharCodeAt:
542  // Other browsers treat an omitted parameter as 0 rather than NaN.
543  // That doesn't match the ECMA standard, but is needed for site compatibility.
544  dpos = JSValue::isUndefined(a0) ? 0 : JSValue::toInteger(a0, exec);
545  if (dpos >= 0 && dpos < len) { // false for NaN
546  result = jsNumber(s[static_cast<int>(dpos)].unicode());
547  } else {
548  result = jsNaN();
549  }
550  break;
551  case Concat: {
552  ListIterator it = args.begin();
553  for (; it != args.end(); ++it) {
554  s += JSValue::toString(*it, exec);
555  }
556  result = jsString(s);
557  break;
558  }
559  case EndsWith:
560  if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
561  return throwError(exec, TypeError, "RegExp reserved for future");
562  }
563  u2 = JSValue::toString(a0, exec);
564  if (JSValue::isUndefined(a1))
565  i = s.size();
566  else
567  i = minInt(s.size(), JSValue::toInteger(a1, exec));
568  pos = i - u2.size();
569  result = jsBoolean(pos >= 0 && s.substr(pos, u2.size()) == u2);
570  break;
571  case Includes:
572  if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
573  return throwError(exec, TypeError, "RegExp reserved for future");
574  }
575  u2 = JSValue::toString(a0, exec);
576  pos = JSValue::toInteger(a1, exec);
577  i = s.find(u2, pos);
578  result = jsBoolean(i >= 0);
579  break;
580  case IndexOf:
581  u2 = JSValue::toString(a0, exec);
582  if (JSValue::isUndefined(a1)) {
583  dpos = 0;
584  } else {
585  dpos = JSValue::toInteger(a1, exec);
586  if (dpos >= 0) { // false for NaN
587  if (dpos > len) {
588  dpos = len;
589  }
590  } else {
591  dpos = 0;
592  }
593  }
594  result = jsNumber(s.find(u2, static_cast<int>(dpos)));
595  break;
596  case LastIndexOf:
597  u2 = JSValue::toString(a0, exec);
598  d = JSValue::toNumber(a1, exec);
599  if (JSValue::isUndefined(a1) || KJS::isNaN(d)) {
600  dpos = len;
601  } else {
602  dpos = JSValue::toInteger(a1, exec);
603  if (dpos >= 0) { // false for NaN
604  if (dpos > len) {
605  dpos = len;
606  }
607  } else {
608  dpos = 0;
609  }
610  }
611  result = jsNumber(s.rfind(u2, static_cast<int>(dpos)));
612  break;
613  case Match:
614  case Search: {
615  u = s;
616  RegExp *reg, *tmpReg = nullptr;
617  RegExpImp *imp = nullptr;
618  if (JSValue::isObject(a0) && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) {
619  reg = static_cast<RegExpImp *>(a0)->regExp();
620  } else {
621  /*
622  * ECMA 15.5.4.12 String.prototype.search (regexp)
623  * If regexp is not an object whose [[Class]] property is "RegExp", it is
624  * replaced with the result of the expression new RegExp(regexp).
625  */
626  reg = tmpReg = new RegExp(JSValue::toString(a0, exec), RegExp::None);
627  }
628  if (!reg->isValid()) {
629  delete tmpReg;
630  return throwError(exec, SyntaxError, "Invalid regular expression");
631  }
632  RegExpObjectImp *regExpObj = static_cast<RegExpObjectImp *>(exec->lexicalInterpreter()->builtinRegExp());
633 
634  RegExpStringContext ctx(u);
635  UString mstr = regExpObj->performMatch(reg, exec, ctx, u, 0, &pos);
636 
637  if (id == Search) {
638  result = jsNumber(pos);
639  } else {
640  // Exec
641  if ((reg->flags() & RegExp::Global) == 0) {
642  // case without 'g' flag is handled like RegExp.prototype.exec
643  if (mstr.isNull()) {
644  result = jsNull();
645  } else {
646  result = regExpObj->arrayOfMatches(exec, mstr);
647  }
648  } else {
649  // return array of matches
650  List list;
651  int lastIndex = 0;
652  while (pos >= 0) {
653  if (mstr.isNull()) {
654  list.append(jsUndefined());
655  } else {
656  list.append(jsString(mstr));
657  }
658  lastIndex = pos;
659  pos += mstr.isEmpty() ? 1 : mstr.size();
660  mstr = regExpObj->performMatch(reg, exec, ctx, u, pos, &pos);
661  }
662  if (imp) {
663  imp->put(exec, "lastIndex", jsNumber(lastIndex), DontDelete | DontEnum);
664  }
665  if (list.isEmpty()) {
666  // if there are no matches at all, it's important to return
667  // Null instead of an empty array, because this matches
668  // other browsers and because Null is a false value.
669  result = jsNull();
670  } else {
671  result = exec->lexicalInterpreter()->builtinArray()->construct(exec, list);
672  }
673  }
674  }
675 
676  delete tmpReg;
677  break;
678  }
679  case Replace:
680  result = replace(exec, s, a0, a1);
681  break;
682  case Slice: {
683  // The arg processing is very much like ArrayProtoFunc::Slice
684  double start = JSValue::toInteger(a0, exec);
685  double end = JSValue::isUndefined(a1) ? len : JSValue::toInteger(a1, exec);
686  double from = start < 0 ? len + start : start;
687  double to = end < 0 ? len + end : end;
688  if (to > from && to > 0 && from < len) {
689  if (from < 0) {
690  from = 0;
691  }
692  if (to > len) {
693  to = len;
694  }
695  result = jsString(s.substr(static_cast<int>(from), static_cast<int>(to - from)));
696  } else {
697  result = jsString("");
698  }
699  break;
700  }
701  case Split: {
702  JSObject *constructor = exec->lexicalInterpreter()->builtinArray();
703  JSObject *res = static_cast<JSObject *>(constructor->construct(exec, List::empty()));
704  result = res;
705  u = s;
706  i = p0 = 0;
707  uint32_t limit = JSValue::isUndefined(a1) ? 0xFFFFFFFFU : JSValue::toUInt32(a1, exec);
708  if (JSValue::isObject(a0) && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) {
709  RegExp *reg = static_cast<RegExpImp *>(a0)->regExp();
710 
711  RegExpStringContext ctx(u);
712  bool error = false;
713  if (u.isEmpty() && !reg->match(ctx, u, &error, 0).isNull()) {
714 
715  // empty string matched by regexp -> empty array
716  res->put(exec, exec->propertyNames().length, jsNumber(0));
717  break;
718  }
719  pos = 0;
720  while (!error && static_cast<uint32_t>(i) != limit && pos < u.size()) {
721  // TODO: back references
722  int mpos;
723  int *ovector = nullptr;
724  UString mstr = reg->match(ctx, u, &error, pos, &mpos, &ovector);
725  delete [] ovector; ovector = nullptr;
726  if (mpos < 0) {
727  break;
728  }
729  pos = mpos + (mstr.isEmpty() ? 1 : mstr.size());
730  if (mpos != p0 || !mstr.isEmpty()) {
731  res->put(exec, i, jsString(u.substr(p0, mpos - p0)));
732  p0 = mpos + mstr.size();
733  i++;
734  }
735  }
736 
737  if (error) {
738  RegExpObjectImp::throwRegExpError(exec);
739  }
740 
741  } else {
742  u2 = JSValue::toString(a0, exec);
743  if (u2.isEmpty()) {
744  if (u.isEmpty()) {
745  // empty separator matches empty string -> empty array
746  put(exec, exec->propertyNames().length, jsNumber(0));
747  break;
748  } else {
749  while (static_cast<uint32_t>(i) != limit && i < u.size() - 1) {
750  res->put(exec, i++, jsString(u.substr(p0++, 1)));
751  }
752  }
753  } else {
754  while (static_cast<uint32_t>(i) != limit && (pos = u.find(u2, p0)) >= 0) {
755  res->put(exec, i, jsString(u.substr(p0, pos - p0)));
756  p0 = pos + u2.size();
757  i++;
758  }
759  }
760  }
761  // add remaining string, if any
762  if (static_cast<uint32_t>(i) != limit) {
763  res->put(exec, i++, jsString(u.substr(p0)));
764  }
765  res->put(exec, exec->propertyNames().length, jsNumber(i));
766  }
767  break;
768  case StartsWith:
769  if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
770  return throwError(exec, TypeError, "RegExp reserved for future");
771  }
772  u2 = JSValue::toString(a0, exec);
773  pos = maxInt(0, JSValue::toInteger(a1, exec));
774  result = jsBoolean(s.substr(pos, u2.size()) == u2);
775  break;
776  case Substr: { //B.2.3
777  // Note: NaN is effectively handled as 0 here for both length
778  // and start, hence toInteger does fine, and removes worries
779  // about weird comparison results below.
780  int len = s.size();
781  double start = JSValue::toInteger(a0, exec);
782  double length = JSValue::isUndefined(a1) ? len : JSValue::toInteger(a1, exec);
783 
784  if (start >= len) {
785  return jsString("");
786  }
787  if (length < 0) {
788  return jsString("");
789  }
790  if (start < 0) {
791  start += s.size();
792  if (start < 0) {
793  start = 0;
794  }
795  }
796 
797  if (length > len - start) {
798  length = len - start;
799  }
800 
801  result = jsString(s.substr(static_cast<int>(start), static_cast<int>(length)));
802  break;
803  }
804  case Substring: {
805  double start = JSValue::toNumber(a0, exec);
806  double end = JSValue::toNumber(a1, exec);
807  if (isNaN(start)) {
808  start = 0;
809  }
810  if (isNaN(end)) {
811  end = 0;
812  }
813  if (start < 0) {
814  start = 0;
815  }
816  if (end < 0) {
817  end = 0;
818  }
819  if (start > len) {
820  start = len;
821  }
822  if (end > len) {
823  end = len;
824  }
825  if (JSValue::isUndefined(a1)) {
826  end = len;
827  }
828  if (start > end) {
829  double temp = end;
830  end = start;
831  start = temp;
832  }
833  result = jsString(s.substr((int)start, (int)end - (int)start));
834  }
835  break;
836  case ToLowerCase:
837  case ToLocaleLowerCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented.
838  u = s;
839  u.copyForWriting();
840  uint16_t *dataPtr = reinterpret_cast<uint16_t *>(u.rep()->data());
841  uint16_t *destIfNeeded;
842 
843  int len = toLowerF(dataPtr, u.size(), destIfNeeded);
844  if (len >= 0) {
845  result = jsString(UString(reinterpret_cast<UChar *>(destIfNeeded ? destIfNeeded : dataPtr), len));
846  } else {
847  result = jsString(s);
848  }
849 
850  free(destIfNeeded);
851  break;
852  }
853  case ToUpperCase:
854  case ToLocaleUpperCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented.
855  u = s;
856  u.copyForWriting();
857  uint16_t *dataPtr = reinterpret_cast<uint16_t *>(u.rep()->data());
858  uint16_t *destIfNeeded;
859 
860  int len = toUpperF(dataPtr, u.size(), destIfNeeded);
861  if (len >= 0) {
862  result = jsString(UString(reinterpret_cast<UChar *>(destIfNeeded ? destIfNeeded : dataPtr), len));
863  } else {
864  result = jsString(s);
865  }
866 
867  free(destIfNeeded);
868  break;
869  }
870  case LocaleCompare:
871  if (args.size() < 1) {
872  return jsNumber(0);
873  }
874  return jsNumber(localeCompare(s, JSValue::toString(a0, exec)));
875  case Repeat: {
876  double n = JSValue::toInteger(args[0], exec);
877  if (exec->hadException())
878  return jsUndefined();
879  if (n < 0 || KJS::isPosInf(n))
880  return throwError(exec, RangeError, "Invalid repeat count");
881 
882  UString ret;
883  for (int i = 0; i < n; ++i)
884  {
885  ret += s;
886  }
887  return jsString(ret);
888  }
889  case Trim:
890  case TrimRight:
891  case TrimLeft: {
892  const uint16_t *dataPtr = reinterpret_cast<uint16_t *>(s.rep()->data());
893 
894  const int size = s.size();
895  int left = 0;
896  if (id != TrimRight)
897  while (left < size && CommonUnicode::isStrWhiteSpace(dataPtr[left])) {
898  left++;
899  }
900 
901  int right = size;
902  if (id != TrimLeft)
903  while (right > left && CommonUnicode::isStrWhiteSpace(dataPtr[right - 1])) {
904  right--;
905  }
906 
907  result = jsString(s.substr(left, right - left));
908  break;
909  }
910 #ifndef KJS_PURE_ECMA
911  case Big:
912  result = jsString("<big>" + s + "</big>");
913  break;
914  case Small:
915  result = jsString("<small>" + s + "</small>");
916  break;
917  case Blink:
918  result = jsString("<blink>" + s + "</blink>");
919  break;
920  case Bold:
921  result = jsString("<b>" + s + "</b>");
922  break;
923  case Fixed:
924  result = jsString("<tt>" + s + "</tt>");
925  break;
926  case Italics:
927  result = jsString("<i>" + s + "</i>");
928  break;
929  case Strike:
930  result = jsString("<strike>" + s + "</strike>");
931  break;
932  case Sub:
933  result = jsString("<sub>" + s + "</sub>");
934  break;
935  case Sup:
936  result = jsString("<sup>" + s + "</sup>");
937  break;
938  case Fontcolor:
939  result = jsString("<font color=\"" + JSValue::toString(a0, exec) + "\">" + s + "</font>");
940  break;
941  case Fontsize:
942  result = jsString("<font size=\"" + JSValue::toString(a0, exec) + "\">" + s + "</font>");
943  break;
944  case Anchor:
945  result = jsString("<a name=\"" + JSValue::toString(a0, exec) + "\">" + s + "</a>");
946  break;
947  case Link:
948  result = jsString("<a href=\"" + JSValue::toString(a0, exec) + "\">" + s + "</a>");
949  break;
950 #endif
951  }
952 
953  return result;
954 }
955 
956 // ------------------------------ StringObjectImp ------------------------------
957 
958 StringObjectImp::StringObjectImp(ExecState *exec,
959  FunctionPrototype *funcProto,
960  StringPrototype *stringProto)
961  : InternalFunctionImp(funcProto)
962 {
963  // ECMA 15.5.3.1 String.prototype
964  putDirect(exec->propertyNames().prototype, stringProto, DontEnum | DontDelete | ReadOnly);
965 
966  putDirectFunction(new StringObjectFuncImp(exec, funcProto, exec->propertyNames().fromCharCode), DontEnum);
967 
968  // no. of arguments for constructor
969  putDirect(exec->propertyNames().length, jsNumber(1), ReadOnly | DontDelete | DontEnum);
970 }
971 
972 bool StringObjectImp::implementsConstruct() const
973 {
974  return true;
975 }
976 
977 // ECMA 15.5.2
978 JSObject *StringObjectImp::construct(ExecState *exec, const List &args)
979 {
980  JSObject *proto = exec->lexicalInterpreter()->builtinStringPrototype();
981  if (args.size() == 0) {
982  return new StringInstance(proto);
983  }
984  return new StringInstance(proto, JSValue::toString(*args.begin(), exec));
985 }
986 
987 // ECMA 15.5.1
988 JSValue *StringObjectImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
989 {
990  if (args.isEmpty()) {
991  return jsString("");
992  } else {
993  JSValue *v = args[0];
994  return jsString(JSValue::toString(v, exec));
995  }
996 }
997 
998 // ------------------------------ StringObjectFuncImp --------------------------
999 
1000 // ECMA 15.5.3.2 fromCharCode()
1001 StringObjectFuncImp::StringObjectFuncImp(ExecState *exec, FunctionPrototype *funcProto, const Identifier &name)
1002  : InternalFunctionImp(funcProto, name)
1003 {
1004  putDirect(exec->propertyNames().length, jsNumber(1), DontDelete | ReadOnly | DontEnum);
1005 }
1006 
1007 JSValue *StringObjectFuncImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
1008 {
1009  UString s;
1010  if (args.size()) {
1011  UChar *buf = static_cast<UChar *>(fastMalloc(args.size() * sizeof(UChar)));
1012  UChar *p = buf;
1013  ListIterator it = args.begin();
1014  while (it != args.end()) {
1015  unsigned short u = JSValue::toUInt16(*it, exec);
1016  *p++ = UChar(u);
1017  it++;
1018  }
1019  s = UString(buf, args.size(), false);
1020  } else {
1021  s = "";
1022  }
1023 
1024  return jsString(s);
1025 }
1026 
1027 }
void append(const T &value)
QTextStream & right(QTextStream &stream)
QString pattern(Mode mode=Reading)
KIOCORE_EXPORT TransferJob * put(const QUrl &url, int permissions, JobFlags flags=DefaultFlags)
void ref()
QTextStream & left(QTextStream &stream)
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
Q_SCRIPTABLE Q_NOREPLY void start()
QAction * replace(const QObject *recvr, const char *slot, QObject *parent)
bool isEmpty() const const
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
static UString from(int i)
Constructs a string from an int.
Definition: ustring.cpp:559
const char * name(StandardAction id)
static const List & empty()
Returns a pointer to a static instance of an empty list.
Definition: list.cpp:311
bool isNaN(double x)
virtual bool put(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName, const QVariant &value)
const QList< QKeySequence > & end()
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sat Sep 30 2023 03:56:10 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.