extwee 1.6.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/.eslintrc.json +25 -24
  2. package/.github/workflows/nodejs.yml +24 -24
  3. package/.travis.yml +13 -13
  4. package/CODE_OF_CONDUCT.md +82 -0
  5. package/LICENSE +21 -21
  6. package/README.md +36 -205
  7. package/SECURITY.md +12 -0
  8. package/babel.config.json +22 -0
  9. package/bin/extwee.js +49 -0
  10. package/index.js +31 -26
  11. package/package.json +59 -45
  12. package/src/FileReader.js +33 -35
  13. package/src/HTMLParser.js +343 -206
  14. package/src/HTMLWriter.js +231 -172
  15. package/src/Passage.js +202 -24
  16. package/src/Story.js +490 -122
  17. package/src/StoryFormat.js +300 -28
  18. package/src/StoryFormatParser.js +142 -59
  19. package/src/TweeParser.js +161 -207
  20. package/src/TweeWriter.js +98 -96
  21. package/story-formats/chapbook-1.2.0/format.js +1 -0
  22. package/story-formats/chapbook-1.2.0/logo.svg +1 -0
  23. package/story-formats/harlowe-1.2.4/format.js +1 -0
  24. package/story-formats/harlowe-1.2.4/icon.svg +78 -0
  25. package/story-formats/harlowe-2.1.0/format.js +2 -0
  26. package/story-formats/harlowe-2.1.0/icon.svg +78 -0
  27. package/story-formats/harlowe-3.1.0/format.js +3 -0
  28. package/story-formats/harlowe-3.1.0/icon.svg +78 -0
  29. package/story-formats/paperthin-1.0.0/format.js +1 -0
  30. package/story-formats/paperthin-1.0.0/icon.svg +5 -0
  31. package/story-formats/snowman-1.4.0/format.js +1 -0
  32. package/story-formats/snowman-1.4.0/icon.svg +436 -0
  33. package/story-formats/snowman-2.0.2/format.js +1 -0
  34. package/story-formats/snowman-2.0.2/icon.svg +436 -0
  35. package/story-formats/sugarcube-1.0.35/LICENSE +23 -0
  36. package/story-formats/sugarcube-1.0.35/format.js +1 -0
  37. package/story-formats/sugarcube-1.0.35/icon.svg +56 -0
  38. package/story-formats/sugarcube-2.31.1/LICENSE +22 -0
  39. package/story-formats/sugarcube-2.31.1/format.js +1 -0
  40. package/story-formats/sugarcube-2.31.1/icon.svg +56 -0
  41. package/test/{HTMLWriter/example7.twee → CLI/example6.twee} +16 -16
  42. package/test/CLI/harlowe.js +3 -0
  43. package/test/CLI/input.html +47 -0
  44. package/test/CLI/test.twee +18 -0
  45. package/test/CLI/test2.html +47 -0
  46. package/test/CLI/tweeExample.twee +17 -0
  47. package/test/CLI/twineExample.html +15 -0
  48. package/test/CLI.test.js +30 -0
  49. package/test/FileReader.test.js +14 -0
  50. package/test/HTMLParser/Example1.html +53 -0
  51. package/test/HTMLParser/Tags.html +15 -0
  52. package/test/HTMLParser/lyingStartnode.html +15 -0
  53. package/test/HTMLParser/lyingTagColors.html +48 -0
  54. package/test/HTMLParser/missingCreator.html +11 -0
  55. package/test/HTMLParser/missingCreatorVersion.html +11 -0
  56. package/test/HTMLParser/missingFormat.html +11 -0
  57. package/test/HTMLParser/missingFormatVersion.html +11 -0
  58. package/test/HTMLParser/missingIFID.html +11 -0
  59. package/test/HTMLParser/missingName.html +33 -0
  60. package/test/HTMLParser/missingPID.html +15 -0
  61. package/test/HTMLParser/missingPassageName.html +15 -0
  62. package/test/HTMLParser/missingPassageTags.html +15 -0
  63. package/test/HTMLParser/missingPosition.html +15 -0
  64. package/test/HTMLParser/missingScript.html +14 -0
  65. package/test/HTMLParser/missingSize.html +35 -0
  66. package/test/HTMLParser/missingStartnode.html +11 -0
  67. package/test/HTMLParser/missingStyle.html +14 -0
  68. package/test/HTMLParser/missingZoom.html +11 -0
  69. package/test/HTMLParser/tagColors.html +31 -0
  70. package/test/HTMLParser/twineExample.html +15 -46
  71. package/test/HTMLParser/twineExample2.html +15 -0
  72. package/test/HTMLParser/twineExample3.html +15 -0
  73. package/test/HTMLParser.test.js +177 -0
  74. package/test/HTMLWriter/TestTags.html +42 -0
  75. package/test/HTMLWriter/{test10.html → creator.html} +8 -5
  76. package/test/HTMLWriter/example6.twee +16 -16
  77. package/test/HTMLWriter/{example.twee → missingStoryTitle.twee} +29 -29
  78. package/test/HTMLWriter/test11.html +123 -0
  79. package/test/HTMLWriter/test2.html +14 -11
  80. package/test/HTMLWriter/test3.html +7 -13
  81. package/test/HTMLWriter/test4.html +8 -5
  82. package/test/HTMLWriter/test6.html +7 -5
  83. package/test/HTMLWriter.test.js +289 -0
  84. package/test/Passage.test.js +104 -0
  85. package/test/Roundtrip/Example1.html +64 -0
  86. package/test/Roundtrip/example1.twee +21 -0
  87. package/test/Roundtrip/example2.twee +18 -0
  88. package/test/Roundtrip/harlowe.js +3 -0
  89. package/test/Roundtrip/round.html +50 -0
  90. package/test/Roundtrip.test.js +48 -0
  91. package/test/Story/startmeta.twee +29 -29
  92. package/test/Story/test.twee +25 -25
  93. package/test/Story.test.js +369 -0
  94. package/test/StoryFormat.test.js +152 -0
  95. package/test/StoryFormatParser/example.js +3 -0
  96. package/test/StoryFormatParser/{test2.js → example2.js} +3 -3
  97. package/test/StoryFormatParser/format_doublename.js +1 -0
  98. package/test/StoryFormatParser/harlowe.js +2 -2
  99. package/test/StoryFormatParser/missingAuthor.js +1 -0
  100. package/test/StoryFormatParser/missingDescription.js +1 -0
  101. package/test/StoryFormatParser/missingImage.js +1 -0
  102. package/test/StoryFormatParser/missingLicense.js +1 -0
  103. package/test/StoryFormatParser/missingName.js +1 -0
  104. package/test/StoryFormatParser/missingProofing.js +1 -0
  105. package/test/StoryFormatParser/missingSource.js +1 -0
  106. package/test/StoryFormatParser/missingURL.js +1 -0
  107. package/test/StoryFormatParser/missingVersion.js +1 -0
  108. package/test/StoryFormatParser/versionWrong.js +1 -0
  109. package/test/StoryFormatParser.test.js +91 -0
  110. package/test/TweeParser/emptytags.twee +2 -2
  111. package/test/TweeParser/example.twee +32 -29
  112. package/test/TweeParser/missing.twee +19 -0
  113. package/test/{HTMLWriter/example5.twee → TweeParser/multipleScriptPassages.twee} +19 -13
  114. package/test/{HTMLWriter/example4.twee → TweeParser/multipleStyleTag.twee} +19 -13
  115. package/test/TweeParser/multipletags.twee +10 -2
  116. package/test/TweeParser/noTitle.twee +2 -0
  117. package/test/TweeParser/notes.twee +16 -32
  118. package/test/TweeParser/pasagemetadataerror.twee +2 -2
  119. package/test/{HTMLWriter/example2.twee → TweeParser/scriptPassage.twee} +16 -13
  120. package/test/TweeParser/singletag.twee +13 -2
  121. package/test/TweeParser/startMetadata.twee +14 -0
  122. package/test/TweeParser/storydataerror.twee +25 -25
  123. package/test/{HTMLWriter/example3.twee → TweeParser/stylePassage.twee} +16 -13
  124. package/test/TweeParser/test.twee +25 -25
  125. package/test/TweeParser.test.js +79 -0
  126. package/test/TweeWriter/test1.twee +14 -9
  127. package/test/TweeWriter/test3.twee +7 -10
  128. package/test/TweeWriter/test4.twee +14 -0
  129. package/test/TweeWriter/test5.twee +20 -0
  130. package/test/TweeWriter.test.js +85 -0
  131. package/main.js +0 -106
  132. package/src/DirectoryReader.js +0 -114
  133. package/src/DirectoryWatcher.js +0 -87
  134. package/test/DirectoryReader/css/test.css +0 -3
  135. package/test/DirectoryReader1/js/Site.js +0 -1
  136. package/test/DirectoryReader2/error.js +0 -1
  137. package/test/DirectoryReader2/example.css +0 -3
  138. package/test/DirectoryReader2/index.twee +0 -6
  139. package/test/DirectoryReader3/twee/index.twee +0 -6
  140. package/test/DirectoryWatcher/example.txt +0 -0
  141. package/test/DirectoryWatcher/test.txt +0 -0
  142. package/test/DirectoryWatcher/test1.txt +0 -0
  143. package/test/HTMLWriter/test5.html +0 -48
  144. package/test/HTMLWriter/test7.html +0 -48
  145. package/test/HTMLWriter/test8.html +0 -48
  146. package/test/HTMLWriter/test9.html +0 -48
  147. package/test/StoryFormatParser/test.js +0 -2
  148. package/test/TweeParser/test.twee3 +0 -11
  149. package/test/TweeWriter/metatest.twee +0 -12
  150. package/test/TweeWriter/test2.twee +0 -15
  151. package/test/test.js +0 -736
