Skip to content

Commit

Permalink
Fixed based on implementation in LyricConverter
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisMBarr committed Jun 21, 2023
1 parent b571c16 commit c6257ca
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 204 deletions.
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"name": "propresenter-parser",
"version": "1.0.0",
"version": "1.0.1",
"description": "Parses ProPresenter 4, 5, and 6 files to extract the data, and can build ProPresenter 5 and 6 files",
"author": {
"name": "Chris Barr",
"url": "http://chrisbarr.me"
},
"main": "dist/main/index.js",
"typings": "dist/main/index.d.ts",
"module": "dist/module/index.js",
Expand All @@ -13,7 +17,7 @@
"bugs": {
"url": "https://github.com/FiniteLooper/ProPresenter-Parser/issues"
},
"keywords": ["ProPresenter", "church", "lyrics", "song"],
"keywords": ["ProPresenter", "church", "lyrics", "song", "pro4", "pro5", "pro6"],
"files": [
"LICENSE",
"README.md",
Expand Down
2 changes: 1 addition & 1 deletion sample-files/v6 - Feature Test.pro6
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RVPresentationDocument height="1080" width="1920" docType="0" versionNumber="600" usedCount="0" backgroundColor="0.196078431372549 0.643137254901961 0.611764705882353 1" drawingBackgroundColor="true" CCLIDisplay="false" lastDateUsed="2023-06-19T19:11:43+00:00" selectedArrangementID="" category="Song" resourcesDirectory="" notes="" CCLISongTitle="" chordChartPath="" os="1" buildNumber="6016">
<RVPresentationDocument height="1080" width="1920" docType="0" versionNumber="600" usedCount="0" backgroundColor="0.196078431372549 0.643137254901961 0.611764705882353 1" drawingBackgroundColor="true" CCLIDisplay="false" lastDateUsed="2023-06-19T19:11:43+00:00" selectedArrangementID="" category="Song" resourcesDirectory="" notes="" chordChartPath="" os="1" buildNumber="6016">
<RVTimeline timeOffset="0" duration="0" selectedMediaTrackIndex="-1" loop="false" rvXMLIvarName="timeline">
<array rvXMLIvarName="timeCues" />
<array rvXMLIvarName="mediaTracks" />
Expand Down
30 changes: 30 additions & 0 deletions src/v4/parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,36 @@ describe('V4 - Parser', (): void => {
}
});

it('should use empty strings for CCLI properties that do not exist', () => {
let testFile = readFileSync('./sample-files/v4 - Be Near.pro4').toString();
testFile = testFile.replace(/(CCLI(ArtistCredits)|(CopyrightInfo)|(LicenseNumber)|(Publisher)|(SongTitle))=".*?"/g, '');
const parsedSong = parser.parse(testFile);

expect(parsedSong.properties).toEqual({
CCLIArtistCredits: '',
CCLICopyrightInfo: '',
CCLIDisplay: false,
CCLILicenseNumber: '',
CCLIPublisher: '',
CCLISongTitle: '',
album: '',
artist: '',
author: '',
backgroundColor: { r: 0, g: 0, b: 0 },
category: 'Song',
creatorCode: 1349676880,
docType: 0,
drawingBackgroundColor: false,
height: 768,
lastDateUsed: new Date('2010-11-07T00:37:36'),
notes: '',
resourcesDirectory: '',
usedCount: 0,
versionNumber: 400,
width: 1024,
} as IPro4Properties);
});

