GenreParser.kt
34 lines · 1.5 KB
Changed in: 0.5.0
| 1 | package dev.stave.domain.model |
| 2 | |
| 3 | /** |
| 4 | * Parse a genre string into individual genre tokens. |
| 5 | * |
| 6 | * UPnP/DIDL servers emit multi-genre tags with inconsistent separators across |
| 7 | * implementations and across the source files' own metadata conventions: |
| 8 | * MinimServer "Rock; Pop" |
| 9 | * iTunes / ID3v2.4 "Rock/Pop" or "Rock\Pop" |
| 10 | * Foobar2000 "Rock // Pop" |
| 11 | * Plex / BubbleUPnP "Rock, Pop" |
| 12 | * Doubled variants "Rock\\Pop" (escaped-backslash or literal double-backslash) |
| 13 | * |
| 14 | * We collapse `//` and `\\` to single delimiters first, then split on any of |
| 15 | * `,`, `;`, `/`, `\`. Tokens are trimmed; blanks are discarded. Order preserved. |
| 16 | * |
| 17 | * Returns an empty list for null/blank input. Doesn't lowercase — callers that need |
| 18 | * case-insensitive comparison should do that themselves. |
| 19 | */ |
| 20 | fun parseGenres(raw: String?): List<String> { |
| 21 | if (raw.isNullOrBlank()) return emptyList() |
| 22 | // Collapse doubled separators first. Without this, "Rock // Pop" split on `/` would |
| 23 | // yield ["Rock ", " ", " Pop"], and the middle empty token would pass through trim() |
| 24 | // as "" and get filtered out — slow but correct. Doing this first is cheaper and |
| 25 | // also handles the literal-double-backslash case "Rock\\Pop" which would otherwise |
| 26 | // split to ["Rock", "", "Pop"]. |
| 27 | val normalized = raw |
| 28 | .replace("//", "/") |
| 29 | .replace("\\\\", "\\") |
| 30 | return normalized |
| 31 | .split(',', ';', '/', '\\') |
| 32 | .map { it.trim() } |
| 33 | .filter { it.isNotEmpty() } |
| 34 | } |


