parse function

Kara? parse(
  1. String raw
)

Implementation

Kara? parse(String raw) {
  if (!raw.startsWith("KARA")) {
    throw NotKaraException;
  }

  late String title;
  late String artist;
  late int year;
  late String album;
  late List<String> languages;
  Map<int, String> singers = {};

  KaraSection currentSection = KaraSection.header;
  List<bool>? currentSingers;
  String? currentLyric;
  Duration? currentStart;
  Duration? currentEnd;
  Map<String, String> currentTranslation = {};

  List<KaraLine> lines = [];
  List<KaraChunk> time = [];

  for (final line in raw.split('\n').skip(1)) {
    if (line.startsWith("#") || line.isEmpty) continue;

    // Section
    // e.g `[Singers]`, `[Intro]`, `[Chorus]`
    if (line.startsWith("[")) {
      final sectionName = line.substring(1, line.length - 1);

      currentSection = switch (sectionName) {
        "Singers" => KaraSection.singers,
        "Intro" => KaraSection.intro,
        "Verse" => KaraSection.verse,
        "Pre-Chorus" => KaraSection.preChorus,
        "Chorus" => KaraSection.chorus,
        "Post-Chorus" => KaraSection.postChorus,
        "Bridge" => KaraSection.bridge,
        "Post-Bridge" => KaraSection.postBridge,
        "Outro" => KaraSection.outro,
        _ => KaraSection.header,
      };
      continue;
      if (currentSection.isSongStructure && time.isNotEmpty) {}
    }

    if (currentSection == KaraSection.header) {
      final parsed = _parseKeyValue(line);
      if (parsed == null) {
        continue;
      }

      final (:key, :value) = parsed;
      switch (key) {
        case "Title":
          title = value;
        case "Artist":
          artist = value;
        case "Year":
          year = int.tryParse(value) ?? 0;
        case "Album":
          album = value;
        case "Languages":
          languages = value.split(",").map((e) => e.trim()).toList();
      }
    }

    if (currentSection == KaraSection.singers) {
      final parsed = _parseKeyValue(line);
      if (parsed == null) {
        continue;
      }

      final (:key, :value) = parsed;
      singers.update(
        int.tryParse(key) ?? 0,
        (_) => value,
        ifAbsent: () => value,
      );
    }

    if (currentSection.isSongStructure) {
      if (_parseTimestamp(line) case KaraChunk parsedTimestamp) {
        if (currentStart == null ||
            currentEnd == null ||
            parsedTimestamp.start < currentStart) {
          currentStart = parsedTimestamp.start;
          currentEnd = parsedTimestamp.end;
        }

        if (parsedTimestamp.end > currentEnd) {
          currentEnd = parsedTimestamp.end;
        }

        time.add(parsedTimestamp);
        continue;
      }

      if (time.isNotEmpty) {
        if (currentLyric == null ||
            currentStart == null ||
            currentEnd == null) {
          continue;
        }
        lines.add(KaraLine(
          section: currentSection,
          singers: currentSingers,
          lyric: currentLyric,
          start: currentStart,
          end: currentEnd,
          translations: currentTranslation.isEmpty ? null : currentTranslation,
          time: time,
        ));
        currentLyric = null;
        currentStart = null;
        currentEnd = null;
        currentTranslation = {};
        time = [];
      }

      final parsed = _parseKeyValue(line);
      if (parsed != null) {
        currentSingers =
            parsed.value.split(",").map((e) => int.tryParse(e.trim())).fold(
                List<bool>.generate(
                  singers.length,
                  (index) => false,
                  growable: false,
                ), (singers, parsedSingerIndex) {
          if (parsedSingerIndex == null) {
            return singers;
          }
          singers?[parsedSingerIndex - 1] = true;
          return singers;
        });
        continue;
      }
      if (currentLyric != null) {
        final translation = line.split(" ");
        currentTranslation.update(
            translation.first, (_) => translation.skip(1).join(' '),
            ifAbsent: () => translation.skip(1).join(" "));
        continue;
      }
      currentLyric = line;
    }
  }

  if (currentLyric != null && currentStart != null && currentEnd != null) {
    lines.add(KaraLine(
      section: currentSection,
      singers: currentSingers,
      lyric: currentLyric,
      start: currentStart,
      end: currentEnd,
      translations: currentTranslation.isEmpty ? null : currentTranslation,
      time: time,
    ));
  }

  return Kara(
    title: title,
    artist: artist,
    year: year,
    album: album,
    languages: languages,
    singers: singers.values.toList(), // FIXME: doesn't allow reordering
    lines: lines,
  );
}