require 'nokogiri' file = ARGV[0] xml = Nokogiri::XML(File.read(file)) title = xml.css('title').text content = xml.css('lyrics').text def to_lines(content) file_lines = content.split("\n") lines = [] pair = [] file_lines.each do |line| if pair.any? pair << line[1..-1] pair[1] ||= "" lines << pair pair = [] elsif line[0] == '.' && pair.empty? pair = [line[1..-1]] elsif line[0] == ' ' && pair.empty? pair = [] lines << ["", line] elsif line =~ /\[V(\d*)\]/ pair = [] lines << ["", "Verse #{$1}:"] elsif line =~ /\[C(\d*)\]/ pair = [] number = $1.length != 0 ? " #{$1}" : "" lines << ["", "Chorus#{number}:"] elsif line =~ /\[PC(\d*)\]/ pair = [] number = $1.length != 0 ? " #{$1}" : "" lines << ["", "Prechorus#{number}:"] elsif line =~ /\[B(\d*)\]/ pair = [] number = $1.length != 0 ? " #{$1}" : "" lines << ["", "Bridge#{number}:"] elsif line.strip == "" pair = [] lines << ["", ""] else pair = [] end end lines end def get_chord_positions(chord_line) positions = [] chord = "" start_pos = 0 (0..chord_line.length - 1).each do |i| if chord_line[i] == ' ' && chord != "" positions << [chord, start_pos] chord = "" start_pos = 0 end if chord_line[i] != ' ' start_pos = i if chord == "" chord << chord_line[i] end end if chord != "" positions << [chord, start_pos] end positions end def interpolate_line(chord_positions, lyrics) output = lyrics.dup offset = 0 chord_positions.each do |chord, position| insert_string = "[#{chord}]" if output.length < (position + offset) output << (" " * (position + offset - output.length)) end output.insert(position + offset, insert_string) offset += insert_string.length end output.strip end lines = to_lines(content) onsong = lines.map { |chords, lyrics| [get_chord_positions(chords), lyrics] }.map{ |chord_positions, lyrics| interpolate_line(chord_positions, lyrics) } key = get_chord_positions(lines.find { |chords, lyrics| chords.length != 0 }[0])[0][0] rescue nil puts title puts "Key: [#{key}]" if key onsong.each do |line| puts line end