/
QSSense.m
110 lines (91 loc) · 4.03 KB
/
QSSense.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//
// QSense.m
// QSqSense
//
// Created by Alcor on 11/22/04.
// Copyright 2004 Blacktree. All rights reserved.
//
#import "QSSense.h"
#define MIN_ABBR_OPTIMIZE 0
#define IGNORED_SCORE 0.9
#define SKIPPED_SCORE 0.15
CGFloat QSScoreForAbbreviationWithRanges(CFStringRef str, CFStringRef abbr, id mask, CFRange strRange, CFRange abbrRange);
CGFloat QSScoreForAbbreviation(CFStringRef str, CFStringRef abbr, id mask) {
return QSScoreForAbbreviationWithRanges(str, abbr, mask, CFRangeMake(0, CFStringGetLength(str)), CFRangeMake(0, CFStringGetLength(abbr)));
}
CGFloat QSScoreForAbbreviationWithRanges(CFStringRef str, CFStringRef abbr, id mask, CFRange strRange, CFRange abbrRange) {
if (!abbrRange.length)
return IGNORED_SCORE; //deduct some points for all remaining letters
if (abbrRange.length > strRange.length)
return 0.0;
static CFCharacterSetRef wordSeparator = NULL;
static CFCharacterSetRef uppercase = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
wordSeparator = CFCharacterSetCreateMutableCopy(NULL, CFCharacterSetGetPredefined(kCFCharacterSetWhitespace));
CFCharacterSetAddCharactersInString((CFMutableCharacterSetRef)wordSeparator, (CFStringRef)@".");
uppercase = CFCharacterSetGetPredefined(kCFCharacterSetUppercaseLetter);
});
// Create an inline buffer version of str. Will be used in loop below
// for faster lookups.
CFStringInlineBuffer inlineBuffer;
CFStringInitInlineBuffer(str, &inlineBuffer, strRange);
CFLocaleRef userLoc = CFLocaleCopyCurrent();
CGFloat score = 0.0, remainingScore = 0.0;
CFIndex i, j;
CFRange matchedRange, remainingStrRange, adjustedStrRange = strRange;
// Search for steadily smaller portions of the abbreviation
for (i = abbrRange.length; i > 0; i--) {
CFStringRef curAbbr = CFStringCreateWithSubstring (NULL, abbr, CFRangeMake(abbrRange.location, i) );
// terminality
// axeen
BOOL found = CFStringFindWithOptionsAndLocale(str, curAbbr,
CFRangeMake(adjustedStrRange.location, adjustedStrRange.length - abbrRange.length + i),
kCFCompareCaseInsensitive | kCFCompareDiacriticInsensitive | kCFCompareLocalized,
userLoc, &matchedRange);
CFRelease(curAbbr);
if (!found) {
continue;
}
if (mask) {
[mask addIndexesInRange:NSMakeRange(matchedRange.location, matchedRange.length)];
}
remainingStrRange.location = matchedRange.location + matchedRange.length;
remainingStrRange.length = strRange.location + strRange.length - remainingStrRange.location;
// Search what is left of the string with the rest of the abbreviation
remainingScore = QSScoreForAbbreviationWithRanges(str, abbr, mask, remainingStrRange, CFRangeMake(abbrRange.location + i, abbrRange.length - i));
if (remainingScore) {
score = remainingStrRange.location-strRange.location;
// ignore skipped characters if is first letter of a word
if (matchedRange.location > strRange.location) {
// if some letters were skipped
UniChar previousChar = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, matchedRange.location - 1);
UniChar character = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, matchedRange.location);
if (CFCharacterSetIsCharacterMember(wordSeparator, previousChar)) {
// We're on the first letter of a word
for (j = matchedRange.location - 2; j >= strRange.location; j--) {
if (CFCharacterSetIsCharacterMember(wordSeparator, CFStringGetCharacterFromInlineBuffer(&inlineBuffer, j)))
score--;
else
score -= SKIPPED_SCORE;
}
} else if (CFCharacterSetIsCharacterMember(uppercase, character)) {
for (j = matchedRange.location - 1; j >= strRange.location; j--) {
if (CFCharacterSetIsCharacterMember(uppercase, CFStringGetCharacterFromInlineBuffer(&inlineBuffer, j)))
score--;
else
score -= SKIPPED_SCORE;
}
} else {
score -= (matchedRange.location-strRange.location)/2;
}
}
score += remainingScore * remainingStrRange.length;
score /= strRange.length;
CFRelease(userLoc);
return score;
}
}
CFRelease(userLoc);
return 0;
}