diff --git a/src/chordsheet.js b/src/chordsheet.js index 788f32ba..f824a462 100644 --- a/src/chordsheet.js +++ b/src/chordsheet.js @@ -4,6 +4,7 @@ import TextFormatter from './formatter/text_formatter'; import HtmlTableFormatter from './formatter/html_table_formatter'; import HtmlDivFormatter from './formatter/html_div_formatter'; import ChordProFormatter from './formatter/chord_pro_formatter'; +import LatexFormatter from './formatter/latex_formatter'; import ChordLyricsPair from './chord_sheet/chord_lyrics_pair'; import Line from './chord_sheet/line'; import Song from './chord_sheet/song'; @@ -16,6 +17,7 @@ export default { HtmlTableFormatter, HtmlDivFormatter, ChordProFormatter, + LatexFormatter, ChordLyricsPair, Line, Song, diff --git a/src/formatter/latex_formatter.js b/src/formatter/latex_formatter.js new file mode 100644 index 00000000..bba3026e --- /dev/null +++ b/src/formatter/latex_formatter.js @@ -0,0 +1,114 @@ +import Tag, { + ARTIST, + TITLE, + SUBTITLE, + COMMENT, + CAPO, + START_OF_CHORUS, + END_OF_CHORUS, + START_OF_VERSE, + END_OF_VERSE, +} from '../chord_sheet/tag'; +import ChordLyricsPair from '../chord_sheet/chord_lyrics_pair'; + +const NEW_LINE = '\n'; +const flatMap = (arr, fn) => arr.reduce((acc, x) => [...acc, ...fn(x)], []); + +/** + * Formats a song into a .tex file for the Latex songs package. + * http://songs.sourceforge.net/docs.html + */ +class LatexFormatter { + isHeaderTag(item) { + return item instanceof Tag && [TITLE, SUBTITLE, ARTIST].indexOf(item.name) !== -1; + } + + /** + * Formats a song into .tex file. + * @param {Song} song The song to be formatted + * @returns {string} The .tex file contents as string + */ + format(song) { + return [ + this.formatHeaderTags(song), + this.formatOther(song), + '\\endsong', + ].join(NEW_LINE); + } + + formatHeaderTags(song) { + const headerTags = flatMap(song.lines, line => line.items) + .filter(this.isHeaderTag) + .reduce((tmp, tag) => ({ + ...tmp, + [tag.name]: tag, + }), {}); + const title = headerTags.title && headerTags.title.value; + const subtitle = headerTags.subtitle && headerTags.subtitle.value; + const artist = headerTags.artist ? headerTags.artist.value : ''; + const titleString = title + (subtitle ? ` \\\\ ${subtitle}` : ''); + return `\\beginsong{${titleString}}[by={${artist}}]`; + } + + formatOther(song) { + return song.lines.map(line => this.formatLine(line)).join(NEW_LINE); + } + + formatLine(line) { + return line.items.map(item => this.formatItem(item)).join(''); + } + + formatItem(item) { + if (this.isHeaderTag(item)) { + return ''; + } else if (item instanceof Tag) { + return this.formatTag(item); + } else if (item instanceof ChordLyricsPair) { + return this.formatChordLyricsPair(item); + } + + return ''; + } + + formatTag(tag) { + switch (tag.name) { + case COMMENT: + return `\\textcomment{${tag.value}}`; + case CAPO: + return `\\capo{${tag.value}}`; + case START_OF_CHORUS: + return '\\beginchorus'; + case END_OF_CHORUS: + return '\\endchorus'; + case START_OF_VERSE: + return '\\beginverse'; + case END_OF_VERSE: + return '\\endverse'; + default: + return tag.hasValue() + ? `\\textcomment{${tag.originalName}: ${tag.value}}` + : `\\textcomment{${tag.originalName}}`; + } + } + + formatChordLyricsPair(chordLyricsPair) { + return [ + this.formatChordLyricsPairChords(chordLyricsPair), + this.formatChordLyricsPairLyrics(chordLyricsPair), + ].join(''); + } + + formatChordLyricsPairChords(chordLyricsPair) { + if (chordLyricsPair.chords) { + return `\\[${chordLyricsPair.chords}]`; + } + + return ''; + } + + formatChordLyricsPairLyrics(chordLyricsPair) { + return chordLyricsPair.lyrics || ''; + } +} + +export default LatexFormatter; diff --git a/test/formatter/latex_formatter.js b/test/formatter/latex_formatter.js new file mode 100644 index 00000000..56bc6e73 --- /dev/null +++ b/test/formatter/latex_formatter.js @@ -0,0 +1,29 @@ +import { expect } from 'chai'; + +import LatexFormatter from '../../src/formatter/latex_formatter'; +import song from '../fixtures/song'; + +describe('LatexFormatter', () => { + it('formats a song to a .tex file correctly', () => { + const formatter = new LatexFormatter(); + + const expectedChordSheet = ` +\\beginsong{Let it be \\\\ ChordSheetJS example version}[by={}] + + +\\textcomment{x_some_setting} +\\textcomment{Bridge} + +\\beginverse +Let it \\[Am]be, let it \\[C/G]be, let it \\[F]be, let it \\[C]be +\\[C]Whisper words of \\[F]wis\\[G]dom, let it \\[F]be \\[C/E] \\[Dm] \\[C] +\\endverse + +\\beginchorus +\\[Am]Whisper words of \\[Bb]wisdom, let it \\[F]be \\[C] +\\endchorus +\\endsong`.substring(1); + + expect(formatter.format(song)).to.equal(expectedChordSheet); + }); +});