package songbook.song; import org.apache.lucene.document.*; import org.apache.lucene.util.BytesRef; import java.io.IOException; import java.util.regex.Pattern; /** * Created by LF5 on 06/04/2015. */ public class SongUtils { public static String CHORD_REGEXP_STR = "(C|D|E|F|G|A|B)(b|#)?(m|M|min|maj|dim|Δ|°|ø|Ø)?((sus|add)?(b|#)?(2|4|5|6|7|9|10|11|13)?)*(\\+|aug|alt)?(\\/(C|D|E|F|G|A|B)(b|#)?)?"; public static Pattern CHORD_REGEXP = Pattern.compile(CHORD_REGEXP_STR); public static Pattern REPEAT_REGEXP = Pattern.compile("\\(x[0-9]+\\)"); public static String getTitle(String songData) { int indexOfFirstLine = songData.replace("\r\n", "\n").replace("\r", "\n").indexOf("\n"); if (indexOfFirstLine != -1) { return songData.substring(0, indexOfFirstLine); } return songData; } public static Document indexSong(String songData) { Document document = new Document(); String[] songLines = getSongLines(songData); document.add(new TextField("song", songData, Field.Store.NO)); document.add(new SortedDocValuesField("title", new BytesRef(songLines[0]))); document.add(new StringField("title", songLines[0], Field.Store.YES)); for (int i = 1; i < songLines.length; i++) { String line = songLines[i].trim(); int indexOfCol = line.indexOf(":"); if (indexOfCol != -1) { String propName = line.substring(0, indexOfCol).toLowerCase().trim(); String propValue = line.substring(indexOfCol + 1).trim(); if (!propValue.isEmpty()) { document.add(new StringField(propName, propValue, Field.Store.YES)); } } } return document; } public static String[] getSongLines(String songData) { return songData.replace("\r\n", "\n").replace("\r", "\n").split("\n"); } public static <A extends Appendable> A writeHtml(A w, String songData) { try { String[] songLines = SongUtils.getSongLines(songData); w.append("<div class='song' itemscope='' itemtype='http://schema.org/MusicComposition'>\n"); w.append("<div class='song-title' itemprop='name'>"); w.append(songLines[0]); w.append("</div>\n"); w.append("<div class='song-header'>"); boolean songHeader = true; boolean verse = false; for (int i = 1; i < songLines.length; i++) { String line = songLines[i]; String lowercaseLine = line.trim().toLowerCase(); int indexOfCol = line.indexOf(":"); if (indexOfCol != -1) { String propName = line.substring(0, indexOfCol).toLowerCase().trim(); String propValue = line.substring(indexOfCol + 1).trim(); if (!propValue.isEmpty()) { if (verse) { // close verse w.append("</div>\n"); verse = false; } } else { if (songHeader) { songHeader = false; w.append("</div>\n<div class='song-content'>\n"); } } w.append("<div class='song-"); w.append(propName.replace(" ", "-")); w.append("'>\n"); w.append("<span class='song-metadata-name'>"); w.append(propName); w.append(": </span>\n"); if (!propValue.isEmpty()) { w.append("<span class='song-metadata-value'"); switch (propName) { case "author": case "artist": w.append(" itemprop='composer'"); break; case "album": w.append(" itemprop='inAlbum'"); break; case "tone":case "key": w.append(" itemprop='musicalKey'"); break; } w.append("data-name='"); w.append(propName); w.append("'>"); boolean isLink = propName.equals("video") || propName.equals("audio") || propName.equals("link"); if (isLink) { w.append("<a href='"); w.append(propValue); w.append("'>"); } w.append(propValue); if (isLink) { w.append("</a>"); } w.append("</span>\n"); w.append("</div>\n"); } else { // Start of verse don't close div verse = true; } } else if (lowercaseLine.isEmpty()) { if (verse) { // close verse w.append("</div>\n"); verse = false; } } else if ("chorus".equals(lowercaseLine.toLowerCase()) || "intro".equals(lowercaseLine.toLowerCase()) || "bridge".equals(lowercaseLine.toLowerCase())) { // chorus, intro, or bridge recall if (verse) { // close verse w.append("</div>\n"); verse = false; } w.append("<div class='song-" + lowercaseLine +"-recall'>"); w.append(line); w.append("</div>"); } else { String[] tokens = line.replace("|", " ").split(" "); boolean isLineChords = true; for (int j = 0; isLineChords && j < tokens.length; j++) { if (!tokens[j].isEmpty()) { isLineChords = isLineChords && (CHORD_REGEXP.matcher(tokens[j]).matches() || REPEAT_REGEXP.matcher(tokens[j]).matches()); } } if (isLineChords) { if (!verse) { if (songHeader) { songHeader = false; w.append("</div>\n<div class='song-content'>\n"); } w.append("<div class='song-verse'>"); verse = true; } w.append("<div class='song-chords'>"); w.append(CHORD_REGEXP.matcher(line).replaceAll("<span class='song-chord'>$0</span>")); w.append("</div>\n"); } else { if (!verse) { if (songHeader) { songHeader = false; w.append("</div>\n<div class='song-content'>\n"); } w.append("<div class='song-verse'>"); verse = true; } w.append("<div class='song-line'>"); w.append(line); w.append(" </div>\n"); } } } if (verse) { // close verse w.append("</div>\n"); verse = false; } w.append("</div>\n</div>\n"); } catch (IOException e) { System.err.println("An appendable must not failed here!"); } return w; } }