Skip to content

Commit 0af4c7b

Browse files
committed
Add func to split string with handling quotes
1 parent 05c4496 commit 0af4c7b

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ go get github.com/tiendc/gofn
134134
- [StringLexJoin / StringLexJoinEx](#stringlexjoin--stringlexjoinex)
135135
- [StringWrap / StringUnwrap](#stringwrap--stringunwrap)
136136
- [StringToUpper1stLetter / StringToLower1stLetter](#stringtoupper1stletter--stringtolower1stletter)
137+
- [StringSplitEx](#stringsplitex)
137138

138139
**Number**
139140
- [ParseInt / ParseUint / ParseFloat](#parseint--parseuint--parsefloat)
@@ -1111,6 +1112,15 @@ StringToUpper1stLetter("abc") // "Abc"
11111112
StringToLower1stLetter("Abc") // "abc"
11121113
```
11131114

1115+
#### StringSplitEx
1116+
1117+
Splits a string with handling quotes by ignoring any separator within the quotes.
1118+
1119+
```go
1120+
StringSplitEx("ab cd \"12 34\"", " ", "\"") // "[]string{"ab", "cd", "\"12 34\""}
1121+
StringSplitEx("ab cd {12 34}", " ", "{ }") // "[]string{"ab", "cd", "{12 34}"}
1122+
```
1123+
11141124
### Number
11151125
---
11161126

string.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,80 @@ func StringToLower1stLetter(s string) string {
222222
runes[0] = unicode.ToLower(runes[0])
223223
return string(runes)
224224
}
225+
226+
// StringSplitEx splits a string with handling quotes.
227+
// `quote` param can be a single char (`"`, `'`...) if opening and closing tokens are the same,
228+
// or a space-delimited string (`{ }`, `[ ]`, `{{ }}`...) if the tokens are different.
229+
func StringSplitEx(s string, sep, quote string) (res []string) {
230+
quoteParts := strings.Split(quote, " ")
231+
if s == "" || sep == "" || quote == "" || len(quoteParts) > 2 {
232+
return strings.Split(s, sep)
233+
}
234+
235+
runes, sepRunes := []rune(s), []rune(sep)
236+
quoteOpenRunes := []rune(quoteParts[0])
237+
quoteCloseRunes := quoteOpenRunes
238+
if len(quoteParts) > 1 {
239+
quoteCloseRunes = []rune(quoteParts[1])
240+
}
241+
242+
currPart := make([]rune, 0, 10) //nolint:mnd
243+
i := 0
244+
for i < len(runes) {
245+
if j, match := stringSubMatch(runes, i, quoteOpenRunes); match {
246+
if k := stringFindQuote(runes, j, quoteOpenRunes, quoteCloseRunes); k > j {
247+
currPart = append(currPart, runes[i:k]...)
248+
i = k
249+
} else {
250+
currPart = append(currPart, runes[i:j]...)
251+
i = j
252+
}
253+
continue
254+
}
255+
if j, match := stringSubMatch(runes, i, sepRunes); match {
256+
res = append(res, string(currPart))
257+
currPart = make([]rune, 0, 10) //nolint:mnd
258+
i = j
259+
continue
260+
}
261+
currPart = append(currPart, runes[i])
262+
i++
263+
}
264+
res = append(res, string(currPart))
265+
return res
266+
}
267+
268+
// stringFindQuote finds end index of a quote
269+
func stringFindQuote(s []rune, start int, quoteOpenRunes, quoteCloseRunes []rune) int {
270+
i := start
271+
var j, k int
272+
var match bool
273+
for i < len(s) {
274+
if j, match = stringSubMatch(s, i, quoteCloseRunes); match {
275+
return j
276+
}
277+
// Encounters another quote opening token
278+
if j, match = stringSubMatch(s, i, quoteOpenRunes); match {
279+
if k = stringFindQuote(s, j, quoteOpenRunes, quoteCloseRunes); k > j {
280+
i = k
281+
continue
282+
} else {
283+
break
284+
}
285+
}
286+
i++
287+
}
288+
return start
289+
}
290+
291+
// stringSubMatch checks if a string matches a sub-string at the index
292+
func stringSubMatch(s []rune, start int, sub []rune) (int, bool) {
293+
i := start
294+
for _, ch := range sub {
295+
if i >= len(s) || s[i] != ch {
296+
return start, false
297+
}
298+
i++
299+
}
300+
return i, true
301+
}

string_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,27 @@ func Test_StringToLower1stLetter(t *testing.T) {
203203
assert.Equal(t, "abc", StringToLower1stLetter("Abc"))
204204
assert.Equal(t, "ối", StringToLower1stLetter("Ối"))
205205
}
206+
207+
func Test_StringSplitEx(t *testing.T) {
208+
assert.Equal(t, []string{""}, StringSplitEx("", " ", "\""))
209+
assert.Equal(t, []string{"", ""}, StringSplitEx(" ", " ", "\""))
210+
assert.Equal(t, []string{"", "", ""}, StringSplitEx(",,", ",", "`"))
211+
212+
assert.Equal(t, []string{"abc"}, StringSplitEx("abc", " ", "\""))
213+
assert.Equal(t, []string{"", "ab", "", "cd", "12", "", "", "34", ""},
214+
StringSplitEx(" ab cd 12 34 ", " ", "\""))
215+
assert.Equal(t, []string{"ab", "xy", "\"12 34 \"56"},
216+
StringSplitEx("ab xy \"12 34 \"56", " ", "\""))
217+
assert.Equal(t, []string{"ab", "xy", "''12", "34", "'56", ""},
218+
StringSplitEx("ab xy ''12 34 '56 ", " ", "'"))
219+
220+
assert.Equal(t, []string{"", "ab", "", "cd", "[12 34][ 56 ]", ""},
221+
StringSplitEx(" ab cd [12 34][ 56 ] ", " ", "[ ]"))
222+
assert.Equal(t, []string{"", "ab", "", "cd", "[12 34]", "[ 56 ]", ""},
223+
StringSplitEx(" ab cd [12 34] [ 56 ] ", " ", "[ ]"))
224+
assert.Equal(t, []string{"", "ab", "", "cd", "[[12 34]", "56", ""},
225+
StringSplitEx(" ab cd [[12 34] 56 ", " ", "[ ]"))
226+
227+
assert.Equal(t, []string{"", "ab", "", "cd", "{{12 34}}", "{56", "78}", ""},
228+
StringSplitEx(" ab cd {{12 34}} {56 78} ", " ", "{{ }}"))
229+
}

0 commit comments

Comments
 (0)