package/src/HTMLParser.js CHANGED
@@ -1,206 +1,343 @@
1
- /**
2
- * @external HTML
3
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML|HTML}
4
- */
5
-
6
- const { parse } = require('node-html-parser');
7
- const HtmlParser = parse;
8
- const Story = require('./Story.js');
9
- const Passage = require('./Passage.js');
10
- /**
11
- * @class HTMLParser
12
- * @module HTMLParser
13
- */
14
- class HTMLParser {
15
- /**
16
- * @function HTMLParser
17
- * @class
18
- * @param {HTML} content - HTML to parse
19
- */
20
- constructor (content) {
21
- this.story = null;
22
- this.parse(content);
23
- }
24
-
25
- /**
26
- * Parse HTML text into a JS DOM-like object
27
- *
28
- * @param {string} content - Content to parse
29
- * @returns {void}
30
- */
31
- parse (content) {
32
- // Send to node-html-parser
33
- // Enable getting the content of 'script', 'style', and 'pre' elements
34
- // Get back a DOM
35
- const dom = new HtmlParser(
36
- content,
37
- {
38
- lowerCaseTagName: false,
39
- script: true,
40
- style: true,
41
- pre: true
42
- });
43
-
44
- // Pull out the tw-storydata element
45
- const storyData = dom.querySelector('tw-storydata');
46
-
47
- if (storyData != null) {
48
- this.story = new Story();
49
- this.story.name = storyData.attributes.name;
50
- this.story.creator = storyData.attributes.creator;
51
- this.story.creatorVersion = storyData.attributes['creator-version'];
52
-
53
- this.story.metadata = {};
54
- this.story.metadata.ifid = storyData.attributes.ifid;
55
- this.story.metadata.format = storyData.attributes.format;
56
- this.story.metadata.formatVersion = storyData.attributes['format-version'];
57
- this.story.metadata.zoom = storyData.attributes.zoom;
58
- this.story.metadata.start = storyData.attributes.startnode;
59
- } else {
60
- throw new Error('Error: Not a Twine 2-style file!');
61
- }
62
-
63
- // Pull out the tw-passagedata elements
64
- const storyPassages = dom.querySelectorAll('tw-passagedata');
65
-
66
- // Create an empty array
67
- this.story.passages = [];
68
-
69
- // Set default pid
70
- let pid = 1;
71
-
72
- // Add StoryTitle
73
- this.story.passages.push(
74
- new Passage(
75
- 'StoryTitle',
76
- [],
77
- {},
78
- this.story.name,
79
- pid
80
- )
81
- );
82
-
83
- // Increase PID by one before parsing any other passages
84
- pid++;
85
-
86
- // Move through the passages
87
- for (const passage in storyPassages) {
88
- // Get the passage attributes
89
- const attr = storyPassages[passage].attributes;
90
- // Get the passage text
91
- const text = storyPassages[passage].rawText;
92
-
93
- // Save position
94
- const position = attr.position;
95
-
96
- // Save size
97
- const size = attr.size;
98
-
99
- // Escape the name
100
- const name = this._escapeMetacharacters(attr.name);
101
-
102
- // Create empty tags
103
- let tags = '';
104
-
105
- // Escape any tags
106
- // (Attributes can, themselves, be emtpy strings.)
107
- if (attr.tags.length > 0 && attr.tags !== '""') {
108
- tags = this._escapeMetacharacters(attr.tags);
109
- }
110
-
111
- // Split by spaces
112
- tags = tags.split(' ');
113
-
114
- // Remove any empty strings
115
- tags = tags.filter(tag => tag !== '');
116
-
117
- // Add a new Passage into an array
118
- this.story.passages.push(
119
- new Passage(
120
- name,
121
- tags,
122
- {
123
- position: position,
124
- size: size
125
-
126
- },
127
- text,
128
- pid
129
- )
130
- );
131
-
132
- pid++;
133
- }
134
-
135
- // Look for the style element
136
- const styleElement = dom.querySelector('#twine-user-stylesheet');
137
-
138
- // Check if there is any content.
139
- // If not, we won't add empty passages
140
- if (styleElement.rawText.length > 0) {
141
- // Add UserStylesheet
142
- this.story.passages.push(
143
- new Passage(
144
- 'UserStylesheet',
145
- ['stylesheet'],
146
- {},
147
- styleElement.rawText
148
- )
149
- );
150
- }
151
-
152
- // Look for the script element
153
- const scriptElement = dom.querySelector('#twine-user-script');
154
-
155
- // Check if there is any content.
156
- // If not, we won't add empty passages
157
- if (scriptElement.rawText.length > 0) {
158
- // Add UserScript
159
- this.story.passages.push(
160
- new Passage(
161
- 'UserScript',
162
- ['script'],
163
- {},
164
- scriptElement.rawText
165
- )
166
- );
167
- }
168
-
169
- // Now that all passages have been handled,
170
- // change the start name
171
- this.story.metadata.start = this.story.getStartingPassage();
172
-
173
- // Add StoryData
174
- this.story.passages.push(
175
- new Passage(
176
- 'StoryData',
177
- [],
178
- {},
179
- JSON.stringify(this.story.metadata, null, 4)
180
- )
181
- );
182
- }
183
-
184
- /**
185
- * Try to escape meta-characters
186
- *
187
- * @param {string} result - Text to parse
188
- * @returns {string} Escaped characters
189
- */
190
- _escapeMetacharacters (result) {
191
- // Replace any single backslash with two of them
192
- result = result.replace(/\\/g, '\\');
193
- // Double-escape escaped {
194
- result = result.replace(/\\\{/g, '\\\\{');
195
- // Double-escape escaped }
196
- result = result.replace(/\\\}/g, '\\\\}');
197
- // Double-escape escaped [
198
- result = result.replace(/\\\[/g, '\\\\[');
199
- // Double-escape escaped ]
200
- result = result.replace(/\\\]/g, '\\\\]');
201
-
202
- return result;
203
- }
204
- }
205
-
206
- module.exports = HTMLParser;
1
+ /**
2
+ * @external HTML
3
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML|HTML}
4
+ */
5
+
6
+ import { parse as HtmlParser } from 'node-html-parser';
7
+ import Story from './Story.js';
8
+ import Passage from './Passage.js';
9
+ /**
10
+ * @class HTMLParser
11
+ * @module HTMLParser
12
+ */
13
+ export default class HTMLParser {
14
+ /**
15
+ * Parse HTML text into a JS DOM-like object
16
+ *
17
+ * @public
18
+ * @static
19
+ * @function parse
20
+ * @param {string} content - Content to parse
21
+ * @returns {Story} story
22
+ */
23
+ static parse (content) {
24
+ let story = null;
25
+ let startNode = null;
26
+
27
+ // Send to node-html-parser
28
+ // Enable getting the content of 'script', 'style', and 'pre' elements
29
+ // Get back a DOM
30
+ const dom = new HtmlParser(
31
+ content,
32
+ {
33
+ lowerCaseTagName: false,
34
+ script: true,
35
+ style: true,
36
+ pre: true
37
+ });
38
+
39
+ // Pull out the tw-storydata element
40
+ const storyData = dom.querySelector('tw-storydata');
41
+
42
+ // Does the <tw-storydata> element exist?
43
+ if (storyData !== null) {
44
+ // Create a Story.
45
+ story = new Story();
46
+
47
+ /**
48
+ * name: (string) Required.
49
+ * The name of the story.
50
+ */
51
+ if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'name')) {
52
+ // Create StoryTitle passage based on name
53
+ story.addPassage(new Passage('StoryTitle', storyData.attributes.name));
54
+ } else {
55
+ // Name is a required filed. Warn user.
56
+ console.warn('Twine 2 HTML must have a name!');
57
+ // Set a default name
58
+ story.addPassage(new Passage('StoryTitle', 'Untitled'));
59
+ }
60
+
61
+ /**
62
+ * ifid: (string) Required.
63
+ * An IFID is a sequence of between 8 and 63 characters,
64
+ * each of which shall be a digit, a capital letter or a
65
+ * hyphen that uniquely identify a story (see Treaty of Babel).
66
+ */
67
+ if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'ifid')) {
68
+ // Update story IFID
69
+ story.IFID = storyData.attributes.ifid;
70
+ } else {
71
+ // Name is a required filed. Warn user.
72
+ console.warn('Twine 2 HTML must have an IFID!');
73
+ }
74
+
75
+ /**
76
+ * creator: (string) Optional.
77
+ * The name of program used to create the file.
78
+ */
79
+ if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'creator')) {
80
+ // Update story creator
81
+ story.creator = storyData.attributes.creator;
82
+ }
83
+
84
+ /**
85
+ * creator-version: (string) Optional.
86
+ * The version of the program used to create the file.
87
+ */
88
+ if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'creator-version')) {
89
+ // Update story creator version
90
+ story.creatorVersion = storyData.attributes['creator-version'];
91
+ }
92
+
93
+ /**
94
+ * format: (string) Optional.
95
+ * The story format used to create the story.
96
+ */
97
+ if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'format')) {
98
+ // Update story format
99
+ story.format = storyData.attributes.format;
100
+ }
101
+
102
+ /**
103
+ * format-version: (string) Optional.
104
+ * The version of the story format used to create the story.
105
+ */
106
+ if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'format-version')) {
107
+ // Update story format version
108
+ story.formatVersion = storyData.attributes['format-version'];
109
+ }
110
+
111
+ /**
112
+ * zoom: (string) Optional.
113
+ * The decimal level of zoom (i.e. 1.0 is 100% and 1.2 would be 120% zoom level).
114
+ */
115
+ if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'zoom')) {
116
+ // Update story zoom
117
+ story.zoom = Number(Number.parseFloat(storyData.attributes.zoom).toFixed(2));
118
+ }
119
+
120
+ /**
121
+ * startnode: (string) Optional.
122
+ * The PID matching a <tw-passagedata> element whose content should be displayed first.
123
+ */
124
+ if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'startnode')) {
125
+ // Take string value and convert to Int
126
+ startNode = Number.parseInt(storyData.attributes.startnode, 10);
127
+ }
128
+ } else {
129
+ // If there is not a <tw-storydata> element, this is not a Twine 2 story!
130
+ throw new Error('Not a Twine 2-style file!');
131
+ }
132
+
133
+ // Pull out the tw-passagedata elements
134
+ const storyPassages = dom.querySelectorAll('tw-passagedata');
135
+
136
+ // Move through the passages
137
+ for (const passage in storyPassages) {
138
+ // Get the passage attributes
139
+ const attr = storyPassages[passage].attributes;
140
+ // Get the passage text
141
+ const text = storyPassages[passage].rawText;
142
+
143
+ // Set a default position.
144
+ let position = null;
145
+ // Does position exist?
146
+ if (Object.prototype.hasOwnProperty.call(attr, 'position')) {
147
+ // Update position.
148
+ position = attr.position;
149
+ }
150
+
151
+ // Set a default size.
152
+ let size = null;
153
+ // Does size exist?
154
+ if (Object.prototype.hasOwnProperty.call(attr, 'size')) {
155
+ // Update size.
156
+ size = attr.size;
157
+ }
158
+
159
+ /**
160
+ * name: (string) Required.
161
+ * The name of the passage.
162
+ *
163
+ * https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md#passages
164
+ */
165
+ // Create a default value
166
+ let name = null;
167
+ // Does name exist?
168
+ if (Object.prototype.hasOwnProperty.call(attr, 'name')) {
169
+ // Escape the name
170
+ name = HTMLParser.escapeMetacharacters(attr.name);
171
+ } else {
172
+ console.warn('Encountered passage without a name! Will not add.');
173
+ }
174
+
175
+ // Create empty tag array.
176
+ let tags = [];
177
+ // Does the tags attribute exist?
178
+ if (Object.prototype.hasOwnProperty.call(attr, 'tags')) {
179
+ // Escape any tags
180
+ // (Attributes can, themselves, be empty strings.)
181
+ if (attr.tags.length > 0 && attr.tags !== '""') {
182
+ // Escape the tags
183
+ tags = HTMLParser.escapeMetacharacters(attr.tags);
184
+ // Split by spaces into an array
185
+ tags = tags.split(' ');
186
+ }
187
+
188
+ // Remove any empty strings.
189
+ tags = tags.filter(tag => tag !== '');
190
+ }
191
+
192
+ // Create metadata for passage.
193
+ const metadata = {};
194
+
195
+ // Does position exist?
196
+ if (position !== null) {
197
+ // Add the property to metadata
198
+ metadata.position = position;
199
+ }
200
+
201
+ // Does size exist?
202
+ if (size !== null) {
203
+ // Add the property to metadata
204
+ metadata.size = size;
205
+ }
206
+
207
+ /**
208
+ * pid: (string) Required.
209
+ * The Passage ID (PID).
210
+ * (Note: This is subject to change during editing with Twine 2.)
211
+ *
212
+ * https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md#passages
213
+ */
214
+ // Create a default PID
215
+ let pid = -1;
216
+ // Does pid exist?
217
+ if (Object.prototype.hasOwnProperty.call(attr, 'pid')) {
218
+ // Parse string into int
219
+ // Update PID
220
+ pid = Number.parseInt(attr.pid, 10);
221
+ } else {
222
+ console.warn('Passages are required to have PID. Will not add!');
223
+ }
224
+
225
+ // If passage is missing name and PID (required attributes),
226
+ // they are not added.
227
+ if (name !== null && pid !== -1) {
228
+ // Add a new Passage into an array
229
+ story.addPassage(
230
+ new Passage(
231
+ name,
232
+ text,
233
+ tags,
234
+ metadata,
235
+ pid
236
+ )
237
+ );
238
+ }
239
+ }
240
+
241
+ // Look for the style element
242
+ const styleElement = dom.querySelector('#twine-user-stylesheet');
243
+
244
+ // Does the style element exist?
245
+ if (styleElement !== null) {
246
+ // Check if there is any content.
247
+ if (styleElement.rawText.length > 0) {
248
+ // Update stylesheet passage
249
+ story.addPassage(new Passage(
250
+ 'UserStylesheet',
251
+ styleElement.rawText,
252
+ ['stylesheet'])
253
+ );
254
+ }
255
+ }
256
+
257
+ // Look for the script element
258
+ const scriptElement = dom.querySelector('#twine-user-script');
259
+
260
+ // Does the script element exist?
261
+ if (scriptElement !== null) {
262
+ // Check if there is any content.
263
+ if (scriptElement.rawText.length > 0) {
264
+ story.addPassage(new Passage(
265
+ 'UserScript',
266
+ scriptElement.rawText,
267
+ ['script'])
268
+ );
269
+ }
270
+ }
271
+
272
+ // Was there a startNode?
273
+ if (startNode !== null) {
274
+ // Try to find starting passage by PID.
275
+ const startingPassage = story.getPassageByPID(startNode);
276
+ // Does the passage exist (yet)?
277
+ if (startingPassage !== null) {
278
+ // If so, update property to name of passage.
279
+ story.start = startingPassage.name;
280
+ } else {
281
+ throw new Error('Invalid startnode detected in <tw-storydata>!');
282
+ }
283
+ }
284
+
285
+ // Look for all <tw-tag> elements
286
+ const twTags = dom.querySelectorAll('tw-tag');
287
+
288
+ // Parse through the entries
289
+ twTags.forEach((tags) => {
290
+ // Parse each tag element
291
+ const attributes = tags.attributes;
292
+
293
+ // Create default value for name
294
+ let name = '';
295
+
296
+ // Create default value for color
297
+ let color = '';
298
+
299
+ // Check for name
300
+ if (Object.prototype.hasOwnProperty.call(attributes, 'name')) {
301
+ name = attributes.name;
302
+ }
303
+
304
+ // Check for color
305
+ if (Object.prototype.hasOwnProperty.call(attributes, 'color')) {
306
+ color = attributes.color;
307
+ }
308
+
309
+ // If both are not empty strings, use them.
310
+ if (name !== '' && color !== '') {
311
+ // Add name and color to the object
312
+ story.tagColors[name] = color;
313
+ }
314
+ });
315
+
316
+ // Return the parsed story
317
+ return story;
318
+ }
319
+
320
+ /**
321
+ * Try to escape meta-characters
322
+ *
323
+ * @public
324
+ * @static
325
+ * @function escapeMetacharacters
326
+ * @param {string} result - Text to parse
327
+ * @returns {string} Escaped characters
328
+ */
329
+ static escapeMetacharacters (result) {
330
+ // Replace any single backslash with two of them
331
+ result = result.replace(/\\/g, '\\');
332
+ // Double-escape escaped {
333
+ result = result.replace(/\\\{/g, '\\\\{');
334
+ // Double-escape escaped }
335
+ result = result.replace(/\\\}/g, '\\\\}');
336
+ // Double-escape escaped [
337
+ result = result.replace(/\\\[/g, '\\\\[');
338
+ // Double-escape escaped ]
339
+ result = result.replace(/\\\]/g, '\\\\]');
340
+
341
+ return result;
342
+ }
343
+ }