mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user