extwee 2.0.5 → 2.2.0
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.
- package/.eslintrc.json +25 -25
- package/.github/FUNDING.yml +3 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/nodejs.yml +25 -24
- package/.travis.yml +13 -13
- package/CODE_OF_CONDUCT.md +82 -82
- package/LICENSE +21 -21
- package/README.md +173 -36
- package/SECURITY.md +12 -12
- package/babel.config.json +18 -22
- package/build/extwee +0 -0
- package/build/extwee.exe +0 -0
- package/build/extwee.web.min.js +2 -0
- package/build/extwee.web.min.js.LICENSE.txt +1 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +167 -0
- package/docs/_sidebar.md +19 -0
- package/docs/examples/dynamicPassages.md +28 -0
- package/docs/examples/jsonToTwee.md +23 -0
- package/docs/examples/twsToTwee.md +25 -0
- package/docs/formats/json.md +17 -0
- package/docs/formats/twee.md +13 -0
- package/docs/formats/twine1HTML.md +13 -0
- package/docs/formats/twine2ArchiveHTML.md +13 -0
- package/docs/formats/twine2HTML.md +13 -0
- package/docs/formats/tws.md +9 -0
- package/docs/index.html +26 -0
- package/docs/install/binaries.md +9 -0
- package/docs/install/npm.md +20 -0
- package/docs/install/npx.md +9 -0
- package/docs/objects/passage.md +47 -0
- package/docs/objects/story.md +70 -0
- package/docs/objects/storyformat.md +27 -0
- package/index.html +22 -0
- package/index.js +29 -31
- package/package.json +65 -58
- package/src/JSON/parse.js +128 -0
- package/src/Passage.js +298 -202
- package/src/Story.js +650 -489
- package/src/StoryFormat/parse.js +134 -0
- package/src/StoryFormat.js +259 -300
- package/src/TWS/parse.js +86 -0
- package/src/Twee/parse.js +157 -0
- package/src/Twine1HTML/compile.js +58 -0
- package/src/Twine1HTML/parse.js +134 -0
- package/src/Twine2ArchiveHTML/compile.js +36 -0
- package/src/Twine2ArchiveHTML/parse.js +49 -0
- package/src/Twine2HTML/compile.js +35 -0
- package/src/Twine2HTML/parse.js +348 -0
- package/src/extwee.js +206 -0
- package/test/CLI/CLI.test.js +49 -0
- package/test/CLI/files/example.json +1 -0
- package/test/CLI/files/example6.twee +22 -0
- package/test/{Roundtrip → CLI/files}/harlowe.js +2 -2
- package/test/CLI/{input.html → files/input.html} +47 -47
- package/test/CLI/files/output/test.twee +0 -0
- package/test/CLI/{tweeExample.twee → files/tweeExample.twee} +17 -17
- package/test/CLI/files/twine1/LICENSE.txt +32 -0
- package/test/CLI/files/twine1/code.js +5 -0
- package/test/CLI/files/twine1/engine.js +43 -0
- package/test/CLI/files/twine1/header.html +325 -0
- package/test/CLI/files/twine1Test.html +371 -0
- package/test/{HTMLParser → CLI/files}/twineExample.html +16 -15
- package/test/JSON/JSON.Parse.test.js +316 -0
- package/test/Passage.test.js +175 -104
- package/test/Roundtrip/{Example1.html → Files/Example1.html} +63 -63
- package/test/Roundtrip/Files/LICENSE +19 -0
- package/test/Roundtrip/Files/example1.twee +10 -0
- package/test/Roundtrip/{example2.twee → Files/example2.twee} +27 -18
- package/test/Roundtrip/Files/example4.twee +27 -0
- package/test/{StoryFormatParser → Roundtrip/Files}/harlowe.js +2 -2
- package/test/Roundtrip/{round.html → Files/round.html} +6 -7
- package/test/Roundtrip/Roundtrip.test.js +54 -0
- package/test/Story.test.js +624 -355
- package/test/StoryFormat/StoryFormat.Parse.test.js +91 -0
- package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/example.js +3 -3
- package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/example2.js +3 -3
- package/test/{CLI → StoryFormat/StoryFormatParser}/harlowe.js +2 -2
- package/test/StoryFormat.test.js +152 -152
- package/test/TWS/Parse.test.js +78 -0
- package/test/TWS/TWSParser/Example1.tws +150 -0
- package/test/TWS/TWSParser/Example5.tws +414 -0
- package/test/TWS/TWSParser/noscale.tws +0 -0
- package/test/TWS/TWSParser/nostory.tws +0 -0
- package/test/Twee/Twee.Parse.test.js +76 -0
- package/test/{TweeParser → Twee/TweeParser}/emptytags.twee +2 -2
- package/test/{TweeParser → Twee/TweeParser}/example.twee +32 -32
- package/test/{TweeParser → Twee/TweeParser}/missing.twee +19 -19
- package/test/{TweeParser → Twee/TweeParser}/multipleScriptPassages.twee +19 -19
- package/test/{TweeParser → Twee/TweeParser}/multipleStyleTag.twee +19 -19
- package/test/{TweeParser → Twee/TweeParser}/multipletags.twee +10 -10
- package/test/{TweeParser → Twee/TweeParser}/noTitle.twee +2 -2
- package/test/{TweeParser → Twee/TweeParser}/notes.twee +16 -16
- package/test/{TweeParser → Twee/TweeParser}/pasagemetadataerror.twee +2 -2
- package/test/{TweeParser → Twee/TweeParser}/scriptPassage.twee +16 -16
- package/test/{TweeParser → Twee/TweeParser}/singletag.twee +13 -13
- package/test/{TweeParser → Twee/TweeParser}/startMetadata.twee +14 -14
- package/test/{TweeParser → Twee/TweeParser}/storydataerror.twee +25 -25
- package/test/{TweeParser → Twee/TweeParser}/stylePassage.twee +16 -16
- package/test/{Story → Twee/TweeParser}/test.twee +25 -25
- package/test/Twine1HTML/Twine1HTML.Compile.test.js +180 -0
- package/test/Twine1HTML/Twine1HTML.Parse.test.js +183 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/LICENSE +674 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/engine.js +43 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/jquery.js +4 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/modernizr.js +4 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/engineTest.html +1 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/LICENSE +32 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/code.js +4 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/header.html +327 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test.html +0 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test1.html +6 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test2.html +6 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test3.html +43 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test4.html +372 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test5.html +372 -0
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Compile.test.js +35 -0
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +25 -0
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLCompiler/test1.html +6 -0
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLParser/test1.html +3 -0
- package/test/Twine2HTML/Twine2HTML.Compile.test.js +224 -0
- package/test/Twine2HTML/Twine2HTML.Parse.test.js +172 -0
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/creator.html +4 -5
- package/test/{CLI → Twine2HTML/Twine2HTMLCompiler}/example6.twee +15 -15
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/missingStoryTitle.twee +29 -29
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test11.html +1 -3
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test2.html +10 -11
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test3.html +1 -2
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test4.html +4 -5
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test6.html +1 -2
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/Example1.html +52 -52
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/Tags.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/lyingStartnode.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/lyingTagColors.html +48 -48
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingCreator.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingCreatorVersion.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingFormat.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingFormatVersion.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingIFID.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingName.html +33 -33
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPID.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPassageName.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPassageTags.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPosition.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingScript.html +14 -14
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingSize.html +35 -35
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingStartnode.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingStyle.html +14 -14
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingZoom.html +11 -11
- package/test/Twine2HTML/Twine2HTMLParser/twineExample.html +23 -0
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample2.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample3.html +15 -15
- package/web-index.js +29 -0
- package/webpack.config.js +12 -0
- package/bin/extwee.js +0 -47
- package/src/FileReader.js +0 -33
- package/src/HTMLParser.js +0 -343
- package/src/HTMLWriter.js +0 -231
- package/src/StoryFormatParser.js +0 -142
- package/src/TweeParser.js +0 -161
- package/src/TweeWriter.js +0 -98
- package/story-formats/chapbook-1.2.0/format.js +0 -1
- package/story-formats/chapbook-1.2.0/logo.svg +0 -1
- package/story-formats/harlowe-1.2.4/format.js +0 -1
- package/story-formats/harlowe-1.2.4/icon.svg +0 -78
- package/story-formats/harlowe-2.1.0/format.js +0 -2
- package/story-formats/harlowe-2.1.0/icon.svg +0 -78
- package/story-formats/harlowe-3.1.0/format.js +0 -3
- package/story-formats/harlowe-3.1.0/icon.svg +0 -78
- package/story-formats/paperthin-1.0.0/format.js +0 -1
- package/story-formats/paperthin-1.0.0/icon.svg +0 -5
- package/story-formats/snowman-1.4.0/format.js +0 -1
- package/story-formats/snowman-1.4.0/icon.svg +0 -436
- package/story-formats/snowman-2.0.2/format.js +0 -1
- package/story-formats/snowman-2.0.2/icon.svg +0 -436
- package/story-formats/sugarcube-1.0.35/LICENSE +0 -23
- package/story-formats/sugarcube-1.0.35/format.js +0 -1
- package/story-formats/sugarcube-1.0.35/icon.svg +0 -56
- package/story-formats/sugarcube-2.31.1/LICENSE +0 -22
- package/story-formats/sugarcube-2.31.1/format.js +0 -1
- package/story-formats/sugarcube-2.31.1/icon.svg +0 -56
- package/test/CLI/test2.html +0 -47
- package/test/CLI/twineExample.html +0 -15
- package/test/CLI.test.js +0 -30
- package/test/FileReader/t1.txt +0 -1
- package/test/FileReader.test.js +0 -14
- package/test/HTMLParser.test.js +0 -177
- package/test/HTMLWriter/example6.twee +0 -16
- package/test/HTMLWriter.test.js +0 -289
- package/test/Roundtrip/example1.twee +0 -21
- package/test/Roundtrip.test.js +0 -48
- package/test/Story/startmeta.twee +0 -29
- package/test/StoryFormatParser.test.js +0 -91
- package/test/TweeParser/test.twee +0 -25
- package/test/TweeParser.test.js +0 -79
- package/test/TweeWriter/test1.twee +0 -18
- package/test/TweeWriter/test3.twee +0 -12
- package/test/TweeWriter/test4.twee +0 -14
- package/test/TweeWriter/test5.twee +0 -20
- package/test/TweeWriter.test.js +0 -85
- /package/test/CLI/{test.twee → files/test.twee} +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/format.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/format_doublename.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingAuthor.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingDescription.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingImage.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingLicense.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingName.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingProofing.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingSource.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingURL.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingVersion.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/versionWrong.js +0 -0
- /package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/TestTags.html +0 -0
- /package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/tagColors.html +0 -0
package/web-index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { parse as parseTwee } from './src/Twee/parse.js';
|
|
2
|
+
import { parse as parseJSON } from './src/JSON/parse.js';
|
|
3
|
+
import { parse as parseStoryFormat } from './src/StoryFormat/parse.js';
|
|
4
|
+
import { parse as parseTwine1HTML } from './src/Twine1HTML/parse.js';
|
|
5
|
+
import { parse as parseTwine2HTML } from './src/Twine2HTML/parse.js';
|
|
6
|
+
import { parse as parseTwine2ArchiveHTML } from './src/Twine2ArchiveHTML/parse.js';
|
|
7
|
+
import { parse as parseTWS } from './src/TWS/parse.js';
|
|
8
|
+
import { compile as compileTwine1HTML } from './src/Twine1HTML/compile.js';
|
|
9
|
+
import { compile as compileTwine2HTML } from './src/Twine2HTML/compile.js';
|
|
10
|
+
import { compile as compileTwine2ArchiveHTML } from './src/Twine2ArchiveHTML/compile.js';
|
|
11
|
+
import Story from './src/Story.js';
|
|
12
|
+
import Passage from './src/Passage.js';
|
|
13
|
+
import StoryFormat from './src/StoryFormat.js';
|
|
14
|
+
|
|
15
|
+
window.Extwee = {
|
|
16
|
+
parseTwee,
|
|
17
|
+
parseJSON,
|
|
18
|
+
parseTWS,
|
|
19
|
+
parseStoryFormat,
|
|
20
|
+
parseTwine1HTML,
|
|
21
|
+
parseTwine2HTML,
|
|
22
|
+
parseTwine2ArchiveHTML,
|
|
23
|
+
compileTwine1HTML,
|
|
24
|
+
compileTwine2HTML,
|
|
25
|
+
compileTwine2ArchiveHTML,
|
|
26
|
+
Story,
|
|
27
|
+
Passage,
|
|
28
|
+
StoryFormat
|
|
29
|
+
};
|
package/bin/extwee.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @fileoverview CLI for Extwee
|
|
5
|
-
* @author Dan Cox
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Import everything
|
|
9
|
-
import Extwee from '../index.js';
|
|
10
|
-
// Import Commander
|
|
11
|
-
import { Command } from 'commander';
|
|
12
|
-
|
|
13
|
-
// Create a new Command
|
|
14
|
-
const program = new Command();
|
|
15
|
-
|
|
16
|
-
program
|
|
17
|
-
.name('extwee')
|
|
18
|
-
.version('2.0.5')
|
|
19
|
-
.option('-c', 'From Twee into HTML')
|
|
20
|
-
.option('-d', 'From HTML into Twee')
|
|
21
|
-
.option('-s <storyformat>', 'Path to storyformat')
|
|
22
|
-
.option('-i <inputFile>', 'Path to input file')
|
|
23
|
-
.option('-o <outputFile>', 'Path to output file');
|
|
24
|
-
|
|
25
|
-
// Parse the passed arguments
|
|
26
|
-
program.parse(process.argv);
|
|
27
|
-
|
|
28
|
-
// Create object of passed arguments parsed by Commander
|
|
29
|
-
const options = program.opts();
|
|
30
|
-
|
|
31
|
-
// Decompile branch
|
|
32
|
-
if(options.d === true) {
|
|
33
|
-
const inputHTML = Extwee.readFile(options.i);
|
|
34
|
-
const storyObject = Extwee.parseHTML(inputHTML);
|
|
35
|
-
Extwee.writeTwee(storyObject, options.o);
|
|
36
|
-
process.exit();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Compile branch
|
|
40
|
-
if(options.c === true) {
|
|
41
|
-
const inputTwee = Extwee.readFile(options.i);
|
|
42
|
-
const story = Extwee.parseTwee(inputTwee);
|
|
43
|
-
const inputStoryFormat = Extwee.readFile(options.s);
|
|
44
|
-
const parsedStoryFormat = Extwee.parseStoryFormat(inputStoryFormat);
|
|
45
|
-
Extwee.writeHTML(options.o, story, parsedStoryFormat);
|
|
46
|
-
process.exit();
|
|
47
|
-
}
|
package/src/FileReader.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
/**
|
|
3
|
-
* @class FileReader
|
|
4
|
-
* @module FileReader
|
|
5
|
-
*/
|
|
6
|
-
export default class FileReader {
|
|
7
|
-
/**
|
|
8
|
-
* Read a text file
|
|
9
|
-
*
|
|
10
|
-
* @static
|
|
11
|
-
* @public
|
|
12
|
-
* @function readFile
|
|
13
|
-
* @memberof FileReader
|
|
14
|
-
* @param {string} file - Path of file to read
|
|
15
|
-
* @returns {string} Content of file
|
|
16
|
-
*/
|
|
17
|
-
static read (file) {
|
|
18
|
-
// Setup default value
|
|
19
|
-
let contents = '';
|
|
20
|
-
|
|
21
|
-
// Attempt to find the file
|
|
22
|
-
if (fs.existsSync(file)) {
|
|
23
|
-
// The file exists.
|
|
24
|
-
contents = fs.readFileSync(file, 'utf8');
|
|
25
|
-
} else {
|
|
26
|
-
// Throw error if file does not exist
|
|
27
|
-
throw new Error('Error: File not found!');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Return default or updated values
|
|
31
|
-
return contents;
|
|
32
|
-
}
|
|
33
|
-
}
|
package/src/HTMLParser.js
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
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
|
-
}
|
package/src/HTMLWriter.js
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @external Story
|
|
3
|
-
* @see Story.js
|
|
4
|
-
* @external StoryFormat
|
|
5
|
-
* @see StoryFormat.js
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import Story from './Story.js';
|
|
10
|
-
import StoryFormat from './StoryFormat.js';
|
|
11
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @class HTMLWriter
|
|
15
|
-
* @module HTMLWriter
|
|
16
|
-
*/
|
|
17
|
-
export default class HTMLWriter {
|
|
18
|
-
/**
|
|
19
|
-
* Write story to file using story format and adding any CSS and JS
|
|
20
|
-
*
|
|
21
|
-
* @public
|
|
22
|
-
* @static
|
|
23
|
-
* @function writeFile
|
|
24
|
-
* @param {string} file - File to write
|
|
25
|
-
* @param {Story} story - Story object to write
|
|
26
|
-
* @param {StoryFormat} storyFormat - StoryFormat to write
|
|
27
|
-
*/
|
|
28
|
-
static write (file, story, storyFormat) {
|
|
29
|
-
if (!(story instanceof Story)) {
|
|
30
|
-
throw new Error('Error: story must be a Story object!');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!(storyFormat instanceof StoryFormat)) {
|
|
34
|
-
throw new Error('storyFormat must be a StoryFormat object!');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
let outputContents = '';
|
|
38
|
-
let storyData = '';
|
|
39
|
-
|
|
40
|
-
// Look for StoryTitle
|
|
41
|
-
const storyTitle = story.getPassageByName('StoryTitle');
|
|
42
|
-
|
|
43
|
-
// Does the passage exist?
|
|
44
|
-
if (storyTitle != null) {
|
|
45
|
-
// Always overwrite any existing name with StoryTitle (per spec)
|
|
46
|
-
story.name = storyTitle.text;
|
|
47
|
-
|
|
48
|
-
// Use story.name for name.
|
|
49
|
-
storyData += `<tw-storydata name="${story.name}"`;
|
|
50
|
-
} else {
|
|
51
|
-
throw new Error("'name' is required attribute. (Add StoryTitle to story.)");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Does start exist?
|
|
55
|
-
if (story.start !== '') {
|
|
56
|
-
// Try to get starting passage
|
|
57
|
-
const startingPassage = story.getPassageByName(story.start);
|
|
58
|
-
// Does it exist currently?
|
|
59
|
-
if (startingPassage !== null) {
|
|
60
|
-
// Add the starting passage
|
|
61
|
-
storyData += ` startnode="${startingPassage.pid}"`;
|
|
62
|
-
} else {
|
|
63
|
-
// Throw error if no starting passage exists
|
|
64
|
-
throw new Error('Starting passage not found');
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
// Throw error if no starting passage exists
|
|
68
|
-
throw new Error('No starting passage found!');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Defaults to 'extwee' if missing.
|
|
72
|
-
storyData += ` creator="${story.creator}"`;
|
|
73
|
-
|
|
74
|
-
// Default to extwee version.
|
|
75
|
-
storyData += ` creator-version="${story.creatorVersion}"`;
|
|
76
|
-
|
|
77
|
-
// Check if IFID exists.
|
|
78
|
-
if (story.IFID !== '') {
|
|
79
|
-
// Write the existing IFID
|
|
80
|
-
storyData += ` ifid="${story.IFID}"`;
|
|
81
|
-
} else {
|
|
82
|
-
// Generate a new IFID
|
|
83
|
-
// Twine 2 uses v4 (random) UUIDs, using only capital letters
|
|
84
|
-
storyData += ` ifid="${uuidv4().toUpperCase()}"`;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Write existing or default value.
|
|
88
|
-
storyData += ` zoom="${story.zoom}"`;
|
|
89
|
-
|
|
90
|
-
// Write existing or default value.
|
|
91
|
-
storyData += ` format="${storyFormat.name}"`;
|
|
92
|
-
|
|
93
|
-
// Write existing or default value.
|
|
94
|
-
storyData += ` format-version="${storyFormat.version}"`;
|
|
95
|
-
|
|
96
|
-
// Add the default.
|
|
97
|
-
storyData += ' options hidden>\n';
|
|
98
|
-
|
|
99
|
-
// Start the STYLE.
|
|
100
|
-
storyData += '\t<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">';
|
|
101
|
-
|
|
102
|
-
// Get stylesheet passages
|
|
103
|
-
const stylesheetPassages = story.getPassagesByTag('stylesheet');
|
|
104
|
-
|
|
105
|
-
// Concatenate passages
|
|
106
|
-
stylesheetPassages.forEach((passage) => {
|
|
107
|
-
// Add text of passages
|
|
108
|
-
storyData += passage.text;
|
|
109
|
-
// Remove from story
|
|
110
|
-
story.removePassage(passage);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Close the STYLE
|
|
114
|
-
storyData += '</style>\n';
|
|
115
|
-
|
|
116
|
-
// Start the SCRIPT
|
|
117
|
-
storyData += '\t<script role="script" id="twine-user-script" type="text/twine-javascript">';
|
|
118
|
-
|
|
119
|
-
// Get stylesheet passages
|
|
120
|
-
const scriptPassages = story.getPassagesByTag('script');
|
|
121
|
-
|
|
122
|
-
// Concatenate passages
|
|
123
|
-
scriptPassages.forEach((passage) => {
|
|
124
|
-
// Add text of passages
|
|
125
|
-
storyData += passage.text;
|
|
126
|
-
// Remove from story
|
|
127
|
-
story.removePassage(passage);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Close SCRIPT
|
|
131
|
-
storyData += '</script>\n';
|
|
132
|
-
|
|
133
|
-
// Build the passages
|
|
134
|
-
story.forEach((passage) => {
|
|
135
|
-
// Start the passage element
|
|
136
|
-
storyData += '\t<tw-passagedata';
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* pid: (string) Required.
|
|
140
|
-
* The Passage ID (PID).
|
|
141
|
-
*/
|
|
142
|
-
storyData += ` pid="${passage.pid}"`;
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* name: (string) Required.
|
|
146
|
-
* The name of the passage.
|
|
147
|
-
*/
|
|
148
|
-
storyData += ` name="${passage.name}"`;
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* tags: (string) Optional.
|
|
152
|
-
* Any tags for the passage separated by spaces.
|
|
153
|
-
*/
|
|
154
|
-
if (passage.tags.length > 1) {
|
|
155
|
-
storyData += ` tags="${passage.tags.join(' ')}" `;
|
|
156
|
-
} else if (passage.tags.length === 1) {
|
|
157
|
-
storyData += ` tags="${passage.tags[0]}" `;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* position: (string) Optional.
|
|
162
|
-
* Comma-separated X and Y position of the upper-left of the passage
|
|
163
|
-
* when viewed within the Twine 2 editor.
|
|
164
|
-
*/
|
|
165
|
-
if (Object.prototype.hasOwnProperty.call(passage.metadata, 'position')) {
|
|
166
|
-
storyData += ` position="${passage.metadata.position}" `;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* size: (string) Optional.
|
|
171
|
-
* Comma-separated width and height of the passage
|
|
172
|
-
* when viewed within the Twine 2 editor.
|
|
173
|
-
*/
|
|
174
|
-
if (Object.prototype.hasOwnProperty.call(passage.metadata, 'size')) {
|
|
175
|
-
storyData += `size="${passage.metadata.size}" `;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
storyData += `>${HTMLWriter.escape(passage.text)}</tw-passagedata>\n`;
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
storyData += '</tw-storydata>';
|
|
182
|
-
|
|
183
|
-
// Replace the story name in the source file
|
|
184
|
-
storyFormat.source = storyFormat.source.replaceAll(/{{STORY_NAME}}/gm, story.name);
|
|
185
|
-
|
|
186
|
-
// Replace the story data
|
|
187
|
-
storyFormat.source = storyFormat.source.replaceAll(/{{STORY_DATA}}/gm, storyData);
|
|
188
|
-
|
|
189
|
-
// Combine everything together.
|
|
190
|
-
outputContents += storyFormat.source;
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
// Try to write.
|
|
194
|
-
fs.writeFileSync(file, outputContents);
|
|
195
|
-
} catch (event) {
|
|
196
|
-
// Throw error
|
|
197
|
-
throw new Error('Error: Cannot write HTML file!');
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Escape HTML characters
|
|
203
|
-
*
|
|
204
|
-
* @public
|
|
205
|
-
* @static
|
|
206
|
-
* @function escape
|
|
207
|
-
* @param {string} text - Text to escape
|
|
208
|
-
* @returns {string} Escaped text
|
|
209
|
-
*/
|
|
210
|
-
static escape (text) {
|
|
211
|
-
// Throw error if text is not a string
|
|
212
|
-
if (Object.prototype.toString.call(text) !== '[object String]') {
|
|
213
|
-
throw new Error('Text argument is not a String');
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const rules = [
|
|
217
|
-
['&', '&'],
|
|
218
|
-
['<', '<'],
|
|
219
|
-
['>', '>'],
|
|
220
|
-
['"', '"'],
|
|
221
|
-
["'", '''],
|
|
222
|
-
['`', '`']
|
|
223
|
-
];
|
|
224
|
-
|
|
225
|
-
rules.forEach(([rule, template]) => {
|
|
226
|
-
text = text.replaceAll(rule, template);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
return text;
|
|
230
|
-
}
|
|
231
|
-
}
|