Merge branch 'master' of git@github.com:280north/cappuccino

This commit is contained in:
Tom Robinson
2008-11-13 21:27:29 -08:00
4 changed files with 1192 additions and 3 deletions
+2 -2
View File
@@ -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)
{
+596
View File
@@ -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];
}
+1 -1
View File
@@ -423,7 +423,7 @@
*/
- (BOOL)isEqual:(id)anObject
{
return self === anObject;
return self === anObject || [self hash] === [anObject hash];
}
/*!
+593
View File
@@ -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]);
}