diff --git a/core/modules/parsers/wikiparser/rules/block/classblock.js b/core/modules/parsers/wikiparser/rules/block/classblock.js
new file mode 100644
index 000000000..4b0df5c4b
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/block/classblock.js
@@ -0,0 +1,49 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/block/classblock.js
+type: application/javascript
+module-type: wikiblockrule
+
+Wiki text block rule for assigning classes to paragraphs and other blocks. For example:
+
+{{{
+{{myClass{
+This paragraph will have the CSS class `myClass`.
+
+* The `
` around this list will also have the class `myClass`
+* List item 2
+
+ }}}
+}}}
+
+Note that the opening and closing braces both must be immediately followed by a newline.
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "classblock";
+
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match
+ this.matchRegExp = /\{\{([^\{\r\n]+)\{\r?\n/mg;
+};
+
+exports.parse = function() {
+ var reEndString = "(\\}\\}\\}$(?:\\r?\\n)?)";
+ // Get the class
+ var classString = this.match[1];
+ // Move past the match
+ this.parser.pos = this.matchRegExp.lastIndex;
+ // Parse the body
+ var tree = this.parser.parseBlocks(reEndString);
+ for(var t=0; t 1) {
+ $tw.utils.addAttributeToParseTreeNode(last.element,"colspan",colSpanCount);
+ colSpanCount = 1;
+ }
+ }
+ // Move to just before the `|` terminating the cell
+ this.parser.pos = cellRegExp.lastIndex - 1;
+ } else if(cellMatch[1] === ">") {
+ // Colspan
+ colSpanCount++;
+ // Move to just before the `|` terminating the cell
+ this.parser.pos = cellRegExp.lastIndex - 1;
+ } else if(cellMatch[2]) {
+ // End of row
+ if(prevCell && colSpanCount > 1) {
+ $tw.utils.addAttributeToParseTreeNode(prevCell,"colspan",colSpanCount);
+ }
+ this.parser.pos = cellRegExp.lastIndex - 1;
+ break;
+ } else {
+ // For ordinary cells, step beyond the opening `|`
+ this.parser.pos++;
+ // Look for a space at the start of the cell
+ var spaceLeft = false,
+ chr = this.parser.source.substr(this.parser.pos,1);
+ while(chr === " ") {
+ spaceLeft = true;
+ this.parser.pos++;
+ chr = this.parser.source.substr(this.parser.pos,1);
+ }
+ // Check whether this is a heading cell
+ var cell;
+ if(chr === "!") {
+ this.parser.pos++;
+ cell = {type: "element", tag: "th", children: []};
+ } else {
+ cell = {type: "element", tag: "td", children: []};
+ }
+ tree.push(cell);
+ // Record information about this cell
+ prevCell = cell;
+ prevColumns[col] = {rowSpanCount:1,element:cell};
+ // Check for a colspan
+ if(colSpanCount > 1) {
+ $tw.utils.addAttributeToParseTreeNode(cell,"colspan",colSpanCount);
+ colSpanCount = 1;
+ }
+ // Parse the cell
+ cell.children = this.parser.parseRun(cellTermRegExp,{eatTerminator: true});
+ // Set the alignment for the cell
+ if(cellMatch[1].substr(cellMatch[1].length-1,1) === " ") { // spaceRight
+ $tw.utils.addAttributeToParseTreeNode(cell,"align",spaceLeft ? "center" : "left");
+ } else if(spaceLeft) {
+ $tw.utils.addAttributeToParseTreeNode(cell,"align","right");
+ }
+ // Move back to the closing `|`
+ this.parser.pos--;
+ }
+ col++;
+ cellRegExp.lastIndex = this.parser.pos;
+ cellMatch = cellRegExp.exec(this.parser.source);
+ }
+ return tree;
+};
+
+exports.parse = function() {
+ var rowContainerTypes = {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
+ table = {type: "element", tag: "table", children: []},
+ rowRegExp = /^\|([^\n]*)\|([fhck]?)\r?\n/mg,
+ rowTermRegExp = /(\|(?:[fhck]?)\r?\n)/mg,
+ prevColumns = [],
+ currRowType,
+ rowContainer,
+ rowCount = 0;
+ // Match the row
+ rowRegExp.lastIndex = this.parser.pos;
+ var rowMatch = rowRegExp.exec(this.parser.source);
+ while(rowMatch && rowMatch.index === this.parser.pos) {
+ var rowType = rowMatch[2];
+ // Check if it is a class assignment
+ if(rowType === "k") {
+ $tw.utils.addClassToParseTreeNode(table,rowMatch[1]);
+ this.parser.pos = rowMatch.index + rowMatch[0].length;
+ } else {
+ // Otherwise, create a new row if this one is of a different type
+ if(rowType != currRowType) {
+ rowContainer = {type: "element", tag: rowContainerTypes[rowType], children: []};
+ table.children.push(rowContainer);
+ currRowType = rowType;
+ }
+ // Is this a caption row?
+ if(currRowType === "c") {
+ // If so, move past the opening `|` of the row
+ this.parser.pos++;
+ // Move the caption to the first row if it isn't already
+ if(table.children.length !== 1) {
+ table.children.pop(); // Take rowContainer out of the children array
+ table.children.splice(0,0,rowContainer); // Insert it at the bottom
+ }
+ // Set the alignment - TODO: figure out why TW did this
+// rowContainer.attributes.align = rowCount === 0 ? "top" : "bottom";
+ // Parse the caption
+ rowContainer.children = this.parser.parseRun(rowTermRegExp,{eatTerminator: true});
+ } else {
+ // Create the row
+ var theRow = {type: "element", tag: "tr", children: []};
+ $tw.utils.addClassToParseTreeNode(theRow,rowCount%2 ? "oddRow" : "evenRow");
+ rowContainer.children.push(theRow);
+ // Process the row
+ theRow.children = processRow.call(this,prevColumns);
+ this.parser.pos = rowMatch.index + rowMatch[0].length;
+ // Increment the row count
+ rowCount++;
+ }
+ }
+ rowMatch = rowRegExp.exec(this.parser.source);
+ }
+ return [table];
+};
+
+})();
diff --git a/core/modules/parsers/wikiparser/rules/run/classrun.js b/core/modules/parsers/wikiparser/rules/run/classrun.js
new file mode 100644
index 000000000..0151a407f
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/run/classrun.js
@@ -0,0 +1,51 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/run/classrun.js
+type: application/javascript
+module-type: wikirunrule
+
+Wiki text run rule for assigning classes to runs of text. For example:
+
+{{{
+{{myClass{This text will have the CSS class `myClass`.
+
+* This will not be recognised as a list
+
+List item 2}}}
+}}}
+
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "classrun";
+
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match
+ this.matchRegExp = /\{\{([^\{\r\n]+)\{/mg;
+};
+
+exports.parse = function() {
+ var reEnd = /(\}\}\})/g;
+ // Get the class
+ var classString = this.match[1];
+ // Move past the match
+ this.parser.pos = this.matchRegExp.lastIndex;
+ // Parse the run up to the terminator
+ var tree = this.parser.parseRun(reEnd,{eatTerminator: true});
+ // Return the classed span
+ return [{
+ type: "element",
+ tag: "span",
+ attributes: {
+ "class": {type: "string", value: classString}
+ },
+ children: tree
+ }];
+};
+
+})();
diff --git a/core/modules/parsers/wikiparser/rules/run/coderun.js b/core/modules/parsers/wikiparser/rules/run/coderun.js
new file mode 100644
index 000000000..513178ee4
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/run/coderun.js
@@ -0,0 +1,58 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/run/coderun.js
+type: application/javascript
+module-type: wikirunrule
+
+Wiki text run rule for code runs. For example:
+
+{{{
+ This is a {{{code run}}} and `so is this`.
+}}}
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "coderun";
+
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match
+ this.matchRegExp = /(\{\{\{)|(`)/mg;
+};
+
+exports.parse = function() {
+ // Move past the match
+ this.parser.pos = this.matchRegExp.lastIndex;
+ var reEnd;
+ if(this.match[0] === "{{{") {
+ reEnd = /(\}\}\})/mg;
+ } else {
+ reEnd = /(`)/mg;
+ }
+ // Look for the end marker
+ reEnd.lastIndex = this.parser.pos;
+ var match = reEnd.exec(this.parser.source),
+ text;
+ // Process the text
+ if(match) {
+ text = this.parser.source.substring(this.parser.pos,match.index);
+ this.parser.pos = match.index + match[0].length;
+ } else {
+ text = this.parser.source.substr(this.parser.pos);
+ this.parser.pos = this.parser.sourceLength;
+ }
+ return [{
+ type: "element",
+ tag: "code",
+ children: [{
+ type: "text",
+ text: text
+ }]
+ }];
+};
+
+})();
diff --git a/core/modules/parsers/wikiparser/rules/run/comment.js b/core/modules/parsers/wikiparser/rules/run/comment.js
new file mode 100644
index 000000000..5eae8a3cc
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/run/comment.js
@@ -0,0 +1,34 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/run/comment.js
+type: application/javascript
+module-type: wikirunrule
+
+Wiki text run rule for HTML comments. For example:
+
+{{{
+
+}}}
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "comment";
+
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match - HTML comment regexp by Stephen Ostermiller, http://ostermiller.org/findhtmlcomment.html
+ this.matchRegExp = /\/mg;
+};
+
+exports.parse = function(match,isBlock) {
+ // Move past the match
+ this.parser.pos = this.matchRegExp.lastIndex;
+ // Don't return any elements
+ return [];
+};
+
+})();
diff --git a/core/modules/parsers/wikiparser/rules/run/dash.js b/core/modules/parsers/wikiparser/rules/run/dash.js
new file mode 100644
index 000000000..60ddb3e9e
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/run/dash.js
@@ -0,0 +1,41 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/run/dash.js
+type: application/javascript
+module-type: wikirunrule
+
+Wiki text run rule for dashes. For example:
+
+{{{
+This is an en-dash: --
+
+This is an em-dash: ---
+}}}
+
+Dashes must be followed by whitespace in order to be distinguished from strikethrough notation (`--strikethrough--`).
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "dash";
+
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match
+ this.matchRegExp = /-{2,3}(?=\s)/mg;
+};
+
+exports.parse = function() {
+ // Move past the match
+ this.parser.pos = this.matchRegExp.lastIndex;
+ var dash = this.match[0].length === 2 ? "–" : "—";
+ return [{
+ type: "entity",
+ entity: dash
+ }];
+};
+
+})();
diff --git a/core/modules/parsers/wikiparser/rules/run/emphasis.js b/core/modules/parsers/wikiparser/rules/run/emphasis.js
new file mode 100644
index 000000000..939473a7f
--- /dev/null
+++ b/core/modules/parsers/wikiparser/rules/run/emphasis.js
@@ -0,0 +1,78 @@
+/*\
+title: $:/core/modules/parsers/wikiparser/rules/run/emphasis.js
+type: application/javascript
+module-type: wikirunrule
+
+Wiki text run rule for emphasis. For example:
+
+{{{
+ This is ''bold'' text
+
+ This is //italic// text
+
+ This is __underlined__ text
+
+ This is ^^superscript^^ text
+
+ This is ,,subscript,, text
+
+ This is ~~strikethrough~~ text
+}}}
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports.name = "emphasis";
+
+exports.init = function(parser) {
+ this.parser = parser;
+ // Regexp to match
+ this.matchRegExp = /''|\/\/|__|\^\^|,,|~~/mg;
+};
+
+exports.parse = function() {
+ // Move past the match
+ this.parser.pos = this.matchRegExp.lastIndex;
+ // Figure out which element and closing regexp to use
+ var tag,reEnd;
+ switch(this.match[0]) {
+ case "''": // Bold
+ tag = "strong";
+ reEnd = /''/mg;
+ break;
+ case "//": // Italics
+ tag = "em";
+ reEnd = /\/\//mg;
+ break;
+ case "__": // Underline
+ tag = "u";
+ reEnd = /__/mg;
+ break;
+ case "^^": // Superscript
+ tag = "sup";
+ reEnd = /\^\^/mg;
+ break;
+ case ",,": // Subscript
+ tag = "sub";
+ reEnd = /,,/mg;
+ break;
+ case "~~": // Strikethrough
+ tag = "strike";
+ reEnd = /~~/mg;
+ break;
+ }
+ // Parse the run including the terminator
+ var tree = this.parser.parseRun(reEnd,{eatTerminator: true});
+ // Return the classed span
+ return [{
+ type: "element",
+ tag: tag,
+ children: tree
+ }];
+};
+
+})();