diff --git a/src/Twine2HTMLParser.js b/src/Twine2HTMLParser.js index 1ae13aa4..affdd930 100644 --- a/src/Twine2HTMLParser.js +++ b/src/Twine2HTMLParser.js @@ -25,7 +25,10 @@ export default class Twine2HTMLParser { * @returns {Story} Story */ static parse (content) { - let story = null; + // Create new story. + const story = new Story(); + + // Set default start node. let startNode = null; // Send to node-html-parser @@ -40,103 +43,99 @@ export default class Twine2HTMLParser { pre: true }); - // Pull out the tw-storydata element + // Pull out the `` element. const storyData = dom.querySelector('tw-storydata'); - // Does the element exist? - if (storyData !== null) { - // Create a Story. - story = new Story(); + // If there was no element, we cannot continue. + if (storyData === null) { + // If there is not a element, this is not a Twine 2 story! + throw new Error('Not Twine 2 HTML content!'); + } - /** - * name: (string) Required. - * The name of the story. - */ - if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'name')) { - // Create StoryTitle passage based on name - story.addPassage(new Passage('StoryTitle', storyData.attributes.name)); - // Set the story name - story.name = storyData.attributes.name; - } else { - // Name is a required filed. Warn user. - console.warn('Twine 2 HTML must have a name!'); - // Set a default name - story.addPassage(new Passage('StoryTitle', 'Untitled')); - } + /** + * name: (string) Required. + * The name of the story. + */ + if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'name')) { + // Set the story name + story.name = storyData.attributes.name; + } else { + // Name is a required field. Warn user. + console.warn('Twine 2 HTML must have a name!'); + } - /** - * ifid: (string) Required. - * An IFID is a sequence of between 8 and 63 characters, - * each of which shall be a digit, a capital letter or a - * hyphen that uniquely identify a story (see Treaty of Babel). - */ - if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'ifid')) { - // Update story IFID - story.IFID = storyData.attributes.ifid; - } else { - // Name is a required filed. Warn user. - console.warn('Twine 2 HTML must have an IFID!'); - } + /** + * ifid: (string) Required. + * An IFID is a sequence of between 8 and 63 characters, + * each of which shall be a digit, a capital letter or a + * hyphen that uniquely identify a story (see Treaty of Babel). + */ + if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'ifid')) { + // Update story IFID. + story.IFID = storyData.attributes.ifid; + } else { + // Name is a required filed. Warn user. + console.warn('Twine 2 HTML must have an IFID!'); + } - /** - * creator: (string) Optional. - * The name of program used to create the file. - */ - if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'creator')) { - // Update story creator - story.creator = storyData.attributes.creator; - } + /** + * creator: (string) Optional. + * The name of program used to create the file. + */ + if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'creator')) { + // Update story creator + story.creator = storyData.attributes.creator; + } - /** - * creator-version: (string) Optional. - * The version of the program used to create the file. - */ - if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'creator-version')) { - // Update story creator version - story.creatorVersion = storyData.attributes['creator-version']; - } + /** + * creator-version: (string) Optional. + * The version of the program used to create the file. + */ + if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'creator-version')) { + // Update story creator version + story.creatorVersion = storyData.attributes['creator-version']; + } - /** - * format: (string) Optional. - * The story format used to create the story. - */ - if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'format')) { - // Update story format - story.format = storyData.attributes.format; - } + /** + * format: (string) Optional. + * The story format used to create the story. + */ + if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'format')) { + // Update story format + story.format = storyData.attributes.format; + } - /** - * format-version: (string) Optional. - * The version of the story format used to create the story. - */ - if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'format-version')) { - // Update story format version - story.formatVersion = storyData.attributes['format-version']; - } + /** + * format-version: (string) Optional. + * The version of the story format used to create the story. + */ + if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'format-version')) { + // Update story format version + story.formatVersion = storyData.attributes['format-version']; + } - /** - * zoom: (string) Optional. - * The decimal level of zoom (i.e. 1.0 is 100% and 1.2 would be 120% zoom level). - */ - if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'zoom')) { - // Update story zoom - story.zoom = Number(Number.parseFloat(storyData.attributes.zoom).toFixed(2)); - } + /** + * zoom: (string) Optional. + * The decimal level of zoom (i.e. 1.0 is 100% and 1.2 would be 120% zoom level). + */ + if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'zoom')) { + // Update story zoom + story.zoom = Number(Number.parseFloat(storyData.attributes.zoom).toFixed(2)); + } - /** - * startnode: (string) Optional. - * The PID matching a element whose content should be displayed first. - */ - if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'startnode')) { - // Take string value and convert to Int - startNode = Number.parseInt(storyData.attributes.startnode, 10); - } + /** + * startnode: (string) Optional. + * The PID matching a `` element whose content should be displayed first. + */ + if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'startnode')) { + // Take string value and convert to Int + startNode = Number.parseInt(storyData.attributes.startnode, 10); } else { - // If there is not a element, this is not a Twine 2 story! - throw new Error('Not a Twine 2-style file!'); + // Throw error without start node. + throw new Error('Missing startnode in !'); } - // Pull out the tw-passagedata elements + // Pull out the `` element. const storyPassages = dom.querySelectorAll('tw-passagedata'); // Move through the passages @@ -146,6 +145,11 @@ export default class Twine2HTMLParser { // Get the passage text const text = storyPassages[passage].rawText; + /** + * position: (string) Optional. + * Comma-separated X and Y position of the upper-left + * of the passage when viewed within the Twine 2 editor. + */ // Set a default position. let position = null; // Does position exist? @@ -154,6 +158,11 @@ export default class Twine2HTMLParser { position = attr.position; } + /** + * size: (string) Optional. + * Comma-separated width and height of the + * passage when viewed within the Twine 2 editor. + */ // Set a default size. let size = null; // Does size exist? @@ -175,7 +184,7 @@ export default class Twine2HTMLParser { // Escape the name name = Twine2HTMLParser.escapeMetacharacters(attr.name); } else { - console.warn('Encountered passage without a name! Will not add.'); + throw new Error('Cannot parse passage data without name!'); } // Create empty tag array. @@ -228,6 +237,13 @@ export default class Twine2HTMLParser { console.warn('Passages are required to have PID. Will not add!'); } + // Check the current PID against startNode number. + if (pid === startNode) { + // These match. + // Save the passage name. + story.start = name; + } + // If passage is missing name and PID (required attributes), // they are not added. if (name !== null && pid !== -1) { @@ -237,13 +253,17 @@ export default class Twine2HTMLParser { name, text, tags, - metadata, - pid + metadata ) ); } } + // There was an invalid startNode. + if (story.start === '') { + throw new Error('startNode does not exist within passages!'); + } + // Look for the style element const styleElement = dom.querySelector('#twine-user-stylesheet'); @@ -275,23 +295,10 @@ export default class Twine2HTMLParser { } } - // Was there a startNode? - if (startNode !== null) { - // Try to find starting passage by PID. - const startingPassage = story.getPassageByPID(startNode); - // Does the passage exist (yet)? - if (startingPassage !== null) { - // If so, update property to name of passage. - story.start = startingPassage.name; - } else { - throw new Error('Invalid startnode detected in !'); - } - } - - // Look for all elements + // Look for all elements. const twTags = dom.querySelectorAll('tw-tag'); - // Parse through the entries + // Parse through the entries. twTags.forEach((tags) => { // Parse each tag element const attributes = tags.attributes; diff --git a/test/Story/startmeta.twee b/test/Story/startmeta.twee deleted file mode 100644 index 6dde9413..00000000 --- a/test/Story/startmeta.twee +++ /dev/null @@ -1,29 +0,0 @@ -:: StoryTitle -twineExample - -:: Another -This should be the start passage! - -:: Start -Some content. - -:: StoryData -{ - "ifid": "2B68ECD6-348F-4CF5-96F8-549A512A8128", - "format": "Harlowe", - "formatVersion": "2.1.0", - "zoom": "1", - "start": "Another" -} - -:: Script1 [script] -// Some code - -:: Script2 [script] -//More code here! - -:: Style1 [stylesheet] -body {font-size: 1.2em} - -:: Style2 [stylesheet] -p {font-style: italic;} diff --git a/test/Story/test.twee b/test/Story/test.twee deleted file mode 100644 index ef6ce961..00000000 --- a/test/Story/test.twee +++ /dev/null @@ -1,25 +0,0 @@ -:: StoryTitle -twineExample - -:: Start -Some content. - -:: StoryData -{ - "ifid": "2B68ECD6-348F-4CF5-96F8-549A512A8128", - "format": "Harlowe", - "formatVersion": "2.1.0", - "zoom": "1" -} - -:: Script1 [script] -// Some code - -:: Script2 [script] -//More code here! - -:: Style1 [stylesheet] -body {font-size: 1.2em} - -:: Style2 [stylesheet] -p {font-style: italic;} diff --git a/test/TweeWriter/test1.twee b/test/TweeWriter/test1.twee deleted file mode 100644 index a542f3ca..00000000 --- a/test/TweeWriter/test1.twee +++ /dev/null @@ -1,10 +0,0 @@ -:: StoryData -{ - "ifid": "E0F01980-B9C6-4828-A31F-7FEFC5A164D2", - "format": "Test", - "format-version": "1.2.3", - "zoom": 1, - "start": "Untitled" -} - -[object Object][object Object][object Object] \ No newline at end of file diff --git a/test/TweeWriter/test3.twee b/test/TweeWriter/test3.twee deleted file mode 100644 index 1c3fa9a0..00000000 --- a/test/TweeWriter/test3.twee +++ /dev/null @@ -1,7 +0,0 @@ -:: StoryData -{ - "ifid": "DE7DF8AD-E4CD-499E-A4E7-C5B98B73449A", - "start": "Start" -} - -[object Object][object Object] \ No newline at end of file diff --git a/test/TweeWriter/test4.twee b/test/TweeWriter/test4.twee deleted file mode 100644 index d11a0d04..00000000 --- a/test/TweeWriter/test4.twee +++ /dev/null @@ -1,14 +0,0 @@ -:: StoryData -{ - "ifid": "D5D54A73-4E5F-4186-9276-EBDE28110006" -} - -:: StoryTitle -Title - -:: Start -Content - -:: Untitled -Some stuff - diff --git a/test/TweeWriter/test5.twee b/test/TweeWriter/test5.twee deleted file mode 100644 index 4ab5bee3..00000000 --- a/test/TweeWriter/test5.twee +++ /dev/null @@ -1,12 +0,0 @@ -:: StoryData -{ - "ifid": "BAD36C0B-0BD5-4A81-8EF7-255C9A6912A2", - "start": "Start", - "tag-colors": { - "bar": "green", - "foo": "red", - "qaz": "blue" - } -} - -[object Object][object Object][object Object] \ No newline at end of file diff --git a/test/TweeWriter/test6.twee b/test/TweeWriter/test6.twee deleted file mode 100644 index 0061f4f6..00000000 --- a/test/TweeWriter/test6.twee +++ /dev/null @@ -1,7 +0,0 @@ -:: StoryData -{ - "ifid": "371ADF1A-CBF2-46BB-B17A-E19F7C921013", - "start": "Start" -} - -[object Object][object Object][object Object] \ No newline at end of file diff --git a/test/TweeWriter/test7.twee b/test/TweeWriter/test7.twee deleted file mode 100644 index d84cb0c9..00000000 --- a/test/TweeWriter/test7.twee +++ /dev/null @@ -1,7 +0,0 @@ -:: StoryData -{ - "ifid": "F183E248-D1CD-47FB-8C35-27BABB875CEA", - "start": "Start" -} - -[object Object][object Object][object Object] \ No newline at end of file diff --git a/test/Twine2HTMLParser.IGNORE.js b/test/Twine2HTMLParser.test.js similarity index 85% rename from test/Twine2HTMLParser.IGNORE.js rename to test/Twine2HTMLParser.test.js index 95d90fbb..ab1cd02b 100644 --- a/test/Twine2HTMLParser.IGNORE.js +++ b/test/Twine2HTMLParser.test.js @@ -14,8 +14,7 @@ describe('Twine2HTMLParser', () => { it('Should be able to parse Twine 2 HTML for story name', () => { const fr = FileReader.read('test/Twine2HTMLParser/twineExample.html'); const story = Twine2HTMLParser.parse(fr); - const storyTitle = story.getPassageByName('StoryTitle'); - expect(storyTitle.text).toBe('twineExample'); + expect(story.name).toBe('twineExample'); }); it('Should be able to parse Twine 2 HTML for correct number of passages', () => { @@ -31,11 +30,10 @@ describe('Twine2HTMLParser', () => { expect(p.tags).toHaveLength(2); }); - it('Should set a missing name to Untitled', () => { + it('Should have default name', () => { const fr = FileReader.read('test/Twine2HTMLParser/missingName.html'); const story = Twine2HTMLParser.parse(fr); - const p = story.getPassageByName('StoryTitle'); - expect(p.text).toBe('Untitled'); + expect(story.name).toBe(''); }); it('Should set a missing IFID to an empty string', () => { @@ -118,27 +116,22 @@ describe('Twine2HTMLParser', () => { expect(stylesheetPassages.length).toBe(1); }); - it('Should not have start property if startNode does not exist when parsed', () => { + it('Should throw error if startNode is missing', () => { const fr = FileReader.read('test/Twine2HTMLParser/missingStartnode.html'); - const story = Twine2HTMLParser.parse(fr); - expect(story.start).toBe(''); + expect(() => { Twine2HTMLParser.parse(fr); }).toThrow(); }); - it('Should not add passages without names', () => { + it('Should throw error if passage name is missing', () => { const fr = FileReader.read('test/Twine2HTMLParser/missingPassageName.html'); - const story = Twine2HTMLParser.parse(fr); - // There is only one passage, StoryTitle - expect(story.size()).toBe(0); + expect(() => { Twine2HTMLParser.parse(fr); }).toThrow(); }); - it('Should not add passages without PID', () => { + it('Should throw error without PID', () => { const fr = FileReader.read('test/Twine2HTMLParser/missingPID.html'); - const story = Twine2HTMLParser.parse(fr); - // There is only one passage, StoryTitle - expect(story.size()).toBe(0); + expect(() => { Twine2HTMLParser.parse(fr); }).toThrow(); }); - it('Parse tag colors', () => { + it('Should parse tag colors', () => { const fr = FileReader.read('test/Twine2HTMLParser/tagColors.html'); const story = Twine2HTMLParser.parse(fr); // Test for tag colors @@ -146,14 +139,12 @@ describe('Twine2HTMLParser', () => { expect(tagColors.a).toBe('red'); }); - it('Will not set start if startnode is missing', () => { + it('Should throw error if startnode is missing', () => { const fr = FileReader.read('test/Twine2HTMLParser/missingStartnode.html'); - const story = Twine2HTMLParser.parse(fr); - // Test for default start - expect(story.start).toBe(''); + expect(() => { Twine2HTMLParser.parse(fr); }).toThrow(); }); - it('Throw error when startnode has PID that does not exist', () => { + it('Should throw error when startnode has PID that does not exist', () => { const fr = FileReader.read('test/Twine2HTMLParser/lyingStartnode.html'); expect(() => { Twine2HTMLParser.parse(fr);