diff --git a/README.md b/README.md index 1b0cac1..3c593ca 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,10 @@ Simply import `OpenLyricsParser`, then pass the contents of an OpenLyrics XML fi ### For TypeScript projects ```typescript import { readFile } from 'fs'; -import { OpenLyricsParser } from 'openlyrics-parser'; -import { IOpenLyricsSong } from 'openlyrics-parser/dist/main/parser.model'; +import { OpenLyricsParser, IParserRoot } from 'openlyrics-parser'; readFile('example.xml', (contents): void => { - const song: IOpenLyricsSong.IRoot = OpenLyricsParser(contents.toString()); + const song: IParserRoot = OpenLyricsParser(contents.toString()); console.log(song); }); ``` @@ -49,10 +48,9 @@ Simply import `OpenLyricsBuilder`, then pass the song data to it, and it will re ### For TypeScript projects ```typescript -import { OpenLyricsBuilder } from 'openlyrics-parser'; -import { INewOpenLyricsSong } from 'openlyrics-parser/dist/main/builder.model'; +import { OpenLyricsBuilder, IBuilderOptions } from 'openlyrics-parser'; -const opts: INewOpenLyricsSong.IOptions = { +const opts: IBuilderOptions = { properties: { titles: 'Amazing Grace' }, verses: [ { diff --git a/docs/Builder.md b/docs/Builder.md index ba097ef..45c2549 100644 --- a/docs/Builder.md +++ b/docs/Builder.md @@ -5,10 +5,9 @@ Simply import `OpenLyricsBuilder`, then pass the song data to it, and it will re ### For TypeScript projects ```typescript -import { OpenLyricsBuilder } from 'openlyrics-parser'; -import { INewOpenLyricsSong } from 'openlyrics-parser/dist/main/builder.model'; +import { OpenLyricsBuilder, IBuilderOptions } from 'openlyrics-parser'; -const opts: INewOpenLyricsSong.IOptions = { +const opts: IBuilderOptions = { properties: { titles: 'Amazing Grace' }, verses: [ { @@ -57,39 +56,39 @@ This object is optional and it contains information about the document itself. T | Property | Type | Required | Default Value | |:--------------|:---------|:---------|:----------------------------| -|`createdIn` | `string` | No | `'openlyrics-parser 1.1.0'` | +|`createdIn` | `string` | No | `'openlyrics-parser 1.1.1'` | |`chordNotation`| `string` | No | `undefined` | |`lang` | `string` | No | `'en'` | -|`modifiedIn` | `string` | No | `'openlyrics-parser 1.1.0'` | +|`modifiedIn` | `string` | No | `'openlyrics-parser 1.1.1'` | ### `properties` Object - ⚠️REQUIRED This required object contains information about the song. Read about the properties in [the OpenLyrics Song Properties docs](https://docs.openlyrics.org/en/latest/dataformat.html#song-properties) -| Property | Type | Required | Description | -|:----------------------------|:------------------------|:---------|:----------------------------| -| `titles` | `string` or `ITitle[]` | ⚠️Yes | For a simple single title just provide a string. For multiples, an array of objects: See [the `ITitle[]` docs](#properties--titles-ititle) below. | -| `authors` | `string` or `IAuthor[]` | No | For a simple single author just provide a string. For multiples, an array of objects: See [the `IAuthor[]` docs](#properties--authors-iauthor) below. | -| `ccliNo` | `string` or `number` | No | The CCLI license number for this song | -| `comments` | `string[]` | No | Any additional, unspecified user data about this song | -| `copyright` | `string` or `number` | No | The copyright information of the song. In some countries it is a legal requirement to display copyright information during the presentation of songs. The tag has no specific format, though it is recommended that the value contains at least the year and copyright holder of the song | -| `key` | `string` | No | The key the song is in, eg: `'C#'` | -| `keywords` | `string` | No | A space separated list of words used for more precise results when searching for a song in a song database | -| `publisher` | `string` | No | The publisher of the song | -| `released` or `releaseDate` | `string` or `number` | No | The year or date when a song was released or published. OpenLyrics 0.8 uses the `releaseDate` property, while 0.9 uses `released`. Using either one will result in the `released` property being set for version 0.9 compatibility | -| `songBooks` | `ISongBook[]` | No | Most songs come from some sort of collection of songs (a book or a folder of some sort). It may be useful to track where the song comes from. See [the `ISongBook[]` docs](#properties--songbooks-isongbook) below.| -| `tempo` | `string` or `number` | No | The tempo of the song, which can be expressed as a number, eg: `90` (as BPM), or as text, eg: `'slow'` or `'moderate'` | -| `themes` | `ITheme[]` | No | Used to categorize the song. See [the `ITheme[]` docs](#properties--themes-itheme) below.| -| `timeSignature` | `string` | No | Used to define the time signature of the song, eg: `'3/4'` | -| `transposition` | `string` or `number` | No | Used when it is necessary to move the key or the pitch of chords up or down. The value must be an integer between `-11` and `11` | -| `variant` | `string` | No | A description which is used to differentiate songs which are identical, but may be performed or sung differently | -| `verseOrder` | `string` | No | A space-separated string of verse and instrument `name`s defines the order in which the verses and instrumental parts are typically sung or performed. eg: `'v1 v2 c c v1'` | -| `version` | `string` or `number` | No | Any text or "version number" of the song since song can be updated over time, sometimes to add additional verses, sometimes to fix spelling or grammatical errors | - - - -#### `properties` => `titles: ITitle[]` +| Property | Type | Required | Description | +|:----------------------------|:-------------------------------|:---------|:----------------------------| +| `titles` | `string` or `IBuilderTitle[]` | ⚠️Yes | For a simple single title just provide a string. For multiples, an array of objects: See [the `IBuilderTitle[]` docs](#properties--titles-ibuildertitle) below. | +| `authors` | `string` or `IBuilderAuthor[]` | No | For a simple single author just provide a string. For multiples, an array of objects: See [the `IBuilderAuthor[]` docs](#properties--authors-ibuilderauthor) below. | +| `ccliNo` | `string` or `number` | No | The CCLI license number for this song | +| `comments` | `string[]` | No | Any additional, unspecified user data about this song | +| `copyright` | `string` or `number` | No | The copyright information of the song. In some countries it is a legal requirement to display copyright information during the presentation of songs. The tag has no specific format, though it is recommended that the value contains at least the year and copyright holder of the song | +| `key` | `string` | No | The key the song is in, eg: `'C#'` | +| `keywords` | `string` | No | A space separated list of words used for more precise results when searching for a song in a song database | +| `publisher` | `string` | No | The publisher of the song | +| `released` or `releaseDate` | `string` or `number` | No | The year or date when a song was released or published. OpenLyrics 0.8 uses the `releaseDate` property, while 0.9 uses `released`. Using either one will result in the `released` property being set for version 0.9 compatibility | +| `songBooks` | `IBuilderSongBook[]` | No | Most songs come from some sort of collection of songs (a book or a folder of some sort). It may be useful to track where the song comes from. See [the `IBuilderSongBook[]` docs](#properties--songbooks-ibuildersongbook) below.| +| `tempo` | `string` or `number` | No | The tempo of the song, which can be expressed as a number, eg: `90` (as BPM), or as text, eg: `'slow'` or `'moderate'` | +| `themes` | `IBuilderTheme[]` | No | Used to categorize the song. See [the `IBuilderTheme[]` docs](#properties--themes-ibuildertheme) below.| +| `timeSignature` | `string` | No | Used to define the time signature of the song, eg: `'3/4'` | +| `transposition` | `string` or `number` | No | Used when it is necessary to move the key or the pitch of chords up or down. The value must be an integer between `-11` and `11` | +| `variant` | `string` | No | A description which is used to differentiate songs which are identical, but may be performed or sung differently | +| `verseOrder` | `string` | No | A space-separated string of verse and instrument `name`s defines the order in which the verses and instrumental parts are typically sung or performed. eg: `'v1 v2 c c v1'` | +| `version` | `string` or `number` | No | Any text or "version number" of the song since song can be updated over time, sometimes to add additional verses, sometimes to fix spelling or grammatical errors | + + + +#### `properties` => `titles: IBuilderTitle[]` If a song has more than one title you must provide the titles in the form of these object. Each title object can have these properties. See [the OpenLyrics Titles documentation](https://docs.openlyrics.org/en/latest/dataformat.html#titles) for more information | Property | Type | Required | Description | |:----------------|:---------|:---------|:--------------------------| @@ -119,7 +118,7 @@ If a song has more than one title you must provide the titles in the form of the -#### `properties` => `authors: IAuthor[]` +#### `properties` => `authors: IBuilderAuthor[]` If a song has more than one author you must provide the authors in the form of these object. Each author object can have these properties. See [the OpenLyrics Authors documentation](https://docs.openlyrics.org/en/latest/dataformat.html#authors) for more information | Property | Type | Required | Description | |:----------|:---------|:---------|:--------------------------| @@ -139,7 +138,7 @@ If a song has more than one author you must provide the authors in the form of t -#### `properties` => `songBooks: ISongBook[]` +#### `properties` => `songBooks: IBuilderSongBook[]` Most songs come from some sort of collection of songs (a book or a folder of some sort). It may be useful to track where the song comes from. Each song book object can have these properties. Read more about them in [the OpenLyrics SongBooks docs](https://docs.openlyrics.org/en/latest/dataformat.html#song-books) | Property | Type | Required | Description | |:----------|:---------------------|:---------|:--------------------------| @@ -157,7 +156,7 @@ Most songs come from some sort of collection of songs (a book or a folder of som -#### `properties` => `themes: ITheme[]` +#### `properties` => `themes: IBuilderTheme[]` Themes are used to categorize songs. Having songs categorized can be useful when choosing songs for a ceremony or for a particular sermon topic. Each theme just has a simple string value and an optional language specified. Read about them in [the OpenLyrics Themes docs](https://docs.openlyrics.org/en/latest/dataformat.html#themes) | Property | Type | Required | Description | @@ -183,14 +182,14 @@ Themes are used to categorize songs. Having songs categorized can be useful when ### `format` Array This optional object contains information about any custom format tags on the document. Read about them in [the OpenLyrics Formatting Extensions docs](https://docs.openlyrics.org/en/latest/dataformat.html#formatting-extensions). You should only need to use this if your song will use custom `` nodes which your song presentation software can interpret. -| Property | Type | Required | Default Value | -|:--------------|:---------------|:---------|:----------------------------| -|`application` | `string` | ⚠️Yes | The name of the target application or processor identifier that will use the specified format tags | -|`tags` | `IFormatTag[]` | ⚠️Yes | An array of tag objects, described below | +| Property | Type | Required | Default Value | +|:--------------|:----------------------|:---------|:----------------------------| +|`application` | `string` | ⚠️Yes | The name of the target application or processor identifier that will use the specified format tags | +|`tags` | `IBuilderFormatTag[]` | ⚠️Yes | An array of tag objects, described below | -#### `format` => `tags: IFormatTag[]` +#### `format` => `tags: IBuilderFormatTag[]` | Property | Type | Required | Default Value | |:---------|:---------|:---------|:----------------------------| |`name` | `string` | ⚠️Yes | The name of this tag | @@ -224,26 +223,26 @@ This optional object contains information about any custom format tags on the do ### `verses` Array - ⚠️REQUIRED This required array of objects is used to describe all of the words of the song and other data related to them. Read about the supported properties on [the OpenLyrics Song Lyrics docs](https://docs.openlyrics.org/en/latest/dataformat.html#song-lyrics) -| Property | Type | Required | Default Value | -|:-----------------|:-----------------------------|:---------|:----------------------------| -|`name` | `string` | ⚠️Yes | The name of this verse. Can be anything string like: `'v1'`, `'v2'`, `'c'`, etc. | -|`lines` | `string[]` or `IVerseLine[]` | ⚠️Yes | Each verse can contain multiple "lines" of text. Pass a string in the array for each line. Any `\n`or `\r` line break character will be transformed into a `
` tag. Alternatively for anything more complex an `IVerseLine[]` may be passed instead of a plain `string[]` See [the `IVerseLine[]` Docs](#verses--lines-iverseline) below. | -|`optionalBreak` | `boolean` | No | If `true` an application can decide to break the verse in two slides if it doesn’t fit on one screen | -|`lang` | `string` | No | The language this verse is written in. This should match languages specified elsewhere if different from the document language. | -|`transliteration` | `string` | No | If a verse has been transliterated from another language, store that language code here. Only set this when also setting `lang` | +| Property | Type | Required | Default Value | +|:-----------------|:------------------------------------|:---------|:----------------------------| +|`name` | `string` | ⚠️Yes | The name of this verse. Can be anything string like: `'v1'`, `'v2'`, `'c'`, etc. | +|`lines` | `string[]` or `IBuilderVerseLine[]` | ⚠️Yes | Each verse can contain multiple "lines" of text. Pass a string in the array for each line. Any `\n`or `\r` line break character will be transformed into a `
` tag. Alternatively for anything more complex an `IBuilderVerseLine[]` may be passed instead of a plain `string[]` See [the `IBuilderVerseLine[]` Docs](#verses--lines-ibuilderverseline) below. | +|`optionalBreak` | `boolean` | No | If `true` an application can decide to break the verse in two slides if it doesn’t fit on one screen | +|`lang` | `string` | No | The language this verse is written in. This should match languages specified elsewhere if different from the document language. | +|`transliteration` | `string` | No | If a verse has been transliterated from another language, store that language code here. Only set this when also setting `lang` | -#### `verses` => `lines: IVerseLine[]` +#### `verses` => `lines: IBuilderVerseLine[]` | Property | Type | Required | Default Value | |:-----------------|:-----------------------------|:---------|:----------------------------| -| `content` | `IVerseLineContent[]` | ⚠️Yes | An array of items that can appear on this line of this verse. See [the `IVerseLineContent[]` docs](#verses--lines--content-iverselinecontent) below. | +| `content` | `IBuilderVerseLineContent[]` | ⚠️Yes | An array of items that can appear on this line of this verse. See [the `IBuilderVerseLineContent[]` docs](#verses--lines--content-ibuilderverselinecontent) below. | | `part` | `string` | No | This line of this verse can have a part, for example `'men'` or `'women'` are common parts to use. Read more about this on [the OpenLyrics Verse Parts Docs](https://docs.openlyrics.org/en/latest/dataformat.html#verse-parts-groups-of-lines) | | `optionalBreak` | `boolean` | No | If `true` an application can decide to break this line in two slides if it doesn’t fit on one screen | | `repeat` | `number` | No | The number of times this particular line should be repeated. Read more about this on [the OpenLyrics Line Repeat Docs](https://docs.openlyrics.org/en/latest/dataformat.html#line-repeat) | -#### `verses` => `lines` => `content: IVerseLineContent[]` +#### `verses` => `lines` => `content: IBuilderVerseLineContent[]` The content of a line on a verse can be one of four types: `'text'`, `'comment'`, `'tag'`, or `'chord'` . Each of these object types have slightly different properties. See the examples below to understand how to best use the objects together to get the desired XML output. @@ -338,23 +337,23 @@ verses: { ### `instruments` Array This optional array of objects is used to describe all of the instrumental music in the song. It is very similar to the above `verses` but it cannot contain any words, only `chord`s or `beat`s. All `beat`s may only contain `chord`s. No text is allowed anywhere inside of `instruments`. Read about the supported properties on [the OpenLyrics Instrumental Parts docs](https://docs.openlyrics.org/en/latest/dataformat.html#instrumental-parts) -| Property | Type | Required | Default Value | -|:----------|:--------------------|:---------|:----------------------------| -|`name` | `string` | ⚠️Yes | The name of this instrumental section. Should be similar to a verse name like `'i'` (intro), `'s'` (solo), etc. See [the docs](https://docs.openlyrics.org/en/latest/dataformat.html#instrumental-parts) for examples. | -|`lines` | `IInstrumentLine[]` | ⚠️Yes | An array of instrument lines. See below for details. | +| Property | Type | Required | Default Value | +|:----------|:---------------------------|:---------|:----------------------------| +|`name` | `string` | ⚠️Yes | The name of this instrumental section. Should be similar to a verse name like `'i'` (intro), `'s'` (solo), etc. See [the docs](https://docs.openlyrics.org/en/latest/dataformat.html#instrumental-parts) for examples. | +|`lines` | `IBuilderInstrumentLine[]` | ⚠️Yes | An array of instrument lines. See below for details. | -#### `instruments` => `lines: IInstrumentLine[]` -| Property | Type | Required | Default Value | -|:----------|:---------------------------|:---------|:----------------------------| -|`content` | `IInstrumentLineContent[]` | ⚠️Yes | An array of objects to create ``s and ``s in this line | -|`part` | `string` | No | The name of this instrumental part, eg: `'piano'`, `'guitar'`, etc. | -|`repeat` | `number` | No | The number of times this line should be repeated | +#### `instruments` => `lines: IBuilderInstrumentLine[]` +| Property | Type | Required | Default Value | +|:----------|:----------------------------------|:---------|:----------------------------| +|`content` | `IBuilderInstrumentLineContent[]` | ⚠️Yes | An array of objects to create ``s and ``s in this line | +|`part` | `string` | No | The name of this instrumental part, eg: `'piano'`, `'guitar'`, etc. | +|`repeat` | `number` | No | The number of times this line should be repeated | -#### `instruments` => `lines` => `content: IInstrumentLineContent[]` +#### `instruments` => `lines` => `content: IBuilderInstrumentLineContent[]` The `content` array for each line of an instrumental part can only contain one of two types of objects. `type: 'chord'`, or `type: 'beat'`. Nothing else is possible to add here. An object with `type: 'chord'` works exactly like it does above for verses. An object with `type: 'beat'` simply contains an array of `type: 'chord'` objects, it is only there as a container for chords. diff --git a/docs/Parser.md b/docs/Parser.md index 8d075f4..9355263 100644 --- a/docs/Parser.md +++ b/docs/Parser.md @@ -7,11 +7,10 @@ Simply import `OpenLyricsParser`, then pass the contents of an OpenLyrics XML fi Full type definitions are provided. You may need to import them separately, as shown below, if you need to explicitly provide type definitions to something. ```typescript import { readFile } from 'fs'; -import { OpenLyricsParser } from 'openlyrics-parser'; -import { IOpenLyricsSong } from 'openlyrics-parser/dist/main/parser.model'; +import { OpenLyricsParser, IParserRoot } from 'openlyrics-parser'; readFile('example.xml', (contents): void => { - const song: IOpenLyricsSong.IRoot = OpenLyricsParser(contents.toString()); + const song: IParserRoot = OpenLyricsParser(contents.toString()); console.log(song); }); ``` @@ -138,31 +137,31 @@ It only has a few primitive properties. If no value is provided strings will def ## `properties` Object This object contains information about the song. Read about the properties in [the OpenLyrics Song Properties docs](https://docs.openlyrics.org/en/latest/dataformat.html#song-properties) -| Property | Return Type | -|:-------------------------------------------|:---------------------------------------------| -|[`authors`](#properties--authors-array) | [`IAuthor[]`](#properties--authors-array) | -|[`titles`](#properties--titles-array) | [`ITitle[]`](#properties--titles-array) | -|[`songBooks`](#properties--songbooks-array) | [`ISongBook[]`](#properties--songbooks-array)| -|`comments` | `string[]` | -|`copyright` | `string` | -|`ccliNo` | `string` | -|`released` (or `releaseDate`) | `string` | -|`transposition` | `string` | -|`tempo` | `string` | -|`tempoType` | `string` | -|`timeSignature` | `string` | -|[`themes`](#properties--themes-array) | [`ITheme[]`](#properties--themes-array) | -|`key` | `string` | -|`variant` | `string` | -|`publisher` | `string` | -|`version` | `string` | -|`keywords` | `string` | -|`verseOrder` | `string` | +| Property | Return Type | +|:-------------------------------------------|:---------------------------------------------------| +|[`authors`](#properties--authors-array) | [`IParserAuthor[]`](#properties--authors-array) | +|[`titles`](#properties--titles-array) | [`IParserTitle[]`](#properties--titles-array) | +|[`songBooks`](#properties--songbooks-array) | [`IParserSongBook[]`](#properties--songbooks-array)| +|`comments` | `string[]` | +|`copyright` | `string` | +|`ccliNo` | `string` | +|`released` (or `releaseDate`) | `string` | +|`transposition` | `string` | +|`tempo` | `string` | +|`tempoType` | `string` | +|`timeSignature` | `string` | +|[`themes`](#properties--themes-array) | [`IParserTheme[]`](#properties--themes-array) | +|`key` | `string` | +|`variant` | `string` | +|`publisher` | `string` | +|`version` | `string` | +|`keywords` | `string` | +|`verseOrder` | `string` | ## `properties` => `authors` array -| `IAuthor` Property | Return Type | +| `IParserAuthor` Property | Return Type | |:-------------------|:------------| |`value` | `string` | |`type` | `string` | @@ -190,12 +189,12 @@ the following array is produced ## `properties` => `titles` array -| `ITitle` Property | Return Type | -|:-------------------|:--------------------| -|`value` | `string` | -|`lang` | `string` | -|`transliteration` | `string` | -|`original` | `boolean` or `null` | +| `IParserTitle` Property | Return Type | +|:-------------------------|:--------------------| +|`value` | `string` | +|`lang` | `string` | +|`transliteration` | `string` | +|`original` | `boolean` or `null` | given this titles XML ```xml @@ -220,7 +219,7 @@ the following array is produced ## `properties` => `songBooks` array -| `ISongBook` Property | Return Type | +| `IParserSongBook` Property | Return Type | |:---------------------|:------------| |`name` | `string` | |`entry` | `string` | @@ -246,10 +245,10 @@ the following array is produced ## `properties` => `themes` array -| `ITheme` Property | Return Type | -|:------------------|:---------| -|`value` | `string` | -|`lang` | `string` | +| `IParserTheme` Property | Return Type | +|:------------------------|:------------| +|`value` | `string` | +|`lang` | `string` | given this titles XML @@ -284,19 +283,19 @@ the following array is produced ## `format` Object This object contains information about any custom format tags on the document. Read about them in [the OpenLyrics Formatting Extensions docs](https://docs.openlyrics.org/en/latest/dataformat.html#formatting-extensions) -| Property | Return Type | -|:-----------------------------|:---------------------------------------| -|`application` | `string` | -|[`tags`](#format--tags-array) | [`IFormatTag[]`](#format--tags-array) | +| Property | Return Type | +|:-----------------------------|:--------------------------------------------| +|`application` | `string` | +|[`tags`](#format--tags-array) | [`IParserFormatTag[]`](#format--tags-array) | ## `format` => `tags` array -| `IFormatTag` Property | Return Type | -|:----------------------|:------------| -|`name` | `string` | -|`open` | `string` | -|`close` | `string` | +| `IParserFormatTag` Property | Return Type | +|:----------------------------|:------------| +|`name` | `string` | +|`open` | `string` | +|`close` | `string` | For example, if the following `` XML is provided: ```xml @@ -347,22 +346,22 @@ The following JSON object will be produced on the `format` property This is an array of objects to describe all of the `` nodes inside of the `` node. Read about the supported properties on [the OpenLyrics Song Lyrics docs](https://docs.openlyrics.org/en/latest/dataformat.html#song-lyrics) Each item in the `verses` array has the following properties -| `IVerse` Property | Return Type | +| `IParserVerse` Property | Return Type | |:-------------------------------|:---------------------------------------| |`name` | `string` | |`lang` | `string` | |`transliteration` | `string` | |`break` | `string` | -|[`lines`](#verses--lines-array) | [`IVerseLine[]`](#verses--lines-array) | +|[`lines`](#verses--lines-array) | [`IParserVerseLine[]`](#verses--lines-array) | ## `verses` => `lines` array -| `IVerseLine` Property | Type | +| `IParserVerseLine` Property | Type | |:------------------------------------------|:-------------------------------------------------------| |`break` | `string` | -|[`content`](#verses--lines--content-array) | [`IVerseLineContent[]`](#verses--lines--content-array) | +|[`content`](#verses--lines--content-array) | [`IParserVerseLineContent[]`](#verses--lines--content-array) | |`part` | `string` | |`repeat` | `string | number` | @@ -371,34 +370,34 @@ See below for an example of the XML to JSON transformation after you have read t ## `verses` => `lines` => `content` array -Each `IVerseLineContent` object in this `content` array can be one of 4 different types of objects, which are all very similar. +Each `IParserVerseLineContent` object in this `content` array can be one of 4 different types of objects, which are all very similar. They all have the `type` property which is set to specific string values for each object type. Note that any `
` nodes inside of these objects will be transformed into `\n` characters -| `IVerseLineContent` for **text** | Return Type | -|:--------------------------------------|:------------| -|`type` | `'text'` | -|`value` | `string` | - -| `IVerseLineContent` for a **comment** | Return Type | -|:--------------------------------------|:------------| -|`type` | `'comment'` | -|`value` | `string` | - -| `IVerseLineContent` for a **tag** | Return Type | -|:--------------------------------------|:------------| -|`type` | `'tag'` | -|`value` | `string` | - -| `IVerseLineContent` for a **chord** | Return Type | Extra Info | -|:------------------------------------|:------------|:-----------| -|`type` | `'chord'` | | -|`bass` | `string` | Property only present if it is on the parsed `` tag | -|`value` | `string` | Property only present if it is on the parsed `` tag | -|`name` | `string` | Property only present if it is on the parsed `` tag. Note: this property is only used in OpenLyrics 0.8 or older. Version 0.9 will use the `root` property instead. | -|`root` | `string` | Property only present if it is on the parsed `` tag | -|`structure` | `string` | Property only present if it is on the parsed `` tag | -|`upbeat` | `string` | Property only present if it is on the parsed `` tag | +| `IParserVerseLineContent` for **text** | Return Type | +|:---------------------------------------|:------------| +|`type` | `'text'` | +|`value` | `string` | + +| `IParserVerseLineContent` for a **comment** | Return Type | +|:--------------------------------------------|:------------| +|`type` | `'comment'` | +|`value` | `string` | + +| `IParserVerseLineContent` for a **tag** | Return Type | +|:----------------------------------------|:------------| +|`type` | `'tag'` | +|`value` | `string` | + +| `IParserVerseLineContent` for a **chord** | Return Type | Extra Info | +|:------------------------------------------|:------------|:-----------| +|`type` | `'chord'` | | +|`bass` | `string` | Property only present if it is on the parsed `` tag | +|`value` | `string` | Property only present if it is on the parsed `` tag | +|`name` | `string` | Property only present if it is on the parsed `` tag. Note: this property is only used in OpenLyrics 0.8 or older. Version 0.9 will use the `root` property instead. | +|`root` | `string` | Property only present if it is on the parsed `` tag | +|`structure` | `string` | Property only present if it is on the parsed `` tag | +|`upbeat` | `string` | Property only present if it is on the parsed `` tag | Note: Chords may also return any other properties that are placed on them since they seem to be used in a diverse way. @@ -492,34 +491,34 @@ Which will produce this object: This is an array of objects to describe all of the `` nodes inside of the `` node. It is very similar to the above `verses` but each `` node is only allowed to contain either `` or `` nodes. All `` nodes may only contain `` nodes. No text is allowed anywhere inside of ``. Read about the supported properties on [the OpenLyrics Instrumental Parts docs](https://docs.openlyrics.org/en/latest/dataformat.html#instrumental-parts) Each item in the `instruments` array has the following properties -| `IInstrument` Property | Return Type | +| `IParserInstrument` Property | Return Type | |:------------------------------------|:-------------------------------------------------| |`name` | `string` | -|[`lines`](#instruments--lines-array) | [`IInstrumentLine[]`](#instruments--lines-array) | +|[`lines`](#instruments--lines-array) | [`IParserInstrumentLine[]`](#instruments--lines-array) | ## `instruments` => `lines` array -| `IInstrumentLine` Property | Return Type | -|:---------------------------|:---------------------------| -|`content` | `IInstrumentLineContent[]` | -|`part` | `string` | -|`repeat` | `string | number` | +| `IParserInstrumentLine` Property | Return Type | +|:---------------------------------|:---------------------------| +|`content` | `IParserInstrumentLineContent[]` | +|`part` | `string` | +|`repeat` | `string | number` | ## `instruments` => `lines` => `content` array -Each `IInstrumentLineContent` object in this `content` array can be one of 2 different types of objects: a **beat** and a **chord**. A beat can only contain an array of chords. No text is allowed here. Both objects have a `type` property so they can be differentiated. - -| `IInstrumentLineContent` for **beat** | Return Type | -|:-------------------------------------------|:------------| -|`type` | `'beat'` | -|`chords` | `[]` of chord objects (below) | - -| `IInstrumentLineContent` for a **chord** | Return Type | Extra Info | -|:-------------------------------------------|:------------|:-----------| -|`type` | `'chord'` | Property only present if it is on the parsed `` tag. | -|`name?` | `string` | Property only present if it is on the parsed `` tag. Note: this property is only used in OpenLyrics 0.8 or older. Version 0.9 will use the `root` property instead. | -|`root?` | `string` | Property only present if it is on the parsed `` tag. | -|`structure?` | `string` | Property only present if it is on the parsed `` tag. | -|`upbeat?` | `string` | Property only present if it is on the parsed `` tag. | +Each `IParserInstrumentLineContent` object in this `content` array can be one of 2 different types of objects: a **beat** and a **chord**. A beat can only contain an array of chords. No text is allowed here. Both objects have a `type` property so they can be differentiated. + +| `IParserInstrumentLineContent` for **beat** | Return Type | +|:--------------------------------------------|:------------| +|`type` | `'beat'` | +|`chords` | `[]` of chord objects (below) | + +| `IParserInstrumentLineContent` for a **chord** | Return Type | Extra Info | +|:-----------------------------------------------|:------------|:-----------| +|`type` | `'chord'` | Property only present if it is on the parsed `` tag. | +|`name?` | `string` | Property only present if it is on the parsed `` tag. Note: this property is only used in OpenLyrics 0.8 or older. Version 0.9 will use the `root` property instead. | +|`root?` | `string` | Property only present if it is on the parsed `` tag. | +|`structure?` | `string` | Property only present if it is on the parsed `` tag. | +|`upbeat?` | `string` | Property only present if it is on the parsed `` tag. | Note: Chords may also return any other properties that are placed on them since they seem to be used in a diverse way. diff --git a/package.json b/package.json index 14e246c..cc7cf4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openlyrics-parser", - "version": "1.1.0", + "version": "1.1.1", "description": "Parses and extracts data from OpenLyrics files, and creates them!", "main": "dist/main/index.js", "typings": "dist/main/index.d.ts", @@ -86,7 +86,7 @@ }, "devDependencies": { "@types/jest": "^29.5.2", - "@types/node": "^20.2.6", + "@types/node": "^20.3.0", "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", "eslint": "^8.42.0", diff --git a/src/builder.model.ts b/src/builder.model.ts index 11190b6..71d5b88 100644 --- a/src/builder.model.ts +++ b/src/builder.model.ts @@ -2,61 +2,28 @@ //Most everything here is similar to the parser.model except nothing has // index properties and many properties are optional -export namespace INewOpenLyricsSong { - export interface IBuilderObject { - '?xml': { - '@version': string; - '@encoding': string; - }; - '?xml-stylesheet': { - '@href': string; - '@type': string; - }; - song: { - '@xmlns': string; - '@xml:lang': string; - '@version': string; - '@createdIn': string; - '@chordNotation'?: string; - '@modifiedIn': string; - '@modifiedDate': string; - properties: IPropertiesXml; - //We need to define the format as optional so we can remove it. - //We need to be able to remove it so that we can place above the lyrics in the output. - //So if a format option is provided it will be filled in, if not it will be removed, - //This prevents an empty format XML node from being output - format?: { - tags?: IFormatXml[]; - }; - lyrics: { - instrument?: IInstrumentXml[]; - verse: IVerseXml[]; - }; - }; - } - - export interface IOptions { - format?: IFormat[]; - instruments?: IInstrument[]; - meta?: IMeta; - properties: IProperties; - verses: IVerse[]; - } +export interface IBuilderOptions { + format?: IBuilderFormat[]; + instruments?: IBuilderInstrument[]; + meta?: IBuilderMeta; + properties: IBuilderProperties; + verses: IBuilderVerse[]; +} - //============================================ - //Meta - export interface IMeta { - chordNotation?: string; - createdIn?: string; - modifiedIn?: string; - lang?: string; - } +//============================================ +//Meta +export interface IBuilderMeta { + chordNotation?: string; + createdIn?: string; + modifiedIn?: string; + lang?: string; +} - //============================================ - //Properties - export interface IProperties { - [key: string]: any; - /** +//============================================ +//Properties +export interface IBuilderProperties { + [key: string]: any; + /** * @description The single author of this song as a simple string, or an array of IAuthor objects * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#authors} * @example @@ -67,293 +34,204 @@ export namespace INewOpenLyricsSong { { value: 'František Foo', type: 'translation', lang: 'cs' }, ] */ - authors?: string | IAuthor[]; - /** - * @description CCLI stands for Christian Copyright Licensing International. CCLI is an organization that offers copyright licensing of songs and other resource materials to churches and Christian organizations for use in Christian worship. For registered churches, CCLI offers songs and other resources for download. A CCLI ID is assigned to every song. This provides integration with CCLI. - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#ccli-number} - */ - ccliNo?: string | number; - /** - * @description Used to store any additional, unspecified user data in a free-form text field - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#comments} - */ - comments?: string[]; - /** - * @description The copyright information of the song. In some countries it is a legal requirement to display copyright information during the presentation of songs. It has no specific format, though it is recommended that the value contains at least the year and copyright holder of the song. - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#copyright} - */ - copyright?: string | number; - /** - * @description The key determines the musical scale of a song. See the documentation link for examples - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#key} - */ - key?: string; - /** - * @description - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#keywords} - */ - keywords?: string; - /** - * @description The name of the publisher of the song - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#publisher} - */ - publisher?: string; - /** - * @description Tracks the date when a song was released or published. it can just be a year, or a year and a month, or even a specific time. See the documentation link for examples. - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#release-date} - */ - released?: string | number; - /** - * @description An array of ISongBook objects. Most songs come from some sort of collection of songs, be it a book or a folder of some sort. It may be useful to track where the song comes from. - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#song-books} - * @example - * [ - * {name: "Rippon's Selection of Hymns"}, - * {name: "Teszt könyv", entry: "166"} - * ] - */ - songBooks?: ISongBook[]; - /** - * @description The tempo of a song defines the speed at which a song is to be played. It could be expressed as a number in beats per minute (BPM) or as any text value like "slow" or "moderate". - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#tempo} - */ - tempo?: string | number; - /** - * @description Themes are used to categorize songs. Having songs categorized can be useful when choosing songs for a ceremony or for a particular sermon topic. - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#themes} - */ - themes?: ITheme[]; - /** - * @description Defines the time signature of the song - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#time-signature} - */ - timeSignature?: string; - /** - * @description A single title as a string or a list of ITitle objects for this song. At least one title is required. - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#titles} - * @example - * [ - * { value: 'Amazing Grace', lang: 'en' }, - * { value: 'Erstaunliche Anmut', lang: 'de' } - * ] - */ - titles: ITitle[] | string; - /** - * @description Used when it is necessary to move the key or the pitch of chords up or down. The value must be an integer between -11 and 11 - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#transposition} - */ - transposition?: string | number; - /** - * @description Used to differentiate between songs which are identical, but may be performed or sung differently. - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#variant} - */ - variant?: string; - /** - * @description Defines the order in which the verses and instrumental parts are typically sung or performed. It is a space-separated string of verse and instrumental names (which are defined on the verses and instruments). Verse names can appear multiple times, and should be lowercase. - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#verse-order} - */ - verseOrder?: string; - /** - * @description No song is ever created once, never to be edited again. Songs are updated over time, sometimes to add additional verses, sometimes to fix spelling or grammatical errors. This tag can contain any arbitrary text which could help the user to distinguish between various versions of a song, like a version number, a date, or a description - * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#custom-version} - */ - version?: string | number; - } - - export interface IPropertiesXml { - [key: string]: any; - authors?: { - author: IAuthorXml[]; - }; - ccliNo?: string; - comments?: { - comment: string[]; - }; - copyright?: string | number; - key?: string; - keywords?: string; - publisher?: string; - released?: string; - songbooks?: { - songbook: ISongBookXml[]; - }; - tempo?: { - '#text': string | number; - '@type'?: string; - }; - tempoType?: string; - themes?: { - theme: IThemeXml[]; - }; - titles: { - title: ITitleXml[]; - }; - transposition?: string | number; - variant?: string; - verseOrder?: string; - version?: string; - } - - export interface IAuthor { - lang?: string; - type?: string; - value: string; - } - - export interface IAuthorXml { - '@lang'?: string; - '@type'?: string; - '#text': string; - } - - export interface ITitle { - lang?: string; - original?: boolean; - transliteration?: string; - value: string; - } - - export interface ITitleXml { - '@lang'?: string; - '@original'?: string; - '@translit'?: string; - '#text': string; - } - - export interface ITheme { - lang?: string; - value: string; - } - - export interface IThemeXml { - '@lang'?: string; - '#text': string; - } - - export interface ISongBook { - entry?: string | number; - name: string; - } - - export interface ISongBookXml { - '@entry'?: string | number; - '@name': string; - } - - //============================================ - //Format - export interface IFormat { - application: string; - tags: IFormatTag[]; - } - - export interface IFormatXml { - '@application': string; - tag: IFormatTagXml[]; - } + authors?: string | IBuilderAuthor[]; + /** + * @description CCLI stands for Christian Copyright Licensing International. CCLI is an organization that offers copyright licensing of songs and other resource materials to churches and Christian organizations for use in Christian worship. For registered churches, CCLI offers songs and other resources for download. A CCLI ID is assigned to every song. This provides integration with CCLI. + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#ccli-number} + */ + ccliNo?: string | number; + /** + * @description Used to store any additional, unspecified user data in a free-form text field + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#comments} + */ + comments?: string[]; + /** + * @description The copyright information of the song. In some countries it is a legal requirement to display copyright information during the presentation of songs. It has no specific format, though it is recommended that the value contains at least the year and copyright holder of the song. + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#copyright} + */ + copyright?: string | number; + /** + * @description The key determines the musical scale of a song. See the documentation link for examples + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#key} + */ + key?: string; + /** + * @description + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#keywords} + */ + keywords?: string; + /** + * @description The name of the publisher of the song + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#publisher} + */ + publisher?: string; + /** + * @description Tracks the date when a song was released or published. it can just be a year, or a year and a month, or even a specific time. See the documentation link for examples. + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#release-date} + */ + released?: string | number; + /** + * @description An array of ISongBook objects. Most songs come from some sort of collection of songs, be it a book or a folder of some sort. It may be useful to track where the song comes from. + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#song-books} + * @example + * [ + * {name: "Rippon's Selection of Hymns"}, + * {name: "Teszt könyv", entry: "166"} + * ] + */ + songBooks?: IBuilderSongBook[]; + /** + * @description The tempo of a song defines the speed at which a song is to be played. It could be expressed as a number in beats per minute (BPM) or as any text value like "slow" or "moderate". + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#tempo} + */ + tempo?: string | number; + /** + * @description Themes are used to categorize songs. Having songs categorized can be useful when choosing songs for a ceremony or for a particular sermon topic. + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#themes} + */ + themes?: IBuilderTheme[]; + /** + * @description Defines the time signature of the song + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#time-signature} + */ + timeSignature?: string; + /** + * @description A single title as a string or a list of ITitle objects for this song. At least one title is required. + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#titles} + * @example + * [ + * { value: 'Amazing Grace', lang: 'en' }, + * { value: 'Erstaunliche Anmut', lang: 'de' } + * ] + */ + titles: IBuilderTitle[] | string; + /** + * @description Used when it is necessary to move the key or the pitch of chords up or down. The value must be an integer between -11 and 11 + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#transposition} + */ + transposition?: string | number; + /** + * @description Used to differentiate between songs which are identical, but may be performed or sung differently. + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#variant} + */ + variant?: string; + /** + * @description Defines the order in which the verses and instrumental parts are typically sung or performed. It is a space-separated string of verse and instrumental names (which are defined on the verses and instruments). Verse names can appear multiple times, and should be lowercase. + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#verse-order} + */ + verseOrder?: string; + /** + * @description No song is ever created once, never to be edited again. Songs are updated over time, sometimes to add additional verses, sometimes to fix spelling or grammatical errors. This tag can contain any arbitrary text which could help the user to distinguish between various versions of a song, like a version number, a date, or a description + * @see {@link https://docs.openlyrics.org/en/latest/dataformat.html#custom-version} + */ + version?: string | number; +} - export interface IFormatTag { - close: string; - name: string; - open: string; - } - export interface IFormatTagXml { - close: string; - '@name': string; - open: string; - } +export interface IBuilderAuthor { + lang?: string; + type?: string; + value: string; +} - //============================================ - //Chord base interface, Verses & Instruments extend this interface which we do not export - interface IChordBase { - bass?: string; - root?: string; - structure?: string; - type: 'chord'; - upbeat?: boolean; - } +export interface IBuilderTitle { + lang?: string; + original?: boolean; + transliteration?: string; + value: string; +} - //============================================ - //Verses - export interface IVerse { - optionalBreak?: boolean; - lang?: string; - lines: string[] | IVerseLine[]; - name: string; - transliteration?: string; - } +export interface IBuilderTheme { + lang?: string; + value: string; +} - export interface IVerseXml { - '@break'?: 'optional'; - '@lang'?: string; - lines: IVerseLineXml[]; - '@name': string; - '@transliteration'?: string; - } +export interface IBuilderSongBook { + entry?: string | number; + name: string; +} - export interface IVerseLine { - content: IVerseLineContent[]; - part?: string; - optionalBreak?: boolean; - repeat?: number; - } +//============================================ +//Format +export interface IBuilderFormat { + application: string; + tags: IBuilderFormatTag[]; +} - export interface IVerseLineXml { - '#text': string; - '@part'?: string; - '@break'?: string; - '@repeat'?: number; - } +export interface IBuilderFormatTag { + close: string; + name: string; + open: string; +} - export interface IVerseChord extends IChordBase { - value?: string; - } +//============================================ +//Chord base interface, Verses & Instruments extend this interface which we do not export +interface IBuilderChordBase { + bass?: string; + root?: string; + structure?: string; + type: 'chord'; + upbeat?: boolean; +} - export type IVerseLineContent = IVerseLineContentStandard | IVerseLineContentTag | IVerseChord; +//============================================ +//Verses +export interface IBuilderVerse { + optionalBreak?: boolean; + lang?: string; + lines: string[] | IBuilderVerseLine[]; + name: string; + transliteration?: string; +} - export interface IVerseLineContentStandard { - type: 'text' | 'comment'; - value: string; - } +export interface IBuilderVerseLine { + content: IBuilderVerseLineContent[]; + part?: string; + optionalBreak?: boolean; + repeat?: number; +} - export interface IVerseLineContentTag { - name: string; - type: 'tag'; - value: string; - } +export interface IBuilderVerseChord extends IBuilderChordBase { + value?: string; +} - //============================================ - //Instruments - export interface IInstrument { - lines: IInstrumentLine[]; - name: string; - } +export type IBuilderVerseLineContent = + | IBuilderVerseLineContentStandard + | IBuilderVerseLineContentTag + | IBuilderVerseChord; - export interface IInstrumentXml { - lines: IInstrumentLineXml[]; - '@name': string; - } +export interface IBuilderVerseLineContentStandard { + type: 'text' | 'comment'; + value: string; +} - export interface IInstrumentLine { - content: IInstrumentLineContent[]; - part?: string; - repeat?: number; - } +export interface IBuilderVerseLineContentTag { + name: string; + type: 'tag'; + value: string; +} - export interface IInstrumentLineXml { - '#text': string; - '@part'?: string; - '@repeat'?: number; - } +//============================================ +//Instruments +export interface IBuilderInstrument { + lines: IBuilderInstrumentLine[]; + name: string; +} - export interface IInstrumentChord extends IChordBase { - //Same as the base chord object but renamed here - } +export interface IBuilderInstrumentLine { + content: IBuilderInstrumentLineContent[]; + part?: string; + repeat?: number; +} - export interface IInstrumentLineContentBeat { - chords: IInstrumentChord[]; - type: 'beat'; - } +export interface IBuilderInstrumentChord extends IBuilderChordBase { + //Same as the base chord object but renamed here +} - export type IInstrumentLineContent = IInstrumentLineContentBeat | IInstrumentChord; +export interface IBuilderInstrumentLineContentBeat { + chords: IBuilderInstrumentChord[]; + type: 'beat'; } + +export type IBuilderInstrumentLineContent = + | IBuilderInstrumentLineContentBeat + | IBuilderInstrumentChord; diff --git a/src/builder.spec.ts b/src/builder.spec.ts index 9802eef..b56d1b2 100644 --- a/src/builder.spec.ts +++ b/src/builder.spec.ts @@ -1,4 +1,4 @@ -import { INewOpenLyricsSong } from './builder.model'; +import * as builderModel from './builder.model'; import { OpenLyricsBuilder } from '.'; //NOTE: @@ -10,7 +10,7 @@ function normalizeModifiedDate(xmlStr: string): string { return xmlStr.replace(/modifiedDate=".+?"/, 'modifiedDate="2023-01-01T01:01:01"'); } -function normalizeExpected(opts: INewOpenLyricsSong.IOptions, xmlStr: string): string { +function normalizeExpected(opts: builderModel.IBuilderOptions, xmlStr: string): string { const pkgNameAndVersion = `${process.env.npm_package_name} ${process.env.npm_package_version}`; if (opts.meta?.createdIn == null) { //No `createdIn` option provided, so let's replace this value with the current version number @@ -34,7 +34,7 @@ describe('OpenLyricsBuilder', (): void => { describe('Meta', () => { it('should set the meta properties on the element', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { meta: { chordNotation: 'hungarian', lang: 'hu', @@ -68,7 +68,7 @@ describe('OpenLyricsBuilder', (): void => { describe('Properties', () => { it('should build a title list using a single string', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { titles: 'Amazing Grace', }, @@ -94,7 +94,7 @@ describe('OpenLyricsBuilder', (): void => { }); it('should build a title list using an array of objects', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { titles: [ { lang: 'en-US', original: true, value: 'Amazing Grace' }, @@ -129,7 +129,7 @@ Amazing }); it('should build an author list using a single string', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { authors: 'John Newton', titles: 'Required', @@ -159,7 +159,7 @@ Amazing }); it('should build an author list using an array of objects', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { authors: [ { value: 'John Newton' }, @@ -198,7 +198,7 @@ John Newton }); it('should build a song book list', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { songBooks: [ { name: "Rippon's Selection of Hymns" }, @@ -234,7 +234,7 @@ John Newton }); it('should build a themes list', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { themes: [ { value: 'Adoration' }, @@ -271,7 +271,7 @@ Adoration }); it('should build all the primitive properties (with tempo as a number value)', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { ccliNo: '1234', comments: ['one', 'two'], @@ -326,7 +326,7 @@ Adoration }); it('should build the tempo using a string value', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { tempo: 'slow', titles: 'Amazing Grace', @@ -356,7 +356,7 @@ Adoration describe('Format', () => { it('should create the Format tags', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { titles: 'Amazing Grace', }, @@ -409,7 +409,7 @@ Adoration describe('Lyrics', () => { it('should build lyrics using verse lines as an array of strings', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { titles: 'Amazing Grace', }, @@ -444,7 +444,7 @@ Adoration }); it('should build lyrics using verse lines as an array of text objects', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { titles: 'Amazing Grace', }, @@ -489,7 +489,7 @@ Amazing grace how sweet the sound
that saved a wretch like me;
}); it('should build lyrics using verse lines (with parts) as an array of mixed text and chord objects', () => { - const opts: INewOpenLyricsSong.IOptions = { + const opts: builderModel.IBuilderOptions = { properties: { titles: 'Amazing Grace', }, @@ -562,7 +562,7 @@ Amazing grace how sweet the sound
saved
a `; } - private getBeat(beatObj: INewSong.IInstrumentLineContentBeat): string { + private getBeat(beatObj: builderModel.IBuilderInstrumentLineContentBeat): string { //A `` can only contain ``s //Docs: https://docs.openlyrics.org/en/latest/dataformat.html#instrumental-parts const chordsArr = beatObj.chords.map((c) => this.getChord(c)); @@ -312,8 +324,8 @@ export class Builder { } private isVerseChord( - x: INewSong.IVerseChord | INewSong.IInstrumentChord - ): x is INewSong.IVerseChord { + x: builderModel.IBuilderVerseChord | builderModel.IBuilderInstrumentChord + ): x is builderModel.IBuilderVerseChord { return 'value' in x; } } diff --git a/src/builder.xml.model.ts b/src/builder.xml.model.ts new file mode 100644 index 0000000..6f881e4 --- /dev/null +++ b/src/builder.xml.model.ts @@ -0,0 +1,128 @@ +export interface IBuilderXml { + '?xml': { + '@version': string; + '@encoding': string; + }; + '?xml-stylesheet': { + '@href': string; + '@type': string; + }; + song: { + '@xmlns': string; + '@xml:lang': string; + '@version': string; + '@createdIn': string; + '@chordNotation'?: string; + '@modifiedIn': string; + '@modifiedDate': string; + properties: IPropertiesXml; + //We need to define the format as optional so we can remove it. + //We need to be able to remove it so that we can place above the lyrics in the output. + //So if a format option is provided it will be filled in, if not it will be removed, + //This prevents an empty format XML node from being output + format?: { + tags?: IFormatXml[]; + }; + lyrics: { + instrument?: IInstrumentXml[]; + verse: IVerseXml[]; + }; + }; +} + +//============================================ +//Properties +export interface IPropertiesXml { + [key: string]: any; + authors?: { + author: IAuthorXml[]; + }; + ccliNo?: string; + comments?: { + comment: string[]; + }; + copyright?: string | number; + key?: string; + keywords?: string; + publisher?: string; + released?: string; + songbooks?: { + songbook: ISongBookXml[]; + }; + tempo?: { + '#text': string | number; + '@type'?: string; + }; + tempoType?: string; + themes?: { + theme: IThemeXml[]; + }; + titles: { + title: ITitleXml[]; + }; + transposition?: string | number; + variant?: string; + verseOrder?: string; + version?: string; +} +export interface IAuthorXml { + '@lang'?: string; + '@type'?: string; + '#text': string; +} +export interface ITitleXml { + '@lang'?: string; + '@original'?: string; + '@translit'?: string; + '#text': string; +} + +export interface IThemeXml { + '@lang'?: string; + '#text': string; +} +export interface ISongBookXml { + '@entry'?: string | number; + '@name': string; +} + +//============================================ +//Format +export interface IFormatXml { + '@application': string; + tag: IFormatTagXml[]; +} +export interface IFormatTagXml { + close: string; + '@name': string; + open: string; +} + +//============================================ +//Verses +export interface IVerseXml { + '@break'?: 'optional'; + '@lang'?: string; + lines: IVerseLineXml[]; + '@name': string; + '@transliteration'?: string; +} +export interface IVerseLineXml { + '#text': string; + '@part'?: string; + '@break'?: string; + '@repeat'?: number; +} + +//============================================ +//Instruments +export interface IInstrumentXml { + lines: IInstrumentLineXml[]; + '@name': string; +} + +export interface IInstrumentLineXml { + '#text': string; + '@part'?: string; + '@repeat'?: number; +} diff --git a/src/index.ts b/src/index.ts index 1b6fd34..f5019cc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,15 @@ import { XMLBuilder, XMLParser } from 'fast-xml-parser'; + import { Builder } from './builder'; -import { INewOpenLyricsSong } from './builder.model'; +import { IBuilderOptions } from './builder.model'; +import { IBuilderXml } from './builder.xml.model'; + import { Parser } from './parser'; -import { IOpenLyricsSong } from './parser.model'; -import { OpenLyricsXml } from './xml.model'; +import { IParserRoot } from './parser.model'; +import { IXmlDocRoot } from './parser.xml.model'; -export const OpenLyricsParser = (fileContent: string): IOpenLyricsSong.IRoot => { +export * from './parser.model'; +export const OpenLyricsParser = (fileContent: string): IParserRoot => { //When certain XML nodes only have one item the parser will convert them into objects //Here we maintain a list of node paths to always keep as arrays //This keeps our code structure and type definitions more sane and normalized @@ -51,7 +55,7 @@ export const OpenLyricsParser = (fileContent: string): IOpenLyricsSong.IRoot => }, }); - const parsedDoc: OpenLyricsXml.IDocRoot = xmlParser.parse(fileContent); + const parsedDoc: IXmlDocRoot = xmlParser.parse(fileContent); const olParser = new Parser(); const meta = olParser.getSongMeta(parsedDoc.song); @@ -69,11 +73,12 @@ export const OpenLyricsParser = (fileContent: string): IOpenLyricsSong.IRoot => }; }; -export const OpenLyricsBuilder = (songData: INewOpenLyricsSong.IOptions): string => { +export * from './builder.model'; +export const OpenLyricsBuilder = (songData: IBuilderOptions): string => { //Certain items are required: https://docs.openlyrics.org/en/latest/dataformat.html#required-data-items const olBuilder = new Builder(); - const documentObj: INewOpenLyricsSong.IBuilderObject = { + const documentObj: IBuilderXml = { '?xml': { '@version': '1.0', '@encoding': 'UTF-8', diff --git a/src/parser.model.ts b/src/parser.model.ts index 85a7ab7..04bca1e 100644 --- a/src/parser.model.ts +++ b/src/parser.model.ts @@ -1,163 +1,173 @@ //The models for the objects returned by the OpenLyricsParser method -export namespace IOpenLyricsSong { - export interface IRoot { - [key: string]: IFormat | IInstrument[] | IMeta | IProperties | IVerse[]; - format: IFormat; - instruments: IInstrument[]; - meta: IMeta; - properties: IProperties; - verses: IVerse[]; - } - - //============================================ - //Meta - export interface IMeta { - [key: string]: string | Date | null; - chordNotation: string; - createdIn: string; - lang: string; - modifiedDate: Date | null; - modifiedIn: string; - version: string; - } - - //============================================ - //Properties - export interface IProperties { - [key: string]: number | string | string[] | IAuthor[] | ISongBook[] | ITheme[] | ITitle[]; - authors: IAuthor[]; - ccliNo: string; - comments: string[]; - copyright: string; - key: string; - keywords: string; - publisher: string; - released: string; - songBooks: ISongBook[]; - tempo: string | number; - tempoType: string; - themes: ITheme[]; - timeSignature: string; - titles: ITitle[]; - transposition: string; - variant: string; - verseOrder: string; - version: string; - } - - export interface IAuthor { - [key: string]: string; - lang: string; - type: string; - value: string; - } - - export interface ITitle { - [key: string]: string | boolean | null; - lang: string; - original: boolean | null; - transliteration: string; - value: string; - } - - export interface ITheme { - [key: string]: string; - lang: string; - value: string; - } - - export interface ISongBook { - [key: string]: string; - entry: string; - name: string; - } - - //============================================ - //Format - export interface IFormat { - [key: string]: string | IFormatTag[]; - application: string; - tags: IFormatTag[]; - } - - export interface IFormatTag { - [key: string]: string; - close: string; - name: string; - open: string; - } - - //============================================ - //Verses & Instruments (Shared) - export interface IVerseAndInstrumentLineContentChord { - [key: string]: string | undefined; - bass?: string; - root?: string; - structure?: string; - type: 'chord'; - upbeat?: string; - value?: string; - } - - //============================================ - //Verses - export interface IVerse { - [key: string]: string | IVerseLine[]; - break: string; - lang: string; - lines: IVerseLine[]; - name: string; - transliteration: string; - } - - export interface IVerseLine { - [key: string]: string | number | IVerseLineContent[]; - break: string; - content: IVerseLineContent[]; - part: string; - repeat: string | number; - } - - export type IVerseLineContent = - | IVerseLineContentStandard - | IVerseLineContentTag - | IVerseAndInstrumentLineContentChord; - - export interface IVerseLineContentStandard { - [key: string]: string; - type: 'text' | 'comment'; - value: string; - } - - export interface IVerseLineContentTag { - [key: string]: string; - name: string; - type: 'tag'; - value: string; - } - - //============================================ - //Instruments - export interface IInstrument { - [key: string]: string | IInstrumentLine[]; - lines: IInstrumentLine[]; - name: string; - } - - export interface IInstrumentLine { - [key: string]: string | number | IInstrumentLineContent[]; - content: IInstrumentLineContent[]; - part: string; - repeat: string | number; - } - - export interface IInstrumentLineContentBeat { - [key: string]: string | IVerseAndInstrumentLineContentChord[]; - chords: IVerseAndInstrumentLineContentChord[]; - type: 'beat'; - } - - export type IInstrumentLineContent = - | IInstrumentLineContentBeat - | IVerseAndInstrumentLineContentChord; +export interface IParserRoot { + [key: string]: + | IParserFormat + | IParserInstrument[] + | IParserMeta + | IParserProperties + | IParserVerse[]; + format: IParserFormat; + instruments: IParserInstrument[]; + meta: IParserMeta; + properties: IParserProperties; + verses: IParserVerse[]; } + +//============================================ +//Meta +export interface IParserMeta { + [key: string]: string | Date | null; + chordNotation: string; + createdIn: string; + lang: string; + modifiedDate: Date | null; + modifiedIn: string; + version: string; +} + +//============================================ +//Properties +export interface IParserProperties { + [key: string]: + | number + | string + | string[] + | IParserAuthor[] + | IParserSongBook[] + | IParserTheme[] + | IParserTitle[]; + authors: IParserAuthor[]; + ccliNo: string; + comments: string[]; + copyright: string; + key: string; + keywords: string; + publisher: string; + released: string; + songBooks: IParserSongBook[]; + tempo: string | number; + tempoType: string; + themes: IParserTheme[]; + timeSignature: string; + titles: IParserTitle[]; + transposition: string; + variant: string; + verseOrder: string; + version: string; +} + +export interface IParserAuthor { + [key: string]: string; + lang: string; + type: string; + value: string; +} + +export interface IParserTitle { + [key: string]: string | boolean | null; + lang: string; + original: boolean | null; + transliteration: string; + value: string; +} + +export interface IParserTheme { + [key: string]: string; + lang: string; + value: string; +} + +export interface IParserSongBook { + [key: string]: string; + entry: string; + name: string; +} + +//============================================ +//Format +export interface IParserFormat { + [key: string]: string | IParserFormatTag[]; + application: string; + tags: IParserFormatTag[]; +} + +export interface IParserFormatTag { + [key: string]: string; + close: string; + name: string; + open: string; +} + +//============================================ +//Verses & Instruments (Shared) +export interface IParserVerseAndInstrumentLineContentChord { + [key: string]: string | undefined; + bass?: string; + root?: string; + structure?: string; + type: 'chord'; + upbeat?: string; + value?: string; +} + +//============================================ +//Verses +export interface IParserVerse { + [key: string]: string | IParserVerseLine[]; + break: string; + lang: string; + lines: IParserVerseLine[]; + name: string; + transliteration: string; +} + +export interface IParserVerseLine { + [key: string]: string | number | IParserVerseLineContent[]; + break: string; + content: IParserVerseLineContent[]; + part: string; + repeat: string | number; +} + +export type IParserVerseLineContent = + | IParserVerseLineContentStandard + | IParserVerseLineContentTag + | IParserVerseAndInstrumentLineContentChord; + +export interface IParserVerseLineContentStandard { + [key: string]: string; + type: 'text' | 'comment'; + value: string; +} + +export interface IParserVerseLineContentTag { + [key: string]: string; + name: string; + type: 'tag'; + value: string; +} + +//============================================ +//Instruments +export interface IParserInstrument { + [key: string]: string | IParserInstrumentLine[]; + lines: IParserInstrumentLine[]; + name: string; +} + +export interface IParserInstrumentLine { + [key: string]: string | number | IParserInstrumentLineContent[]; + content: IParserInstrumentLineContent[]; + part: string; + repeat: string | number; +} + +export interface IParserInstrumentLineContentBeat { + [key: string]: string | IParserVerseAndInstrumentLineContentChord[]; + chords: IParserVerseAndInstrumentLineContentChord[]; + type: 'beat'; +} + +export type IParserInstrumentLineContent = + | IParserInstrumentLineContentBeat + | IParserVerseAndInstrumentLineContentChord; diff --git a/src/parser.spec.ts b/src/parser.spec.ts index 7a88c85..4129400 100644 --- a/src/parser.spec.ts +++ b/src/parser.spec.ts @@ -1,5 +1,5 @@ import { readFileSync } from 'fs'; -import { IOpenLyricsSong } from './parser.model'; +import * as parserModel from './parser.model'; import { OpenLyricsParser } from '.'; //NOTE: @@ -17,7 +17,7 @@ describe('OpenLyricsParser', (): void => { const testFile = readFileSync('./sample-files/examples/simple.xml').toString(); const parsedSong = OpenLyricsParser(testFile); - expect(parsedSong.meta).toEqual({ + expect(parsedSong.meta).toEqual({ createdIn: 'OpenLP 1.9.0', chordNotation: '', lang: '', @@ -25,8 +25,8 @@ describe('OpenLyricsParser', (): void => { modifiedIn: 'MyApp 0.0.1', version: '0.8', }); - expect(parsedSong.format).toEqual({ application: '', tags: [] }); - expect(parsedSong.properties).toEqual({ + expect(parsedSong.format).toEqual({ application: '', tags: [] }); + expect(parsedSong.properties).toEqual({ authors: [], ccliNo: '', comments: [], @@ -46,7 +46,7 @@ describe('OpenLyricsParser', (): void => { verseOrder: '', version: '', }); - expect(parsedSong.verses).toEqual([ + expect(parsedSong.verses).toEqual([ { break: '', name: 'v1', @@ -67,14 +67,14 @@ describe('OpenLyricsParser', (): void => { ], }, ]); - expect(parsedSong.instruments).toEqual([]); + expect(parsedSong.instruments).toEqual([]); }); it('should return a song for file: complex.xml"', () => { const testFile = readFileSync('./sample-files/examples/complex.xml').toString(); const parsedSong = OpenLyricsParser(testFile); - expect(parsedSong.meta).toEqual({ + expect(parsedSong.meta).toEqual({ createdIn: 'OpenLP 1.9.0', chordNotation: '', lang: '', @@ -82,8 +82,8 @@ describe('OpenLyricsParser', (): void => { modifiedIn: 'ChangingSong 0.0.1', version: '0.8', }); - expect(parsedSong.format).toEqual({ application: '', tags: [] }); - expect(parsedSong.properties).toEqual({ + expect(parsedSong.format).toEqual({ application: '', tags: [] }); + expect(parsedSong.properties).toEqual({ titles: [ { lang: 'en-US', original: true, transliteration: '', value: 'Amazing Grace' }, { lang: 'en', original: null, transliteration: '', value: 'Amazing Grace' }, @@ -125,7 +125,7 @@ describe('OpenLyricsParser', (): void => { verseOrder: 'v1 v2 v3 c v4 c1 c2 b b1 b2', version: '0.99', }); - expect(parsedSong.verses).toEqual([ + expect(parsedSong.verses).toEqual([ { name: 'v1', lang: 'en', @@ -256,14 +256,14 @@ describe('OpenLyricsParser', (): void => { ], }, ]); - expect(parsedSong.instruments).toEqual([]); + expect(parsedSong.instruments).toEqual([]); }); it('should return a song with format tags for file: format.xml"', () => { const testFile = readFileSync('./sample-files/examples/format.xml').toString(); const parsedSong = OpenLyricsParser(testFile); - expect(parsedSong.meta).toEqual({ + expect(parsedSong.meta).toEqual({ createdIn: 'OpenLP 1.9.0', chordNotation: '', lang: '', @@ -271,7 +271,7 @@ describe('OpenLyricsParser', (): void => { modifiedIn: 'OpenLP 1.9.7', version: '0.8', }); - expect(parsedSong.format).toEqual({ + expect(parsedSong.format).toEqual({ application: 'OpenLP', tags: [ { @@ -286,7 +286,7 @@ describe('OpenLyricsParser', (): void => { }, ], }); - expect(parsedSong.properties).toEqual({ + expect(parsedSong.properties).toEqual({ authors: [], ccliNo: '', comments: [], @@ -306,7 +306,7 @@ describe('OpenLyricsParser', (): void => { verseOrder: '', version: '', }); - expect(parsedSong.verses).toEqual([ + expect(parsedSong.verses).toEqual([ { name: 'v1', transliteration: '', @@ -339,14 +339,14 @@ describe('OpenLyricsParser', (): void => { ], }, ]); - expect(parsedSong.instruments).toEqual([]); + expect(parsedSong.instruments).toEqual([]); }); it('should return a song with format tags for file: format2.xml"', () => { const testFile = readFileSync('./sample-files/examples/format2.xml').toString(); const parsedSong = OpenLyricsParser(testFile); - expect(parsedSong.meta).toEqual({ + expect(parsedSong.meta).toEqual({ createdIn: 'OpenLP 1.9.7', chordNotation: '', lang: '', @@ -354,7 +354,7 @@ describe('OpenLyricsParser', (): void => { modifiedIn: 'OpenLP 1.9.7', version: '0.8', }); - expect(parsedSong.format).toEqual({ + expect(parsedSong.format).toEqual({ application: 'OpenLP', tags: [ { @@ -404,7 +404,7 @@ describe('OpenLyricsParser', (): void => { }, ], }); - expect(parsedSong.properties).toEqual({ + expect(parsedSong.properties).toEqual({ authors: [{ lang: '', type: '', value: 'M. Jan Hus' }], ccliNo: '', comments: [], @@ -426,7 +426,7 @@ describe('OpenLyricsParser', (): void => { verseOrder: '', version: '', }); - expect(parsedSong.verses).toEqual([ + expect(parsedSong.verses).toEqual([ { name: 'v1', transliteration: '', @@ -565,14 +565,14 @@ describe('OpenLyricsParser', (): void => { ], }, ]); - expect(parsedSong.instruments).toEqual([]); + expect(parsedSong.instruments).toEqual([]); }); it('should return a song with instrument tags in the lyrics for file: laboratory.xml"', () => { const testFile = readFileSync('./sample-files/examples/laboratory.xml').toString(); const parsedSong = OpenLyricsParser(testFile); - expect(parsedSong.meta).toEqual({ + expect(parsedSong.meta).toEqual({ createdIn: '', chordNotation: '', lang: 'en', @@ -580,11 +580,11 @@ describe('OpenLyricsParser', (): void => { modifiedIn: '', version: '0.9', }); - expect(parsedSong.format).toEqual({ + expect(parsedSong.format).toEqual({ application: '', tags: [], }); - expect(parsedSong.properties).toEqual({ + expect(parsedSong.properties).toEqual({ authors: [{ lang: '', type: '', value: 'Gellért Gyuris' }], ccliNo: '', comments: [], @@ -604,7 +604,7 @@ describe('OpenLyricsParser', (): void => { verseOrder: 'i v1 v2', version: '', }); - expect(parsedSong.verses).toEqual([ + expect(parsedSong.verses).toEqual([ { name: 'v1', transliteration: '', @@ -708,7 +708,7 @@ describe('OpenLyricsParser', (): void => { ], }, ]); - expect(parsedSong.instruments).toEqual([ + expect(parsedSong.instruments).toEqual([ { lines: [ { @@ -750,7 +750,7 @@ describe('OpenLyricsParser', (): void => { const testFile = readFileSync('./sample-files/examples/test0.9.xml').toString(); const parsedSong = OpenLyricsParser(testFile); - expect(parsedSong.meta).toEqual({ + expect(parsedSong.meta).toEqual({ createdIn: '', chordNotation: 'hungarian', lang: 'hu', @@ -758,11 +758,11 @@ describe('OpenLyricsParser', (): void => { modifiedIn: '', version: '0.9', }); - expect(parsedSong.format).toEqual({ + expect(parsedSong.format).toEqual({ application: '', tags: [], }); - expect(parsedSong.properties).toEqual({ + expect(parsedSong.properties).toEqual({ authors: [ { lang: '', type: '', value: 'Csiszér László' }, { lang: '', type: 'words', value: 'Flach Ferenc' }, @@ -788,7 +788,7 @@ describe('OpenLyricsParser', (): void => { verseOrder: 'i v1', version: '', }); - expect(parsedSong.verses).toEqual([ + expect(parsedSong.verses).toEqual([ { name: 'v1', transliteration: '', @@ -804,7 +804,7 @@ describe('OpenLyricsParser', (): void => { ], }, ]); - expect(parsedSong.instruments).toEqual([ + expect(parsedSong.instruments).toEqual([ { lines: [ { @@ -945,7 +945,7 @@ describe('OpenLyricsParser', (): void => { const testFile = readFileSync('./sample-files/examples/version0.9.xml').toString(); const parsedSong = OpenLyricsParser(testFile); - expect(parsedSong.meta).toEqual({ + expect(parsedSong.meta).toEqual({ createdIn: '', chordNotation: 'hungarian', lang: 'hu', @@ -953,11 +953,11 @@ describe('OpenLyricsParser', (): void => { modifiedIn: '', version: '0.9', }); - expect(parsedSong.format).toEqual({ + expect(parsedSong.format).toEqual({ application: '', tags: [], }); - expect(parsedSong.properties).toEqual({ + expect(parsedSong.properties).toEqual({ authors: [{ lang: '', type: '', value: 'ismeretlen' }], ccliNo: '', comments: [], @@ -977,7 +977,7 @@ describe('OpenLyricsParser', (): void => { verseOrder: 'i v1', version: '', }); - expect(parsedSong.verses).toEqual([ + expect(parsedSong.verses).toEqual([ { name: 'v1', transliteration: '', @@ -1010,7 +1010,7 @@ describe('OpenLyricsParser', (): void => { ], }, ]); - expect(parsedSong.instruments).toEqual([ + expect(parsedSong.instruments).toEqual([ { lines: [ { @@ -1052,7 +1052,7 @@ describe('OpenLyricsParser', (): void => { ).toString(); const parsedSong = OpenLyricsParser(testFile); - expect(parsedSong.meta).toEqual({ + expect(parsedSong.meta).toEqual({ createdIn: 'opensong2openlyrics.py 0.3', chordNotation: '', lang: '', @@ -1060,11 +1060,11 @@ describe('OpenLyricsParser', (): void => { modifiedIn: 'convert-schema.py', version: '0.9', }); - expect(parsedSong.format).toEqual({ + expect(parsedSong.format).toEqual({ application: '', tags: [], }); - expect(parsedSong.properties).toEqual({ + expect(parsedSong.properties).toEqual({ authors: [{ lang: '', type: '', value: 'Martin Luther' }], ccliNo: '', comments: [], @@ -1089,7 +1089,7 @@ describe('OpenLyricsParser', (): void => { verseOrder: 'v1 v2 v3 v4', version: '', }); - expect(parsedSong.verses).toEqual([ + expect(parsedSong.verses).toEqual([ { name: 'v1', transliteration: '', @@ -1445,14 +1445,14 @@ describe('OpenLyricsParser', (): void => { ], }, ]); - expect(parsedSong.instruments).toEqual([]); + expect(parsedSong.instruments).toEqual([]); }); it('should return a song for the HEBREW file: Hava Nagila.xml"', () => { const testFile = readFileSync('./sample-files/songs/Hava Nagila.xml').toString(); const parsedSong = OpenLyricsParser(testFile); - expect(parsedSong.meta).toEqual({ + expect(parsedSong.meta).toEqual({ createdIn: 'Trac 0.11.2', chordNotation: '', lang: '', @@ -1460,11 +1460,11 @@ describe('OpenLyricsParser', (): void => { modifiedIn: 'convert-schema.py', version: '0.9', }); - expect(parsedSong.format).toEqual({ + expect(parsedSong.format).toEqual({ application: '', tags: [], }); - expect(parsedSong.properties).toEqual({ + expect(parsedSong.properties).toEqual({ authors: [], ccliNo: '', comments: [], @@ -1498,7 +1498,7 @@ describe('OpenLyricsParser', (): void => { //NOTE: The below Hebrew characters have an invisible control // character to switch the text direction to RTL!!! // Had to use string template quotes to work around this in testing - expect(parsedSong.verses).toEqual([ + expect(parsedSong.verses).toEqual([ { name: 'v1', transliteration: '', @@ -1690,7 +1690,7 @@ describe('OpenLyricsParser', (): void => { ], }, ]); - expect(parsedSong.instruments).toEqual([]); + expect(parsedSong.instruments).toEqual([]); }); }); }); diff --git a/src/parser.ts b/src/parser.ts index 3917d4d..665b083 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,6 +1,6 @@ import { XMLParser } from 'fast-xml-parser'; -import { IOpenLyricsSong } from './parser.model'; -import { OpenLyricsXml } from './xml.model'; +import * as parserModel from './parser.model'; +import * as xmlModel from './parser.xml.model'; export class Parser { private readonly lyricLineParser = new XMLParser({ @@ -14,7 +14,7 @@ export class Parser { //============================================================================== //Semi-public methods to be used in the index file where the "real" public API is called from - public getSongMeta(olSong: OpenLyricsXml.ISong): IOpenLyricsSong.IMeta { + public getSongMeta(olSong: xmlModel.IXmlSong): parserModel.IParserMeta { // console.log('song', olSong); return { createdIn: olSong.createdIn ?? '', @@ -26,7 +26,7 @@ export class Parser { }; } - public getSongProperties(props: OpenLyricsXml.IProperties): IOpenLyricsSong.IProperties { + public getSongProperties(props: xmlModel.IXmlProperties): parserModel.IParserProperties { // console.log('props', props); //Property was renamed in OpenLyrics 0.9. Move it over if it exists @@ -57,9 +57,9 @@ export class Parser { }; } - public getSongFormat(format?: OpenLyricsXml.IFormat): IOpenLyricsSong.IFormat { + public getSongFormat(format?: xmlModel.IXmlFormat): parserModel.IParserFormat { let application = ''; - let tags: IOpenLyricsSong.IFormatTag[] = []; + let tags: parserModel.IParserFormatTag[] = []; if (format) { // console.log('format', format.tags); application = format.tags.application; @@ -74,8 +74,8 @@ export class Parser { return { application, tags }; } - public getSongVerses(verses?: OpenLyricsXml.IVerse[]): IOpenLyricsSong.IVerse[] { - const versesArr: IOpenLyricsSong.IVerse[] = []; + public getSongVerses(verses?: xmlModel.IXmlVerse[]): parserModel.IParserVerse[] { + const versesArr: parserModel.IParserVerse[] = []; if (verses) { for (const v of verses) { versesArr.push({ @@ -91,9 +91,9 @@ export class Parser { } public getSongInstruments( - instruments?: OpenLyricsXml.IInstrument[] - ): IOpenLyricsSong.IInstrument[] { - const instrumentsArr: IOpenLyricsSong.IInstrument[] = []; + instruments?: xmlModel.IXmlInstrument[] + ): parserModel.IParserInstrument[] { + const instrumentsArr: parserModel.IParserInstrument[] = []; if (instruments) { for (const i of instruments) { instrumentsArr.push({ @@ -108,9 +108,9 @@ export class Parser { //============================================================================== //Verse/Instrument/Line/Chord parsing methods private getVerseLines( - lines: OpenLyricsXml.IVerseOrInstrumentLineUnparsed[] - ): IOpenLyricsSong.IVerseLine[] { - const linesArr: IOpenLyricsSong.IVerseLine[] = []; + lines: xmlModel.IXmlVerseOrInstrumentLineUnparsed[] + ): parserModel.IParserVerseLine[] { + const linesArr: parserModel.IParserVerseLine[] = []; for (const line of lines) { // console.log('verse', typeof line, line); const rawLineTextAndXml = this.getStringOrTextProp(line); @@ -126,9 +126,9 @@ export class Parser { } private getInstrumentLines( - lines: OpenLyricsXml.IVerseOrInstrumentLineUnparsed[] - ): IOpenLyricsSong.IInstrumentLine[] { - const linesArr: IOpenLyricsSong.IInstrumentLine[] = []; + lines: xmlModel.IXmlVerseOrInstrumentLineUnparsed[] + ): parserModel.IParserInstrumentLine[] { + const linesArr: parserModel.IParserInstrumentLine[] = []; for (const line of lines) { // console.log('instrument', typeof line, line); const rawLineTextAndXml = this.getStringOrTextProp(line); @@ -157,8 +157,8 @@ export class Parser { ); } - private getVerseContentObjects(textAndXmlArr: string[]): IOpenLyricsSong.IVerseLineContent[] { - const contentArr: IOpenLyricsSong.IVerseLineContent[] = []; + private getVerseContentObjects(textAndXmlArr: string[]): parserModel.IParserVerseLineContent[] { + const contentArr: parserModel.IParserVerseLineContent[] = []; //Here we get an array of strings. Some might be plain text, others will be only XML nodes //We add an object for each item to describe it's type and all the properties it has @@ -197,8 +197,8 @@ export class Parser { private getInstrumentContentObjects( textAndXmlArr: string[] - ): IOpenLyricsSong.IInstrumentLineContent[] { - const contentArr: IOpenLyricsSong.IInstrumentLineContent[] = []; + ): parserModel.IParserInstrumentLineContent[] { + const contentArr: parserModel.IParserInstrumentLineContent[] = []; //Here we get an array of strings. These should all be XML nodes only since that's all that is allowed for instrument content //We add an object for each item to describe it's type and all the properties it has for (const part of textAndXmlArr) { @@ -220,11 +220,11 @@ export class Parser { } private getChordObject( - chordObj: OpenLyricsXml.ILineChord - ): IOpenLyricsSong.IVerseAndInstrumentLineContentChord { + chordObj: xmlModel.IXmlLineChord + ): parserModel.IParserVerseAndInstrumentLineContentChord { //A node. This can have a lot of properties, so we just add whatever is there //Sometimes chords can have ending tags too: ipsum - const chord: IOpenLyricsSong.IVerseAndInstrumentLineContentChord = { type: 'chord' }; + const chord: parserModel.IParserVerseAndInstrumentLineContentChord = { type: 'chord' }; Object.keys(chordObj).forEach((k) => { //When we have an ending tag we want to add the inner text between the tags as a value property let keyName = k === '#text' ? 'value' : k; @@ -241,8 +241,8 @@ export class Parser { //============================================================================== //Specific property parsing methods - private getSongPropertyAuthors(authors?: OpenLyricsXml.IAuthors): IOpenLyricsSong.IAuthor[] { - const authorsArr: IOpenLyricsSong.IAuthor[] = []; + private getSongPropertyAuthors(authors?: xmlModel.IXmlAuthors): parserModel.IParserAuthor[] { + const authorsArr: parserModel.IParserAuthor[] = []; if (authors) { // console.log('authors', authors.author); for (const a of authors.author) { @@ -256,7 +256,7 @@ export class Parser { return authorsArr; } - private getSongPropertyComments(comments?: OpenLyricsXml.IComments): string[] { + private getSongPropertyComments(comments?: xmlModel.IXmlComments): string[] { let commentArr: string[] = []; if (comments) { // console.log('comments', comments.comment); @@ -265,8 +265,8 @@ export class Parser { return commentArr; } - private getSongPropertyThemes(themes?: OpenLyricsXml.IThemes): IOpenLyricsSong.ITheme[] { - const titlesArr: IOpenLyricsSong.ITheme[] = []; + private getSongPropertyThemes(themes?: xmlModel.IXmlThemes): parserModel.IParserTheme[] { + const titlesArr: parserModel.IParserTheme[] = []; if (themes) { // console.log('themes', themes.theme); for (const t of themes.theme) { @@ -279,8 +279,8 @@ export class Parser { return titlesArr; } - private getSongPropertyTitles(titles?: OpenLyricsXml.ITitles): IOpenLyricsSong.ITitle[] { - const titlesArr: IOpenLyricsSong.ITitle[] = []; + private getSongPropertyTitles(titles?: xmlModel.IXmlTitles): parserModel.IParserTitle[] { + const titlesArr: parserModel.IParserTitle[] = []; if (titles) { // console.log('titles', titles.title); for (const t of titles.title) { @@ -296,9 +296,9 @@ export class Parser { } private getSongPropertySongBooks( - songBooks?: OpenLyricsXml.ISongBooks - ): IOpenLyricsSong.ISongBook[] { - const titlesArr: IOpenLyricsSong.ISongBook[] = []; + songBooks?: xmlModel.IXmlSongBooks + ): parserModel.IParserSongBook[] { + const titlesArr: parserModel.IParserSongBook[] = []; if (songBooks) { // console.log('songBooks', songBooks.songbook); for (const t of songBooks.songbook) { diff --git a/src/parser.xml.model.ts b/src/parser.xml.model.ts new file mode 100644 index 0000000..7cc523a --- /dev/null +++ b/src/parser.xml.model.ts @@ -0,0 +1,110 @@ +//A representation of an OpenLyrics document as a JSON object parsed by fast-xml-parser +//OpenLyrics Format Docs: https://docs.openlyrics.org/en/latest/dataformat.html +//The below data model relies on the following options being set: +// { ignoreAttributes: false, attributeNamePrefix: '', parseAttributeValue: true, ignoreDeclaration: true, } +// All options here: https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/v4/2.XMLparseOptions.md + +export interface IXmlDocRoot { + '?xml-stylesheet': { + href: string; + type: string; + }; + song: IXmlSong; +} + +export interface IXmlSong { + properties: IXmlProperties; + lyrics: IXmlLyrics; + format?: IXmlFormat; + xmlns: string; + 'xml:lang'?: string; + version: string; + createdIn?: string; + chordNotation?: string; + modifiedIn?: string; + modifiedDate?: string; +} + +export interface IXmlProperties { + authors?: IXmlAuthors; + ccliNo?: string | number; + comments?: IXmlComments; + copyright?: string | number; + key?: string; + keywords?: string; + publisher?: string; + released?: string | number; + releaseDate?: string | number; + songbooks?: IXmlSongBooks; + tempo?: IXmlTempo; + themes?: IXmlThemes; + timeSignature?: string; + titles?: IXmlTitles; + transposition?: string | number; + variant?: string; + verseOrder?: string; + version?: string | number; +} + +export interface IXmlTitles { + title: (string | { '#text': string; lang?: string; translit?: string; original?: boolean })[]; +} + +export interface IXmlAuthors { + author: (string | { '#text': string; lang?: string; type?: string })[]; +} + +export interface IXmlComments { + comment: (string | { '#text': string })[]; +} + +export interface IXmlSongBooks { + songbook: { name: string; entry?: string | number }[]; +} + +export interface IXmlThemes { + theme: (string | { '#text': string; lang?: string })[]; +} + +export interface IXmlTempo { + '#text': string | number; + type: string; +} + +export interface IXmlFormat { + tags: { + tag: { open: string; close?: string; name: string }[]; + application: string; + }; +} + +export interface IXmlLyrics { + instrument?: IXmlInstrument[]; + verse?: IXmlVerse[]; +} + +export type IXmlVerseOrInstrumentLineUnparsed = + | string + | { '#text': string; part?: string; repeat?: string | number; break?: string }; + +export interface IXmlVerse { + break?: string; + lines: IXmlVerseOrInstrumentLineUnparsed[]; + name: string; + lang?: string; + translit?: string; +} + +export interface IXmlInstrument { + lines: IXmlVerseOrInstrumentLineUnparsed[]; + name: string; +} + +export interface IXmlLineChord { + [key: string]: string | undefined; + name?: string; + root?: string; + structure?: string; + upbeat?: string; + bass?: string; +} diff --git a/src/version.ts b/src/version.ts index 4424d2e..f981842 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ // Generated by genversion. -export const version = '1.1.0'; +export const version = '1.1.1'; diff --git a/src/xml.model.ts b/src/xml.model.ts deleted file mode 100644 index c8f3830..0000000 --- a/src/xml.model.ts +++ /dev/null @@ -1,112 +0,0 @@ -//A representation of an OpenLyrics document as a JSON object parsed by fast-xml-parser -//OpenLyrics Format Docs: https://docs.openlyrics.org/en/latest/dataformat.html -//The below data model relies on the following options being set: -// { ignoreAttributes: false, attributeNamePrefix: '', parseAttributeValue: true, ignoreDeclaration: true, } -// All options here: https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/v4/2.XMLparseOptions.md - -export namespace OpenLyricsXml { - export interface IDocRoot { - '?xml-stylesheet': { - href: string; - type: string; - }; - song: ISong; - } - - export interface ISong { - properties: IProperties; - lyrics: ILyrics; - format?: IFormat; - xmlns: string; - 'xml:lang'?: string; - version: string; - createdIn?: string; - chordNotation?: string; - modifiedIn?: string; - modifiedDate?: string; - } - - export interface IProperties { - authors?: IAuthors; - ccliNo?: string | number; - comments?: IComments; - copyright?: string | number; - key?: string; - keywords?: string; - publisher?: string; - released?: string | number; - releaseDate?: string | number; - songbooks?: ISongBooks; - tempo?: ITempo; - themes?: IThemes; - timeSignature?: string; - titles?: ITitles; - transposition?: string | number; - variant?: string; - verseOrder?: string; - version?: string | number; - } - - export interface ITitles { - title: (string | { '#text': string; lang?: string; translit?: string; original?: boolean })[]; - } - - export interface IAuthors { - author: (string | { '#text': string; lang?: string; type?: string })[]; - } - - export interface IComments { - comment: (string | { '#text': string })[]; - } - - export interface ISongBooks { - songbook: { name: string; entry?: string | number }[]; - } - - export interface IThemes { - theme: (string | { '#text': string; lang?: string })[]; - } - - export interface ITempo { - '#text': string | number; - type: string; - } - - export interface IFormat { - tags: { - tag: { open: string; close?: string; name: string }[]; - application: string; - }; - } - - export interface ILyrics { - instrument?: IInstrument[]; - verse?: IVerse[]; - } - - export type IVerseOrInstrumentLineUnparsed = - | string - | { '#text': string; part?: string; repeat?: string | number; break?: string }; - - export interface IVerse { - break?: string; - lines: IVerseOrInstrumentLineUnparsed[]; - name: string; - lang?: string; - translit?: string; - } - - export interface IInstrument { - lines: IVerseOrInstrumentLineUnparsed[]; - name: string; - } - - export interface ILineChord { - [key: string]: string | undefined; - name?: string; - root?: string; - structure?: string; - upbeat?: string; - bass?: string; - } -}