UNPKG

screenplay-js

Version:

A modern Typescript, Foutain screenplay parser. Convert Final Draft (.fdx) files to Fountain, and then parse Fountain markdown to HTML.

167 lines (166 loc) 7.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.tokenizer = void 0; var sections_1 = require("./sections"); var ScriptTokenizer = /** @class */ (function () { function ScriptTokenizer() { } /** * Cleans the script text to allow the parser to break * the script into correct tokens, pages, etc. * * @param { String } script * @returns { String } */ ScriptTokenizer.prototype.clean = function (script) { var s = script.replace(sections_1.sections.boneyard, '\n$1\n') .replace(sections_1.sections.standardizer, '\n') .replace(sections_1.sections.cleaner, '') .replace(sections_1.sections.whitespacer, '') // PR: Treat whitespace-only lines as empty // https://github.com/nathanhoad/fountain-js/pull/2 .replace(/^\s+$/gm, ''); return s; }; /** * Return an array of token objects breaking the screenplay into type, text, etc. tokens. * * @param { String } script - Text of the screenplay * @returns { Array<any> } */ ScriptTokenizer.prototype.tokenize = function (script) { var script_lines = this.clean(script).split(sections_1.sections.splitter), match, parts, text, meta, tokens = [], scene_number = 0; for (var i = 0; i < script_lines.length; i++) { var line = script_lines[i]; // title page if (sections_1.sections.title_page.test(line)) { match = line.replace(sections_1.sections.title_page, '\n$1').split(sections_1.sections.splitter); for (var x = 0, length_1 = match.length; x < length_1; x++) { parts = match[x].replace(sections_1.sections.cleaner, '').split(/\:\n*/); // Handle titles with colons in them if (parts.length > 2) { var colonTitle = parts.slice(1).map(function (p) { return p.trim(); }).join(': '); tokens.push({ type: parts[0].trim().toLowerCase().replace(' ', '_'), text: colonTitle.trim() }); } else { tokens.push({ type: parts[0].trim().toLowerCase().replace(' ', '_'), text: parts[1].trim() }); } } continue; } /** * Scene headings * * @todo: * - Figure out what the match fields are returning, * and how to update the parsed meta results */ if (match = line.match(sections_1.sections.scene_heading)) { text = match[1] || match[2]; if (text.indexOf(' ') !== text.length - 2) { if (meta = text.match(sections_1.sections.scene_number)) { meta = meta[2]; text = text.replace(sections_1.sections.scene_number, ''); } if (meta) { scene_number = meta; } tokens.push({ type: 'scene_heading', text: text, scene_number: scene_number }); // increment scene number scene_number += 1; } continue; } // centered if (match = line.match(sections_1.sections.centered)) { tokens.push({ type: 'centered', text: match[0].replace(/>|</g, '') }); continue; } // transitions if (match = line.match(sections_1.sections.transition)) { tokens.push({ type: 'transition', text: match[1] || match[2] }); continue; } // dialogue blocks - characters, parentheticals and dialogue if (match = line.match(sections_1.sections.dialogue)) { if (match[1].indexOf(' ') !== match[1].length - 2) { // PR: Fixed the bug where parentheticals are after the dialogue // https://github.com/nathanhoad/fountain-js/pull/7 // parts = match[3].split(/(\(.+\))(?:\n+)/).reverse(); parts = match[3].split(/(\(.+\))(?:\n+)/); var dual_diaglogue = !!match[2]; if (dual_diaglogue) { // If dual dialogue, we need to traverse back four indexes // and insert those into a dual dialogue block // Get last index of the last inserted dialogue block var lastDialogueBeginIndex = 0; for (var idx = tokens.length - 1; idx >= 0; idx--) { if (tokens[idx].type === 'dialogue_begin') { lastDialogueBeginIndex = idx; break; } } var leftDualDialogueBlocks = tokens.splice(lastDialogueBeginIndex); tokens.push({ type: 'dual_dialogue_begin' }); // Insert previous dialogue block into dual dialogue tokens = tokens.concat(leftDualDialogueBlocks); } tokens.push({ type: 'dialogue_begin' }); tokens.push({ type: 'character', text: match[1].trim() }); for (var x = 0, length_2 = parts.length; x < length_2; x++) { text = parts[x].trim(); if (text.length > 0) { tokens.push({ type: sections_1.sections.parenthetical.test(text) ? 'parenthetical' : 'dialogue', text: text }); } } tokens.push({ type: 'dialogue_end' }); if (dual_diaglogue) { tokens.push({ type: 'dual_dialogue_end' }); } continue; } } // section if (match = line.match(sections_1.sections.section)) { tokens.push({ type: 'section', text: match[2], depth: match[1].length }); continue; } // synopsis if (match = line.match(sections_1.sections.synopsis)) { tokens.push({ type: 'synopsis', text: match[1] }); continue; } // notes if (match = line.match(sections_1.sections.note)) { tokens.push({ type: 'note', text: match[1] }); continue; } // boneyard if (match = line.match(sections_1.sections.boneyard)) { tokens.push({ type: match[0][0] === '/' ? 'boneyard_begin' : 'boneyard_end' }); continue; } // page breaks if (sections_1.sections.page_break.test(line)) { tokens.push({ type: 'page_break' }); continue; } // line breaks if (sections_1.sections.line_break.test(line)) { tokens.push({ type: 'line_break' }); continue; } // lyrics if (sections_1.sections.lyrics.test(line)) { tokens.push({ type: 'lyrics', text: line }); continue; } tokens.push({ type: 'action', text: line }); } return tokens; }; return ScriptTokenizer; }()); ; var tokenizer = new ScriptTokenizer(); exports.tokenizer = tokenizer;