mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-22 10:20:43 +00:00
Merge branch 'master' of git@github.com:280north/cappuccino
This commit is contained in:
@@ -461,9 +461,9 @@
|
||||
*/
|
||||
- (unsigned)indexOfObject:(id)anObject sortedByFunction:(Function)aFunction context:(id)aContext
|
||||
{
|
||||
if (!aFunction || !anObject)
|
||||
if (!aFunction || anObject === undefined)
|
||||
return CPNotFound;
|
||||
|
||||
|
||||
var mid, c, first = 0, last = length - 1;
|
||||
while (first <= last)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,596 @@
|
||||
/*
|
||||
* CPAttributedString.j
|
||||
* Foundation
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
import <Foundation/CPObject.j>
|
||||
import <Foundation/CPString.j>
|
||||
|
||||
@implementation CPAttributedString : CPObject
|
||||
{
|
||||
CPString _string;
|
||||
CPArray _rangeEntries;
|
||||
}
|
||||
|
||||
//Creating an NSAttributedString Object
|
||||
- (id)initWithString:(CPString)aString
|
||||
{
|
||||
return [self initWithString:aString attributes:nil];
|
||||
}
|
||||
|
||||
- (id)initWithAttributedString:(CPAttributedString)aString
|
||||
{
|
||||
var string = [self initWithString:"" attributes:nil];
|
||||
|
||||
[string setAttributedString:aString];
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
- (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (!attributes)
|
||||
attributes = [CPDictionary dictionary];
|
||||
|
||||
_string = ""+aString;
|
||||
_rangeEntries = [makeRangeEntry(CPMakeRange(0, _string.length), attributes)];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
//Retrieving Character Information
|
||||
- (CPString)string
|
||||
{
|
||||
return _string;
|
||||
}
|
||||
|
||||
- (CPString)mutableString
|
||||
{
|
||||
return [self string];
|
||||
}
|
||||
|
||||
- (unsigned)length
|
||||
{
|
||||
return _string.length;
|
||||
}
|
||||
|
||||
- (unsigned)_indexOfEntryWithIndex:(unsigned)anIndex
|
||||
{
|
||||
if (anIndex < 0 || anIndex > _string.length || anIndex === undefined)
|
||||
return CPNotFound;
|
||||
|
||||
//find the range entry that contains anIndex.
|
||||
var sortFunction = function(index, entry)
|
||||
{
|
||||
//index is the character index we're searching for, while range is the actual range entry we're comparing against
|
||||
if (CPLocationInRange(index, entry.range))
|
||||
return CPOrderedSame;
|
||||
else if (CPMaxRange(entry.range) <= index)
|
||||
return CPOrderedDescending;
|
||||
else
|
||||
return CPOrderedAscending;
|
||||
}
|
||||
|
||||
return [_rangeEntries indexOfObject:anIndex sortedByFunction:sortFunction];
|
||||
}
|
||||
|
||||
//Retrieving Attribute Information
|
||||
- (CPDictionary)attributesAtIndex:(unsigned)anIndex effectiveRange:(CPRangePointer)aRange
|
||||
{
|
||||
//find the range entry that contains anIndex.
|
||||
var entryIndex = [self _indexOfEntryWithIndex:anIndex];
|
||||
|
||||
if (entryIndex == CPNotFound)
|
||||
return nil;
|
||||
|
||||
var matchingRange = _rangeEntries[entryIndex];
|
||||
if (aRange)
|
||||
{
|
||||
aRange.location = matchingRange.range.location;
|
||||
aRange.length = matchingRange.range.length;
|
||||
}
|
||||
|
||||
return matchingRange.attributes;
|
||||
}
|
||||
|
||||
- (CPDictionary)attributesAtIndex:(unsigned)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
|
||||
{
|
||||
var startingEntryIndex = [self _indexOfEntryWithIndex:anIndex];
|
||||
|
||||
if (startingEntryIndex == CPNotFound)
|
||||
return nil;
|
||||
|
||||
if (!aRange)
|
||||
return _rangeEntries[startingEntryIndex].attributes;
|
||||
|
||||
if (CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
|
||||
{
|
||||
aRange.location = rangeLimit.location;
|
||||
aRange.length = rangeLimit.length;
|
||||
|
||||
return _rangeEntries[startingEntryIndex].attributes;
|
||||
}
|
||||
|
||||
//scan backwards
|
||||
var nextRangeIndex = startingEntryIndex - 1,
|
||||
currentEntry = _rangeEntries[startingEntryIndex],
|
||||
comparisonDict = currentEntry.attributes;
|
||||
|
||||
while (nextRangeIndex >= 0)
|
||||
{
|
||||
var nextEntry = _rangeEntries[nextRangeIndex];
|
||||
|
||||
if (CPMaxRange(nextEntry.range) > rangeLimit.location && [nextEntry.attributes isEqualToDictionary:comparisonDict])
|
||||
{
|
||||
currentEntry = nextEntry;
|
||||
nextRangeIndex--;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
|
||||
|
||||
//scan forwards
|
||||
currentEntry = _rangeEntries[startingEntryIndex];
|
||||
nextRangeIndex = startingEntryIndex + 1;
|
||||
|
||||
while (nextRangeIndex < _rangeEntries.length)
|
||||
{
|
||||
var nextEntry = _rangeEntries[nextRangeIndex];
|
||||
|
||||
if (nextEntry.range.location < CPMaxRange(rangeLimit) && [nextEntry.attributes isEqualToDictionary:comparisonDict])
|
||||
{
|
||||
currentEntry = nextEntry;
|
||||
nextRangeIndex++;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
aRange.length = MIN(CPMaxRange(currentEntry.range), CPMaxRange(rangeLimit)) - aRange.location;
|
||||
|
||||
return comparisonDict;
|
||||
}
|
||||
|
||||
- (id)attribute:(CPString)attribute atIndex:(unsigned)index effectiveRange:(CPRangePointer)aRange
|
||||
{
|
||||
if (!attribute)
|
||||
{
|
||||
if (aRange)
|
||||
{
|
||||
aRange.location = 0;
|
||||
aRange.length = _string.length;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [[self attributesAtIndex:index effectiveRange:aRange] valueForKey:attribute];
|
||||
}
|
||||
|
||||
- (id)attribute:(CPString)attribute atIndex:(unsigned)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
|
||||
{
|
||||
//find the range entry that contains anIndex.
|
||||
var startingEntryIndex = [self _indexOfEntryWithIndex:anIndex];
|
||||
|
||||
if (startingEntryIndex == CPNotFound || !attribute)
|
||||
return nil;
|
||||
|
||||
if (!aRange)
|
||||
return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
|
||||
|
||||
if (CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
|
||||
{
|
||||
aRange.location = rangeLimit.location;
|
||||
aRange.length = rangeLimit.length;
|
||||
|
||||
return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
|
||||
}
|
||||
|
||||
//scan backwards
|
||||
var nextRangeIndex = startingEntryIndex - 1,
|
||||
currentEntry = _rangeEntries[startingEntryIndex],
|
||||
comparisonAttribute = [currentEntry.attributes objectForKey:attribute];
|
||||
|
||||
while (nextRangeIndex >= 0)
|
||||
{
|
||||
var nextEntry = _rangeEntries[nextRangeIndex];
|
||||
|
||||
if (CPMaxRange(nextEntry.range) > rangeLimit.location && isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
|
||||
{
|
||||
currentEntry = nextEntry;
|
||||
nextRangeIndex--;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
|
||||
|
||||
//scan forwards
|
||||
currentEntry = _rangeEntries[startingEntryIndex];
|
||||
nextRangeIndex = startingEntryIndex + 1;
|
||||
|
||||
while (nextRangeIndex < _rangeEntries.length)
|
||||
{
|
||||
var nextEntry = _rangeEntries[nextRangeIndex];
|
||||
|
||||
if (nextEntry.range.location < CPMaxRange(rangeLimit) && isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
|
||||
{
|
||||
currentEntry = nextEntry;
|
||||
nextRangeIndex++;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
aRange.length = MIN(CPMaxRange(currentEntry.range), CPMaxRange(rangeLimit)) - aRange.location;
|
||||
|
||||
return comparisonAttribute;
|
||||
}
|
||||
|
||||
//Comparing Attributed Strings
|
||||
- (BOOL)isEqualToAttributedString:(CPAttributedString)aString
|
||||
{
|
||||
if(!aString)
|
||||
return NO;
|
||||
|
||||
if(_string != [aString string])
|
||||
return NO;
|
||||
|
||||
var myRange = CPMakeRange(),
|
||||
comparisonRange = CPMakeRange(),
|
||||
myAttributes = [self attributesAtIndex:0 effectiveRange:myRange],
|
||||
comparisonAttributes = [aString attributesAtIndex:0 effectiveRange:comparisonRange],
|
||||
length = _string.length;
|
||||
|
||||
while (CPMaxRange(CPUnionRange(myRange, comparisonRange)) < length)
|
||||
{
|
||||
if (CPIntersectionRange(myRange, comparisonRange).length > 0 && ![myAttributes isEqualToDictionary:comparisonAttributes])
|
||||
return NO;
|
||||
if (CPMaxRange(myRange) < CPMaxRange(comparisonRange))
|
||||
myAttributes = [self attributesAtIndex:CPMaxRange(myRange) effectiveRange:myRange];
|
||||
else
|
||||
comparisonAttributes = [aString attributesAtIndex:CPMaxRange(comparisonRange) effectiveRange:comparisonRange];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)anObject
|
||||
{
|
||||
if (anObject == self)
|
||||
return YES;
|
||||
|
||||
if ([anObject isKindOfClass:[self class]])
|
||||
return [self isEqualToAttributedString:anObject];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
//Extracting a Substring
|
||||
- (CPAttributedString)attributedSubstringFromRange:(CPRange)aRange
|
||||
{
|
||||
if (!aRange || CPMaxRange(aRange) > _string.length || aRange.location < 0)
|
||||
[CPException raise:CPRangeException
|
||||
reason:"tried to get attributedSubstring for an invalid range: "+(aRange?CPStringFromRange(aRange):"nil")];
|
||||
|
||||
var newString = [[CPAttributedString alloc] initWithString:_string.substring(aRange.location, CPMaxRange(aRange))],
|
||||
entryIndex = [self _indexOfEntryWithIndex:aRange.location],
|
||||
currentRangeEntry = _rangeEntries[entryIndex],
|
||||
lastIndex = CPMaxRange(aRange);
|
||||
|
||||
newString._rangeEntries = [];
|
||||
|
||||
while (currentRangeEntry && CPMaxRange(currentRangeEntry.range) < lastIndex)
|
||||
{
|
||||
var newEntry = copyRangeEntry(currentRangeEntry);
|
||||
newEntry.range.location -= aRange.location;
|
||||
|
||||
if (newEntry.range.location < 0)
|
||||
{
|
||||
newEntry.range.length += newEntry.range.location;
|
||||
newEntry.range.location = 0;
|
||||
}
|
||||
|
||||
newString._rangeEntries.push(newEntry);
|
||||
currentRangeEntry = _rangeEntries[++entryIndex];
|
||||
}
|
||||
|
||||
if (currentRangeEntry)
|
||||
{
|
||||
var newRangeEntry = copyRangeEntry(currentRangeEntry);
|
||||
|
||||
newRangeEntry.range.length = CPMaxRange(aRange) - newRangeEntry.range.location;
|
||||
newRangeEntry.range.location -= aRange.location;
|
||||
|
||||
if (newRangeEntry.range.location < 0)
|
||||
{
|
||||
newRangeEntry.range.length += newRangeEntry.range.location;
|
||||
newRangeEntry.range.location = 0;
|
||||
}
|
||||
|
||||
newString._rangeEntries.push(newRangeEntry);
|
||||
}
|
||||
|
||||
return newString;
|
||||
}
|
||||
|
||||
//Changing Characters
|
||||
- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
|
||||
{
|
||||
[self beginEditing];
|
||||
|
||||
if (!aString)
|
||||
aString = "";
|
||||
|
||||
var startingIndex = [self _indexOfEntryWithIndex:aRange.location],
|
||||
startingRangeEntry = _rangeEntries[startingIndex],
|
||||
endingIndex = [self _indexOfEntryWithIndex:CPMaxRange(aRange)-1],
|
||||
endingRangeEntry = _rangeEntries[endingIndex],
|
||||
additionalLength = aString.length - aRange.length;
|
||||
|
||||
_string = _string.substring(0, aRange.location) + aString + _string.substring(CPMaxRange(aRange));
|
||||
|
||||
if (startingIndex == endingIndex)
|
||||
startingRangeEntry.range.length += additionalLength;
|
||||
else
|
||||
{
|
||||
endingRangeEntry.range.length = CPMaxRange(endingRangeEntry.range) - CPMaxRange(aRange);
|
||||
endingRangeEntry.range.location = CPMaxRange(aRange);
|
||||
|
||||
startingRangeEntry.range.length = CPMaxRange(aRange) - startingRangeEntry.range.location;
|
||||
|
||||
_rangeEntries.splice(startingIndex, endingIndex - startingIndex);
|
||||
}
|
||||
|
||||
endingIndex = startingIndex + 1;
|
||||
|
||||
while(endingIndex < _rangeEntries.length)
|
||||
_rangeEntries[endingIndex++].range.location+=additionalLength;
|
||||
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
- (void)deleteCharactersInRange:(CPRange)aRange
|
||||
{
|
||||
[self replaceCharactersInRange:aRange withString:nil];
|
||||
}
|
||||
|
||||
//Changing Attributes
|
||||
- (void)setAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
|
||||
{
|
||||
[self beginEditing];
|
||||
|
||||
var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
|
||||
endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
|
||||
current = startingEntryIndex;
|
||||
|
||||
if (endingEntryIndex == CPNotFound)
|
||||
endingEntryIndex = _rangeEntries.length;
|
||||
|
||||
while (current < endingEntryIndex)
|
||||
_rangeEntries[current++].attributes = [aDictionary copy];
|
||||
|
||||
//necessary?
|
||||
[self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
|
||||
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
- (void)addAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
|
||||
{
|
||||
[self beginEditing];
|
||||
|
||||
var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
|
||||
endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
|
||||
current = startingEntryIndex;
|
||||
|
||||
if (endingEntryIndex == CPNotFound)
|
||||
endingEntryIndex = _rangeEntries.length;
|
||||
|
||||
while (current < endingEntryIndex)
|
||||
{
|
||||
var keys = [aDictionary allKeys],
|
||||
count = [keys count];
|
||||
|
||||
while (count--)
|
||||
[_rangeEntries[current].attributes setObject:[aDictionary objectForKey:keys[count]] forKey:keys[count]];
|
||||
|
||||
current++;
|
||||
}
|
||||
|
||||
//necessary?
|
||||
[self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
|
||||
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
- (void)addAttribute:(CPString)anAttribute value:(id)aValue range:(CPRange)aRange
|
||||
{
|
||||
[self addAttributes:[CPDictionary dictionaryWithObject:aValue forKey:anAttribute] range:aRange];
|
||||
}
|
||||
|
||||
- (void)removeAttribute:(CPString)anAttribute range:(CPRange)aRange
|
||||
{
|
||||
[self addAttribute:anAttribute value:nil range:aRange];
|
||||
}
|
||||
|
||||
//Changing Characters and Attributes
|
||||
- (void)appendAttributedString:(CPAttributedString)aString
|
||||
{
|
||||
[self insertAttributedString:aString atIndex:_string.length];
|
||||
}
|
||||
|
||||
- (void)insertAttributedString:(CPAttributedString)aString atIndex:(CPString)anIndex
|
||||
{
|
||||
[self beginEditing];
|
||||
|
||||
if (anIndex < 0 || anIndex > [self length])
|
||||
[CPException raise:CPRangeException reason:"tried to insert attributed string at an invalid index: "+anIndex];
|
||||
|
||||
var entryIndexOfNextEntry = [self _indexOfRangeEntryForIndex:anIndex splitOnMaxIndex:YES],
|
||||
otherRangeEntries = aString._rangeEntries,
|
||||
length = [aString length];
|
||||
|
||||
if (entryIndexOfNextEntry == CPNotFound)
|
||||
entryIndexOfNextEntry = _rangeEntries.length;
|
||||
|
||||
_string = _string.substring(0, anIndex) + aString._string + _string.substring(anIndex);
|
||||
|
||||
var current = entryIndexOfNextEntry;
|
||||
while (current < _rangeEntries.length)
|
||||
_rangeEntries[current++].range.location += length;
|
||||
|
||||
var newRangeEntryCount = otherRangeEntries.length,
|
||||
index = 0;
|
||||
|
||||
while (index < newRangeEntryCount)
|
||||
{
|
||||
var entryCopy = copyRangeEntry(otherRangeEntries[index++]);
|
||||
entryCopy.range.location += anIndex;
|
||||
|
||||
_rangeEntries.splice(entryIndexOfNextEntry-1+index, 0, entryCopy);
|
||||
}
|
||||
|
||||
//necessary?
|
||||
//[self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:startingEntryIndex+rangeEntries.length];
|
||||
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
- (void)replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPAttributedString)aString
|
||||
{
|
||||
[self beginEditing];
|
||||
|
||||
[self deleteCharactersInRange:aRange];
|
||||
[self insertAttributedString:aString atIndex:aRange.location];
|
||||
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
- (void)setAttributedString:(CPAttributedString)aString
|
||||
{
|
||||
[self beginEditing];
|
||||
|
||||
_string = aString._string;
|
||||
_rangeEntries = [];
|
||||
|
||||
for (var i=0, count = aString._rangeEntries.length; i<count; i++)
|
||||
_rangeEntries.push(copyRangeEntry(aString._rangeEntries[i]));
|
||||
|
||||
[self endEditing];
|
||||
}
|
||||
|
||||
//Private methods
|
||||
- (void)_indexOfRangeEntryForIndex:(unsigned)characterIndex splitOnMaxIndex:(BOOL)split
|
||||
{
|
||||
var index = [self _indexOfEntryWithIndex:characterIndex];
|
||||
|
||||
if (index < 0)
|
||||
return index;
|
||||
|
||||
var rangeEntry = _rangeEntries[index];
|
||||
|
||||
if (rangeEntry.range.location == characterIndex || (CPMaxRange(rangeEntry.range) - 1 == characterIndex && !split))
|
||||
return index;
|
||||
|
||||
var newEntries = splitRangeEntryAtIndex(rangeEntry, characterIndex);
|
||||
_rangeEntries.splice(index, 1, newEntries[0], newEntries[1]);
|
||||
index++;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
- (void)_coalesceRangeEntriesFromIndex:(unsigned)start toIndex:(unsigned)end
|
||||
{
|
||||
var current = start;
|
||||
|
||||
if (end >= _rangeEntries.length)
|
||||
end = _rangeEntries.length -1;
|
||||
|
||||
while (current < end)
|
||||
{
|
||||
var a = _rangeEntries[current],
|
||||
b = _rangeEntries[current+1];
|
||||
|
||||
if ([a.attributes isEqualToDictionary:b.attributes])
|
||||
{
|
||||
a.range.length = CPMaxRange(b.range) - a.range.location;
|
||||
_rangeEntries.splice(current+1, 1);
|
||||
end--;
|
||||
}
|
||||
else
|
||||
current++;
|
||||
}
|
||||
}
|
||||
|
||||
//Grouping Changes
|
||||
- (void)beginEditing
|
||||
{
|
||||
//do nothing (says cocotron and gnustep)
|
||||
}
|
||||
|
||||
- (void)endEditing
|
||||
{
|
||||
//do nothing (says cocotron and gnustep)
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPMutableAttributedString : CPAttributedString {}
|
||||
@end
|
||||
|
||||
var isEqual = function isEqual(a, b)
|
||||
{
|
||||
if (a == b)
|
||||
return YES;
|
||||
|
||||
if ([a respondsToSelector:@selector(isEqual:)] && [a isEqual:b])
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
var makeRangeEntry = function makeRangeEntry(/*CPRange*/aRange, /*CPDictionary*/attributes)
|
||||
{
|
||||
return {range:aRange, attributes:[attributes copy]};
|
||||
}
|
||||
|
||||
var copyRangeEntry = function copyRangeEntry(/*RangeEntry*/aRangeEntry)
|
||||
{
|
||||
return makeRangeEntry(CPCopyRange(aRangeEntry.range), [aRangeEntry.attributes copy]);
|
||||
}
|
||||
|
||||
var splitRangeEntry = function splitRangeEntryAtIndex(/*RangeEntry*/aRangeEntry, /*unsigned*/anIndex)
|
||||
{
|
||||
var newRangeEntry = copyRangeEntry(aRangeEntry),
|
||||
cachedIndex = CPMaxRange(aRangeEntry.range);
|
||||
|
||||
aRangeEntry.range.length = anIndex - aRangeEntry.range.location;
|
||||
newRangeEntry.range.location = anIndex;
|
||||
newRangeEntry.range.length = cachedIndex - anIndex;
|
||||
newRangeEntry.attributes = [newRangeEntry.attributes copy];
|
||||
|
||||
return [aRangeEntry, newRangeEntry];
|
||||
}
|
||||
@@ -423,7 +423,7 @@
|
||||
*/
|
||||
- (BOOL)isEqual:(id)anObject
|
||||
{
|
||||
return self === anObject;
|
||||
return self === anObject || [self hash] === [anObject hash];
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -0,0 +1,593 @@
|
||||
|
||||
import <Foundation/CPAttributedString.j>
|
||||
|
||||
var sharedObject = [CPObject new];
|
||||
|
||||
@implementation CPAttributedStringTest : OJTestCase
|
||||
|
||||
- (CPAttributedString)stringForTesting
|
||||
{
|
||||
var string = [[CPAttributedString alloc] initWithString:"The quick brown fox jumped over the lazy dog."];
|
||||
|
||||
string._rangeEntries = [];
|
||||
|
||||
string._rangeEntries.push({
|
||||
range:CPMakeRange(0, 9),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[1, "bar", sharedObject, 20] forKeys:["a", "b", "c", "d"]]
|
||||
});
|
||||
string._rangeEntries.push({
|
||||
range:CPMakeRange(9, 11),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[2, "baz", sharedObject] forKeys:["a", "b", "c"]]
|
||||
});
|
||||
string._rangeEntries.push({
|
||||
range:CPMakeRange(20, 12),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[2, "baz", "astring", [CPNull null]] forKeys:["a", "b", "c", "d"]]
|
||||
});
|
||||
string._rangeEntries.push({
|
||||
range:CPMakeRange(32, 13),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[37, "baz", 1, 20, 55, 43] forKeys:["a", "b", "c", "d", "e", "f"]]
|
||||
});
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
- (id)testInitWithString
|
||||
{
|
||||
var string = [[CPAttributedString alloc] initWithString:@"hi there"];
|
||||
|
||||
[self assertTrue:([string string] === @"hi there")
|
||||
message:"testInitWithString: expected:" + @"hi there" + " actual:" + [string string]];
|
||||
}
|
||||
- (id)testInitWithAttributedString
|
||||
{
|
||||
var string = [[CPAttributedString alloc] initWithString:@"hi there"],
|
||||
attributedString = [[CPAttributedString alloc] initWithAttributedString:string];
|
||||
|
||||
[self assertTrue:([string isEqualToAttributedString:attributedString])
|
||||
message:"testInitWithAttributedString: expected:" + string + " actual:" + attributedString];
|
||||
|
||||
//TODO add a case where we init with a string that actually adds attributes
|
||||
string = [[CPAttributedString alloc] initWithAttributedString:[self stringForTesting]];
|
||||
|
||||
[self assertTrue:([string isEqualToAttributedString:[self stringForTesting]])
|
||||
message:"testInitWithAttributedString: expected:" + [self stringForTesting] + " actual:" + string];
|
||||
}
|
||||
|
||||
- (id)testIinitWithString_attributes
|
||||
{
|
||||
var string = [[CPAttributedString alloc] initWithString:@"hi there" attributes:[CPDictionary dictionary]];
|
||||
|
||||
[self assertTrue:([string string] === @"hi there")
|
||||
message:"testIinitWithString_attributes: expected:" + @"hi there" + " actual:" + [string string]];
|
||||
|
||||
var string = [[CPAttributedString alloc] initWithString:@"hi there" attributes:[CPDictionary dictionaryWithObjects:[1, "bar"] forKeys:["number", "foo"]]];
|
||||
|
||||
[self assertTrue:([[string attributesAtIndex:0 effectiveRange:nil] objectForKey:@"number"] === 1)
|
||||
message:"testIinitWithString_attributes: value for key 'number' expected:" + 1 + " actual:" + [[string attributesAtIndex:0 effectiveRange:nil] objectForKey:@"number"]];
|
||||
|
||||
[self assertTrue:([[string attributesAtIndex:0 effectiveRange:nil] objectForKey:@"foo"] === "bar")
|
||||
message:"testIinitWithString_attributes: value for key 'foo' expected:" + "bar" + " actual:" + [[string attributesAtIndex:0 effectiveRange:nil] objectForKey:@"foo"]];
|
||||
}
|
||||
|
||||
//Retrieving Character Information
|
||||
- (CPString)testString
|
||||
{
|
||||
var string = [[CPAttributedString alloc] initWithString:@"hi there"];
|
||||
|
||||
[self assertTrue:([string string] === string._string)
|
||||
message:"testString: expected:" + @"hi there" + " actual:" + [string string]];
|
||||
}
|
||||
|
||||
- (unsigned)testLength
|
||||
{
|
||||
var string = [[CPAttributedString alloc] initWithString:@"hi there"];
|
||||
|
||||
[self assertTrue:([string length] === 8)
|
||||
message:"testLength: expected:" + 8 + " actual:" + [string length]];
|
||||
|
||||
[self assertTrue:([[self stringForTesting] length] === 45)
|
||||
message:"testLength: expected:" + 45 + " actual:" + [[self stringForTesting] length]];
|
||||
}
|
||||
|
||||
- (unsigned)test_indexOfEntryWithIndex
|
||||
{
|
||||
var string = [self stringForTesting];
|
||||
|
||||
[self assertTrue:[string _indexOfEntryWithIndex:0]===0 message:@"expecting index 0, was:"+[string _indexOfEntryWithIndex:0]];
|
||||
[self assertTrue:[string _indexOfEntryWithIndex:10]===1 message:@"expecting index 1, was:"+[string _indexOfEntryWithIndex:10]];
|
||||
[self assertTrue:[string _indexOfEntryWithIndex:30]===2 message:@"expecting index 2, was:"+[string _indexOfEntryWithIndex:30]];
|
||||
[self assertTrue:[string _indexOfEntryWithIndex:35]===3 message:@"expecting index 3, was:"+[string _indexOfEntryWithIndex:35]];
|
||||
[self assertTrue:[string _indexOfEntryWithIndex:8]===0 message:@"expecting index 0, was:"+[string _indexOfEntryWithIndex:8]];
|
||||
[self assertTrue:[string _indexOfEntryWithIndex:9]===1 message:@"expecting index 1, was:"+[string _indexOfEntryWithIndex:9]];
|
||||
[self assertTrue:[string _indexOfEntryWithIndex:20]===2 message:@"expecting index 2, was:"+[string _indexOfEntryWithIndex:20]];
|
||||
[self assertTrue:[string _indexOfEntryWithIndex:32]===3 message:@"expecting index 3, was:"+[string _indexOfEntryWithIndex:32]];
|
||||
}
|
||||
|
||||
//Retrieving Attribute Information
|
||||
- (CPDictionary)testAttributesAtIndexEffectiveRange
|
||||
{
|
||||
var string = [self stringForTesting],
|
||||
expectedValues = {a:1, b:"bar", c:sharedObject, d:20};
|
||||
|
||||
testAttributesAtIndexWithValues(string, 1, expectedValues, self);
|
||||
|
||||
expectedValues = {a:37, b:"baz", f:43}
|
||||
|
||||
testAttributesAtIndexWithValues(string, 33, expectedValues, self);
|
||||
}
|
||||
|
||||
//- (CPDictionary)attributesAtIndex:(unsigned)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
|
||||
- (CPDictionary)testAttributesAtIndexLongestEffectiveRangeInRange
|
||||
{
|
||||
var string = [self stringForTesting];
|
||||
|
||||
string._rangeEntries[3].range.length = 10;
|
||||
string._rangeEntries.push({range:CPMakeRange(42, 3), attributes:[string._rangeEntries[3].attributes copy]});
|
||||
|
||||
var range = CPMakeRange(0, 0),
|
||||
attributes = [string attributesAtIndex:35 longestEffectiveRange:range inRange:CPMakeRange(0, 43)];
|
||||
|
||||
[self assertTrue:CPMaxRange(range)==43 message:@"expecting attributes to be valuable at range 43, was: "+CPMaxRange(range)];
|
||||
[self assertTrue:range.location==32 message:@"expecting attributes to be valuable at range 32, was: "+range.location];
|
||||
|
||||
[self assertTrue:[attributes objectForKey:"a"]===37 message:@"expecting 'a' to be '1', was: "+[attributes objectForKey:"a"]];
|
||||
[self assertTrue:[attributes objectForKey:"b"]==="baz" message:@"expecting 'b' to be 'baz', was: "+[attributes objectForKey:"b"]];
|
||||
[self assertTrue:[attributes objectForKey:"f"]===43 message:@"expecting 'f' to be 43, was: "+[attributes objectForKey:"f"]];
|
||||
}
|
||||
|
||||
//- (id)attribute:(CPString)attribute atIndex:(unsigned)index effectiveRange:(CPRangePointer)aRange
|
||||
- (id)testAttributeAtIndexEffectiveRange
|
||||
{
|
||||
var string = [self stringForTesting];
|
||||
|
||||
testAttributeAtIndexWithValue(string, 1, "a", 1, self);
|
||||
testAttributeAtIndexWithValue(string, 20, "d", [CPNull null], self);
|
||||
}
|
||||
|
||||
//- (id)attribute:(CPString)attribute atIndex:(unsigned)index longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
|
||||
- (id)testAttributeAtIndexLongestEffectiveRangeInRange
|
||||
{
|
||||
var string = [self stringForTesting];
|
||||
|
||||
var range = CPMakeRange(0, 0),
|
||||
attribute = [string attribute:"b" atIndex:35 longestEffectiveRange:range inRange:CPMakeRange(0, 43)];
|
||||
|
||||
[self assertTrue:CPMaxRange(range)==43 message:@"expecting attributes to be valuable at range 43, was: "+CPMaxRange(range)];
|
||||
[self assertTrue:range.location==9 message:@"expecting attributes to be valuable at range 9, was: "+range.location];
|
||||
|
||||
[self assertTrue:attribute==="baz" message:@"expecting 'b' to be 'baz', was: "+attribute];
|
||||
}
|
||||
|
||||
//Comparing Attributed Strings
|
||||
- (BOOL)testIsEqualToAttributedString
|
||||
{
|
||||
[self assertTrue:[[self stringForTesting] isEqualToAttributedString:[self stringForTesting]] message:"expected stringForTesting to equal itself, but it didn't"];
|
||||
|
||||
[self assertFalse:[[self stringForTesting] isEqualToAttributedString:[[CPAttributedString alloc] initWithString:@"HELLO!"]] message:"Expected stringForTesting to not equal 'HELLO!', but it did"];
|
||||
}
|
||||
|
||||
- (BOOL)testIsEqual
|
||||
{
|
||||
[self assertTrue:[[self stringForTesting] isEqual:[self stringForTesting]] message:"expected stringForTesting to equal itself, but it didn't"];
|
||||
|
||||
[self assertFalse:[[self stringForTesting] isEqual:[[CPAttributedString alloc] initWithString:@"HELLO!"]] message:"Expected stringForTesting to not equal 'HELLO!', but it did"];
|
||||
|
||||
var a = [self stringForTesting];
|
||||
|
||||
[self assertTrue:[a isEqual:a] message:"expected a to equal itself, but it didn't"];
|
||||
|
||||
[self assertFalse:[a isEqual:@"HELLO!"] message:"Expected a to not equal 'HELLO!', but it did"];
|
||||
}
|
||||
|
||||
//Extracting a Substring
|
||||
//- (CPAttributedString)attributedSubstringFromRange:(CPRange)aRange
|
||||
- (CPAttributedString)testAttributedSubstringFromRange
|
||||
{
|
||||
var a = [[self stringForTesting] attributedSubstringFromRange:CPMakeRange(0, 9)],
|
||||
expectedValues = {a:1, b:"bar", c:sharedObject, d:20};
|
||||
|
||||
testAttributesAtIndexWithValues(a, 0, expectedValues, self);
|
||||
|
||||
var b = [[self stringForTesting] attributedSubstringFromRange:CPMakeRange(0, 10)],
|
||||
expectedValues = {a:1, b:"bar", c:sharedObject, d:20};
|
||||
|
||||
testAttributesAtIndexWithValues(b, 0, expectedValues, self);
|
||||
|
||||
var c = [[self stringForTesting] attributedSubstringFromRange:CPMakeRange(0, 45)];
|
||||
[self assertTrue:[c isEqual:[self stringForTesting]] message:"expected c to equal itself, but it didn't"];
|
||||
|
||||
var d = [[self stringForTesting] attributedSubstringFromRange:CPMakeRange(9, 11)],
|
||||
expectedValues = {a:2, b:"baz", c:sharedObject};
|
||||
|
||||
testAttributesAtIndexWithValues(d, 0, expectedValues, self);
|
||||
|
||||
var e = [[self stringForTesting] attributedSubstringFromRange:CPMakeRange(8, 30)],
|
||||
expectedValues = {a:2, b:"baz", c:"astring", d:[CPNull null]};
|
||||
|
||||
testAttributesAtIndexWithValues(e, 13, expectedValues, self);
|
||||
|
||||
[self assertTrue:CPEqualRanges(e._rangeEntries[0].range, CPMakeRange(0, 1)) message:"expected range to be {0, 1}, was "+CPStringFromRange(e._rangeEntries[0].range)];
|
||||
}
|
||||
|
||||
//Changing Characters
|
||||
//- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
|
||||
- (void)testReplaceCharactersInRangeWithString
|
||||
{
|
||||
var string = [self stringForTesting];
|
||||
|
||||
[string replaceCharactersInRange:CPMakeRange(10, 5) withString:"firetruck red"];
|
||||
|
||||
[self assertTrue:[string string]==="The quick firetruck red fox jumped over the lazy dog." message:"replacing 'brown' with 'firetruck red' produced: "+[string string].substr(10, 5)];
|
||||
|
||||
testAttributesAtIndexWithValues(string, 21, {a:2, b:"baz", c:sharedObject}, self);
|
||||
testAttributesAtIndexWithValues(string, 40, {a:37, b:"baz", c:1, d:20, e:55, f:43}, self);
|
||||
testAttributesAtIndexWithValues(string, [string length] - 1, {a:37, b:"baz", c:1, d:20, e:55, f:43}, self);
|
||||
}
|
||||
|
||||
//- (void)deleteCharactersInRange:(CPRange)aRange
|
||||
- (void)testDeleteCharactersInRange
|
||||
{
|
||||
var string = [self stringForTesting];
|
||||
|
||||
[string deleteCharactersInRange:CPMakeRange(10, 5)];
|
||||
|
||||
[self assertTrue:[string string]==="The quick fox jumped over the lazy dog." message:"replacing 'brown' with '' produced: "+[string string]];
|
||||
|
||||
testAttributesAtIndexWithValues(string, 10, {a:2, b:"baz", c:sharedObject}, self);
|
||||
testAttributesAtIndexWithValues(string, 27, {a:37, b:"baz", c:1, d:20, e:55, f:43}, self);
|
||||
testAttributesAtIndexWithValues(string, [string length] - 1, {a:37, b:"baz", c:1, d:20, e:55, f:43}, self);
|
||||
|
||||
[string deleteCharactersInRange:CPMakeRange(0, [string length])];
|
||||
|
||||
[self assertTrue:[string isEqual:[[CPAttributedString alloc] initWithString:""]] message:"emptry string was not equal: "+[string string]];
|
||||
|
||||
var string = [self stringForTesting];
|
||||
|
||||
//this deletes an exact rangeEntry
|
||||
[string deleteCharactersInRange:CPMakeRange(9, 11)];
|
||||
|
||||
testAttributesAtIndexWithValues(string, 9, {a:2, b:"baz", c:"astring", d:[CPNull null]}, self);
|
||||
}
|
||||
|
||||
//Private methods
|
||||
//- (void)_indexOfRangeEntryForIndex:(unsigned)characterIndex splitOnMaxIndex:(BOOL)split
|
||||
- (void)testIndexOfRangeEntryForIndexSplitOnMaxIndex
|
||||
{
|
||||
var string = [self stringForTesting];
|
||||
|
||||
var index = [string _indexOfRangeEntryForIndex:4 splitOnMaxIndex:YES];
|
||||
|
||||
[self assertTrue:index==1 message:"index of character 4 should have been 1, was: "+index];
|
||||
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[0].range, CPMakeRange(0, 4)) message:"range 0 should be {0, 4}; was "+CPStringFromRange(string._rangeEntries[0].range)];
|
||||
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[1].range, CPMakeRange(4, 5)) message:"range 1 should be {4, 5}; was "+CPStringFromRange(string._rangeEntries[1].range)];
|
||||
|
||||
[self assertTrue:[string isEqual:[self stringForTesting]] message:"should be equal to template attributed string but wasn't"];
|
||||
|
||||
string = [self stringForTesting];
|
||||
|
||||
index = [string _indexOfRangeEntryForIndex:8 splitOnMaxIndex:YES];
|
||||
|
||||
[self assertTrue:index==1 message:"index of character 8 should have been 1, was: "+index];
|
||||
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[0].range, CPMakeRange(0, 8)) message:"range 0 should be {0, 8}; was "+CPStringFromRange(string._rangeEntries[0].range)];
|
||||
|
||||
index = [string _indexOfRangeEntryForIndex:9 splitOnMaxIndex:YES];
|
||||
|
||||
[self assertTrue:index==2 message:"index of character 9 should have been 2, was: "+index];
|
||||
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[0].range, CPMakeRange(0, 8)) message:"range 0 should be {0, 8}; was "+CPStringFromRange(string._rangeEntries[0].range)];
|
||||
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[1].range, CPMakeRange(8, 1)) message:"range 1 should be {8, 1}; was "+CPStringFromRange(string._rangeEntries[1].range)];
|
||||
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[2].range, CPMakeRange(9, 11)) message:"range 2 should be {9, 11}; was "+CPStringFromRange(string._rangeEntries[2].range)];
|
||||
|
||||
string = [self stringForTesting];
|
||||
|
||||
index = [string _indexOfRangeEntryForIndex:44 splitOnMaxIndex:YES];
|
||||
|
||||
[self assertTrue:index==4 message:"index of character 44 should have been 4, was: "+index];
|
||||
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[index].range, CPMakeRange(44, 1)) message:"range 3 should be {44, 1}; was "+CPStringFromRange(string._rangeEntries[index].range)];
|
||||
|
||||
index = [string _indexOfRangeEntryForIndex:43 splitOnMaxIndex:YES];
|
||||
|
||||
[self assertTrue:index==4 message:"index of character 43 should have been 4, was: "+index];
|
||||
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[index-1].range, CPMakeRange(32, 11)) message:"range 3 should be {32, 11}; was "+CPStringFromRange(string._rangeEntries[index-1].range)];
|
||||
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[index].range, CPMakeRange(43, 1)) message:"range 4 should be {43, 1}; was "+CPStringFromRange(string._rangeEntries[index].range)];
|
||||
}
|
||||
|
||||
//- (void)_coalesceRangeEntriesFromIndex:(unsigned)start toIndex:(unsigned)end
|
||||
- (void)testCoalesceRangeEntriesFromIndexToIndex
|
||||
{
|
||||
var templateString = [[CPAttributedString alloc] initWithString:"The quick brown fox jumped over the lazy dog."];
|
||||
|
||||
templateString._rangeEntries = [];
|
||||
|
||||
templateString._rangeEntries.push({
|
||||
range:CPMakeRange(0, 4),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[1, "bar", sharedObject, 20] forKeys:["a", "b", "c", "d"]]
|
||||
});
|
||||
templateString._rangeEntries.push({
|
||||
range:CPMakeRange(4, 5),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[1, "bar", sharedObject, 20] forKeys:["a", "b", "c", "d"]]
|
||||
});
|
||||
templateString._rangeEntries.push({
|
||||
range:CPMakeRange(9, 10),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[2, "baz", sharedObject] forKeys:["a", "b", "c"]]
|
||||
});
|
||||
templateString._rangeEntries.push({
|
||||
range:CPMakeRange(19, 1),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[2, "baz", sharedObject] forKeys:["a", "b", "c"]]
|
||||
});
|
||||
templateString._rangeEntries.push({
|
||||
range:CPMakeRange(20, 12),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[2, "baz", "astring", [CPNull null]] forKeys:["a", "b", "c", "d"]]
|
||||
});
|
||||
templateString._rangeEntries.push({
|
||||
range:CPMakeRange(32, 1),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[37, "baz", 1, 20, 55, 43] forKeys:["a", "b", "c", "d", "e", "f"]]
|
||||
});
|
||||
templateString._rangeEntries.push({
|
||||
range:CPMakeRange(33, 10),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[37, "baz", 1, 20, 55, 43] forKeys:["a", "b", "c", "d", "e", "f"]]
|
||||
});
|
||||
templateString._rangeEntries.push({
|
||||
range:CPMakeRange(43, 2),
|
||||
attributes:[CPDictionary dictionaryWithObjects:[37, "baz", 1, 20, 55, 43] forKeys:["a", "b", "c", "d", "e", "f"]]
|
||||
});
|
||||
|
||||
var string = [[CPAttributedString alloc] initWithAttributedString:templateString];
|
||||
|
||||
[string _coalesceRangeEntriesFromIndex:0 toIndex:string._rangeEntries.length-1];
|
||||
|
||||
[self assertTrue:[string isEqual:templateString] message:"string should equal template string but does not."];
|
||||
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[0].range, CPMakeRange(0, 9)) message:"range 0 should be {0, 9}; was "+CPStringFromRange(string._rangeEntries[0].range)];
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[1].range, CPMakeRange(9, 11)) message:"range 1 should be {9, 11}; was "+CPStringFromRange(string._rangeEntries[1].range)];
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[2].range, CPMakeRange(20, 12)) message:"range 2 should be {20, 12}; was "+CPStringFromRange(string._rangeEntries[2].range)];
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[3].range, CPMakeRange(32, 13)) message:"range 3 should be {32, 13}; was "+CPStringFromRange(string._rangeEntries[3].range)];
|
||||
|
||||
var string = [[CPAttributedString alloc] initWithAttributedString:templateString];
|
||||
|
||||
[string _coalesceRangeEntriesFromIndex:4 toIndex:7];
|
||||
|
||||
[self assertTrue:[string isEqual:templateString] message:"string should equal template string but does not."];
|
||||
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[0].range, CPMakeRange(0, 4)) message:"range 0 should be {0, 4}; was "+CPStringFromRange(string._rangeEntries[0].range)];
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[1].range, CPMakeRange(4, 5)) message:"range 1 should be {4, 5}; was "+CPStringFromRange(string._rangeEntries[1].range)];
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[2].range, CPMakeRange(9, 10)) message:"range 2 should be {9, 10}; was "+CPStringFromRange(string._rangeEntries[2].range)];
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[3].range, CPMakeRange(19, 1)) message:"range 3 should be {19, 1}; was "+CPStringFromRange(string._rangeEntries[3].range)];
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[4].range, CPMakeRange(20, 12)) message:"range 4 should be {20, 12}; was "+CPStringFromRange(string._rangeEntries[4].range)];
|
||||
[self assertTrue:CPEqualRanges(string._rangeEntries[5].range, CPMakeRange(32, 13)) message:"range 5 should be {32, 13}; was "+CPStringFromRange(string._rangeEntries[5].range)];
|
||||
}
|
||||
|
||||
//Changing Attributes
|
||||
//- (void)setAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
|
||||
- (void)testSetAttributesRange
|
||||
{
|
||||
var string = [self stringForTesting];
|
||||
|
||||
[string setAttributes:[CPDictionary dictionary] range:CPMakeRange(0, [string length])];
|
||||
|
||||
testAttributesAtIndexWithValues(string, 10, {a:undefined, b:undefined, c:undefined, d:undefined, e:undefined, f:undefined}, self);
|
||||
|
||||
string = [self stringForTesting];
|
||||
|
||||
[string setAttributes:[CPDictionary dictionaryWithObjects:[1, 2, 3, 4, 5] forKeys:["a", "b", "c", "d", "e"]] range:CPMakeRange(0, [string length])];
|
||||
|
||||
testAttributesAtIndexWithValues(string, 10, {a:1, b:2, c:3, d:4, e:5}, self);
|
||||
|
||||
var range = CPMakeRange(0, 0);
|
||||
|
||||
[string setAttributes:[CPDictionary dictionaryWithObject:"FOO" forKey:"BAR"] range:CPMakeRange(15, 20)];
|
||||
|
||||
var value = [string attribute:"BAR" atIndex:16 longestEffectiveRange:range inRange:CPMakeRange(0, 45)];
|
||||
|
||||
[self assertTrue:value==="FOO" message:"expected value to be 'FOO', was: "+value];
|
||||
[self assertTrue:CPEqualRanges(range, CPMakeRange(15, 20)) message:"expected key to be valid across {15, 20}, was: "+CPStringFromRange(range)];
|
||||
|
||||
var index = range.location;
|
||||
while(index < CPMaxRange(range))
|
||||
[self assertTrue:[string attribute:"BAR" atIndex:index++ effectiveRange:nil]=="FOO" message:"incorrect value for key BAR"];
|
||||
|
||||
string = [self stringForTesting];
|
||||
|
||||
[string addAttributes:[CPDictionary dictionaryWithObject:"FOO" forKey:"BAR"] range:CPMakeRange(0, 45)];
|
||||
|
||||
var value = [string attribute:"BAR" atIndex:16 longestEffectiveRange:range inRange:CPMakeRange(0, 45)];
|
||||
|
||||
[self assertTrue:value==="FOO" message:"expected value to be 'FOO', was: "+value];
|
||||
[self assertTrue:CPEqualRanges(range, CPMakeRange(0, 45)) message:"expected key to be valid across {0, 45}, was: "+CPStringFromRange(range)];
|
||||
|
||||
var index = range.location;
|
||||
while(index < CPMaxRange(range))
|
||||
[self assertTrue:[string attribute:"BAR" atIndex:index++ effectiveRange:nil]=="FOO" message:"incorrect value for key BAR"];
|
||||
}
|
||||
|
||||
//- (void)addAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
|
||||
- (void)testAddAttributesRange
|
||||
{
|
||||
var string = [self stringForTesting],
|
||||
range = CPMakeRange(0, 0);
|
||||
|
||||
[string addAttributes:[CPDictionary dictionaryWithObject:"FOO" forKey:"BAR"] range:CPMakeRange(15, 20)];
|
||||
|
||||
var value = [string attribute:"BAR" atIndex:16 longestEffectiveRange:range inRange:CPMakeRange(0, 45)];
|
||||
|
||||
[self assertTrue:value==="FOO" message:"expected value to be 'FOO', was: "+value];
|
||||
[self assertTrue:CPEqualRanges(range, CPMakeRange(15, 20)) message:"expected key to be valid across {15, 20}, was: "+CPStringFromRange(range)];
|
||||
|
||||
var index = range.location;
|
||||
while(index < CPMaxRange(range))
|
||||
[self assertTrue:[string attribute:"BAR" atIndex:index++ effectiveRange:nil]=="FOO" message:"incorrect value for key BAR"];
|
||||
|
||||
string = [self stringForTesting];
|
||||
|
||||
[string addAttributes:[CPDictionary dictionaryWithObjects:[1, 2, 3, 4, 5] forKeys:["a", "b", "c", "d", "e"]] range:CPMakeRange(0, [string length])];
|
||||
|
||||
testAttributesAtIndexWithValues(string, 10, {a:1, b:2, c:3, d:4, e:5}, self);
|
||||
|
||||
string = [self stringForTesting];
|
||||
|
||||
[string addAttributes:[CPDictionary dictionaryWithObject:"FOO" forKey:"BAR"] range:CPMakeRange(0, 45)];
|
||||
|
||||
var value = [string attribute:"BAR" atIndex:16 longestEffectiveRange:range inRange:CPMakeRange(0, 45)];
|
||||
|
||||
[self assertTrue:value==="FOO" message:"expected value to be 'FOO', was: "+value];
|
||||
[self assertTrue:CPEqualRanges(range, CPMakeRange(0, 45)) message:"expected key to be valid across {0, 45}, was: "+CPStringFromRange(range)];
|
||||
|
||||
var index = range.location;
|
||||
while(index < CPMaxRange(range))
|
||||
[self assertTrue:[string attribute:"BAR" atIndex:index++ effectiveRange:nil]=="FOO" message:"incorrect value for key BAR"];
|
||||
}
|
||||
|
||||
//- (void)addAttribute:(CPString)anAttribute value:(id)aValue range:(CPRange)aRange
|
||||
- (void)testAddAttributeValueRange
|
||||
{
|
||||
var string = [self stringForTesting];
|
||||
|
||||
[string addAttribute:"duck" value:"goose" range:CPMakeRange(10, 30)];
|
||||
|
||||
testAttributeAtIndexWithValue(string, 10, "duck", "goose", self);
|
||||
testAttributeAtIndexWithValue(string, 30, "duck", "goose", self);
|
||||
testAttributeAtIndexWithValue(string, 40, "duck", undefined, self);
|
||||
|
||||
}
|
||||
|
||||
//- (void)removeAttribute:(CPString)anAttribute range:(CPRange)aRange
|
||||
- (void)testRemoveAttributeRange
|
||||
{
|
||||
var string = [self stringForTesting];
|
||||
|
||||
[string removeAttribute:"a" range:CPMakeRange(10, 30)];
|
||||
|
||||
testAttributeAtIndexWithValue(string, 10, "a", null, self);
|
||||
testAttributeAtIndexWithValue(string, 30, "a", null, self);
|
||||
testAttributeAtIndexWithValue(string, 40, "a", 37, self);
|
||||
}
|
||||
|
||||
//Changing Characters and Attributes
|
||||
//- (void)appendAttributedString:(CPAttributedString)aString
|
||||
- (void)testAppendAttributedString
|
||||
{
|
||||
var string = [self stringForTesting],
|
||||
addOn = [string attributedSubstringFromRange:CPMakeRange(41, 3)];
|
||||
|
||||
[string appendAttributedString:addOn];
|
||||
[string appendAttributedString:addOn];
|
||||
|
||||
[self assertTrue:[string string]==="The quick brown fox jumped over the lazy dog.dogdog" message:"wrong string after appending"];
|
||||
|
||||
var range = CPMakeRange(0, 0),
|
||||
value = [string attribute:"c" atIndex:47 longestEffectiveRange:range inRange:CPMakeRange(0, [string length])];
|
||||
|
||||
[self assertTrue:value===1 message:"expected value to be '1', was: "+value];
|
||||
[self assertTrue:CPEqualRanges(range, CPMakeRange(32, 13+3+3)) message:"expected key to be valid across {32, 19}, was: "+CPStringFromRange(range)];
|
||||
}
|
||||
|
||||
//- insertAttributedString:(CPAttributedString)aString atIndex:(CPString)anIndex
|
||||
- (void)testInsertAttributedStringAtIndex
|
||||
{
|
||||
var string = [self stringForTesting],
|
||||
addOn = [[self stringForTesting] attributedSubstringFromRange:CPMakeRange(41, 3)];
|
||||
|
||||
[string insertAttributedString:addOn atIndex:[string length]];
|
||||
|
||||
testAttributesAtIndexWithValues(string, 46, {a:37, b:"baz", c:1, d:20, e:55, f:43}, self);
|
||||
|
||||
string = [self stringForTesting];
|
||||
|
||||
addOn = [[self stringForTesting] attributedSubstringFromRange:CPMakeRange(41, 3)];
|
||||
|
||||
[string insertAttributedString:addOn atIndex:9];
|
||||
|
||||
testAttributesAtIndexWithValues(string, 9, {a:37, b:"baz", c:1, d:20, e:55, f:43}, self);
|
||||
|
||||
addOn = [[self stringForTesting] attributedSubstringFromRange:CPMakeRange(41, 3)];
|
||||
|
||||
[string insertAttributedString:addOn atIndex:8];
|
||||
|
||||
testAttributesAtIndexWithValues(string, 8, {a:37, b:"baz", c:1, d:20, e:55, f:43}, self);
|
||||
|
||||
var range = CPMakeRange(0, 0),
|
||||
value = [string attribute:"a" atIndex:9 longestEffectiveRange:range inRange:CPMakeRange(0, 45)];
|
||||
|
||||
[self assertTrue:value===37 message:"expected value to be '37', was: "+value];
|
||||
[self assertTrue:CPEqualRanges(range, CPMakeRange(8, 3)) message:"expected key to be valid across {8, 3}, was: "+CPStringFromRange(range)];
|
||||
|
||||
value = [string attribute:"a" atIndex:11 longestEffectiveRange:range inRange:CPMakeRange(0, 45)];
|
||||
|
||||
[self assertTrue:value===1 message:"expected value to be '37', was: "+value];
|
||||
[self assertTrue:CPEqualRanges(range, CPMakeRange(11, 1)) message:"expected key to be valid across {11, 1}, was: "+CPStringFromRange(range)];
|
||||
}
|
||||
|
||||
//- (void)replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPAttributedString)aString
|
||||
- (void)testReplaceCharactersInRangeWithAttributedString
|
||||
{
|
||||
var string = [self stringForTesting],
|
||||
addOn = [string attributedSubstringFromRange:CPMakeRange(41, 3)];
|
||||
|
||||
[string replaceCharactersInRange:CPMakeRange(41, 3) withAttributedString:addOn];
|
||||
|
||||
[self assertTrue:[string string]===[[self stringForTesting] string] message:"wrong string after replacing."];
|
||||
|
||||
var range = CPMakeRange(0, 0),
|
||||
value = [string attribute:"c" atIndex:42 longestEffectiveRange:range inRange:CPMakeRange(0, [string length])];
|
||||
|
||||
[self assertTrue:value===1 message:"expected value to be '1', was: "+value];
|
||||
[self assertTrue:CPEqualRanges(range, CPMakeRange(32, 13)) message:"expected key to be valid across {32, 13}, was: "+CPStringFromRange(range)];
|
||||
}
|
||||
|
||||
//- (void)setAttributedString:(CPAttributedString)aString
|
||||
- (void)testSetAttributedString
|
||||
{
|
||||
var string = [[CPAttributedString alloc] initWithString:"HELLO THERE"];
|
||||
[string setAttributedString:[self stringForTesting]];
|
||||
|
||||
[self assertTrue:[[self stringForTesting] isEqual:string] message:"setAttributedString should have made strings equal, but they were not"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
function testAttributesAtIndexWithValues(aString, anIndex, values, aSelf)
|
||||
{
|
||||
var range = CPMakeRange(0, 0),
|
||||
attributes = [aString attributesAtIndex:anIndex effectiveRange:range];
|
||||
|
||||
for(key in values)
|
||||
[aSelf assertTrue:[attributes objectForKey:key] === values[key] message: "expecting '"+key+"' to be '"+values[key]+"', was '"+[attributes objectForKey:key]];
|
||||
|
||||
var index = range.location;
|
||||
while(index < CPMaxRange(range))
|
||||
{
|
||||
attributes = [aString attributesAtIndex:index++ effectiveRange:nil];
|
||||
|
||||
for(key in values)
|
||||
[aSelf assertTrue:[attributes objectForKey:key] === values[key] message: "expecting '"+key+"' in loop to be '"+values[key]+"', was '"+[attributes objectForKey:key]];
|
||||
}
|
||||
}
|
||||
|
||||
function testAttributeAtIndexWithValue(aString, anIndex, aKey, aValue, aSelf)
|
||||
{
|
||||
var range = CPMakeRange(0, 0),
|
||||
attribute = [aString attribute:aKey atIndex:anIndex effectiveRange:range];
|
||||
|
||||
[aSelf assertTrue: attribute === aValue message: "expecting '"+aKey+"' to be '"+aValue+"', was '"+attribute];
|
||||
|
||||
var index = range.location;
|
||||
while(index < CPMaxRange(range))
|
||||
{
|
||||
attribute = [aString attribute:aKey atIndex:index++ effectiveRange:nil];
|
||||
|
||||
[aSelf assertTrue: attribute === aValue message: "expecting '"+aKey+"' to be '"+aValue+"', was '"+attribute];
|
||||
}
|
||||
}
|
||||
|
||||
function printRangeEntry(entry)
|
||||
{
|
||||
print("range: "+CPStringFromRange(entry.range)+" "+[entry.attributes description]);
|
||||
}
|
||||
Reference in New Issue
Block a user