it('should get the data from "Be Near.pro4"', () => {
const testFile = readFileSync('./sample-files/v4 - Be Near.pro4').toString();
const parsedSong = parser.parse(testFile);
Expand Down
10 changes: 5 additions & 5 deletions src/v4/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ export class v4Parser {

private getProperties(doc: IXmlPro4Doc): IPro4Properties {
return {
CCLIArtistCredits: doc['@CCLIArtistCredits'],
CCLICopyrightInfo: doc['@CCLICopyrightInfo'],
CCLIArtistCredits: doc['@CCLIArtistCredits'] ?? '',
CCLICopyrightInfo: doc['@CCLICopyrightInfo'] ?? '',
CCLIDisplay: Boolean(doc['@CCLIDisplay']),
CCLILicenseNumber: doc['@CCLILicenseNumber'],
CCLIPublisher: doc['@CCLIPublisher'],
CCLISongTitle: doc['@CCLISongTitle'],
CCLILicenseNumber: doc['@CCLILicenseNumber'] ?? '',
CCLIPublisher: doc['@CCLIPublisher'] ?? '',
CCLISongTitle: doc['@CCLISongTitle'] ?? '',
album: doc['@album'],
artist: doc['@artist'],
author: doc['@author'],
Expand Down
10 changes: 5 additions & 5 deletions src/v4/xml.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ export interface IXmlPro4DocRoot {
}

export interface IXmlPro4Doc {
'@CCLIArtistCredits': string;
'@CCLICopyrightInfo': number;
'@CCLIArtistCredits'?: string;
'@CCLICopyrightInfo'?: number;
'@CCLIDisplay': number;
'@CCLILicenseNumber': string | number;
'@CCLIPublisher': string;
'@CCLISongTitle': string;
'@CCLILicenseNumber'?: string | number;
'@CCLIPublisher'?: string;
'@CCLISongTitle'?: string;
'@album': string;
'@artist': string;
'@author': string;
Expand Down
300 changes: 150 additions & 150 deletions src/v5/builder.spec.ts

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions src/v5/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ export class v5Builder {
format: true,
ignoreAttributes: false,
processEntities: false,
suppressUnpairedNode: false,
unpairedTags: [
'arrangements',
'timeCues',
'mediaTracks',
'bibleReference',
'cues',
'_-RVProTransitionObject-_transitionObject',
'_-RVRect3D-_position',
'NSColor',
'NSNumber',
'NSMutableString',
],
});

//Set the options, and force the type
Expand Down Expand Up @@ -226,6 +239,7 @@ export class v5Builder {
'@revealType': 0,
'@serialization-array-index': 0,
stroke: {
'@containerClass': 'NSMutableDictionary',
NSColor: {
'@serialization-native-value': '0 0 0 1',
'@serialization-dictionary-key': 'RVShapeElementStrokeColorKey',
Expand All @@ -236,6 +250,7 @@ export class v5Builder {
},
},
'_-D-_serializedShadow': {
'@containerClass': 'NSMutableDictionary',
NSMutableString: {
'@serialization-native-value': `{3.4641016, -2}`,
'@serialization-dictionary-key': 'shadowOffset',
Expand Down
47 changes: 47 additions & 0 deletions src/v5/parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,53 @@ describe('V5 - Parser', (): void => {
}
});

it('should use empty strings for CCLI properties that do not exist', () => {
let testFile = readFileSync('./sample-files/v5 - Be Near.pro5').toString();
testFile = testFile.replace(/(CCLI(ArtistCredits)|(CopyrightInfo)|(LicenseNumber)|(Publisher)|(SongTitle))=".*?"/g, '');
const parsedSong = parser.parse(testFile);

expect(parsedSong.properties).toEqual({
CCLIArtistCredits: '',
CCLICopyrightInfo: '',
CCLIDisplay: false,
CCLILicenseNumber: '',
CCLIPublisher: '',
CCLISongTitle: '',
album: '',
artist: 'Shane Bernard',
author: '',
backgroundColor: { r: 0, g: 0, b: 0 },
category: 'Song',
creatorCode: 1349676880,
chordChartPath: '',
docType: 0,
drawingBackgroundColor: false,
height: 1050,
lastDateUsed: new Date('2014-10-12T20:44:32'),
notes: '',
resourcesDirectory: '',
usedCount: 0,
versionNumber: 500,
width: 1680,
} as IPro5Properties);
});

it('should not return arrangements for a song with no arrangements specified', () => {
let testFile = readFileSync('./sample-files/v5 - Be Near.pro5').toString();
testFile = testFile.replace(/<arrangements[\s\S]+?<\/arrangements>/g, '');
const parsedSong = parser.parse(testFile);

expect(parsedSong.arrangements).toEqual([]);
});

it('use an empty string when a group label is missing', () => {
let testFile = readFileSync('./sample-files/v5 - Be Near.pro5').toString();
testFile = testFile.replace('name="Background" ', '');
const parsedSong = parser.parse(testFile);

expect(parsedSong.slideGroups[0].groupLabel).toEqual('');
});

it('should get the data from "Be Near.pro5"', () => {
const testFile = readFileSync('./sample-files/v5 - Be Near.pro5').toString();
const parsedSong = parser.parse(testFile);
Expand Down
56 changes: 29 additions & 27 deletions src/v5/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
IPro5SlideTextElement,
IPro5Song,
} from './parser.model';
import { IXmlPro5Arrangement, IXmlPro5Doc, IXmlPro5DocRoot, IXmlPro5Slide, IXmlPro5SlideGroup } from './xml.model';
import { IXmlPro5Doc, IXmlPro5DocRoot, IXmlPro5Slide, IXmlPro5SlideGroup } from './xml.model';

export class v5Parser {
parse(fileContent: string): IPro5Song {
Expand Down Expand Up @@ -45,19 +45,19 @@ export class v5Parser {

const properties = this.getProperties(parsedDoc.RVPresentationDocument);
const slideGroups = this.getSlideGroups(parsedDoc.RVPresentationDocument.groups.RVSlideGrouping);
const arrangements = this.getArrangements(parsedDoc.RVPresentationDocument.arrangements.RVSongArrangement, slideGroups);
const arrangements = this.getArrangements(parsedDoc.RVPresentationDocument, slideGroups);

return { properties, slideGroups, arrangements };
}

private getProperties(xmlDoc: IXmlPro5Doc): IPro5Properties {
return {
CCLIArtistCredits: xmlDoc['@CCLIArtistCredits'],
CCLICopyrightInfo: xmlDoc['@CCLICopyrightInfo'],
CCLIArtistCredits: xmlDoc['@CCLIArtistCredits'] ?? '',
CCLICopyrightInfo: xmlDoc['@CCLICopyrightInfo'] ?? '',
CCLIDisplay: Boolean(xmlDoc['@CCLIDisplay']),
CCLILicenseNumber: xmlDoc['@CCLILicenseNumber'],
CCLIPublisher: xmlDoc['@CCLIPublisher'],
CCLISongTitle: xmlDoc['@CCLISongTitle'],
CCLILicenseNumber: xmlDoc['@CCLILicenseNumber'] ?? '',
CCLIPublisher: xmlDoc['@CCLIPublisher'] ?? '',
CCLISongTitle: xmlDoc['@CCLISongTitle'] ?? '',
album: xmlDoc['@album'],
artist: xmlDoc['@artist'],
author: xmlDoc['@author'],
Expand All @@ -82,7 +82,7 @@ export class v5Parser {
const groupColor = sg['@color'] === '' ? null : Utils.normalizeColorToRgbObj(sg['@color']);
return {
groupColor,
groupLabel: sg['@name'],
groupLabel: sg['@name'] ?? '',
groupId: sg['@uuid'],
slides: this.getSlidesForGroup(sg.slides.RVDisplaySlide),
};
Expand Down Expand Up @@ -139,28 +139,30 @@ export class v5Parser {
});
}

private getArrangements(xmlArrangements: IXmlPro5Arrangement[], slideGroups: IPro5SlideGroup[]): IPro5Arrangement[] {
private getArrangements(xmlDoc: IXmlPro5Doc, slideGroups: IPro5SlideGroup[]): IPro5Arrangement[] {
const arrangementsArr: IPro5Arrangement[] = [];

for (const a of xmlArrangements) {
arrangementsArr.push({
color: Utils.normalizeColorToRgbObj(a['@color']),
label: a['@name'],
groupOrder: a.groupIDs.NSMutableString.map((group) => {
//This should always find a match since you can't put something in an arrangement that doesn't already exist
//So because of that it's OK to have a non-null assertion here
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const slideGroupMatch = slideGroups.find(
//Look up the actual slide group by ID so we can get its name
(sg) => sg.groupId === group['@serialization-native-value']
)!;
if (xmlDoc.arrangements?.RVSongArrangement) {
for (const a of xmlDoc.arrangements.RVSongArrangement) {
arrangementsArr.push({
color: Utils.normalizeColorToRgbObj(a['@color']),
label: a['@name'],
groupOrder: a.groupIDs.NSMutableString.map((group) => {
//This should always find a match since you can't put something in an arrangement that doesn't already exist
//So because of that it's OK to have a non-null assertion here
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const slideGroupMatch = slideGroups.find(
//Look up the actual slide group by ID so we can get its name
(sg) => sg.groupId === group['@serialization-native-value']
)!;

return {
groupId: group['@serialization-native-value'],
groupLabel: slideGroupMatch.groupLabel,
};
}),
});
return {
groupId: group['@serialization-native-value'],
groupLabel: slideGroupMatch.groupLabel,
};
}),
});
}
}

return arrangementsArr;
Expand Down
20 changes: 11 additions & 9 deletions src/v5/xml.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ export interface IXmlPro5DocRoot {
}

export interface IXmlPro5Doc {
'@CCLIArtistCredits': string;
'@CCLICopyrightInfo': string | number;
'@CCLIDisplay': number;
'@CCLILicenseNumber': string | number;
'@CCLIPublisher': string;
'@CCLISongTitle': string;
'@CCLIArtistCredits'?: string;
'@CCLICopyrightInfo'?: string | number;
'@CCLIDisplay'?: number;
'@CCLILicenseNumber'?: string | number;
'@CCLIPublisher'?: string;
'@CCLISongTitle'?: string;
'@album': string;
'@artist': string;
'@author': string;
Expand All @@ -28,9 +28,9 @@ export interface IXmlPro5Doc {
'@versionNumber': number;
'@width': number;

arrangements: {
arrangements?: {
'@containerClass': 'NSMutableArray';
RVSongArrangement: IXmlPro5Arrangement[];
RVSongArrangement?: IXmlPro5Arrangement[];
};
groups: {
'@containerClass': 'NSMutableArray';
Expand Down Expand Up @@ -98,7 +98,7 @@ export interface IXmlPro5ArrangementGroupId {
//Slide Groups and Slides

export interface IXmlPro5SlideGroup {
'@name': string;
'@name'?: string;
'@uuid': string;
'@color': string;
'@serialization-array-index': number;
Expand Down Expand Up @@ -230,6 +230,7 @@ export interface IXmlPro5ElementPosition {
}

export interface IXmlPro5SlideElementStroke {
'@containerClass': 'NSMutableDictionary';
NSColor: {
'@serialization-native-value': string;
'@serialization-dictionary-key': 'RVShapeElementStrokeColorKey';
Expand All @@ -241,6 +242,7 @@ export interface IXmlPro5SlideElementStroke {
}

export interface IXmlPro5SlideElementShadow {
'@containerClass': 'NSMutableDictionary';
NSMutableString: {
'@serialization-native-value': string;
'@serialization-dictionary-key': 'shadowOffset';
Expand Down
4 changes: 2 additions & 2 deletions src/v6/builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('V6 - Builder', (): void => {
const expectedOutput = normalizeDatesAndIdsForTesting(`
<?xml version="1.0" encoding="utf-8"?>
<RVPresentationDocument CCLIArtistCredits="" CCLIAuthor="" CCLICopyrightYear="" CCLIDisplay="false" CCLIPublisher="" CCLISongNumber="" CCLISongTitle="My Test Song" category="Song" notes="" lastDateUsed="2023-06-20T15:29:44" height="720" width="1280" backgroundColor="0 0 0 1" buildNumber="6016" chordChartPath="" docType="0" drawingBackgroundColor="false" resourcesDirectory="" selectedArrangementID="" os="1" usedCount="0" versionNumber="600">
<RVTransition rvXMLIvarName="transitionObject" transitionType="-1" transitionDirection="0" transitionDuration="1" motionEnabled="false" motionDuration="0" motionSpeed="0" groupIndex="0" orderIndex="0" slideBuildAction="0" slideBuildDelay="0"></RVTransition>
<RVTransition rvXMLIvarName="transitionObject" transitionType="-1" transitionDirection="0" transitionDuration="1" motionEnabled="false" motionDuration="0" motionSpeed="0" groupIndex="0" orderIndex="0" slideBuildAction="0" slideBuildDelay="0"/>
<RVTimeline rvXMLIvarName="timeline" timeOffset="0" duration="0" selectedMediaTrackIndex="0" loop="false">
<array rvXMLIvarName="timeCues"/>
<array rvXMLIvarName="mediaTracks"/>
Expand Down Expand Up @@ -148,7 +148,7 @@ describe('V6 - Builder', (): void => {
const expectedOutput = normalizeDatesAndIdsForTesting(`
<?xml version="1.0" encoding="utf-8"?>
<RVPresentationDocument CCLIArtistCredits="Chris Tomlin" CCLIAuthor="John Newton, Chris Tomlin" CCLICopyrightYear="2006" CCLIDisplay="true" CCLIPublisher="worshiptogether.com Songs/sixsteps Music, Vamos Publishing, admin. Capitol CMG Publishing" CCLISongNumber="12345678" CCLISongTitle="My Test Song" category="Hymn" notes="Pastor Bill loves this one" lastDateUsed="2023-06-21T01:33:21" height="1080" width="1920" backgroundColor="0 0 0 1" buildNumber="6016" chordChartPath="" docType="0" drawingBackgroundColor="false" resourcesDirectory="" selectedArrangementID="" os="1" usedCount="0" versionNumber="600">
<RVTransition rvXMLIvarName="transitionObject" transitionType="106" transitionDirection="0" transitionDuration="0.4" motionEnabled="false" motionDuration="0" motionSpeed="0" groupIndex="0" orderIndex="0" slideBuildAction="0" slideBuildDelay="0"></RVTransition>
<RVTransition rvXMLIvarName="transitionObject" transitionType="106" transitionDirection="0" transitionDuration="0.4" motionEnabled="false" motionDuration="0" motionSpeed="0" groupIndex="0" orderIndex="0" slideBuildAction="0" slideBuildDelay="0"/>
<RVTimeline rvXMLIvarName="timeline" timeOffset="0" duration="0" selectedMediaTrackIndex="0" loop="false">
<array rvXMLIvarName="timeCues"/>
<array rvXMLIvarName="mediaTracks"/>
Expand Down
2 changes: 1 addition & 1 deletion src/v6/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class v6Builder {
processEntities: false,
suppressUnpairedNode: false,
suppressBooleanAttributes: false,
unpairedTags: ['array'],
unpairedTags: ['array', 'RVTransition'],
});

//Set the options, and force the type
Expand Down
2 changes: 1 addition & 1 deletion src/v6/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class v6Parser {
CCLIDisplay: xmlDoc['@CCLIDisplay'],
CCLIPublisher: xmlDoc['@CCLIPublisher'] ?? '',
CCLISongNumber: xmlDoc['@CCLISongNumber'] ?? '',
CCLISongTitle: xmlDoc['@CCLISongTitle'],
CCLISongTitle: xmlDoc['@CCLISongTitle'] ?? '',
backgroundColor: Utils.normalizeColorToRgbObj(xmlDoc['@backgroundColor']),
buildNumber: xmlDoc['@buildNumber'],
category: xmlDoc['@category'],
Expand Down
2 changes: 1 addition & 1 deletion src/v6/xml.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface IXmlPro6Doc {
'@CCLIDisplay': boolean;
'@CCLIPublisher'?: string;
'@CCLISongNumber'?: string | number;
'@CCLISongTitle': string;
'@CCLISongTitle'?: string;
'@backgroundColor': string;
'@buildNumber': number;
'@category': string;
Expand Down

0 comments on commit c6257ca

Please sign in to comment.