UNPKG

propresenter-parser

Version:

Parses ProPresenter 4, 5, and 6 files to extract the data, and can build ProPresenter 5 and 6 files

132 lines (131 loc) 6.3 kB
import { XMLParser } from 'fast-xml-parser'; import { Base64 } from 'js-base64'; import * as Utils from '../utils'; export class v5Parser { parse(fileContent) { const alwaysArray = [ 'RVPresentationDocument.timeline.timeCues', 'RVPresentationDocument.timeline.mediaTracks', 'RVPresentationDocument.arrangements.RVSongArrangement', 'RVPresentationDocument.arrangements.RVSongArrangement.groupIDs.NSMutableString', 'RVPresentationDocument.groups.RVSlideGrouping', 'RVPresentationDocument.groups.RVSlideGrouping.slides.RVDisplaySlide', 'RVPresentationDocument.groups.RVSlideGrouping.slides.RVDisplaySlide.cues.RVMediaCue', 'RVPresentationDocument.groups.RVSlideGrouping.slides.RVDisplaySlide.displayElements.RVTextElement', ]; const xmlParser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@', parseAttributeValue: true, isArray: (_name, jPath) => alwaysArray.includes(jPath), }); const parsedDoc = xmlParser.parse(fileContent); if (parsedDoc.RVPresentationDocument['@versionNumber'] !== 500) { throw new Error(`Expected a ProPresenter 5 file with versionNumber="500" but got versionNumber="${parsedDoc.RVPresentationDocument['@versionNumber']}"`); } const properties = this.getProperties(parsedDoc.RVPresentationDocument); const slideGroups = this.getSlideGroups(parsedDoc.RVPresentationDocument.groups.RVSlideGrouping); const arrangements = this.getArrangements(parsedDoc.RVPresentationDocument, slideGroups); return { properties, slideGroups, arrangements }; } getProperties(xmlDoc) { return { CCLIArtistCredits: xmlDoc['@CCLIArtistCredits'] ?? '', CCLICopyrightInfo: xmlDoc['@CCLICopyrightInfo'] ?? '', CCLIDisplay: Boolean(xmlDoc['@CCLIDisplay']), CCLILicenseNumber: xmlDoc['@CCLILicenseNumber'] ?? '', CCLIPublisher: xmlDoc['@CCLIPublisher'] ?? '', CCLISongTitle: xmlDoc['@CCLISongTitle'] ?? '', album: xmlDoc['@album'], artist: xmlDoc['@artist'], author: xmlDoc['@author'], backgroundColor: Utils.normalizeColorToRgbObj(xmlDoc['@backgroundColor']), category: xmlDoc['@category'], creatorCode: xmlDoc['@creatorCode'], chordChartPath: xmlDoc['@chordChartPath'], docType: xmlDoc['@docType'], drawingBackgroundColor: Boolean(xmlDoc['@drawingBackgroundColor']), height: xmlDoc['@height'], lastDateUsed: new Date(xmlDoc['@lastDateUsed']), notes: xmlDoc['@notes'], resourcesDirectory: xmlDoc['@resourcesDirectory'], usedCount: xmlDoc['@usedCount'], versionNumber: xmlDoc['@versionNumber'], width: xmlDoc['@width'], }; } getSlideGroups(xmlGroups) { return xmlGroups.map((sg) => { const groupColor = sg['@color'] === '' ? null : Utils.normalizeColorToRgbObj(sg['@color']); return { groupColor, groupLabel: sg['@name'] ?? '', groupId: sg['@uuid'], slides: this.getSlidesForGroup(sg.slides.RVDisplaySlide), }; }); } getSlidesForGroup(xmlSlides) { return xmlSlides.map((slide) => { let textElements = []; if (slide.displayElements.RVTextElement) { textElements = slide.displayElements.RVTextElement.map((txt) => { const decodedContent = Base64.decode(txt['@RTFData']); const textProps = Utils.getTextPropsFromRtf(decodedContent); return { position: { x: txt['_-RVRect3D-_position']['@x'], y: txt['_-RVRect3D-_position']['@y'], z: txt['_-RVRect3D-_position']['@z'], height: txt['_-RVRect3D-_position']['@height'], width: txt['_-RVRect3D-_position']['@width'], }, rawRtfContent: decodedContent, textContent: Utils.stripRtf(decodedContent), color: textProps.color, font: textProps.font, size: textProps.size, }; }); } let mediaCues = []; if (slide.cues.RVMediaCue) { mediaCues = slide.cues.RVMediaCue.map((cue) => ({ displayName: cue.element['@displayName'], source: cue.element['@source'], })); } const highlightColor = slide['@highlightColor'] === '' ? null : Utils.normalizeColorToRgbObj(slide['@highlightColor']); return { backgroundColor: Utils.normalizeColorToRgbObj(slide['@backgroundColor']), chordChartPath: slide['@chordChartPath'], enabled: Boolean(slide['@enabled']), highlightColor, id: slide['@UUID'], label: slide['@label'], notes: slide['@notes'], mediaCues, textElements, }; }); } getArrangements(xmlDoc, slideGroups) { const arrangementsArr = []; 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) => { const slideGroupMatch = slideGroups.find((sg) => sg.groupId === group['@serialization-native-value']); return { groupId: group['@serialization-native-value'], groupLabel: slideGroupMatch.groupLabel, }; }), }); } } return arrangementsArr; } }