Fixed: empty strings and indexes out of range were not handled

Previously CPAttributedString did not provide an -init method,
with the result that an attributed string created with [[CPAttributedString alloc] init]
would not be correctly initialized.

Also, some methods that took indexes into the string were not checking for an invalid index and were not raising exceptions when the documentation said they should.

Finally, some methods that were supposed to return empty dictionaries were returning nil.

This commit fixes all of these problems. Empty strings should no longer cause errors.

Closes #533
This commit is contained in:
Aparajita Fishman
2013-02-26 20:15:15 -05:00
parent 395d140c76
commit ae2ed8bf94
+65 -40
View File
@@ -50,6 +50,15 @@
}
// Creating a CPAttributedString Object
/*!
Creates an empty attributed string.
@return a new empty CPAttributedString.
*/
- (id)init
{
return [self initWithString:@"" attributes:nil];
}
/*!
Creates a new attributed string from a character string.
@param aString is the string to initialise from.
@@ -67,7 +76,7 @@
*/
- (id)initWithAttributedString:(CPAttributedString)aString
{
var string = [self initWithString:"" attributes:nil];
var string = [self initWithString:@"" attributes:nil];
[string setAttributedString:aString];
@@ -91,7 +100,7 @@
if (!attributes)
attributes = @{};
_string = ""+aString;
_string = aString;
_rangeEntries = [makeRangeEntry(CPMakeRange(0, _string.length), attributes)];
}
@@ -135,10 +144,11 @@
if (anIndex < 0 || anIndex > _string.length || anIndex === undefined)
return CPNotFound;
//find the range entry that contains anIndex.
// 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
// 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)
@@ -166,18 +176,19 @@
same as those at index, \c anIndex. If not required pass
\c nil.
@return a CPDictionary containing the attributes associated with the
character at index \c anIndex. Returns \c nil if index
character at index \c anIndex. Returns an empty dictionary if index
is out of bounds.
*/
- (CPDictionary)attributesAtIndex:(unsigned)anIndex effectiveRange:(CPRangePointer)aRange
{
//find the range entry that contains anIndex.
// find the range entry that contains anIndex.
var entryIndex = [self _indexOfEntryWithIndex:anIndex];
if (entryIndex == CPNotFound)
return nil;
if (entryIndex === CPNotFound)
return @{};
var matchingRange = _rangeEntries[entryIndex];
if (aRange)
{
aRange.location = matchingRange.range.location;
@@ -205,15 +216,15 @@
@param rangeLimit a range limiting the search for the attributes' applicable
range.
@return a CPDictionary containing the attributes associated with the
character at index \c anIndex. Returns \c nil if index
character at index \c anIndex. Returns an empty dictionary if index
is out of bounds.
*/
- (CPDictionary)attributesAtIndex:(unsigned)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
{
var startingEntryIndex = [self _indexOfEntryWithIndex:anIndex];
if (startingEntryIndex == CPNotFound)
return nil;
if (startingEntryIndex === CPNotFound)
return @{};
if (!aRange)
return _rangeEntries[startingEntryIndex].attributes;
@@ -226,7 +237,7 @@
return _rangeEntries[startingEntryIndex].attributes;
}
//scan backwards
// scan backwards
var nextRangeIndex = startingEntryIndex - 1,
currentEntry = _rangeEntries[startingEntryIndex],
comparisonDict = currentEntry.attributes;
@@ -246,7 +257,7 @@
aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
//scan forwards
// scan forwards
currentEntry = _rangeEntries[startingEntryIndex];
nextRangeIndex = startingEntryIndex + 1;
@@ -323,10 +334,9 @@
*/
- (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)
if (startingEntryIndex === CPNotFound || !attribute)
return nil;
if (!aRange)
@@ -340,7 +350,7 @@
return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
}
//scan backwards
// scan backwards
var nextRangeIndex = startingEntryIndex - 1,
currentEntry = _rangeEntries[startingEntryIndex],
comparisonAttribute = [currentEntry.attributes objectForKey:attribute];
@@ -360,7 +370,7 @@
aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
//scan forwards
// scan forwards
currentEntry = _rangeEntries[startingEntryIndex];
nextRangeIndex = startingEntryIndex + 1;
@@ -394,7 +404,7 @@
if (!aString)
return NO;
if (_string != [aString string])
if (_string !== [aString string])
return NO;
var myRange = CPMakeRange(),
@@ -405,9 +415,12 @@
while (CPMaxRange(CPUnionRange(myRange, comparisonRange)) < length)
{
if (CPIntersectionRange(myRange, comparisonRange).length > 0 && ![myAttributes isEqualToDictionary:comparisonAttributes])
if (CPIntersectionRange(myRange, comparisonRange).length > 0 &&
![myAttributes isEqualToDictionary:comparisonAttributes])
{
return NO;
if (CPMaxRange(myRange) < CPMaxRange(comparisonRange))
}
else if (CPMaxRange(myRange) < CPMaxRange(comparisonRange))
myAttributes = [self attributesAtIndex:CPMaxRange(myRange) effectiveRange:myRange];
else
comparisonAttributes = [aString attributesAtIndex:CPMaxRange(comparisonRange) effectiveRange:comparisonRange];
@@ -425,7 +438,7 @@
*/
- (BOOL)isEqual:(id)anObject
{
if (anObject == self)
if (anObject === self)
return YES;
if ([anObject isKindOfClass:[self class]])
@@ -449,8 +462,12 @@
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],
entryIndex = [self _indexOfEntryWithIndex:aRange.location];
if (entryIndex === CPNotFound)
_CPRaiseRangeException(self, _cmd, aRange.location, _string.length);
var currentRangeEntry = _rangeEntries[entryIndex],
lastIndex = CPMaxRange(aRange);
newString._rangeEntries = [];
@@ -507,17 +524,25 @@
- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
{
if (!aString)
aString = "";
aString = @"";
var startingIndex = [self _indexOfEntryWithIndex:aRange.location],
startingRangeEntry = _rangeEntries[startingIndex],
endingIndex = [self _indexOfEntryWithIndex:MAX(CPMaxRange(aRange) - 1, 0)],
endingRangeEntry = _rangeEntries[endingIndex],
var startingIndex = [self _indexOfEntryWithIndex:aRange.location];
if (startingIndex === CPNotFound)
_CPRaiseRangeException(self, _cmd, aRange.location, _string.length);
var startingRangeEntry = _rangeEntries[startingIndex],
endingIndex = [self _indexOfEntryWithIndex:MAX(CPMaxRange(aRange) - 1, 0)];
if (endingIndex === CPNotFound)
_CPRaiseRangeException(self, _cmd, MAX(CPMaxRange(aRange) - 1, 0), _string.length);
var endingRangeEntry = _rangeEntries[endingIndex],
additionalLength = aString.length - aRange.length;
_string = _string.substring(0, aRange.location) + aString + _string.substring(CPMaxRange(aRange));
if (startingIndex == endingIndex)
if (startingIndex === endingIndex)
startingRangeEntry.range.length += additionalLength;
else
{
@@ -562,7 +587,7 @@
endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
current = startingEntryIndex;
if (endingEntryIndex == CPNotFound)
if (endingEntryIndex === CPNotFound)
endingEntryIndex = _rangeEntries.length;
while (current < endingEntryIndex)
@@ -588,7 +613,7 @@
endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
current = startingEntryIndex;
if (endingEntryIndex == CPNotFound)
if (endingEntryIndex === CPNotFound)
endingEntryIndex = _rangeEntries.length;
while (current < endingEntryIndex)
@@ -635,7 +660,7 @@
endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
current = startingEntryIndex;
if (endingEntryIndex == CPNotFound)
if (endingEntryIndex === CPNotFound)
endingEntryIndex = _rangeEntries.length;
while (current < endingEntryIndex)
@@ -674,7 +699,7 @@
otherRangeEntries = aString._rangeEntries,
length = [aString length];
if (entryIndexOfNextEntry == CPNotFound)
if (entryIndexOfNextEntry === CPNotFound)
entryIndexOfNextEntry = _rangeEntries.length;
_string = _string.substring(0, anIndex) + aString._string + _string.substring(anIndex);
@@ -734,12 +759,12 @@
{
var index = [self _indexOfEntryWithIndex:characterIndex];
if (index < 0)
if (index === CPNotFound)
return index;
var rangeEntry = _rangeEntries[index];
if (rangeEntry.range.location == characterIndex || (CPMaxRange(rangeEntry.range) - 1 == characterIndex && !split))
if (rangeEntry.range.location === characterIndex || (CPMaxRange(rangeEntry.range) - 1 === characterIndex && !split))
return index;
var newEntries = splitRangeEntryAtIndex(rangeEntry, characterIndex);
@@ -805,9 +830,9 @@
@end
var isEqual = function isEqual(a, b)
var isEqual = function(a, b)
{
if (a == b)
if (a === b)
return YES;
if ([a respondsToSelector:@selector(isEqual:)] && [a isEqual:b])
@@ -816,17 +841,17 @@ var isEqual = function isEqual(a, b)
return NO;
};
var makeRangeEntry = function makeRangeEntry(/*CPRange*/aRange, /*CPDictionary*/attributes)
var makeRangeEntry = function(/*CPRange*/aRange, /*CPDictionary*/attributes)
{
return {range:aRange, attributes:[attributes copy]};
};
var copyRangeEntry = function copyRangeEntry(/*RangeEntry*/aRangeEntry)
var copyRangeEntry = function(/*RangeEntry*/aRangeEntry)
{
return makeRangeEntry(CPMakeRangeCopy(aRangeEntry.range), [aRangeEntry.attributes copy]);
};
var splitRangeEntryAtIndex = function splitRangeEntryAtIndex(/*RangeEntry*/aRangeEntry, /*unsigned*/anIndex)
var splitRangeEntryAtIndex = function(/*RangeEntry*/aRangeEntry, /*unsigned*/anIndex)
{
var newRangeEntry = copyRangeEntry(aRangeEntry),
cachedIndex = CPMaxRange(aRangeEntry.range);