求助:将和弦匹配到对应歌词位置的格式转换实现
和弦歌词匹配格式转换解决方案
问题描述
我有若干行歌词与和弦,原格式为和弦行与对应歌词行交替出现(和弦行在上,歌词行在下),需要将和弦插入到歌词的对应位置,转换为「[和弦]歌词片段」的目标格式。目前已能提取和弦行,但无法准确定位和弦在对应歌词行的插入位置。以下是我的Dart代码实现,在此处遇到瓶颈,求C#、JS或Dart的解决方案:
_read({required String text}) { var result = text.split('\n'); RegExp re = RegExp(r\"^[A-G\d#b\s/-/m]*$(?:\r\n)?\", caseSensitive: false, multiLine: true); var chords = ''; for (final i in re.allMatches(text)) { chords = '$chords\n${(i[0]) ?? ''}'; } var arrChords = chords.split('\n'); for (int j = 0; j < arrChords.length; j++) { final i = arrChords[j]; print(i); if (i.isNotEmpty) { // I am stuck from here var lineChords = i.split(' '); for (int k = lineChords.length - 1; k > 0; k--) { final e = lineChords[k]; if (e.isNotEmpty) { var sRight = result[j + 1].substring(k); var sLeft = result[j + 1].substring(0, k); print(sLeft + sRight); // result[j + 1] = '$sLeft[$e]$sRight'; } } } } // return result; }
原代码问题分析
- 用
split(' ')分割和弦行,会把连续空格拆成空字符串,且无法保留和弦在原行的精确起始位置——和弦是靠空格对齐到歌词上方的,必须通过字符索引来匹配位置。 - 直接用和弦数组的下标
k作为歌词的分割索引,逻辑错误,因为k和和弦在原行的实际位置没有对应关系。
解决方案
核心思路:通过正则匹配和弦在原行的起始索引,从后往前在歌词行插入和弦标记(避免插入后索引偏移)。
Dart 实现
List<String> formatChordsWithLyrics(String text) { final lines = text.split('\n').map((line) => line.trimRight()).toList(); final result = <String>[]; // 匹配纯和弦行(允许空格、常见和弦符号) final chordPattern = RegExp(r'^[A-G0-9#b/m\- ]+$'); int i = 0; while (i < lines.length) { final currentLine = lines[i]; // 判断当前行是和弦行,且下一行存在对应歌词 if (chordPattern.hasMatch(currentLine) && i + 1 < lines.length) { final chordLine = currentLine; final lyricLine = lines[i + 1]; final lyricBuffer = StringBuffer(lyricLine); // 匹配所有合法和弦,同时获取它们在和弦行的起始位置 final chordMatches = RegExp(r'[A-G][#b]?(?:m|maj|min|dim|aug)?[0-9]?(?:\/[A-G][#b]?)?') .allMatches(chordLine); // 从后往前插入,避免插入后字符索引偏移 for (final match in chordMatches.toList().reversed) { final chord = match.group(0)!; final insertPos = match.start; // 防止插入位置超出歌词长度 if (insertPos <= lyricBuffer.length) { lyricBuffer.insert(insertPos, '[$chord]'); } } result.add(lyricBuffer.toString()); i += 2; // 跳过已处理的和弦行+歌词行 } else { // 非和弦行直接加入结果 result.add(currentLine); i += 1; } } return result; } // 示例用法 void main() { final input = '''C G Am 我爱着你 风里雨里 不问归期 F C G 往后余生 目光所至 都是你'''; final formatted = formatChordsWithLyrics(input); formatted.forEach(print); }
JavaScript 实现
function formatChordsWithLyrics(text) { const lines = text.split('\n').map(line => line.trimEnd()); const result = []; const chordPattern = /^[A-G0-9#b/m\- ]+$/; // 匹配常见和弦格式 const chordMatchPattern = /[A-G][#b]?(?:m|maj|min|dim|aug)?[0-9]?(?:\/[A-G][#b]?)?/g; let i = 0; while (i < lines.length) { const currentLine = lines[i]; if (chordPattern.test(currentLine) && i + 1 < lines.length) { const chordLine = currentLine; let lyricLine = lines[i + 1]; const matches = []; let match; // 收集所有和弦的位置与内容 while ((match = chordMatchPattern.exec(chordLine)) !== null) { matches.push({ pos: match.index, chord: match[0] }); } // 从后往前插入 for (const { pos, chord } of matches.reverse()) { if (pos <= lyricLine.length) { lyricLine = lyricLine.slice(0, pos) + `[${chord}]` + lyricLine.slice(pos); } } result.push(lyricLine); i += 2; } else { result.push(currentLine); i += 1; } } return result; } // 示例 const input = `C G Am 我爱着你 风里雨里 不问归期 F C G 往后余生 目光所至 都是你`; const formatted = formatChordsWithLyrics(input); console.log(formatted.join('\n'));
C# 实现
using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; public class ChordFormatter { public static List<string> FormatChordsWithLyrics(string text) { var lines = text.Split(new[] { '\n' }, StringSplitOptions.None) .Select(line => line.TrimEnd()) .ToList(); var result = new List<string>(); var chordPattern = new Regex(@"^[A-G0-9#b/m\- ]+$"); var chordMatchPattern = new Regex(@"[A-G][#b]?(?:m|maj|min|dim|aug)?[0-9]?(?:\/[A-G][#b]?)?"); int i = 0; while (i < lines.Count) { var currentLine = lines[i]; if (chordPattern.IsMatch(currentLine) && i + 1 < lines.Count) { var chordLine = currentLine; var lyricLine = lines[i + 1]; var matches = chordMatchPattern.Matches(chordLine); // 从后往前插入,避免索引偏移 for (int j = matches.Count - 1; j >= 0; j--) { var match = matches[j]; var chord = match.Value; var insertPos = match.Index; if (insertPos <= lyricLine.Length) { lyricLine = lyricLine.Insert(insertPos, $"[{chord}]"); } } result.Add(lyricLine); i += 2; } else { result.Add(currentLine); i += 1; } } return result; } // 示例用法 public static void Main() { var input = @"C G Am 我爱着你 风里雨里 不问归期 F C G 往后余生 目光所至 都是你"; var formatted = FormatChordsWithLyrics(input); foreach (var line in formatted) { Console.WriteLine(line); } } }
内容的提问来源于stack exchange,提问作者Fadhly Permata
相关产品推荐
相关产品推荐

