Parsing Twee: Part 3: Moving from Text to HTML

Parsing Twee

Twee is a human-readable format for saving the “code” of a Twine story. It predates Twine by a few years and has been a format used by many to create Twine-compatible projects for a decade.


Moving from Text to HTML

In the first part, Twee was explained as a series of passages. Serving as separate areas within the format, these contained code and information about the story itself. In the second part, story formats were examined.

A story format (in the Twine 2-era) is both presentation and processing. It contains CSS rules about how passages should be shown to users as well as additional JavaScript for how macro usage should be handled or anything else it might support.

In this last part, the HTML elements of Twine are explored.

Twine HTML

All story formats expected the data of a Twine story to be stored in a certain way. Part the work that went into designing Twine 2 was in creating a standard for how passages and information about the story were to be encoded into HTML for story formats to read.

tw-storydata

<tw-storydata 
  name="Twee Example" 
  startnode="1" 
  creator="Twine" 
  creator-version="2.3.2" 
  ifid="0DA3FEF1-9942-4665-B614-B16C37FEAC32" 
  zoom="1" 
  format="Snowman" 
  format-version="1.3.0" 
  options="" hidden>
  ...
</tw-storydata>

The primary storage area in Twine HTML is the tw-storydata element. It contains attributes that account for the metadata of the story.

The Twee specification (and most developers working in Twee) do not worry about all of the possible metadata. The following outlines all of the possible data from the Twee specification itself:

  • ifid: (string) Required. Maps to <tw-storydata ifid>.
  • format: (string) Optional. Maps to <tw-storydata format>.
  • format-version: (string) Optional. Maps to <tw-storydata format-version>.
  • start: (string) Optional. Maps to <tw-passagedata name> of the node whose pid matches <tw-storydata startnode>.
  • tag-colors: (object of tag(string):color(string) pairs) Optional. Pairs map to <tw-tag> nodes as <tw-tag name>:<tw-tag color>.
  • zoom: (decimal) Optional. Maps to <tw-storydata zoom>.

The only required metadata is the IFID. Everything else is optional. This means that it can help if the resulting HTML might be opened in the Twine editor one day, but most compilers (mine included) will either ignore or put some default values into the attributes.

tw-passagedata

<tw-passagedata pid="1" name="Start" tags="" position="300,198" size="100,100">It starts with one thing </tw-passagedata>

Each passage in Twee (or Twine) eventually exists as a tw-passagedata element. These encode the metadata of the passage as attributes.

As outlined in Twee specification, the only two required information areas for passages are their starting tokens and its name. The tag and metadata blocks are optional.

What about PID?

The PID data attribute is not included in the Twee specification. The reason for this is that Twine itself sets this data, and it is up to other tools to write their own numbers.

The PID, by itself, is not valuable information. Twine uses it to mark which passages were created in which order.

What about style and script tags?

In Twee, any passages with the style or script tags are combined together into those two categories. These are added as STYLE and SCRIPT elements before the tw-passagedata elements.


<tw-storydata 
  ...>
  <style 
    role="stylesheet" 
    id="twine-user-stylesheet" 
    type="text/twine-css">
  </style>
  <script 
    role="script" 
    id="twine-user-script" 
    type="text/twine-javascript">
  </script>
...
</tw-storydata>

Parsing Twee

In Extwee, the HTMLWriter.js object handles creating the HTML from Twee code. It first creates the tw-storydata element and then works on the STYLE and SCRIPT elements before moving on to the passages and their HTML.

// Build <tw-storydata>
this.storyData +=
'<tw-storydata name="' + this.story.name + '" ' +
'startnode="' + this.story.getStartingPassage() + '" ' +
'creator="' + this.story.creator + '" ' +
'creator-version="' + this.story.creatorVersion + '" ' +
'ifid="' + this.story.metadata.ifid + '" ' +
'zoom="' + this.story.metadata.zoom + '" ' +
'format="' + this.storyFormat.name + '" ' +
'format-version="' + this.storyFormat.version + '" ' +
'options hidden>\n';
// Build the STYLE
this.storyData += '<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">';
// Get any passages tagged with 'style'
let stylePassages = this.story.getStylePassages();
// Iterate through the collection and add to storyData
for(let content of stylePassages) {
this.storyData += content.text;
}
this.storyData += '</style>\n';
// Build the SCRIPT
this.storyData += '<script role="script" id="twine-user-script" type="text/twine-javascript">';
// Get any passages tagged with 'script'
let scriptPassages = this.story.getScriptPassages();
// Iterate through the collection and add to storyData
for(let content of scriptPassages) {
this.storyData += content.text;
}
this.storyData += '</script>\n';
// All the script data has been written.
// Delete all 'script'-tagged passages
this.story.deleteAllByTag("script");
// All the style data has been written.
// Delete all 'style'-tagged passages
this.story.deleteAllByTag("style");
view raw HTMLWriter.js hosted with ❤ by GitHub