#cocoa-to-cappuccino
I found working with strings in Cappuccino to be more logical than in Cocoa, but I experienced some uncertainty about what methods and functions were available in JavaScript. So I thought this example and writeup might be useful to others, who are coming to Cappuccino from Cocoa, as many of us are.
One of the main differences between Cocoa and Cappuccino when working with strings, is that Cappuccino lacks a scanner class. So Cappuccino applications must handle scanning by themselves. I thought it would be useful and illustrative to implement a Category that performs scanning, similar to Cocoa’s NSScanner. And since JavaScript strings are toll-free bridged to Cappuccino’s CPString class that was not too difficult.
I’ve constructed my API as a Category, rather than as a Subclass, so it can be used with all CPStrings. It corresponds roughly to NSScanner, but doesn’t mirror it.
I’ve maintained a Cocoa-like style. This may change as time goes by, but for now it feels more readable, especially since I’m going back and forth between Cappuccino and Cocoa.
And I haven’t addressed performance issues in writing this. The code is adequate for my purposes. But it would be interesting to profile and optimize it somewhere down the line.
IMPLEMENTATION NOTE: I’m not currently supporting “skip characters.” That’s because the next scan position is determined by the length of the previously scanned string. If you skip characters there isn’t a simple way (that I could think of) to communicate this to the next use of the the scanner. Also all scans are case sensitive. An extended scanning category could add methods to support these. But for now, I’m avoiding that to keep things simple.
If you’re not familiar with NSScanner, it’s useful to note that the term “scan” means scanning from a particular starting point. That is, if you’re scanning for a particular string and it isn’t at the starting location of the string being scanned, then an empty string will be returned.
I’ll summarize the API here, but it will be much more instructive to view the source, which I’ve wrapped in a test program called cappscanner.
SCANNING METHODS
scanString – scans SELF, returning theString if a match is found.
-(CPString)scanString:(CPString)theString startingAt:(int)startIndex
scanUpToString – scans SELF until a given string is encountered, accumulating characters into a string that’s returned. Scans to the end of SELF if stopString is not found.
- (CPString) scanUpToString:(CPString)stopString startingAt:(int)startIndex
scanUpToCharactersFromSet – scans SELF until a stopChar is encountered, accumulating characters into a string that’s returned. Scans to the end of SELF if no stopChars are found.
-(CPString)scanUpToCharactersFromSet:(CPString)stopChars startIndex:(int)index
scanCharactersFromSet – scans SELF as long as charsToScan are encountered, accumulating characters into a string that’s returned. Returns an empty string if no charsToScan are found.
-(CPString)scanCharactersFromSet:(CPString)charsToScan startIndex:(int)index
UTILITY METHODS
stringByReplacingString – replaces “target” with “replacement”, where “target” is a substring of SELF.
- (CPString)stringByReplacingString:(CPString)target withString:(CPString)replacement
setCharacterAtIndex – replaces the character at “index” in SELF.
-(CPString)setCharacterAtIndex:(unsigned)index theChar:(unichar)character
filterString – returns a copy of SELF filtering out the specified characters.
-(CPString)filterString:(CPString)charactersToRemove
stripPrefix – returns a copy of SELF without thePrefix. Does nothing if SELF doesn’t have thePrefix
-(CPString)stripPrefix:(CPString)thePrefix
stripSuffix – returns a copy of SELF without theSuffix. Does nothing if SELF doesn’t have theSuffix.
-(CPString)stripSuffix:(CPString)theSuffix
dropCharacters – drops numCharsToDrop from the end of SELF. Does nothing if charsToDrop > [string length]. Returns an empty string if numCharsToDrop == [string length]
-(CPString)dropCharacters:(int)numCharsToDrop
TEST RELATED METHODS
decimalTail – returns the tail of a decimal string.
-(CPString)decimalTail
formatNodecString – replaces the period in a formatted decimal string with a single space character.
-(CPString)formatNodecString
formatNosepString – removes commas and the period from a formatted decimal string.
-(CPString)formatNosepString
removeSurroundingParentheses – strips any leading or trailing spaces too. N.B. Won’t remove an odd parenthesis on one end!
-(CPString)removeSurroundingParentheses
parseScript – parses the if, then and else components of a script string. Notice how the scanner walks down the script using the cumulative offset of previously scanned components. This is illustrative of a repetitive scanning pattern.
-(void)parseScript
rectFromAnnot – converts the RECT string from a pdf annotation (e.g. RECT [432.97 580.92 441.86 589.95]) into a CGRect.
-(CGRect)rectFromAnnot
scanRect – scans the RECT string found in pdf annotations. Returns an array of strings for the left, bottom, right and top coordinates. Note that scanRect also employs a repetitive scanning pattern.
-(CPArray)scanRect
tokensSeparatedByCharactersFromSet – breaks the input string into an array of substrings.
-(CPArray)tokensSeparatedByCharactersFromSet:(CPString)separatorSet
SCANNING TESTS
When you double-click the index.html file and then click the “Perform Scan Tests” button in the Scan Tests window, a Cappuccino Run Log Window will appear that contains these statements:
Performing scan tests.
Testing Scanning Methods.
Scan if-then-else script.
script is =IF(L222<12000;3500*L106e;0)
scriptIf = L222<12000
scriptThen = 3500*L106e
scriptElse = 0
Scan pdf style Rect.
Build CGRect.
left = 432.97
bottom = 580.92
right = 441.86
top = 589.95
Testing Utility Methods.
Test stringByReplacingString
string A plus string B
string A + string B
Test setCharacterAtIndex
1234567
A2C4E6G
Test stripPrefix
Mr. Coffee
Coffee
Test stripSuffix
String Jr.
String
Test dropCharacters
123.45
123
Testing Test Related Methods.
Test decimalTail
1,099.87
87
Test formatNodecString
1,099.87
1,099 87
Test formatNosepString
1,099.87
109987
Scan tests complete.
“It works”
My thanks to the Cappuccino Community, and especially to the Core Team.