Skip to content

Commit fb012b7

Browse files
committed
feat: add option to ignore diacritics
Add `ignoreDiacritics` to ignore diacritics in search
1 parent 43eebfa commit fb012b7

File tree

10 files changed

+119
-7
lines changed

10 files changed

+119
-7
lines changed

docs/api/options.md

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ tags:
1414

1515
Indicates whether comparisons should be case sensitive.
1616

17+
### `ignoreDiacritics`
18+
19+
- Type: `boolean`
20+
- Default: `false`
21+
22+
Indicates whether comparisons should ignore diacritics (accents).
23+
1724
### `includeScore`
1825

1926
- Type: `boolean`

src/core/config.js

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export const BasicOptions = {
1616
// When `true`, the algorithm continues searching to the end of the input even if a perfect
1717
// match is found before the end of the same input.
1818
isCaseSensitive: false,
19+
// When `true`, the algorithm will ignore diacritics (accents) in comparisons
20+
ignoreDiacritics: false,
1921
// When true, the matching function will continue to the end of a search pattern even if
2022
includeScore: false,
2123
// List of properties that will be searched. This also supports nested properties.

src/core/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default class Fuse {
1212
constructor(docs, options = {}, index) {
1313
this.options = { ...Config, ...options }
1414

15+
console.log(this.options);
1516
if (
1617
this.options.useExtendedSearch &&
1718
!process.env.EXTENDED_SEARCH_ENABLED

src/helpers/diacritics.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const stripDiacritics = String.prototype.normalize
2+
? ((str) => str.normalize('NFD').replace(/[\u0300-\u036F]/g, ''))
3+
: ((str) => str);

src/index.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,8 @@ export type FuseOptionKey<T> = FuseOptionKeyObject<T> | string | string[]
292292
export interface IFuseOptions<T> {
293293
/** Indicates whether comparisons should be case sensitive. */
294294
isCaseSensitive?: boolean
295+
/** Indicates whether comparisons should ignore diacritics (accents). */
296+
ignoreDiacritics?: boolean
295297
/** Determines how close the match must be to the fuzzy location (specified by `location`). An exact letter match which is `distance` characters away from the fuzzy location would score as a complete mismatch. A `distance` of `0` requires the match be at the exact `location` specified. A distance of `1000` would require a perfect match to be within `800` characters of the `location` to be found using a `threshold` of `0.8`. */
296298
distance?: number
297299
/** When true, the matching function will continue to the end of a search pattern even if a perfect match has already been located in the string. */

src/search/bitap/index.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import search from './search'
22
import createPatternAlphabet from './createPatternAlphabet'
33
import { MAX_BITS } from './constants'
44
import Config from '../../core/config'
5+
import { stripDiacritics } from '../../helpers/diacritics'
56

67
export default class BitapSearch {
78
constructor(
@@ -14,6 +15,7 @@ export default class BitapSearch {
1415
findAllMatches = Config.findAllMatches,
1516
minMatchCharLength = Config.minMatchCharLength,
1617
isCaseSensitive = Config.isCaseSensitive,
18+
ignoreDiacritics = Config.ignoreDiacritics,
1719
ignoreLocation = Config.ignoreLocation
1820
} = {}
1921
) {
@@ -25,10 +27,13 @@ export default class BitapSearch {
2527
findAllMatches,
2628
minMatchCharLength,
2729
isCaseSensitive,
30+
ignoreDiacritics,
2831
ignoreLocation
2932
}
3033

31-
this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase()
34+
pattern = isCaseSensitive ? pattern : pattern.toLowerCase()
35+
pattern = ignoreDiacritics ? stripDiacritics(pattern) : pattern;
36+
this.pattern = pattern;
3237

3338
this.chunks = []
3439

@@ -66,11 +71,10 @@ export default class BitapSearch {
6671
}
6772

6873
searchIn(text) {
69-
const { isCaseSensitive, includeMatches } = this.options
74+
const { isCaseSensitive, ignoreDiacritics, includeMatches } = this.options
7075

71-
if (!isCaseSensitive) {
72-
text = text.toLowerCase()
73-
}
76+
text = isCaseSensitive ? text : text.toLowerCase()
77+
text = ignoreDiacritics ? stripDiacritics(text) : text
7478

7579
// Exact match
7680
if (this.pattern === text) {

src/search/extended/FuzzyMatch.js

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default class FuzzyMatch extends BaseMatch {
1313
findAllMatches = Config.findAllMatches,
1414
minMatchCharLength = Config.minMatchCharLength,
1515
isCaseSensitive = Config.isCaseSensitive,
16+
ignoreDiacritics = Config.ignoreDiacritics,
1617
ignoreLocation = Config.ignoreLocation
1718
} = {}
1819
) {
@@ -25,6 +26,7 @@ export default class FuzzyMatch extends BaseMatch {
2526
findAllMatches,
2627
minMatchCharLength,
2728
isCaseSensitive,
29+
ignoreDiacritics,
2830
ignoreLocation
2931
})
3032
}

src/search/extended/index.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import parseQuery from './parseQuery'
22
import FuzzyMatch from './FuzzyMatch'
33
import IncludeMatch from './IncludeMatch'
44
import Config from '../../core/config'
5+
import { stripDiacritics } from '../../helpers/diacritics'
56

67
// These extended matchers can return an array of matches, as opposed
78
// to a singl match
@@ -40,6 +41,7 @@ export default class ExtendedSearch {
4041
pattern,
4142
{
4243
isCaseSensitive = Config.isCaseSensitive,
44+
ignoreDiacritics = Config.ignoreDiacritics,
4345
includeMatches = Config.includeMatches,
4446
minMatchCharLength = Config.minMatchCharLength,
4547
ignoreLocation = Config.ignoreLocation,
@@ -52,6 +54,7 @@ export default class ExtendedSearch {
5254
this.query = null
5355
this.options = {
5456
isCaseSensitive,
57+
ignoreDiacritics,
5558
includeMatches,
5659
minMatchCharLength,
5760
findAllMatches,
@@ -61,7 +64,9 @@ export default class ExtendedSearch {
6164
distance
6265
}
6366

64-
this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase()
67+
pattern = isCaseSensitive ? pattern : pattern.toLowerCase()
68+
pattern = ignoreDiacritics ? stripDiacritics(pattern) : pattern
69+
this.pattern = pattern
6570
this.query = parseQuery(this.pattern, this.options)
6671
}
6772

@@ -79,9 +84,10 @@ export default class ExtendedSearch {
7984
}
8085
}
8186

82-
const { includeMatches, isCaseSensitive } = this.options
87+
const { includeMatches, isCaseSensitive, ignoreDiacritics } = this.options
8388

8489
text = isCaseSensitive ? text : text.toLowerCase()
90+
text = ignoreDiacritics ? stripDiacritics(text) : text
8591

8692
let numMatches = 0
8793
let allIndices = []

test/extended-search.test.js

+43
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,46 @@ describe('ignoreLocation when useExtendedSearch is true', () => {
109109
expect(result).toHaveLength(1)
110110
})
111111
})
112+
113+
describe('Searching using extended search ignoring diactrictics', () => {
114+
const list = [
115+
{
116+
text: 'déjà'
117+
},
118+
{
119+
text: 'cafe'
120+
}
121+
]
122+
123+
const options = {
124+
useExtendedSearch: true,
125+
ignoreDiacritics: true,
126+
threshold: 0,
127+
keys: ['text']
128+
}
129+
const fuse = new Fuse(list, options)
130+
131+
test('Search: query with diactrictics, list with diactrictics', () => {
132+
let result = fuse.search('déjà')
133+
expect(result).toHaveLength(1)
134+
expect(result[0].refIndex).toBe(0)
135+
})
136+
137+
test('Search: query without diactrictics, list with diactrictics', () => {
138+
let result = fuse.search('deja')
139+
expect(result).toHaveLength(1)
140+
expect(result[0].refIndex).toBe(0)
141+
})
142+
143+
test('Search: query with diactrictics, list without diactrictics', () => {
144+
let result = fuse.search('café')
145+
expect(result).toHaveLength(1)
146+
expect(result[0].refIndex).toBe(1)
147+
})
148+
149+
test('Search: query without diactrictics, list without diactrictics', () => {
150+
let result = fuse.search('cafe')
151+
expect(result).toHaveLength(1)
152+
expect(result[0].refIndex).toBe(1)
153+
})
154+
})

test/fuzzy-search.test.js

+42
Original file line numberDiff line numberDiff line change
@@ -1209,3 +1209,45 @@ describe('Breaking values', () => {
12091209
expect(result).toHaveLength(1)
12101210
})
12111211
})
1212+
1213+
describe('Searching ignoring diactrictics', () => {
1214+
const list = [
1215+
{
1216+
text: 'déjà'
1217+
},
1218+
{
1219+
text: 'cafe'
1220+
}
1221+
]
1222+
1223+
const options = {
1224+
ignoreDiacritics: true,
1225+
threshold: 0,
1226+
keys: ['text']
1227+
}
1228+
const fuse = new Fuse(list, options)
1229+
1230+
test('Search: query with diactrictics, list with diactrictics', () => {
1231+
let result = fuse.search('déjà')
1232+
expect(result).toHaveLength(1)
1233+
expect(result[0].refIndex).toBe(0)
1234+
})
1235+
1236+
test('Search: query without diactrictics, list with diactrictics', () => {
1237+
let result = fuse.search('deja')
1238+
expect(result).toHaveLength(1)
1239+
expect(result[0].refIndex).toBe(0)
1240+
})
1241+
1242+
test('Search: query with diactrictics, list without diactrictics', () => {
1243+
let result = fuse.search('café')
1244+
expect(result).toHaveLength(1)
1245+
expect(result[0].refIndex).toBe(1)
1246+
})
1247+
1248+
test('Search: query without diactrictics, list without diactrictics', () => {
1249+
let result = fuse.search('cafe')
1250+
expect(result).toHaveLength(1)
1251+
expect(result[0].refIndex).toBe(1)
1252+
})
1253+
})

0 commit comments

Comments
 (0)