source: trunk/ICeCoffEE/ICeCoffEE/ICeCoffEEParser.m @ 375

Last change on this file since 375 was 375, checked in by Nicholas Riley, 12 years ago

ICeCoffEE.[hm]: Restore ICCF_CheckRange, moved in [322], as we don't
want the range limited in TestParser?.

ICeCoffEEParser.m: Remove ICCF_CheckRange. Comment out expandFront
URL/parens stuff; it's as yet untested. Handle {...} in URLs. Don't
assert when )/} is last character in string. Fix indentation.

ICeCoffEE.xcodeproj: Link TestParser? to Cocoa normally. Remove
obsolete build settings.

TestParser?.m: Stub out ICCF_CheckRange. Test beginning with
one-character ranges all the way through the string. Output summary
stats at end.

File size: 8.1 KB
Line 
1//
2//  ICeCoffEEParser.m
3//  ICeCoffEE
4//
5//  Created by Nicholas Riley on 6/21/07.
6//  Copyright 2007 Nicholas Riley. All rights reserved.
7//
8
9#import "ICeCoffEEParser.h"
10#import "ICeCoffEE.h"
11
12void ICCF_Delimiters(NSCharacterSet **leftPtr, NSCharacterSet **rightPtr) {
13    static NSCharacterSet *urlLeftDelimiters = nil, *urlRightDelimiters = nil;
14   
15    if (urlLeftDelimiters == nil || urlRightDelimiters == nil) {
16        NSMutableCharacterSet *set = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
17        NSMutableCharacterSet *tmpSet;
18        [urlLeftDelimiters release];
19        [urlRightDelimiters release];
20       
21        [set autorelease];
22        [set formUnionWithCharacterSet: [[NSCharacterSet characterSetWithRange: NSMakeRange(0x21, 0x5e)] invertedSet]]; // nonprintable and non-ASCII characters
23        [set formUnionWithCharacterSet: [NSCharacterSet punctuationCharacterSet]];
24        // XXX obsoleted by RFC 3986 now... use §2.1, 2.2, 2.3
25        [set removeCharactersInString: @";/?:@&=+$,-_.!~*'()%#"]; // RFC 2396 §2.2, 2.3, 2.4, plus % and # from "delims" set
26       
27        tmpSet = [[set mutableCopy] autorelease];
28        [tmpSet formUnionWithCharacterSet: [NSCharacterSet characterSetWithCharactersInString: @"><("]];
29        urlLeftDelimiters = [tmpSet copy]; // make immutable again - for efficiency
30       
31        tmpSet = [[set mutableCopy] autorelease];
32        [tmpSet formUnionWithCharacterSet: [NSCharacterSet characterSetWithCharactersInString: @"><)"]];
33        urlRightDelimiters = [tmpSet copy]; // make immutable again - for efficiency
34    }
35   
36    *leftPtr = urlLeftDelimiters; *rightPtr = urlRightDelimiters;
37}
38
39static ICInstance ICCF_icInst = NULL;
40
41void ICCF_StartIC() {
42    OSStatus err;
43   
44    if (ICCF_icInst != NULL) {
45        ICLog(@"ICCF_StartIC: Internet Config is already running!");
46        ICCF_StopIC();
47    }
48    err = ICStart(&ICCF_icInst, kICCFCreator);
49    NSCAssert1(err == noErr, ICCF_LocalizedString(@"Unable to start Internet Config (error %d)"), err);
50}
51
52void ICCF_StopIC() {
53    if (ICCF_icInst == NULL) {
54        ICLog(@"ICCF_StopIC: Internet Config is not running!");
55    } else {
56        ICStop(ICCF_icInst);
57        ICCF_icInst = NULL;
58    }
59}
60
61ICInstance ICCF_GetInst() {
62    NSCAssert(ICCF_icInst != NULL, @"Internal error: Called ICCF_GetInst without ICCF_StartIC");
63    return ICCF_icInst;
64}
65
66// input/output 'range' is the range of source document which contains 'string'
67void ICCF_ParseURL(NSString *string, NSRange *range) {
68    OSStatus err;
69    Handle h;
70    long selStart = 0, selEnd = range->length; // local offsets within 'string'
71    char *urlData = NULL;
72   
73    NSCAssert(selEnd == [string length], @"Internal error: URL string is wrong length");
74   
75    NS_DURING
76        if ([[NSCharacterSet characterSetWithCharactersInString: @";,."] characterIsMember:
77            [string characterAtIndex: selEnd - 1]]) {
78            selEnd--;
79        }
80        NSCharacterSet *alphanumericCharacterSet = [NSCharacterSet alphanumericCharacterSet];
81        while (![alphanumericCharacterSet characterIsMember: [string characterAtIndex: selStart]]) {
82            selStart++;
83            NSCAssert(selStart < selEnd, @"No URL is selected");
84        }
85       
86        string = [string substringWithRange: NSMakeRange(selStart, selEnd - selStart)];
87       
88        ICLog(@"Parsing URL |%@|", string);
89       
90        NSCAssert([string canBeConvertedToEncoding: NSASCIIStringEncoding], @"No URL is selected");
91       
92        urlData = (char *)malloc( (range->length + 1) * sizeof(char));
93        NSCAssert(urlData != NULL, @"Internal error: can't allocate memory for URL string");
94       
95        // XXX getCString: is deprecated in 10.4, but this is safe and shouldn't assert because we've already verified the string can be converted to ASCII, which should be a subset of any possible system encoding.  The replacement (getCString:maxLength:encoding:) is not available until 10.4, so we leave this until we dump Internet Config and gain IDN friendliness.
96        [string getCString: urlData];
97       
98        h = NewHandle(0);
99        NSCAssert(h != NULL, @"Internal error: can't allocate URL handle");
100       
101        err = ICParseURL(ICCF_GetInst(), "\pmailto", urlData, range->length, &selStart, &selEnd, h);
102        DisposeHandle(h);
103       
104        ICCF_OSErrCAssert(err, @"ICParseURL");
105       
106        range->length = selEnd - selStart;
107        range->location += selStart;
108    NS_HANDLER
109        free(urlData);
110        [localException raise];
111    NS_ENDHANDLER
112   
113    free(urlData);
114}
115
116NSRange ICCF_URLEnclosingRange(NSString *s, NSRange range) {
117    NSCharacterSet *urlLeftDelimiters = nil, *urlRightDelimiters = nil;
118    NSRange delimiterRange;
119    unsigned extraLen;
120   
121    ICCF_CheckRange(range);
122   
123    ICCF_Delimiters(&urlLeftDelimiters, &urlRightDelimiters);
124   
125expandFront:
126    // XXX instead of 0, make this stop at the max URL length to prevent protracted searches
127   
128    // XXX here's how this is supposed to work:
129    // (http://web.sabi.net/) and <http://web.sabi.net/> should work if they are the entire document, even if clicking at the end/beginning of the document, not barfing with "no URL" (correct, as now) or selecting the final >, or ) (what would happen if we remove this "add 1" accommodation).  But how about "http://web.sabi.net/(foo)"?  That should work too, as long as it's not preceded by a (.
130    // Should probably backport to ICeCoffEETerminal, now I finally understand the method to this madness.
131    // add 1 to range to trap delimiters that are on the edge of the selection (i.e., <...)
132    delimiterRange = [s rangeOfCharacterFromSet: urlLeftDelimiters
133                                        options: NSLiteralSearch | NSBackwardsSearch
134                                          range: NSMakeRange(0, range.location + (range.location != [s length]))];
135    if (delimiterRange.location == NSNotFound) {
136        // extend to beginning of string
137        range.length += range.location;
138        range.location = 0;
139    } else {
140        NSCAssert(delimiterRange.length == 1, @"Internal error: delimiter matched range is not of length 1");
141        range.length += range.location - delimiterRange.location - 1;
142        range.location = delimiterRange.location + 1;
143       
144        // in url/(parens)stuff, handle clicking inside or after (parens).
145        /*if ([s characterAtIndex: delimiterRange.location] == '(' &&
146            range.location > 2) { // prevent wrapping, ordinarily not necessary
147            if ([s rangeOfString: @")" options: NSLiteralSearch range: range].location != NSNotFound ||
148                [s rangeOfCharacterFromSet: [NSCharacterSet characterSetWithCharactersInString: @"/."]
149                                   options: NSLiteralSearch range: range].location == NSNotFound) {
150                range.location -= 2;
151                range.length += 2;
152                ICLog(@"expanding past (, now |%@|", [s substringWithRange: range]);
153                goto expandFront;
154            }
155        } */       
156    }
157   
158    ICCF_CheckRange(range);
159   
160expandBack:
161    // XXX instead of length of string, make this stop at the max URL length to prevent protracted searches
162    // add 1 to range to trap delimiters that are on the edge of the selection (i.e., ...>)
163    extraLen = [s length] - range.location - range.length;
164    delimiterRange = [s rangeOfCharacterFromSet: urlRightDelimiters
165                                        options: NSLiteralSearch
166                                          range: NSMakeRange(range.location + range.length - (range.length != 0),
167                                                             extraLen + (range.length != 0))];
168    if (delimiterRange.location == NSNotFound) {
169        // extend to end of string
170        range.length += extraLen;
171    } else {
172        NSCAssert(delimiterRange.length == 1, @"Internal error: delimiter matched range is not of length 1");
173        range.length += delimiterRange.location - range.location - range.length;
174       
175        // grow URL past closing paren/brace if we've seen an open paren/brace
176        unichar closing = [s characterAtIndex: delimiterRange.location];
177        NSString *opening;
178        if (closing == ')') opening = @"(";
179        else if (closing == '}') opening = @"{";
180        else goto checkRange;
181        if ([s rangeOfString: opening options: NSLiteralSearch range: range].location == NSNotFound)
182            goto checkRange;
183       
184        if (extraLen == 0) {
185            range.length += 1;
186            ICLog(@"expanding past %c, now |%@|", closing, [s substringWithRange: range]);
187        } else {
188            range.length += 2;
189            ICLog(@"expanding past %c, now |%@|", closing, [s substringWithRange: range]);
190            goto expandBack;
191        }
192    }
193   
194checkRange:
195    ICCF_CheckRange(range);
196   
197    ICCF_ParseURL([s substringWithRange: range], &range);
198   
199    return range;
200}
Note: See TracBrowser for help on using the repository browser.