From ae2ed8bf9458e4fc24fa87fda636efca522f1d3e Mon Sep 17 00:00:00 2001 From: Aparajita Fishman Date: Tue, 26 Feb 2013 20:15:04 -0500 Subject: [PATCH] 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 --- Foundation/CPAttributedString.j | 105 ++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/Foundation/CPAttributedString.j b/Foundation/CPAttributedString.j index bbefafc6b..2b3c357c1 100644 --- a/Foundation/CPAttributedString.j +++ b/Foundation/CPAttributedString.j @@ -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);